모델이 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건에 머물 수 있다는 뜻입니다. 이 코드는 구조만 보여 주는 장난감이며, 실제 평가에는 내 도구 정의와 정답 라벨이 필요합니다.

적용 체크리스트

  1. 지금 서빙 중인 vLLM 버전과 파서의 strict 지원을 문서에서 확인한다.
  2. 같은 요청 묶음을 제약 있음/없음으로 두 번 돌려 스키마 준수율, 도구 선택 정확도, 실제 작업 성공률을 각각 기록한다.
  3. 엔진을 올릴 때마다 위 평가를 다시 돌린다. 기본값이 바뀌어도 모델 이름은 그대로다.
  4. 모델 쪽 부작용(지연, 요청 실패, 추측 디코딩 충돌)을 같이 기록한다.

정리

설정 하나로 형식 오류를 없앨 수 있다는 점은 실용적입니다. 하지만 100%는 형식의 숫자이고, 에이전트의 성공률은 따로 재야 합니다. 그리고 이 사례의 진짜 교훈은 91%로 돌아간 순간에 있었습니다. 추론 엔진은 모델의 일부로 취급하고 버전과 설정을 평가 기록에 남기세요.

글 끝의 유료 홍보 링크는 에이전트를 하네스·루프·그래프로 설계하는 강의입니다. vLLM 서빙이나 제약 디코딩을 가르치는 강의가 아닙니다.

함께 읽으면 좋은 글

참고 자료