main
py 201 lines 6.87 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from __future__ import annotations
5
6 from typing import TYPE_CHECKING
7
8 from vllm.logger import init_logger
9 from vllm.tool_parsers.tool_strict_level import ToolStrictLevel
10
11 if TYPE_CHECKING:
12 from vllm.parser.abstract_parser import Parser
13 from vllm.reasoning import ReasoningParser
14 from vllm.tokenizers import TokenizerLike
15 from vllm.tool_parsers import ToolParser
16
17 logger = init_logger(__name__)
18
19 HF_PARSER = "hf"
20
21
22 class ParserManager:
23 """Provides a unified Parser by composing reasoning and tool parser adapters."""
24
25 @classmethod
26 def get_tool_parser(
27 cls,
28 tool_parser_name: str | None = None,
29 enable_auto_tools: bool = False,
30 model_name: str | None = None,
31 ) -> type[ToolParser] | None:
32 """Get the tool parser based on the name."""
33 from vllm.tool_parsers import ToolParserManager
34
35 parser: type[ToolParser] | None = None
36 if not enable_auto_tools or tool_parser_name is None:
37 return parser
38 logger.info_once('"auto" tool choice has been enabled.')
39
40 try:
41 if (
42 tool_parser_name == "pythonic"
43 and model_name
44 and model_name.startswith("meta-llama/Llama-3.2")
45 ):
46 logger.warning(
47 "Llama3.2 models may struggle to emit valid pythonic tool calls"
48 )
49 parser = ToolParserManager.get_tool_parser(tool_parser_name)
50 except Exception as e:
51 raise TypeError(
52 "Error: --enable-auto-tool-choice requires "
53 f"tool_parser:'{tool_parser_name}' which has not "
54 "been registered"
55 ) from e
56 return parser
57
58 @classmethod
59 def get_reasoning_parser(
60 cls,
61 reasoning_parser_name: str | None,
62 ) -> type[ReasoningParser] | None:
63 """Get the reasoning parser based on the name."""
64 from vllm.reasoning import ReasoningParserManager
65
66 parser: type[ReasoningParser] | None = None
67 if not reasoning_parser_name:
68 return None
69 try:
70 parser = ReasoningParserManager.get_reasoning_parser(reasoning_parser_name)
71 assert parser is not None
72 except Exception as e:
73 raise TypeError(f"{reasoning_parser_name=} has not been registered") from e
74 return parser
75
76 @classmethod
77 def get_parser(
78 cls,
79 tool_parser_name: str | None = None,
80 reasoning_parser_name: str | None = None,
81 enable_auto_tools: bool = False,
82 model_name: str | None = None,
83 is_harmony: bool = False,
84 tool_strict_level: str = "auto",
85 tokenizer: TokenizerLike | None = None,
86 ) -> type[Parser] | None:
87 """Get a Parser that handles both reasoning and tool parsing.
88
89 Composes the individual parsers into a ``DelegatingParser`` subclass.
90
91 Args:
92 tool_parser_name: The name of the tool parser.
93 reasoning_parser_name: The name of the reasoning parser.
94 enable_auto_tools: Whether auto tool choice is enabled.
95 model_name: The model name for parser-specific warnings.
96 is_harmony: Whether the selected model uses the Harmony format.
97 If True, HarmonyParser is always returned.
98 tool_strict_level: Server-side floor for tool-call structural
99 tags (``--tool-strict-level``).
100 tokenizer: Tokenizer whose `response_template` metadata is
101 validated when the `hf` parser is selected.
102
103 Returns:
104 A Parser class, or None if neither parser is specified.
105
106 """
107 if not tool_parser_name and not reasoning_parser_name:
108 return None
109
110 reasoning_parser_cls = cls.get_reasoning_parser(reasoning_parser_name)
111 tool_parser_cls = cls.get_tool_parser(
112 tool_parser_name, enable_auto_tools, model_name
113 )
114
115 if reasoning_parser_cls is None and tool_parser_cls is None:
116 return None
117
118 strict_level = ToolStrictLevel.from_name(tool_strict_level)
119
120 if is_harmony:
121 from vllm.parser.harmony import HarmonyParser
122
123 HarmonyParser.reasoning_parser_cls = reasoning_parser_cls
124 HarmonyParser.tool_parser_cls = tool_parser_cls
125 HarmonyParser.tool_strict_level = strict_level
126 return HarmonyParser
127
128 if HF_PARSER in (reasoning_parser_name, tool_parser_name):
129 if {reasoning_parser_name, tool_parser_name} - {
130 HF_PARSER,
131 None,
132 "",
133 }:
134 raise TypeError(
135 "The hf parser cannot be combined with other "
136 "reasoning or tool call parsers"
137 )
138 from vllm.parser.response_template import (
139 ResponseTemplateParser,
140 validate_tokenizer_response_template,
141 )
142
143 if tokenizer is not None:
144 validate_tokenizer_response_template(
145 tokenizer,
146 reasoning=reasoning_parser_cls is not None,
147 tools=tool_parser_cls is not None,
148 )
149
150 r_cls = reasoning_parser_cls
151 t_cls = tool_parser_cls
152 auto_tools = enable_auto_tools
153
154 class _ResponseTemplateParser(ResponseTemplateParser):
155 reasoning_parser_cls = r_cls
156 tool_parser_cls = t_cls
157 tool_strict_level = strict_level
158 _enable_auto_tools = auto_tools
159
160 return _ResponseTemplateParser
161
162 if reasoning_parser_name == "kimi_k3" or tool_parser_name == "kimi_k3":
163 from vllm.parser.kimi_k3 import KimiK3Parser
164
165 r_cls = reasoning_parser_cls
166 t_cls = tool_parser_cls
167
168 class _KimiK3Parser(KimiK3Parser):
169 reasoning_parser_cls = r_cls
170 tool_parser_cls = t_cls
171 tool_strict_level = strict_level
172
173 return _KimiK3Parser
174
175 if {reasoning_parser_name, tool_parser_name} & {
176 "cohere_command3",
177 "cohere_command4",
178 }:
179 from vllm.parser.cohere_command import CohereCommandParser
180
181 r_cls = reasoning_parser_cls
182 t_cls = tool_parser_cls
183
184 class _CohereCommandParser(CohereCommandParser):
185 reasoning_parser_cls = r_cls
186 tool_parser_cls = t_cls
187 tool_strict_level = strict_level
188
189 return _CohereCommandParser
190
191 from vllm.parser.abstract_parser import DelegatingParser
192
193 r_cls = reasoning_parser_cls
194 t_cls = tool_parser_cls
195
196 class _Parser(DelegatingParser):
197 reasoning_parser_cls = r_cls
198 tool_parser_cls = t_cls
199 tool_strict_level = strict_level
200
201 return _Parser