main
py 144 lines 4.72 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """DeepSeek V3.2 parser: DSML tool calls with ``function_calls`` wrapper.
4
5 DeepSeek V3.2 output format::
6
7 <|DSML|function_calls>
8 <|DSML|invoke name="func_name">
9 <|DSML|parameter name="location" string="true">杭州</|DSML|parameter>
10 <|DSML|parameter name="count" string="false">5</|DSML|parameter>
11 </|DSML|invoke>
12 </|DSML|function_calls>
13
14 This is identical to DeepSeek V4 except for the outer wrapper
15 (``function_calls`` instead of ``tool_calls``) and the absence of
16 ``<think>``/``</think>`` reasoning tags.
17 """
18
19 from __future__ import annotations
20
21 import functools
22 from typing import TYPE_CHECKING
23
24 from vllm.parser.deepseek_v4 import (
25 DSML_INVOKE_END,
26 DSML_INVOKE_NAME_END,
27 DSML_INVOKE_PREFIX,
28 DSML_PARAM_CLOSE,
29 DSML_PARAM_START,
30 _dsml_arg_converter,
31 _unwrap_wrapper_args,
32 )
33 from vllm.parser.engine.events import EventType
34 from vllm.parser.engine.parser_engine import ParserEngine
35 from vllm.parser.engine.parser_engine_config import (
36 ParserEngineConfig,
37 ParserState,
38 Transition,
39 )
40
41 if TYPE_CHECKING:
42 from vllm.tokenizers import TokenizerLike
43 from vllm.tool_parsers.abstract_tool_parser import Tool
44
45 _DSML = "|DSML|"
46
47 DSML_FUNC_START = f"<{_DSML}function_calls>"
48 DSML_FUNC_END = f"</{_DSML}function_calls>"
49
50
51 @functools.cache
52 def deepseek_v32_config() -> ParserEngineConfig:
53 return ParserEngineConfig(
54 name="deepseek_v32",
55 initial_state=ParserState.CONTENT,
56 wait_for_reasoning=False,
57 terminals={
58 "TOOL_START": DSML_FUNC_START,
59 "TOOL_END": DSML_FUNC_END,
60 "INVOKE_PREFIX": DSML_INVOKE_PREFIX,
61 "INVOKE_NAME_END": DSML_INVOKE_NAME_END,
62 "INVOKE_END": DSML_INVOKE_END,
63 "PARAM_START": DSML_PARAM_START,
64 "PARAM_CLOSE": DSML_PARAM_CLOSE,
65 },
66 token_id_terminals={
67 "TOOL_START": DSML_FUNC_START,
68 "TOOL_END": DSML_FUNC_END,
69 },
70 transitions={
71 (ParserState.CONTENT, "TOOL_START"): Transition(
72 ParserState.TOOL_PREAMBLE,
73 (),
74 ),
75 (ParserState.TOOL_PREAMBLE, "INVOKE_PREFIX"): Transition(
76 ParserState.TOOL_NAME,
77 (EventType.TOOL_CALL_START,),
78 ),
79 (ParserState.CONTENT, "INVOKE_PREFIX"): Transition(
80 ParserState.TOOL_NAME,
81 (EventType.TOOL_CALL_START,),
82 ),
83 (ParserState.TOOL_NAME, "INVOKE_NAME_END"): Transition(
84 ParserState.TOOL_ARGS,
85 (),
86 ),
87 (ParserState.TOOL_ARGS, "INVOKE_END"): Transition(
88 ParserState.TOOL_BETWEEN,
89 (EventType.TOOL_CALL_END,),
90 ),
91 (ParserState.TOOL_ARGS, "TOOL_END"): Transition(
92 ParserState.TOOL_BETWEEN,
93 (EventType.TOOL_CALL_END,),
94 ),
95 # Parallel tool calls
96 (ParserState.TOOL_BETWEEN, "INVOKE_PREFIX"): Transition(
97 ParserState.TOOL_NAME,
98 (EventType.TOOL_CALL_START,),
99 ),
100 # A tool call ends the turn: stay in TOOL_BETWEEN so any text
101 # after the block is dropped.
102 (ParserState.TOOL_BETWEEN, "TOOL_END"): Transition(
103 ParserState.TOOL_BETWEEN,
104 (),
105 ),
106 (ParserState.TOOL_BETWEEN, "TOOL_START"): Transition(
107 ParserState.TOOL_PREAMBLE,
108 (),
109 ),
110 },
111 content_events={
112 ParserState.CONTENT: EventType.TEXT_CHUNK,
113 ParserState.TOOL_NAME: EventType.TOOL_NAME,
114 ParserState.TOOL_ARGS: EventType.ARG_VALUE_CHUNK,
115 },
116 arg_converter=_dsml_arg_converter,
117 arg_structural_chars=frozenset(">"),
118 strip_content_whitespace_with_tools=False,
119 tool_args_json=False,
120 )
121
122
123 class DeepSeekV32Parser(ParserEngine):
124 def __init__(
125 self,
126 tokenizer: TokenizerLike,
127 tools: list[Tool] | None = None,
128 **kwargs,
129 ) -> None:
130 kwargs.pop("chat_template_kwargs", None)
131 super().__init__(
132 tokenizer,
133 tools,
134 parser_engine_config=deepseek_v32_config(),
135 **kwargs,
136 )
137 self._arg_converter = self._convert_args
138
139 def _convert_args(self, raw_args: str, partial: bool) -> str:
140 result = _dsml_arg_converter(raw_args, partial)
141 if not self._tools:
142 return result
143 func_name = next((s.name for s in self._tool_slots if s.args == raw_args), None)
144 return _unwrap_wrapper_args(result, self._tools, func_name)