tool_parser/parsers/
mistral.rs1use async_trait::async_trait;
2use openai_protocol::common::Tool;
3use serde_json::Value;
4
5use crate::{
6 errors::{ParserError, ParserResult},
7 parsers::helpers,
8 partial_json::PartialJson,
9 traits::ToolParser,
10 types::{FunctionCall, StreamingParseResult, ToolCall},
11};
12
13pub struct MistralParser {
20 partial_json: PartialJson,
22
23 buffer: String,
25
26 prev_tool_call_arr: Vec<Value>,
28
29 current_tool_id: i32,
31
32 current_tool_name_sent: bool,
34
35 streamed_args_for_tool: Vec<String>,
37
38 bot_token: &'static str,
40 eot_token: &'static str,
41 tool_call_separator: &'static str,
42
43 array_closed: bool,
45}
46
47impl MistralParser {
48 pub fn build_structural_tag(tools: &[Tool], at_least_one: bool) -> Value {
56 let mut tags = Vec::new();
57 for tool in tools {
58 let name = &tool.function.name;
59 if name.is_empty() {
60 continue;
61 }
62 let schema = tool.function.parameters_schema();
63 let name_json = serde_json::to_string(name).unwrap_or_default();
64 tags.push(serde_json::json!({
65 "begin": format!("{{\"name\": {name_json}, \"arguments\": "),
66 "content": {
67 "type": "json_schema",
68 "json_schema": schema,
69 },
70 "end": "}",
71 }));
72 }
73 serde_json::json!({
74 "format": {
75 "type": "triggered_tags",
76 "triggers": ["[TOOL_CALLS]"],
77 "tags": [{
78 "begin": "[TOOL_CALLS] [",
79 "content": {
80 "type": "tags_with_separator",
81 "tags": tags,
82 "separator": ", ",
83 "at_least_one": at_least_one,
84 },
85 "end": "]",
86 }],
87 "at_least_one": at_least_one,
88 }
89 })
90 }
91
92 pub fn new() -> Self {
94 Self {
95 partial_json: PartialJson::default(),
96 buffer: String::new(),
97 prev_tool_call_arr: Vec::new(),
98 current_tool_id: -1,
99 current_tool_name_sent: false,
100 streamed_args_for_tool: Vec::new(),
101 bot_token: "[TOOL_CALLS] [",
102 eot_token: "]",
103 tool_call_separator: ", ",
104 array_closed: false,
105 }
106 }
107
108 fn extract_json_array_with_pos(text: &str) -> Option<(usize, &str)> {
109 const BOT_TOKEN: &str = "[TOOL_CALLS] [";
110
111 let start_idx = text.find(BOT_TOKEN)?;
113
114 let json_start = start_idx + BOT_TOKEN.len() - 1;
117
118 let mut bracket_count = 0;
119 let mut in_string = false;
120 let mut escape_next = false;
121
122 let bytes = text.as_bytes();
123
124 for i in json_start..text.len() {
125 let char = bytes[i];
126
127 if escape_next {
128 escape_next = false;
129 continue;
130 }
131
132 if char == b'\\' {
133 escape_next = true;
134 continue;
135 }
136
137 if char == b'"' && !escape_next {
138 in_string = !in_string;
139 continue;
140 }
141
142 if !in_string {
143 if char == b'[' {
144 bracket_count += 1;
145 } else if char == b']' {
146 bracket_count -= 1;
147 if bracket_count == 0 {
148 return Some((start_idx, &text[json_start..=i]));
150 }
151 }
152 }
153 }
154
155 None
157 }
158
159 fn parse_json_array(json_str: &str) -> ParserResult<Vec<ToolCall>> {
161 let value: Value = serde_json::from_str(json_str)
162 .map_err(|e| ParserError::ParsingFailed(e.to_string()))?;
163
164 let mut tools = Vec::new();
165
166 if let Value::Array(arr) = value {
167 for item in &arr {
168 if let Some(tool) = Self::parse_single_object(item)? {
169 tools.push(tool);
170 }
171 }
172 } else {
173 if let Some(tool) = Self::parse_single_object(&value)? {
175 tools.push(tool);
176 }
177 }
178
179 Ok(tools)
180 }
181
182 fn parse_single_object(obj: &Value) -> ParserResult<Option<ToolCall>> {
184 let name = obj.get("name").and_then(|v| v.as_str());
185
186 if let Some(name) = name {
187 let empty_obj = Value::Object(serde_json::Map::new());
189 let args = obj.get("arguments").unwrap_or(&empty_obj);
190
191 let arguments = serde_json::to_string(args)
193 .map_err(|e| ParserError::ParsingFailed(e.to_string()))?;
194
195 Ok(Some(ToolCall {
196 function: FunctionCall {
197 name: name.to_string(),
198 arguments,
199 },
200 }))
201 } else {
202 Ok(None)
203 }
204 }
205}
206
207impl Default for MistralParser {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213#[async_trait]
214impl ToolParser for MistralParser {
215 async fn parse_complete(&self, text: &str) -> ParserResult<(String, Vec<ToolCall>)> {
216 if !self.has_tool_markers(text) {
218 return Ok((text.to_string(), vec![]));
219 }
220
221 if let Some((start_idx, json_array)) = Self::extract_json_array_with_pos(text) {
223 let normal_text_before = if start_idx > 0 {
225 text[..start_idx].to_string()
226 } else {
227 String::new()
228 };
229
230 match Self::parse_json_array(json_array) {
231 Ok(tools) => Ok((normal_text_before, tools)),
232 Err(e) => {
233 tracing::debug!("Failed to parse tool call: {}", e);
235 Ok((text.to_string(), vec![]))
236 }
237 }
238 } else {
239 Ok((text.to_string(), vec![]))
241 }
242 }
243
244 async fn parse_incremental(
245 &mut self,
246 chunk: &str,
247 tools: &[Tool],
248 ) -> ParserResult<StreamingParseResult> {
249 self.buffer.push_str(chunk);
251 let current_text = &self.buffer.clone();
252
253 let has_tool_start = self.has_tool_markers(current_text)
255 || (self.current_tool_id > 0 && current_text.starts_with(self.tool_call_separator));
256
257 if !has_tool_start {
258 if helpers::ends_with_partial_token(&self.buffer, self.bot_token).is_none() {
260 let mut normal_text = self.buffer.clone();
261 self.buffer.clear();
262
263 if !self.array_closed
266 && self.current_tool_id > 0
267 && normal_text.starts_with(self.eot_token)
268 {
269 if let Some(stripped) = normal_text.strip_prefix(self.eot_token) {
271 normal_text = stripped.to_string();
272 }
273 self.array_closed = true;
274 }
275
276 return Ok(StreamingParseResult {
277 normal_text,
278 calls: vec![],
279 });
280 } else {
281 return Ok(StreamingParseResult::default());
283 }
284 }
285
286 let tool_indices = helpers::get_tool_indices(tools);
288
289 let start_idx = if let Some(pos) = current_text.find(self.bot_token) {
291 pos + self.bot_token.len()
292 } else if self.current_tool_id > 0 && current_text.starts_with(self.tool_call_separator) {
293 self.tool_call_separator.len()
294 } else {
295 0
296 };
297
298 helpers::handle_json_tool_streaming(
299 current_text,
300 start_idx,
301 &mut self.partial_json,
302 &tool_indices,
303 &mut self.buffer,
304 &mut self.current_tool_id,
305 &mut self.current_tool_name_sent,
306 &mut self.streamed_args_for_tool,
307 &mut self.prev_tool_call_arr,
308 )
309 }
310
311 fn has_tool_markers(&self, text: &str) -> bool {
312 text.contains("[TOOL_CALLS]")
313 }
314
315 fn get_unstreamed_tool_args(&self) -> Option<Vec<crate::types::ToolCallItem>> {
316 helpers::get_unstreamed_args(&self.prev_tool_call_arr, &self.streamed_args_for_tool)
317 }
318
319 fn take_unstreamed_normal_text(&mut self) -> String {
320 helpers::take_unstreamed_normal_text(&mut self.buffer, self.current_tool_id)
321 }
322
323 fn reset(&mut self) {
324 helpers::reset_parser_state(
325 &mut self.buffer,
326 &mut self.prev_tool_call_arr,
327 &mut self.current_tool_id,
328 &mut self.current_tool_name_sent,
329 &mut self.streamed_args_for_tool,
330 );
331 self.array_closed = false;
332 }
333}