AI가 도구를 한 번 더 부르려 했는데, 서버가 조용히 버리고 있었습니다
AI 채팅에서 가장 위험한 사고는 에러가 아니었습니다. 틀린 답이 멀쩡한 얼굴로 저장되는 것이었습니다. 운영 중인 내부 프로젝트 관리 도구의 AI 채팅에서, 모델이 답을 만들다 만 중간 나레이션이 최종 답변으로 저장되는 일이 있었습니다. 화면만 보면 완성된 답처럼 보였고, 사용자는 그것을 정답으로 믿을 수밖에 없는 구조였습니다.
이 채팅은 사용자가 질문하면 모델이 검색, 노드 조회, 이웃 조회 같은 도구를 호출해 답을 만드는 구조입니다. 처음 설계는 단순했습니다. 질문이 들어오면 도구를 한 번 부르고, 그 결과로 답변을 만들어 끝. 질문 → 도구 1회 → 답변이라는 흐름을 상정했습니다.
후속 도구 요청이 조용히 사라졌다
증상은 이랬습니다. 모델이 첫 도구 결과를 받아본 뒤 정보가 부족하다고 판단해 전문 검색 같은 추가 도구를 한 번 더 요청합니다. 그런데 backend에는 그 요청을 받아줄 코드가 없었습니다. 후속 요청은 조용히 버려지고, 그때까지 모델이 흘려둔 중간 나레이션이 최종 답변으로 저장됐습니다. 관련 항목을 찾았으니 상세 내용을 확인해보겠다는 문장이 답변의 자리에 앉아 있었습니다.
더 나쁜 점은 이 과정에 에러가 하나도 없었다는 것입니다. 로그 경고도 없이 대화는 정상 종료로 기록됐습니다. 미완성 답이 완성의 형식을 갖추고 저장된 셈입니다.

원인은 한 줄, 1왕복 고정
원인을 따라가 보니 채팅 서비스가 정확히 1왕복으로 하드코딩돼 있었습니다. 모델 호출, 도구 실행, 모델 재호출, 그리고 무조건 종료. 도구는 한 번 부르고 끝이라는 가정이 코드에 박혀 있었습니다.
이 가정은 단순 파이프라인에서는 맞습니다. 하지만 에이전트에서는 틀립니다. 에이전트의 본질은 모델이 도구 결과를 보고 다음 행동을 정한다는 데 있습니다. 결과를 읽고 한 번 더 조회하겠다고 판단하는 것 자체가 에이전트가 일하는 방식인데, 실행기가 그 왕복을 받아주지 못하면 그 판단은 구조적으로 버려집니다.
종료 조건까지 도는 루프로 바꿨다
수정 방향은 하나였습니다. 도구 실행을 고정 횟수가 아니라 종료 조건까지 도는 루프로 바꾸는 것입니다. 더 이상 도구 요청이 없거나 한도에 도달할 때까지 왕복이 계속됩니다. 다만 모든 도구를 똑같이 돌리지는 않았습니다.
도구 종류 | 루프 정책 | 이유 |
|---|---|---|
읽기 도구(검색·조회) | 최대 4라운드 자율 반복 | 부작용이 없어 모델 판단에 맡겨도 안전 |
쓰기 도구(생성·수정) | preview 후 정지, 사용자 확인 대기 | 자율 루프에 쓰기를 넣으면 폭주 위험 |
읽기와 쓰기를 가른 것이 이 수정의 핵심이었습니다. 읽기는 몇 번을 반복해도 데이터가 변하지 않으니 모델의 자율에 맡길 수 있습니다. 반면 쓰기는 한 번의 실수가 데이터에 남습니다. 자율 반복 안에 쓰기를 넣어두면 모델이 스스로 만들고 고치고 지우는 일이 사람 확인 없이 이어질 수 있습니다. 그래서 쓰기 도구는 기존대로 preview를 보여주고 정지하는 정책을 유지했습니다.

실행기가 진행을 통제한다는 원리는 이번이 처음이 아니었습니다. 같은 도구에서 다섯 역할의 LLM이 토론하는 기능을 만들 때도 발언 순서와 라운드 수를 실행기가 쥐고 있어야 결과가 수렴했습니다. 모델에게 판단을 맡기되 진행의 틀은 시스템이 잡는다는 점에서 같은 문제였습니다.
중단은 무음이 아니라 흔적으로
루프를 도입하면서 실패 처리도 같이 바꿨습니다. 왕복 도중 오류가 나면 처리 중 오류로 중단됐다는 문구를 assistant 메시지로 영속화합니다. 사용자 화면에 중단 사실이 그대로 남습니다.
무음 실패가 최악인 이유는 틀린 답이 정답의 얼굴을 하기 때문입니다. 시스템이 조용히 멈추면 사용자에게는 판별 수단이 없습니다. 차라리 빨간 에러가 낫습니다. 에러는 다시 물어보게 만들지만, 멀쩡해 보이는 미완성 답은 그대로 믿게 만듭니다.

자율 루프의 안전장치
자율 반복에는 폭주와 지연이라는 위험이 따라옵니다. 그래서 한도와 타임아웃을 같이 걸었습니다. 읽기 루프는 최대 4라운드에서 멈추고, 스트리밍 타임아웃은 느린 provider의 최악 지연을 감안해 120초에서 300초로 올렸습니다.
한도 4가 정답이라는 뜻은 아닙니다. 대부분의 질문은 여전히 1왕복이면 끝나므로 루프는 필요할 때만 확장됩니다. 반대로 한도가 너무 낮으면 복잡한 질문이 중간에 잘리고, 왕복이 길어질수록 컨텍스트가 누적돼 토큰 비용이 커집니다. 이 값은 도메인마다 다시 잡는 튜닝 대상입니다.
검증은 네 갈래로 했습니다. 다중 왕복 완주, 한도 도달 시 종료, 쓰기 도구의 확인 대기, 에러 영속화를 각각 테스트했고, e2e에서는 검색, 노드 조회, 이웃 조회로 이어지는 3라운드 왕복이 실제로 완주되는 것까지 확인했습니다. 수정 후 중간 나레이션이 최종 답으로 굳던 사고는 사라졌습니다.
이번 일이 왕복 횟수라는 축의 문제였다면, 도구 개수가 늘어나는 축의 문제도 따로 있습니다. 루프가 아무리 잘 돌아도 모델 앞에 놓인 도구가 100개면 선택 단계부터 흔들립니다.
📋 도구 실행이 고정 횟수로 하드코딩돼 있는지, 종료 조건 루프인지 확인했는가
📋 모델의 후속 도구 요청이 버려질 때 시스템이 무음인가, 흔적을 남기는가
📋 읽기 도구와 쓰기 도구의 루프 정책이 분리돼 있는가
📋 자율 루프에 라운드 한도와 타임아웃이 걸려 있는가
📋 중단 시 사용자에게 보이는 에러 메시지가 대화에 영속화되는가
1왕복 고정은 처음 만들 때는 합리적인 단순화였습니다. 하지만 에이전트로 쓰이는 순간 그 단순화는 모델의 후속 판단을 버리는 구조가 됩니다. 지금은 새 도구를 붙일 때마다 읽기인지 쓰기인지, 루프 안에서 돌 것인지 멈출 것인지부터 정합니다. 도구 실행기는 한 번 부르는 함수가 아니라 종료 조건까지 도는 루프라는 전제로 설계하는 편이 뒤탈이 적었습니다.