캔버스 하네스를 검증하다가 최종 제출 도구를 호출하고도 실행이 계속되는 것을 봤습니다. 반대 방향의 위험도 같이 있었습니다. 이전 회차에 제출한 기록이 남아 있으면 지금 진행 중인 회차를 일찍 끝낼 수 있었습니다. '끝났다'를 어디서 읽고 있었는지, 그리고 왜 재시도 상한 하나로는 아무것도 막을 수 없었는지 정리합니다.
끝났다는 사실을 대화 기록에서 찾고 있었습니다
초기 엔진에도 재시도와 종료는 있었습니다. 공급자 오류가 나면 같은 요청을 다시 보냈고, 품질이 기준에 못 미치면 피드백을 붙여 새 답을 만들었습니다. 정책 검사는 모델 호출 전후와 도구 실행 직전, 회차 경계에 뒀습니다.
각 장치를 따로 보면 전부 동작했습니다.
6월 8일부터 사흘간 캔버스 하네스를 검증하면서 이것들이 겹치는 자리를 봤습니다. 메시지 전송처럼 그 호출 자체가 제출을 뜻하는 도구를 부르고도 실행이 이어졌습니다.
원인은 종료를 판단하는 방식이었습니다. 저희는 전체 대화 이력에서 종착 도구 이름을 찾고 있었습니다.
이 방법은 양쪽으로 다 틀립니다.
도구는 실행됐는데 결정 단계가 현재 호출을 못 봄
→ 끝났어야 할 실행에 모델 호출이 한 번 더 붙는다
과거 회차의 종착 기록이 이력에 남아 있음
→ 지금 진행 중인 회차를 완료로 판단한다
대화 기록은 무슨 일이 있었는지 말해주지만 지금 어떤 상태인지는 말해주지 않습니다. 저희는 이력을 상태처럼 읽고 있었습니다.
그래서 종료 판단의 범위를 현재 회차로 좁혔습니다. 가장 최근 모델 응답이 이번 회차에 만든 도구 요청인지, 그 도구가 종착으로 등록됐는지, 실행이 성공했는지를 함께 봅니다. 세 조건이 다 맞을 때만 완료로 갑니다.
현재 응답에 도구 요청 없음 → 일반 답변으로 보고 판정한다
현재 응답에 중간 도구 있음 → 결과를 더하고 계속한다
현재 응답에 종착 도구 있음 → 성공을 확인하고 끝낸다
이력은 근거로 남되 현재 상태를 대신하지 않습니다.
로그와 상태를 같은 것으로 다루는 설계는 처음에는 코드가 줄어듭니다. 문제는 로그가 여러 회차에 걸쳐 누적된다는 점입니다. 누적된 기록에서 조건을 찾으면 언제 있었던 일인지가 사라집니다.
재시도 상한을 낮췄더니 정상적인 도구 탐색이 끊겼습니다
종료를 정리하면서 반복도 같이 봤습니다. 당시에는 상한이 사실상 하나였습니다.
비용이 커져서 그 상한을 낮췄습니다. 그러자 답을 잘 만들던 실행까지 중간에 끊겼습니다.
끊긴 것은 품질 재작성이 아니라 도구 탐색이었습니다. 검색을 세 번 하며 자료를 모으던 실행이 상한에 걸려 멈췄습니다.
같은 상한이 서로 다른 세 가지를 세고 있었습니다.
공급자 호출 시도 응답을 못 받아 같은 요청을 다시 보냄 → 상태가 전진하지 않음
도구 진행 회차 새 관측을 얻고 작업이 앞으로 나아감 → 상태가 전진함
품질 재작성 반려 사유를 받아 새 후보를 만듦 → 다른 답을 만듦
세 개는 소비하는 것도 다르고, 막아야 하는 이유도 다르고, 상한을 걸 자리도 다릅니다. 하나로 묶어두면 비용을 줄이려는 조정이 기능을 끊고, 기능을 살리려는 조정이 비용을 풀어놓습니다.
그래서 상한을 각각 반복이 시작된 계층에 뒀습니다. 공급자 호출 시도는 모델 호출 계층이, 품질 재작성은 판정 경로가, 도구를 포함한 전체 진행은 실행 루프와 예산이 제한합니다.
설정 이름은 비슷하게 남을 수 있습니다. 중요한 것은 어느 계층이 그 값을 읽고 어떤 사건을 세는가입니다.
공급자 오류로 다시 보내는 것은 진행이 아니었습니다
속도 제한이나 일시적인 과부하로 모델 응답을 못 받으면 답 후보도 도구 요청도 없습니다. 이때 다시 보내는 것은 새 작업이 아니라 같은 논리 호출을 복구하는 일입니다.
그래서 복구 가능한 오류는 공급자 계층 안에서 처리합니다. 요청과 대화 상태는 그대로 두고, 오류 종류에 맞는 대기 시간을 적용한 뒤 다시 호출합니다.
여기서 늘어나는 값은 공급자 호출 시도뿐입니다. 에이전트의 전체 회차나 품질 재작성 횟수는 움직이지 않습니다.
정한 시도를 다 쓰면 호출 실패를 상위 루프로 올려보냅니다. 빈 응답을 만들어 정상 결과처럼 다음 단계에 넘기지 않습니다. 이것도 4편에서 판정을 건너뛴 상태를 점수로 위장하지 않기로 한 것과 같은 규칙입니다.
재시도 사건에는 오류 종류와 시도 번호, 대기 시간을 남깁니다. 그래야 전체 지연에서 모델이 생각한 시간과 전송 복구로 기다린 시간을 나눠 볼 수 있습니다.
에이전트가 느리다는 신고를 받았을 때 이 구분이 없으면 어디를 손봐야 할지 알 수 없습니다. 모델을 바꿀 일인지, 도구를 줄일 일인지, 네트워크를 볼 일인지가 같은 숫자 안에 들어 있습니다.
도구를 요청한 응답에 품질 판정을 걸었더니 정상 탐색이 반려됐습니다
품질 판정을 붙이고 나서 자연스럽게 든 생각은 모든 모델 응답을 판정하자는 것이었습니다.
그런데 도구를 요청한 응답에는 완성된 텍스트가 없습니다. 판정기는 그것을 보고 답변이 부족하다고 판단했습니다.
정상적으로 자료를 찾고 있던 실행이 재작성 실패로 기록되기 시작했습니다.
그러니까 저희가 만든 것은 품질 판정이 아니라 '텍스트가 있는지 보는 검사'였습니다. 판정할 수 있는 응답과 진행 중인 응답을 구분하지 않은 채 같은 자를 대고 있었습니다.
수정은 판정 로직이 아니라 실행 단계에서 했습니다. 판정을 적용할 수 있는 응답인지를 먼저 가리고, 도구 진행 회차에는 일반 답변과 같은 판정을 걸지 않습니다.
품질 재시도는 공급자 계층이 아니라 판정과 결정 단계가 소유합니다. 기준별 점수와 미달 사유를 교정 피드백으로 넘기고 재작성 횟수를 올립니다. 상한에 닿으면 더 이상 후보를 만들지 않되, 그것을 품질 통과로 기록하지는 않습니다. 최종 결과에 마지막 판정과 재시도 소진 여부가 함께 남습니다.
평가 장치를 새로 붙일 때는 평가 대상이 아닌 입력이 들어왔을 때 무엇을 반환할지 먼저 정해두는 편이 안전합니다. 대체로 그런 입력은 있고, 기본값이 없으면 낮은 점수가 나옵니다.
검사 순서를 바꿀 수 없는 이유가 따로 있었습니다
한 회차의 순서는 모델 호출, 응답 정책 검사, 도구 정책과 실행, 품질 판정, 회차 경계 결정으로 이어집니다.
이 순서가 취향의 문제가 아닌 이유는 각 검사가 보호하는 대상이 다르기 때문입니다. 입력이 허용됐어도 모델 응답에 민감정보가 생길 수 있습니다. 응답 자체는 안전해도 그 응답이 요청한 도구 실행에는 별도 승인이 필요할 수 있습니다.
정책이나 비용 한도에 걸리면 그 검사 지점에서 종료 이유를 만들고, 뒤따르는 모델 호출이나 도구 호출을 막습니다.
테스트도 횟수가 아니라 사건의 순서를 봤습니다
검증에서 최종 문자열은 보지 않았습니다. 대신 사건이 올바른 순서로 났는지를 확인했습니다.
일시 오류 뒤에 논리 응답이 하나만 만들어지는지, 도구 회차가 품질 재시도 횟수를 건드리지 않는지, 종착 도구가 현재 회차에서 실행되면 추가 모델 호출 없이 끝나는지, 과거의 종착 기록만으로는 끝나지 않는지를 봤습니다. 차단 정책 뒤에 호출이 더 이어지지 않는지도 확인했습니다.
여기까지 정리하고도 남은 것이 있습니다. 종착 도구 뒤에 이어지는 캔버스 출력 노드까지 포함하면 부수효과를 누가 소유하는지가 여전히 겹쳤습니다. 이 문제는 9편에서 따로 다뤘습니다. 후보와 점수가 회차를 넘어 남는 더 미세한 최신성 문제도 나중에 발견돼 10편으로 넘어갔습니다.
호출 횟수는 원인이 아니라 결과였습니다
이 편에서 저희가 한 일을 한 줄로 줄이면 이렇습니다. 같은 숫자로 세고 있던 것들을 갈라놓았습니다.
공급자 복구는 상태를 전진시키지 않고, 도구 진행은 전진시키고, 품질 재작성은 다른 답을 만듭니다. 종료도 완료와 중단과 한도 소진이 다릅니다.
호출 횟수는 그 구분들이 남긴 결과일 뿐이고, 그것만 보면 실행 시간도 비용도 설명되지 않습니다. 1편에서 '재시도 3회'가 아무것도 설명하지 못했던 것과 같은 자리에, 이번에는 종료와 상한이 있었습니다.
경계가 생기자 처음으로 판정 점수를 쓸 데가 생겼습니다. 그때까지 점수는 통과와 반려를 가르는 데만 썼는데, 이제는 어떤 설정에서 점수가 잘 나오는지 비교할 수 있게 됐습니다. 다음 편에서는 그 점수로 설정 후보를 탐색하면서, 탐색 자체가 폭주하지 않게 제한한 방법을 다룹니다.
이전 편 → 답을 만든 모델에게 채점까지 맡기고 있었습니다 (4편)
다음 편 → 검증셋을 떼어뒀다고 믿었는데 아니었습니다 (6편)

