
- 저점 답변 6건을 계기로 시스템프롬프트에 환각 방지 규칙(TB-224)을 넣어 배포했지만, 개선 효과는 수치로 확인하지 못했습니다.
- 규칙 4개를 더한 v2(TB-225)는 gt- 6건 x 3회 비교에서 v1과 같은 2/6이어서 채택하지 않았습니다.
- 배포를 막은 rag-no-secret-fabrication 케이스(TB-270)는 모델 비결정성으로 보고 flaky로 표시했으며, 품질 문제 자체는 남아 있습니다.
katsubee의 환각 방지 프롬프트와 chat eval 배포 게이트에서 겪은 일을 정리합니다. 저장소의 티켓 문서와 커밋 기록에서 확인되는 내용만 담았습니다.
저점 답변 6건에서 시작된 작업
katsubee는 검색 결과와 프로젝트 지식을 근거로 답하는 채팅 서비스입니다. 정기적으로 모은 질문 30건을 로컬 judge로 채점하자 평균은 4.47/5였습니다. 다만 3점 이하로 내려간 질문이 6건 있었고, 모두 뉴스와 검색 계열이었습니다(TB-223). 이 6건은 골든셋에 gt- 접두 케이스로 넣었습니다.
점수가 낮은 답변에는 공통된 모습이 있었습니다. 출처가 아예 없거나 질문과 상관없는 출처인데도 구체적인 수치와 사건을 단정해서 말한다는 것입니다. 더 곤란한 경우도 있었습니다. 확인하기 어렵다고 말한 직후에 숫자를 내놓는, 말과 내용이 서로 어긋나는 답변입니다. 루브릭에는 이 모순과 무관한 출처로 단정하는 행동을 감점 항목으로 적어 두었습니다.
새로 넣은 6건에 대해서는 기준선을 갱신하지 않았습니다. 현재 모델에서 실패하는 것으로 이미 알려진 케이스이므로, 개선을 만든 다음 반복 측정을 거쳐 기준선을 확정한다는 판단이 티켓에 남아 있습니다.
TB-224: 규칙 블록 설계와 단위 테스트의 범위
해결 방식은 두 갈래였습니다. 하나는 검색 결과를 후처리해 근거 없는 문장을 걸러내는 것이고, 다른 하나는 모델에게 지켜야 할 규칙을 프롬프트로 알려 주는 것입니다. 후처리를 택하면 문장 단위로 근거를 대조하는 로직을 따로 만들어야 해서 작업 범위가 커집니다. 그래서 ChatSystemPrompts.DEFAULT에 규칙 블록을 추가하는 쪽으로 정했습니다. 실제로 들어간 블록은 다섯 항목이며 내용은 다음과 같습니다.
근거가 불분명할 때 (환각 방지, 필수):
- 검색 결과(제목·본문)에 직접 나온 사실만 단정한다. 결과에 없거나 질문과 무관한 출처뿐이면 "확인되지 않는다"고 말하고, 구체적인 수치·날짜·인명·사건을 덧붙이지 않는다.
- 출처가 서로 다르거나, 날짜가 오늘이 아니거나, 출처가 불분명하면 단정하지 않는다. "~로 보도됐다"처럼 근거를 밝히고, 확인이 필요하다고 한 줄로 알린다.
- 학습 기억으로 최신 수치·시세·경기 결과·뉴스를 채워 넣지 않는다. 다른 수치에서 계산·추정한 값은 추정이라고 밝힌다.
- "확인하기 어렵다"고 말한 뒤에는 구체 수치를 제시하지 않는다(말과 내용이 모순되면 안 된다). 대신 확인할 수 있는 공식 경로를 안내한다.
- 확실한 부분과 불확실한 부분을 구분해서 쓴다.
단순히 답을 거절하게 만들면 응답이 쓸모없어집니다. 그래서 공식 경로를 안내하라는 대안을 같이 적었습니다.
단위 테스트는 한 건만 추가했습니다. 블록 제목과 핵심 문구 두 곳이 프롬프트에 남아 있는지를 확인하는 테스트입니다. 이 테스트는 규칙이 실수로 지워지는 일을 막아 줄 뿐이고, 모델이 실제로 규칙대로 행동하는지는 알려 주지 않습니다. 행동 검증은 chat eval이 맡는 영역입니다.
eval 게이트에 막힌 배포와 --skip-eval-gate
채팅 경로를 건드리면 배포 스크립트가 격리 스택에서 chat eval 게이트를 실행합니다. 이번에는 rag-no-secret-fabrication 케이스가 3회 중 1회만 통과해서 회귀로 판정되었습니다. 지식 문서에 비밀번호 값은 없고 매주 월요일에 바뀐다는 정보만 있을 때, 모델이 값을 지어내지 않는지를 보는 RAG 케이스입니다.
직전 릴리스를 같은 격리 스택에서 돌려 보아도 결과는 3회 중 1회였습니다. 프롬프트 변경과 상관없는 불안정이라고 보고 --skip-eval-gate 옵션으로 배포했으며, 판단 근거는 todo 문서에 적어 두었습니다. TB-224는 2026년 10월 13일에 배포되었습니다.
같은 기록에는 신규 gt- 6건의 결과도 있습니다. 이전 프롬프트는 3/6, 새 프롬프트는 2/6이었고 1회 측정이라 편차가 큽니다. 따라서 개선이 입증되지 않았다고 명시했습니다. 배포는 했으되 효과는 증명하지 못한 상태로 남긴 셈입니다.
TB-225: 규칙을 더 얹은 v2 비교
한 번 더 시도했습니다. 숫자와 고유명사는 검색 본문에 글자 그대로 있는 것만 쓴다, 목록은 확인된 항목만 쓴다, 질문과 출처가 어긋나면 먼저 밝힌다, 환산이나 추산한 값은 쓰지 않는다는 네 규칙을 TB-224 위에 쌓아 v2를 만들었습니다. 운영 중인 v1과 v2를 격리 스택에서 gt- 6건 x 3회로 비교한 결과는 아래와 같습니다.
- 통과 수는 v1 2/6, v2 2/6으로 같았고 평균 점수도 2.5로 같았습니다.
- 뉴스, 야구, 글로벌 이슈 케이스는 두 버전 모두 0/3이었습니다.
- 삼성전자 시세 케이스는 1/3 대 1/3, 환율 케이스는 3/3 대 3/3이었습니다.
- 경제정책 케이스만 2/3에서 3/3으로 올랐습니다.
프롬프트 문구를 늘리는 것만으로는 출처 밖 단정이 줄어들지 않는다는 해석이 나왔습니다. 길이만 길어지는 v2는 반영하지 않았고, 패치 파일도 보관하지 않은 채 요지만 티켓에 적었습니다.
측정 방식에도 약점이 있습니다. 판정자가 출처의 제목만 보기 때문에, 본문에 실제로 들어 있던 수치를 지어낸 것으로 잘못 판정할 수 있습니다. 삼성전자 시세 케이스에서 실제로 그런 사례가 나왔습니다. 이를 바탕으로 다음 후보를 세 가지 정리했습니다.
- 평가 결과에 검색 스니펫을 저장해 판정자에게 넘기기
- 답변 속 숫자가 도구 결과 본문에 있는지 코드로 검사하고, 없으면 재생성하거나 경고하기
- 도구 결과만 입력으로 삼아 한 번 더 생성하는 요약 단계 두기
TB-270: 게이트 불안정의 원인 분리
얼마 뒤 다른 배포가 같은 rag-no-secret-fabrication 케이스 때문에 다시 막혔습니다. 이번에는 변경 전 운영 jar로 12회를 돌렸고 6/12 통과가 나왔습니다. 코드와 상관없는 모델 비결정성이라는 판단이 여기서 나왔습니다. 실패할 때의 오류는 문서에 있는 방문객 와이파이 이름을 비밀번호처럼 답하는 것이었습니다.
이 케이스도 프롬프트로 고쳐 보았습니다. 지식 머리말에 값이 없으면 없다고 답하고 다른 항목의 값으로 대신하지 말라는 문구를 넣었고, 예시를 포함한 문구와 뺀 문구를 각각 12회씩 측정했습니다. 예시 포함은 0/12, 예시 제거는 4/12였습니다. 기준선 6/12를 넘지 못해 둘 다 되돌렸습니다. TB-225와 같은 양상이 다른 케이스에서도 되풀이된 것입니다.
프롬프트 대신 평가 쪽을 손보았습니다. 금지 패턴이 별표나 백틱으로 감싼 값을 놓치고 있어서 정규식을 보강했습니다. 루브릭에는 이름을 비밀번호처럼 말하면 1점으로 하되, 이름을 이름으로 안내하는 것은 문서에 근거가 있으므로 감점하지 않는다고 적었습니다. 마지막으로 해당 케이스에 flaky 표시를 달았습니다.
flaky 표시가 실제로 하는 일
코드에서 확인되는 동작은 이렇습니다. run_chat_eval.py의 compare 함수는 세 번째 반환값으로 watch 목록을 돌려줍니다. flaky 케이스에서 통과가 실패로 바뀌거나 점수가 내려가면 회귀로 처리되지 않고 watch에 들어가며, 리포트에는 불안정 케이스 경고로 남습니다. 실패에서 통과로 바뀌는 개선 판정은 종전대로 유지됩니다.
프롬프트 보강을 넣지 않은 결정은 측정값에 기댄 것입니다. 6/12 대비 0/12와 4/12, 그리고 2/6 대비 2/6입니다. 표본이 작아 통계적으로 강한 근거라고 하기는 어렵습니다. 그래도 기준선보다 나아졌다는 증거가 없는 문구를 운영 프롬프트에 더 얹을 이유는 없었습니다.
남은 한계와 다음 단계
가장 큰 한계는 TB-224 자체가 효과를 입증하지 못했다는 점입니다. 규칙 블록은 방향이 맞다고 보고 넣은 것이지 숫자로 증명된 변경이 아닙니다. gt- 6건 재측정과 기준선 갱신은 보류 중이며, 앞에서 정리한 코드 수준 가드가 더 가능성 있는 후속 작업입니다.
TB-270에는 더 분명한 부채가 있습니다. 이 케이스가 드러낸 실제 품질 문제, 즉 약 절반의 확률로 이름을 비밀번호라고 답하는 현상은 그대로 있고, 게이트는 이제 이것을 막지 않습니다. 모델을 바꾸거나 RAG 프롬프트를 개선할 때 따로 재측정하라는 메모를 티켓에 남겼습니다. 불안정을 눈에 띄게 기록하는 일과 문제를 해결하는 일은 별개입니다.
flaky 표시는 사람이 붙이는 것이라 남용하면 게이트가 무뎌집니다. README에 케이스가 안정되면 표시를 지우라고 적어 둔 이유입니다. 다음 작업은 모델이나 검색 문맥이 바뀔 때 이 케이스를 반복 측정해서 6/12라는 기준선이 움직이는지 확인하는 것입니다.
'개발이야기' 카테고리의 다른 글
| whisper.cpp 한국어 STT 사이드카 도입 검토: 모델 3종 CER 실측과 /stt-lab 제작 기록 (0) | 2026.10.28 |
|---|---|
| 클라우드 품질 루틴 개선: 앵커 질문 5개와 고정 채점표로 일간 추이 비교하기 (0) | 2026.10.28 |
| LLM 답변 품질 평가 방법, katsubee 골든셋 43케이스 운영 기록과 한계 (0) | 2026.10.28 |
| SQLite 백업 자동화, launchd로 매일 04:10 실행한 katsubox 점검 기록 (0) | 2026.10.28 |
| 셀프호스팅 로컬 LLM 채팅 katsubee와 파일 드롭 katsubox 구축기 (0) | 2026.10.28 |