1use async_trait::async_trait;
31use openai_protocol::common::Tool;
32use serde_json::Value;
33
34use crate::{
35 errors::{ParserError, ParserResult},
36 parsers::helpers,
37 partial_json::PartialJson,
38 traits::ToolParser,
39 types::{FunctionCall, StreamingParseResult, ToolCall, ToolCallItem},
40};
41
42const START_ACTION: &str = "<|START_ACTION|>";
43const END_ACTION: &str = "<|END_ACTION|>";
44const START_RESPONSE: &str = "<|START_RESPONSE|>";
45const END_RESPONSE: &str = "<|END_RESPONSE|>";
46const START_TEXT: &str = "<|START_TEXT|>";
47const END_TEXT: &str = "<|END_TEXT|>";
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51enum ParseState {
52 Text,
54 InAction,
56}
57
58pub struct CohereParser {
63 state: ParseState,
65
66 partial_json: PartialJson,
68
69 buffer: String,
71
72 prev_tool_call_arr: Vec<Value>,
74
75 current_tool_id: i32,
77
78 current_tool_name_sent: bool,
80
81 streamed_args_for_tool: Vec<String>,
83}
84
85impl CohereParser {
86 pub fn new() -> Self {
88 Self {
89 state: ParseState::Text,
90 partial_json: PartialJson::default(),
91 buffer: String::new(),
92 prev_tool_call_arr: Vec::new(),
93 current_tool_id: -1,
94 current_tool_name_sent: false,
95 streamed_args_for_tool: Vec::new(),
96 }
97 }
98
99 fn clean_text(text: &str) -> String {
101 text.replace(START_RESPONSE, "")
102 .replace(END_RESPONSE, "")
103 .replace(START_TEXT, "")
104 .replace(END_TEXT, "")
105 }
106
107 fn convert_tool_call(json_str: &str) -> ParserResult<Vec<ToolCall>> {
109 let value: Value = serde_json::from_str(json_str.trim())
110 .map_err(|e| ParserError::ParsingFailed(format!("Invalid JSON: {e}")))?;
111
112 let tools = match value {
113 Value::Array(arr) => arr,
114 single => vec![single],
115 };
116
117 tools
118 .into_iter()
119 .filter_map(|tool| {
120 let name = tool
122 .get("tool_name")
123 .and_then(|v| v.as_str())
124 .or_else(|| tool.get("name").and_then(|v| v.as_str()))?;
125
126 let parameters = tool
128 .get("parameters")
129 .or_else(|| tool.get("arguments"))
130 .map(|v| v.to_string())
131 .unwrap_or_else(|| "{}".to_string());
132
133 Some(Ok(ToolCall {
134 function: FunctionCall {
135 name: name.to_string(),
136 arguments: parameters,
137 },
138 }))
139 })
140 .collect()
141 }
142
143 fn extract_action_json(text: &str) -> Option<(usize, &str, usize)> {
145 let start_idx = text.find(START_ACTION)?;
146 let json_start = start_idx + START_ACTION.len();
147 let end_offset = Self::find_end_action_outside_strings(&text[json_start..])?;
148 let json_str = &text[json_start..json_start + end_offset];
149 Some((
150 start_idx,
151 json_str,
152 json_start + end_offset + END_ACTION.len(),
153 ))
154 }
155
156 fn find_end_action_outside_strings(s: &str) -> Option<usize> {
161 let mut in_string = false;
162 let mut escape = false;
163 let bytes = s.as_bytes();
164 let needle = END_ACTION.as_bytes();
165 let mut i = 0;
166 while i + needle.len() <= bytes.len() {
167 if escape {
168 escape = false;
169 i += 1;
170 continue;
171 }
172 let b = bytes[i];
173 if in_string {
174 if b == b'\\' {
175 escape = true;
176 i += 1;
177 continue;
178 }
179 if b == b'"' {
180 in_string = false;
181 }
182 i += 1;
183 continue;
184 }
185 if b == b'"' {
186 in_string = true;
187 i += 1;
188 continue;
189 }
190 if Some(&b) == needle.first() && bytes[i..].starts_with(needle) {
191 return Some(i);
192 }
193 i += 1;
194 }
195 None
196 }
197}
198
199impl Default for CohereParser {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205#[async_trait]
206impl ToolParser for CohereParser {
207 async fn parse_complete(&self, text: &str) -> ParserResult<(String, Vec<ToolCall>)> {
208 if !self.has_tool_markers(text) {
210 let cleaned = Self::clean_text(text);
211 return Ok((cleaned.trim().to_string(), vec![]));
212 }
213
214 let mut normal_text = String::new();
215 let mut tool_calls = Vec::new();
216 let mut remaining = text;
217
218 while let Some((start_idx, json_str, end_idx)) = Self::extract_action_json(remaining) {
219 normal_text.push_str(&remaining[..start_idx]);
221
222 match Self::convert_tool_call(json_str) {
224 Ok(calls) => tool_calls.extend(calls),
225 Err(e) => {
226 tracing::debug!("Failed to parse Cohere tool call: {}", e);
227 }
228 }
229
230 remaining = &remaining[end_idx..];
231 }
232
233 normal_text.push_str(remaining);
235
236 let cleaned_text = Self::clean_text(&normal_text);
238
239 Ok((cleaned_text.trim().to_string(), tool_calls))
240 }
241
242 async fn parse_incremental(
243 &mut self,
244 chunk: &str,
245 tools: &[Tool],
246 ) -> ParserResult<StreamingParseResult> {
247 self.buffer.push_str(chunk);
248
249 match self.state {
250 ParseState::Text => {
251 let start_pos = self.buffer.find(START_ACTION);
253 if let Some(pos) = start_pos {
254 let text_before = Self::clean_text(&self.buffer[..pos]);
256
257 self.state = ParseState::InAction;
259 self.buffer.drain(..pos + START_ACTION.len());
260
261 return Ok(StreamingParseResult {
262 normal_text: text_before,
263 calls: vec![],
264 });
265 }
266
267 if helpers::ends_with_partial_token(&self.buffer, START_ACTION).is_some() {
269 return Ok(StreamingParseResult::default());
271 }
272
273 let cleaned = Self::clean_text(&self.buffer);
275 self.buffer.clear();
276 Ok(StreamingParseResult {
277 normal_text: cleaned,
278 calls: vec![],
279 })
280 }
281
282 ParseState::InAction => {
283 if let Some(pos) = Self::find_end_action_outside_strings(&self.buffer) {
285 let json_content = self.buffer[..pos].to_string();
287
288 let tool_indices = helpers::get_tool_indices(tools);
290
291 let mut temp_buffer = String::new();
293
294 let result = helpers::handle_json_tool_streaming(
296 &json_content,
297 0,
298 &mut self.partial_json,
299 &tool_indices,
300 &mut temp_buffer,
301 &mut self.current_tool_id,
302 &mut self.current_tool_name_sent,
303 &mut self.streamed_args_for_tool,
304 &mut self.prev_tool_call_arr,
305 )?;
306
307 self.buffer.drain(..pos + END_ACTION.len());
309 self.state = ParseState::Text;
310
311 return Ok(result);
312 }
313
314 Ok(StreamingParseResult::default())
318 }
319 }
320 }
321
322 fn has_tool_markers(&self, text: &str) -> bool {
323 text.contains(START_ACTION) || text.contains(END_ACTION)
324 }
325
326 fn get_unstreamed_tool_args(&self) -> Option<Vec<ToolCallItem>> {
327 helpers::get_unstreamed_args(&self.prev_tool_call_arr, &self.streamed_args_for_tool)
328 }
329
330 fn take_unstreamed_normal_text(&mut self) -> String {
331 let text = helpers::take_unstreamed_normal_text(&mut self.buffer, self.current_tool_id);
337 if text.is_empty() {
338 text
339 } else {
340 Self::clean_text(&text)
341 }
342 }
343
344 fn reset(&mut self) {
345 self.state = ParseState::Text;
346 helpers::reset_parser_state(
347 &mut self.buffer,
348 &mut self.prev_tool_call_arr,
349 &mut self.current_tool_id,
350 &mut self.current_tool_name_sent,
351 &mut self.streamed_args_for_tool,
352 );
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[tokio::test]
361 async fn test_single_tool_call() {
362 let parser = CohereParser::new();
363 let input = r#"<|START_RESPONSE|>Let me search for that.<|END_RESPONSE|>
364<|START_ACTION|>
365{"tool_name": "search", "parameters": {"query": "rust programming"}}
366<|END_ACTION|>"#;
367
368 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
369 assert_eq!(tools.len(), 1);
370 assert_eq!(normal_text, "Let me search for that.");
371 assert_eq!(tools[0].function.name, "search");
372
373 let args: Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
374 assert_eq!(args["query"], "rust programming");
375 }
376
377 #[tokio::test]
378 async fn test_multiple_tool_calls_array() {
379 let parser = CohereParser::new();
380 let input = r#"<|START_ACTION|>
381[
382 {"tool_name": "search", "parameters": {"query": "rust"}},
383 {"tool_name": "get_weather", "parameters": {"city": "Paris"}}
384]
385<|END_ACTION|>"#;
386
387 let (_, tools) = parser.parse_complete(input).await.unwrap();
388 assert_eq!(tools.len(), 2);
389 assert_eq!(tools[0].function.name, "search");
390 assert_eq!(tools[1].function.name, "get_weather");
391 }
392
393 #[tokio::test]
394 async fn test_no_tool_calls() {
395 let parser = CohereParser::new();
396 let input = "<|START_RESPONSE|>Hello, how can I help?<|END_RESPONSE|>";
397
398 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
399 assert_eq!(tools.len(), 0);
400 assert_eq!(normal_text, "Hello, how can I help?");
401 }
402
403 #[tokio::test]
404 async fn test_has_tool_markers() {
405 let parser = CohereParser::new();
406
407 assert!(parser.has_tool_markers("<|START_ACTION|>"));
408 assert!(parser.has_tool_markers("<|END_ACTION|>"));
409 assert!(parser.has_tool_markers("Some text <|START_ACTION|> more"));
410 assert!(!parser.has_tool_markers("Just plain text"));
411 assert!(!parser.has_tool_markers("[TOOL_CALLS]")); }
413
414 #[tokio::test]
415 async fn test_empty_parameters() {
416 let parser = CohereParser::new();
417 let input = r#"<|START_ACTION|>{"tool_name": "ping"}<|END_ACTION|>"#;
418
419 let (_, tools) = parser.parse_complete(input).await.unwrap();
420 assert_eq!(tools.len(), 1);
421 assert_eq!(tools[0].function.name, "ping");
422 assert_eq!(tools[0].function.arguments, "{}");
423 }
424
425 #[tokio::test]
426 async fn test_nested_json() {
427 let parser = CohereParser::new();
428 let input = r#"<|START_ACTION|>
429{"tool_name": "process", "parameters": {"config": {"nested": {"value": [1, 2, 3]}}}}
430<|END_ACTION|>"#;
431
432 let (_, tools) = parser.parse_complete(input).await.unwrap();
433 assert_eq!(tools.len(), 1);
434
435 let args: Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
436 assert_eq!(
437 args["config"]["nested"]["value"],
438 serde_json::json!([1, 2, 3])
439 );
440 }
441
442 #[tokio::test]
443 async fn test_text_markers_cleaned() {
444 let parser = CohereParser::new();
445 let input = r#"<|START_TEXT|>Some intro<|END_TEXT|>
446<|START_ACTION|>{"tool_name": "test", "parameters": {}}<|END_ACTION|>
447<|START_TEXT|>Conclusion<|END_TEXT|>"#;
448
449 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
450 assert_eq!(tools.len(), 1);
451 assert!(normal_text.contains("Some intro"));
452 assert!(normal_text.contains("Conclusion"));
453 assert!(!normal_text.contains("<|START_TEXT|>"));
454 assert!(!normal_text.contains("<|END_TEXT|>"));
455 }
456
457 #[tokio::test]
458 async fn test_malformed_json() {
459 let parser = CohereParser::new();
460 let input = r#"<|START_ACTION|>{"tool_name": invalid}<|END_ACTION|>"#;
461
462 let (_, tools) = parser.parse_complete(input).await.unwrap();
463 assert_eq!(tools.len(), 0);
465 }
466}