CoolFace
Apppublic

hskim-solv/bidmate-docagent

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
App README

BidMate Agent

RFP 문서 이해를 위한 Agentic RAG 시스템

![License: MIT](LICENSE) ![PR Eval Delta](https://github.com/hskim-solv/BidMate-DocAgent/actions/workflows/pr-eval.yml) ![Python 3.11](pyproject.toml) ![Engineering notes](https://hskim-solv.github.io/BidMate-DocAgent/) ![Open in Colab](https://colab.research.google.com/github/hskim-solv/BidMate-DocAgent/blob/main/demo/bidmate_quickstart.ipynb) ![Open in HF Spaces](https://huggingface.co/spaces/hskim-solv/bidmate-docagent)

<!-- Hero demo asset slot. Recording guide: docs/deployment.md#recording-the-demo-video. Replace docs/assets/demo.gif with the actual asset once captured; renders inline in the GitHub README and in the pinned-repo profile card. --> [image]

🚀 Live demo

경로상태비고
Colab 5분 quickstart![Open in Colab](https://colab.research.google.com/github/hskim-solv/BidMate-DocAgent/blob/main/demo/bidmate_quickstart.ipynb)클론 / 설치 없이 브라우저에서 바로 grounded answer 1건 실행
Streamlit on HF Spaces![Open in HF Spaces](https://huggingface.co/spaces/hskim-solv/bidmate-docagent)브라우저 한 번 클릭으로 라이브 데모 도달. 🔁 Space sleep 시 cold-start 약 30–60s — 그동안은 아래 docker / Colab 행 사용. 운영: `docs/deployment.md#hugging-face-spaces`
Self-host (Fly.io / Railway / Spaces CLI)배포 가이드: `docs/deployment.md`동일 Dockerfile + Streamlit SDK로 본인 계정에 배포
One-line docker rundocker run -p 8501:8501 -p 8000:8000 -e BIDMATE_DEMO_MODE=both ghcr.io/hskim-solv/bidmate-demo:latest클론 없이 published image 로 Streamlit + FastAPI 동시 실행 (`docs/deployment.md` 참고)
FastAPI Swaggermake api 후 /docs프로그래매틱 사용·통합 테스트용
로컬 1분 시작make index && make demohttp://localhost:8501
데모 비디오 (2~3분)녹화 가이드: docs/deployment.md#recording-the-demo-video라이브 배포 후 README 상단에 embed 예정
📈 Live leaderboardhttps://hskim-solv.github.io/BidMate-DocAgent/leaderboard/메인 브랜치 머지마다 자동 누적되는 headline metric time-series + bootstrap CI 밴드 (ADR 0005 aggregate-only)
데모 비디오 (2~3분)후속 작업 — 가이드: docs/deployment.md#recording-the-demo-video배포·녹화 완료 후 본 표 상단에 embed
엔지니어링 노트hskim-solv.github.io/BidMate-DocAgent결정의 왜와 측정 결과를 정리한 GitHub Pages 사이트 (블로그 3편 + 1-page deep-dive)

데모 UI는 3개 pipeline preset(naive_baseline · agentic_full · agentic_full_llm)을 라디오 버튼으로 전환하고, 같은 질의에 대한 extractive vs LLM 합성(ADR 0011) 답변을 side-by-side로 비교합니다. 모든 claim은 evidence chunk_id로 추적 가능하며, abstention 케이스(예: 기관 A의 양자암호 적용 방안은?)는 🔴 insufficient status로 명시되어 근거 부족을 정직하게 인정합니다.

Business context

RFP/제안요청서는 한국 B2B/공공 입찰 시장에서 평균 수십~수백 페이지에 달하며, 검토자는 (a) 요건 추출, (b) 평가 기준 매핑, (c) 모순/누락 탐지를 수동으로 수행한다. 도메인 보고에 따르면 RFP 1건당 검토 시간은 복잡도에 따라 약 4–20시간 범위로 추정되고, 누락된 요건은 입찰 실격 또는 계약 조건 불이익으로 직결된다. 본 시스템은 위 세 단계를 grounded answer 형태로 자동화해 검토자가 판단에 집중하도록 시간을 절감하는 것을 목표로 한다.

비즈니스 임팩트는 보수적 추정 범위로 표기했다. 정확한 시간 단축률은 도메인 사용자 평가가 필요하며, 본 저장소의 정량 지표(groundedness, citation precision 등)는 검토 보조 품질의 proxy로 측정됐다. 비즈니스 임팩트 실증은 다음 실험 사이클 항목.

Why extractive, not generative?

기본 pipeline(naive_baseline, agentic_full)은 외부 LLM 호출 없이 retrieved evidence에서 claim을 추출하는 extractive grounded-answer입니다. Generator를 의도적으로 extractive로 한정한 4가지 이유:

  1. 1.재현성: 외부 API 키 / 네트워크 / 모델 버전 의존이 0. CI에서 매 PR마다 동일 평가셋을 같은 결과로 재실행 가능.
  2. 2.비용 영점: query당 LLM token cost = 0. retry policy의 cost-quality trade-off가 latency 1축으로 단순화됨.
  3. 3.LLM-as-judge confound 제거: generator와 verifier가 같은 LLM이 아니므로 self-consistency 편향 없음.
  4. 4.Citation grounding 내재화: claim이 retrieved evidence에서만 도출되므로 hallucination이 구조적으로 불가능.

한계 / Trade-off: 생성 유창성에 제약이 있습니다. RFP 도메인은 정확도와 근거 추적이 유창성보다 우선이므로 수용 가능한 trade-off로 판단했습니다. 결정의 contract와 답변 출력 정책은 ADR 0003 및 `docs/answer-policy.md`을 참고하세요.

Additive LLM synthesis ablation (ADR 0011)

agentic_full_llm preset은 위의 extractive 파이프라인을 교체하지 않고, summary / answer_text 렌더링 단계에서만 LLM 합성을 추가 ablation으로 활성화합니다. claims, citations, status, insufficiency는 결정적 verifier가 그대로 결정하며 — LLM이 evidence에 없는 chunk_id를 인용하면 합성 결과가 거부되고 extractive 렌더러로 fallback됩니다. Public CI는 stub 백엔드(pass-through, 결정적)로 zero-regression 계약을 잠그고, real-data + 라이브 데모는 BIDMATE_SYNTHESIS_BACKEND=anthropic으로 전환합니다. 자세한 설계는 ADR 0011을 참고하세요.

LLM Ops observability (ADR 0013)

각 run_rag_query 호출은 stage별 trace span을 자동으로 emit합니다 — query_analysis, context_resolution, retrieve, verify, answer_generation, synthesis. 기본값은 BIDMATE_TRACE_BACKEND=none(zero overhead noop); langfuse 또는 otel로 전환하면 LangFuse self-hosted, Honeycomb, Datadog, Grafana Tempo 등 OTLP 호환 백엔드 어디든 연결됩니다. Streamlit 데모는 각 답변 아래 "🔍 View trace" 링크를 렌더링하고, FastAPI 응답과 CLI outputs/answer.json 모두 diagnostics.trace_url을 노출합니다. 백엔드 장애(missing dep / credentials / span exception / finish exception)는 모두 fail-closed — query는 절대 깨지지 않고 diagnostics.trace_error에 사유가 남습니다. 설계: ADR 0013, 설정 레시피: `docs/observability.md`.

TL;DR

  • —문제: 길고 복잡한 RFP 문서에서 실무 의사결정에 필요한 핵심 조건(예산/일정/요구사항/제출조건)을 빠르게 찾기 어렵습니다.
  • —해결: 질문 유형 분석 + metadata-first 검색 + local dense retrieval/reranking + 근거 검증/retry를 결합한 Agentic RAG 파이프라인을 구현했습니다.
  • —시스템 설계: 외부 LLM(GPT/Claude 등) 호출 없이, 검색 evidence에서 claim을 추출하고 citation을 연결하는 extractive grounded-answer 파이프라인입니다. 재현성 / 비용 영점 / LLM-as-judge confound 제거를 위해 generator를 의도적으로 extractive로 한정했습니다 (ADR 0003, docs/answer-policy.md).
  • —성과: 공개 synthetic 평가셋 n=42 (singledoc 14 / comparison 10 / followup 9 / abstention 9) 기준 단일 추출/다문서 비교/후속질문/부재판별의 근거 기반 응답 품질을 검증했습니다. 통계적 유의성 한계와 다음 실험 우선순위는 아래 성능표 캐비뱃에 정직하게 명시했습니다.
  • —재현: 실행 방법과 평가 절차를 문서화해 동일 환경에서 재검증 가능하도록 구성했습니다.

Key technical contribution — comparison-aware balanced top-k

본 프로젝트의 가장 큰 차별점은 RFP 비교 질의(query_type == "comparison")에서 발생하는 한쪽 문서 starvation을 막는 balanced top-k retrieval ranking 입니다. 일반 agentic RAG 튜토리얼에는 없는 RFP 도메인-특화 ranking 결정입니다.

문제 패턴: 단순 global top-k 컷은 score가 높은 한 문서가 결과 슬롯을 과점하면 다른 비교 대상 문서가 evidence에서 누락됩니다. 이로 인해 verifier가 근거 부족을 감지해 불필요한 retry를 트리거하거나 abstention으로 응답하는 실패가 발생합니다.

설계: Query Analyzer가 추출한 비교 target 별로 min_per_target=1 이상 evidence를 보장한 뒤, 남은 슬롯을 글로벌 score 순으로 채웁니다. 단일 문서 질의에서는 no-op으로 동작해 추가 비용이 없습니다.

구현 / 테스트 / 설계 문서:

  • —구현: `apply_comparison_balance()` (rag_core.py:1854), `retrieve()` 내 호출 (rag_core.py:1838), 기본 설정 `DEFAULT_COMPARISON_BALANCE` (rag_core.py:41)
  • —테스트: asymmetric corpus 균형 보장 (tests/test_fuzzy_retrieval.py:750), disabled 시 global ordering 보존 (tests/test_fuzzy_retrieval.py:769), single-doc no-op (tests/test_fuzzy_retrieval.py:781)
  • —설계 문서: `docs/comparison-ranking.md` — target 식별, balance 알고리즘, diagnostics 스키마, eval 지표
One-line pitch: RFP 비교 질의의 실패 패턴(한쪽 문서 starvation → verifier retry → abstention)을 발견하고, 이를 막는 retrieval ranking 전략을 설계·구현·테스트로 검증한 것이 본 프로젝트의 핵심 기여입니다.

Quick Review

1) 문제 (Problem)

  • —RFP는 문서 길이·형식·용어가 다양해 단순 키워드 검색만으로는 정확한 의사결정 지원이 어렵습니다.
  • —특히 다문서 비교, 후속 질문, 문서 부재 정보 판별이 병목이 됩니다.

2) 해결 (Solution)

  • —Query Analyzer: 질문 유형 및 핵심 엔터티(기관/사업/주제) 추출
  • —Planner: 메타데이터 필터 중심 검색 전략 수립
  • —Retriever: dense retrieval + reranking
  • —Verifier/Retry: 근거 부족 시 재검색·재시도 후 grounded answer 생성
  • —Answer Policy: claim 단위 citation, partial/insufficient 상태, 사람이 읽는 answer_text를 함께 출력

3) 성과 (Outcome)

  • —평가 범위: 단일 문서 추출, 단일 문서 심화 탐색, 다문서 비교, 후속 질문, 부재 정보 판별
  • —핵심 지표: Answer Accuracy, Groundedness, Citation Precision, Claim Citation Alignment, Abstention Accuracy, Latency, Retry Rate
  • —상세 수치/해석은 아래 성능표 및 docs/ 문서 참고

4) 재현 (Reproducibility)

  • —실행/평가 절차를 README에 요약하고, 상세 배경/실패사례/회고는 docs/로 분리
  • —원본 RFP 비공개 제약을 고려해 공개 synthetic RFP 문서와 평가셋으로 재현 가능성 확보
  • —크로스머신 재현성 해시: make reproduce가 환경-불변 metric subset(accuracy/groundedness/citationprecision/abstention/CI bounds, latency·timestamp 제외)에 SHA-256을 찍어 `reports/evalsummary.reproducibility.sha256에 기록. 다른 호스트(Linux container 등)에서 BASELINE=<hash> make reproduce`로 매치 검증 가능 — 정합성이 깨지면 exit 2로 알려줌.
  • —한국어 공개 일반 텍스트 (KorQuAD 2.1, n=150): make korean-public-eval로 한국어 위키 out-of-domain generalization 검증 가능 (ADR 0018). 합성 surface와 분리 — 절대 CI 게이트가 아니며, RFP 도메인-특화 시스템이 일반 한국어 텍스트에서 어떻게 trade-off하는지 측정용. 자세한 metric 해석은 `eval/korean_public/README.md`.

Limitations & honest signals

본 프로젝트 결과를 해석할 때 명시할 한계 5가지. 각 항목은 caveat이 기록된 위치를 cross-reference.

  • —평가셋 n=42, bootstrap 95% CI 보고됨 (seed=17, 1000 resamples) — 위 성능표의 모든 metric은 mean과 CI 둘 다 보고합니다. 이 시각화가 드러내는 핵심 사실: full vs naive_baseline의 accuracy 차이(0.906 vs 0.844)는 CI가 겹쳐 통계적으로 약함(0.781–1.000 vs 0.719–0.969), 반면 citation_precision 차이(0.905 vs 0.512)는 CI가 분리되어 진짜 효과(0.821–0.976 vs 0.393–0.631). full / no_rerank / hierarchical이 동일 metric을 보이는 것은 n=42에서 검출 불가임을 CI가 명시(±0.12). n≥100 확장은 다음 사이클. 구현은 eval/bootstrap.py.
  • —Extractive-only trade-off — generator 유창성 한계는 인정. RFP 도메인 정확도/근거 추적이 유창성보다 우선이라 수용한 결정. ADR 0003 참고.
  • —HWP native parse — v3 로드맵 작업 영역 — 현재 기본 경로는 CSV 텍스트 컬럼 fallback (visual_fallback_hwp 마커). 한국 정부조달 RFP 도메인에서 HWP 비율이 높으므로 v3 사이클에서 두 native 경로(hwp5txt 텍스트 추출 vs libreoffice --headless HWP→PDF → visual-v2)를 비교 측정 중. 비교 설계와 결과 기록: `docs/hwp-extraction-comparison.md`. 사촌 spike(pyhwp Python API): `docs/hwp-native-spike.md`.
  • —Embedding 디폴트 = MiniLM-L12-v2 ([ADR 0019](docs/adr/0019-embedding-default-stays-minilm.md) 잠금) — 1차 측정(MiniLM-L12-v2 vs multilingual-e5-base, 2026-05-11): full agentic 파이프라인에서는 0pp Δ, naive_baseline에서는 e5-base가 accuracy +18.8pp — metadata-first filtering이 임베딩 선택에 robust함을 실증. 2차 사이클(BGE-M3 / e5-large-instruct / KURE-v1, 2026-05-12) 측정 시도: Python 환경 mismatch(torch == 2.2.2 vs CVE-2025-32434의 torch >= 2.6 요구, huggingface-hub == 1.14.0 vs transformers의 < 1.0 요구)로 측정 연기됨. ADR 0019에 재오픈 조건(env 업그레이드 + full 파이프라인에서 ≥+5pp accuracy/groundedness 증가)을 명시 — 다음 contributor가 같은 작업을 반복하지 않도록 잠금. 측정 결과·러너: `docs/embedding-ablation.md`.
  • —External baseline 프레임워크 도입, 실측 다음 사이클 — LangChain RetrievalQA / LlamaIndex QueryEngine 비교는 ADR 0009 메서드론(docs/adr/0009-external-baseline-comparison.md)에 따라 scripts/compare_external_baselines.py로 분리 실행합니다. 대칭 metric subset(accuracy / retrievalrecall@k / latency)만 보고하고, 외부 system이 producer하지 않는 *비대칭 metric*(citationprecision / claimcitationalignment / abstentionaccuracy / answerformat_compliance)은 null로 명시 — 이게 "왜 자체 구축?" 질문에 대한 정량적 답변 자체입니다. 현재는 stub backend(결정적, plumbing 검증용)만 실행 가능; 실 LangChain·LlamaIndex 실행은 별도 cycle.

Portfolio Review Guide

채용 검토자가 빠르게 확인할 수 있도록 5분 리뷰 경로와 포트폴리오 관점의 핵심 질문을 함께 정리했습니다. 상세한 의사결정 흐름은 `docs/portfolio-case-study.md`를, 시니어 엔지니어링 시그널 관점의 narrative와 인터뷰 talking point는 `docs/senior-positioning.md`를, LLM 모델 교체·업그레이드 운영 절차는 `docs/model-upgrade-playbook.md`를 참고하세요.

5-minute reviewer path

처음 보는 리뷰어는 아래 순서로 확인하면 문제 정의, 데모, 검증 근거를 짧게 훑을 수 있습니다. 명령과 대표 질의는 `docs/reviewer-evidence-pack.md`에 모았습니다.

  1. 1.문제 이해: 이 README의 TL;DR, Quick Review, 아키텍처를 확인합니다.
  2. 2.데모 실행: scripts/build_index.py로 인덱스를 만들고 app.py로 대표 비교 질의를 실행합니다.
  3. 3.예시 출력 확인: outputs/answer.json에서 answer.status, claim별 citation, top-level evidence를 확인합니다.
  4. 4.평가/ablation 확인: reports/eval_summary.json, docs/ablation-results.md에서 metric과 설계 선택의 영향을 확인합니다.
  5. 5.실패/개선 근거 확인: docs/failure-cases.md, docs/retrospective.md에서 한계와 다음 실험 방향을 확인합니다.

이 프로젝트가 답하려는 핵심 질문은 다음 7개입니다.

  1. 1.왜 이 문제를 골랐는가: RFP QA는 단순 검색보다 다문서 비교, 근거 정합성, 부재판별이 중요해 RAG 역량을 검증하기 좋습니다.
  2. 2.성공 기준을 어떻게 정했는가: 답변 정확도뿐 아니라 Groundedness, Citation Precision, Abstention, Latency/Retry를 함께 봅니다.
  3. 3.어떤 실패가 났는가: 메타데이터 불일치, 비교 질의의 한쪽 문서 누락, 후속 질문의 엔터티 소실을 주요 실패로 분리했습니다.
  4. 4.어떤 실험을 비교했는가: keyword-only, dense-only, metadata-first+dense/rerank, verifier/retry 유무를 비교 축으로 삼았습니다.
  5. 5.왜 A안이 아니라 B안을 택했는가: 생성 유창성보다 근거 재현성과 검증 가능성을 우선해 metadata-first + verifier/retry 구조를 채택했습니다.
  6. 6.에이전트 산출물을 어떻게 검증했는가: evidence doc id, expected terms, abstention 여부, README metric sync check로 산출물을 검증합니다.
  7. 7.다음 실험을 왜 그렇게 설계했는가: 평가셋 확대, citation 자동 검증, latency/retry 비용 분석을 다음 병목 확인 실험으로 둡니다.

엔지니어링 노트 (GitHub Pages)

설계 결정의 왜와 측정 결과를 정리한 보조 사이트 — hskim-solv.github.io/BidMate-DocAgent. 본 README가 source of truth고, 노트는 narrative와 의사결정 배경을 보강합니다.


Demo / 산출물

비교 질의에 대한 grounded answer 출력 예시 (`outputs/answer.json` 발췌). 각 claim에는 출처 문서/섹션 citation이 붙어 있고, diagnostics에 latency와 사용된 embedding backend가 함께 기록된다.

json
{
  "query": "기관 A와 기관 B의 AI 요구사항 차이 알려줘",
  "answer": {
    "schema_version": 2,
    "status": "supported",
    "claims": [
      {
        "target": "기관 A",
        "claim": "사업 개요 — 기관 A는 AI 품질관리 플랫폼 구축을 추진한다.",
        "citations": [
          {"doc_id": "rfp-agency-a-ai-quality", "section": "문서 전체"}
        ]
      },
      {
        "target": "기관 B",
        "claim": "기관 B의 핵심 AI 요구사항은 데이터 거버넌스, MLOps 배포 자동화, 모델 모니터링이다.",
        "citations": [
          {"doc_id": "rfp-agency-b-mlops-governance", "section": "문서 전체"}
        ]
      }
    ]
  },
  "diagnostics": {"latency_ms": 1.91, "embedding_backend": "hashing", "pipeline": "naive_baseline"}
}
  • —질의 실행 결과: outputs/answer.json
  • —평가 요약: reports/eval_summary.json
  • —Planner/rewrite trace: reports/traces/<run>/<case>.trace.json (eval/run_eval.py 실행 시 생성, Git 미추적). 사람이 읽는 요약은 python3 scripts/replay.py reports/traces/<run>/<case>.trace.json.
  • —Benchmark registry: benchmarks/registry.json
  • —Benchmark local artifacts: artifacts/benchmarks/ (gitignored)
  • —PDF/HWP ingestion 진단 리포트: data/index/ingestion_report.json (--metadata_csv 사용 시)
  • —Visual parsing v2 artifact: data/index/visual_artifacts/*.visual.json (--visual_input_dir 또는 --ingestion_mode visual 사용 시)
  • —Parser-stage 평가 리포트: reports/parser_eval_summary.json (eval/run_parser_eval.py 사용 시)

답변 출력 정책

outputs/answer.json의 answer는 schema_version: 2인 구조화 객체입니다. status는 supported, partial, insufficient 중 하나이며, status_reason은 machine-readable 사유를 담습니다. claims의 각 항목은 target, claim, support, citations를 포함합니다. 근거가 부족하면 claims를 비우고 insufficiency에 사유와 확인 대상이 기록됩니다.

CLI와 리뷰 편의를 위해 같은 내용을 사람이 읽기 쉬운 answer_text로도 제공합니다. 자세한 예시는 `docs/answer-policy.md`를 참고하세요.

Planner와 query rewrite 결정은 outputs/answer.json의 trace와 eval 실행 후 reports/traces/에서 확인할 수 있습니다. Grounding/eval hardening 변경 사항과 trace 해석 방법은 `docs/grounding-eval-hardening.md`를 참고하세요.

Evidence boundary defense

외부 RFP 문서에서 온 retrieved chunk가 chat-template 토큰(<|im_start|> 등), role 태그(SYSTEM: / ASSISTANT:), 또는 instruction-override 문구를 포함할 수 있습니다. neutralize_instruction_patterns() (rag_core.py)가 verifier와 LLM judge로 흐르는 evidence text를 normalize해 downstream LLM 소비자가 RFP 본문에 의해 영향받지 않도록 합니다. 본문 내용은 보존되고 marker로 wrapping만 됩니다 — 결정은 ADR 0008, regression 테스트는 `tests/test_prompt_injection_regression.py`.

Baseline policy

기본 CLI/eval reference는 naive_baseline입니다. 이 baseline은 fixed-size chunking, hashing dense top-k=4 retrieval, minimal grounded extractive answer prompt만 사용하며 metadata-first filtering, rerank, verifier/retry는 제외합니다.

현재 agentic pipeline은 agentic_full preset으로 유지합니다. 기본 control과 비교하려면 app.py --pipeline agentic_full 또는 benchmark의 full run을 사용합니다.


핵심 성능표 (실측)

측정 환경:

  • —시스템 타입: Extractive-only — 외부 LLM(GPT/Claude 등) 호출 없음, 의도된 설계입니다.
  • —임베딩 백엔드: 아래 metric table은 hashing (CI source of truth) 측정값입니다. sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 비교는 Latency by embedding backend 보조 표를 참고하세요.
  • —측정 범위: Latency p95 컬럼은 queryanalysis + contextresolution + answergeneration 합의 walltime입니다. retrieve / verify stage는 `reports/evalsummary.json의 stage_latency` 블록에서 별도 확인할 수 있습니다.
  • —실행 환경: macOS / CPU-only / Python 3.11 / 단일 워커.
  • —Cold start 분리: 첫 질의의 임베딩 모델 로드 시간은 별도 cold_start_samples 블록으로 분리 측정합니다 (hashing ≈ 2.1ms / sentence-transformers ≈ 5.7s).
  • —평가셋: 공개 synthetic n=42 (singledoc 14 / comparison 10 / followup 9 / abstention 9). 비공개 RFP eval은 ADR 0005에 따라 분리합니다.

<!-- METRICS_TABLE:START --> | Category | Metric | Score (95% CI) | |---|---:|---:| | Overall | Answer Accuracy | 0.844 (0.719–0.969) | | Single-doc extraction | Answer Accuracy | 1.000 (1.000–1.000) | | Multi-doc comparison | Groundedness Rate | 0.700 (0.400–0.900) | | Follow-up | Answer Accuracy | 0.750 (0.375–1.000) | | Evidence | Citation Precision | 0.512 (0.393–0.631) | | Evidence | Claim Citation Alignment | 0.974 (0.921–1.000) | | Evidence | Answer Format Compliance | 0.667 (0.524–0.810) | | Abstention | Abstention Accuracy | 0.222 (0.000–0.556) | | System | Latency (p50/p95) | p50 3.2ms / p95 6.5ms | | System | Retry Rate | 0.000 (0.000–0.000) |

Ablation comparison

RunPipelineTop-kMetadata-firstRerankVerifier/RetryAccuracyGroundednessCitationClaim AlignFormatAbstentionRetryLatency p95
naive_baselinenaive_baseline4offoffoff0.844±0.120.714±0.140.512±0.120.974±0.050.6670.3000.0006.5ms
fullagentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.4ms
full_llmagenticfullllmautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.4ms
hierarchicalagentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.5ms
nometadatafirstagentic_fullautooffonon0.844±0.120.881±0.100.679±0.110.968±0.060.8571.0000.0003.6ms
no_rerankagentic_fullautoonoffon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.4ms
noverifierretryagentic_fullautoononoff0.906±0.120.762±0.140.762±0.141.000±0.000.7140.3000.0002.6ms
hybrid_bm25agentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.8ms
hybridbm25k10agentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.5ms
hybridbm25k30agentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.4ms
hybridbm25k100agentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.5ms
hybridbm25extra_stopwordsagentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.5ms
full_rerankeragentic_fullautoononon0.906±0.120.929±0.070.905±0.081.000±0.000.9051.0000.3104.4ms
Values shown as mean±half-width for the 95% bootstrap CI (n=cases, 1000 resamples, seed=17). The non-CI columns (Format, Abstention, Retry) are point estimates; their CIs appear in the detailed main table above.

<!-- METRICS_TABLE:END -->

Ablation 해석 — CI가 말해주는 검출 한계 vs 실측 trade-off: no_rerank / hierarchical / full_llm은 full과 primary metrics(Accuracy 0.906±0.12 / Groundedness 0.929±0.07 / Citation 0.905±0.08)가 동일하게 보이는데, *이는 기능이 동등해서가 아니라 n=42 + bootstrap CI가 차이를 검출하지 못해서* 입니다 — CI 폭이 너무 넓어 미세 차이는 noise에 묻힙니다. 세 ablation은 실제로 다른 코드 경로를 exercise합니다 — rerank scoring weight 차이([rag_core.py:1798–1803](rag_core.py)), hierarchical reassembly([rag_core.py:1836–1838](rag_core.py)), LLM 합성 경로([rag_synthesis.py](rag_synthesis.py)). 통계적 분리는 n≥100에서 가능. 한편 CI가 분리되는 진짜 효과: `no_metadata_first`의 citation precision은 0.679±0.11 (CI 0.571–0.786) — `full`의 0.905±0.08 (0.821–0.976)과 CI가 겹치지 않으므로** metadata-first filtering 효용이 통계적으로 입증됩니다. no_verifier_retry도 groundedness 0.762±0.14 (CI 0.619–0.881)가 full의 0.929±0.07 (0.857–1.000)과 거의 분리되어 verifier loop의 효용을 시사합니다.

Latency by embedding backend

동일 ablation runs를 두 임베딩 백엔드(hashing fallback vs sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)로 측정한 p95 latency 비교입니다. agentic pipeline에서 두 백엔드의 정량 품질 차이는 작아 (full 기준 hashing accuracy 0.906 / groundedness 0.929) latency는 약 10–200× 차이가 납니다. CI/CD 재현성을 위해 hashing을 기본으로 사용하고, sentence-transformers는 production-grade 품질 비교용으로 로컬에서 별도 측정합니다.

Runp95 (hashing)p95 (sentence-transformers)Notes
naive_baseline1.6ms367.4msdense retrieval만 — 전체 corpus에 ST 임베딩 비용 직격
full1.9ms32.2msmetadata-first가 dense 호출을 우회해 비용 절감
hierarchical2.0ms30.0msfull과 동일 운영, retrieval_mode 차이만
nometadatafirst1.7ms15.4ms단순 dense — metadata 우회 없음
no_rerank1.9ms30.1msmetadata-first + 무 rerank
noverifierretry1.4ms16.9msverifier loop 제거

Cold start (모델 첫 로드): hashing ≈ 2.1ms / sentence-transformers ≈ 5.7s. ST cold start는 모델 캐시가 있어도 로드 + 초기 inference warm-up 비용이 발생합니다. 재실행 방법은 실행 방법 섹션의 --embedding_backend 플래그를 참고하세요.

데이터 범위: 성능표는 공개 synthetic RFP 평가셋(n=42) 기준이며, hashing 백엔드 측정값입니다. 원본 RFP 데이터는 비공개 제약으로 저장소에 포함하지 않았습니다. 통계적 유의성 한계: n=42는 ablation 차이의 통계적 유의성 검증에 충분치 않습니다. 평가셋 확대(n≥100)와 bootstrap CI 보고가 다음 실험 사이클의 최우선 항목입니다. Latency 해석: CLI 프로세스 walltime이며, 첫 질의의 모델 로드 시간은 위 cold-start sample로 분리 측정합니다. stage별 latency(retrieve/verify 포함)는 reports/eval_summary.json의 stage_latency 블록에서 확인할 수 있습니다. 시스템 설계 재확인: 위 latency 측정에는 외부 LLM API 호출 비용이 포함되지 않습니다(extractive-only). 검증된 설계 가치 — no_verifier_retry ablation에서 accuracy는 동일(full 0.906 = no_verifier_retry 0.906)하지만 groundedness가 큰 폭으로 하락(0.929→0.762, −16.7pp), abstention이 0.300으로 무너지는 점이 verifier/retry 루프가 근거 보존·부재 판별에 기여하는 설계 효용을 수치로 보여줍니다.

Synthetic LLM-judge (RAGAS-style, ADR 0012)

make synthetic-judge로 공개 synthetic eval(n=42)에 LLM-judge를 적용해 RAGAS-style faithfulness / answer-relevance 신호를 산출합니다. 공개 CI는 stub 백엔드만 호출(토큰 비용 0, 재현 가능) — live 점수는 개발자가 BIDMATE_SYNTHETIC_JUDGE_BACKEND=openai_compatible로 수동 실행 후 commit합니다. ADR 0006의 real-data-only judge 정책을 보존하면서 공개 surface에 신호를 노출하는 stub-default 패턴(ADR 0012).

SlicenFaithfulnessAnswer relevanceGrounded rateAgreement w/ verifier
Overall420.760.740.881.00
single_doc140.850.801.001.00
comparison100.850.801.001.00
follow_up90.600.630.671.00
abstention90.680.690.781.00
현재 수치는 stub 백엔드 기준 — verifier status를 거울 반사하므로 agreement_with_verifier=1.0이고 faithfulness / answerrelevance는 status-derived fixture(supported→0.85, partial→0.5, insufficient→0.1)입니다. **진짜 RAGAS 신호가 아닙니다** — 단지 schema-stable plumbing입니다. 실제 LLM judge 점수를 보려면 live 백엔드로 `make synthetic-judge`를 다시 돌리고 `reports/syntheticjudge.aggregate.json`을 commit합니다.

아키텍처 (요약)

mermaid
flowchart TD
    Q[User Query] --> A[Query Analyzer]
    A --> P["Planner<br/>metadata-first<br/><b>comparison-aware top_k</b>"]
    P --> RD["Dense channel<br/>MiniLM cosine"]
    P --> RB["Lexical channel<br/>BM25 (optional, ADR 0010)"]
    RD --> FU{retrieval_backend}
    RB --> FU
    FU -->|dense| W["Weighted fusion<br/>dense + lexical + metadata"]
    FU -->|hybrid| RRF["RRF k=60<br/>rank-based fusion"]
    W --> E[Evidence Aggregator]
    RRF --> E
    E --> V[Verifier / Retry Loop]
    V --> G["Answer Generator<br/>structured claims<br/><b>extractive — no LLM</b>"]
    G --> F[Final Response<br/>grounded with citations]

    classDef highlight fill:#fffbdd,stroke:#d4a017,stroke-width:2px,color:#000
    class P,G highlight
강조된 두 노드는 본 프로젝트의 핵심 설계 결정에 대응합니다. - Planner의 comparison-aware top_k → 상단 Key technical contribution — comparison-aware balanced top-k 섹션 - Answer Generator의 extractive — no LLM → 상단 Why extractive, not generative? 섹션

비교 질의(query_type == "comparison")에서는 단순 global top-k 컷이 한쪽 문서만 채워 verifier가 불필요한 retry를 트리거하는 문제를 막기 위해, 각 비교 대상에 최소 1개 이상의 evidence가 들어가도록 보장하는 balanced top-k 컷을 적용한다. Metadata filter staging, alias lexicon, follow-up carryover, ambiguity clarification, query-type top_k 진단은 docs/retrieval-hardening.md에 정리했다. 비교 ranking 상세 설계는 docs/comparison-ranking.md 참고.

retrieval_backend 는 직교 축이다: 기본값 dense 는 기존 weighted fusion (dense + lexical + metadata) 을 그대로 사용해 naive_baseline (ADR 0001) 을 보존하고, hybrid 는 BM25 채널을 dense 와 RRF (k=60) 로 결합한다. 자세한 근거는 ADR 0010 참고.


Korean RFP domain adaptations

본 시스템은 한국 정부조달/B2B RFP 도메인 특성을 반영해 다음 5가지를 의도적으로 다르게 설계했습니다. 일반 multilingual RAG 템플릿에는 없는 결정들입니다.

1) HWP 파일 처리 — v3 로드맵: native parse 비교 실험 진행 중

한국 정부조달 RFP의 상당 비율이 HWP/HWPX 포맷이므로 HWP 처리는 본 시스템의 핵심 v3 로드맵 항목입니다. 현재 기본 경로는 data_list.csv의 텍스트 컬럼을 본문 소스로 사용하고 visual_fallback_hwp 마커를 부여하는 CSV fallback이며, 이는 사용자가 사전에 추출한 plain text에 의존하는 외부 preprocessing 부채를 남깁니다. ADR 0001 baseline invariant에 따라 기본 경로는 변경 없이 유지하면서, v3에서 이 의존성을 native parse로 대체하기 위해 두 후보 경로를 비교 측정하고 있습니다.

  • —Path A — `hwp5txt` (pyhwp CLI): OLE binary를 파싱해 paragraph plain text만 추출. 표/이미지/layout 정보는 없음.
  • —Path B — `libreoffice --headless --convert-to pdf`: HWP→PDF 변환 후 기존 parse_pdf_artifact(visual-v2)에 그대로 통과시켜 텍스트 + 표 + page/bbox까지 수집.

비교 설계·실행 가이드·결과 기록은 `docs/hwp-extraction-comparison.md`에, 비교 스크립트는 `scripts/compare_hwp_extraction.py`에 있습니다. CSV 텍스트 컬럼 baseline vs pyhwp Python API의 1:1 spike는 사촌 이슈로 분리되어 있습니다(`docs/hwp-native-spike.md`). libreoffice 의존성은 CI에 포함하지 않고 로컬 실험 전용으로 운용합니다 (raw HWP 샘플 confidentiality + 200MB 급 의존성 부피 고려, ADR 0005 boundary).

  • —CSV 텍스트 로더 (현 기본): `HwpCsvTextLoader` (ingestion.py:104)
  • —Native opt-in loader (spike #167): `HwpNativeLoader` (ingestion.py:108)
  • —Visual fallback document: `make_hwp_fallback_document` (visual_ingestion.py:750)

2) Korean RFP 메타데이터 컬럼 컨벤션

공고 번호, 사업명, 발주 기관, 파일형식, 파일명, 텍스트 6개 컬럼을 REQUIRED_COLUMNS로 강제하고 metadata-first filter의 1차 필드로 사용합니다. 한국 조달 시스템의 표준 메타데이터 표기와 일치.

  • —컬럼 정의: `REQUIRED_COLUMNS` (ingestion.py:21)

3) Korean tokenization — 외부 형태소 분석기 미사용

[A-Za-z0-9]+|[가-힣]+ 정규식과 한국어 조사 제거 normalize만 사용합니다. 의도적으로 KoNLPy / soynlp / kiwipiepy 같은 형태소 분석 라이브러리를 도입하지 않았습니다. 이유: 한자/영문 기술 용어와 한글 사업명이 혼재된 RFP 텍스트에서 형태소 분석의 정확도 이득 대비, container 부피 / CI 재현성 / Java JVM 의존(KoNLPy) 비용이 큽니다. 동일 토크나이저를 ADR 0010 의 BM25 채널에서도 그대로 재사용합니다.

  • —토큰 정규식: `TOKEN_RE` (rag_core.py:93)
  • —조사 제거 정규화: `normalize_metadata_token` (rag_core.py:297)

4) 기관 약칭/alias 자동 추출

기관 A ↔ 기관A 같은 공백 압축형, 한글 1–4자 접두 토큰을 자동 추출해 metadata filter의 fuzzy match에 사용합니다. CSV 메타데이터의 {field}_aliases 컬럼도 병합.

  • —구현: `metadata_aliases` (rag_core.py:1137)

5) Embedding 모델 선택 — multilingual MiniLM

sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2를 기본으로 채택. 채택 이유: 한국어/영어 multilingual 지원, L12 경량(~120MB), API 의존 0, 로컬 재현성. 한계 명시: 2019년 모델로 2025년 최신 multilingual 모델(BGE-M3, multilingual-e5-large, KURE 등) 대비 품질 ablation을 아직 실행하지 않았습니다. 다음 실험 사이클 항목.

  • —기본값: `DEFAULT_EMBEDDING_MODEL` (rag_core.py:25)

도메인-특화 retrieval hardening의 전체 카탈로그는 `docs/retrieval-hardening.md`를 참고하세요.


실행 방법 (검증됨)

두 가지 흐름을 제공합니다.

  • —CLI 평가 흐름 (source of truth): scripts/build_index.py → app.py → eval/run_eval.py. 재현 가능한 측정/벤치마크/ablation 보고서 생성용.
  • —API 데모 흐름 (리뷰어용): make api 또는 make api-docker로 FastAPI 서버를 띄워 /health, /pipelines, POST /query 엔드포인트로 RAG를 호출. 출력은 grounded answer/citation 계약을 그대로 보존. 자세한 내용은 `docs/api-demo.md`.

현재 공개본은 data/raw의 synthetic RFP 문서를 사용해 로컬에서 end-to-end RAG를 실행합니다. 기본 실행은 naive_baseline control이며, embedding auto 모드는 캐시된 sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 모델을 우선 사용하고 모델을 사용할 수 없는 환경에서는 deterministic hashing embedding으로 자동 fallback합니다.

1) 환경 준비

bash
# Python 3.10+ 권장
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

2) 인덱싱

bash
python3 scripts/build_index.py --input_dir data/raw --output_dir data/index

3) 질의 실행

bash
python3 app.py --input_dir data/index --output_dir outputs --query "기관 A와 기관 B의 AI 요구사항 차이 알려줘"

강한 agentic 파이프라인을 확인하려면 명시적으로 preset을 지정합니다.

bash
python3 app.py \
  --input_dir data/index \
  --output_dir outputs \
  --query "기관 A와 기관 B의 AI 요구사항 차이 알려줘" \
  --pipeline agentic_full

후속 질문을 재현하려면 세션 상태 파일을 명시적으로 지정합니다. 상태에는 현재 활성 agency/project/topic/doc id와 최근 턴 요약이 JSON으로 저장되며, 생략된 참조가 모호하면 답을 추정하지 않고 clarification 응답으로 중단합니다.

bash
python3 app.py \
  --input_dir data/index \
  --output_dir outputs \
  --query "기관 A의 AI 요구사항은?" \
  --session_state outputs/session_state.json \
  --reset_session

python3 app.py \
  --input_dir data/index \
  --output_dir outputs \
  --query "그 기관이 요구한 보안 조건도 보여줘" \
  --session_state outputs/session_state.json

4) 평가 실행

bash
python3 eval/run_eval.py --index_dir data/index --output_dir reports --config eval/config.yaml

5) 성능표 갱신

bash
python3 scripts/update_readme_metrics.py --report reports/eval_summary.json --readme README.md

6) 일관성 검증 (reports ↔ README)

bash
python3 scripts/update_readme_metrics.py --report reports/eval_summary.json --readme README.md --check

7) Benchmark / ablation registry

bash
python3 scripts/run_benchmark.py \
  --suite benchmarks/suites/public_synthetic_rfp.yaml \
  --ablations benchmarks/ablations/rag_quality_axes.yaml

python3 scripts/summarize_benchmark.py \
  --manifest artifacts/benchmarks/<run_id>/run_manifest.json

Benchmark source of truth는 benchmarks/suites/, benchmarks/ablations/, benchmarks/registry.schema.json에 둡니다. Raw predictions, traces, logs, latency samples, error examples는 artifacts/benchmarks/ 아래에 생성되며 Git에 커밋하지 않습니다. 사람이 읽는 결과 해석은 `docs/benchmarking.md`와 `docs/ablation-results.md`를 참고하세요.

Private 100-doc 실험은 원문/개별 예측 없이 anonymized aggregate만 요약합니다. 아래 fixture는 summary flow 검증용이며 실측 private 성과가 아닙니다.

bash
python3 scripts/summarize_benchmark.py \
  --manifest benchmarks/examples/private100_aggregate_manifest.example.json \
  --registry /private/tmp/private100-registry.json \
  --docs /private/tmp/private100-summary.md

운영 기준과 커밋 금지 항목은 `docs/private-100-doc-experiments.md`를 참고하세요.

선택) Harness smoke run

재현 가능한 smoke 실행의 config snapshot, 로그, prediction, metric을 한 디렉터리에 모으려면 python3 scripts/run_harness.py --config harness/smoke.yaml 또는 make harness-smoke를 실행합니다. 산출물은 artifacts/runs/<run_id>/ 아래에 생성되며 Git 추적 대상이 아닙니다. 자세한 흐름은 `docs/harness.md`를 참고하세요.

참고: 모델을 처음 내려받아 실제 sentence-transformers 인덱스를 만들려면 --embedding_backend sentence-transformers를 사용하세요. 네트워크가 제한된 환경에서는 --embedding_backend hashing으로 재현성을 우선한 로컬 실행이 가능합니다. 산출물 경로는 data/index, outputs/, reports/로 고정합니다. Chunking 기본값은 naive baseline 기준인 --chunking_strategy fixed --chunk_max_chars 520 --chunk_overlap_sentences 1입니다. section-aware 비교는 --chunking_strategy auto 또는 section으로 명시합니다. 질의 기본값은 --pipeline naive_baseline의 flat dense top-k=4 retrieval입니다. parent section 단위 재조립을 확인하려면 app.py에 --pipeline agentic_full --retrieval_mode hierarchical을 지정하거나 eval/config.yaml의 hierarchical ablation run을 실행합니다.

평가 재현 기본 순서: 인덱싱(`scripts/build_index.py`) → 질의 실행(`app.py`) → 평가 실행(`eval/run_eval.py`) → 성능표 갱신(`scripts/update_readme_metrics.py`)

- 인덱스: data/index/index.json - 질의 응답: outputs/answer.json - 평가 요약: reports/eval_summary.json

선택) PDF/HWP + data_list.csv ingestion

비공개 원본 파일을 로컬에 보유한 경우 data_list.csv의 텍스트 컬럼을 v1 본문 소스로 사용해 PDF/HWP 메타데이터를 인덱스에 반영할 수 있습니다. data/data_list.csv와 data/files/는 비공개 데이터이므로 Git 추적 대상이 아닙니다.

bash
python3 scripts/build_index.py \
  --metadata_csv data/data_list.csv \
  --files_dir data/files \
  --output_dir data/index \
  --embedding_backend hashing

이 모드는 data/index/index.json과 함께 data/index/ingestion_report.json을 생성합니다. 리포트에는 문서별 indexed/failed 상태와 missing_file, empty_text, unsupported_file_format, duplicate_doc_id 같은 실패 사유가 기록됩니다.

Optional real-data profile

공개 synthetic baseline과 성능표는 그대로 유지하고, 로컬 private 실데이터는 별도 profile로 실행합니다.

bash
bash scripts/smoke_real.sh

기본 입력은 data/data_list.csv와 data/files/이며, 산출물은 data/index/real100/, outputs/real100/, reports/real100/에 생성됩니다. eval/real_config.local.yaml이 있으면 실데이터 gold 평가까지 실행하고, 없으면 인덱싱과 대표 질의까지만 실행합니다. 로컬 평가 파일은 eval/real_config.example.yaml을 복사해 만들며, eval/*.local.yaml은 Git 추적 대상이 아닙니다.

선택) Document visual parsing v2

원본 PDF/이미지 문서를 직접 파싱해 page/bbox/region metadata가 포함된 v2 artifact를 만들 수 있습니다. PDF는 text layer block을 우선 사용하고, text가 부족한 page 또는 이미지 파일은 OCR adapter를 사용합니다. HWP는 이번 v2에서 native visual parsing 대상이 아니며, metadata CSV visual mode에서는 기존 텍스트 컬럼으로 fallback하고 visual_fallback_hwp로 표시합니다.

What this v2 is NOT: 이 파이프라인은 OCR-only입니다. PyMuPDF(PDF text layer) + pdfplumber(table 후보) + pytesseract(OCR fallback) 조합으로 동작하며, LayoutLMv3 / Donut / ColPali / Nougat / pix2struct 같은 layout-aware vision foundation model은 import되지 않습니다. Vision foundation model과의 1-page 비교 spike는 다음 사이클 항목입니다. 자세한 한계 라벨링은 `docs/visual-ingestion-v2.md`의 "What this is NOT" 섹션을 참고하세요.
bash
python3 scripts/build_index.py \
  --visual_input_dir data/visual_samples \
  --output_dir data/index \
  --embedding_backend hashing

metadata CSV와 함께 v2를 비교하려면 다음처럼 실행합니다.

bash
python3 scripts/build_index.py \
  --metadata_csv data/data_list.csv \
  --files_dir data/files \
  --ingestion_mode visual \
  --output_dir data/index \
  --embedding_backend hashing

이 모드는 data/index/index.json, data/index/ingestion_report.json, data/index/visual_artifacts/*.visual.json을 생성합니다. OCR에는 Python 패키지(pymupdf, pdfplumber, pytesseract, Pillow, opencv-python-headless)와 시스템 Tesseract 설치가 필요합니다. OCR 엔진이 없으면 text-layer PDF는 계속 처리할 수 있지만 image-only 문서는 ocr_unavailable로 실패합니다.

visual parsing 품질은 QA 평가와 별도로 parser-stage 평가로 확인합니다. 이 평가는 이미 생성된 *.visual.json artifact와 gold 기대값을 비교하며 OCR text, layout block, section boundary, table, field, bbox/page-region 지표를 reports/parser_eval_summary.json에 기록합니다. 공개 fixture로 먼저 실행해 report 형태를 확인할 수 있습니다.

bash
python3 eval/run_parser_eval.py \
  --artifact_dir eval/fixtures/parser_visual_v2 \
  --gold eval/parser_visual_v2_gold.yaml \
  --output_dir reports \
  --run_name visual_v2_fixture \
  --parser_version 2

실제 visual ingestion 산출물을 비교할 때는 같은 gold 형식에서 doc_id와 artifact 이름을 맞춘 뒤 --artifact_dir data/index/visual_artifacts를 지정합니다. README의 핵심 성능표는 기존 QA 평가(reports/eval_summary.json) 기준으로 유지하고, parser-stage 지표는 별도 리포트로 관리합니다.


상세 설계 링크

  • —1-page Architecture deep-dive: `docs/architecture-deep-dive.md` (GitHub Pages 렌더)
  • —엔지니어링 블로그 시리즈: `docs/blog/` (GitHub Pages 렌더)
  • —포트폴리오 case study: `docs/portfolio-case-study.md`
  • —Benchmarking: `docs/benchmarking.md`
  • —Ablation results: `docs/ablation-results.md`
  • —Private 100-doc experiments: `docs/private-100-doc-experiments.md`
  • —설계 배경 및 의사결정: `docs/design-background.md`
  • —Chunking diagnostics: `docs/chunking-diagnostics.md`
  • —PDF/HWP ingestion: `docs/real-data-ingestion.md`
  • —Visual parsing v2: `docs/visual-ingestion-v2.md`
  • —Citation grounding evaluation: `docs/citation-grounding-eval.md`
  • —Grounding/eval hardening: `docs/grounding-eval-hardening.md`
  • —Reproducible harness: `docs/harness.md`
  • —API demo (FastAPI + container): `docs/api-demo.md`
  • —Architecture Decision Records: `docs/adr/README.md`
  • —Engineering governance (workflow map): `docs/engineering-governance.md`
  • —답변 출력 정책: `docs/answer-policy.md`
  • —실패 사례 분석: `docs/failure-cases.md`
  • —회고 및 개선 방향: `docs/retrospective.md`
  • —프로젝트 상세 문서 인덱스: `docs/README.md`

Notice

  • —원본 RFP 문서는 외부 공유 제한으로 저장소에 포함하지 않았습니다.
  • —data/raw 문서는 공개 재현을 위해 작성한 synthetic RFP 샘플입니다.
  • —본 저장소는 재현 가능한 구조/평가 관점의 포트폴리오 문서화를 목표로 합니다.