개발 취재 노트

AI 글 생성 파이프라인, 테스트는 다 통과했는데 실제로 돌리면 통과 글이 0개일 때

테스트가 모두 통과해도 실제 글의 통과율은 따로 확인해야 한다. 주제 선정 → 웹 조사 → 집필 → 심사를 거치는 IT 문제해결 글 생성 파이프라인에서 테스트 9개가 통과했지만, 실제 Claude CLI 실행은 후보 31개 중 통과 0개로 보고됐다. 출처 충돌 단계에서 27개가 탈락했고, 모델 심사에 간 4개는 고쳐 쓰기(repair)를 한 번씩 거친 뒤에도 탈락했다 .

결론

이 사건은 해결 완료 사례가 아니다. 다음 확인 대상은 출처 충돌로 탈락한 근거가 실제 모순인지, 기기·버전 조건 차이인지다. 조건 차이의 오탐 가능성은 제기됐지만, 27개를 개별 재검증한 결과는 남아 있지 않다 .

수치는 한 번의 실행에 대한 assistant의 요약 보고다. 원시 실행 로그를 독립적으로 확인한 결과가 아니며, 이전 실행과 조건이 같았는지도 확인되지 않았다. 따라서 변경의 효과 크기나 IT 글 자동화 전체의 가능성을 이 숫자만으로 판정하지 않는다 .

상황

만들던 것은 IT 문제해결 글을 자동으로 생성하는 프로그램이다. discover → research → write → 게이트 구조로 후보 주제를 고르고, 웹에서 근거를 모아 글을 쓴 뒤 심사한다. 통과한 글은 저장하며, 실패 지점에 따라 집필 전에 탈락할 수도 있다 .

이번 사건과 관련된 검사는 다음과 같다.

검사역할
주제 점수minimum_topic_score 미만이면 웹 조사·집필 전에 탈락
근거 검사(evidence)조사한 출처의 충돌(source_conflict)로 탈락
모델 심사(model_review)집필 후 근거와의 모순이나 지어낸 구체값 등을 검사

앞선 실행은 통과 2개·폐기 309개로 집계됐다. 폐기 중 주제 점수 미달이 290개, 모델 심사가 10개, 근거 단계가 9개였다. 당시에는 주제 문턱이 가장 큰 탈락 지점이었다 .

증상

이 집계를 바탕으로 다음 변경을 넣었다 .

  1. minimum_topic_score를 75에서 68로 낮췄다.
  2. revise()에 심사 지적 사항과 근거를 전달해 문제 부분을 최소 수정하게 했다. max_repair_attempts 기본값은 1이다.
  3. 심사 기준을 조정해 근거와 모순되거나 지어낸 구체값은 탈락시키고, 근거로 반박되지 않는 안전한 상식은 허용하도록 했다.

이어 조사 전에 유사 제목을 거르는 maximum_title_similarity: 0.8도 추가했다. 기존 6개, repair 관련 2개, 중복 검사 1개를 합친 테스트 9개가 모두 통과했다고 보고됐다 .

하지만 실제 Claude CLI 실행 결과는 43분 동안 후보 31개, 통과 0개였다. 탈락 내역은 evidence 27개, model_review 4개, 주제 점수 미달 0개였다 .

원인 분석

1. 테스트가 확인한 범위와 실제 품질은 달랐다

일반 픽스처는 심사를 통과하는 설정이었다. 엔드투엔드 결과는 ready 2 / rejected 1, 통과 글의 repair_attempts [0,0]으로 보고됐다. 따라서 이 실행은 repair의 효과를 보여주지 않는다 .

반면 별도 repair 테스트에는 첫 심사 실패 후 수정해 통과하는 시나리오가 있었다. 완료 보고에는 “repair로 심사 실패 글 회복”, “repair=0이면 즉시 폐기” 두 테스트가 명시돼 있다. repair 경로를 전혀 테스트하지 않은 것은 아니다. 다만 정해진 테스트 시나리오의 통과와 실제 모델이 쓴 글의 회복은 다른 확인 항목이다 .

2. 가장 많은 탈락은 repair보다 앞에 있었다

실제 실행에서는 27개가 출처 충돌로 탈락했다. 당시 구현 설명에 따르면 repair는 집필 후 평가 실패를 고치는 기능이며, 이 근거 단계의 탈락에는 적용되지 않았다 .

결과 보고는 evidence_gate가 conflicts를 하나라도 받으면 폐기한다고 설명했다. 기기·버전마다 메뉴가 다른 것을 출처의 모순으로 과잉 판정했을 가능성도 함께 제기했다. 그러나 실제 모순과 조건 차이의 비율은 확인되지 않았다. 출처 충돌이 가장 큰 탈락 사유였다는 것과, 그 판정이 잘못됐다는 것은 구분해야 한다 .

3. 한 번의 repair로 회복된 글은 없었다

심사 탈락 4개는 모두 repair를 한 번 거쳤다. 보고된 오류 예시는 Google 근거의 사실을 Apple·Samsung 것처럼 표기한 출처 귀속 오류와, Pixel 한정 사실의 일반화였다 .

당시 해석은 출처가 뒤섞여 최소 수정 한 번으로 해결되지 않았다는 것이었다. 이 기록으로 확인할 수 있는 범위는 이번 4개가 한 번의 수정 후에도 탈락했다는 것이다. 이런 오류는 언제나 한 번으로 못 고친다거나, 두 번이면 해결된다고 단정할 수 없다 .

다음에 확인할 것

이 세션의 마지막 결과 보고에도 수율을 회복한 후속 실행은 없다. 주제 점수 미달은 이번 실행에서 0개였지만, 이전 실행과 조건이 같다는 근거가 없어 이를 변경 효과의 크기로 환산할 수는 없다 .

다음은 이 글의 검증 제안이며, 완료된 해결책이 아니다.

  1. 출처 충돌 탈락 건의 근거를 읽고 실제 모순과 기기·버전 조건 차이를 구분한다.
  2. 조건 차이의 오탐이 확인되면 조사 프롬프트와 근거 검사 규칙을 수정하고 다시 측정한다.
  3. repair 전후 글에서 출처 귀속과 적용 범위가 어떻게 바뀌었는지 비교한다. 횟수만 늘려 효과를 가정하지 않는다.

1단계 예시: 충돌 분류표

아래 표는 분류 방법을 보이려고 새로 만든 합성 예시다. 세션의 27개 실제 근거가 아니다. 탈락한 후보마다 충돌한 두 출처의 주장을 나란히 적고, 기기·버전 같은 조건을 뽑아 비교한다.

후보출처 A 주장출처 B 주장조건 비교판정
x1"설정 > 블루투스에서 기기 삭제" (Pixel, Android 14)"설정 > 연결된 기기에서 삭제" (Galaxy, One UI 6)기기가 다름조건 차이 (오탐)
x2"재부팅 후 페어링 유지됨" (같은 기기·같은 버전)"재부팅 후 페어링 초기화됨" (같은 기기·같은 버전)조건 같음, 결론 반대실제 모순
x3"펌웨어 1.2에서 해결""업데이트 후에도 재현" (펌웨어 버전 미기재)한쪽 조건 없음판정 보류

판정 규칙은 세 가지다.

27개 전부를 이렇게 분류하면 조건 차이 n / 실제 모순 m / 보류 k가 나온다. 이 비율이 나와야 근거 검사 규칙을 고칠지 판단할 수 있다. 위 x1~x3은 방법의 예이며 실제 비율과 무관하다.

집계 코드

집계 예시도 합성 입력이다. 세션의 실제 코드나 리포트 스키마가 아니며, 아래 필드가 있다고 가정한다.

필드뜻
id후보 식별자
status최종 상태. ready(통과) 또는 rejected(탈락)
stage탈락한 단계(topic_score, evidence, model_review). 통과 후보는 done
repair_attempts고쳐 쓰기를 시도한 횟수
from collections import Counter

results = [
    {"id": "c01", "status": "rejected", "stage": "evidence", "repair_attempts": 0},
    {"id": "c02", "status": "rejected", "stage": "evidence", "repair_attempts": 0},
    {"id": "c03", "status": "rejected", "stage": "evidence", "repair_attempts": 0},
    {"id": "c04", "status": "rejected", "stage": "model_review", "repair_attempts": 1},
    {"id": "c05", "status": "rejected", "stage": "model_review", "repair_attempts": 1},
    {"id": "c06", "status": "ready", "stage": "done", "repair_attempts": 1},
    {"id": "c07", "status": "ready", "stage": "done", "repair_attempts": 0},
    {"id": "c08", "status": "rejected", "stage": "topic_score", "repair_attempts": 0},
]

rejected = [r for r in results if r["status"] == "rejected"]
by_stage = Counter(r["stage"] for r in rejected)
repaired = [r for r in results if r.get("repair_attempts", 0) > 0]
recovered = [r for r in repaired if r["status"] == "ready"]

print(by_stage)
print(f"repair 시도 후보 {len(repaired)} / 회복 후보 {len(recovered)}")

예상 출력은 다음과 같다.

Counter({'evidence': 3, 'model_review': 2, 'topic_score': 1})
repair 시도 후보 3 / 회복 후보 1

읽는 법: 탈락 6개 중 3개가 evidence에서 멈췄고, repair를 시도한 3개(c04·c05·c06) 중 c06 하나만 통과했다. 세션의 실제 실행은 이 형태로 보면 evidence 27, model_review 4, repair 시도 4, 회복 0으로 보고됐다 .

테스트로 실패 후 수정 흐름을 확인하고, 실제 실행에서는 단계별 탈락과 수정 후 회복을 별도로 측정한다. 이 둘을 함께 봐야 다음에 고칠 지점을 정할 수 있다.


취재 후기 — 초록불 아홉 개와 0개의 글

박도은
우리가 만드는 건 IT 문제해결 글 생성기야. 주제 선정, 웹 조사, 집필, 심사를 거치는데 테스트 아홉 개가 통과한 뒤 실제 실행 결과는 후보 31개에 통과 0개로 나왔어 .
정바다
새로 넣은 repair가 아예 안 돈 거 아닐까? 심사에서 떨어진 글에 지적 사항을 전달해서 고쳐 쓰게 했는데, 그 단계가 빠지면 그냥 버려질 테니까 .
정하늘
먼저 결과 보고의 repair_attempts, 고쳐 쓰기를 시도한 횟수를 보자. 0인지 1인지 확인하면 수정 시도가 기록됐는지는 구분할 수 있어 .
박도은
심사 탈락 네 개는 모두 repair_attempts:1이야. 한 번씩 시도했지만 통과한 글은 없다고 나와 .
정하늘
그럼 아예 시도하지 않았다는 가설은 보고와 안 맞아. 다만 횟수가 찍혔다는 것만으로 수정 내용이나 구현 전체가 정상이라고 판단할 수는 없어 .
박도현
시도했는지와 살렸는지를 따로 세야겠네. 호출 횟수는 실행의 기록이고, 우리가 원하는 결과는 심사를 통과한 글이니까.
정바다
그런데 테스트도 통과했잖아. 실패한 글을 고쳐서 통과시키는 테스트라면 실제 글도 좋아졌다는 뜻인 줄 알았어 .
정하늘
어떤 입력과 응답으로 검사했는지 나눠 보자. 실제 모델 대신 정해진 응답을 주는 테스트용 provider를 썼다면 확인한 범위가 다를 수 있어.
박도은
일반 픽스처 실행은 심사를 통과하는 설정이라 수정 횟수가 [0,0]이었어. 별도 repair 테스트에는 첫 심사 실패 후 수정해서 통과하는 시나리오가 있었고 .
정하늘
그러면 수정 경로를 검사하지 않았다는 말도 틀려. 다만 준비한 시나리오가 통과한 것과 실제 모델의 글이 좋아진 것은 별도로 확인해야 해 .
박도현
테스트가 답한 질문을 정확히 읽어야겠어. 실패 후 수정 흐름이 이어지는지 확인했다고 해서 실제 생성물의 회복률까지 얻은 건 아니니까.
정바다
심사 탈락이 네 개면 나머지 27개는 어디서 멈춘 거야? 앞에서 막혔다면 고쳐 쓰기까지 도착하지 못했을 수도 있겠네 .
박도은
evidence, 조사한 근거를 검사하는 단계야. 27개 모두 source_conflict, 출처 충돌로 탈락했다고 나와 .
정바다
출처가 그렇게 많이 충돌하면 IT 글은 자동으로 못 만드는 것 아닐까? 근거를 모으는 단계부터 대부분 막힌 셈이잖아 .
정하늘
그 판단 전에 충돌 판정 기준을 보자. 실제로 같은 조건에서 답이 모순되는 건지, 기기와 버전이 달라 답이 다른 건지 구분해야 해.
박도은
결과 보고에는 conflicts가 하나라도 있으면 폐기한다고 나와. 기기마다 메뉴가 다른 것까지 충돌로 잡았을 가능성도 적혀 있어 .
정하늘
그러면 분야 전체가 불가능하다고 결론 낼 수 없어. 지금 보고만으로는 그 27개 중 실제 모순과 조건 차이가 각각 몇 개인지 모르니까 .
박도현
탈락 사유의 이름과 실제 원인을 구분해야겠네. 충돌이라고 분류된 수만 보고 근거 자체가 전부 잘못됐다고 판단하면 고칠 곳을 놓쳐.
정바다
그럼 심사에 간 네 개는 수정 횟수를 두 번으로 늘려 볼까? 한 번 시도하고도 탈락했다면 수정 기회가 부족했을 수도 있잖아 .
정하늘
횟수를 바꾸기 전에 지적 사항과 수정 전후 글을 비교하자. 어떤 오류가 남았는지 알아야 같은 지시를 반복할지, 수정 방법을 바꿀지 정할 수 있어.
박도은
보고된 예시는 Google 근거를 Apple·Samsung 사실처럼 쓴 것과 Pixel 한정 사실을 일반화한 것이야. 네 개 모두 한 번 수정한 뒤에도 탈락했어 .
정하늘
출처 귀속과 적용 범위가 남은 문제였다는 설명이네. 하지만 이 결과만으로 두 번이면 낫는지, 이런 오류는 한 번으로 절대 못 고치는지까지는 알 수 없어 .
박도현
횟수는 바꿀 수 있는 설정일 뿐이야. 수정 전후를 비교하고 회복 수를 다시 재야 그 변경이 도움이 됐는지 알 수 있어.
정바다
그러면 주제 점수 탈락 0개와 repair 네 번 시도는 확인했어도, 우리가 바꾼 게 전부 효과 있었다고 말할 단계는 아니겠네 .
정하늘
맞아. 이번 보고의 통과 수는 여전히 0개야. 앞선 실행과 조건이 같은지도 확인되지 않았으니, 지금은 출처 충돌과 수정 실패를 다음 조사 대상으로 잡는 게 맞아 .
박도현
테스트 통과, 실제 수정 시도, 수정 후 회복을 따로 확인하자. 단계별 탈락 수와 회복 수를 함께 봐야 다음 변경의 근거가 생겨.