@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 582bbe6bd7
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 14, 05:30 UTC

[Fix] correct tool_id for kimi-k2 when use tool_choice=required (#21259)

Co-authored-by: wangzhengtao <wangzhengtao@msh.team>

bigmoyan committed Aug 21, 2025 at 03:59 UTC 582bbe6bd708d01d74d6d02d6ef59b4c3c34a7b1
15 files changed +283 -166
tests/entrypoints/openai/test_completion_with_function_calling.py
+191 -123
@@ -13,6 +13,127 @@ from ...utils import RemoteOpenAIServer
13 # any model with a chat template should work here
14 MODEL_NAME = "Qwen/Qwen3-0.6B"
15
16 +tools = [
17 + {
18 + "type": "function",
19 + "function": {
20 + "name": "get_current_weather",
21 + "description": "Get the current weather in a given location",
22 + "parameters": {
23 + "type": "object",
24 + "properties": {
25 + "city": {
26 + "type": "string",
27 + "description":
28 + "The city to find the weather for, e.g. 'Vienna'",
29 + "default": "Vienna",
30 + },
31 + "country": {
32 + "type":
33 + "string",
34 + "description":
35 + "The country that the city is in, e.g. 'Austria'",
36 + },
37 + "unit": {
38 + "type": "string",
39 + "description": "The unit to fetch the temperature in",
40 + "enum": ["celsius", "fahrenheit"],
41 + },
42 + "options": {
43 + "$ref": "#/$defs/WeatherOptions",
44 + "description": "Optional parameters for weather query",
45 + },
46 + },
47 + "required": ["country", "unit"],
48 + "$defs": {
49 + "WeatherOptions": {
50 + "title": "WeatherOptions",
51 + "type": "object",
52 + "additionalProperties": False,
53 + "properties": {
54 + "unit": {
55 + "type": "string",
56 + "enum": ["celsius", "fahrenheit"],
57 + "default": "celsius",
58 + "description": "Temperature unit",
59 + "title": "Temperature Unit",
60 + },
61 + "include_forecast": {
62 + "type": "boolean",
63 + "default": False,
64 + "description":
65 + "Whether to include a 24-hour forecast",
66 + "title": "Include Forecast",
67 + },
68 + "language": {
69 + "type": "string",
70 + "default": "zh-CN",
71 + "description": "Language of the response",
72 + "title": "Language",
73 + "enum": ["zh-CN", "en-US", "ja-JP"],
74 + },
75 + },
76 + },
77 + },
78 + },
79 + },
80 + },
81 + {
82 + "type": "function",
83 + "function": {
84 + "name": "get_forecast",
85 + "description": "Get the weather forecast for a given location",
86 + "parameters": {
87 + "type": "object",
88 + "properties": {
89 + "city": {
90 + "type": "string",
91 + "description":
92 + "The city to get the forecast for, e.g. 'Vienna'",
93 + "default": "Vienna",
94 + },
95 + "country": {
96 + "type":
97 + "string",
98 + "description":
99 + "The country that the city is in, e.g. 'Austria'",
100 + },
101 + "days": {
102 + "type":
103 + "integer",
104 + "description":
105 + "Number of days to get the forecast for (1-7)",
106 + },
107 + "unit": {
108 + "type": "string",
109 + "description": "The unit to fetch the temperature in",
110 + "enum": ["celsius", "fahrenheit"],
111 + },
112 + },
113 + "required": ["country", "days", "unit"],
114 + },
115 + },
116 + },
117 +]
118 +
119 +messages = [
120 + {
121 + "role": "user",
122 + "content": "Hi! How are you doing today?"
123 + },
124 + {
125 + "role": "assistant",
126 + "content": "I'm doing well! How can I help you?"
127 + },
128 + {
129 + "role":
130 + "user",
131 + "content":
132 + "Can you tell me what the current weather is in Berlin and the "\
133 + "forecast for the next 5 days, in fahrenheit?",
134 + },
135 +]
136 +
137
138 @pytest.fixture(scope="module")
139 def server(): # noqa: F811
@@ -27,6 +148,8 @@ def server(): # noqa: F811
148 "hermes",
149 "--reasoning-parser",
150 "qwen3",
151 + "--gpu-memory-utilization",
152 + "0.4"
153 ]
154
155 with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
@@ -54,129 +177,6 @@ async def client(server):
177 async def test_function_tool_use(client: openai.AsyncOpenAI, model_name: str,
178 stream: bool, tool_choice: Union[str, dict],
179 enable_thinking: bool):
57 - tools = [
58 - {
59 - "type": "function",
60 - "function": {
61 - "name": "get_current_weather",
62 - "description": "Get the current weather in a given location",
63 - "parameters": {
64 - "type": "object",
65 - "properties": {
66 - "city": {
67 - "type": "string",
68 - "description":
69 - "The city to find the weather for, e.g. 'Vienna'",
70 - "default": "Vienna",
71 - },
72 - "country": {
73 - "type":
74 - "string",
75 - "description":
76 - "The country that the city is in, e.g. 'Austria'",
77 - },
78 - "unit": {
79 - "type": "string",
80 - "description":
81 - "The unit to fetch the temperature in",
82 - "enum": ["celsius", "fahrenheit"],
83 - },
84 - "options": {
85 - "$ref": "#/$defs/WeatherOptions",
86 - "description":
87 - "Optional parameters for weather query",
88 - },
89 - },
90 - "required": ["country", "unit"],
91 - "$defs": {
92 - "WeatherOptions": {
93 - "title": "WeatherOptions",
94 - "type": "object",
95 - "additionalProperties": False,
96 - "properties": {
97 - "unit": {
98 - "type": "string",
99 - "enum": ["celsius", "fahrenheit"],
100 - "default": "celsius",
101 - "description": "Temperature unit",
102 - "title": "Temperature Unit",
103 - },
104 - "include_forecast": {
105 - "type": "boolean",
106 - "default": False,
107 - "description":
108 - "Whether to include a 24-hour forecast",
109 - "title": "Include Forecast",
110 - },
111 - "language": {
112 - "type": "string",
113 - "default": "zh-CN",
114 - "description": "Language of the response",
115 - "title": "Language",
116 - "enum": ["zh-CN", "en-US", "ja-JP"],
117 - },
118 - },
119 - },
120 - },
121 - },
122 - },
123 - },
124 - {
125 - "type": "function",
126 - "function": {
127 - "name": "get_forecast",
128 - "description": "Get the weather forecast for a given location",
129 - "parameters": {
130 - "type": "object",
131 - "properties": {
132 - "city": {
133 - "type": "string",
134 - "description":
135 - "The city to get the forecast for, e.g. 'Vienna'",
136 - "default": "Vienna",
137 - },
138 - "country": {
139 - "type":
140 - "string",
141 - "description":
142 - "The country that the city is in, e.g. 'Austria'",
143 - },
144 - "days": {
145 - "type":
146 - "integer",
147 - "description":
148 - "Number of days to get the forecast for (1-7)",
149 - },
150 - "unit": {
151 - "type": "string",
152 - "description":
153 - "The unit to fetch the temperature in",
154 - "enum": ["celsius", "fahrenheit"],
155 - },
156 - },
157 - "required": ["country", "days", "unit"],
158 - },
159 - },
160 - },
161 - ]
162 -
163 - messages = [
164 - {
165 - "role": "user",
166 - "content": "Hi! How are you doing today?"
167 - },
168 - {
169 - "role": "assistant",
170 - "content": "I'm doing well! How can I help you?"
171 - },
172 - {
173 - "role":
174 - "user",
175 - "content":
176 - "Can you tell me what the current weather is in Berlin and the "\
177 - "forecast for the next 5 days, in fahrenheit?",
178 - },
179 - ]
180 if not stream:
181 # Non-streaming test
182 chat_completion = await client.chat.completions.create(
@@ -216,3 +216,71 @@ async def test_function_tool_use(client: openai.AsyncOpenAI, model_name: str,
216 output.extend(chunk.choices[0].delta.tool_calls)
217
218 assert len(output) > 0
219 +
220 +
221 +@pytest.fixture(scope="module")
222 +def k2_server(): # noqa: F811
223 + args = [
224 + # use half precision for speed and memory savings in CI environment
225 + "--dtype",
226 + "half",
227 + "--enable-auto-tool-choice",
228 + "--guided-decoding-backend",
229 + "xgrammar",
230 + "--tool-call-parser",
231 + "hermes",
232 + "--reasoning-parser",
233 + "qwen3",
234 + "--gpu-memory-utilization",
235 + "0.4",
236 + ]
237 + # hack to test kimi_k2 tool use tool_id format.
238 + # avoid error in is_deepseek_mla check by setting kv_lora_rank=null
239 + with RemoteOpenAIServer(MODEL_NAME,
240 + args,
241 + override_hf_configs={
242 + "model_type": 'kimi_k2',
243 + 'kv_lora_rank': None
244 + }) as remote_server:
245 + yield remote_server
246 +
247 +
248 +@pytest_asyncio.fixture
249 +async def k2_client(k2_server):
250 + async with k2_server.get_async_client() as async_client:
251 + yield async_client
252 +
253 +
254 +@pytest.mark.asyncio
255 +@pytest.mark.parametrize("model_name", [MODEL_NAME])
256 +@pytest.mark.parametrize("stream", [True, False])
257 +@pytest.mark.parametrize("tool_choice", ["required"])
258 +async def test_tool_id_kimi_k2(k2_client: openai.AsyncOpenAI, model_name: str,
259 + stream: bool, tool_choice: str):
260 +
261 + if not stream:
262 + # Non-streaming test
263 + chat_completion = await k2_client.chat.completions.create(
264 + messages=messages,
265 + model=model_name,
266 + tools=tools,
267 + tool_choice=tool_choice)
268 + assert chat_completion.choices[0].message.tool_calls is not None
269 + assert len(chat_completion.choices[0].message.tool_calls) > 0
270 + assert chat_completion.choices[0].message.tool_calls[
271 + 0].id == 'functions.get_current_weather:0'
272 + else:
273 + # Streaming test
274 + output_stream = await k2_client.chat.completions.create(
275 + messages=messages,
276 + model=model_name,
277 + tools=tools,
278 + tool_choice=tool_choice,
279 + stream=True)
280 +
281 + output = []
282 + async for chunk in output_stream:
283 + if chunk.choices and chunk.choices[0].delta.tool_calls:
284 + output.extend(chunk.choices[0].delta.tool_calls)
285 + for o in output:
286 + assert o.id is None or o.id == 'functions.get_current_weather:0'
tests/utils.py
+9 -1
@@ -5,6 +5,7 @@ import asyncio
5 import copy
6 import functools
7 import importlib
8 +import json
9 import os
10 import signal
11 import subprocess
@@ -101,7 +102,8 @@ class RemoteOpenAIServer:
102 env_dict: Optional[dict[str, str]] = None,
103 seed: Optional[int] = 0,
104 auto_port: bool = True,
104 - max_wait_seconds: Optional[float] = None) -> None:
105 + max_wait_seconds: Optional[float] = None,
106 + override_hf_configs: Optional[dict[str, Any]] = None) -> None:
107 if auto_port:
108 if "-p" in vllm_serve_args or "--port" in vllm_serve_args:
109 raise ValueError("You have manually specified the port "
@@ -120,6 +122,12 @@ class RemoteOpenAIServer:
122
123 vllm_serve_args = vllm_serve_args + ["--seed", str(seed)]
124
125 + if override_hf_configs is not None:
126 + vllm_serve_args = vllm_serve_args + [
127 + "--hf-overrides",
128 + json.dumps(override_hf_configs)
129 + ]
130 +
131 parser = FlexibleArgumentParser(
132 description="vLLM's remote OpenAI server.")
133 subparsers = parser.add_subparsers(required=False, dest="subparser")
vllm/entrypoints/chat_utils.py
+15 -2
@@ -1345,5 +1345,18 @@ def apply_mistral_chat_template(
1345 "template")
1346 raise ValueError(str(e)) from e
1347
1348 -def random_tool_call_id() -> str:
1349 - return f"chatcmpl-tool-{random_uuid()}"
1348 +def get_history_tool_calls_cnt(conversation: list[ConversationMessage]):
1349 + idx = 0
1350 + for msg in conversation:
1351 + if msg['role'] == 'assistant':
1352 + tool_calls = msg.get('tool_calls')
1353 + idx += len(list(tool_calls)) if tool_calls is not None else 0 # noqa
1354 + return idx
1355 +
1356 +def make_tool_call_id(id_type:str='random', func_name=None, idx=None):
1357 +
1358 + if id_type=='kimi_k2':
1359 + return f'functions.{func_name}:{idx}'
1360 + else:
1361 + # by default return random
1362 + return f"chatcmpl-tool-{random_uuid()}"
vllm/entrypoints/openai/protocol.py
+2 -2
@@ -38,7 +38,7 @@ from typing_extensions import TypeAlias
38
39 from vllm import envs
40 from vllm.entrypoints.chat_utils import (ChatCompletionMessageParam,
41 - random_tool_call_id)
41 + make_tool_call_id)
42 from vllm.entrypoints.score_utils import (ScoreContentPartParam,
43 ScoreMultiModalParam)
44 from vllm.logger import init_logger
@@ -1634,7 +1634,7 @@ class FunctionCall(OpenAIBaseModel):
1634
1635
1636 class ToolCall(OpenAIBaseModel):
1637 - id: str = Field(default_factory=random_tool_call_id)
1637 + id: str = Field(default_factory=make_tool_call_id)
1638 type: Literal["function"] = "function"
1639 function: FunctionCall
1640
vllm/entrypoints/openai/serving_chat.py
+46 -18
@@ -19,7 +19,8 @@ from vllm.config import ModelConfig
19 from vllm.engine.protocol import EngineClient
20 from vllm.entrypoints.chat_utils import (ChatTemplateContentFormatOption,
21 ConversationMessage,
22 - random_tool_call_id)
22 + get_history_tool_calls_cnt,
23 + make_tool_call_id)
24 from vllm.entrypoints.harmony_utils import (
25 get_developer_message, get_stop_tokens_for_assistant_actions,
26 get_streamable_parser_for_assistant, get_system_message, parse_chat_input,
@@ -133,6 +134,10 @@ class OpenAIServingChat(OpenAIServing):
134 source = "model" if source == "auto" else source
135 logger.info("Using default chat sampling params from %s: %s",
136 source, self.default_sampling_params)
137 + if self.model_config.hf_config.model_type == 'kimi_k2':
138 + self.tool_call_id_type = 'kimi_k2'
139 + else:
140 + self.tool_call_id_type = 'random'
141
142 self.use_harmony = model_config.hf_config.model_type == "gpt_oss"
143 if self.use_harmony:
@@ -379,6 +384,7 @@ class OpenAIServingChat(OpenAIServing):
384 current_text: Optional[str],
385 delta_text: str,
386 function_name_returned: bool,
387 + tool_call_idx: Optional[int] = None
388 ) -> tuple[Optional[DeltaMessage], bool]:
389 if current_text is None or current_text == "":
390 # if the current text is empty, we cannot parse it
@@ -424,8 +430,12 @@ class OpenAIServingChat(OpenAIServing):
430 current_tool_call = obj[-2]
431
432 function_name_returned = True
433 + tool_call_id = make_tool_call_id(
434 + id_type=self.tool_call_id_type,
435 + func_name=current_tool_call["name"],
436 + idx=tool_call_idx)
437 delta_message = DeltaMessage(tool_calls=[
428 - DeltaToolCall(id=random_tool_call_id(),
438 + DeltaToolCall(id=tool_call_id,
439 function=DeltaFunctionCall(
440 name=current_tool_call["name"],
441 arguments=arguments),
@@ -491,6 +501,10 @@ class OpenAIServingChat(OpenAIServing):
501
502 all_previous_token_ids: Optional[list[list[int]]]
503 function_name_returned = [False] * num_choices
504 + if self.tool_call_id_type == 'kimi_k2':
505 + history_tool_call_cnt = get_history_tool_calls_cnt(conversation)
506 + else:
507 + history_tool_call_cnt = 0
508
509 # Always track previous_texts for comprehensive output logging
510 previous_texts = [""] * num_choices
@@ -673,7 +687,6 @@ class OpenAIServingChat(OpenAIServing):
687 previous_text = previous_texts[i]
688 previous_token_ids = all_previous_token_ids[i]
689 current_text = previous_text + delta_text
676 -
690 # avoid the None + list error.
691 if previous_token_ids:
692 current_token_ids = previous_token_ids + as_list(
@@ -733,7 +746,7 @@ class OpenAIServingChat(OpenAIServing):
746 index=i)
747 else:
748 delta_tool_call = DeltaToolCall(
736 - id=random_tool_call_id(),
749 + id=make_tool_call_id(),
750 type="function",
751 function=DeltaFunctionCall(
752 name=tool_choice_function_name,
@@ -764,7 +777,11 @@ class OpenAIServingChat(OpenAIServing):
777 previous_text=previous_text,
778 current_text=content,
779 delta_text=delta_text,
767 - function_name_returned=fn_name_returned))
780 + function_name_returned=fn_name_returned,
781 + tool_call_idx=history_tool_call_cnt))
782 + if (delta_message and delta_message.tool_calls and
783 + delta_message.tool_calls[0].id is not None):
784 + history_tool_call_cnt += 1
785
786 # update the previous values for the next iteration
787 previous_texts[i] = current_text
@@ -1089,6 +1106,10 @@ class OpenAIServingChat(OpenAIServing):
1106 assert final_res is not None
1107
1108 choices: list[ChatCompletionResponseChoice] = []
1109 + if self.tool_call_id_type == 'kimi_k2':
1110 + history_tool_call_cnt = get_history_tool_calls_cnt(conversation)
1111 + else:
1112 + history_tool_call_cnt = 0
1113
1114 role = self.get_chat_request_role(request)
1115 for output in final_res.outputs:
@@ -1194,17 +1215,26 @@ class OpenAIServingChat(OpenAIServing):
1215 assert content is not None
1216 tool_calls = TypeAdapter(
1217 list[FunctionDefinition]).validate_json(content)
1218 + tool_call_ids = []
1219 + for tool_call in tool_calls:
1220 + tool_call_ids.append(
1221 + make_tool_call_id(id_type=self.tool_call_id_type,
1222 + func_name=tool_call.name,
1223 + idx=history_tool_call_cnt))
1224 + history_tool_call_cnt += 1
1225 message = ChatMessage(
1226 role=role,
1227 content="",
1200 - reasoning_content=reasoning_content,
1228 tool_calls=[
1202 - tool_call_class(function=FunctionCall(
1203 - name=tool_call.name,
1204 - arguments=json.dumps(tool_call.parameters,
1205 - ensure_ascii=False)))
1206 - for tool_call in tool_calls
1207 - ])
1229 + tool_call_class(id=tool_call_ids[i],
1230 + function=FunctionCall(
1231 + name=tool_call.name,
1232 + arguments=json.dumps(
1233 + tool_call.parameters,
1234 + ensure_ascii=False)))
1235 + for i, tool_call in enumerate(tool_calls)
1236 + ],
1237 + reasoning_content=reasoning_content)
1238
1239 # if the request doesn't use tool choice
1240 # OR specifies to not use a tool
@@ -1248,7 +1278,6 @@ class OpenAIServingChat(OpenAIServing):
1278 if (tool_call_info.content
1279 and len(tool_call_info.content) > 0):
1280 ret_content = tool_call_info.content
1251 -
1281 message = ChatMessage(role=role,
1282 reasoning_content=reasoning_content,
1283 content=ret_content)
@@ -1327,12 +1356,11 @@ class OpenAIServingChat(OpenAIServing):
1356 elif choice.message.tool_calls:
1357 # For tool calls, log the function name and arguments
1358 tool_call_descriptions = []
1330 - for tool_call in choice.message.tool_calls:
1331 - if hasattr(tool_call.function, "name") and hasattr(
1332 - tool_call.function, "arguments"):
1359 + for tc in choice.message.tool_calls:
1360 + if hasattr(tc.function, "name") and hasattr(
1361 + tc.function, "arguments"):
1362 tool_call_descriptions.append(
1334 - f"{tool_call.function.name}({tool_call.function.arguments})"
1335 - )
1363 + f"{tc.function.name}({tc.function.arguments})")
1364 tool_calls_str = ", ".join(tool_call_descriptions)
1365 output_text = f"[tool_calls: {tool_calls_str}]"
1366
vllm/entrypoints/openai/tool_parsers/deepseekv3_tool_parser.py
+2 -2
@@ -6,7 +6,7 @@ from typing import Union
6
7 import regex as re
8
9 -from vllm.entrypoints.chat_utils import random_tool_call_id
9 +from vllm.entrypoints.chat_utils import make_tool_call_id
10 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
11 DeltaFunctionCall, DeltaMessage,
12 DeltaToolCall,
@@ -267,7 +267,7 @@ class DeepSeekV3ToolParser(ToolParser):
267 DeltaToolCall(
268 index=self.current_tool_id,
269 type="function",
270 - id=random_tool_call_id(),
270 + id=make_tool_call_id(),
271 function=DeltaFunctionCall(
272 name=function_name).model_dump(
273 exclude_none=True),
vllm/entrypoints/openai/tool_parsers/granite_20b_fc_tool_parser.py
+2 -2
@@ -10,7 +10,7 @@ import partial_json_parser
10 import regex as re
11 from partial_json_parser.core.options import Allow
12
13 -from vllm.entrypoints.chat_utils import random_tool_call_id
13 +from vllm.entrypoints.chat_utils import make_tool_call_id
14 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
15 DeltaFunctionCall, DeltaMessage,
16 DeltaToolCall,
@@ -203,7 +203,7 @@ class Granite20bFCToolParser(ToolParser):
203 delta = DeltaMessage(tool_calls=[
204 DeltaToolCall(index=self.current_tool_id,
205 type="function",
206 - id=random_tool_call_id(),
206 + id=make_tool_call_id(),
207 function=DeltaFunctionCall(
208 name=function_name).model_dump(
209 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/granite_tool_parser.py
+2 -2
@@ -8,7 +8,7 @@ from typing import Union
8 import partial_json_parser
9 from partial_json_parser.core.options import Allow
10
11 -from vllm.entrypoints.chat_utils import random_tool_call_id
11 +from vllm.entrypoints.chat_utils import make_tool_call_id
12 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
13 DeltaFunctionCall, DeltaMessage,
14 DeltaToolCall,
@@ -185,7 +185,7 @@ class GraniteToolParser(ToolParser):
185 delta = DeltaMessage(tool_calls=[
186 DeltaToolCall(index=self.current_tool_id,
187 type="function",
188 - id=random_tool_call_id(),
188 + id=make_tool_call_id(),
189 function=DeltaFunctionCall(
190 name=function_name).model_dump(
191 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/hermes_tool_parser.py
+2 -2
@@ -9,7 +9,7 @@ import partial_json_parser
9 import regex as re
10 from partial_json_parser.core.options import Allow
11
12 -from vllm.entrypoints.chat_utils import random_tool_call_id
12 +from vllm.entrypoints.chat_utils import make_tool_call_id
13 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
14 DeltaFunctionCall, DeltaMessage,
15 DeltaToolCall,
@@ -307,7 +307,7 @@ class Hermes2ProToolParser(ToolParser):
307 return DeltaMessage(tool_calls=[
308 DeltaToolCall(index=self.current_tool_id,
309 type="function",
310 - id=random_tool_call_id(),
310 + id=make_tool_call_id(),
311 function=DeltaFunctionCall(
312 name=function_name).model_dump(
313 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/internlm2_tool_parser.py
+2 -2
@@ -8,7 +8,7 @@ from typing import Union
8 import partial_json_parser
9 from partial_json_parser.core.options import Allow
10
11 -from vllm.entrypoints.chat_utils import random_tool_call_id
11 +from vllm.entrypoints.chat_utils import make_tool_call_id
12 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
13 DeltaFunctionCall, DeltaMessage,
14 DeltaToolCall,
@@ -107,7 +107,7 @@ class Internlm2ToolParser(ToolParser):
107 delta = DeltaMessage(tool_calls=[
108 DeltaToolCall(index=self.current_tool_id,
109 type="function",
110 - id=random_tool_call_id(),
110 + id=make_tool_call_id(),
111 function=DeltaFunctionCall(
112 name=function_name).model_dump(
113 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/jamba_tool_parser.py
+2 -2
@@ -9,7 +9,7 @@ import partial_json_parser
9 import regex as re
10 from partial_json_parser.core.options import Allow
11
12 -from vllm.entrypoints.chat_utils import random_tool_call_id
12 +from vllm.entrypoints.chat_utils import make_tool_call_id
13 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
14 DeltaFunctionCall, DeltaMessage,
15 DeltaToolCall,
@@ -222,7 +222,7 @@ class JambaToolParser(ToolParser):
222 delta = DeltaMessage(tool_calls=[
223 DeltaToolCall(index=self.current_tool_id,
224 type="function",
225 - id=random_tool_call_id(),
225 + id=make_tool_call_id(),
226 function=DeltaFunctionCall(
227 name=function_name).model_dump(
228 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/llama_tool_parser.py
+2 -2
@@ -10,7 +10,7 @@ import regex as re
10 from partial_json_parser.core.options import Allow
11 from transformers import PreTrainedTokenizerBase
12
13 -from vllm.entrypoints.chat_utils import random_tool_call_id
13 +from vllm.entrypoints.chat_utils import make_tool_call_id
14 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
15 DeltaFunctionCall, DeltaMessage,
16 DeltaToolCall,
@@ -213,7 +213,7 @@ class Llama3JsonToolParser(ToolParser):
213 delta = DeltaMessage(tool_calls=[
214 DeltaToolCall(index=self.current_tool_id,
215 type="function",
216 - id=random_tool_call_id(),
216 + id=make_tool_call_id(),
217 function=DeltaFunctionCall(
218 name=function_name).model_dump(
219 exclude_none=True))
vllm/entrypoints/openai/tool_parsers/minimax_tool_parser.py
+2 -2
@@ -7,7 +7,7 @@ from typing import Any, Optional, Union
7
8 import regex as re
9
10 -from vllm.entrypoints.chat_utils import random_tool_call_id
10 +from vllm.entrypoints.chat_utils import make_tool_call_id
11 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
12 DeltaFunctionCall, DeltaMessage,
13 DeltaToolCall,
@@ -394,7 +394,7 @@ class MinimaxToolParser(ToolParser):
394 sent_tools.append({
395 "sent_name": False,
396 "sent_arguments": "",
397 - "id": random_tool_call_id(),
397 + "id": make_tool_call_id(),
398 })
399
400 while len(tool_ids) < tool_count:
vllm/entrypoints/openai/tool_parsers/phi4mini_tool_parser.py
+2 -2
@@ -8,7 +8,7 @@ from typing import Any, Optional
8 import regex as re
9 from transformers import PreTrainedTokenizerBase
10
11 -from vllm.entrypoints.chat_utils import random_tool_call_id
11 +from vllm.entrypoints.chat_utils import make_tool_call_id
12 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
13 DeltaMessage,
14 ExtractedToolCallInformation,
@@ -74,7 +74,7 @@ class Phi4MiniJsonToolParser(ToolParser):
74
75 tool_calls: list[ToolCall] = [
76 ToolCall(
77 - id=random_tool_call_id(),
77 + id=make_tool_call_id(),
78 type="function",
79 function=FunctionCall(
80 name=raw_function_call["name"],
vllm/entrypoints/openai/tool_parsers/xlam_tool_parser.py
+2 -2
@@ -7,7 +7,7 @@ from typing import Any, Optional, Union
7
8 import regex as re
9
10 -from vllm.entrypoints.chat_utils import random_tool_call_id
10 +from vllm.entrypoints.chat_utils import make_tool_call_id
11 from vllm.entrypoints.openai.protocol import (ChatCompletionRequest,
12 DeltaFunctionCall, DeltaMessage,
13 DeltaToolCall,
@@ -226,7 +226,7 @@ class xLAMToolParser(ToolParser):
226 function_name = name_match.group(1)
227
228 # The test expects us to send just the name first
229 - tool_id = random_tool_call_id()
229 + tool_id = make_tool_call_id()
230 delta = DeltaMessage(tool_calls=[
231 DeltaToolCall(
232 index=0,