본문으로 건너뛰기
AI 노트게임기 버전으로 보기

AI 노트 02 / 06

환각과 조용한 실패를 코드로 막는다

형식만 맞는 빈 응답, 기동마다 흔들리는 프롬프트 지문, 문장으로는 맞아 보이던 지표 정의 — 스키마나 합의가 아니라 코드 검증과 데이터 검산으로 잡은 사례들입니다.

30 → 7
JVM 30회 기동에서 관측된 프롬프트 순서 7가지

LLM 출력의 문제는 틀리는 것보다 '틀렸는데 아무것도 깨지지 않는 것'입니다. 아래 네 사례는 전부 화면이 정상으로 보이는 상태에서 발견됐고, 그래서 규칙이 하나씩 남았습니다.

사례

  1. 02-1

    형식은 맞는데 내용이 빈 응답이 '성공'으로 판정되던 결함

    문제
    AI 진단 배지가 'AI 분석'으로 바뀌었는데 화면은 빈 껍데기였습니다. 받은 응답은 headline이 placeholder, metric과 value가 빈 문자열인 형태였습니다.
    설계
    JSON 스키마의 required는 키의 존재만 보장하고 빈 문자열·빈 배열도 통과시킵니다. 스키마 위에 세 겹을 더했습니다 — 재료 통제(프롬프트에 들어갈 숫자는 코드가 계산한다) → 코드 게이트(표본 5건 미만이면 호출하지 않는다) → 응답 검증(파싱 단계에서 빈 값을 거부한다). LLM은 계산 결과를 문장으로 옮기는 역할만 맡습니다.
    결과
    빈 응답은 실패로 분류돼 규칙 기반 폴백이 표시됩니다. 부가 원인으로 모델 effort를 낮춰 사고 토큰이 0이 된 것이 빈 응답을 유발했음을 확인했습니다.
  2. 02-2

    같은 입력인데 프롬프트 지문이 흔들린다

    문제
    저장된 진단을 재사용해야 하는데 매번 폴백이 표시됐습니다. 폴백이 정상 동작해서 아무것도 깨지지 않았고, 그래서 한참 눈치채지 못했습니다.
    설계
    재사용 판정용 SHA-256 지문이 JVM 기동마다 달라졌습니다. Map.copyOf가 돌려주는 불변 맵의 순회 순서가 기동 시 nanoTime으로 뽑는 SALT에 좌우되기 때문입니다. '실제로 얼마나 흔들리는가'를 JVM 30회 기동으로 실측한 뒤 enum 선언 순서로 순회를 고정하고 회귀 테스트를 붙였습니다.
    결과
    항목 4개 컬렉션에서 7가지 순서 관측. 같은 원인이 2회, 정렬 tiebreaker 누락으로 같은 규칙의 다른 표면이 1회 더 발견돼 '프롬프트에 들어가는 컬렉션은 순회 순서를 코드로 고정한다'를 규칙으로 승격했습니다. 저장분을 버릴 때 지문 불일치를 로그에 남기도록 바꿨습니다.
  3. 02-3

    합의는 증거가 아니다 — 지표 정의를 데이터로 검산

    문제
    밀집 구간 정의 문답에서 '간격 4일 이하 경기가 2회 이상 연속'이 추천되고 수용 직전이었습니다. 문장으로 읽으면 자연스러웠고 반론이 없었습니다.
    설계
    실제 한 시즌 52경기로 추천안과 대안(임의의 14일 창 안 5경기 이상, 슬라이딩 윈도우)을 각각 계산했습니다. 지표의 목적이 '전 대회 일정이 리그 단독보다 얼마나 빡빡한가'이므로, 리그 단독에서도 밀집을 검출하는 정의는 변별력이 없습니다.
    결과
    추천안 전 대회 8구간·리그만 3구간, 대안 3구간·0구간. 대안을 채택해 응답 계약에 승격(14일·5경기는 파라미터)하고 인수 테스트에 수치를 고정했습니다. '숫자를 만드는 규칙은 문장이 아니라 결과표로 승인한다'가 규칙으로 남았습니다.
  4. 02-4

    회사 코드베이스 — 정답을 참조 구현의 파일:라인으로 고정

    문제
    'AI야 이 페이지 테스트 짜줘'는 AI가 현재 코드를 정답으로 삼게 만듭니다. 현재 코드가 틀렸으면 틀린 테스트가 통과합니다. 세션이 끝나면 알아낸 함정도 사라져 다음 세션이 같은 함정을 다시 밟았습니다.
    설계
    테스트를 짜기 전에 정답의 출처를 참조 구현(모바일 앱)의 파일:라인으로 문서에 먼저 고정하고, AI에게 인용을 강제하고 추측을 금지했습니다. 세션을 넘어 유지할 지식은 프로젝트 메모리(CLAUDE.md)에 Truth Source·Invariants·Known Trap·Fix 형식으로 적었습니다.
    결과
    77개 페이지 시나리오 약 700건이 참조 구현 인용을 달고 작성됐습니다. 두 레이어의 결제 금액이 달랐던 사례는 저장소 4개를 교차 검증해 원인 5종(페이로드 필드 누락·서버 반올림·설정 미적용·메모이제이션 의존성 누락·참조 구현과의 패턴 불일치)을 동시에 정합했습니다.

남은 규칙

  1. 01스키마는 형식만 본다. 내용의 비어 있음은 코드가 거부한다.
  2. 02숫자는 코드가 만들고, LLM은 문장으로 옮기기만 한다.
  3. 03프롬프트에 들어가는 컬렉션은 순회 순서를 코드로 고정한다.
  4. 04폴백이 있는 실패는 반드시 로그를 남긴다 — 잘 동작하는 폴백이 결함을 숨긴다.
  5. 05숫자를 만드는 규칙은 문장 합의가 아니라 실제 데이터 결과표로 승인한다.
  6. 06테스트의 정답은 현재 코드가 아니라 참조 구현의 파일:라인이다.
각(Gak)