동네 가게는 여전히 신용 장부로 운영된다: Gemma와 TabPFN을 이용해 인터넷 없이 구현하다
요약
본 글은 인도 등지에서 흔히 볼 수 있는 동네 가게의 '신용 장부(udhaar khata)' 관리 문제를 해결하기 위한 AI 백오피스 시스템을 소개합니다. 이 시스템은 인터넷 연결 없이도 작동하며, 음성 및 텍스트 입력을 통해 거래 내역을 기록하고 부채 현황, 재고 예측 등 복잡한 질문에 답변할 수 있습니다.
핵심 포인트
- 인터넷 연결이 필요 없는 오프라인 AI 백오피스 솔루션입니다.
- 음성/텍스트 입력으로 다양한 언어의 거래를 자동으로 기록합니다.
- 단순 부채 조회 외에도 연체 가능성 및 추가 신용 한도까지 분석합니다.
이 글은 Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle에 제출하는 내용입니다.
카운터 뒤의 장부
멕시코에서는 이를 _fiado_라고 부릅니다. 필리핀 사리사리 가게에서는 _utang_이라고 하며, _listahan_에 기록합니다. 인도네시아의 와룽(warung)에서는 hutang 또는 _bon_입니다. 인디아에서는 _udhaar_라고 하며, _khata_에 보관됩니다. 단어는 다르지만 장부는 같습니다: 동네 가게는 이웃이 물건을 먼저 가져가고 나중에 지불하게 하며, 이를 손으로 적습니다.
인도의 모든 키라나(kirana) 가게에는 이런 장부가 있습니다. 보통 빨간색의 길고 얇은 공책에 _Patil Kaku_와 Sharma Ji 같은 이름들과 계속 늘어나는 숫자들이 가득합니다. 이것이 바로 udhaar khata, 즉 신용 장부입니다. 이웃 절반가구가 여기서 물건을 사서 월말이나 그 다음 달, 또는 또 그다음 달에 지불합니다.
아우랑가바드(Aurangabad)의 나르마다 일반 상점(Narmada General Store)에서 저는 주인들이 일일 거래를 추적하기 위해 모든 주문을 공책에 신중하게 적는 것을 발견했습니다. 이러한 수기 기록을 유지하는 모습을 보면서, 가게 주인이 얼마나 많은 수작업 기록 관리에 의존하는지, 그리고 이렇게 많은 항목들을 관리하는 것이 얼마나 쉽게 시간이 많이 걸리는 작업이 될 수 있는지 깨달았습니다.
이 문제를 해결하려는 모든 앱들은 주인들에게 기름 묻은 손으로 저녁 바쁜 시간에 _타이핑_을 하라고 요구합니다. 또한 고객들의 부채를 다른 사람의 서버에 업로드하도록 요청하기도 합니다. 그래서 대부분의 가게들은 여전히 장부를 사용합니다.
제가 만든 것
Udhaar는 가게 자체 노트북으로 구동되는 동네 가게용 AI 백오피스입니다. 이 시스템은 하나의 상자(box)로 되어 있습니다. 마라티어, 힌디어, 영어, 스페인어, 필리핀어 또는 인도네시아어로 말하거나 타이핑할 수 있으며, 실제로 카운터에서 사람들이 사용하는 혼합 언어도 가능합니다.
_
세 가지 항목 모두 같은 종류의 거래 내역으로 동일한 장부에 기록됩니다. 저는 이 시스템을 인도 오랑가바드(Aurangabad)의 실제 가게를 기반으로 구축했지만, 여기에 있는 내용 중 인도에서만 사용되는 것은 없습니다.
노트에 적는 방식대로 말하면, 그것이 기록됩니다:
"Patil kaku, don kilo sakhar ani ek litre tel, 210 udhaar" → Patil Kaku에게 ₹210의 외상값: 설탕 2kg, 기름 1L.
질문을 하면, 가게 자체 기록을 바탕으로 답변합니다:
"Bhosale dada kiti baki?" → Bhosale Dada는 ₹11,396를 빚지고 있습니다. 마지막 결제일은 9월 9일입니다. 연체 가능성: 99%.
"How much sugar next week?" → 약 43kg (평소 61kg). 디왈리(Diwali) 전 2주 동안: 154kg, +27%.
그리고 어떤 노트도 할 수 없는, 모든 가게 주인이 실제로 고민하는 질문에 답변합니다:
"How much more udhaar can I give Shinde Mama?" → 최대 ₹4,750까지 더 주는 것이 안전합니다. 그가 한 달 동안 갚은 가장 많은 금액은 ₹7,402입니다.
"Bhosale dada ko aur udhaar de sakte?" → 먼저 회수하세요. 그는 ₹11,396를 빚지고 있으며 연체될 가능성이 99%입니다.
"What if Gaikwad Sir doesn't pay for 3 weeks?" → 그의 연체 가능성은 X%에서 Y%로 변합니다.
모든 거래 내역에는 조용한 가격 확인(price check) 기능도 추가됩니다. 마이크가 설탕 2kg에 대해 _"920"_을 들으면, Udhaar는 다음과 같이 질문합니다: "설탕 2kg에 ₹920요? 이 가게는 보통 약 ₹92를 청구합니다." 한 번의 터치로 수정됩니다. 외상 장부에서 잘못된 추가 '0'은 결코 빌리지 않은 돈을 옆집 사람에게 요구하는 것과 같습니다.
매일 아침, 주인에게 세 줄짜리 **브리핑(briefing)**을 제공합니다:
- 오늘 누구에게서 무엇을 회수해야 하고, 그 이유는 무엇인지;
- 축제 전에 무엇을 주문해야 하고, 얼마나 많은지;
- 어제가 평소와 비교했을 때 어땠는지.
각 줄에는 한 번의 터치로 실행할 수 있는 액션이 있습니다: Gemma가 정중한 WhatsApp 알림 초안 작성, 또는 예측을 바탕으로 만들어진 도매업체 주문 목록입니다.
모든 위험 점수에는 _"34일 동안 결제 없음. 평소 43일에 결제함."_과 같이 명확한 말로 그 이유가 제시됩니다. 가게 주인은 논쟁할 수 없는 숫자를 신뢰하지 않을 것입니다.
누가 비용을 지불하는가, 그리고 왜 오픈 모델이 비즈니스를 만드는가
누가 비용을 지불하는가: 신용 거래로 50~300명의 고객을 보유한 동네 가게 주인입니다. 저는 이 서비스를 월 ₹149에 책정할 것이며, 이는 한 고객의 월 설탕값보다 적습니다. 샘플 가게는 '우다르(udhaar)'로 ₹37,000이 미수금 상태입니다. 만약 이 시스템이 연체된 계좌 하나를 일주일 먼저 회수하거나, 추가적인 0을 막아준다면, 한 달 치 비용은 충당됩니다.
이 가격은 AI가 항목별로 저에게 아무런 비용을 발생시키지 않을 때만 유효합니다. 바쁜 카운터는 하루에 150~300줄의 기록을 남기며, 모든 줄은 음성-텍스트 변환(speech-to-text), 언어 모델(language model) 및 가격 확인 과정을 거칩니다. 따라서:
- 비용. Whisper와 Gemma 3 4B를 제 일상 노트북(i5에 RTX 2050, VRAM 4 GB 탑재)에서 구동합니다. 항목 하나당 비용은 전기세입니다. 만약 호출당 API 방식을 사용한다면, AI 청구서가 고객 수만큼 늘어날 것이고, 바쁜 가게에서는 ₹149 구독료로는 감당할 수 없습니다.
- 개인 정보 보호. '카타(khata)'는 누가 돈을 빚졌는지 목록입니다. 이 기록은 가게 주인이 한 번도 들어본 적 없는 서버에 존재해서는 안 됩니다. 장부는 가게 노트북의 SQLite 파일 하나이며, 음성 및 파싱 과정은 절대 외부로 나가지 않습니다.
- 인터넷이 작동하지 않을 때에도 작동합니다. 비가 올 때마다 가게 연결망이 끊깁니다. '우다르'는 영향을 받지 않습니다.
오픈(open)이 폐쇄형보다 실제로 우위를 점하는 곳
제가 예상하지 못했던 이유: Gemma가 무료로 구동되기 때문에 두 번 실행할 수 있습니다. 모든 항목은 빠른 규칙에 의해 즉시 작성되고, 그 후 Gemma가 백그라운드에서 같은 줄을 다시 읽어보고 불일치하면 조용히 수정합니다. 유료 API를 사용했다면 이런 설계를 절대 하지 않았을 것입니다. 왜냐하면 모든 항목을 재검사하는 데 비용을 지불해야 하기 때문입니다. 로컬 모델을 사용하면 모든 줄에 대한 두 번째 의견이 아무 비용도 들지 않으며, 이것이 카운터를 빠르면서도 정확하게 만드는 비결입니다.
두 번째 이유: 측정하고 교체할 수 있었습니다. 저는 자체 GPU에서 Gemma 3 1B와 4B로 동일한 평가를 실행했고, 1B가 제 정규 표현식 규칙보다 성능이 떨어지는 것을 확인하여 직접 절충안을 만들었습니다(아래 참조). 또한 Ollama를 통해 JSON 스키마로 Gemma의 출력을 제한했기 때문에 유효한 항목 외에는 절대 반환할 수 없습니다.
솔직히 말해서, 폐쇄된 환경에서는 승리했을 것입니다. 최첨단 API라면 제가 제외시킨 스페인어/필리피노어/인도네시아어 줄을 제 노트북보다 더 잘, 그리고 더 빠르게 구문 분석할 수 있을 것입니다. 예측의 경우 저는 정확도가 더 높은 Prior Labs가 호스팅하는 TabPFN-3.5를 사용합니다. 이 모델은 오직 _미지급 금액, 마지막 결제일로부터 경과된 일수, 요일, 축제까지 남은 일수_와 같은 익명의 숫자만 볼 뿐입니다. 이름이나 품목은 없습니다. 네트워크 연결이 전혀 필요 없는 가게는 동일한 코드로 TabPFN의 **로컬에서 구동되는 오픈 웨이트(open weights)**로 전환할 수 있습니다.
작동 방식
speak / type ─► faster-whisper ─► 항목 또는 질문?
│ │
항목: 규칙 적용 후, Gemma가 재검사 질문: 계획 (규칙 또는 Gemma)
...
모델이 라우팅하고, 데이터베이스가 답변합니다. 질문의 경우, Gemma는 _무엇_을 묻고 있는지만 결정합니다. 예를 들어
음성 → 항목(entry). Gemma는 상점 주인이 어떻게 말하는지 설명하는 짧은 시스템 프롬프트를 받습니다. udhaar/khata = 외상값(credit), jama/dile = 지불됨(paid back), sakhar = 설탕(sugar), tup = 기(ghee)입니다. 또한 세 가지 예시도 받습니다. Ollama의 format 매개변수는 모델을 JSON 스키마로 제한하므로, 저는 모델의 출력을 정규표현식(regex)으로 처리할 필요가 없습니다. 그 뒤에는 작은 규칙 기반 파서(rule-based parser)가 있어, 모델이 로딩 중일 때도 상점이 계속 작동하도록 합니다.
저는 데바나가리 문자, 반값 지불, 그리고 한 번에 세 가지 품목을 포함한 18개의 실제 같은 상점 거래 내역을 작성했고, 두 파서 모두를 테스트했습니다:
| 파서 | 완벽하게 정확한 항목 수 | 중앙값 지연 시간(Median latency) |
|---|---|---|
| 규칙만 사용 | 12 / 18 | <1 ms |
| ... | ||
| Gemma는 모든 18개 라인에서 항목 유형, 고객, 금액을 정확하게 파악했습니다. 반면, 규칙 기반 파서는 15, 14, 16개를 처리했습니다. Gemma가 놓친 세 가지 오류는 모두 품목 목록에 관한 것이었는데, 이는 가장 비용이 적게 드는 실수입니다. 돈은 여전히 정확하고 상점 주인은 한 번의 터치로 품목을 수정할 수 있습니다. |
그것 때문에 저는 실제 문제를 안게 되었습니다. 제 노트북 GPU는 4GB인데, Gemma 3 4B는 약 3.5GB가 필요하며 생각할 공간도 필요하기 때문에 절반이 CPU로 넘어가고 모든 항목에 약 6초가 걸립니다. 저녁 혼잡 시간대에 누가 라인당 6초를 기다리겠습니까? 1B 모델은 GPU에 완전히 맞지만, 제 정규표현식 규칙보다 더 나빴습니다.
인도 외 지역. 저는 각각 멕시코의 tiendita, 사리-사리 상점(sari-sari store), 그리고 warung 스타일로 15개의 상점 거래 내역을 작성했고, 이들에 맞춰 규칙 파서를 조정하여 모두 15개를 처리하도록 만들었습니다. 그 숫자는 그 자체로는 의미가 없기 때문에, 저는 제가 조정하지 않은 9개의 새로운 라인을 추가로 작성했습니다:
| 라인 수 | 규칙 기반 | Gemma 3 4B |
|---|---|---|
| 인도 (마라티어 / 힌디어 / 영어), 18 | 12 | 15 |
| 스페인어 / 필리피노어 / 인도네시아어, 미조정, 9 | 5 | 7 |
네 가지 언어에서 규칙을 작동시키면서 얼마나 많은 충돌이 있는지 알게 되었습니다: _don_은 마라티어로는 '2'를 의미하지만 스페인어에서는 직함('Don Pepe')이고, _andi_는 마라티어로는 '달걀'이지만 인도네시아의 흔한 이름('Mas Andi')입니다. 수천 개의 단어가 인도네시아에서는 '48.000'으로 쓰여 있지만 '50 ribu'로 발음됩니다. 바로 이런 경우들이 held-out lines가 잡아내는 케이스이며, 언어 모델이 자신의 가치를 증명하는 곳입니다.
그래서 Udhaar는 이 두 가지를 모두 사용합니다. 규칙이 즉시 항목을 작성하고 (밀리초 단위 미만), 상점 주인은 다음 고객에게로 넘어갈 수 있습니다. Gemma가 백그라운드에서 같은 라인을 다시 읽어보고, 만약 다르게 이해했다면 몇 초 후에 작은 'Corrected by gemma3:4b' 메모와 함께 항목을 조용히 수정합니다. 계산대는 규칙의 속도와 모델의 정확성을 모두 갖게 됩니다. 질문 처리도 똑같습니다. 규칙이 일반적인 구문을 즉시 답변하고, Gemma는 오직 긴 꼬리(long tail)에 대해서만 호출됩니다.
누가 연체할지 예측 (TabPFN 분류기). '우리 이웃들의 상환 습관'에 대한 레이블링된 데이터셋은 존재하지 않으며, 작은 가게가 모델을 학습시키기에 충분한 데이터를 갖는 일은 없을 것입니다. 바로 이런 곳에서 TabPFN이 빛을 발합니다: 훈련 과정 없이 몇 백 줄의 데이터만으로 예측을 수행하기 때문입니다. 저는 카타(khata)를 재현하고 매주 모든 미결제 계좌의 스냅샷을 찍습니다. 각 스냅샷은 해당 사람이 30일 이내에 부채의 최소 절반이라도 상환했는지 여부로 레이블링됩니다. TabPFN은 이런 스냅샷 약 700개로부터 몇 초 만에 학습하고 오늘날의 계좌들을 점수화합니다.
무엇을 재고할지 예측 (TabPFN 회귀 모델). 키라나(Kirana) 수요는 모두 달력 기반입니다: 주말, 월급이 들어오는 첫째 주, 그리고 축제 전까지의 증가 추세가 있습니다. 각 행은 _(품목, 요일, 급여 주차, 축제까지 남은 일수, 최근 7일 및 28일 평균, 예측 기간)_으로 구성됩니다. 저는 반복문(looping)을 사용하는 대신 하나의 배치 호출로 디왈리(Diwali)까지 모든 품목 × 모든 날짜에 대한 예측을 수행합니다. API를 사용하면 모든 품목의 예측을 새로고침하는 데 약 30초가 걸리므로, 백그라운드에서 실행되어 계산대가 기다릴 일이 없습니다.
작은 예측 에이전트 내부의 TabPFN
질문은 작은 에이전트 루프를 거칩니다: 계획(plan)을 세우고, 도구를 호출합니다. 플래너는 한 줄짜리 질문에 대한 즉각적인 규칙이며, Gemma가 복합적이거나 조언 스타일의 질문("신대 엄마가 디왈리에 돈이 더 필요하다고 하는데, 내가 뭘 줘야 할까? 그 사람이 나한테 얼마를 빚졌지?" → customer_due + credit_limit)에 사용됩니다. Gemma는 도구만 선택할 뿐, 절대로 숫자를 직접 작성하지 않습니다.
도구로는 SQL과 TabPFN이 있습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

