11# SPDX-License-Identifier: Apache-2.0
22# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
33
4- from collections .abc import Callable
5- from typing import Any , Literal
6-
4+ from collections .abc import Callable , Sequence
5+ from typing import Any , Literal , TypeAlias
6+
7+ from openai .types .responses import FunctionTool
8+ from openai .types .responses .response import ToolChoice as ResponsesToolChoice
9+ from openai .types .responses .tool import Tool as ResponsesTool
10+ from openai .types .responses .tool_choice_allowed import ToolChoiceAllowed
11+ from openai .types .responses .tool_choice_function import ToolChoiceFunction
712from xgrammar import StructuralTag , normalize_tool_choice
813from xgrammar import get_model_structural_tag as get_xgrammar_model_structural_tag
914from xgrammar .openai_tool_call_schema import (
2530 ChatCompletionToolsParam ,
2631)
2732
28- ToolChoice = (
29- Literal ["none" , "auto" , "required" ] | ChatCompletionNamedToolChoiceParam | None
33+ ToolChoice : TypeAlias = (
34+ Literal ["none" , "auto" , "required" ]
35+ | ChatCompletionNamedToolChoiceParam
36+ | ResponsesToolChoice
37+ | None
3038)
31- SimplifiedToolChoice = Literal ["auto" , "required" , "forced" ]
32- StructuralTagBuilder = Callable [
39+ AllowedToolRef : TypeAlias = dict [str , object ]
40+ SimplifiedToolChoice : TypeAlias = Literal ["auto" , "required" , "forced" ]
41+ StructuralTagBuilder : TypeAlias = Callable [
3342 [
3443 list [FunctionToolParam ],
3544 list [BuiltinToolParam ],
@@ -77,7 +86,7 @@ def decorator(func: StructuralTagBuilder) -> StructuralTagBuilder:
7786
7887def get_model_structural_tag (
7988 model : str ,
80- tools : list [ChatCompletionToolsParam ] | None ,
89+ tools : Sequence [ChatCompletionToolsParam | ResponsesTool ] | None ,
8190 tool_choice : ToolChoice ,
8291 reasoning : bool ,
8392) -> StructuralTag | None :
@@ -86,8 +95,8 @@ def get_model_structural_tag(
8695 if not tools or tool_choice == "none" :
8796 return None
8897
89- dumped_tools = [_model_dump (tool ) for tool in tools ]
90- dumped_tool_choice = _model_dump (tool_choice )
98+ dumped_tools = [_dump_tool_for_xgrammar (tool ) for tool in tools ]
99+ dumped_tool_choice = _dump_tool_choice_for_xgrammar (tool_choice )
91100
92101 if model in _VLLM_STRUCTURAL_TAG_REGISTRY :
93102 function_tools , builtin_tools , simplified_tool_choice = normalize_tool_choice (
@@ -113,12 +122,72 @@ def get_model_structural_tag(
113122 )
114123
115124
116- def _model_dump (value : Any ) -> Any :
117- """Convert vLLM/Pydantic request objects to xgrammar's dict protocol."""
125+ def _dump_tool_for_xgrammar (
126+ tool : ChatCompletionToolsParam | ResponsesTool ,
127+ ) -> dict [str , Any ]:
128+ """Convert tool objects to xgrammar's Chat Completions tool protocol."""
129+
130+ if isinstance (tool , FunctionTool ):
131+ function : dict [str , Any ] = {"name" : tool .name }
132+ if tool .description is not None :
133+ function ["description" ] = tool .description
134+ if tool .parameters is not None :
135+ function ["parameters" ] = tool .parameters
136+ if tool .strict is not None :
137+ function ["strict" ] = tool .strict
138+ return {"type" : "function" , "function" : function }
139+ dumped_tool = tool .model_dump (mode = "json" , exclude_none = True )
140+ if isinstance (tool , ChatCompletionToolsParam ):
141+ return dumped_tool
142+ return dict (dumped_tool )
143+
144+
145+ def _dump_tool_choice_for_xgrammar (
146+ tool_choice : ToolChoice ,
147+ ) -> dict [str , Any ] | str | None :
148+ """Convert tool_choice objects to xgrammar's expected protocol."""
149+
150+ if tool_choice is None :
151+ return None
118152
119- if hasattr (value , "model_dump" ):
120- return value .model_dump (exclude_none = True )
121- return value
153+ if isinstance (tool_choice , str ):
154+ return tool_choice
155+
156+ if isinstance (tool_choice , ChatCompletionNamedToolChoiceParam ):
157+ return tool_choice .model_dump (mode = "json" , exclude_none = True )
158+
159+ if isinstance (tool_choice , ToolChoiceFunction ):
160+ return {
161+ "type" : "function" ,
162+ "function" : {"name" : tool_choice .name },
163+ }
164+
165+ if isinstance (tool_choice , ToolChoiceAllowed ):
166+ return {
167+ "type" : "allowed_tools" ,
168+ "allowed_tools" : {
169+ "mode" : tool_choice .mode ,
170+ "tools" : [
171+ _dump_allowed_tool_ref_for_xgrammar (tool )
172+ for tool in tool_choice .tools
173+ ],
174+ },
175+ }
176+
177+ return tool_choice .model_dump (mode = "json" , exclude_none = True )
178+
179+
180+ def _dump_allowed_tool_ref_for_xgrammar (tool_ref : AllowedToolRef ) -> AllowedToolRef :
181+ if (
182+ tool_ref .get ("type" ) == "function"
183+ and "function" not in tool_ref
184+ and "name" in tool_ref
185+ ):
186+ return {
187+ "type" : "function" ,
188+ "function" : {"name" : tool_ref ["name" ]},
189+ }
190+ return tool_ref
122191
123192
124193def _get_function_parameters (function ) -> dict [str , Any ] | bool :
0 commit comments