OCR + Rule + LLM
AI & HCI영수증 사진을 올리면 OCR이 읽고, Rule이 좁히고, LLM이 뽑아낸다 — 근거 없는 값은 절대 반환하지 않는다
Built a local-first receipt understanding pipeline that separates concerns across three explicit stages: OCR extracts text, Rules narrow candidates, and LLM performs semantic extraction — each doing only its own job.
The key principle: no hallucinated evidence. The LLM returns a value only when it explicitly provides supporting evidence from the OCR text. When evidence is missing, the field stays empty. Rule-based fallback values are clearly labeled in the UI — never mixed with LLM outputs.
Every stage is visible in the Streamlit UI: raw OCR text, rule candidates, raw LLM output, and final extracted fields. Nothing is hidden.
Key decisions:
| Decision | Rationale |
|---|---|
| OCR → Rule → LLM pipeline | LLMs infer, not read. Narrowing input with Rules reduces hallucination surface |
| No hallucinated evidence | LLM output is only accepted when evidence is explicitly provided |
| Fully local (Ollama + PaddleOCR) | Receipts contain personal data — no external API calls |
| SQLite (no ORM) | Simple, inspectable storage with direct SQL access |
| Raw LLM output stored | Parse failures are surfaced in the UI, not silently swallowed |
Tech Stack:
| Layer | Tool |
|---|---|
| Language | Python 3.10+ |
| UI | Streamlit |
| OCR | PaddleOCR (local, singleton) |
| LLM | Ollama (local inference) |
| Rules | regex + heuristics |
| Storage | SQLite |
OCR · Rule · LLM 세 단계가 역할을 분리해 영수증을 처리하는 로컬 파이프라인을 만들었다. OCR은 텍스트만 추출하고, Rule은 후보만 좁히고, LLM은 의미만 뽑는다. 각자 자기 일만 한다.
핵심 원칙: 근거 없는 값은 반환하지 않는다. LLM이 OCR 텍스트에서 명시적인 근거를 제공할 때만 필드를 채운다. 근거가 없으면 비워둔다. Rule 기반 fallback 값은 UI에서 별도 표시 — LLM 추출값과 절대 섞지 않는다.
Streamlit UI에서 모든 단계의 출력을 확인할 수 있다: OCR 원문, Rule 후보, LLM 원본 출력, 최종 추출값. 블랙박스가 없다.
핵심 결정:
| 결정 | 이유 |
|---|---|
| OCR → Rule → LLM 순서 | LLM은 읽는 게 아니라 추론한다. Rule로 입력을 좁혀야 할루시네이션이 줄어든다 |
| 근거 없는 값 반환 금지 | "그럴듯한 값"과 "근거 있는 값"을 구분해야 파이프라인을 신뢰할 수 있다 |
| 완전 로컬 실행 | 영수증에는 개인정보가 있다. Ollama + PaddleOCR로 외부 API 호출 없음 |
| SQLite (ORM 없음) | 단순하고 검사하기 쉬운 저장소. SQL로 직접 접근 가능 |
| LLM 원본 출력 저장 | 파싱 실패도 UI에서 보인다. 조용히 삼키지 않는다 |
사용 기술:
| 레이어 | 도구 |
|---|---|
| Language | Python 3.10+ |
| UI | Streamlit |
| OCR | PaddleOCR (로컬, 싱글턴) |
| LLM | Ollama (로컬 추론) |
| Rules | regex + 휴리스틱 |
| Storage | SQLite |
Leave a Comment: