| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Named tool choice for Kimi K3: allowed when the XTML structural tag is |
| 4 | attached (strict tool calling), rejected otherwise.""" |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from vllm.entrypoints.openai.chat_completion.protocol import ( |
| 9 | ChatCompletionRequest, |
| 10 | ) |
| 11 | from vllm.exceptions import VLLMValidationError |
| 12 | from vllm.sampling_params import StructuredOutputsParams |
| 13 | |
| 14 | |
| 15 | class _DummyTokenizer: |
| 16 | def get_vocab(self): |
| 17 | return {} |
| 18 | |
| 19 | def encode(self, text, add_special_tokens=False): |
| 20 | return [ord(c) for c in text] |
| 21 | |
| 22 | |
| 23 | def _request(with_tag: bool) -> ChatCompletionRequest: |
| 24 | req = ChatCompletionRequest( |
| 25 | model="k3", |
| 26 | messages=[{"role": "user", "content": "hi"}], |
| 27 | tools=[ |
| 28 | { |
| 29 | "type": "function", |
| 30 | "function": { |
| 31 | "name": "get_weather", |
| 32 | "parameters": { |
| 33 | "type": "object", |
| 34 | "properties": {"city": {"type": "string"}}, |
| 35 | "required": ["city"], |
| 36 | }, |
| 37 | }, |
| 38 | } |
| 39 | ], |
| 40 | tool_choice={"type": "function", "function": {"name": "get_weather"}}, |
| 41 | ) |
| 42 | if with_tag: |
| 43 | req.structured_outputs = StructuredOutputsParams( |
| 44 | structural_tag='{"type": "structural_tag", "format": {}}' |
| 45 | ) |
| 46 | return req |
| 47 | |
| 48 | |
| 49 | def _parser(): |
| 50 | from vllm.tool_parsers.kimi_k3_tool_parser import KimiK3ToolParser |
| 51 | |
| 52 | return KimiK3ToolParser(_DummyTokenizer()) |
| 53 | |
| 54 | |
| 55 | def test_named_choice_allowed_with_structural_tag(): |
| 56 | req = _parser().adjust_request(_request(with_tag=True)) |
| 57 | assert req.skip_special_tokens is False |
| 58 | |
| 59 | |
| 60 | def test_named_choice_rejected_without_structural_tag(): |
| 61 | with pytest.raises(VLLMValidationError): |
| 62 | _parser().adjust_request(_request(with_tag=False)) |