@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / b6e04390d3
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 13, 02:00 UTC

[Bugfix] Fix Kimi-K2 tool parser concatenated tool calls parsing (#28831)

Signed-off-by: Thomas Mao <yiyeguhu@gmail.com> Signed-off-by: bbartels <benjamin@bartels.dev> Co-authored-by: Thomas Mao <yiyeguhu@gmail.com> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>

Benjamin Bartels committed Nov 18, 2025 at 03:13 UTC b6e04390d3ea5ebc79ac70d1b76d638c56fa8ce2
2 files changed +124 -1
tests/tool_use/test_kimi_k2_tool_parser.py
+122
@@ -60,6 +60,11 @@ def test_extract_tool_calls_no_tools(kimi_k2_tool_parser):
60 ids=[
61 "tool_call_with_content_before",
62 "multi_tool_call_with_content_before",
63 + "concatenated_tool_calls_bug_fix",
64 + "three_concatenated_tool_calls",
65 + "mixed_spacing_tool_calls",
66 + "angle_brackets_in_json",
67 + "newlines_in_json",
68 ],
69 argnames=["model_output", "expected_tool_calls", "expected_content"],
70 argvalues=[
@@ -114,6 +119,123 @@ functions.get_weather:1 <|tool_call_argument_begin|> {"city": "Shanghai"} <|tool
119 ],
120 "I'll help you check the weather. ",
121 ),
122 + (
123 + """I'll get the weather and news for LA today. First, let me get the weather using Los Angeles coordinates, and then get the latest news. <|tool_calls_section_begin|><|tool_call_begin|>functions.get_weather:0<|tool_call_argument_begin|>{"latitude": 34.0522, "longitude": -118.2437}<|tool_call_end|><|tool_call_begin|>functions.get_news:1<|tool_call_argument_begin|>{"content": "Los Angeles today"}<|tool_call_end|><|tool_calls_section_end|>""",
124 + [
125 + ToolCall(
126 + id="functions.get_weather:0",
127 + function=FunctionCall(
128 + name="get_weather",
129 + arguments=json.dumps(
130 + {"latitude": 34.0522, "longitude": -118.2437}
131 + ),
132 + ),
133 + type="function",
134 + ),
135 + ToolCall(
136 + id="functions.get_news:1",
137 + function=FunctionCall(
138 + name="get_news",
139 + arguments=json.dumps({"content": "Los Angeles today"}),
140 + ),
141 + type="function",
142 + ),
143 + ],
144 + "I'll get the weather and news for LA today. First, let me get the weather using Los Angeles coordinates, and then get the latest news. ",
145 + ),
146 + (
147 + """I'll help you with multiple tasks. <|tool_calls_section_begin|><|tool_call_begin|>functions.get_weather:0<|tool_call_argument_begin|>{"city": "New York"}<|tool_call_end|><|tool_call_begin|>functions.get_news:1<|tool_call_argument_begin|>{"topic": "technology"}<|tool_call_end|><|tool_call_begin|>functions.send_email:2<|tool_call_argument_begin|>{"to": "user@example.com", "subject": "Daily Update"}<|tool_call_end|><|tool_calls_section_end|>""",
148 + [
149 + ToolCall(
150 + id="functions.get_weather:0",
151 + function=FunctionCall(
152 + name="get_weather",
153 + arguments=json.dumps({"city": "New York"}),
154 + ),
155 + type="function",
156 + ),
157 + ToolCall(
158 + id="functions.get_news:1",
159 + function=FunctionCall(
160 + name="get_news",
161 + arguments=json.dumps({"topic": "technology"}),
162 + ),
163 + type="function",
164 + ),
165 + ToolCall(
166 + id="functions.send_email:2",
167 + function=FunctionCall(
168 + name="send_email",
169 + arguments=json.dumps(
170 + {"to": "user@example.com", "subject": "Daily Update"}
171 + ),
172 + ),
173 + type="function",
174 + ),
175 + ],
176 + "I'll help you with multiple tasks. ",
177 + ),
178 + (
179 + """Mixed spacing test. <|tool_calls_section_begin|> <|tool_call_begin|> functions.test:0 <|tool_call_argument_begin|> {} <|tool_call_end|><|tool_call_begin|>functions.test2:1<|tool_call_argument_begin|>{}<|tool_call_end|> <|tool_calls_section_end|>""",
180 + [
181 + ToolCall(
182 + id="functions.test:0",
183 + function=FunctionCall(
184 + name="test",
185 + arguments=json.dumps({}),
186 + ),
187 + type="function",
188 + ),
189 + ToolCall(
190 + id="functions.test2:1",
191 + function=FunctionCall(
192 + name="test2",
193 + arguments=json.dumps({}),
194 + ),
195 + type="function",
196 + ),
197 + ],
198 + "Mixed spacing test. ",
199 + ),
200 + (
201 + """I need to process HTML content. <|tool_calls_section_begin|><|tool_call_begin|>functions.process_html:0<|tool_call_argument_begin|>{"html": "<div>content</div>", "text": "normal text"}<|tool_call_end|><|tool_calls_section_end|>""",
202 + [
203 + ToolCall(
204 + id="functions.process_html:0",
205 + function=FunctionCall(
206 + name="process_html",
207 + arguments=json.dumps(
208 + {"html": "<div>content</div>", "text": "normal text"}
209 + ),
210 + ),
211 + type="function",
212 + )
213 + ],
214 + "I need to process HTML content. ",
215 + ),
216 + (
217 + """I need to process formatted JSON. <|tool_calls_section_begin|><|tool_call_begin|>functions.process_data:0<|tool_call_argument_begin|>{
218 + "name": "test",
219 + "value": 123,
220 + "nested": {
221 + "key": "value"
222 + }
223 +}<|tool_call_end|><|tool_calls_section_end|>""",
224 + [
225 + ToolCall(
226 + id="functions.process_data:0",
227 + function=FunctionCall(
228 + name="process_data",
229 + arguments=json.dumps(
230 + {"name": "test", "value": 123, "nested": {"key": "value"}},
231 + indent=2,
232 + ),
233 + ),
234 + type="function",
235 + )
236 + ],
237 + "I need to process formatted JSON. ",
238 + ),
239 ],
240 )
241 def test_extract_tool_calls(
vllm/entrypoints/openai/tool_parsers/kimi_k2_tool_parser.py
+2 -1
@@ -60,7 +60,8 @@ class KimiK2ToolParser(ToolParser):
60 self.tool_call_end_token: str = "<|tool_call_end|>"
61
62 self.tool_call_regex = re.compile(
63 - r"<\|tool_call_begin\|>\s*(?P<tool_call_id>.+:\d+)\s*<\|tool_call_argument_begin\|>\s*(?P<function_arguments>.*?)\s*<\|tool_call_end\|>"
63 + r"<\|tool_call_begin\|>\s*(?P<tool_call_id>[^<]+:\d+)\s*<\|tool_call_argument_begin\|>\s*(?P<function_arguments>(?:(?!<\|tool_call_begin\|>).)*?)\s*<\|tool_call_end\|>",
64 + re.DOTALL,
65 )
66
67 self.stream_tool_call_portion_regex = re.compile(