툴 호출은 결과가 아니다
고객 한 명이 문의를 남겼어요. 745달러짜리 주방 가전이 내슈빌 물류센터에서 '예외' 상태로 15일째 멈춰 있다는 내용이죠. AI 에이전트는 꽤 성실하게 움직입니다. 주문 조회, 배송 추적, 고객 프로필 확인, 환불 정책 두 번 검색, 기존 티켓 없음 확인, 티켓 생성, 타임라인 기록까지 총 9번의 툴 호출을 수행했어요. 정책 판단도 틀리지 않았습니다. 해당 고객 세그먼트는 지연 배송 보상 대상이 아니었거든요.
그런데 에이전트는 티켓을 resolved로 닫아버리고 이렇게 답합니다.
"문의가 해결되었습니다. 추가로 도와드릴 일이 있을까요?"
두 가지가 틀렸어요. 첫째, 운송사 예외 건은 여전히 열려 있어서 요구되는 최종 상태는 hold였습니다. 둘째, 고객은 자신이 실제로 물어본 것에 대한 답을 받지 못했어요.
툴 호출만 세는 평가자는 9개의 잘 만들어진 호출을 봅니다. 응답 내용만 보는 평가자도 합격을 줍니다. DB가 아니라고 말하는 거죠.
이 간극을 측정하는 게 ThinkingBox입니다. 507개의 상태 기반(stateful) 비즈니스 워크플로를 여러 LLM으로 각각 20회씩 돌리면서, 최종 백엔드 상태와 부수 효과만 채점해요. 이 글에서는 무엇을 발견했는지, 일관성의 비용은 얼마인지, 그리고 OpenEnv로 직접 돌려보는 방법을 정리합니다.
근거자료: ThinkingBox: Grading AI Agents on Database State, Not Sentences

왜 '한 번 성공'은 신뢰가 아닌가
에이전트가 환불을 한 번은 제대로 처리하고 다음 네 번은 망치는 경우, 그건 환불 에이전트가 아니에요. 그래서 ThinkingBox는 모든 태스크를 동일한 초기 상태에서 20회 독립 실행하고 세 가지 숫자를 보고합니다.
| 지표 | 측정 대상 | 대답하는 질문 |
|---|---|---|
| pass@1 | 전체 시도 중 성공 비율 | 평소에 얼마나 잘하나? |
| pass@20 | 20회 중 최소 1회 성공한 태스크 비율 | 할 수는 있나? (넓이) |
| Observed 20/20 | 20회 전부 통과한 태스크 수 | 항상 맞출 수 있나? (일관성) |
12개 모델, 121,680건의 유효 시도에서 79,853건이 실행 가능한 체크(executable check)를 통과하지 못했어요. 그 실패 중 67.24%가 깔끔하게 종료되었고, 상태 변경 툴을 호출했으며, 최종 툴 에러도 보고하지 않았습니다. 즉 로그만 보면 성공처럼 보였어요. 그런데 실행 체크로 들여다보니 77.61%는 필드 값이 틀렸고, 43.30%는 의도치 않은 부수 효과가 생겼고, 25.36%는 필요한 효과가 누락됐습니다.
벤치마크 직접 돌려보기 (핵심 코드)
ThinkingBox는 Hugging Face에 공개되어 있고, OpenEnv 인터페이스로 감싸서 에피소드마다 binary pass/fail 보상을 돌려줍니다. 설치는 Linux/WSL, Python 3.11+, uv, Docker 환경을 전제로 해요.
# 1. OpenEnv + ThinkingBox 환경
git clone https://github.com/huggingface/OpenEnv
cd OpenEnv
uv sync --project envs/thinkingbox_env --frozen
# 2. 실행 가능한 벤치마크 (고정 릴리스)
git clone https://github.com/microsoft/thinkingbox-data
git -C thinkingbox-data checkout thinkingbox-bench-v1.0
# 3. ThinkingBox CLI (tb 명령 제공)
uv tool install "thinkingbox @ git+https://github.com/microsoft/thinkingbox"
Typesense 30.1을 띄우고, MCP 서버와 OpenEnv 서버를 순서대로 올립니다. 각각 별도 터미널을 쓰는 게 편해요.
# Typesense (검색/상태 백엔드)
mkdir -p .typesense-data
docker run --rm -d --name thinkingbox-typesense \
-p 8108:8108 \
-v "$PWD/.typesense-data:/data" \
typesense/typesense:30.1 \
--data-dir /data --api-key=Fake --enable-cors
until curl -fsS http://127.0.0.1:8108/health; do sleep 1; done
# MCP 세션 프록시 + 서버 기동
cd OpenEnv
tb mcp-start --host 127.0.0.1 --port 7111 \
--servers "$PWD/thinkingbox-data/servers/servers.yaml"
# OpenEnv 서버 (에이전트/시뮬레이션 유저/판정 모델 지정)
OPENENV_TB_CONFIG="$PWD/thinkingbox.yaml" \
uv run --project envs/thinkingbox_env --frozen server
준비 상태는 반드시 게이트로 확인하세요. 데이터, 설정, 세션 프록시 체크가 통과되기 전까지 /ready는 503을 반환합니다.
curl -sS http://127.0.0.1:8000/ready
이제 실제 에피소드 하나를 채점합니다. example_usage.py는 리셋과 툴 목록만 보여주니, 액션·효과·어서션까지 보려면 패키징된 evaluator를 쓰세요.
echo "- sandbox_external_retail_group1.py:test_case_ST002_001" > one_task.yaml
uv run --project envs/thinkingbox_env thinkingbox-eval \
one_task.yaml \
--config "$PWD/thinkingbox.yaml" \
--output results.jsonl \
--errors-output errors.jsonl \
--repeat 1 --message-timeout 1800
포인트는 --errors-output이에요. 운영 실패를 모델 결과와 섞지 않고 사이드카로 분리해서 재실행 가능하게 만듭니다. 정식 결과를 낼 때는 이 실패들을 해소하거나 명시적으로 집계에 반영해야 해요. 원 논문에서는 시스템 에러도 실패 시도로 카운트했습니다.

일관성의 가격표와 실패 서명
대부분의 리더보드는 pass@1에서 멈춥니다. 하지만 실서비스에 넣을 사람에게 중요한 건 성공한 작업 단위당 비용이에요. ThinkingBox는 507×20 캠페인의 실제 토큰 사용량을 OpenRouter 기준 정가로 환산해서 두 가지 지표를 계산합니다.
- 성공 시도당 비용 = 507회 시도 비용 ÷ (507 × pass@1)
- 신뢰 가능한 태스크당 비용 = 20회 전체 캠페인 비용 ÷ 20/20 통과 태스크 수
| 모델 | 20/20 통과 | 20회 캠페인 비용 | 신뢰 태스크당 비용 |
|---|---|---|---|
| GPT-5.4 | 128 (25.25%) | $869.80 | $6.80 |
| GPT-6 Astra | 231 (45.56%) | $1,720.60 | $7.45 |
| Claude Opus 5.5 | 241 (47.53%) | $1,880.77 | $7.80 |
| GPT-5.6 Sol | 82 (16.17%) | $800.00 | $9.76 |
| Claude Opus 5 | 241 (47.53%) | $3,206.00 | $13.30 |
| Kimi-K3 | 68 (13.41%) | $1,406.40 | $20.68 |
재미있는 지점이 여기예요. 단일 성공당 비용이 가장 싼 GPT-5.6 Sol($0.127)은 신뢰 태스크당으로 가면 $9.76로 뛰어요. 싸게 맞는 답과 싸게 믿을 수 있는 답은 다릅니다.
넓이와 일관성은 따로 논다
Kimi-K3는 벤치마크 최광범위 모델이에요. 507개 중 476개(93.89%)를 최소 한 번은 풀어냅니다. 완전히 실패한 태스크는 31개로 전 필드 최소. 리테일에서는 82.24% pass@1로 상용 모델을 앞서요. 그런데 20회 전부 통과한 태스크는 68개(13.41%)뿐입니다.
Claude Opus 5는 정반대예요. 최소 1회 성공은 79.09%로 더 낮지만, 20회 전부 통과는 47.53%로 훨씬 높습니다. 그리고 최신 모델이 이걸 고쳐주지도 않아요. Claude Opus 5.5는 pass@1이 67.16%로 Opus 5(66.50%)보다 높지만, 20/20 통과 태스크 수는 정확히 241개로 동일합니다. 헤드라인 정확도 0.66포인트가 신뢰성은 1도 사주지 못한 거죠.
실패의 80%는 추론이 아니라 툴 처리
실패 트레이스마다 결정적 진단 서명을 하나씩 붙였더니 결과가 아주 실무적입니다.
| 실패 서명 | 비중 |
|---|---|
| 툴 사용 (Tool usage) | 79.9% |
| 잘못된 상태 업데이트 | 10.3% |
| 불완전한 사용자 해결 | 7.0% |
| 상태 변경 액션 없음 | 2.9% |
패턴은 단순해요. 에이전트는 대개 워크플로를 시도할 만큼은 갑니다. 그다음 툴 에러, 실패한 사전 조건, 빈 조회 결과에서 복구하지 못해요. 이건 모델 문제라기보다 재시도와 에러 복구 설계 문제입니다.
국내 SI 환경에서는 특히 이 부분이 중요해요. 사내 시스템은 레거시 API가 많아서 '빈 조회 결과'와 '권한 없음'이 같은 코드로 떨어지는 경우가 흔합니다. 그 상태에서 에이전트가 재시도를 무한 루프로 돌리면 배치 비용이 순식간에 튀어요. 툴 에러를 recoverable / non-recoverable로 분류하는 작업을 먼저 하시는 걸 권합니다.
이 접근법의 한계
ThinkingBox도 만능은 아닙니다. 공개 벤치마크의 모든 태스크는 합성 재구성이에요. 워크플로와 정책은 실제 엔터프라이즈 에이전트 패턴을 모델링했지만 고객은 실존 인물이 아닙니다. 507개 태스크 중 477개는 상태만으로 채점하고, 30개만 응답 루브릭을 추가합니다. 즉 "이 설명이 사용자에게 충분히 친절했는가" 같은 정성적 품질은 거의 측정하지 않아요. 그리고 비용 수치는 2026년 9월 20일 OpenRouter 스냅샷 기준 정가 환산값이지, 실제 클라우드 청구서가 아닙니다.

결론: 20/20을 설계 입력으로 쓰세요
이 작업에서 진짜 쓸모 있는 건 pass@1 리더보드가 아니라 환경입니다. 실서비스에 바로 적용할 수 있는 원칙 네 가지로 정리할게요.
- 커밋 전에 터미널 상태를 확인하세요. 모델이 "완료했습니다"라고 요약한 문장이 아니라, 실제로 바뀐 레코드를 보세요. 이 신호는 벤치마크만의 것이 아니라 프로덕션에도 그대로 있습니다.
- 툴/시스템 에러를 분류하세요. 재시도가 의미 있는 에러와 그렇지 않은 에러를 나눠야 재시도가 폭주하지 않습니다.
- 툴 표면을 줄이세요. 워크플로에 필요한 최소한의 툴만 노출하는 것만으로 툴 사용 실패율이 크게 달라집니다.
- 되돌리기 비싼 변경에는 사람 승인을 거세요. 환불, 계정 상태 변경, 삭제 같은 액션이 여기에 해당해요.
벤치마크를 돌릴 때는 반복 지표를 반드시 함께 보고하세요. k를 몇으로 잡든, best-of-k인지 every-of-k인지 명시하는 게 핵심입니다. "우리 에이전트 90% 성공"이라는 문장은 20회 중 1회 성공인지 20회 전부 성공인지에 따라 완전히 다른 말이니까요.
다음 단계 학습 방향
- OpenEnv 환경 직접 실행: 위 설치 절차를 따라 리테일 태스크 하나를 본인 모델로 재현해 보세요.
sandbox_external_retail_group1.py:test_case_ST003_006이 글 서두의 그 케이스입니다. - 실패 트레이스 열어보기: 깔끔하게 종료됐는데 실패한 런을 찾아 DB에서 실제로 뭐가 바뀌었는지 확인하세요. 본인 평가 체계가 뭘 놓치고 있는지 감이 옵니다.
- MCP 툴 설계로 확장: 툴 스키마와 사전 조건 검증 설계는 별도 주제예요. 사내 MCP 서버를 직접 만든다면 스키마 검증 계층을 먼저 잡는 걸 추천합니다.