지난 글에서 모델 모드 평가 러너를 만들고 남긴 일은 승인된 무료 실행 한 번이었다. 이번 글은 그 실행과, 실행이 드러낸 보고서의 빈틈, 그 빈틈을 메우다 리뷰에서 잡힌 결함 하나다. 코드는 PR #26이다. 실제 모델 호출은 모두 OpenRouter의 :free 모델 하나로, 합성 골든 텍스트만 보냈다.

첫 스모크가 답하지 못한 두 질문

키를 갱신하고 골든셋 3건으로 스모크를 돌렸다. 9회 호출, 실패 없음, 비용 0. 그런데 REALISTIC을 기대한 두 사례가 STRETCH로 나왔고, 세 사례가 낸 인용 22개 중 해석된 것은 8개였다. 보고서는 최종 판정만 담고 있어서 STRETCH가 모델의 판정인지 정책이 근거 없는 매칭을 지운 결과인지 알 수 없었고, 인용 14개가 어떤 규칙에 걸려 버려졌는지도 플래그 하나뿐이었다.

둘 다 텍스트 없이 기록할 수 있는 정보다. 모델 초안의 판정·확신도와 매칭·부족·결격·인용 개수를 최종 결과 옆에 두고, 후처리가 남기는 고정 문장을 짧은 코드로 세어 사유 집계를 만들었다. 파이프라인의 각 단계는 인용을 버리거나 매칭을 지울 때 고정 문장을 missingInformation에 남긴다. M5-B에서 그렇게 한 이유는 버려진 인용문이 결과에 섞여 들어오지 않게 규칙 이름만 남기려는 것이었다. 그 문장이 있는지 보면 무슨 일이 있었는지 안다고 생각했다.

모델도 그 문장을 쓸 수 있다

리뷰는 정상 초안의 missingInformation에 고정 문장 여섯 개를 주입해 봤다. 매칭은 1에서 1, 인용은 1개 중 1개 유효. 아무 일도 없었는데 여섯 사유가 전부 집계됐다. 이유는 단순했다. 최종 결과의 missingInformation은 모델이 쓴 초안의 항목을 그대로 보존한다. 모델이 우연히든 어떻게든 같은 문장을 출력하면, 실제로 일어나지 않은 후처리가 기록된다. 리뷰 문장을 그대로 옮기면 “모델 문자열과 서버 진단의 출처를 구분해야 이번 PR이 의도한 원인 분석에 사용할 수 있습니다.”

초안과 최종의 문자열 차집합도 답이 아니다. 모델이 그 문장을 쓴 채로 서버에서도 같은 일이 실제로 일어나면, 중복 제거 때문에 차집합에서 사라진다. 리뷰어가 요구한 대조군이 정확히 그 둘이었다. 문구만 주입한 정상 초안은 0이어야 하고, 같은 문구가 있어도 실제 오류가 있으면 기록돼야 한다.

고친 방식은 각 단계가 자기가 한 일을 개수로 돌려주는 것이다. 정책은 지운 근거 없는 매칭 수와 옮기거나 버린 인용 수를, 인용 검증기는 풀리지 않은 인용 수와 고아 인용 수를, 파이프라인은 그 둘을 합치고 스크리닝 컨텍스트를 버렸는지를 반환한다. 기존 함수는 상세 결과에서 판정 객체만 반환하도록 위임했다. 기존 테스트와 정책 기준선 비교에서는 회귀가 없었다. 보고서의 사유 코드는 이 개수에서만 나오고 문장은 읽지 않는다. 어댑터의 인용 경계 처리는 이번 단계 진단의 수집 범위 밖이므로, 해당 사유를 집계에서 제외하고 문서에 남겼다. 필드의 의미가 바뀌었으니 보고서 버전도 올렸다. 그 전에 돌던 라이브 실행의 보고서는 관측 기록으로 남기되 사유 집계는 기준선으로 쓰지 않는다.

같은 문장을 만드는 코드가 있다는 것과 그 코드가 실행됐다는 것은 다른 사실이다. 지난 글에서 검증 코드가 있다는 것과 검증이 실행을 멈춘다는 것을 구분했는데, 이번에는 진단 문장에서 같은 구분을 배웠다.

13건을 돌렸더니

스모크 뒤 앞 13건, 최대 39회 호출을 승인받아 돌렸다. 결과는 29회 호출 뒤 종료. 7건 채점, 1건 판정 타임아웃, 5건 추출 실패.

타임아웃 1건은 판정 호출이 300초에 끊긴 것이다. 지난 PR에서 SDK 타임아웃이 응답 헤더까지만 막는다는 지적을 받고 호출마다 signal을 넣었는데, 실제 제공자 호출에서도 300초 deadline이 요청을 중단하고 다음 사례로 넘어갔다. 응답 본문까지 취소되는 동작은 앞선 로컬 재현에서 확인했고, 이번 호출이 헤더를 기다리던 중이었는지 본문을 받던 중이었는지는 기록에 없다. 보고서는 끝까지 저장됐다.

추출 실패 5건은 프로필 추출 첫 호출이 75~402밀리초 만에 인증 오류로 끝난 것이다. 실행 도중 키가 더 이상 받아들여지지 않게 됐고, 실행 뒤 키 정보 조회도 401을 돌려줬다. 하루짜리로 만든 키였지만 만료 시각 전이었고, 401만으로는 원인을 확정할 수 없다. 러너 입장에서는 실패가 실패로 분류되고 다음 사례로 넘어간 것이 전부다.

추출에 성공한 8건에서 요건 텍스트 대응은 9/9였다. 채점된 7건은 결격 재현율 2/2, 허용 집합 안의 판정 5/7, 인용 35개 중 13개 해석, 스키마 실패·거절·잘림 0. 판정 호출 8회는 타임아웃을 포함해 중앙값 약 54초였다. 전체 실행에서 사용량이 보고된 23회의 출력 토큰 합계는 54,731개였고, 제공자가 보고한 추론 토큰은 49,040개였다. 초안과 최종을 나란히 보니 두 가지가 갈렸다. 정책이 판정을 바꾼 사례는 2건이다. 하나는 모델이 STRETCH라 한 것을 결격 승격으로 PASS로, 다른 하나는 모델이 REALISTIC이라 한 것을 같은 이유로 PASS로. 확신도가 high에서 low로 내려간 사례는 따로 5건인데, 근거 없는 매칭이 지워지거나 인용이 풀리지 않아 후처리에서 낮아졌다. 판정이 바뀐 것과 확신도가 바뀐 것은 다른 일이라 따로 센다.

뒤의 13건 실행에서는 REALISTIC을 기대한 두 사례의 STRETCH가 모델 초안에서 나왔다. 정책은 하나는 건드리지 않았고 다른 하나는 근거 없는 매칭을 지우며 확신도만 낮췄다. 초안을 남기지 않은 첫 스모크의 원인까지 확인한 것은 아니다. 이 골든셋의 기대 판정은 아직 아무도 서명하지 않은 pending이라, 모델이 틀렸는지 골드가 좁은지는 사람 검토 항목이다.

확인한 것과 남은 것

테스트는 434건, 정책 평가 35건, 기준선 비교는 28건 그대로에 회귀 없음. 라이브 수치는 dots-studio/dots-3-note-preview:free 한 모델, AtlasCloud 엔드포인트 하나에서 나온 것이고 다른 모델에 대해서는 아무것도 말하지 않는다. 무료 모델에는 요청 한도가 있다. 이번에 직접 확인한 것은 48회까지 한도 오류 없이 실행됐다는 사실뿐이다.

남은 것은 새 키로 v3 러너를 돌려 첫 기준선을 만드는 일이다. 사유 집계까지 담긴 보고서가 나오면 M5-E 관측으로 넘어간다. 단계 진단의 계약은 저장소의 pipeline.ts에 있다.