01 — RESEARCH ·R-004 ·2026.06.14

LLM은 숫자를 지어낸다 — 환각과 싸운 기록

월드컵 RAG 개발기 4편 — 표는 코드가 만든다, goal_timeline 단일 출처, 45+2와 자책골.

목차
  1. 했다: LLM으로 경기 리뷰 자동 생성
  2. 막혔다: 환각의 유형학
  3. 풀었다: 원칙 세 개
  4. 원칙 1 — 표·집계는 코드가 만들고, LLM은 복사만 한다
  5. 원칙 2 — 민감한 사실은 “단일 출처”를 지정한다
  6. 원칙 3 — 생성 후 검산한다
  7. 부록: RAG 챗봇에서의 환각
  8. 배운 것

연재 · 월드컵 RAG 프로젝트 개발기 (4/5)

  1. 월드컵을 통째로 서비스에 담아보기로 했다
  2. 크롤러가 죽었다 — 데이터 수집 잔혹사
  3. 배포했더니 데이터가 사라졌다 — Railway 운영의 함정들
  4. LLM은 숫자를 지어낸다 — 환각과 싸운 기록 ← 지금 글
  5. 손흥민 리포트 제작기 — 이벤트 데이터로 기사급 리포트 만들기

했다: LLM으로 경기 리뷰 자동 생성

경기 CSV를 프롬프트에 넣고 리뷰를 생성했다. 문장은 그럴듯했다. 문제는 숫자였다.

막혔다: 환각의 유형학

몇 주 운영하며 환각을 유형별로 수집하게 됐다.

유형 1 — 표를 지어낸다. 원시 수치를 주고 “표로 정리해줘” 하면 합계가 안 맞거나, 행을 창작하거나, 두 팀 수치를 바꿔치기했다. 특히 표가 길수록 중간부터 무너졌다.

유형 2 — 골 시간을 “추론”한다. 슛맵에는 시간대별 슛 분포(0-15분, 76-90분…)가 있다. LLM은 이걸 보고 “후반 83분 결승골”처럼 골 시간을 슛 분포에서 창작했다. 실제 골은 59분이었는데.

유형 3 — 추가시간을 산수한다. 45+2분 골을 “47분 골”로 합산해 버린다. 축구 표기 관례를 모르는 게 아니라, 아는데도 굳이 계산한다.

유형 4 — 자책골 귀속을 틀린다. 자책골은 넣은 선수 팀이 아니라 상대 팀 득점이다. 데이터에 자책골 플래그가 있어도 서술에서 뒤집혔다.

풀었다: 원칙 세 개

원칙 1 — 표·집계는 코드가 만들고, LLM은 복사만 한다

pandas로 집계하고 마크다운 표 문자열까지 코드가 완성해서 payload에 넣는다. 프롬프트에는 “이 표를 본문에 그대로 복사해 넣고(재계산·행 추가 금지) 아래에 해석만 붙여라”라고 못 박는다. 표 환각이 그날로 사라졌다.

나중엔 한발 더 나갔다. 기자 전달용 리포트에서는 LLM이 표를 복사하는 것조차 못 믿어서, 본문에 [[TABLE:키]] 플레이스홀더만 쓰게 하고 코드가 정본 표로 치환했다. 표 위·변조가 구조적으로 불가능해진다.

원칙 2 — 민감한 사실은 “단일 출처”를 지정한다

골 시간·득점자·전후반은 goal_timeline이라는 별도 최상위 필드로 뽑아 주고, 프롬프트에 이렇게 쓴다:

“득점 시간·득점자·전후반은 오직 goal_timeline만이 정답이다. 각 골의 분은 minute_display(‘45+2’ 등)를 그대로 쓰고 절대 합산하지 마라. 슛 분포(time_distribution)는 골 시간이 아니다 — 이걸로 골 시각을 추정하지 마라.”

자책골은 데이터 단계에서 수혜팀으로 귀속시키고 own_goal: true를 달아 “본문에 ‘(자책골)‘만 명시”하게 했다. 모델에게 판단을 맡기지 말고, 판단이 끝난 데이터를 줘라.

원칙 3 — 생성 후 검산한다

생성이 끝나면 두 단계로 검산한다:

  1. 표 무결성(기계): 문서의 표 행들이 코드 정본과 정확히 일치하는지 문자열 대조
  2. 서술-수치 일치(LLM 대조): 본문 문장 속 모든 숫자·방향성 주장(“2배 늘었다”, “최고치”)을 확정 지표 JSON과 대조시키는 검수 전용 프롬프트. 불일치가 나오면 해당 문장만 최소 수정 후 재검수

2번은 LLM으로 LLM을 검사하는 구조라 완벽하진 않지만, “숫자를 통째로 지어내는” 대형 사고는 전부 걸러냈다. 실전에서 잡아낸 예: 후반 롱볼 성공률(성공/시도)을 롱볼 비중(전체 패스 중 비율)으로 잘못 서술한 문장 — 수치는 맞는데 라벨이 틀린, 사람도 하기 쉬운 실수였다. 그래서 프롬프트에 “‘성공률’과 ‘비중’ 혼용 금지” 같은 용어 규칙도 쌓여 갔다.

부록: RAG 챗봇에서의 환각

챗봇은 수집 기사(48개 팀, 2,400여 건)를 컨텍스트로 쓴다. 여기서의 환각은 수치보다 출처 없는 단정이었다. 컨텍스트에 없는 이적설·부상 정보를 아는 척하는 것. “컨텍스트에 없으면 모른다고 답하라”는 고전적 지시가 기본이고, 경기 식별(“2026 월드컵 A조 2차전”)을 팀 별칭 매칭으로 시스템이 먼저 확정해서 넘기면 환각 여지가 크게 줄었다.

배운 것

  • LLM 파이프라인 설계의 핵심 질문은 “무엇을 생성시킬 것인가”가 아니라 **“무엇을 생성 못 하게 막을 것인가”**다.
  • 숫자·시간·귀속처럼 정답이 하나인 정보는 코드가 확정 → LLM은 인용 구조가 정답에 가깝다.
  • 검산은 사치가 아니라 파이프라인의 일부다. 특히 결과물이 사람 이름을 달고 나가는 경우(기자 리포트)라면.