TT Lab
블로그

한국어

시작하기
블로그

블로그

도구 호출: 파서가 글을 함수 호출로 바꾸고, strict 가 인자를 묶는다

상태: 초안 (2026-10-27), 실측. 측정은 8GB급 노트북 GPU(Ada 세대) 한 장, vLLM 0.31.0, Qwen3-Reranker-0.6B(재순위용으로 미세 조정된 Qwen3 0.6B 인과 언어 모델)로 했습니다. 공식 문서는 0.32.0(2026-10-18 출시) 기준으로 2026-10-27 에 열어 확인했고, 0.31.0 문서와 차이가 나는 곳은 따로 적었습니다. 2026-10-27 추가. 대화용 Qwen3-0.6B, Qwen3-0.6B-GPTQ-Int8, Qwen3-4B-AWQ 와 vLLM 0.32.0 으로 다시 잰 결과를 3-1절에 더했습니다. 0.31.0 으로 잰 앞의 수치는 그대로 두었습니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.

목차

1. 도구 호출은 정해진 모양의 글이다

에이전트가 날씨 API 를 부르는 장면을 떠올려 봅시다. 모델 안에 「함수 호출」이라는 특별한 출력 장치가 있는 것은 아닙니다. 모델은 여전히 토큰을 하나씩 만들고, 다만 학습 때 「도구를 부를 때는 이런 모양으로 쓴다」를 배웠을 뿐입니다. Qwen 계열은 Hermes 형식을 씁니다.

<tool_call>
{"name": "get_weather", "arguments": {"city": "Seoul", "unit": "c"}}
</tool_call>

서버가 하는 일은 세 가지입니다.

  1. 도구 목록을 프롬프트에 넣습니다. 요청의 tools 를 모델의 채팅 템플릿이 시스템 프롬프트 안에 글로 적어 넣습니다.
  2. 모델이 쓴 글을 고릅니다. 생성된 글에서 위 모양을 찾아 OpenAI 형식의 message.tool_calls 로 옮기는 것이 도구 파서입니다. 모델마다 모양이 달라서 파서도 hermes, llama3_json, mistral, qwen3_xml 처럼 여럿입니다.
  3. 필요하면 모양을 강제합니다. tool_choice 가 required 이거나 특정 함수 이름이면, 4편의 구조화 출력이 호출의 겉모양을 토큰 단계에서 강제합니다.

켜는 옵션은 다음과 같습니다. --enable-auto-tool-choice 가 없으면 tool_choice="auto" 요청을 받지 않습니다.

vllm serve <모델> --enable-auto-tool-choice --tool-call-parser hermes

2. tool_choice 네 가지

tool_choice 호출 의무 무엇이 모양을 보장하나
auto 없음, 모델이 정함 파서뿐. 모델이 모양을 틀리면 글로 남거나 빠짐
required 하나 이상 반드시 구조화 출력이 겉모양을 강제
이름 지정({"type":"function","function":{"name":...}}) 그 함수 하나 구조화 출력이 겉모양을 강제
none 부르지 않음 도구 정의는 기본으로 프롬프트에 남음(--exclude-tools-when-tool-choice-none 으로 뺌)

공식 문서는 이름 지정 호출에 대해 「파싱 가능한 호출은 보장하지만 질 좋은 호출은 보장하지 않는다」고 적습니다. 이 문장이 이 글의 실측 결과를 그대로 요약합니다.

겉모양과 인자는 따로 묶인다

0.21.0 부터 xgrammar 의 구조 태그(structural tag) 로 도구 호출을 강제하는 길이 들어왔고, 지금 문서는 강제를 두 겹으로 나눕니다.

서버 쪽 바닥값 --tool-strict-level 은 auto(기본, 요청을 따름), function(모든 요청에서 겉봉투 강제), parameter(모든 도구를 strict 처럼) 셋입니다. 대부분의 에이전트 프레임워크가 strict 를 달지 않으므로, 운영자가 바닥을 올릴 수 있게 만든 것입니다. 환경 변수 VLLM_ENFORCE_STRICT_TOOL_CALLING=false 는 구조 태그를 통째로 끕니다.

3. 직접 재 보기

환경과 방법

결과

tool_choice strict tool_calls 가 나온 횟수 스키마를 지킨 인자 응답 시간(중앙)
auto 없음 0/5 0/5 24.5ms
auto 있음 0/5 0/5 24.6ms
required 없음 4/5 0/5 216.6ms
이름 지정 없음 4/5 0/5 216.2ms
required 있음 5/5 5/5 263.1ms
이름 지정 있음 5/5 5/5 261.7ms

원자료의 응답 몇 개를 그대로 옮깁니다.

같은 시드에서 required 와 이름 지정의 출력은 글자까지 같았습니다. 도구가 하나뿐이면 두 방식이 같은 문법을 만들기 때문입니다.

서버도 이것을 셉니다. 시험이 끝난 뒤 /metrics 의 vllm:tool_call_parser_invocations_total 은 비스트리밍 채팅 요청에서 outcome="tool_call" 18, outcome="no_tool_call" 12 였습니다. 클라이언트가 센 값(tool_calls 가 나온 응답 18개, 나오지 않은 응답 12개)과 같습니다.

읽는 법

  1. strict 없는 required 는 「호출이 있다」만 보장했습니다. 함수 이름까지는 맞았지만 인자는 빈 객체이거나 필수 필드가 빠졌습니다. 0.31.0 과 0.32.0 문서 모두 「strict 가 없는 도구는 required 와 이름 지정에서도 넓은 인자 문법만 받는다」고 적고 있고, 실측이 그 문장과 맞았습니다.
  2. strict 를 켜면 스키마는 지켰지만 뜻은 맞지 않았습니다. 모든 인자가 JSON 으로 읽히고 필수 필드와 열거값을 지켰지만, 질문의 Seoul 은 한 번도 나오지 않았고 "none" 같은 값도 나왔습니다. 문법은 「허용되는 토큰」만 남길 뿐, 남은 토큰 중 무엇을 고를지는 여전히 모델이 정합니다.
  3. 시간 차이는 대부분 토큰 수입니다. strict 쪽이 50ms 가량 길었지만 {} 대신 두 필드를 다 쓰느라 토큰이 늘어난 몫이 섞여 있어, 문법 검사 자체의 비용으로 읽으면 안 됩니다. 문법 비용만 따로 본 값은 4편에 있습니다.

3-1. 2026-10-27 재측정: 도구를 아는 모델과 0.32.0

앞의 시험은 도구를 모르는 모델로 「서버가 붙잡아 주는 범위」를 봤습니다. 이번에는 도구 호출을 학습한 모델로 내용까지 봤습니다.

모델 tool_choice strict 호출이 나온 횟수 맞는 도시·단위 응답 시간 중앙
Qwen3-0.6B auto 없음 6/6 6/6 162.1ms
Qwen3-0.6B required 없음 6/6 6/6 178.0ms
Qwen3-0.6B 이름 지정 없음 6/6 6/6 178.1ms
Qwen3-0.6B auto·required·이름 지정 있음 18/18 18/18 184.1~185.4ms
Qwen3-0.6B GPTQ 8비트 여섯 경우 모두 없음·있음 36/36 36/36 115.7~140.6ms
Qwen3-4B AWQ 4비트 여섯 경우 모두 없음·있음 36/36 36/36 338.7~362.4ms

이 시험은 도구 하나, 짧은 질문 하나라 쉬운 편입니다. 도구가 여럿이거나 인자가 복잡할 때의 정확도는 여전히 미실측입니다.

4. 운영에서 가져갈 것

5. 확인하지 못한 것

참고

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다