1use 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 DeepSeekDsmlParser {
34 block_name: &'static str,
36 block_open: String,
38 block_close: String,
40
41 tool_call_complete_regex: Regex,
43 invoke_complete_regex: Regex,
45 parameter_complete_regex: Regex,
47 partial_parameter_regex: Regex,
49 invoke_regex: Regex,
51
52 buffer: String,
54 prev_tool_call_arr: Vec<Value>,
56 current_tool_id: i32,
58 current_tool_name_sent: bool,
60 streamed_args_for_tool: Vec<String>,
62}
63
64const DSML_PARAMETER_END_TAG: &str = "</|DSML|parameter>";
66const DSML_INVOKE_END_TAG: &str = "</|DSML|invoke>";
67
68const EOS_TOKEN: &str = "<|end▁of▁sentence|>";
71
72fn strip_dsml_trailing(s: &str, closing_tag: &str) -> String {
79 for (idx, _) in s.char_indices() {
80 if closing_tag.starts_with(&s[idx..]) {
81 return s[..idx].to_string();
82 }
83 }
84 s.to_string()
85}
86
87impl DeepSeekDsmlParser {
88 pub fn v32() -> Self {
90 Self::new("function_calls")
91 }
92
93 pub fn v4() -> Self {
95 Self::new("tool_calls")
96 }
97
98 pub fn block_name(&self) -> &'static str {
100 self.block_name
101 }
102
103 #[expect(
104 clippy::expect_used,
105 reason = "regex patterns are compile-time string literals"
106 )]
107 fn new(block_name: &'static str) -> Self {
108 let tool_call_complete_regex = Regex::new(&format!(
112 r"(?s)<|DSML|{block_name}>(.*?)</|DSML|{block_name}>"
113 ))
114 .expect("Valid regex pattern");
115
116 let invoke_complete_regex =
117 Regex::new(r#"(?s)<|DSML|invoke\s+name="([^"]+)"\s*>(.*?)</|DSML|invoke>"#)
118 .expect("Valid regex pattern");
119
120 let parameter_complete_regex = Regex::new(
121 r#"(?s)<|DSML|parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>(.*?)</|DSML|parameter>"#,
122 )
123 .expect("Valid regex pattern");
124
125 let partial_parameter_regex = Regex::new(
126 r#"(?s)<|DSML|parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>(.*)$"#,
127 )
128 .expect("Valid regex pattern");
129
130 let invoke_regex =
135 Regex::new(r#"(?s)<|DSML|invoke\s+name="([^"]*)"\s*>(.*?)(</|DSML|invoke>|$)"#)
136 .expect("Valid regex pattern");
137
138 Self {
139 block_name,
140 block_open: format!("<|DSML|{block_name}>"),
141 block_close: format!("</|DSML|{block_name}>"),
142 tool_call_complete_regex,
143 invoke_complete_regex,
144 parameter_complete_regex,
145 partial_parameter_regex,
146 invoke_regex,
147 buffer: String::new(),
148 prev_tool_call_arr: Vec::new(),
149 current_tool_id: -1,
150 current_tool_name_sent: false,
151 streamed_args_for_tool: Vec::new(),
152 }
153 }
154
155 fn parse_parameters_from_dsml(&self, invoke_content: &str, allow_partial: bool) -> String {
164 let trimmed = invoke_content.trim();
165
166 if trimmed.starts_with('{') {
168 if allow_partial {
169 return strip_dsml_trailing(trimmed, DSML_INVOKE_END_TAG).replace(EOS_TOKEN, "");
173 } else if trimmed.ends_with('}') {
174 return trimmed.to_string();
175 }
176 }
177
178 let mut params = serde_json::Map::new();
180
181 for cap in self.parameter_complete_regex.captures_iter(invoke_content) {
182 let name = cap.get(1).map_or("", |m| m.as_str());
183 let is_string = cap.get(2).map_or("true", |m| m.as_str());
184 let value = cap.get(3).map_or("", |m| m.as_str()).replace(EOS_TOKEN, "");
187
188 let json_value = if is_string == "true" {
189 Value::String(value.to_string())
190 } else {
191 serde_json::from_str(value.trim())
192 .unwrap_or_else(|_| Value::String(value.to_string()))
193 };
194
195 params.insert(name.to_string(), json_value);
196 }
197
198 if allow_partial {
202 let last_match_end = self
204 .parameter_complete_regex
205 .find_iter(invoke_content)
206 .last()
207 .map(|m| m.end())
208 .unwrap_or(0);
209
210 let remaining = &invoke_content[last_match_end..];
211 let cleaned = strip_dsml_trailing(remaining, DSML_PARAMETER_END_TAG);
212
213 if let Some(cap) = self.partial_parameter_regex.captures(&cleaned) {
214 let name = cap.get(1).map_or("", |m| m.as_str());
215 let is_string = cap.get(2).map_or("true", |m| m.as_str());
216 let value = cap.get(3).map_or("", |m| m.as_str()).replace(EOS_TOKEN, "");
220 let value = value.trim();
221
222 if !value.is_empty() && !params.contains_key(name) {
224 let json_value = if is_string == "true" {
225 Value::String(value.to_string())
226 } else {
227 serde_json::from_str(value)
228 .unwrap_or_else(|_| Value::String(value.to_string()))
229 };
230 params.insert(name.to_string(), json_value);
231 }
232 }
233 }
234
235 serde_json::to_string(&Value::Object(params)).unwrap_or_else(|_| "{}".to_string())
236 }
237
238 fn parse_invoke(&self, name: &str, content: &str) -> ToolCall {
240 let arguments = self.parse_parameters_from_dsml(content, false);
241
242 ToolCall {
243 function: FunctionCall {
244 name: name.trim().to_string(),
245 arguments,
246 },
247 }
248 }
249}
250
251#[async_trait]
254impl ToolParser for DeepSeekDsmlParser {
255 async fn parse_complete(&self, text: &str) -> ParserResult<(String, Vec<ToolCall>)> {
256 if !self.has_tool_markers(text) {
257 return Ok((text.to_string(), vec![]));
258 }
259
260 let idx = text
261 .find(self.block_open.as_str())
262 .ok_or_else(|| ParserError::ParsingFailed("DSML marker not found".to_string()))?;
263 let normal_text = text[..idx].trim_end().to_string();
264
265 let mut tools = Vec::new();
266
267 for fc_cap in self.tool_call_complete_regex.captures_iter(text) {
268 let fc_content = fc_cap.get(1).map_or("", |m| m.as_str());
269
270 for inv_cap in self.invoke_complete_regex.captures_iter(fc_content) {
271 let func_name = inv_cap.get(1).map_or("", |m| m.as_str());
272 let invoke_content = inv_cap.get(2).map_or("", |m| m.as_str());
273
274 tools.push(self.parse_invoke(func_name, invoke_content));
275 }
276 }
277
278 if tools.is_empty() {
279 return Ok((normal_text, vec![]));
280 }
281
282 Ok((normal_text, tools))
283 }
284
285 async fn parse_incremental(
286 &mut self,
287 chunk: &str,
288 tools: &[Tool],
289 ) -> ParserResult<StreamingParseResult> {
290 self.buffer.push_str(chunk);
291 let current_text = self.buffer.clone();
292
293 let has_dsml = current_text.contains("<|DSML|");
306 let has_partial_prefix = current_text.ends_with('<')
307 || current_text.ends_with("<|")
308 || current_text.ends_with("</")
309 || current_text.ends_with("</|");
310
311 if !has_dsml && !has_partial_prefix {
312 let mut normal_text = std::mem::take(&mut self.buffer);
313 for end_token in [
314 self.block_close.as_str(),
315 DSML_INVOKE_END_TAG,
316 DSML_PARAMETER_END_TAG,
317 EOS_TOKEN,
318 ] {
319 normal_text = normal_text.replace(end_token, "");
320 }
321 return Ok(StreamingParseResult {
322 normal_text,
323 calls: vec![],
324 });
325 }
326
327 if !has_dsml && has_partial_prefix {
329 return Ok(StreamingParseResult::default());
330 }
331
332 let tool_indices = helpers::get_tool_indices(tools);
333 let mut all_calls: Vec<ToolCallItem> = Vec::new();
334
335 loop {
337 let buf_snapshot = self.buffer.clone();
338 let invoke_match = self.invoke_regex.captures(&buf_snapshot);
339
340 let captures = match invoke_match {
341 Some(c) => c,
342 None => break,
343 };
344
345 let func_name = captures
346 .get(1)
347 .map_or(String::new(), |m| m.as_str().trim().to_string());
348 let invoke_content = captures
349 .get(2)
350 .map_or(String::new(), |m| m.as_str().to_string());
351 let is_complete = captures
352 .get(3)
353 .is_some_and(|m| m.as_str().contains("</|DSML|invoke>"));
354 let match_end = captures.get(0).map(|m| m.end());
355 drop(captures);
356
357 let name_invalid =
363 func_name.is_empty() || !tool_indices.contains_key(func_name.as_str());
364 if name_invalid {
365 tracing::debug!("Invalid tool name '{}' - skipping", func_name);
366 if is_complete {
367 if let Some(end) = match_end {
369 self.buffer = self.buffer[end..].to_string();
370 }
371 continue;
372 } else {
373 helpers::reset_current_tool_state(
376 &mut self.buffer,
377 &mut self.current_tool_name_sent,
378 &mut self.streamed_args_for_tool,
379 &self.prev_tool_call_arr,
380 );
381 return Ok(StreamingParseResult {
382 normal_text: String::new(),
383 calls: all_calls,
384 });
385 }
386 }
387
388 if self.current_tool_id == -1 {
390 self.current_tool_id = 0;
391 self.prev_tool_call_arr = Vec::new();
392 self.streamed_args_for_tool = vec![String::new()];
393 }
394
395 helpers::ensure_capacity(
396 self.current_tool_id,
397 &mut self.prev_tool_call_arr,
398 &mut self.streamed_args_for_tool,
399 );
400
401 if !self.current_tool_name_sent && !func_name.is_empty() {
403 all_calls.push(ToolCallItem {
404 tool_index: self.current_tool_id as usize,
405 name: Some(func_name.to_string()),
406 parameters: String::new(),
407 });
408 self.current_tool_name_sent = true;
409
410 let tool_id = self.current_tool_id as usize;
411 if self.prev_tool_call_arr.len() <= tool_id {
412 self.prev_tool_call_arr
413 .resize_with(tool_id + 1, || Value::Null);
414 }
415 self.prev_tool_call_arr[tool_id] = serde_json::json!({
416 "name": func_name,
417 "arguments": {},
418 });
419 }
420
421 let current_args = self.parse_parameters_from_dsml(&invoke_content, !is_complete);
423 let tool_id = self.current_tool_id as usize;
424
425 let sent_len = self
427 .streamed_args_for_tool
428 .get(tool_id)
429 .map(|s| s.len())
430 .unwrap_or(0);
431
432 let prev_args = if tool_id < self.prev_tool_call_arr.len() {
433 self.prev_tool_call_arr[tool_id]
434 .get("arguments")
435 .and_then(|v| v.as_str())
436 .map(|s| s.to_string())
437 } else {
438 None
439 };
440
441 let argument_diff = if is_complete {
442 if sent_len < current_args.len() {
443 Some(current_args[sent_len..].to_string())
444 } else {
445 Some(String::new())
446 }
447 } else if let Some(prev) = &prev_args {
448 if current_args == *prev {
449 None
450 } else {
451 let prefix = helpers::find_common_prefix(prev, ¤t_args);
452 if prefix.len() > sent_len {
453 Some(prefix[sent_len..].to_string())
454 } else {
455 None
456 }
457 }
458 } else if sent_len < current_args.len() && current_args != "{}" {
459 Some(current_args[sent_len..].to_string())
463 } else {
464 None
465 };
466
467 if let Some(diff) = argument_diff {
468 if !diff.is_empty() {
469 if tool_id < self.streamed_args_for_tool.len() {
470 self.streamed_args_for_tool[tool_id].push_str(&diff);
471 }
472 all_calls.push(ToolCallItem {
473 tool_index: tool_id,
474 name: None,
475 parameters: diff,
476 });
477 }
478 }
479
480 if tool_id < self.prev_tool_call_arr.len() {
482 self.prev_tool_call_arr[tool_id] = serde_json::json!({
483 "name": func_name,
484 "arguments": current_args,
485 });
486 }
487
488 if is_complete {
490 if let Some(end) = match_end {
491 self.buffer = self.buffer[end..].to_string();
492 } else {
493 self.buffer.clear();
494 }
495 self.current_tool_id += 1;
496 self.current_tool_name_sent = false;
497 continue;
498 } else {
499 break;
500 }
501 }
502
503 Ok(StreamingParseResult {
504 normal_text: String::new(),
505 calls: all_calls,
506 })
507 }
508
509 fn has_tool_markers(&self, text: &str) -> bool {
510 text.contains(self.block_open.as_str())
511 }
512
513 fn get_unstreamed_tool_args(&self) -> Option<Vec<ToolCallItem>> {
514 helpers::get_unstreamed_args(&self.prev_tool_call_arr, &self.streamed_args_for_tool)
515 }
516
517 fn reset(&mut self) {
518 self.buffer.clear();
519 self.prev_tool_call_arr.clear();
520 self.current_tool_id = -1;
521 self.current_tool_name_sent = false;
522 self.streamed_args_for_tool.clear();
523 }
524}