본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 04. 23:14

Claude Vision API로 청구서 OCR을 구현한 지식 — 프롬프트 설계부터 정밀도 개선까지

요약

Anthropic Claude Sonnet 4.6의 Vision API를 활용하여 청구서의 송금 정보를 JSON으로 추출하는 OCR 구현 사례를 다룹니다. 레이아웃에 구애받지 않는 멀티모달 LLM의 장점과 환각 방지를 위한 프롬프트 설계 전략을 설명합니다.

핵심 포인트

  • Vision API를 통한 이미지의 구조화 데이터 일괄 변환
  • 불확실한 정보에 대한 'requires_review' 플래그 설계로 정밀도 보완
  • 날짜 및 계좌 정보의 정규화 규칙을 프롬프트에 포함
  • 파싱 오류 방지를 위한 순수 JSON 출력 지시

청구서의 송금 정보를 자동 추출하는 서비스를 개발하는 과정에서, Anthropic Claude Sonnet 4.6의 Vision API를 사용하여 청구서 OCR을 구현했습니다.

기존의 OCR 라이브러리(Tesseract 등)가 아닌 멀티모달 LLM(Large Language Model)을 선택한 이유, 프롬프트 설계의 포인트, 실제 정밀도와 개선책을 정리합니다.

레이아웃 분석이 필요함: 청구서 포맷은 기업마다 다르기 때문에, "이 좌표의 텍스트가 은행명"이라는 규칙 기반(Rule-based) 처리는 비현실적 -
후처리가 방대함: OCR로 얻은 텍스트에서 "은행명", "계좌번호" 등을 정규 표현식이나 규칙으로 분류해야 함 -
수기 및 장식 폰트: 손으로 쓴 청구서나 디자인성이 높은 청구서에서는 정밀도가 현저히 저하

이미지 → 구조화 데이터의 일괄 변환: "이 이미지에서 은행 송금 정보를 JSON 형식으로 추출해줘"라고 지시하기만 하면 됨 -
레이아웃에 의존하지 않음: 청구서 형식을 불문하고, 의미적으로 이해하여 정보를 추출 -
일본어 성능: Claude Sonnet 4.6은 일본어 텍스트 이해력이 높음

청구서 해석을 위한 시스템 프롬프트(System Prompt)는 다음과 같은 구조로 설계했습니다:

1. 역할 정의 (누구인가)
2. 기본 규칙 (해도 되는 것·하면 안 되는 것)
3. 추출 항목의 상세 사양
...

가장 중요한 것은 **"이미지에 적혀 있지 않은 것을 날조하지 말 것"**입니다.

정보의 추출 원천은 "제공된 이미지"뿐입니다.
이미지에 기재되지 않은 정보를 멋대로 날조(Hallucination)해서는 안 됩니다.

특히 가타카나 명의(recipient_name_kana)는 이미지에 기재되지 않은 경우가 많아, 한자 회사명으로부터 추측하게 됩니다. 이때 반드시 requires_review: true를 붙이도록 명시하고 있습니다.

OCR과 달리, LLM은 "자신이 없는" 것을 스스로 신고할 수 있습니다. 이를 활용한 requires_review 플래그 설계가 정밀도 향상의 핵심이었습니다.

{
"bank_name": { "value": "三菱UFJ銀行", "requires_review": false },
"recipient_name_kana": { "value": "カブシキガイシャサンプル", "requires_review": true }
...
}

플래그를 true로 설정해야 하는 조건을 구체적으로 프롬프트에 열거합니다:

  • 글자가 흐릿하여 판독에 자신이 없음
  • 여러 계좌가 기재되어 있어 어떤 것인지 망설여짐
  • 가타카나 명의를 한자로부터 추측함
  • 이미지에 기재되어 있지 않아 null로 처리함

이 플래그가 있음으로써, UI 측에서 "확인 필요" 마크를 표시하여 사용자의 주의를 적절히 유도할 수 있습니다.

일본 청구서 특유의 사정에 맞춘 정규화(Normalization) 규칙을 프롬프트에 포함하고 있습니다:

화력(和暦) → 서력 변환: "令和8年5月" → 2026-05-01

계좌번호: 하이픈 제거, 숫자만 남김 -
계좌 종류: "フツウ", "ふつう", "普通預金" → 普通으로 정규화 -
금액: 콤마 제거, 정수화

LLM은 기본적으로 "이하가 JSON 출력입니다:"와 같은 서두 텍스트를 출력하는 경향이 있습니다.

출력은 순수한 JSON 문자열로만 하며,
마크다운(```json 등)이나 해설 텍스트는 일절 포함하지 마십시오.

이 지시를 넣음으로써 응답의 파싱(Parsing) 실패를 방지합니다.

const AI_MODEL = "claude-sonnet-4-6";
const AI_TIMEOUT_MS = 60_000;
const MAX_RETRIES = 1;
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const response = await client.messages.create({
model: AI_MODEL,
max_tokens: 2048,
system: SYSTEM_PROMPT,
messages: [{
role: "user",
content: [
{
type: "image",
source: { type: "base64", media_type: mimeType, data: base64Data }
},
{
type: "text",
text: "이 청구서 이미지에서 송금에 필요한 정보를 추출해 주세요나."
}
]
}]
});

for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
return await parseInvoiceInternal(fileKey, fileName);
} catch (error) {
if (error instanceof ParseInvoiceError && error.retryable) {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
continue;
}
throw error;
}
}


리트라이 대상은 `ai_timeout`

...

export type ParseErrorType =
|"ai_timeout" // API 타임아웃
|"ai_rate_limit" // API 레이트 제한
|"ai_service_error" // API 서비스 에러
|"ai_parse_failed" // 파싱 결과의 파스 실패
|"invalid_image" // 이미지가 부정확하거나 손상됨
|"not_invoice" // 청구서가 아닌 이미지
|"low_quality" // 화질이 너무 낮음
|"unknown";


Anthropic SDK의 에러를 `catch`

...

import zenginCode from "zengin-code";
export function resolveBankCode(bankName: string): string | null {
const normalized = bankName
.replace(/銀行$/, "")
.replace(/信用金庫$/, "");
for (const [code, bank] of Object.entries(zenginCode)) {
if (bank.name.includes(normalized) || bank.kana.includes(toKana(normalized))) {
return code;
}
}
return null;
}


1,500개 이상의 금융기관과 수만 개의 지점을 커버하는 마스터 데이터로 AI의 추출 결과를 코드로 변환하고 있습니다.

테스트용 청구서 샘플(약 20종류)으로 검증한 결과:

| 카테고리 | 추출 정밀도 | 비고 |
|---|---|---|
| 은행명 | 95%+ | 거의 정확함 |
| ... | 
**가타카나 명의가 최대 병목 지점**입니다. 이미지에 가타카나 표기가 없는 경우, AI는 한자 회사명에서 추측하지만, 읽는 법이 유일하게 정해지지 않는 경우가 있습니다 (예: "東" = 히가시 or 아즈마). 이 항목은 항상 `requires_review: true`
으로 사용자 확인을 요청하는 설계로 하고 있습니다.

| 항목 | 값 |
|---|---|
| 모델 | Claude Sonnet 4.6 |
| ... |1건당 분석 비용 |
약 ¥3〜5 |

이미지 크기와 프롬프트 길이에 따라 변동하지만, 1건당 ¥5 이하로 유지되고 있습니다.

-
**LLM Vision은 구조화 데이터 추출에 강점**: 레이아웃 비의존적으로 청구서 정보를 직접 JSON으로 변환할 수 있음 -
: AI의 불확실성을 UI에 반영하여 인간의 체크를 적절하게 유도`requires_review`

패턴이 실용적임 -
**프롬프트의 구체성이 정밀도를 결정**: 추출 항목별 정규화 규칙, NG 예시, JSON 출력 예시를 명시 -
**가타카나 명의가 최대 과제**: 이미지에 없는 정보의 추측에는 한계가 있어 사용자 확인이 필수

청구서 OCR 외에도 명함・영수증・계약서 등 정형 문서의 구조화 데이터 추출에 동일한 접근 방식이 응용 가능합니다.

**실제로 이 기술을 사용한 서비스**: 트루카 송금 어시스트 — 청구서에서 AI로 전일 포맷의 송금 데이터를 자동 생성

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0