대상 05_분석툴_기능명세서.md v1.0 구현
문서 버전 v1.0 · 2026-08-04
전제 본 문서의 모든 수치는 이번 세션에서 실측한 값(문서 크기, 하드웨어 사양, 변환 결과)에 기반한다. 추정치는 추정치라고 표기했다.
| 계층 | 선정 | 핵심 근거 |
|---|---|---|
| 언어/런타임 | Python 3.12 (uv 관리) | PDF·한글·임베딩 생태계가 Python 우선. 기존 pdf2md.py 자산 승계 |
| PDF 텍스트/렌더 | PyMuPDF (기본) / pypdfium2 + pdfplumber (라이선스 대안) | 검증 완료. AGPL 이슈로 인터페이스 추상화 필수 (§4.2) |
| HWP | pyhwp → 실패 시 LibreOffice headless → 실패 시 PDF 동반본 | 3단 폴백 |
| 이미지 판독 | Claude vision (기본) / PaddleOCR (로컬 전용 모드) | maindev에 GPU 없음 → 로컬 VLM 불가, OCR은 CPU 가능 |
| LLM | Opus 5 / Sonnet 5 / 판정 없음 3단 배치 (§4.5) | 단계별 난이도에 맞춘 비용 배분 |
| 근거 추출 | Claude Citations API | cited_text + page_location을 API가 직접 반환 → M9 환각 차단의 1차 방어선 |
| 임베딩 | BGE-M3 (CPU) | Anthropic은 임베딩 API 미제공. GPU 없어 CPU 배치 추론 |
| 저장소 | SQLite (+ sqlite-vec) | 단일 파일·서버 불필요·재현성. 벡터 규모가 작음(수천 청크) |
| 오케스트레이션 | 자체 DAG + 콘텐츠 해시 캐시 | 재현성 NFR이 어차피 결정론적 캐시를 요구 |
| 리포팅 | Jinja2 → MD / xlsxwriter → XLSX / 기존 build_site.py → HTML |
이미 동작하는 자산 존재 |
| CLI/설정/검증 | Typer / pydantic-settings / pytest + 골든셋 | — |
가장 중요한 아키텍처 결정 하나: 수행안내서 456쪽이 Claude의 1M 컨텍스트 윈도우에 통째로 들어간다. 따라서 기준문서에 대한 RAG(청킹→검색→재구성)를 만들지 않는다. 대신 안내서에서 추출한 지식베이스(121지표 + 12체크리스트)를 프롬프트 캐시 프리픽스에 상주시킨다. 근거와 비용 계산은 §5.
CPU 24 코어 MEM 62 GB (가용 33 GB)
GPU 없음 ← 결정적 제약
DISK 652 GB 여유
Python 3.10.12 (시스템)
있음 docker, git, node
없음 uv, poetry, soffice(LibreOffice), tesseract, nvidia 드라이버
GPU 부재가 세 가지를 강제한다.
1. 로컬 비전 모델(Qwen2.5-VL 등)로 슬라이드를 판독하는 선택지가 사라진다 → 클라우드 vision 또는 CPU OCR.
2. 임베딩은 CPU 배치 추론 (BGE-M3, 수천 청크 기준 수 분 — 실용적).
3. 리랭커(cross-encoder) 도입은 비용 대비 효과가 나쁘다 → 도입하지 않는다.
| 문서 | 쪽/장 | 문자 수 | 성격 |
|---|---|---|---|
| 수행안내서 | 456쪽 | 408,506 | 기준문서, 표 다수 |
| 제안요청서 | 81쪽 | 73,475 | 요구사항 정의서 = 전부 표 |
| 제안요약서 | 50슬라이드 | 90,556 (판독 후) | 텍스트 레이어 0 (원본 1,618자) |
입력 문서에 미공개 사업정보와 실명 인력정보가 들어 있다(수탁사 목록, 투입인력 경력·자격). 이는 스택 선정에 직접 영향을 준다.
--offline)를 1급 기능으로 지원해야 한다 → OCR 폴백 경로가 장식이 아니라 필수인 이유.tools/pdf2md.py는 456쪽·81쪽 문서로 동작을 확인했다. 재작성하지 않는다.| 항목 | 내용 |
|---|---|
| 결정 | Python 3.12, 패키지·가상환경은 uv |
| 대안 | TypeScript/Node (웹 UI 유리, PDF·한글 생태계 열세) / Python 3.10 유지 |
| 근거 | PyMuPDF·pdfplumber·sentence-transformers·anthropic SDK 모두 Python 1급. maindev의 3.10.12는 시스템 Python이므로 건드리지 않고 uv가 3.12를 별도 설치·고정 |
| uv 선택 이유 | uv.lock이 재현성 NFR과 직결. poetry 대비 설치 속도·락파일 정확성 우위 |
| 리스크 | uv 미설치 상태 → 부트스트랩 스크립트에 설치 단계 포함 |
| 항목 | 내용 |
|---|---|
| 결정 | 기본 백엔드 PyMuPDF. PdfBackend 인터페이스 뒤에 격리하고 pypdfium2 + pdfplumber 대체 백엔드를 Phase 1에 함께 구현 |
| 라이선스 | PyMuPDF는 AGPL-3.0 (또는 상용). 사내 도구는 무방하나 배포·SaaS 전환 시 소스 공개 의무가 발생한다. 나중에 발견하면 M2 전체를 재작성해야 한다 |
| 대체 경로 검증 | 필요한 것은 글자 단위 bbox(M2-F01 띄어쓰기 복원의 입력). pdfplumber(MIT)의 page.chars가 x0/x1/top/bottom/size/fontname을 제공 → 알고리즘 이식 가능. 렌더링은 pypdfium2(BSD/Apache) |
| 트레이드오프 | 대체 경로는 느리고 표 추출 품질이 낮다 → 기본은 PyMuPDF, 라이선스 전환 시 스위치 |
| 인터페이스 | chars(page) / lines(page) / tables(page) / render(page, dpi) / page_count() — 5개 메서드면 충분 |
| 순위 | 수단 | 비고 |
|---|---|---|
| 1 | pyhwp (HWP v5) |
GPL — PyMuPDF와 동일 격리 원칙 적용 |
| 2 | LibreOffice headless (soffice --convert-to pdf) |
maindev 미설치 → Docker 이미지로 격리 실행 |
| 3 | 동반 PDF 사용 | 이번 케이스가 여기 해당([붙임1] 제안요청서-마사회.hwp와 .pdf가 함께 존재) |
HWPX는 zip+XML이므로 표준 라이브러리로 직접 파싱한다.
| 모드 | 수단 | 근거 |
|---|---|---|
| 기본 | Claude vision (image 블록, base64) |
이번 세션에서 50슬라이드 전량 판독 성공. 표·조직도·타임라인의 구조 보존이 OCR보다 우월 — GFM 표로 바로 나온다 |
--offline |
PaddleOCR (한국어 모델, CPU) | 보안 제약(§2.3) 대응. 구조 보존은 포기하고 텍스트만 |
| 미채택 | 로컬 VLM | GPU 없음 |
| 미채택 | Tesseract | 미설치 + 한국어 표 구조 열세 |
해상도 정책: Opus 5 / Sonnet 5는 고해상도 비전(장변 2576px, 최대 4784 토큰/이미지)을 지원한다. 이번 세션은 110 dpi(장변 약 1180px)로 충분히 판독됐다 — 슬라이드는 원래 저밀도 문서다. 기본 110 dpi, 판독 실패 시 200 dpi 재시도. 기본을 고해상도로 두면 이미지 토큰이 3배가 되므로 올리지 않는다.
Anthropic Claude API. 모델 ID는 날짜 접미사 없는 정확한 문자열을 쓴다.
| 단계 | 모델 | effort | 이유 |
|---|---|---|---|
| M3 슬라이드 판독 | claude-sonnet-5 (기본) / claude-opus-5 (실패 재시도) |
medium | 판독은 구조 인식 작업. Sonnet 5도 고해상도 비전을 지원하며 입력가가 Opus의 60% |
| M6 커버리지 판정 | claude-sonnet-5 + Batch API |
medium | 요구사항 42건 × 문서 = 고volume·저난이도. 배치로 50% 추가 할인, 지연은 무관(비대화형) |
| M7 방법론 룰 | claude-opus-5 |
high | 16개 룰 중 R-DOMAIN-01·R-METH-01 같은 판단은 이번 분석의 핵심 가치. 여기서 아끼면 도구가 무의미 |
| M8 문서위생 | LLM 없음 | — | 정규식·구조 검사 |
| M9 인용 검증 | LLM 없음 | — | 문자열 대조 |
프롬프트 캐싱 (핵심 비용 레버):
- 캐시 프리픽스 = 지식베이스(121지표 + 12체크리스트) + 룰 정의. 문서별로 바뀌지 않는다.
- 캐시 읽기 ≈ 입력가의 0.1배, 쓰기 1.25배(5분 TTL) / 2배(1시간 TTL).
- 5분 TTL 기준 2회 요청부터 이득. 한 번의 분석이 수십 회 호출이므로 항상 이득.
- 주의: 최소 캐시 가능 프리픽스가 모델별로 다르다 — Opus 5는 512토큰, Sonnet 5는 1024토큰. 지식베이스는 수만 토큰이므로 문제없다.
- 캐시 무효화 방지: 프리픽스에 타임스탬프·UUID·요청 ID를 넣지 않는다. JSON 직렬화는 sort_keys=True. 도구 목록은 이름순 정렬 고정.
구조화 출력: Finding 스키마(§05 M10-F02)를 Pydantic으로 정의하고 client.messages.parse()로 검증받는다. 파싱 실패·재시도 루프를 직접 짜지 않는다.
Citations API — M9의 1차 방어선:
{"type": "document", "source": {...}, "citations": {"enabled": True}}
응답이 cited_text와 page_location(1-indexed)을 직접 반환한다. 인용을 모델에게 "적어달라"고 요청하는 대신 API가 구조적으로 보장하므로, M9-F04의 문자열 대조는 2차 검증이 된다. 단 구조화 출력(output_config.format)과 동시 사용 시 400 — 두 경로를 분리해야 한다:
- 근거 추출 호출 → Citations 사용, 자유 텍스트
- 판정 호출 → 구조화 출력 사용, 근거는 앞 단계 결과를 입력으로 받음
05_분석툴_기능명세서.md의 비기능 요구사항에 "LLM 온도 0" 이라고 적었는데, 이는 현행 모델에서 틀렸다. Claude Opus 5 / Sonnet 5는 temperature·top_p·top_k를 파라미터 자체로 거부한다(400). 05 문서는 정정했다.
재현성은 다른 세 축으로 확보한다.
| 축 | 수단 |
|---|---|
| 출력 형식 고정 | 구조화 출력(JSON 스키마) + strict: true 도구. 자유 서술을 파싱하지 않는다 |
| 근거 고정 | 인용 검증(M9-F04) — 원문에 없는 인용은 Finding 폐기. 표현이 흔들려도 근거는 흔들리지 않는다 |
| 회귀 고정 | 골든셋 13건 재현 테스트를 CI에 건다. "같은 문장이 나오는가"가 아니라 "같은 Finding이 나오는가" 를 본다 |
추가로 model·프롬프트 해시·지식베이스 버전·룰 버전을 매 실행 기록한다. 결정론이 아니라 감사 가능성이 목표다.
| 항목 | 내용 |
|---|---|
| 결정 | BAAI/bge-m3 via sentence-transformers, CPU 배치 |
| Anthropic 임베딩 API는 없다 | Claude API는 임베딩 엔드포인트를 제공하지 않는다. 임베딩은 별도 조달 |
| 왜 로컬 | 보안 제약(§2.3) + GPU 없어도 CPU로 감당 가능한 규모(수천 청크) |
| 용도 한정 | M6-F01 후보 매칭 1순위 필터로만. 최종 판정은 LLM. 임베딩 점수로 직접 판정하지 않는다 |
| 한국어 | BGE-M3는 다국어 학습 모델로 한국어 성능 검증됨. 대안 KURE-v1도 후보 |
| 리스크 | 최초 모델 다운로드 ~2.3GB. CPU 추론 지연 → 배치 크기 조정 |
| 항목 | 내용 |
|---|---|
| 결정 | SQLite 단일 파일 (run.db), 벡터는 sqlite-vec 확장 또는 numpy 인메모리 |
| 대안 | Postgres + pgvector (서버 필요) / LanceDB (파일이지만 의존성 추가) |
| 근거 | 지표 121개 + 요구사항 42개 + 청크 수천 개. 벡터DB를 도입할 규모가 아니다. 단일 파일은 실행 결과를 통째로 첨부·비교·재현할 수 있다 |
| 전환 조건 | 다중 사용자 웹 UI(Phase 7) 도입 시 Postgres 검토 |
Prefect·Dagster·Airflow를 쓰지 않는다. 이유:
@stage(inputs=["doc.pdf"], version="m2-v3")
def convert(doc): ... # 입력 해시 + 코드 버전 → 캐시 키
LLM 호출 결과도 같은 캐시에 들어간다 → 재실행 시 API 비용 0. 개발 중 가장 큰 비용 절감 수단이다.
| 영역 | 선정 | 비고 |
|---|---|---|
| 리포팅 | Jinja2 → Markdown, xlsxwriter → XLSX, 기존 build_site.py → HTML |
HTML 렌더러는 이미 동작 |
| CLI | Typer | pia-audit run/convert/kb-build/report |
| 설정 | pydantic-settings + config.yaml |
룰·모델·임계값 외부화 |
| 스키마 | Pydantic v2 | 구조화 출력과 직결 |
| 테스트 | pytest + 스냅샷 + 골든셋 | §07 참조 |
| 로깅 | structlog(JSON) + 토큰/비용 회계 | 비용 회계는 NFR이 아니라 Phase 1 필수 — 없으면 최적화 판단 불가 |
| 토큰 계산 | client.messages.count_tokens |
tiktoken 금지 — OpenAI 토크나이저이며 Claude에서 15~20% 과소계산 |
| 모델 | 입력 | 출력 | 컨텍스트 |
|---|---|---|---|
claude-opus-5 |
$5.00 | $25.00 | 1M |
claude-sonnet-5 |
$3.00 (~2026-08-31 도입가 $2.00) | $15.00 (도입가 $10.00) | 1M |
claude-haiku-4-5 |
$1.00 | $5.00 | 200K |
캐시 읽기 ≈ 입력가 × 0.1 / 캐시 쓰기 = 입력가 × 1.25(5분) 또는 × 2(1시간) / Batch API = 전 항목 50%
안내서 408,506자를 토큰으로 환산하면 — 한국어+마크다운 혼합 기준 대략 1.5~2.5자/토큰으로 잡아 약 165K~270K 토큰(반드시 count_tokens로 실측할 것).
| 방식 | 프리픽스 토큰 | Opus 5 캐시 읽기 비용/호출 | 구현 복잡도 |
|---|---|---|---|
| 안내서 원문 전체를 캐시 | ~200K | ~$0.10 | 낮음 |
| 지식베이스만 캐시 | ~20K | ~$0.01 | 중간(M4 필요) |
| RAG (청킹·검색·재구성) | ~5K | ~$0.003 | 높음 |
중간을 택한다. RAG는 비용을 10배 더 줄이지만, 청킹 경계에서 지표가 잘리고 검색 실패가 조용한 누락으로 이어진다 — 이 도구의 핵심 실패 모드(§05 원칙 3)와 정면 충돌한다. 지식베이스 방식은 121개 지표를 항상 전부 본다는 보장을 유지하면서 비용을 10배 줄인다. 이것이 M4가 "정리"가 아니라 압축 단계인 이유다.
| 단계 | 호출 | 모델 | 추정 |
|---|---|---|---|
| M3 슬라이드 판독 | 50 | Sonnet 5 | ~$1.5 |
| M6 커버리지 | 42 | Sonnet 5 + Batch | ~$0.8 |
| M7 방법론 룰 | 16 | Opus 5 | ~$1.6 |
| M8/M9 | — | 없음 | $0 |
| 합계 | 약 $4~6 / 건 |
이 숫자의 지위: 실측이 아니라 단가 × 추정 토큰이다. Phase 1의 토큰 회계가 붙는 즉시 실측으로 교체한다. 다만 자릿수는 신뢰할 만하다 — 컨설턴트 1인시 대비 압도적으로 싸다는 판단에는 충분하다.
count_tokens 사전 호출 — 대형 요청 전 예상 비용 로깅| 목표 | 공공 개인정보 컨설팅 산출물의 준거성 격차를 근거와 함께 자동 검출한다 |
| 성공 정의 | 이번 세션에서 사람이 찾은 13개 Finding을 도구가 재현하고, 준거성 양호한 문서에서 HIGH 오탐 ≤1건 |
| 비목표 | 컨설팅 산출물 자동 작성. 법률 자문. 정성 품질("충실히 작성되었는가") 자동 판정 |
| 사용자 | 사용 시점 | 대체하는 것 |
|---|---|---|
| 발주기관 평가담당 | 제안서 평가 | 제안서 N건 × 요구사항 42개 수작업 대조 |
| 발주기관 검수담당 | 중간·완료보고 | RFP 산출정보 대비 납품물 대조 |
| 컨설팅사 PM | 제출 전 | 미기입·결번·누락 셀프체크 |
| 컨설팅사 품질조직 | 납품 전 | 안내서 체크리스트 12항목 자가진단 |
| 지표 | 목표 | 측정 |
|---|---|---|
| 골든셋 재현율 | 13/13 | CI 회귀 |
| HIGH 오탐 | ≤1건/문서 | 양호 문서 5건 |
| 인용 무결성 | 불일치 0 | 전수 문자열 대조 |
| 변환 정확도 | 띄어쓰기 ≥95%, 표 셀 손실 0 | 샘플 10건 육안 |
| 1회 분석 비용 | ≤$10 | 토큰 회계 |
| 분석 소요 | ≤30분 (이미지 문서 포함) | 실행 로그 |
| # | 리스크 | 영향 | 대응 |
|---|---|---|---|
| R1 | PyMuPDF AGPL — 배포·SaaS 전환 시 소스 공개 의무 | 높음 | Phase 1에 백엔드 추상화 + MIT/Apache 대체 경로 동시 구현 (§4.2) |
| R2 | API 계약 변경 — 모델 세대마다 파라미터가 바뀜(§4.6이 실제 사례) | 높음 | 모델 파라미터를 설정 파일 한 곳에 격리. 모델 ID·베타 헤더 버전 고정. 업그레이드는 골든셋 회귀로 검증 |
| R3 | 기준문서 개정 — 안내서는 3년 주기 + 수시 개정 | 중 | M4-F05 버전 diff. 개정 시 지표 추가/삭제 자동 산출 |
| R4 | 오탐 신뢰 붕괴 — HIGH 오탐 한 건이 도구 전체 신뢰를 깎는다 | 높음 | INSUFFICIENT_EVIDENCE 3값 판정. 요약본 입력 시 MISSING 자동 강등 |
| R5 | 보안 — 미공개 사업정보·실명 인력정보의 외부 전송 | 높음 | --offline 모드 1급 지원. 전송 항목 로깅. 렌더 이미지 처리 후 파기 |
| R6 | GPU 부재 — 로컬 대안 경로가 좁다 | 중 | 임베딩 CPU 배치로 감당. 로컬 VLM은 범위 밖으로 확정 |
| R7 | 의미 동등성 미탐지 — 다른 용어로 같은 절차를 기술한 경우 | 중 | MISSING은 사람 확인 필수로 명시. 임베딩 후보를 3개까지 LLM에 제시 |
| Phase | 기간 | 누적 |
|---|---|---|
| P1 변환 기반 | 2주 | 2주 |
| P2 결정론 검사 | 2주 | 4주 |
| P3 지식베이스 | 3주 | 7주 |
| P4 룰 엔진 | 3주 | 10주 |
| P5 이미지 판독 | 2주 | 12주 |
| P6 리포팅 | 2주 | 14주 |
| P7 웹 UI (선택) | 3주 | 17주 |
P2 종료(4주) 시점에 이미 실사용 가능하다. LLM 없이 미기입·결번·산출물 누락을 잡아내며, 이번 세션 §4의 6건이 전부 P2 범위다. 상세 슬라이스 계획은 07_개발계획서_vertical_slice.md.
ant auth login 프로파일)[project]
requires-python = ">=3.12"
dependencies = [
"pymupdf>=1.28", # AGPL — 4.2 참조
"pdfplumber>=0.11", # MIT, 대체 백엔드
"pypdfium2>=4", # BSD/Apache, 대체 렌더러
"anthropic>=0.116",
"pydantic>=2.9",
"pydantic-settings>=2",
"typer>=0.12",
"jinja2>=3.1",
"xlsxwriter>=3.2",
"sentence-transformers>=3",
"structlog>=24",
]
[project.optional-dependencies]
offline = ["paddleocr>=2.8", "paddlepaddle>=2.6"]
hwp = ["pyhwp>=0.1"] # GPL — 4.3 참조
| 패키지 | 라이선스 | 조치 |
|---|---|---|
| PyMuPDF | AGPL-3.0 / 상용 | ⚠️ 추상화 격리 (§4.2) |
| pyhwp | GPL | ⚠️ 서브프로세스 격리 검토 |
| pdfplumber / pypdfium2 / anthropic / pydantic / typer / jinja2 / xlsxwriter | MIT · BSD · Apache-2.0 | 제약 없음 |
| BGE-M3 | MIT | 제약 없음 |
| PaddleOCR | Apache-2.0 | 제약 없음 |
AGPL·GPL 2건이 이 스택의 유일한 법적 리스크다. 사내 도구로만 쓴다면 문제없고, 외부 제공 계획이 생기면 §4.2의 대체 경로로 전환한다. 전환 비용을 낮게 유지하는 것이 Phase 1의 설계 목표 중 하나다.