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 reset(&mut self) {
331 self.state = ParseState::Text;
332 helpers::reset_parser_state(
333 &mut self.buffer,
334 &mut self.prev_tool_call_arr,
335 &mut self.current_tool_id,
336 &mut self.current_tool_name_sent,
337 &mut self.streamed_args_for_tool,
338 );
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[tokio::test]
347 async fn test_single_tool_call() {
348 let parser = CohereParser::new();
349 let input = r#"<|START_RESPONSE|>Let me search for that.<|END_RESPONSE|>
350<|START_ACTION|>
351{"tool_name": "search", "parameters": {"query": "rust programming"}}
352<|END_ACTION|>"#;
353
354 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
355 assert_eq!(tools.len(), 1);
356 assert_eq!(normal_text, "Let me search for that.");
357 assert_eq!(tools[0].function.name, "search");
358
359 let args: Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
360 assert_eq!(args["query"], "rust programming");
361 }
362
363 #[tokio::test]
364 async fn test_multiple_tool_calls_array() {
365 let parser = CohereParser::new();
366 let input = r#"<|START_ACTION|>
367[
368 {"tool_name": "search", "parameters": {"query": "rust"}},
369 {"tool_name": "get_weather", "parameters": {"city": "Paris"}}
370]
371<|END_ACTION|>"#;
372
373 let (_, tools) = parser.parse_complete(input).await.unwrap();
374 assert_eq!(tools.len(), 2);
375 assert_eq!(tools[0].function.name, "search");
376 assert_eq!(tools[1].function.name, "get_weather");
377 }
378
379 #[tokio::test]
380 async fn test_no_tool_calls() {
381 let parser = CohereParser::new();
382 let input = "<|START_RESPONSE|>Hello, how can I help?<|END_RESPONSE|>";
383
384 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
385 assert_eq!(tools.len(), 0);
386 assert_eq!(normal_text, "Hello, how can I help?");
387 }
388
389 #[tokio::test]
390 async fn test_has_tool_markers() {
391 let parser = CohereParser::new();
392
393 assert!(parser.has_tool_markers("<|START_ACTION|>"));
394 assert!(parser.has_tool_markers("<|END_ACTION|>"));
395 assert!(parser.has_tool_markers("Some text <|START_ACTION|> more"));
396 assert!(!parser.has_tool_markers("Just plain text"));
397 assert!(!parser.has_tool_markers("[TOOL_CALLS]")); }
399
400 #[tokio::test]
401 async fn test_empty_parameters() {
402 let parser = CohereParser::new();
403 let input = r#"<|START_ACTION|>{"tool_name": "ping"}<|END_ACTION|>"#;
404
405 let (_, tools) = parser.parse_complete(input).await.unwrap();
406 assert_eq!(tools.len(), 1);
407 assert_eq!(tools[0].function.name, "ping");
408 assert_eq!(tools[0].function.arguments, "{}");
409 }
410
411 #[tokio::test]
412 async fn test_nested_json() {
413 let parser = CohereParser::new();
414 let input = r#"<|START_ACTION|>
415{"tool_name": "process", "parameters": {"config": {"nested": {"value": [1, 2, 3]}}}}
416<|END_ACTION|>"#;
417
418 let (_, tools) = parser.parse_complete(input).await.unwrap();
419 assert_eq!(tools.len(), 1);
420
421 let args: Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
422 assert_eq!(
423 args["config"]["nested"]["value"],
424 serde_json::json!([1, 2, 3])
425 );
426 }
427
428 #[tokio::test]
429 async fn test_text_markers_cleaned() {
430 let parser = CohereParser::new();
431 let input = r#"<|START_TEXT|>Some intro<|END_TEXT|>
432<|START_ACTION|>{"tool_name": "test", "parameters": {}}<|END_ACTION|>
433<|START_TEXT|>Conclusion<|END_TEXT|>"#;
434
435 let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
436 assert_eq!(tools.len(), 1);
437 assert!(normal_text.contains("Some intro"));
438 assert!(normal_text.contains("Conclusion"));
439 assert!(!normal_text.contains("<|START_TEXT|>"));
440 assert!(!normal_text.contains("<|END_TEXT|>"));
441 }
442
443 #[tokio::test]
444 async fn test_malformed_json() {
445 let parser = CohereParser::new();
446 let input = r#"<|START_ACTION|>{"tool_name": invalid}<|END_ACTION|>"#;
447
448 let (_, tools) = parser.parse_complete(input).await.unwrap();
449 assert_eq!(tools.len(), 0);
451 }
452}