모델이 web_search 대신 search_web을 부르거나 location 대신 city_name을 넣으면 에이전트는 그 자리에서 멈춥니다. Ubicloud는 Kimi K2.6을 vLLM으로 서빙하면서 이런 형식 오류를 8% 안팎 관찰했고, 서버 설정으로 0%까지 줄였다고 공개했습니다.
이 글의 요점은 숫자 하나가 아니라 그 뒤의 두 가지입니다. 수치가 정확히 무엇을 재는지, 그리고 같은 팀이 서버를 업스트림 vLLM으로 바꾸자 정확도가 91%로 돌아간 이유입니다. 날짜는 원문이 10월 20일, 브리핑에서 주목받은 날이 10월 26일입니다.
92%와 100%가 재는 것
원문에서 확인되는 조건은 이렇습니다.
| 항목 | 값 |
|---|---|
| 모델 | Kimi K2.6 (결론에서 DeepSeek V4 Flash도 언급) |
| 평가 | Moonshot AI의 K2VV ToolCall 벤치마크, 요청 2,000건 |
| 지표 | 스키마 정확도: 도구 호출이 해당 도구의 스키마를 따르는 비율 |
| 기준선 | vLLM 0.28에서 92% |
| 제약 디코딩 적용 후 | 100% |
| 하드웨어 | 원문에 없음 |
| 지연·처리량 | 수치 없음. 오버헤드가 "무시할 만하다"는 주장만 있음 |
100%는 형식 정확도입니다. 호출이 스키마에 맞는다는 뜻이지 올바른 도구를 골랐다거나 올바른 값을 넣었다는 뜻이 아닙니다. 평가는 Ubicloud가 자체 환경에서 한 것이고, 독립 재현은 확인하지 못했습니다.
왜 틀리나, 어떻게 막나
제약 없는 생성은 확률적입니다. 학습 때 본 도구 스키마와 지금 요청의 스키마가 어긋나면 이름이나 키를 비슷하게 틀립니다. 제약 디코딩은 문법에 맞지 않는 토큰을 샘플링 후보에서 빼기 때문에 형식 오류가 구조적으로 나올 수 없습니다. Moonshot도 제약 디코딩을 켜라고 권한다고 원문은 적었습니다.
여기서 의미가 갈립니다. 형식을 강제하면 형식 오류는 사라지지만, 모델이 틀리게 고른 선택은 그대로 형식에 맞는 틀린 호출로 남습니다. vLLM 문서도 필수/지정 도구 호출은 "유효하게 파싱되는 호출일 뿐 품질이 높은 호출은 아니다"라고 밝힙니다.
업스트림으로 옮기자 91%로 돌아갔다
이 사례에서 가장 실무적인 대목입니다. Ubicloud는 자체 포크에서 쓰던 제약 디코딩이 vLLM 0.25에 들어오자 업스트림으로 갈아탔습니다. 그런데 정확도가 91%로 떨어졌습니다. 원인은 버그가 아니라 기본값 변경이었습니다. 명시 설정이 없는 요청에는 더 이상 제약 디코딩을 쓰지 않게 바뀐 것입니다. vLLM이 기본값을 끈 이유는 일부 모델에서 오히려 해를 끼칠 수 있기 때문이고, 원문은 gpt-oss-20b, Qwen 3.6, GLM 5.1 사례 보고를 인용했습니다. 그 밖에 문법 변환 버그로 호출이 깨지거나 제약이 조용히 빠질 수 있고, 추측 디코딩과 겹치면 유효한 토큰이 잘못 거부될 수 있다고도 적었습니다.
교훈은 단순합니다. 추론 엔진을 업그레이드하면 같은 모델이 다른 모델처럼 동작할 수 있습니다. 모델 가중치는 그대로인데 결과가 바뀝니다.
지금 vLLM 문서가 말하는 설정
원문은 vLLM 0.31에서 서버 측 플래그로 나왔다고만 하고 이름은 적지 않았습니다. 현재 vLLM 문서에서 확인한 내용을 그대로 옮기면 이렇습니다.
--tool-strict-level은auto,function,parameter중 하나입니다.auto는 요청의 tool choice와 도구별strict를 따르고,function은 도구 호출의 틀(마크업과 함수 이름)을 모든 요청에서 제약하며,parameter는 모든 도구를strict: true처럼 인자 스키마까지 고정합니다.- 값이 없으면 파서 기본값이 적용되며,
glm47은function, 다른 파서는auto입니다. VLLM_ENFORCE_STRICT_TOOL_CALLING은 기본true이고false로 두면--tool-strict-level보다 우선해 구조 태그를 끕니다.- 도구에
strict: true를 쓰려면additionalProperties: false, 모든 속성 required, 선택 필드는 nullable로 표현해야 합니다.
문서의 kimi_k2 파서 항목은 구조 태그나 strict 지원 여부를 밝히지 않습니다. 제가 이 설정을 Kimi K2.6에서 실행해 본 것은 아니므로, 내 모델·버전 조합에서 실제로 적용되는지는 직접 확인해야 합니다.
세 가지를 따로 재는 하네스
"스키마 100%"를 "에이전트가 잘 한다"로 읽지 않으려면 지표를 나눠야 합니다. 아래는 제가 만든 합성 데이터 5건으로 계산한 예시입니다. Ubicloud의 데이터나 K2VV가 아닙니다.
cases = [ # (기대 호출, 모델 출력)
({"name":"web_search","arguments":{"query":"vllm"}}, {"name":"web_search","arguments":{"query":"vllm"}}),
({"name":"web_search","arguments":{"query":"vllm"}}, {"name":"search_web","arguments":{"query":"vllm"}}), # 이름 오류
({"name":"get_weather","arguments":{"location":"Seoul"}}, {"name":"get_weather","arguments":{"city_name":"Seoul"}}), # 키 오류
({"name":"get_weather","arguments":{"location":"Seoul"}}, {"name":"get_weather","arguments":{"location":"Busan"}}), # 형식은 맞고 값이 틀림
({"name":"web_search","arguments":{"query":"rust pdf"}}, {"name":"get_weather","arguments":{"location":"Seoul"}}), # 형식은 맞고 도구가 틀림
]
schema = sum(schema_ok(o) for _, o in cases) # 스키마 준수
tool_sel = sum(e["name"] == o["name"] for e, o in cases) # 도구 선택
task = sum(e == o for e, o in cases) # 정확 일치
schema validity : 3/5
tool selection : 3/5
exact task match: 1/5
제약 디코딩이 앞의 두 오류를 막아도, 마지막 두 건은 형식에 맞는 채로 남습니다. 스키마 정확도는 5건 중 5건이 되어도 정확 일치는 1~3건에 머물 수 있다는 뜻입니다. 이 코드는 구조만 보여 주는 장난감이며, 실제 평가에는 내 도구 정의와 정답 라벨이 필요합니다.
적용 체크리스트
- 지금 서빙 중인 vLLM 버전과 파서의 strict 지원을 문서에서 확인한다.
- 같은 요청 묶음을 제약 있음/없음으로 두 번 돌려 스키마 준수율, 도구 선택 정확도, 실제 작업 성공률을 각각 기록한다.
- 엔진을 올릴 때마다 위 평가를 다시 돌린다. 기본값이 바뀌어도 모델 이름은 그대로다.
- 모델 쪽 부작용(지연, 요청 실패, 추측 디코딩 충돌)을 같이 기록한다.
정리
설정 하나로 형식 오류를 없앨 수 있다는 점은 실용적입니다. 하지만 100%는 형식의 숫자이고, 에이전트의 성공률은 따로 재야 합니다. 그리고 이 사례의 진짜 교훈은 91%로 돌아간 순간에 있었습니다. 추론 엔진은 모델의 일부로 취급하고 버전과 설정을 평가 기록에 남기세요.
글 끝의 유료 홍보 링크는 에이전트를 하네스·루프·그래프로 설계하는 강의입니다. vLLM 서빙이나 제약 디코딩을 가르치는 강의가 아닙니다.
함께 읽으면 좋은 글
- MCP가 더 많은 권한을 요구하면 다시 묻는다: Claude Code 2.1.288
- 거절된 요청이 남의 KV 캐시를 읽었다: Cloudflare GLM-5.3 사고에서 배울 점검 항목