이 글은 누구를 위한 것인가
- PDF 계약서·영수증을 수동으로 입력하는 팀
- OCR만으로는 구조화된 데이터 추출이 안 되는 팀
- LLM으로 문서에서 특정 정보를 자동으로 뽑고 싶은 개발자
들어가며
영수증 사진에서 금액·날짜·상호를 뽑고, 계약서에서 계약 조건·만료일·당사자를 추출하는 것 — 이것을 LLM이 한다. OCR이 텍스트를 읽어주고, LLM이 의미를 이해하고 구조화한다.
이 글은 bluefoxdev.kr의 AI 문서 처리 가이드 를 참고하여 작성했습니다.
1. 문서 처리 파이프라인
[문서 인텔리전스 처리 흐름]
1. 문서 입력:
PDF, DOCX, 이미지(JPG, PNG), 스캔본
2. 전처리:
이미지: 기울기 보정, 품질 향상
PDF: 레이어 분리 (텍스트 vs 이미지)
보안 PDF: 비밀번호 처리
3. 텍스트 추출:
디지털 PDF: pdfminer/pypdf 직접 추출
스캔/이미지: Tesseract OCR 또는 AWS Textract
표/양식: Amazon Textract (레이아웃 인식)
4. LLM 구조화 추출:
Claude Vision 또는 GPT-4V (이미지 직접)
또는 텍스트 → Claude (텍스트만)
5. 검증:
필수 필드 존재 여부
데이터 타입 검증 (날짜, 금액 형식)
신뢰도 점수 산출
6. 후처리:
정규화 (날짜 형식 통일)
DB 저장
수동 검토 큐 (신뢰도 낮은 건)
2. 영수증·문서 데이터 추출
import anthropic
import base64
from pathlib import Path
from pydantic import BaseModel
class ReceiptData(BaseModel):
merchant_name: str
merchant_address: str | None
date: str
total_amount: float
tax_amount: float | None
items: list[dict]
payment_method: str | None
confidence: float
class ContractData(BaseModel):
contract_type: str
parties: list[dict]
effective_date: str
expiration_date: str | None
key_clauses: list[dict]
payment_terms: str | None
termination_conditions: str | None
confidence: float
client = anthropic.Anthropic()
async def extract_receipt(image_path: str) -> ReceiptData:
"""영수증 이미지에서 데이터 추출"""
with open(image_path, "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
ext = Path(image_path).suffix.lower()
media_type = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png"}.get(ext, "image/jpeg")
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1500,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": media_type,
"data": image_data,
},
},
{
"type": "text",
"text": """이 영수증에서 다음 정보를 JSON으로 추출하세요.
찾을 수 없는 필드는 null로 설정하세요.
{
"merchant_name": "상호명",
"merchant_address": "주소",
"date": "YYYY-MM-DD 형식",
"total_amount": 총금액 (숫자),
"tax_amount": 부가세 (숫자 또는 null),
"items": [{"name": "품목명", "quantity": 수량, "price": 가격}],
"payment_method": "카드/현금/간편결제",
"confidence": 0.0-1.0 (추출 신뢰도)
}"""
}
]
}]
)
import json
raw = json.loads(response.content[0].text)
return ReceiptData(**raw)
async def extract_contract(pdf_text: str) -> ContractData:
"""계약서 텍스트에서 핵심 정보 추출"""
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=3000,
messages=[{
"role": "user",
"content": f"""다음 계약서에서 핵심 정보를 JSON으로 추출하세요.
계약서 내용:
{pdf_text[:8000]}
추출할 정보:
{{
"contract_type": "계약 유형 (임대차/매매/서비스/용역 등)",
"parties": [
{{"role": "甲/乙/공급자/수요자", "name": "이름/상호", "registration_number": "사업자번호"}}
],
"effective_date": "YYYY-MM-DD",
"expiration_date": "YYYY-MM-DD 또는 null",
"key_clauses": [
{{"clause": "조항명", "summary": "한 줄 요약", "is_critical": true/false}}
],
"payment_terms": "대금 지급 조건",
"termination_conditions": "계약 해지 조건",
"confidence": 0.0-1.0
}}"""
}]
)
import json
raw = json.loads(response.content[0].text)
return ContractData(**raw)
async def batch_process_documents(documents: list[dict]) -> list[dict]:
"""문서 일괄 처리 파이프라인"""
results = []
for doc in documents:
doc_type = doc.get("type", "unknown")
try:
if doc_type == "receipt":
data = await extract_receipt(doc["file_path"])
elif doc_type == "contract":
text = await extract_pdf_text(doc["file_path"])
data = await extract_contract(text)
else:
data = None
if data and data.confidence < 0.7:
# 낮은 신뢰도는 수동 검토 큐
await queue_for_manual_review(doc["id"], data.dict())
elif data:
await save_extracted_data(doc["id"], data.dict())
results.append({
"doc_id": doc["id"],
"status": "success",
"confidence": data.confidence if data else 0,
})
except Exception as e:
results.append({
"doc_id": doc["id"],
"status": "error",
"error": str(e),
})
return results
마무리
AI 문서 추출에서 가장 중요한 것은 신뢰도 검증이다. LLM이 "있다고 생각하는" 데이터와 실제 데이터가 다를 수 있다. 신뢰도 70% 이상만 자동 저장하고, 나머지는 인간 검토를 거치는 구조가 안전하다. 계약서의 경우 법적 효력이 있으므로 100% 인간 검토를 권장한다.