0017: LLM 메타데이터 추출을 추가 백엔드로 (0011 확장)
0017: LLM 메타데이터 추출을 추가 백엔드로 (0011 확장)
- Status: Superseded
- Superseded by: ADR 0011 § “Additive opt-in pattern (generalization)”
- Date: 2026-05-12
- Deciders: hskim
- Related: #180, ADR 0001, ADR 0011
TL;DR
rag_metadata_extraction모듈을rag_synthesis와 1:1 미러링 —regex기본,stub/anthropic_tool_use/openai_function_call백엔드.stub↔regexbyte-equivalence 가 계약 단위 테스트 — ADR 0001 naive 기준선 불변식 유지.- 8 필드 스키마 (#180) lock; LLM 추출은 opt-in, ingestion 경로 불변.
배경
오늘의 검색·답변 경로용 메타데이터는 ingestion.normalize_metadata — 구조화 CSV 컬럼 + 결정론 regex 파싱 (예산 정규화, ISO 날짜 강제) 에서 옴. CSV row 가 불완전하거나 body 가 비구조 필드 (contact_email, contact_name) 운반하는 문서엔 regex 경로의 hard ceiling — 더 줄 신호 없음.
ADR 0011 이 이미 rag_synthesis.py 에 추가 LLM 백엔드 패턴 확립: 결정론 stub 기본, opt-in anthropic tool-use 백엔드, openai-compatible 대안, tool + 시스템 프롬프트 캐싱, SDK·키 누락 시 graceful 폴백. 이슈 #180 가 메타데이터 추출에 동일 shape 필요 — ADR 0001 naive 기준선 불변식 안 깨고 LLM-추출 메타데이터를 regex 기준선과 기존 eval 표면에서 비교.
결정
rag_synthesis 1:1 미러링하는 새 rag_metadata_extraction 모듈 추가:
- 공개 진입점
extract_rfp_metadata(document, backend=...)가 #180 의 8 필드 (agency,project_name,budget_amount,budget_currency,deadline_iso,submission_date_iso,contact_email,contact_name) 를 가진 typedMetadataExtractiondataclass 반환. - 백엔드 (
BIDMATE_METADATA_BACKEND):regex(기본 — ADR 0001 불변식 보존),stub(regex위임 → stub 모드 byte-for-byte identical),anthropic_tool_use,openai_function_call. stub↔regexbyte-equivalence 가 계약 단위 테스트. stub 모드 run 의 LLM 비용 0 + 스키마 drift 0 보장 → downstream consumer (eval 분석 변형 row, 대시보드) 가 LLM 경로 미활성 시 안정.- tool 정의
extract_rfp_metadata는 보수적: 모든 필드 선택 +additionalProperties: false→ LLM 이 비구조 페이로드 smuggle 불가. - 어떤 백엔드 예외든
extract_rfp_metadata가_regex_backend로 폴백. 파이프라인이 SDK·네트워크 실패로 메타데이터 조용히 잃지 않음. - body 텍스트는 LLM 전송 전 ~8000 chars 로 truncate. 매우 긴 RFP 는 후반 contact 누락 가능 — 첫 iteration 허용 + truncation 코호트 vs regex eval 회귀 non-trivial 시만 revisit.
결과
rag_synthesis친숙한 reader 가rag_metadata_extraction을 한 번에 이해: 동일 env-key 활성화, 실 백엔드 동일# pragma: no cover - network격리, 동일 stub-matches-baseline 계약 테스트, 동일 dataclass 노출 8 필드 스키마.- 메타데이터 어휘를 #180 8 필드에 lock. 필드 추가는 스키마 변경 — #180 downstream eval 비교 표가 이 정확한 shape 키이므로 ADR 개정 필요.
- LLM 추출은 env var opt-in, 자동 절대 아님. ingestion 경로 불변; LLM 경로는 요청하는 eval 분석 변형 row (
agentic_full_llm_metadata, follow-up PR) 만 호출. - 비용 표면은 ADR 0011 가격 카드 패턴 따름: LLM 분석 변형 실행 시
rag_synthesis의 동일compute_cost_usd훅 (Sonnet 4.6 기본) 상속 → 메타데이터 추출 토큰 소비 10× 리팩터가 답변 합성 10× 와 동일 방식으로 flag.
검토한 대안
ingestion.normalize_metadata내부에 LLM 호출 매장. Reject — 결정론 CSV reader 를 네트워크 백엔드와 결합 + 기본으로 ADR 0001 불변식 위반.normalize_metadata의존 opt-in 표면이 조건부 import 경로 가지게 됨 — 정확히 ADR 0011 이 피한 smell.- stub 백엔드 skip + mock
anthropic클라이언트로 테스트 gate. Reject —rag_synthesis의 동일 접근이 SDK 모킹 없이 CI 결정론 유지 증명. mock 은 drift; stub-matches-baseline 불변식은 PR 마다 검사. - OpenAI 의 JSON 모드 (tool / function call 없음). Reject — tool / function-call 표면이 구조화 스키마 계약 부여. raw JSON 모드는 모델 측 환각에 fragile + parser 레이어에
additionalProperties: falseenforcement 중복.