> 핵심 한 줄: 모델 성능이 오를수록 도구 호출 정확도는 떨어지고 있다. LLM이 텍스트로 도구를 호출한다는 사실에서 오는 본질적 한계, 그리고 하네스가 이를 어떻게 보정해야 하는가.

원문: lucumr.pocoo.org — Better Models, Worse Tools · 긱뉴스 #31148
- --
들어가며 — Pi 프로젝트에서 발견된 역설
Armin Ronacher는 자신의 에이전트 코딩 프로젝트 Pi에서 Claude Opus 4.8과 Sonnet 5를 도입한 뒤 이상한 현상을 발견했다. 모델은 분명 더 똑똑해졌는데, edits[] 도구 호출은 더 자주 실패했다. 호출 거부 사유는 명확했다. 스키마에 없는 필드가 끼어들어간 것. 그런데 이상한 점은 — oldText와 newText 자체는 정확히 맞았고, 거기에 requireUnique, oldText2, matchCase, in_file 같은 가짜 키가 붙어 있었다.
이 현상은 단순한 버그가 아니다. 도구 호출은 본질적으로 모델이 특수 마커와 JSON 형태의 텍스트를 생성하는 과정이고, 제약 없는 샘플링에서는 학습된 관례가 스키마보다 앞설 수 있다. 즉 모델이 "더 잘 알게 되었다"는 것이 도구 사용에서만큼은 "스키마에서 더 잘 벗어나게 되었다"로 직결되는 아이러니다.
- --
무엇이 실제로 일어났는가
실패 사례를 분해하면 패턴이 보인다.
| 항목 | 정상 호출 | 가짜 필드 포함 호출 (실패) |
|---|---|---|
| oldText | 정확한 원본 문자열 | 동일한 정확한 원본 문자열 |
| newText | 원하는 새 문자열 | 동일한 원하는 새 문자열 |
| requireUnique | ❌ 없음 | ✅ true로 추가됨 |
| matchCase | ❌ 없음 | ✅ false로 추가됨 |
| oldText2 | ❌ 없음 | ✅ fallback 후보로 추가됨 |
| in_file | ❌ 없음 | ✅ 파일 경로로 추가됨 |
핵심은 모든 필드가 모델이 학습한 합리적 보강이라는 점이다. requireUnique는 다른 도구에서 본 옵션이고, oldText2는 fuzzy match 의도일 수 있다. 즉 모델이 도구를 더 똑똑하게 쓰려다가 스키마를 어긴 것이다.
재현율도 흥미롭다. 에이전트 이력과 thinking block 포함 여부에 따라 재현율이 달라졌다. 같은 모델, 같은 프롬프트, 같은 도구 정의인데 호출 결과가 다르다. 모델이 자기 자신의 추론을 들여다보면서 추가 필드를 끼워 넣는 경향이 있다는 뜻이다.
- --
모델이 도구를 어기는 세 가지 경로
1. 도구 간 학습 전이 (Cross-tool transfer)
모델은 학습 데이터에서 비슷한 API들을 수백 개 본다. GitHub의 matchCase, Cursor의 requireUnique, LSP의 in_file — 이런 도구들이 모두 비슷한 의도의 옵션을 갖고 있다는 걸 모델은 학습한다. 그리고 자기 도구에 그 의도가 "있을 것"이라고 추측해 호출에 끼워 넣는다.
스키마는 도구가 무엇을 허용하는지 명시하지만, 모델은 그것을 학습 데이터의 패턴과 매칭한다. 둘의 일치가 낮을수록 — 도구가 평범한 OpenAPI 패턴에서 벗어날수록 — 추측이 늘어난다.
2. 사고 흐름 누설 (Reasoning contamination)
Thinking block이 켜진 모델이 edits[] 호출을 떠올릴 때, 자기 사고 흐름에서 "일단 fuzzy match를 시도해볼까"라는 메모를 만들면 그게 출력에 새어 나온다. JSON은 어차피 텍스트 생성의 부산물이라서 sampling temperature가 0이 아닌 한 사고 내용은 출력에 영향을 준다.
3. 관례 우선 (Convention over schema)
모델은 스키마를 보는 것보다 학습 패턴을 따르는 쪽에 더 자신감을 갖는다. "보통 edit 도구라면 matchCase가 있을 것"이라는 학습된 기대가 additionalProperties: false보다 강하게 작동한다.
- --
Claude Code가 이미 하고 있는 일 — 느슨한 보정
Claude Code는 닫힌 하네스지만, 내부적으로 도구 호출 보정을 꽤 많이 수행한다.
def sanitize_tool_call(call: dict, schema: dict) -> dict:
"""도구 호출 정규화 — Claude Code 구현"""
# 1. 잘못된 호출 재시도
# - 스키마 위반 시 한 번 자동 수정 시도
if not validates_against(call, schema):
call = retry_with_hint(call, schema)
# 2. 파라미터 별칭 (A → A2)
# - 모델이 'A'를 호출했다면 정확히 정의된 'A2'로 매핑
call = map_aliases(call, ALIAS_MAP)
# 3. 타입 보정
# - "true" → True, 숫자 문자열 → int 등
call = coerce_types(call, schema)
# 4. Unicode 복구
# - 한글/특수문자 깨짐 시 surrogate pair 재구성
call = repair_unicode(call)
# 5. 알 수 없는 키 필터링
# - additionalProperties: false가 정의돼 있다면 침묵하게 제거
call = drop_unknown_keys(call, schema)
return call
이 다섯 단계 중 어느 것도 모델이 보내온 JSON을 느슨하게 받아들인다. strict 모드는 쓰지 않는다. 왜냐하면 모델이 한 번 거부되면 사용자가 답답해하고 도구 사용률이 떨어지기 때문이다.
- --
Anthropic `strict` 도구 호출 — 해결인가
Anthropic은 이 문제를 정면으로 해결하는 strict tool use 기능을 도입했다. 핵심은 모델 출력 단계에서 JSON 스키마를 강제하는 것이다. 가짜 키는 토큰 샘플링 단계에서 차단된다.
{
"tool": "edit",
"strict": true,
"input_schema": {
"type": "object",
"properties": {
"oldText": {"type": "string"},
"newText": {"type": "string"}
},
"required": ["oldText", "newText"],
"additionalProperties": false
}
}

strict: true가 켜진 호출은 가짜 키가 생성 자체에서 막힌다. matchCase나 requireUnique는 모델의 sampling 분포에서 등장조차 하지 않는다.
하지만 모든 도구를 strict로 만들 수는 없다. 가장 유연한 도구들 — 자유 텍스트 입력, 동적 스키마, 사용자 정의 액션 — 은 의도적으로 loose하게 설계됐다. strict 모드가 강해질수록 대체 도구 스키마가 불리해질 수 있다. 모델이 "더 좋은 호출"을 하고 싶어도 스키마가 못 받으면 도구 사용 자체를 줄인다.
이게 Armin이 지적한 본질적 문제다.
> 모델 성능이 좋아질수록 대체 도구 스키마가 불리해질 수 있다면, 하네스에는 문법 제약 같은 더 강한 보장이 필요하다.
- --
하네스가 책임져야 할 것 — 문법 제약의 부재
지금 LLM 도구 호출 생태계의 빈 자리가 보인다. 모델의 사고를 도구 스키마로 강제하는 중간 계층이 없다.
| 계층 | 책임 | 현재 한계 |
|---|---|---|
| 모델 | 사고 → JSON 텍스트 생성 | 학습 관례가 스키마 우선 |
| 하네스 | JSON → 정규화 → 도구 호출 | 느슨한 보정에 의존, strict 모드 일부만 채택 |
| 도구 | 호출 실행 | 스키마 외 입력은 거부하거나 무시 |
문법 제약(grammar constraint)이란, 모델이 JSON을 생성할 때부터 LLM의 vocabulary mask를 스키마에 맞게 잘라내는 방식이다. Outlines, Guidance, llama.cpp의 GBNF 같은 라이브러리가 이미 존재하지만, LLM API 제공자 레벨에서는 채택되지 않았다.
Anthropic의 strict 모드는 API 응답 단계에서 스키마를 검증한다. 더 앞단인 vocabulary 단계에서 차단은 아니다. 그래서 모델 출력 자체는 가짜 키를 포함하고, 검증 단계에서 거부되어 사용자 입장에서는 실패로 보인다.
진짜 해결책은 vocabulary 단계에서 스키마 외 토큰을 마스킹하는 것이다. 그러면 모델은 아예 가짜 키를 생성하지 못한다. 현재는 Claude API가 이걸 직접 지원하지 않아, 하네스 측에서 sampling을 강제하는 우회만 가능하다.
- --
실전 체크리스트 — 도구 호출 정확도를 올리는 7가지
지금 하네스를 운영하는 사람이라면 다음을 점검할 수 있다.
- [ ] 모든 도구 JSON 스키마에
additionalProperties: false명시 — 모델이 끼워 넣는 가짜 키 사전 차단 - [ ] 파라미터 별칭 맵을 도구 정의 옆에 둔다 — 모델이 흔히 쓰는 다른 도구의 옵션을 매핑
- [ ] strict 모드를 점진적으로 켠다 — 모든 도구 한 번에 켜면 사용성 저하. 안정적인 것부터
- [ ] 에러 메시지에 스키마 위반 키 이름을 명시 — 모델이 다음 호출에서 스스로 교정하도록
- [ ] thinking block 활성화 여부에 따라 도구 호출 결과 차이를 본다 — 사고 누설 경로 모니터링
- [ ] JSON Schema validator를 하네스 진입점에서 1회 강제 — 거부 시 한 번 자동 보정 후 재시도
- [ ] 도구 호출 log에 모델 출력 원본을 보존 — 거부된 호출의 원본 JSON을 나중에 분석 가능하게
Pi의 실패 사례는 단순한 버그가 아니라 LLM 도구 호출의 구조적 문제를 보여준다. 더 똑똑한 모델이 더 똑똑한 도구 호출을 할 것이라고 기대하지만, 적어도 현재 sampling 단계에서는 그 기대가 어긋난다.
- --
전망 — strict가 표준이 되는 시점
OpenAI, Anthropic, Google 모두 strict tool use를 2026년 안에 GA로 끌어올렸다. API 제공자 레벨에서 grammar constraint를 표준화하려는 움직임이 가속화되고 있다. 한 두 해 안에 strict: true가 기본값이 되고, 지금의 느슨한 보정은 legacy path로 밀릴 가능성이 높다.
하지만 그 시점에도 하네스의 역할은 끝나지 않는다. 도구 사용 전략 — 어떤 도구를 어떤 순서로 호출할지 — 은 여전히 하네스가 책임져야 하는 영역이다. JSON 스키마를 지키는 것만으로 충분하지 않다. 호출 흐름 자체의 일관성도 모델의 자기 인식에 따라 달라진다.
따라서 지금 도구를 설계하는 사람이라면: 스키마를 strict하게 닫고, 호출 흐름은 자유롭게 두는 균형이 2026년의 sweet spot이다.
- --
요약
- LLM 도구 호출은 본질적으로 JSON 텍스트 생성이므로 학습 관례가 스키마보다 앞설 수 있다.
- 더 똑똑한 모델이 더 자주 가짜 키(
matchCase,requireUnique등)를 끼워 넣어 호출이 거부된다. - Claude Code는 별칭 매핑, 타입 보정, 알 수 없는 키 필터링 등 느슨한 보정을 다층으로 수행한다.
- Anthropic strict tool use는 모델 출력에서 스키마를 강제하지만, vocabulary 단계 차단은 아니다.
- 진짜 해결은 문법 제약(grammar constraint) 라이브러리가 LLM API 표준으로 채택되는 것이고, 그 시점은 1~2년 내로 보인다.
> 도구는 점점 똑똑해지지만, 그 도구를 호출하는 모델도 똑똑해지고 있다. 두 똑똑함의 충돌 지점을 정확히 파악하는 것이 하네스 설계의 핵심이다.
- --
본문은 2026-07-06 긱뉴스 #31148 "더 나은 모델, 더 나빠진 도구" 글의 자동 정리본입니다.
원문: lucumr.pocoo.org
'AI 뉴스' 카테고리의 다른 글
| dbtrail — MySQL을 위한 타임머신, 모든 행 변경을 기억하고 되돌리는 MySQL Time Machine (0) | 2026.07.06 |
|---|---|
| OpenTag — Slack용 Claude Tag의 오픈소스 대안, 그리고 셀프 호스팅 AI 에이전트의 진짜 가치 (1) | 2026.07.06 |
| 클로드 코드는 요청에 스테가노그래피 표시를 하고 있음 — 바이너리에 숨은 분류 마커 분석 (0) | 2026.07.02 |
| Ornith-1.0 — 에이전트형 코딩을 위한 자기 개선 오픈소스 모델 완전 가이드 (0) | 2026.07.02 |
| Atlassian의 DESIGN.md 공개 — 이식 가능한 디자인 컨텍스트를 실전 테스트하며 얻은 교훈 (0) | 2026.07.02 |