tool_parser/parsers/
deepseek31.rs1use async_trait::async_trait;
2use openai_protocol::common::Tool;
3use regex::Regex;
4use serde_json::Value;
5
6use crate::{
7 errors::{ParserError, ParserResult},
8 parsers::helpers,
9 traits::ToolParser,
10 types::{FunctionCall, StreamingParseResult, ToolCall, ToolCallItem},
11};
12
13pub struct DeepSeek31Parser {
26 tool_call_extractor: Regex,
28 func_detail_extractor: Regex,
30 partial_tool_call_regex: Regex,
32 tool_call_end_pattern: Regex,
34
35 buffer: String,
37
38 prev_tool_call_arr: Vec<Value>,
40
41 current_tool_id: i32,
43
44 current_tool_name_sent: bool,
46
47 streamed_args_for_tool: Vec<String>,
49}
50
51impl DeepSeek31Parser {
52 #[expect(
54 clippy::expect_used,
55 reason = "regex patterns are compile-time string literals"
56 )]
57 pub fn new() -> Self {
58 let tool_call_extractor = Regex::new(r"(?s)<|tool▁call▁begin|>.*?<|tool▁call▁end|>")
59 .expect("Valid regex pattern");
60
61 let func_detail_extractor =
62 Regex::new(r"(?s)<|tool▁call▁begin|>(.*?)<|tool▁sep|>(.*?)<|tool▁call▁end|>")
63 .expect("Valid regex pattern");
64
65 let partial_tool_call_regex =
66 Regex::new(r"(?s)<|tool▁call▁begin|>(.*)<|tool▁sep|>(.*)")
67 .expect("Valid regex pattern");
68
69 let tool_call_end_pattern = Regex::new(r"(?s)<|tool▁call▁begin|>.*?<|tool▁call▁end|>")
70 .expect("Valid regex pattern");
71
72 Self {
73 tool_call_extractor,
74 func_detail_extractor,
75 partial_tool_call_regex,
76 tool_call_end_pattern,
77 buffer: String::new(),
78 prev_tool_call_arr: Vec::new(),
79 current_tool_id: -1,
80 current_tool_name_sent: false,
81 streamed_args_for_tool: Vec::new(),
82 }
83 }
84
85 fn parse_tool_call(&self, block: &str) -> ParserResult<ToolCall> {
87 let captures = self.func_detail_extractor.captures(block).ok_or_else(|| {
88 ParserError::ParsingFailed("Failed to match tool call pattern".to_string())
89 })?;
90
91 let func_name = captures.get(1).map_or("", |m| m.as_str()).trim();
92 if func_name.is_empty() {
93 return Err(ParserError::ParsingFailed(
94 "Empty function name".to_string(),
95 ));
96 }
97
98 let json_args = captures.get(2).map_or("{}", |m| m.as_str()).trim();
99
100 let value = serde_json::from_str::<Value>(json_args)
101 .map_err(|e| ParserError::ParsingFailed(format!("Invalid JSON: {e}")))?;
102
103 let args = if value.is_object() {
104 value
105 } else {
106 serde_json::json!({ "value": value })
107 };
108
109 let arguments =
110 serde_json::to_string(&args).map_err(|e| ParserError::ParsingFailed(e.to_string()))?;
111
112 Ok(ToolCall {
113 function: FunctionCall {
114 name: func_name.to_string(),
115 arguments,
116 },
117 })
118 }
119}
120
121impl Default for DeepSeek31Parser {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127#[async_trait]
128impl ToolParser for DeepSeek31Parser {
129 async fn parse_complete(&self, text: &str) -> ParserResult<(String, Vec<ToolCall>)> {
130 if !self.has_tool_markers(text) {
131 return Ok((text.to_string(), vec![]));
132 }
133
134 let idx = text
135 .find("<|tool▁calls▁begin|>")
136 .ok_or_else(|| ParserError::ParsingFailed("tool call marker not found".to_string()))?;
137 let normal_text = text[..idx].to_string();
138
139 let mut tools = Vec::new();
140 for mat in self.tool_call_extractor.find_iter(text) {
141 match self.parse_tool_call(mat.as_str()) {
142 Ok(tool) => tools.push(tool),
143 Err(e) => {
144 tracing::debug!("Failed to parse tool call: {}", e);
145 continue;
146 }
147 }
148 }
149
150 if tools.is_empty() {
151 return Ok((text.to_string(), vec![]));
152 }
153
154 Ok((normal_text, tools))
155 }
156
157 async fn parse_incremental(
158 &mut self,
159 chunk: &str,
160 tools: &[Tool],
161 ) -> ParserResult<StreamingParseResult> {
162 self.buffer.push_str(chunk);
163 let current_text = self.buffer.clone();
164
165 let has_tool_call =
166 self.has_tool_markers(¤t_text) || current_text.contains("<|tool▁call▁begin|>");
167
168 if !has_tool_call {
169 let mut normal_text = std::mem::take(&mut self.buffer);
170 for end_token in [
171 "<|tool▁calls▁end|>",
172 "<|tool▁call▁end|>",
173 "<|end▁of▁sentence|>",
174 ] {
175 normal_text = normal_text.replace(end_token, "");
176 }
177 return Ok(StreamingParseResult {
178 normal_text,
179 calls: vec![],
180 });
181 }
182
183 let tool_indices = helpers::get_tool_indices(tools);
184 let mut calls: Vec<ToolCallItem> = Vec::new();
185
186 if let Some(captures) = self.partial_tool_call_regex.captures(¤t_text) {
187 let func_name = captures.get(1).map_or("", |m| m.as_str()).trim();
188 let func_args_raw = captures.get(2).map_or("", |m| m.as_str()).trim();
189
190 if !tool_indices.contains_key(func_name) {
191 tracing::debug!("Invalid tool name '{}' - skipping", func_name);
192 helpers::reset_current_tool_state(
193 &mut self.buffer,
194 &mut self.current_tool_name_sent,
195 &mut self.streamed_args_for_tool,
196 &self.prev_tool_call_arr,
197 );
198 return Ok(StreamingParseResult::default());
199 }
200
201 if self.current_tool_id == -1 {
202 self.current_tool_id = 0;
203 self.prev_tool_call_arr = Vec::new();
204 self.streamed_args_for_tool = vec![String::new()];
205 }
206
207 helpers::ensure_capacity(
208 self.current_tool_id,
209 &mut self.prev_tool_call_arr,
210 &mut self.streamed_args_for_tool,
211 );
212
213 if self.current_tool_name_sent {
214 let tool_id = self.current_tool_id as usize;
215 let last_sent = self
216 .streamed_args_for_tool
217 .get(tool_id)
218 .map(|s| s.as_str())
219 .unwrap_or("");
220
221 const END_MARKERS: [&str; 3] = [
226 "<|end▁of▁sentence|>",
227 "<|tool▁calls▁end|>",
228 "<|tool▁call▁end|>",
229 ];
230 let mut func_args_clean = func_args_raw.trim_end();
231 loop {
232 let before = func_args_clean;
233 for marker in END_MARKERS {
234 func_args_clean = func_args_clean.trim_end_matches(marker).trim_end();
235 }
236 if func_args_clean == before {
237 break;
238 }
239 }
240
241 let argument_diff = func_args_clean
242 .strip_prefix(last_sent)
243 .unwrap_or(func_args_clean);
244
245 if !argument_diff.is_empty() {
246 calls.push(ToolCallItem {
247 tool_index: tool_id,
248 name: None,
249 parameters: argument_diff.to_string(),
250 });
251 if tool_id < self.streamed_args_for_tool.len() {
252 self.streamed_args_for_tool[tool_id].push_str(argument_diff);
253 }
254 }
255
256 if helpers::is_complete_json(func_args_clean) {
257 if let Ok(parsed_args) = serde_json::from_str::<Value>(func_args_clean) {
258 let tool_id = self.current_tool_id as usize;
259 if tool_id < self.prev_tool_call_arr.len() {
260 if let Some(obj) = self.prev_tool_call_arr[tool_id].as_object_mut() {
261 obj.insert("arguments".to_string(), parsed_args);
262 }
263 }
264 }
265
266 if let Some(mat) = self.tool_call_end_pattern.find(¤t_text) {
267 self.buffer = current_text[mat.end()..].to_string();
268 } else {
269 self.buffer.clear();
270 }
271
272 let result = StreamingParseResult {
273 normal_text: String::new(),
274 calls,
275 };
276
277 self.current_tool_id += 1;
278 self.current_tool_name_sent = false;
279 return Ok(result);
280 }
281 } else {
282 calls.push(ToolCallItem {
283 tool_index: self.current_tool_id as usize,
284 name: Some(func_name.to_string()),
285 parameters: String::new(),
286 });
287 self.current_tool_name_sent = true;
288
289 let tool_id = self.current_tool_id as usize;
290 if self.prev_tool_call_arr.len() <= tool_id {
291 self.prev_tool_call_arr
292 .resize_with(tool_id + 1, || Value::Null);
293 }
294 self.prev_tool_call_arr[tool_id] = serde_json::json!({
295 "name": func_name,
296 "arguments": {},
297 });
298 }
299 }
300
301 Ok(StreamingParseResult {
302 normal_text: String::new(),
303 calls,
304 })
305 }
306
307 fn has_tool_markers(&self, text: &str) -> bool {
308 text.contains("<|tool▁calls▁begin|>")
309 }
310
311 fn get_unstreamed_tool_args(&self) -> Option<Vec<ToolCallItem>> {
312 helpers::get_unstreamed_args(&self.prev_tool_call_arr, &self.streamed_args_for_tool)
313 }
314
315 fn reset(&mut self) {
316 self.buffer.clear();
317 self.prev_tool_call_arr.clear();
318 self.current_tool_id = -1;
319 self.current_tool_name_sent = false;
320 self.streamed_args_for_tool.clear();
321 }
322}