1use adk_core::Part;
22
23pub fn contains_tool_call_tag(text: &str) -> bool {
25 text.contains("<tool_call>")
26 || text.contains("<|tool_call>")
27 || text.contains("<|python_tag|>")
28 || text.contains("[TOOL_CALLS]")
29 || text.contains("<|tool▁call")
30 || text.contains("<|action_start|>")
31 || (text.contains("```json") && text.contains("\"name\""))
32}
33
34pub fn parse_text_tool_calls(text: &str) -> Option<Vec<Part>> {
42 if !contains_tool_call_tag(text) {
43 return None;
44 }
45
46 let mut parts = Vec::new();
47
48 if let Some(parsed) = parse_qwen_format(text, &mut parts) {
50 return Some(parsed);
51 }
52
53 if let Some(parsed) = parse_llama_format(text, &mut parts) {
55 return Some(parsed);
56 }
57
58 if let Some(parsed) = parse_mistral_nemo_format(text, &mut parts) {
60 return Some(parsed);
61 }
62
63 if let Some(parsed) = parse_deepseek_format(text) {
65 return Some(parsed);
66 }
67
68 if let Some(parsed) = parse_gemma4_format(text) {
70 return Some(parsed);
71 }
72
73 if let Some(parsed) = parse_action_tag_format(text) {
75 return Some(parsed);
76 }
77
78 None
79}
80
81fn parse_qwen_format(text: &str, _parts: &mut Vec<Part>) -> Option<Vec<Part>> {
87 let mut result = Vec::new();
88 let mut remaining = text;
89
90 loop {
91 let start = remaining.find("<tool_call>")?;
92
93 let before = remaining[..start].trim();
95 if !before.is_empty() {
96 result.push(Part::Text { text: before.to_string() });
97 }
98
99 let after_open = &remaining[start + "<tool_call>".len()..];
100 let end = after_open.find("</tool_call>")?;
101 let inner = after_open[..end].trim();
102
103 if let Some(part) = parse_json_tool_call(inner) {
105 result.push(part);
106 }
107 else if let Some(part) = parse_function_tag(inner) {
109 result.push(part);
110 } else {
111 result.push(Part::Text {
113 text: remaining[start..start + "<tool_call>".len() + end + "</tool_call>".len()]
114 .to_string(),
115 });
116 }
117
118 remaining = &after_open[end + "</tool_call>".len()..];
119 if remaining.trim().is_empty() || !remaining.contains("<tool_call>") {
120 let trailing = remaining.trim();
121 if !trailing.is_empty() {
122 result.push(Part::Text { text: trailing.to_string() });
123 }
124 break;
125 }
126 }
127
128 if result.is_empty() { None } else { Some(result) }
129}
130
131fn parse_function_tag(inner: &str) -> Option<Part> {
133 let func_start = inner.find("<function=")?;
134 let after_eq = &inner[func_start + "<function=".len()..];
135 let name_end = after_eq.find('>')?;
136 let name = after_eq[..name_end].trim().to_string();
137
138 let body_start = name_end + 1;
139 let func_end = after_eq.find("</function>")?;
140 let body = after_eq[body_start..func_end].trim();
141
142 let args = if body.is_empty() {
143 serde_json::json!({})
144 } else {
145 serde_json::from_str(body).unwrap_or_else(|_| serde_json::json!({}))
146 };
147
148 Some(Part::FunctionCall { name, args, id: None, thought_signature: None })
149}
150
151fn parse_json_tool_call(json_str: &str) -> Option<Part> {
154 let value: serde_json::Value = serde_json::from_str(json_str).ok()?;
155 let obj = value.as_object()?;
156
157 let name =
158 obj.get("name").or_else(|| obj.get("function")).and_then(|v| v.as_str())?.to_string();
159
160 let args = obj
161 .get("arguments")
162 .or_else(|| obj.get("parameters"))
163 .cloned()
164 .unwrap_or(serde_json::json!({}));
165
166 Some(Part::FunctionCall { name, args, id: None, thought_signature: None })
167}
168
169fn parse_llama_format(text: &str, _parts: &mut Vec<Part>) -> Option<Vec<Part>> {
171 let tag = "<|python_tag|>";
172 let start = text.find(tag)?;
173
174 let mut result = Vec::new();
175 let before = text[..start].trim();
176 if !before.is_empty() {
177 result.push(Part::Text { text: before.to_string() });
178 }
179
180 let json_str = text[start + tag.len()..].trim();
181 if let Some(part) = parse_json_tool_call(json_str) {
182 result.push(part);
183 } else {
184 return None;
185 }
186
187 Some(result)
188}
189
190fn parse_mistral_nemo_format(text: &str, _parts: &mut Vec<Part>) -> Option<Vec<Part>> {
192 let tag = "[TOOL_CALLS]";
193 let start = text.find(tag)?;
194
195 let mut result = Vec::new();
196 let before = text[..start].trim();
197 if !before.is_empty() {
198 result.push(Part::Text { text: before.to_string() });
199 }
200
201 let json_str = text[start + tag.len()..].trim();
202 let arr: Vec<serde_json::Value> = serde_json::from_str(json_str).ok()?;
204 for item in &arr {
205 let obj = item.as_object()?;
206 let name = obj.get("name").and_then(|v| v.as_str())?.to_string();
207 let args = obj
208 .get("arguments")
209 .or_else(|| obj.get("parameters"))
210 .cloned()
211 .unwrap_or(serde_json::json!({}));
212 result.push(Part::FunctionCall { name, args, id: None, thought_signature: None });
213 }
214
215 if result.is_empty() { None } else { Some(result) }
216}
217
218fn parse_deepseek_format(text: &str) -> Option<Vec<Part>> {
223 let fence_start = text.find("```json")?;
224 let json_start = fence_start + "```json".len();
225 let after_fence = &text[json_start..];
226 let fence_end = after_fence.find("```")?;
227 let json_str = after_fence[..fence_end].trim();
228
229 let mut result = Vec::new();
230 let before = text[..fence_start].trim();
231 if !before.is_empty() {
232 result.push(Part::Text { text: before.to_string() });
233 }
234
235 if let Some(part) = parse_json_tool_call(json_str) {
237 result.push(part);
238 } else if let Ok(arr) = serde_json::from_str::<Vec<serde_json::Value>>(json_str) {
239 for item in &arr {
240 if let Some(obj) = item.as_object() {
241 let name = obj
242 .get("name")
243 .or_else(|| obj.get("function"))
244 .and_then(|v| v.as_str())?
245 .to_string();
246 let args = obj
247 .get("arguments")
248 .or_else(|| obj.get("parameters"))
249 .cloned()
250 .unwrap_or(serde_json::json!({}));
251 result.push(Part::FunctionCall { name, args, id: None, thought_signature: None });
252 }
253 }
254 } else {
255 return None;
256 }
257
258 if result.is_empty() { None } else { Some(result) }
259}
260
261fn parse_gemma4_format(text: &str) -> Option<Vec<Part>> {
266 let mut result = Vec::new();
267 let mut remaining = text;
268
269 loop {
270 let start = remaining.find("<|tool_call>")?;
271 let before = remaining[..start].trim();
272 if !before.is_empty() {
273 result.push(Part::Text { text: before.to_string() });
274 }
275
276 let after_open = &remaining[start + "<|tool_call>".len()..];
277 let end = after_open.find("<tool_call|>")?;
278 let inner = after_open[..end].trim();
279
280 if let Some(call_body) = inner.strip_prefix("call:") {
282 let brace_start = call_body.find('{');
283 let name = if let Some(bs) = brace_start {
284 call_body[..bs].trim().to_string()
285 } else {
286 call_body.trim().to_string()
287 };
288
289 let args = if let Some(bs) = brace_start {
290 let args_raw = &call_body[bs..];
291 let json_str = args_raw.replace("<|\"|>", "\"");
293 serde_json::from_str(&json_str).unwrap_or(serde_json::json!({}))
294 } else {
295 serde_json::json!({})
296 };
297
298 result.push(Part::FunctionCall { name, args, id: None, thought_signature: None });
299 }
300
301 remaining = &after_open[end + "<tool_call|>".len()..];
302 if remaining.trim().is_empty() || !remaining.contains("<|tool_call>") {
303 let trailing = remaining.trim();
304 if !trailing.is_empty() {
305 result.push(Part::Text { text: trailing.to_string() });
306 }
307 break;
308 }
309 }
310
311 if result.is_empty() { None } else { Some(result) }
312}
313
314fn parse_action_tag_format(text: &str) -> Option<Vec<Part>> {
319 let start_tag = "<|action_start|>";
320 let end_tag = "<|action_end|>";
321
322 let start = text.find(start_tag)?;
323 let mut result = Vec::new();
324
325 let before = text[..start].trim();
326 if !before.is_empty() {
327 result.push(Part::Text { text: before.to_string() });
328 }
329
330 let after_open = &text[start + start_tag.len()..];
331 let end = after_open.find(end_tag)?;
332 let inner = after_open[..end].trim();
333
334 if let Some(part) = parse_json_tool_call(inner) {
335 result.push(part);
336 } else {
337 return None;
338 }
339
340 let trailing = after_open[end + end_tag.len()..].trim();
341 if !trailing.is_empty() {
342 result.push(Part::Text { text: trailing.to_string() });
343 }
344
345 Some(result)
346}
347
348const TOOL_CALL_PREFIXES: &[&str] = &[
352 "<tool_call",
353 "<|tool_call>",
354 "<|python_tag|",
355 "[TOOL_CALLS]",
356 "<|action_start|>",
357 "<\u{ff5c}\u{2581}tool", ];
359
360const MAX_BUFFER_SIZE: usize = 4096;
362
363pub struct ToolCallBuffer {
386 buffer: String,
387 buffering: bool,
388}
389
390pub enum BufferAction {
392 Emit(Vec<Part>),
394 Buffering,
396}
397
398impl ToolCallBuffer {
399 pub fn new() -> Self {
401 Self { buffer: String::new(), buffering: false }
402 }
403
404 pub fn push(&mut self, text: &str) -> BufferAction {
409 self.buffer.push_str(text);
410
411 if self.buffering {
412 if self.has_complete_tool_call() {
414 return self.try_parse_and_emit();
415 }
416 if self.buffer.len() > MAX_BUFFER_SIZE {
418 return self.flush_as_emit();
419 }
420 BufferAction::Buffering
421 } else {
422 if self.starts_tool_call_prefix() {
424 self.buffering = true;
425 if self.has_complete_tool_call() {
427 return self.try_parse_and_emit();
428 }
429 BufferAction::Buffering
430 } else if self.has_partial_prefix() {
431 self.buffering = true;
433 BufferAction::Buffering
434 } else {
435 self.flush_as_emit()
437 }
438 }
439 }
440
441 pub fn flush(&mut self) -> Vec<Part> {
444 if self.buffer.is_empty() {
445 return Vec::new();
446 }
447
448 if let Some(parts) = parse_text_tool_calls(&self.buffer) {
450 self.buffer.clear();
451 self.buffering = false;
452 return parts;
453 }
454
455 let text = std::mem::take(&mut self.buffer);
457 self.buffering = false;
458 if text.trim().is_empty() { Vec::new() } else { vec![Part::Text { text }] }
459 }
460
461 fn starts_tool_call_prefix(&self) -> bool {
462 TOOL_CALL_PREFIXES.iter().any(|prefix| self.buffer.contains(prefix))
463 }
464
465 fn has_partial_prefix(&self) -> bool {
466 let buf = &self.buffer;
468 for prefix in TOOL_CALL_PREFIXES {
469 let prefix_chars: Vec<char> = prefix.chars().collect();
471 for i in 1..prefix_chars.len() {
472 let partial: String = prefix_chars[..i].iter().collect();
473 if buf.ends_with(&partial) {
474 return true;
475 }
476 }
477 }
478 false
479 }
480
481 fn has_complete_tool_call(&self) -> bool {
482 (self.buffer.contains("<tool_call>") && self.buffer.contains("</tool_call>"))
483 || (self.buffer.contains("<|tool_call>") && self.buffer.contains("<tool_call|>"))
484 || (self.buffer.contains("<|python_tag|>")
485 && self.buffer.contains('\n')
486 && self.buffer.len() > "<|python_tag|>".len() + 5)
487 || (self.buffer.contains("[TOOL_CALLS]")
488 && self.buffer.contains(']')
489 && self.buffer.rfind(']') > self.buffer.find("[TOOL_CALLS]").map(|i| i + 12))
490 || (self.buffer.contains("```json") && self.buffer.matches("```").count() >= 2)
491 || (self.buffer.contains("<|action_start|>") && self.buffer.contains("<|action_end|>"))
492 }
493
494 fn try_parse_and_emit(&mut self) -> BufferAction {
495 if let Some(parts) = parse_text_tool_calls(&self.buffer) {
496 self.buffer.clear();
497 self.buffering = false;
498 BufferAction::Emit(parts)
499 } else {
500 self.flush_as_emit()
502 }
503 }
504
505 fn flush_as_emit(&mut self) -> BufferAction {
506 let text = std::mem::take(&mut self.buffer);
507 self.buffering = false;
508 if text.trim().is_empty() {
509 BufferAction::Emit(Vec::new())
510 } else {
511 BufferAction::Emit(vec![Part::Text { text }])
512 }
513 }
514}
515
516impl Default for ToolCallBuffer {
517 fn default() -> Self {
518 Self::new()
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn test_qwen_json_format() {
528 let text =
529 r#"<tool_call>{"name": "get_weather", "arguments": {"city": "Tokyo"}}</tool_call>"#;
530 let parts = parse_text_tool_calls(text).unwrap();
531 assert_eq!(parts.len(), 1);
532 match &parts[0] {
533 Part::FunctionCall { name, args, .. } => {
534 assert_eq!(name, "get_weather");
535 assert_eq!(args["city"], "Tokyo");
536 }
537 _ => panic!("expected FunctionCall"),
538 }
539 }
540
541 #[test]
542 fn test_qwen_function_tag_format() {
543 let text = r#"<tool_call><function=screenshot></function></tool_call>"#;
544 let parts = parse_text_tool_calls(text).unwrap();
545 assert_eq!(parts.len(), 1);
546 match &parts[0] {
547 Part::FunctionCall { name, args, .. } => {
548 assert_eq!(name, "screenshot");
549 assert_eq!(*args, serde_json::json!({}));
550 }
551 _ => panic!("expected FunctionCall"),
552 }
553 }
554
555 #[test]
556 fn test_qwen_function_tag_with_args() {
557 let text = r#"<tool_call><function=get_weather>{"city": "Tokyo"}</function></tool_call>"#;
558 let parts = parse_text_tool_calls(text).unwrap();
559 assert_eq!(parts.len(), 1);
560 match &parts[0] {
561 Part::FunctionCall { name, args, .. } => {
562 assert_eq!(name, "get_weather");
563 assert_eq!(args["city"], "Tokyo");
564 }
565 _ => panic!("expected FunctionCall"),
566 }
567 }
568
569 #[test]
570 fn test_text_before_tool_call() {
571 let text = r#"Let me check that for you.
572<tool_call>{"name": "search", "arguments": {"query": "rust"}}</tool_call>"#;
573 let parts = parse_text_tool_calls(text).unwrap();
574 assert_eq!(parts.len(), 2);
575 assert!(matches!(&parts[0], Part::Text { text } if text.contains("check that")));
576 assert!(matches!(&parts[1], Part::FunctionCall { name, .. } if name == "search"));
577 }
578
579 #[test]
580 fn test_multiple_tool_calls() {
581 let text = r#"<tool_call>{"name": "a", "arguments": {}}</tool_call>
582<tool_call>{"name": "b", "arguments": {"x": 1}}</tool_call>"#;
583 let parts = parse_text_tool_calls(text).unwrap();
584 assert_eq!(parts.len(), 2);
585 assert!(matches!(&parts[0], Part::FunctionCall { name, .. } if name == "a"));
586 assert!(matches!(&parts[1], Part::FunctionCall { name, .. } if name == "b"));
587 }
588
589 #[test]
590 fn test_llama_format() {
591 let text = r#"<|python_tag|>{"name": "get_weather", "parameters": {"city": "NYC"}}"#;
592 let parts = parse_text_tool_calls(text).unwrap();
593 assert_eq!(parts.len(), 1);
594 match &parts[0] {
595 Part::FunctionCall { name, args, .. } => {
596 assert_eq!(name, "get_weather");
597 assert_eq!(args["city"], "NYC");
598 }
599 _ => panic!("expected FunctionCall"),
600 }
601 }
602
603 #[test]
604 fn test_mistral_nemo_format() {
605 let text = r#"[TOOL_CALLS][{"name": "search", "arguments": {"q": "rust"}}]"#;
606 let parts = parse_text_tool_calls(text).unwrap();
607 assert_eq!(parts.len(), 1);
608 match &parts[0] {
609 Part::FunctionCall { name, args, .. } => {
610 assert_eq!(name, "search");
611 assert_eq!(args["q"], "rust");
612 }
613 _ => panic!("expected FunctionCall"),
614 }
615 }
616
617 #[test]
618 fn test_no_tool_call_returns_none() {
619 assert!(parse_text_tool_calls("Hello, how can I help?").is_none());
620 assert!(parse_text_tool_calls("").is_none());
621 }
622
623 #[test]
624 fn test_contains_tool_call_tag() {
625 assert!(contains_tool_call_tag("<tool_call>"));
626 assert!(contains_tool_call_tag("text <tool_call> more"));
627 assert!(contains_tool_call_tag("<|python_tag|>"));
628 assert!(contains_tool_call_tag("[TOOL_CALLS]"));
629 assert!(!contains_tool_call_tag("normal text"));
630 }
631
632 #[test]
635 fn test_buffer_normal_text_emits_immediately() {
636 let mut buf = ToolCallBuffer::new();
637 match buf.push("Hello world") {
638 BufferAction::Emit(parts) => {
639 assert_eq!(parts.len(), 1);
640 assert!(matches!(&parts[0], Part::Text { text } if text == "Hello world"));
641 }
642 BufferAction::Buffering => panic!("should emit immediately"),
643 }
644 }
645
646 #[test]
647 fn test_buffer_complete_tool_call_in_one_chunk() {
648 let mut buf = ToolCallBuffer::new();
649 let text = r#"<tool_call>{"name": "search", "arguments": {"q": "rust"}}</tool_call>"#;
650 match buf.push(text) {
651 BufferAction::Emit(parts) => {
652 assert_eq!(parts.len(), 1);
653 assert!(matches!(&parts[0], Part::FunctionCall { name, .. } if name == "search"));
654 }
655 BufferAction::Buffering => panic!("should emit parsed tool call"),
656 }
657 }
658
659 #[test]
660 fn test_buffer_tool_call_split_across_chunks() {
661 let mut buf = ToolCallBuffer::new();
662
663 assert!(matches!(buf.push("<tool_call>"), BufferAction::Buffering));
665
666 assert!(matches!(
668 buf.push(r#"{"name": "get_weather", "arguments": {"city": "Tokyo"}}"#),
669 BufferAction::Buffering
670 ));
671
672 match buf.push("</tool_call>") {
674 BufferAction::Emit(parts) => {
675 assert_eq!(parts.len(), 1);
676 assert!(
677 matches!(&parts[0], Part::FunctionCall { name, .. } if name == "get_weather")
678 );
679 }
680 BufferAction::Buffering => panic!("should emit after closing tag"),
681 }
682 }
683
684 #[test]
685 fn test_buffer_text_then_tool_call() {
686 let mut buf = ToolCallBuffer::new();
687
688 match buf.push("Let me check. ") {
690 BufferAction::Emit(parts) => {
691 assert_eq!(parts.len(), 1);
692 assert!(matches!(&parts[0], Part::Text { .. }));
693 }
694 BufferAction::Buffering => panic!("should emit text"),
695 }
696
697 let tc = r#"<tool_call>{"name": "search", "arguments": {}}</tool_call>"#;
699 match buf.push(tc) {
700 BufferAction::Emit(parts) => {
701 assert_eq!(parts.len(), 1);
702 assert!(matches!(&parts[0], Part::FunctionCall { name, .. } if name == "search"));
703 }
704 BufferAction::Buffering => panic!("should emit tool call"),
705 }
706 }
707
708 #[test]
709 fn test_buffer_flush_incomplete_as_text() {
710 let mut buf = ToolCallBuffer::new();
711 assert!(matches!(buf.push("<tool_call>partial"), BufferAction::Buffering));
712
713 let parts = buf.flush();
715 assert_eq!(parts.len(), 1);
716 assert!(matches!(&parts[0], Part::Text { text } if text.contains("<tool_call>")));
717 }
718
719 #[test]
720 fn test_buffer_flush_empty() {
721 let mut buf = ToolCallBuffer::new();
722 let parts = buf.flush();
723 assert!(parts.is_empty());
724 }
725
726 #[test]
727 fn test_buffer_partial_prefix_detection() {
728 let mut buf = ToolCallBuffer::new();
729 assert!(matches!(buf.push("<tool"), BufferAction::Buffering));
731 assert!(matches!(buf.push("_call>"), BufferAction::Buffering));
733 match buf.push(r#"{"name":"x","arguments":{}}</tool_call>"#) {
735 BufferAction::Emit(parts) => {
736 assert_eq!(parts.len(), 1);
737 assert!(matches!(&parts[0], Part::FunctionCall { name, .. } if name == "x"));
738 }
739 BufferAction::Buffering => panic!("should emit"),
740 }
741 }
742
743 #[test]
746 fn test_deepseek_json_fence() {
747 let text = "```json\n{\"name\": \"search\", \"arguments\": {\"q\": \"rust\"}}\n```";
748 let parts = parse_text_tool_calls(text).unwrap();
749 assert_eq!(parts.len(), 1);
750 match &parts[0] {
751 Part::FunctionCall { name, args, .. } => {
752 assert_eq!(name, "search");
753 assert_eq!(args["q"], "rust");
754 }
755 _ => panic!("expected FunctionCall"),
756 }
757 }
758
759 #[test]
760 fn test_deepseek_with_text_before() {
761 let text = "I'll search for that.\n```json\n{\"name\": \"search\", \"arguments\": {\"q\": \"rust\"}}\n```\n<|tool▁call▁end|>";
762 let parts = parse_text_tool_calls(text).unwrap();
763 assert!(!parts.is_empty());
764 let has_fn_call =
765 parts.iter().any(|p| matches!(p, Part::FunctionCall { name, .. } if name == "search"));
766 assert!(has_fn_call);
767 }
768
769 #[test]
772 fn test_gemma4_simple() {
773 let text = "<|tool_call>call:get_weather{}<tool_call|>";
774 let parts = parse_text_tool_calls(text).unwrap();
775 assert_eq!(parts.len(), 1);
776 match &parts[0] {
777 Part::FunctionCall { name, .. } => assert_eq!(name, "get_weather"),
778 _ => panic!("expected FunctionCall"),
779 }
780 }
781
782 #[test]
783 fn test_gemma4_with_args() {
784 let text = "<|tool_call>call:get_weather{<|\"|>city<|\"|>:<|\"|>Tokyo<|\"|>}<tool_call|>";
785 let parts = parse_text_tool_calls(text).unwrap();
786 assert_eq!(parts.len(), 1);
787 match &parts[0] {
788 Part::FunctionCall { name, args, .. } => {
789 assert_eq!(name, "get_weather");
790 assert_eq!(args["city"], "Tokyo");
791 }
792 _ => panic!("expected FunctionCall"),
793 }
794 }
795
796 #[test]
799 fn test_action_tags() {
800 let text = "<|action_start|>{\"name\": \"search\", \"arguments\": {\"q\": \"rust\"}}<|action_end|>";
801 let parts = parse_text_tool_calls(text).unwrap();
802 assert_eq!(parts.len(), 1);
803 match &parts[0] {
804 Part::FunctionCall { name, args, .. } => {
805 assert_eq!(name, "search");
806 assert_eq!(args["q"], "rust");
807 }
808 _ => panic!("expected FunctionCall"),
809 }
810 }
811
812 #[test]
813 fn test_action_tags_with_surrounding_text() {
814 let text = "Let me look that up. <|action_start|>{\"name\": \"search\", \"arguments\": {}}<|action_end|> Done.";
815 let parts = parse_text_tool_calls(text).unwrap();
816 assert!(parts.len() >= 2); let has_fn_call =
818 parts.iter().any(|p| matches!(p, Part::FunctionCall { name, .. } if name == "search"));
819 assert!(has_fn_call);
820 }
821}