답변 출력 정책

이 문서는 최종 답변 생성 레이어가 어떤 형식으로 근거를 제시하고, 근거 부족을 어떻게 표시하는지 정리한다. 공개본은 외부 LLM 없이 retrieval evidence에서 문장을 추출해 claim과 citation을 연결한다.

상태 값

status 의미 evidence claims
supported 모든 필수 대상과 주제가 근거로 확인됨 있음 1개 이상
partial (a) 비교 질문에서 일부 대상만 근거로 확인됨, 또는 (b) 검증 토픽 중 일부만 evidence에 매칭됨 (relaxed 단계의 partial-topic grounding) 확인된 대상만 있음 1개 이상
insufficient 답변 가능한 근거를 찾지 못함 없음 없음

현재 답변 객체는 schema_version: 2를 사용한다. answer_text는 사람이 빠르게 읽기 위한 요약이고, 검증 가능한 계약은 answer.schema_version, answer.status, answer.status_reason, answer.claims, answer.insufficiency, top-level evidence를 기준으로 본다.

status_reason은 machine-readable 진단 필드다.

field 의미
code verified, partial_comparison, partial_topic_grounding, insufficient_evidence, context_clarification, metadata_ambiguity_clarification 중 하나
verified verifier 기준 통과 여부. 단, 명시 요청된 비교 대상이 corpus에 없으면 verifier 설정과 무관하게 partial이 될 수 있음. relaxed 단계의 partial-topic 매칭으로 통과한 경우 verified=True이지만 status는 partial로 surface된다
verification_reasons topic_not_grounded, partial_topic_grounding, missing_comparison_doc:*, missing_requested_entity:* 같은 근거 부족 또는 약한 근거 사유

좋은 답변 예시

{
  "schema_version": 2,
  "status": "supported",
  "status_reason": {
    "code": "verified",
    "verified": true,
    "verification_reasons": []
  },
  "query_type": "comparison",
  "summary": "기관 A: ... 기관 B: ...",
  "claims": [
    {
      "target": "기관 A",
      "claim": "기관 A의 핵심 AI 요구사항은 모델 품질관리, 보안 통제, 로그 추적이다.",
      "support": "기관 A의 핵심 AI 요구사항은 모델 품질관리, 보안 통제, 로그 추적이다...",
      "citations": [
        {
          "doc_id": "rfp-agency-a-ai-quality",
          "chunk_id": "rfp-agency-a-ai-quality::chunk-002",
          "section": "AI 요구사항"
        }
      ]
    }
  ],
  "insufficiency": null
}

좋은 답변은 claim마다 citation이 있고, citation의 chunk text가 claim을 직접 지지한다. visual parsing v2 인덱스에서는 citation에 page_span과 regions가 추가될 수 있어 page/bbox 근거 위치까지 추적할 수 있다. page/region gold가 있는 평가셋은 citation-grounding-eval.md의 기준으로 문서 단위 citation precision과 위치 grounding을 분리해 본다. 비교 질문에서는 대상별 claim을 나눠 스캔 가능하게 유지한다.

나쁜 답변 예시

{
  "schema_version": 2,
  "status": "supported",
  "status_reason": {
    "code": "verified",
    "verified": true,
    "verification_reasons": []
  },
  "summary": "기관 A는 블록체인 납품 실적이 있습니다.",
  "claims": [],
  "insufficiency": null
}

이 답변은 근거 없는 claim을 supported로 표시했고, claim 단위 citation도 없다. 이런 케이스는 answer_format_compliance와 abstention 평가에서 실패해야 한다.

근거 부족 정책

unsupported 질문은 다음처럼 답한다.

{
  "schema_version": 2,
  "status": "insufficient",
  "status_reason": {
    "code": "insufficient_evidence",
    "verified": false,
    "verification_reasons": ["topic_not_grounded"]
  },
  "query_type": "abstention",
  "summary": "제공된 공개 샘플 RFP 근거에서는 '기관 A의 블록체인 납품 실적은?'에 답할 수 있는 내용을 찾지 못했습니다.",
  "claims": [],
  "insufficiency": {
    "reasons": ["topic_not_grounded"],
    "missing_targets": ["기관 A"],
    "missing_topics": ["블록체인", "납품"]
  }
}

비교 질문에서 한쪽만 확인되면 partial로 표시하고, 확인되지 않은 대상은 missing_targets에 남긴다. 명시적으로 요청된 기관이 corpus metadata에 없을 때도 missing_requested_entity:* 사유를 남겨 partial로 처리한다. 이 경우 확인된 claim만 citation과 함께 제공하며, 빠진 대상을 추측해 채우지 않는다.

계약 강제 메커니즘

schema_version: 2 계약은 두 단계로 강제된다. extractive 경로가 status / claims / citations / status_reason / insufficiency를 결정적으로 락하고, optional LLM 합성 경로는 summary / answer_text만 다시 쓸 수 있다 (ADR 0001 extractive baseline invariant, ADR 0011 additive synthesis).

1단계 — extractive (락 단계)

rag_core.py 안의 함수가 verifier 출력을 그대로 status로 굳힌다.

단계 함수 (rag_core.py) 강제 내용
Evidence 검증 verify_evidence (L2252) topic/entity coverage 체크. allow_partial_topic은 retrieval 마지막 시도에서만 True. partial-topic 매칭으로 통과하면 verified=True지만 status는 partial로 surface (#69 회귀 방지)
Claim 빌드 build_claims / build_comparison_claims / build_extract_claims (L2495–2520) claim마다 chunk_id가 evidence list에 존재함을 by-construction으로 보장
Status 결정 answer_status (L2594), answer_status_reason (L2469) ANSWER_STATUS_{SUPPORTED,PARTIAL,INSUFFICIENT} (L215–217) 셋 중 하나로 클램프. 다른 값은 emit 불가

이 단계의 출력은 다운스트림에서 immutable로 취급된다.

2단계 — LLM synthesis (additive 단계, 옵션)

rag_synthesis.py synthesize_answer는 위 단계 출력을 입력으로 받아 summary와 answer_text만 다시 쓴다. 6개 게이트가 직렬로 실행되며 하나라도 실패하면 fallback flag와 reason을 메타에 박은 채 extractive 답변을 그대로 반환한다.

Gate Trigger fallback_reason
Backend 알 수 없음 BIDMATE_SYNTHESIS_BACKEND 미지원 값 unknown_backend:<value>
Evidence 없음 allowed_chunk_ids가 빈 set no_evidence_chunks
Backend 호출 실패 예외 발생 (network, parse, quota 등) backend_error:<exc_type>:<truncated>
Summary 빈 문자열 payload.summary가 비어 있음 empty_summary
Unauthorized chunk used_chunk_ids ⊄ evidence chunk_ids unauthorized_chunk_ids:<head>
Claim 밖 chunk used_chunk_ids ⊄ claim citation chunk_ids chunks_outside_claims:<head>

이 게이트들은 ADR 0011의 “no new chunk_ids” hard postcondition을 코드 레벨로 구현한다. LLM이 hallucinated citation을 만들어도 set-membership 체크로 즉시 거부되고 extractive 출력이 surface된다.

schema_version bump 규칙

위 contract의 어느 부분이라도 비호환으로 바뀌면 schema_version을 3으로 올리고 ADR 0003 supersede를 새 ADR로 기록한다. additive 변경(예: claims[].citations[]에 optional page_span 추가)은 bump 없음. claims shape 변경, status enum 추가/제거, status_reason.code 의미 변경은 모두 비호환.

테스트 매트릭스

테스트 잠그는 contract
tests/test_llm_synthesis.py 6개 게이트의 fallback reason 정확성, ADR 0011 additive 불변(extractive와 LLM 경로가 같은 claims/citations 반환)
tests/test_partial_topic_grounding.py partial-topic 통과 시 status=partial 강제 (#69 회귀 가드)
tests/test_followup_entity_injection.py follow-up entity carryover에서 status=partial 비강등
eval/run_eval.py의 score_answer_format Answer Format Compliance metric — claim 단위 citation 누락 시 점수 차감

평가 surface와의 연결

eval pipeline은 answer.status_reason.code / answer.claims / top-level evidence만 검증 입력으로 사용한다 (answer_text는 검증 대상 아님). 따라서 위 두 단계가 만든 출력이 평가 metric에 그대로 surface된다.

실패 유형

  • unsupported over-answering: 근거가 없는데 supported로 답함
  • partial coverage hidden: 비교 질문에서 한 대상만 찾고 전체 답변처럼 제시함
  • citation drift: claim은 맞아 보이지만 citation chunk가 같은 claim을 직접 지지하지 않음
  • unreadable comparison: 여러 대상의 답을 한 문장에 섞어 리뷰가 어려움