근거 기반 카드 생성 — B-4~B-6
무엇을
검색한 조항으로 요약 카드를 생성하고 출처를 붙인다. ai/apps/generation/ 이 아직 없다.
모델은 정해져 있다 — 호출 규약까지
_project/decisions/010: EXAONE-4.0-1.2B(250토큰 2.0~2.1초 실측).
런북 14장이 Ollama 호출 규약을 적어 두었다 — think:false + /api/chat 이 필수다.
안 지키면 응답에 사고 과정이 섞여 나온다.
왜 ai/ 인가
모델을 로드한다. server/.importlinter 계약 2 가 server/ 안에서 transformers·langchain import 를
막는다 — 요구사항표 3.1절 아래 「2026-08-26 위치 정정」이
이 표를 server/apps/generation/ → ai/apps/generation/ 으로 옮겨 적은 이유가 그것이다.
절대 원칙 9 — 판정은 규칙이, 설명만 LLM이
카드는 설명이다. 어느 조항이 정답인지는 검색이 정하고, 생성은 그 조항을 읽기 좋게 옮긴다. 생성 모델이 조항을 고르거나 «이 절차는 불가능합니다» 같은 판정을 하면 그건 범위를 넘은 것이다.
완료 조건
- 출처 표시율 100% — 카드마다
doc_id(문서명·조항)가 붙는다. 없으면 카드를 만들지 않는다 - 근거가 부족하면 「관련 문서 없음」을 반환한다 — 지어내지 않는다(2.3절)
- 부록 A-1 금지 표현이 템플릿 어디에도 없다 — 「안전합니다」·「위험도 78%」·등급·점수
- 생성 지연을 재서 4.1절 예산 안인지 본다
주의
약관 원문을 그대로 싣지 않는다(절대 원칙 6). 요약·해석 + 출처(문서명·조항)만 싣는다.
2026-09-15 — 만들었다. 모델은 서류 이름만 고르고, 문구와 판정은 코드가 한다
ai/apps/generation/ — rev.5 가 B-4 를 「근거 기반 서류 목록」 으로 좁힌 대로 만들었다.
검색 결과 ─┬ 상위 1건: 모델이 조항 본문에서 서류 이름을 고름 → 규칙이 근거 대조(본문에 문자 그대로 있나)·금지 표현 검사
│ → 코드가 "필요 서류: A · B" 로 조립
│ └ 「없음」 · 근거 있는 이름 0개 · 타임아웃·오류 → 스니펫 카드
└ 나머지: 스니펫 카드
측정일 2026-09-15 · 커밋 38f2fc3-dirty · 골든셋 v1-150 B 96문항 · 상위 1건 생성 · 로컬 Ollama 0.33.3 (Apple Silicon)
검색 결과를 파일로 고정해 모든 조건에 같은 조항을 넣었다 (rerank-dense · data/processed/generation-eval/docs-rerank-dense.json)
.venv/bin/python scripts/eval_generation.py [--no-schema] [--model …]
조건 원출력 환각 카드 환각 조각 목록 카드 「없음」 출력 토큰 p50 p95 출처 표시율 화면 환각
EXAONE-4.0-1.2B + 스키마 89/96 213 59 0 52 589ms 921ms 100% 0
EXAONE-4.0-1.2B 스키마 없음 96/96 416 44 0 80 922ms 944ms 100% 0
kanana-1.5-2.1b 스키마 없음 27/96 40 74 14 24 526ms 977ms 100% 0
- 출처 표시율 100%(96/96) —
doc_id없는 문서는 카드가 되지 않는다(테스트 고정) - 「관련 문서 없음」: 검색이 0건이면 빈 목록(테스트 고정). ⚠ 실측에서는 한 번도 안 일어났다 — 검색이 항상 5건을 돌려준다(점수 문턱이 없다). 근거 부족을 검색 쪽에서 판정할 규칙이 아직 없다 → 미결
- 금지 표현: 문구를 코드가 짓고 항목에서 판정·점수 표현을 거른다. 세 조건 모두 금지 표현 카드 0
- 지연: 상위 1건 생성 p95 0.92~0.98초(로컬). 4.3절 예산은 트리거→카드 표시 p95 ≤1,000ms 전체라, 검색·리랭킹(p95 289ms MPS)을 더하면 넘는다. ⚠ 같은 조건 p95 가 실행마다 150ms 쯤 흔들렸다. 운영 T4 미측정. 스트리밍(첫 토큰 표시)은 안 만들었다 — 포트가 카드 목록을 한 번에 돌려주는 계약이다
틀린 것 셋 — 고친 순서대로
- 지시문만 줬더니 EXAONE 이 마크다운 설명문을 썼다 — 81/96 카드에 조항에 없는 말, 목록으로 살아남은 카드 32장, p95 1.5초
- 가상 예시 두 쌍을 넣었더니 예시 속 말이 답에 새어 들어왔다 — 「도서관 website」·「공원 개방 시간」. 환각을 줄이려던 장치가 환각의 출처가 됐다. 걷었다
- 조항 제목까지 근거로 삼았더니 「3.15 옥내 배관 문의」가 서류로 통과했다 — 본문만 대조하게 바꿨다
JSON 스키마 강제(Ollama format)는 EXAONE 에는 효과가 있었지만(목록 카드 44 → 59) 배열 안의 환각은 못 막았다.
모델을 바꿨다 → w6-generation-model-compare · decisions/207
운영에는 안 켜졌다
서버는 OLLAMA_URL 과 GENERATION_MODEL 이 둘 다 있어야 생성을 켠다(server/main.py _wire_generation). 런북 16-1 이 OLLAMA_URL 을 이미 주입하므로
모델 이름 없이는 켜지지 않게 했다 — 켜면 추천 지연이 는다.