1use super::config::{ToolCallConfig, ToolCallParserType};
5use super::harmony::{
6 detect_tool_call_start_harmony, find_tool_call_end_position_harmony,
7 parse_tool_calls_harmony_complete,
8};
9use super::json::{
10 detect_tool_call_start_json, find_tool_call_end_position_json, try_tool_call_parse_json,
11};
12use super::pythonic::{
13 detect_tool_call_start_pythonic, find_tool_call_end_position_pythonic,
14 try_tool_call_parse_pythonic,
15};
16use super::response::ToolCallResponse;
17use std::collections::HashMap;
18use std::sync::OnceLock;
19
20static PARSER_MAP: OnceLock<HashMap<&'static str, ToolCallConfig>> = OnceLock::new();
21
22pub fn get_tool_parser_map() -> &'static HashMap<&'static str, ToolCallConfig> {
24 PARSER_MAP.get_or_init(|| {
25 let mut map = HashMap::new();
26 map.insert("hermes", ToolCallConfig::hermes());
27 map.insert("nemotron_deci", ToolCallConfig::nemotron_deci());
28 map.insert("llama3_json", ToolCallConfig::llama3_json());
29 map.insert("mistral", ToolCallConfig::mistral());
30 map.insert("phi4", ToolCallConfig::phi4());
31 map.insert("pythonic", ToolCallConfig::pythonic());
32 map.insert("harmony", ToolCallConfig::harmony());
33 map.insert("deepseek_v3_1", ToolCallConfig::deepseek_v3_1());
34 map.insert("default", ToolCallConfig::default());
35 map
36 })
37}
38
39pub fn get_available_tool_parsers() -> Vec<&'static str> {
40 get_tool_parser_map().keys().copied().collect()
41}
42
43pub async fn try_tool_call_parse(
44 message: &str,
45 config: &ToolCallConfig,
46) -> anyhow::Result<(Vec<ToolCallResponse>, Option<String>)> {
47 match config.format {
49 ToolCallParserType::Json => {
50 let (results, normal_content) = try_tool_call_parse_json(message, &config.json)?;
51 Ok((results, normal_content))
52 }
53 ToolCallParserType::Harmony => {
54 let (results, normal_content) =
55 parse_tool_calls_harmony_complete(message, &config.json).await?;
56 Ok((results, normal_content))
57 }
58 ToolCallParserType::Pythonic => {
59 let (results, normal_content) = try_tool_call_parse_pythonic(message)?;
60 Ok((results, normal_content))
61 }
62 ToolCallParserType::Typescript => {
63 anyhow::bail!("Typescript parser not implemented");
64 }
65 ToolCallParserType::Xml => {
66 anyhow::bail!("Xml parser not implemented");
67 }
68 }
69}
70
71pub async fn detect_and_parse_tool_call(
73 message: &str,
74 parser_str: Option<&str>,
75) -> anyhow::Result<(Vec<ToolCallResponse>, Option<String>)> {
76 let parser_map = get_tool_parser_map();
78
79 let parser_key = match parser_str {
81 Some(s) if !s.is_empty() => s,
82 _ => "default", };
84
85 match parser_map.get(parser_key) {
86 Some(config) => {
87 let (results, normal_content) = try_tool_call_parse(message, config).await?;
88 Ok((results, normal_content))
89 }
90 None => anyhow::bail!(
91 "Parser '{}' is not implemented. Available parsers: {:?}",
92 parser_key,
93 get_available_tool_parsers()
94 ),
95 }
96}
97
98pub fn detect_tool_call_start(chunk: &str, parser_str: Option<&str>) -> anyhow::Result<bool> {
99 let parser_map = get_tool_parser_map();
100 let parser_key = match parser_str {
101 Some(s) if !s.is_empty() => s,
102 _ => "default", };
104
105 match parser_map.get(parser_key) {
106 Some(config) => match config.format {
107 ToolCallParserType::Json => Ok(detect_tool_call_start_json(chunk, &config.json)),
108 ToolCallParserType::Harmony => {
109 Ok(detect_tool_call_start_harmony(chunk, &config.json, false))
110 }
111 ToolCallParserType::Pythonic => Ok(detect_tool_call_start_pythonic(chunk)),
112 ToolCallParserType::Typescript => {
113 anyhow::bail!("Typescript parser not implemented");
114 }
115 ToolCallParserType::Xml => {
116 anyhow::bail!("Xml parser not implemented");
117 }
118 },
119 None => anyhow::bail!(
120 "Parser '{}' is not implemented. Available parsers: {:?}",
121 parser_key,
122 get_available_tool_parsers()
123 ),
124 }
125}
126
127pub fn find_tool_call_end_position(chunk: &str, parser_str: Option<&str>) -> usize {
128 let parser_map = get_tool_parser_map();
129 let parser_key = match parser_str {
130 Some(s) if !s.is_empty() => s,
131 _ => "default",
132 };
133
134 match parser_map.get(parser_key) {
135 Some(config) => match config.format {
136 ToolCallParserType::Json => {
137 let effective_parser = if parser_key == "default" {
139 "nemotron_deci"
140 } else {
141 parser_key
142 };
143 find_tool_call_end_position_json(chunk, effective_parser, &config.json)
144 }
145 ToolCallParserType::Harmony => find_tool_call_end_position_harmony(chunk, &config.json),
146 ToolCallParserType::Pythonic => find_tool_call_end_position_pythonic(chunk),
147 ToolCallParserType::Typescript => {
148 chunk.len()
150 }
151 ToolCallParserType::Xml => {
152 chunk.len()
154 }
155 },
156 None => {
157 chunk.len()
159 }
160 }
161}
162#[cfg(test)]
165mod tests {
166 use super::super::config::JsonParserConfig;
167 use super::*;
168
169 fn extract_name_and_args(call: ToolCallResponse) -> (String, serde_json::Value) {
170 let args: serde_json::Value = serde_json::from_str(&call.function.arguments).unwrap();
171 (call.function.name, args)
172 }
173
174 #[test]
175 fn test_get_available_tool_parsers() {
176 let parsers = get_available_tool_parsers();
177 assert!(!parsers.is_empty());
178 let available_parsers = [
180 "hermes",
181 "llama3_json",
182 "harmony",
183 "nemotron_deci",
184 "mistral",
185 "phi4",
186 "default",
187 "pythonic",
188 "deepseek_v3_1",
189 ];
190 for parser in available_parsers {
191 assert!(parsers.contains(&parser));
192 }
193 }
194
195 #[tokio::test]
196 async fn parses_single_parameters_object() {
197 let input = r#"{ "name": "hello", "parameters": { "x": 1, "y": 2 } }"#;
198 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
199 .await
200 .unwrap();
201 assert_eq!(content, Some("".to_string()));
202 assert!(!result.is_empty());
203 assert_eq!(result.len(), 1);
204 let (name, args) = extract_name_and_args(result[0].clone());
205 assert_eq!(name, "hello");
206 assert_eq!(args["x"], 1);
207 assert_eq!(args["y"], 2);
208 }
209
210 #[tokio::test]
211 async fn parses_single_arguments_object() {
212 let input = r#"{ "name": "world", "arguments": { "a": "abc", "b": 42 } }"#;
213 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
214 .await
215 .unwrap();
216 assert_eq!(content, Some("".to_string()));
217 assert!(!result.is_empty());
218 assert_eq!(result.len(), 1);
219 let (name, args) = extract_name_and_args(result[0].clone());
220 assert_eq!(name, "world");
221 assert_eq!(args["a"], "abc");
222 assert_eq!(args["b"], 42);
223 }
224
225 #[tokio::test]
226 async fn parses_vec_of_parameters() {
227 let input = r#"[{ "name": "first", "parameters": { "a": 1 } }, { "name": "second", "parameters": { "b": 2 } }]"#;
228 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
229 .await
230 .unwrap();
231 assert_eq!(content, Some("".to_string()));
232 assert!(!result.is_empty());
233 assert_eq!(result.len(), 2);
234 let (name, args) = extract_name_and_args(result[0].clone());
235 assert_eq!(name, "first");
236 assert_eq!(args["a"], 1);
237 let (name, args) = extract_name_and_args(result[1].clone());
238 assert_eq!(name, "second");
239 assert_eq!(args["b"], 2);
240 }
241
242 #[tokio::test]
243 async fn parses_vec_of_arguments() {
244 let input = r#"[{ "name": "alpha", "arguments": { "a": "x" } }, { "name": "omega", "arguments": { "z": "y" } }]"#;
245 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
246 .await
247 .unwrap();
248 assert_eq!(content, Some("".to_string()));
249 assert!(!result.is_empty());
250 assert_eq!(result.len(), 2);
251 let (name, args) = extract_name_and_args(result[0].clone());
252 assert_eq!(name, "alpha");
253 assert_eq!(args["a"], "x");
254 let (name, args) = extract_name_and_args(result[1].clone());
255 assert_eq!(name, "omega");
256 assert_eq!(args["z"], "y");
257 }
258
259 #[tokio::test]
260 async fn parses_toolcall_wrapped_payload() {
261 let input =
262 r#"<TOOLCALL>[{ "name": "wrapped", "parameters": { "foo": "bar" } }]</TOOLCALL>"#;
263 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
264 .await
265 .unwrap();
266 assert_eq!(content, Some("".to_string()));
267 assert!(!result.is_empty());
268 assert_eq!(result.len(), 1);
269 let (name, args) = extract_name_and_args(result[0].clone());
270 assert_eq!(name, "wrapped");
271 assert_eq!(args["foo"], "bar");
272 }
273
274 #[tokio::test]
275 async fn parses_python_tag_prefixed_payload() {
276 let input = r#"<|python_tag|>{ "name": "pyfunc", "arguments": { "k": "v" } }"#;
277 let (result, content) = try_tool_call_parse(
278 input,
279 &ToolCallConfig {
280 format: ToolCallParserType::Json,
281 json: JsonParserConfig {
282 tool_call_start_tokens: vec!["<|python_tag|>".to_string()],
283 tool_call_end_tokens: vec!["".to_string()],
284 ..Default::default()
285 },
286 },
287 )
288 .await
289 .unwrap();
290 assert_eq!(content, Some("".to_string()));
291 assert!(!result.is_empty());
292 assert_eq!(result.len(), 1);
293 let (name, args) = extract_name_and_args(result[0].clone());
294 assert_eq!(name, "pyfunc");
295 assert_eq!(args["k"], "v");
296 }
297
298 #[tokio::test]
299 async fn returns_none_on_invalid_input() {
300 let input = r#"not even json"#;
301 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
302 .await
303 .unwrap();
304 assert_eq!(content, Some("not even json".to_string()));
305 assert!(result.is_empty());
306 }
307
308 #[tokio::test]
309 async fn returns_none_on_valid_json_wrong_shape() {
310 let input = r#"{ "foo": "bar" }"#;
311 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
312 .await
313 .unwrap();
314 assert_eq!(content, Some("{ \"foo\": \"bar\" }".to_string()));
315 assert!(result.is_empty());
316 }
317
318 #[tokio::test]
320 async fn test_nvidia_llama3_nemotron_super_49b_simple() {
321 let input = r#"<think>
322Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
323</think>
324
325<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
326 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
327 .await
328 .unwrap();
329 assert!(!result.is_empty());
330 assert_eq!(result.len(), 1);
331 assert_eq!(content, Some("<think>\nOkay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.\n</think>".to_string()));
332 let (name, args) = extract_name_and_args(result[0].clone());
333 assert_eq!(name, "get_weather");
334 assert_eq!(args["location"], "San Francisco, CA");
335 assert_eq!(args["unit"], "fahrenheit");
336 }
337
338 #[tokio::test]
339 async fn test_nvidia_llama3_nemotron_super_49b_simple_with_no_think() {
340 let input = r#"<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
341 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
342 .await
343 .unwrap();
344 assert!(!result.is_empty());
345 assert_eq!(result.len(), 1);
346 assert_eq!(content, Some("".to_string()));
347 let (name, args) = extract_name_and_args(result[0].clone());
348 assert_eq!(name, "get_weather");
349 assert_eq!(args["location"], "San Francisco, CA");
350 assert_eq!(args["unit"], "fahrenheit");
351 }
352
353 #[tokio::test]
354 async fn test_nvidia_llama3_nemotron_super_49b_with_function_array() {
355 let input = r#"<think>
356Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
357</think>
358
359<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]</TOOLCALL>"#;
360 let config = ToolCallConfig::nemotron_deci();
361 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
362 assert_eq!(content, Some("<think>\nOkay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.\n</think>".to_string()));
363 assert!(!result.is_empty());
364 assert_eq!(result.len(), 2);
365 let (name, args) = extract_name_and_args(result[0].clone());
366 assert_eq!(name, "get_weather");
367 assert_eq!(args["location"], "San Francisco, CA");
368 assert_eq!(args["unit"], "fahrenheit");
369 let (name, args) = extract_name_and_args(result[1].clone());
370 assert_eq!(name, "get_weather");
371 assert_eq!(args["location"], "New York, NY");
372 assert_eq!(args["unit"], "fahrenheit");
373 }
374
375 #[tokio::test]
376 async fn test_nvidia_llama3_nemotron_super_49b_with_function_array_with_new_lines() {
377 let input = r#"<think>
378Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
379</think>
380
381<TOOLCALL>
382[{"name": "get_weather",
383 "arguments": {"location": "San Francisco, CA",
384 "unit": "fahrenheit"}},
385 {"name": "get_weather",
386 "arguments":
387 {"location": "New York, NY",
388 "unit": "fahrenheit"}}]
389 </TOOLCALL>
390 "#;
391 let config = ToolCallConfig::nemotron_deci();
392 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
393 assert_eq!(content, Some("<think>\nOkay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.\n</think>".to_string()));
394 assert!(!result.is_empty());
395 assert_eq!(result.len(), 2);
396 let (name, args) = extract_name_and_args(result[0].clone());
397 assert_eq!(name, "get_weather");
398 assert_eq!(args["location"], "San Francisco, CA");
399 assert_eq!(args["unit"], "fahrenheit");
400 let (name, args) = extract_name_and_args(result[1].clone());
401 assert_eq!(name, "get_weather");
402 assert_eq!(args["location"], "New York, NY");
403 assert_eq!(args["unit"], "fahrenheit");
404 }
405
406 #[tokio::test]
407 async fn test_qwen_qwq_32b_simple() {
408 let input = r#"<tool_call>
409{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
410</tool_call>"#;
411 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
412 .await
413 .unwrap();
414 assert_eq!(content, Some("".to_string()));
415 assert!(!result.is_empty());
416 assert_eq!(result.len(), 1);
417 let (name, args) = extract_name_and_args(result[0].clone());
418 assert_eq!(name, "get_weather");
419 assert_eq!(args["location"], "San Francisco, CA");
420 assert_eq!(args["unit"], "fahrenheit");
421 }
422
423 #[tokio::test]
424 async fn test_qwen_qwq_32b_simple_with_normal_text() {
425 let input = r#"Hey How are you? <tool_call>
426{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
427</tool_call>"#;
428 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
429 .await
430 .unwrap();
431 assert_eq!(content, Some("Hey How are you?".to_string()));
432 assert!(!result.is_empty());
433 assert_eq!(result.len(), 1);
434 }
435
436 #[tokio::test]
437 async fn test_nousresearch_hermes3_llama31_8b_simple() {
438 let input = r#"<tool_call>
439{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
440</tool_call>"#;
441 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
442 .await
443 .unwrap();
444 assert_eq!(content, Some("".to_string()));
445 assert!(!result.is_empty());
446 assert_eq!(result.len(), 1);
447 let (name, args) = extract_name_and_args(result[0].clone());
448 assert_eq!(name, "get_weather");
449 assert_eq!(args["location"], "San Francisco, CA");
450 assert_eq!(args["unit"], "fahrenheit");
451 }
452
453 #[tokio::test]
454 async fn test_qwen_qwq_32b_multiple_tool_calls() {
455 let input = r#"<tool_call>
456{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
457</tool_call>
458<tool_call>
459{"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}
460</tool_call>
461"#;
462 let config = ToolCallConfig::hermes();
463 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
464 assert_eq!(content, Some("".to_string()));
465 assert!(!result.is_empty());
466 assert_eq!(result.len(), 2);
467 let (name, args) = extract_name_and_args(result[0].clone());
468 assert_eq!(name, "get_weather");
469 assert_eq!(args["location"], "San Francisco, CA");
470 assert_eq!(args["unit"], "fahrenheit");
471 let (name, args) = extract_name_and_args(result[1].clone());
472 assert_eq!(name, "get_weather");
473 assert_eq!(args["location"], "New York, NY");
474 assert_eq!(args["unit"], "fahrenheit");
475 }
476
477 #[tokio::test]
478 async fn test_qwen_qwq_32b_multiple_tool_calls_with_normal_text() {
479 let input = r#"Hey How are you? <tool_call>
480{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
481</tool_call>
482<tool_call>
483{"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}
484</tool_call>
485"#;
486 let config = ToolCallConfig::hermes();
487 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
488 assert_eq!(content, Some("Hey How are you?".to_string()));
489 assert!(!result.is_empty());
490 assert_eq!(result.len(), 2);
491 let (name, args) = extract_name_and_args(result[0].clone());
492 assert_eq!(name, "get_weather");
493 assert_eq!(args["location"], "San Francisco, CA");
494 assert_eq!(args["unit"], "fahrenheit");
495 let (name, args) = extract_name_and_args(result[1].clone());
496 assert_eq!(name, "get_weather");
497 assert_eq!(args["location"], "New York, NY");
498 assert_eq!(args["unit"], "fahrenheit");
499 }
500
501 #[tokio::test]
502 async fn test_qwen_qwq_32b_multiple_tool_calls_with_new_lines() {
503 let input = r#"<tool_call>
504{"name": "get_weather",
505"arguments": {"location": "San Francisco, CA",
506"unit": "fahrenheit"}}
507</tool_call>
508<tool_call>
509{"name": "get_weather", "arguments":
510{"location": "New York, NY", "unit":
511"fahrenheit"}}
512</tool_call>
513"#;
514 let config = ToolCallConfig::hermes();
515 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
516 assert_eq!(content, Some("".to_string()));
517 assert!(!result.is_empty());
518 assert_eq!(result.len(), 2);
519 let (name, args) = extract_name_and_args(result[0].clone());
520 assert_eq!(name, "get_weather");
521 assert_eq!(args["location"], "San Francisco, CA");
522 assert_eq!(args["unit"], "fahrenheit");
523 let (name, args) = extract_name_and_args(result[1].clone());
524 assert_eq!(name, "get_weather");
525 assert_eq!(args["location"], "New York, NY");
526 assert_eq!(args["unit"], "fahrenheit");
527 }
528
529 #[tokio::test]
530 #[ignore]
531 async fn test_ibm_granite_40_tiny_preview_simple() {
532 let input = r#"[{"arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}, "name": "get_weather"}]"#;
533 let config = ToolCallConfig {
534 format: ToolCallParserType::Json,
535 json: JsonParserConfig {
536 tool_call_start_tokens: vec![],
537 tool_call_end_tokens: vec![],
538 arguments_keys: vec!["arguments".to_string()],
539 ..Default::default()
540 },
541 };
542 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
543 assert_eq!(content, Some("".to_string()));
544 assert!(!result.is_empty());
545 assert_eq!(result.len(), 1);
546 let (name, args) = extract_name_and_args(result[0].clone());
547 assert_eq!(name, "get_weather");
548 assert_eq!(args["location"], "San Francisco, CA");
549 assert_eq!(args["unit"], "fahrenheit");
550 }
551
552 #[tokio::test]
553 async fn test_mistralai_mistral_7b_instruct_v03_simple() {
554 let input = r#" [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
555 let config = ToolCallConfig::mistral();
556 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
557 assert_eq!(content, Some("".to_string()));
558 assert!(!result.is_empty());
559 assert_eq!(result.len(), 1);
560 let (name, args) = extract_name_and_args(result[0].clone());
561 assert_eq!(name, "get_weather");
562 assert_eq!(args["location"], "San Francisco, CA");
563 assert_eq!(args["unit"], "fahrenheit");
564 }
565
566 #[tokio::test]
567 async fn test_mistralai_mistral_7b_instruct_v03_simple_with_normal_text() {
568 let input = r#"Hey How are you? [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
569 let config = ToolCallConfig::mistral();
570 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
571 assert_eq!(content, Some("Hey How are you?".to_string()));
572 assert!(!result.is_empty());
573 assert_eq!(result.len(), 1);
574 let (name, args) = extract_name_and_args(result[0].clone());
575 assert_eq!(name, "get_weather");
576 assert_eq!(args["location"], "San Francisco, CA");
577 assert_eq!(args["unit"], "fahrenheit");
578 }
579
580 #[tokio::test]
581 async fn test_mistralai_mistral_7b_instruct_v03_simple_with_new_lines() {
582 let input = r#"
583 [{"name": "get_weather",
584 "arguments": {"location":
585 "San Francisco, CA",
586 "unit": "fahrenheit"}}]
587 "#;
588 let config = ToolCallConfig::mistral();
589 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
590 assert_eq!(content, Some("".to_string()));
591 assert!(!result.is_empty());
592 assert_eq!(result.len(), 1);
593 let (name, args) = extract_name_and_args(result[0].clone());
594 assert_eq!(name, "get_weather");
595 assert_eq!(args["location"], "San Francisco, CA");
596 assert_eq!(args["unit"], "fahrenheit");
597 }
598
599 #[tokio::test]
600 async fn test_mistralai_mistral_7b_instruct_v03_multiple() {
601 let input = r#" [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]"#;
602 let config = ToolCallConfig::mistral();
603 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
604 assert_eq!(content, Some("".to_string()));
605 assert!(!result.is_empty());
606 assert_eq!(result.len(), 2);
607 let (name, args) = extract_name_and_args(result[0].clone());
608 assert_eq!(name, "get_weather");
609 assert_eq!(args["location"], "San Francisco, CA");
610 assert_eq!(args["unit"], "fahrenheit");
611 let (name, args) = extract_name_and_args(result[1].clone());
612 assert_eq!(name, "get_weather");
613 assert_eq!(args["location"], "New York, NY");
614 assert_eq!(args["unit"], "fahrenheit");
615 }
616
617 #[tokio::test]
618 async fn test_mistralai_mistral_7b_instruct_v03_multiple_with_normal_text() {
619 let input = r#"Hey How are you? [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]"#;
620 let config = ToolCallConfig::mistral();
621 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
622 assert_eq!(content, Some("Hey How are you?".to_string()));
623 assert!(!result.is_empty());
624 assert_eq!(result.len(), 2);
625 let (name, args) = extract_name_and_args(result[0].clone());
626 assert_eq!(name, "get_weather");
627 assert_eq!(args["location"], "San Francisco, CA");
628 assert_eq!(args["unit"], "fahrenheit");
629 let (name, args) = extract_name_and_args(result[1].clone());
630 assert_eq!(name, "get_weather");
631 assert_eq!(args["location"], "New York, NY");
632 assert_eq!(args["unit"], "fahrenheit");
633 }
634
635 #[tokio::test]
636 async fn test_mistralai_mistral_7b_instruct_v03_multiple_with_new_lines() {
637 let input = r#"
638 [{"name": "get_weather",
639 "arguments": {"location":
640 "San Francisco, CA",
641 "unit": "fahrenheit"}},
642 {"name": "get_weather", "arguments":
643 {"location": "New York, NY", "unit":
644 "fahrenheit"}}]
645 "#;
646 let config = ToolCallConfig::mistral();
647 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
648 assert_eq!(content, Some("".to_string()));
649 assert!(!result.is_empty());
650 assert_eq!(result.len(), 2);
651 let (name, args) = extract_name_and_args(result[0].clone());
652 assert_eq!(name, "get_weather");
653 assert_eq!(args["location"], "San Francisco, CA");
654 assert_eq!(args["unit"], "fahrenheit");
655 let (name, args) = extract_name_and_args(result[1].clone());
656 assert_eq!(name, "get_weather");
657 assert_eq!(args["location"], "New York, NY");
658 assert_eq!(args["unit"], "fahrenheit");
659 }
660
661 #[tokio::test]
662 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token() {
663 let input = r#"[TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
664 let config = ToolCallConfig::mistral();
665 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
666 assert_eq!(content, Some("".to_string()));
667 assert!(!result.is_empty());
668 assert_eq!(result.len(), 1);
669 let (name, args) = extract_name_and_args(result[0].clone());
670 assert_eq!(name, "get_weather");
671 assert_eq!(args["location"], "San Francisco, CA");
672 assert_eq!(args["unit"], "fahrenheit");
673 }
674
675 #[tokio::test]
676 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_with_normal_text() {
677 let input = r#"Hey How are you? [TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
678 let config = ToolCallConfig::mistral();
679 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
680 assert_eq!(content, Some("Hey How are you?".to_string()));
681 assert!(!result.is_empty());
682 assert_eq!(result.len(), 1);
683 let (name, args) = extract_name_and_args(result[0].clone());
684 assert_eq!(name, "get_weather");
685 assert_eq!(args["location"], "San Francisco, CA");
686 assert_eq!(args["unit"], "fahrenheit");
687 }
688
689 #[tokio::test]
690 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_tokenwith_new_lines() {
691 let input = r#"
692 [TOOL_CALLS]
693 [{"name": "get_weather",
694 "arguments": {"location":
695 "San Francisco, CA",
696 "unit": "fahrenheit"}}]
697 "#;
698 let config = ToolCallConfig::mistral();
699 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
700 assert_eq!(content, Some("".to_string()));
701 assert!(!result.is_empty());
702 assert_eq!(result.len(), 1);
703 let (name, args) = extract_name_and_args(result[0].clone());
704 assert_eq!(name, "get_weather");
705 assert_eq!(args["location"], "San Francisco, CA");
706 assert_eq!(args["unit"], "fahrenheit");
707 }
708
709 #[tokio::test]
710 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple() {
711 let input = r#"[TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]"#;
712 let config = ToolCallConfig::mistral();
713 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
714 assert_eq!(content, Some("".to_string()));
715 assert!(!result.is_empty());
716 assert_eq!(result.len(), 2);
717 let (name, args) = extract_name_and_args(result[0].clone());
718 assert_eq!(name, "get_weather");
719 assert_eq!(args["location"], "San Francisco, CA");
720 assert_eq!(args["unit"], "fahrenheit");
721 let (name, args) = extract_name_and_args(result[1].clone());
722 assert_eq!(name, "get_weather");
723 assert_eq!(args["location"], "New York, NY");
724 assert_eq!(args["unit"], "fahrenheit");
725 }
726
727 #[tokio::test]
728 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple_with_normal_text()
729 {
730 let input = r#"Hey How are you? [TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]"#;
731 let config = ToolCallConfig::mistral();
732 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
733 assert_eq!(content, Some("Hey How are you?".to_string()));
734 assert!(!result.is_empty());
735 assert_eq!(result.len(), 2);
736 let (name, args) = extract_name_and_args(result[0].clone());
737 assert_eq!(name, "get_weather");
738 assert_eq!(args["location"], "San Francisco, CA");
739 assert_eq!(args["unit"], "fahrenheit");
740 let (name, args) = extract_name_and_args(result[1].clone());
741 assert_eq!(name, "get_weather");
742 assert_eq!(args["location"], "New York, NY");
743 assert_eq!(args["unit"], "fahrenheit");
744 }
745
746 #[tokio::test]
747 async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple_with_new_lines()
748 {
749 let input = r#"
750 [TOOL_CALLS]
751 [{"name": "get_weather",
752 "arguments": {"location":
753 "San Francisco, CA",
754 "unit": "fahrenheit"}},
755 {"name": "get_weather", "arguments":
756 {"location": "New York, NY", "unit":
757 "fahrenheit"}}]
758 "#;
759 let config = ToolCallConfig::mistral();
760 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
761 assert_eq!(content, Some("".to_string()));
762 assert!(!result.is_empty());
763 assert_eq!(result.len(), 2);
764 let (name, args) = extract_name_and_args(result[0].clone());
765 assert_eq!(name, "get_weather");
766 assert_eq!(args["location"], "San Francisco, CA");
767 assert_eq!(args["unit"], "fahrenheit");
768 let (name, args) = extract_name_and_args(result[1].clone());
769 assert_eq!(name, "get_weather");
770 assert_eq!(args["location"], "New York, NY");
771 assert_eq!(args["unit"], "fahrenheit");
772 }
773
774 #[tokio::test]
775 async fn test_meta_llama_llama31_8b_instruct_simple() {
776 let input = r#"{"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}"#;
777 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
778 .await
779 .unwrap();
780 assert_eq!(content, Some("".to_string()));
781 assert!(!result.is_empty());
782 assert_eq!(result.len(), 1);
783 let (name, args) = extract_name_and_args(result[0].clone());
784 assert_eq!(name, "get_weather");
785 assert_eq!(args["location"], "San Francisco, CA");
786 assert_eq!(args["unit"], "fahrenheit");
787 }
788
789 #[tokio::test]
790 async fn test_meta_llama_llama31_8b_instruct_simple_with_normal_text() {
791 let input = r#"Hey How are you? {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}"#;
792 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
793 .await
794 .unwrap();
795 assert_eq!(content, Some("Hey How are you?".to_string()));
796 assert!(!result.is_empty());
797 assert_eq!(result.len(), 1);
798 let (name, args) = extract_name_and_args(result[0].clone());
799 assert_eq!(name, "get_weather");
800 assert_eq!(args["location"], "San Francisco, CA");
801 assert_eq!(args["unit"], "fahrenheit");
802 }
803
804 #[tokio::test]
805 async fn test_meta_llama_llama31_8b_instruct_with_new_lines() {
806 let input = r#"
807 {"name": "get_weather",
808 "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
809 "#;
810 let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
811 .await
812 .unwrap();
813 assert_eq!(content, Some("".to_string()));
814 assert!(!result.is_empty());
815 assert_eq!(result.len(), 1);
816 let (name, args) = extract_name_and_args(result[0].clone());
817 assert_eq!(name, "get_weather");
818 assert_eq!(args["location"], "San Francisco, CA");
819 assert_eq!(args["unit"], "fahrenheit");
820 }
821
822 #[tokio::test]
823 async fn test_meta_llama_llama31_8b_instruct_with_python_tag() {
824 let input = r#"<|python_tag|>{ "name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
825 let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
826 .await
827 .unwrap();
828 assert_eq!(content, Some("".to_string()));
829 assert!(!result.is_empty());
830 assert_eq!(result.len(), 1);
831 let (name, args) = extract_name_and_args(result[0].clone());
832 assert_eq!(name, "get_weather");
833 assert_eq!(args["location"], "San Francisco, CA");
834 assert_eq!(args["unit"], "fahrenheit");
835 }
836
837 #[tokio::test]
838 async fn test_meta_llama_llama31_8b_instruct_with_python_tag_with_normal_text() {
839 let input = r#"Hey How are you? <|python_tag|>{ "name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
840 let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
841 .await
842 .unwrap();
843 assert_eq!(content, Some("Hey How are you?".to_string()));
844 assert!(!result.is_empty());
845 assert_eq!(result.len(), 1);
846 let (name, args) = extract_name_and_args(result[0].clone());
847 assert_eq!(name, "get_weather");
848 assert_eq!(args["location"], "San Francisco, CA");
849 assert_eq!(args["unit"], "fahrenheit");
850 }
851
852 #[tokio::test]
853 async fn test_meta_llama_llama31_8b_instruct_with_python_tag_with_new_lines() {
854 let input = r#"
855 <|python_tag|>
856 {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
857 "#;
858 let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
859 .await
860 .unwrap();
861 assert_eq!(content, Some("".to_string()));
862 assert!(!result.is_empty());
863 assert_eq!(result.len(), 1);
864 let (name, args) = extract_name_and_args(result[0].clone());
865 assert_eq!(name, "get_weather");
866 assert_eq!(args["location"], "San Francisco, CA");
867 assert_eq!(args["unit"], "fahrenheit");
868 }
869
870 #[tokio::test]
871 async fn test_meta_llama_llama31_8b_instruct_with_python_tag_multiple_with_new_lines() {
872 let input = r#"
873 <|python_tag|>
874 {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" }}
875 <|python_tag|>
876 {"name": "get_weather", "parameters": {"location": "New York, NY", "unit": "fahrenheit" }}
877 "#;
878 let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
879 .await
880 .unwrap();
881 assert_eq!(content, Some("".to_string()));
882 assert!(!result.is_empty());
883 assert_eq!(result.len(), 2);
884 let (name, args) = extract_name_and_args(result[0].clone());
885 assert_eq!(name, "get_weather");
886 assert_eq!(args["location"], "San Francisco, CA");
887 assert_eq!(args["unit"], "fahrenheit");
888 let (name, args) = extract_name_and_args(result[1].clone());
889 assert_eq!(name, "get_weather");
890 assert_eq!(args["location"], "New York, NY");
891 assert_eq!(args["unit"], "fahrenheit");
892 }
893
894 #[tokio::test]
895 async fn test_detect_and_parse_tool_call_error_handling() {
896 let input = r#"{"name": "get_weather", "arguments": {"location": "San Francisco, CA"}}"#;
898 let result = detect_and_parse_tool_call(input, Some("unknown_parser")).await;
899 assert!(result.is_err());
900 let err = result.unwrap_err().to_string();
901 assert!(
902 err.contains("is not implemented"),
903 "Unexpected error message: {}",
904 err
905 );
906
907 let input = "not a json";
909 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
910 .await
911 .unwrap();
912 assert_eq!(content, Some("not a json".to_string()));
913 assert!(result.is_empty());
914
915 let input = r#"{"foo": "bar"}"#;
917 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
918 .await
919 .unwrap();
920 assert_eq!(content, Some(r#"{"foo": "bar"}"#.to_string()));
921 assert!(result.is_empty());
922 }
923
924 #[tokio::test]
925 #[ignore]
926 async fn test_internlm_internlm2_5_7b_chat_simple() {
927 let input = r#"San Francisco's weather is known for its mild climate with plenty of fog, especially along the coast. Here's an overview of the weather in Fahrenheit:
928
929- **Summer (June to August)**: Average highs range from the mid-60s to low 70s Fahrenheit, with cooler mornings and evenings. Coastal areas may be cooler than inland spots.
930
931Remember, San Francisco weather can be quite unpredictable, particularly with its famous fog, which can significantly lower temperatures. Always check a local weather forecast for the most accurate and up-to-date information."#;
932 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
933 .await
934 .unwrap();
935 assert_eq!(content, Some(input.to_string()));
936 assert!(result.is_empty()); }
938
939 #[tokio::test]
940 #[ignore]
941 async fn test_ai21labs_ai21_jamba_15_mini_simple() {
942 let input = r#" [
943 {"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
944]"#;
945 let config = ToolCallConfig {
946 format: ToolCallParserType::Json,
947 json: JsonParserConfig {
948 tool_call_start_tokens: vec![],
949 tool_call_end_tokens: vec![],
950 arguments_keys: vec!["arguments".to_string()],
951 ..Default::default()
952 },
953 };
954 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
955 assert_eq!(content, Some("".to_string()));
956 assert!(!result.is_empty());
957 assert_eq!(result.len(), 1);
958 let (name, args) = extract_name_and_args(result[0].clone());
959 assert_eq!(name, "get_weather");
960 assert_eq!(args["location"], "San Francisco, CA");
961 assert_eq!(args["unit"], "fahrenheit");
962 }
963
964 #[tokio::test]
965 #[ignore]
966 async fn test_salesforce_llama_xlam_2_8b_fc_r_simple() {
967 let input = r#"[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
968 let config = ToolCallConfig {
969 format: ToolCallParserType::Json,
970 json: JsonParserConfig {
971 tool_call_start_tokens: vec![],
972 tool_call_end_tokens: vec![],
973 arguments_keys: vec!["arguments".to_string()],
974 ..Default::default()
975 },
976 };
977 let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
978 assert_eq!(content, Some("".to_string()));
979 assert!(!result.is_empty());
980 assert_eq!(result.len(), 1);
981 let (name, args) = extract_name_and_args(result[0].clone());
982 assert_eq!(name, "get_weather");
983 assert_eq!(args["location"], "San Francisco, CA");
984 assert_eq!(args["unit"], "fahrenheit");
985 }
986
987 #[tokio::test]
988 async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci() {
989 let input = r#"<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
990 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
991 assert_eq!(content, Some("".to_string()));
992 assert!(!result.is_empty());
993 assert_eq!(result.len(), 1);
994 let (name, args) = extract_name_and_args(result[0].clone());
995 assert_eq!(name, "get_weather");
996 assert_eq!(args["location"], "San Francisco, CA");
997 assert_eq!(args["unit"], "fahrenheit");
998 }
999
1000 #[tokio::test]
1001 async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci_multiple() {
1002 let input = r#"<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]</TOOLCALL>"#;
1003 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1004 assert_eq!(content, Some("".to_string()));
1005 assert!(!result.is_empty());
1006 assert_eq!(result.len(), 2);
1007 let (name, args) = extract_name_and_args(result[0].clone());
1008 assert_eq!(name, "get_weather");
1009 assert_eq!(args["location"], "San Francisco, CA");
1010 assert_eq!(args["unit"], "fahrenheit");
1011 let (name, args) = extract_name_and_args(result[1].clone());
1012 assert_eq!(name, "get_weather");
1013 assert_eq!(args["location"], "New York, NY");
1014 assert_eq!(args["unit"], "fahrenheit");
1015 }
1016
1017 #[tokio::test]
1018 async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci_multiple_with_normal_text()
1019 {
1020 let input = r#"Hey How are you? <TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]</TOOLCALL>"#;
1021 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1022 assert_eq!(content, Some("Hey How are you?".to_string()));
1023 assert!(!result.is_empty());
1024 assert_eq!(result.len(), 2);
1025 let (name, args) = extract_name_and_args(result[0].clone());
1026 assert_eq!(name, "get_weather");
1027 assert_eq!(args["location"], "San Francisco, CA");
1028 assert_eq!(args["unit"], "fahrenheit");
1029 let (name, args) = extract_name_and_args(result[1].clone());
1030 assert_eq!(name, "get_weather");
1031 assert_eq!(args["location"], "New York, NY");
1032 assert_eq!(args["unit"], "fahrenheit");
1033 }
1034
1035 #[tokio::test]
1036 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag() {
1037 let input = r#"<|python_tag|>{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1038 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1039 assert_eq!(content, Some("".to_string()));
1040 assert!(!result.is_empty());
1041 assert_eq!(result.len(), 1);
1042 let (name, args) = extract_name_and_args(result[0].clone());
1043 assert_eq!(name, "get_weather");
1044 assert_eq!(args["location"], "San Francisco, CA");
1045 assert_eq!(args["unit"], "fahrenheit");
1046 }
1047
1048 #[tokio::test]
1049 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag_with_normal_text()
1050 {
1051 let input = r#"Hey How are you? <|python_tag|>{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1052 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1053 assert_eq!(content, Some("Hey How are you?".to_string()));
1054 assert!(!result.is_empty());
1055 assert_eq!(result.len(), 1);
1056 let (name, args) = extract_name_and_args(result[0].clone());
1057 assert_eq!(name, "get_weather");
1058 assert_eq!(args["location"], "San Francisco, CA");
1059 assert_eq!(args["unit"], "fahrenheit");
1060 }
1061
1062 #[tokio::test]
1063 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag_with_new_lines()
1064 {
1065 let input = r#"
1066 <|python_tag|>
1067 {"name":
1068 "get_weather",
1069 "arguments":
1070 {"location": "San Francisco, CA",
1071 "unit": "fahrenheit" }}
1072 "#;
1073 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1074 assert_eq!(content, Some("".to_string()));
1075 assert!(!result.is_empty());
1076 assert_eq!(result.len(), 1);
1077 let (name, args) = extract_name_and_args(result[0].clone());
1078 assert_eq!(name, "get_weather");
1079 assert_eq!(args["location"], "San Francisco, CA");
1080 assert_eq!(args["unit"], "fahrenheit");
1081 }
1082
1083 #[tokio::test]
1084 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag_multiple_with_new_lines()
1085 {
1086 let input = r#"
1087 {"name": "get_weather", "arguments":
1088 {"location": "San Francisco, CA",
1089 "unit": "fahrenheit" }}
1090 "#;
1091 let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1092 assert_eq!(content, Some("".to_string()));
1093 assert!(!result.is_empty());
1094 assert_eq!(result.len(), 1);
1095 let (name, args) = extract_name_and_args(result[0].clone());
1096 assert_eq!(name, "get_weather");
1097 assert_eq!(args["location"], "San Francisco, CA");
1098 assert_eq!(args["unit"], "fahrenheit");
1099 }
1100
1101 #[tokio::test]
1102 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag() {
1103 let input = r#"{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1104 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
1105 .await
1106 .unwrap();
1107 assert_eq!(content, Some("".to_string()));
1108 assert!(!result.is_empty());
1109 assert_eq!(result.len(), 1);
1110 let (name, args) = extract_name_and_args(result[0].clone());
1111 assert_eq!(name, "get_weather");
1112 assert_eq!(args["location"], "San Francisco, CA");
1113 assert_eq!(args["unit"], "fahrenheit");
1114 }
1115
1116 #[tokio::test]
1117 async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag_with_normal_text()
1118 {
1119 let input = r#"Hey How are you? { "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1120 let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
1121 .await
1122 .unwrap();
1123 assert_eq!(content, Some("Hey How are you?".to_string()));
1124 assert!(!result.is_empty());
1125 assert_eq!(result.len(), 1);
1126 let (name, args) = extract_name_and_args(result[0].clone());
1127 assert_eq!(name, "get_weather");
1128 assert_eq!(args["location"], "San Francisco, CA");
1129 assert_eq!(args["unit"], "fahrenheit");
1130 }
1131
1132 #[tokio::test]
1133 async fn test_phi4_single_function_call() {
1134 let input =
1135 r#"functools[{"name": "get_country_capital", "arguments": {"country": "Poland"}}]"#;
1136 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1137 .await
1138 .unwrap();
1139 assert_eq!(content, Some("".to_string()));
1140 assert_eq!(result.len(), 1);
1141 let (name, args) = extract_name_and_args(result[0].clone());
1142 assert_eq!(name, "get_country_capital");
1143 assert_eq!(args["country"], "Poland");
1144 }
1145
1146 #[tokio::test]
1147 async fn test_phi4_single_function_call_with_normal_text() {
1148 let input = r#"Hey How are you? functools[{"name": "get_country_capital", "arguments": {"country": "Poland"}}]"#;
1149 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1150 .await
1151 .unwrap();
1152 assert_eq!(content, Some("Hey How are you?".to_string()));
1153 assert_eq!(result.len(), 1);
1154 let (name, args) = extract_name_and_args(result[0].clone());
1155 assert_eq!(name, "get_country_capital");
1156 assert_eq!(args["country"], "Poland");
1157 }
1158
1159 #[tokio::test]
1160 async fn test_phi4_multiple_function_calls_simple_arguments() {
1161 let input = r#"functools[
1162 {"name": "get_country_capital", "arguments": {"country": "Poland"}},
1163 {"name": "get_population", "arguments": {"city": "Warsaw"}}
1164]"#;
1165 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1166 .await
1167 .unwrap();
1168 assert_eq!(content, Some("".to_string()));
1169 assert_eq!(result.len(), 2);
1170
1171 let (name1, args1) = extract_name_and_args(result[0].clone());
1172 assert_eq!(name1, "get_country_capital");
1173 assert_eq!(args1["country"], "Poland");
1174
1175 let (name2, args2) = extract_name_and_args(result[1].clone());
1176 assert_eq!(name2, "get_population");
1177 assert_eq!(args2["city"], "Warsaw");
1178 }
1179
1180 #[tokio::test]
1181 async fn test_phi4_multiple_function_calls_simple_arguments_with_normal_text() {
1182 let input = r#"Hey How are you? functools[
1183 {"name": "get_country_capital", "arguments": {"country": "Poland"}},
1184 {"name": "get_population", "arguments": {"city": "Warsaw"}}
1185]"#;
1186 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1187 .await
1188 .unwrap();
1189 assert_eq!(content, Some("Hey How are you?".to_string()));
1190 assert_eq!(result.len(), 2);
1191
1192 let (name1, args1) = extract_name_and_args(result[0].clone());
1193 assert_eq!(name1, "get_country_capital");
1194 assert_eq!(args1["country"], "Poland");
1195
1196 let (name2, args2) = extract_name_and_args(result[1].clone());
1197 assert_eq!(name2, "get_population");
1198 assert_eq!(args2["city"], "Warsaw");
1199 }
1200
1201 #[tokio::test]
1202 async fn test_phi4_single_function_call_nested_json_arguments() {
1203 let input = r#"functools[{"name": "get_weather_forecast", "arguments":
1204 {"location": {"city": "San Francisco",
1205 "state": "CA"}, "date": "2023-10-05"}}]"#;
1206 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1207 .await
1208 .unwrap();
1209 assert_eq!(content, Some("".to_string()));
1210 assert_eq!(result.len(), 1);
1211 let (name, args) = extract_name_and_args(result[0].clone());
1212 assert_eq!(name, "get_weather_forecast");
1213 assert_eq!(args["date"], "2023-10-05");
1214 assert_eq!(args["location"]["city"], "San Francisco");
1215 assert_eq!(args["location"]["state"], "CA");
1216 }
1217
1218 #[tokio::test]
1219 async fn test_phi4_single_function_call_nested_json_arguments_with_normal_text() {
1220 let input = r#"Hey How are you? functools[{"name": "get_weather_forecast", "arguments":
1221 {"location": {"city": "San Francisco",
1222 "state": "CA"}, "date": "2023-10-05"}}]"#;
1223 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1224 .await
1225 .unwrap();
1226 assert_eq!(content, Some("Hey How are you?".to_string()));
1227 assert_eq!(result.len(), 1);
1228 let (name, args) = extract_name_and_args(result[0].clone());
1229 assert_eq!(name, "get_weather_forecast");
1230 assert_eq!(args["date"], "2023-10-05");
1231 assert_eq!(args["location"]["city"], "San Francisco");
1232 assert_eq!(args["location"]["state"], "CA");
1233 }
1234
1235 #[tokio::test]
1236 async fn test_phi4_function_call_with_parameters_instead_of_arguments() {
1237 let input = r#"functools[{"name": "calculate_distance",
1238 "parameters": {"from": "New York", "to": "Los Angeles"}}]"#;
1239 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1240 .await
1241 .unwrap();
1242 assert_eq!(content, Some("".to_string()));
1243 assert_eq!(result.len(), 1);
1244 let (name, args) = extract_name_and_args(result[0].clone());
1245 assert_eq!(name, "calculate_distance");
1246 assert_eq!(args["from"], "New York");
1247 assert_eq!(args["to"], "Los Angeles");
1248 }
1249
1250 #[tokio::test]
1251 async fn test_phi4_function_call_with_parameters_instead_of_arguments_with_normal_text() {
1252 let input = r#"Hey How are you? functools[{"name": "calculate_distance",
1253 "parameters": {"from": "New York", "to": "Los Angeles"}}]"#;
1254 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1255 .await
1256 .unwrap();
1257 assert_eq!(content, Some("Hey How are you?".to_string()));
1258 assert_eq!(result.len(), 1);
1259 let (name, args) = extract_name_and_args(result[0].clone());
1260 assert_eq!(name, "calculate_distance");
1261 assert_eq!(args["from"], "New York");
1262 assert_eq!(args["to"], "Los Angeles");
1263 }
1264
1265 #[tokio::test]
1266 async fn test_phi4_token_leak_reproduction() {
1267 let input = r#"functools{"name": "get_weather","arguments":{"location":"San Francisco"}}"#;
1270 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1271 .await
1272 .unwrap();
1273 assert_eq!(content, Some("".to_string()));
1275 assert_eq!(result.len(), 1);
1276 let (name, args) = extract_name_and_args(result[0].clone());
1277 assert_eq!(name, "get_weather");
1278 assert_eq!(args["location"], "San Francisco");
1279 }
1280
1281 #[tokio::test]
1282 async fn test_phi4_token_leak_edge_case() {
1283 let input = r#"functools"#;
1286 let (result, _content) = detect_and_parse_tool_call(input, Some("phi4"))
1287 .await
1288 .unwrap();
1289 assert_eq!(result.len(), 0); }
1294
1295 #[tokio::test]
1296 async fn test_phi4_token_with_invalid_json() {
1297 let input = r#"functools{invalid json}"#;
1299 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1300 .await
1301 .unwrap();
1302 assert_eq!(content, Some("".to_string()));
1304 assert_eq!(result.len(), 0); }
1306
1307 #[tokio::test]
1308 async fn test_phi4_streaming_partial_tokens() {
1309 let config = super::get_tool_parser_map().get("phi4").unwrap();
1314
1315 use super::super::json::detect_tool_call_start_json;
1317 assert!(
1318 detect_tool_call_start_json("fun", &config.json),
1319 "'fun' should be detected as potential start"
1320 );
1321 assert!(
1322 detect_tool_call_start_json("f", &config.json),
1323 "'f' should be detected as potential start"
1324 );
1325 assert!(
1326 detect_tool_call_start_json("func", &config.json),
1327 "'func' should be detected as potential start"
1328 );
1329 assert!(
1330 detect_tool_call_start_json("functo", &config.json),
1331 "'functo' should be detected as potential start"
1332 );
1333
1334 assert!(
1336 !detect_tool_call_start_json("hello", &config.json),
1337 "'hello' should not be detected"
1338 );
1339 assert!(
1340 !detect_tool_call_start_json("xyz", &config.json),
1341 "'xyz' should not be detected"
1342 );
1343 }
1344
1345 #[tokio::test]
1346 async fn test_phi4_false_positive_words() {
1347 let input = r#"funk music is great"#;
1351 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1352 .await
1353 .unwrap();
1354 assert_eq!(
1356 result.len(),
1357 0,
1358 "No tool calls should be found in 'funk music is great'"
1359 );
1360 assert_eq!(
1361 content,
1362 Some("funk music is great".to_string()),
1363 "Content should contain the original text"
1364 );
1365 }
1366
1367 #[tokio::test]
1368 async fn test_phi4_partial_but_complete_words() {
1369 let input = r#"The function works well"#;
1372 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1373 .await
1374 .unwrap();
1375 assert_eq!(
1376 result.len(),
1377 0,
1378 "No tool calls should be found in 'The function works well'"
1379 );
1380 assert_eq!(content, Some("The function works well".to_string()));
1381
1382 let input = r#"functional programming"#;
1383 let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
1384 .await
1385 .unwrap();
1386 assert_eq!(
1387 result.len(),
1388 0,
1389 "No tool calls should be found in 'functional programming'"
1390 );
1391 assert_eq!(content, Some("functional programming".to_string()));
1392 }
1393
1394 #[tokio::test]
1395 async fn test_phi4_funk_variations() {
1396 let test_cases = vec![
1399 "funk",
1400 "funky",
1401 "funktion", "funked",
1403 "I love funk music",
1404 "This is funky stuff",
1405 ];
1406
1407 for test_input in test_cases {
1408 let (result, content) = detect_and_parse_tool_call(test_input, Some("phi4"))
1409 .await
1410 .unwrap();
1411 assert_eq!(
1412 result.len(),
1413 0,
1414 "No tool calls should be found in '{}'",
1415 test_input
1416 );
1417 assert_eq!(
1418 content,
1419 Some(test_input.to_string()),
1420 "Content should match input for '{}'",
1421 test_input
1422 );
1423 }
1424 }
1425
1426 #[tokio::test]
1427 async fn test_phi4_func_but_not_functools() {
1428 let test_cases = vec![
1431 "func()", "funcdef", "functions are useful",
1434 "functionally speaking",
1435 ];
1436
1437 for test_input in test_cases {
1438 let (result, content) = detect_and_parse_tool_call(test_input, Some("phi4"))
1439 .await
1440 .unwrap();
1441 assert_eq!(
1442 result.len(),
1443 0,
1444 "No tool calls should be found in '{}'",
1445 test_input
1446 );
1447 assert_eq!(
1448 content,
1449 Some(test_input.to_string()),
1450 "Content should match input for '{}'",
1451 test_input
1452 );
1453 }
1454 }
1455
1456 #[tokio::test]
1457 async fn test_pythonic_parser_basic_with_constants() {
1458 let input = r#"[get_weather(location="San Francisco", unit="fahrenheit"), get_weather(location="New York", unit="fahrenheit")]"#;
1459 let (result, content) = detect_and_parse_tool_call(input, Some("pythonic"))
1460 .await
1461 .unwrap();
1462 assert_eq!(content, Some("".to_string()));
1463 assert_eq!(result.len(), 2);
1464 let (name, args) = extract_name_and_args(result[0].clone());
1465 assert_eq!(name, "get_weather");
1466 assert_eq!(args["location"], "San Francisco");
1467 assert_eq!(args["unit"], "fahrenheit");
1468 let (name, args) = extract_name_and_args(result[1].clone());
1469 assert_eq!(name, "get_weather");
1470 assert_eq!(args["location"], "New York");
1471 assert_eq!(args["unit"], "fahrenheit");
1472 }
1473
1474 #[tokio::test]
1475 #[ignore]
1476 async fn test_pythonic_parser_with_constants_and_normal_text() {
1477 let input = r#"Hey How are you? [get_weather(location="San Francisco", unit="fahrenheit"), get_weather(location="New York", unit="fahrenheit")]"#;
1478 let (result, content) = detect_and_parse_tool_call(input, Some("pythonic"))
1479 .await
1480 .unwrap();
1481 assert_eq!(content, Some("Hey How are you?".to_string()));
1482 assert_eq!(result.len(), 2);
1483
1484 let (name, args) = extract_name_and_args(result[0].clone());
1485 assert_eq!(name, "get_weather");
1486 assert_eq!(args["location"], "San Francisco");
1487 assert_eq!(args["unit"], "fahrenheit");
1488 let (name, args) = extract_name_and_args(result[1].clone());
1489 assert_eq!(name, "get_weather");
1490 assert_eq!(args["location"], "New York");
1491 assert_eq!(args["unit"], "fahrenheit");
1492 }
1493
1494 #[tokio::test]
1495 async fn test_harmony_parser_basic() {
1496 let input = r#"
1497 <|channel|>analysis<|message|>Need to use function get_current_weather.<|end|><|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location":"San Francisco", "unit":"fahrenheit"}"#;
1498 let (result, content) = detect_and_parse_tool_call(input, Some("harmony"))
1499 .await
1500 .unwrap();
1501 assert_eq!(
1502 content,
1503 Some("Need to use function get_current_weather.".to_string())
1504 );
1505 assert_eq!(result.len(), 1);
1506 let (name, args) = extract_name_and_args(result[0].clone());
1507 assert_eq!(name, "get_current_weather");
1508 assert_eq!(args["location"], "San Francisco");
1509 assert_eq!(args["unit"], "fahrenheit");
1510 }
1511
1512 #[tokio::test]
1513 async fn test_deepseek_v3_1_parser_basic() {
1514 let input = r#"<|tool▁calls▁begin|><|tool▁call▁begin|>get_current_weather<|tool▁sep|>{"location": "Tokyo"}<|tool▁call▁end|><|tool▁call▁begin|>get_current_weather<|tool▁sep|>{"location": "Paris"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|>"#;
1515 let (result, content) = detect_and_parse_tool_call(input, Some("deepseek_v3_1"))
1516 .await
1517 .unwrap();
1518 assert_eq!(content, Some("".to_string()));
1519 assert_eq!(result.len(), 2);
1520 let (name, args) = extract_name_and_args(result[0].clone());
1521 assert_eq!(name, "get_current_weather");
1522 assert_eq!(args["location"], "Tokyo");
1523 let (name, args) = extract_name_and_args(result[1].clone());
1524 assert_eq!(name, "get_current_weather");
1525 assert_eq!(args["location"], "Paris");
1526 }
1527
1528 #[tokio::test]
1529 async fn test_hermes_parser_without_new_line() {
1530 let input = r#"<tool_call>{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "celsius"}}</tool_call>"
1531 "#;
1532 let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
1533 .await
1534 .unwrap();
1535 assert_eq!(content, Some("".to_string()));
1536 assert_eq!(result.len(), 1);
1537 let (name, args) = extract_name_and_args(result[0].clone());
1538 assert_eq!(name, "get_weather");
1539 assert_eq!(args["location"], "San Francisco, CA");
1540 assert_eq!(args["unit"], "celsius");
1541 }
1542}
1543
1544#[cfg(test)]
1546mod parallel_tool_calling_tests {
1547 use super::*;
1548
1549 fn extract_name_and_args(call: ToolCallResponse) -> (String, serde_json::Value) {
1550 let args: serde_json::Value = serde_json::from_str(&call.function.arguments).unwrap();
1551 (call.function.name, args)
1552 }
1553
1554 fn validate_weather_tool_calls(result: &[ToolCallResponse], expected_cities: &[(&str, &str)]) {
1556 assert_eq!(
1557 result.len(),
1558 expected_cities.len(),
1559 "Expected {} tool calls, got {}",
1560 expected_cities.len(),
1561 result.len()
1562 );
1563
1564 for (i, (expected_city, expected_state)) in expected_cities.iter().enumerate() {
1565 let (name, args) = extract_name_and_args(result[i].clone());
1566 assert_eq!(
1567 name, "get_current_weather",
1568 "Tool call {} should be get_current_weather",
1569 i
1570 );
1571 assert_eq!(
1572 args["city"], *expected_city,
1573 "Tool call {} city should be {}",
1574 i, expected_city
1575 );
1576 assert_eq!(
1577 args["state"], *expected_state,
1578 "Tool call {} state should be {}",
1579 i, expected_state
1580 );
1581 assert_eq!(
1582 args["unit"], "fahrenheit",
1583 "Tool call {} unit should be fahrenheit",
1584 i
1585 );
1586
1587 assert!(
1589 result[i].id.len() >= 9,
1590 "Tool call {} ID should be at least 9 characters",
1591 i
1592 );
1593
1594 assert_eq!(
1596 result[i].tp,
1597 crate::tool_calling::response::ToolCallType::Function,
1598 "Tool call {} type should be 'function'",
1599 i
1600 );
1601 }
1602 }
1603
1604 #[tokio::test]
1609 async fn test_parallel_nemotron_format_two_cities() {
1610 let input = r#" <TOOLCALL>[
1611 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1612 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}
1613]</TOOLCALL>"#;
1614
1615 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1616 .await
1617 .unwrap();
1618
1619 assert_eq!(content, Some("".to_string()));
1620 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1621 }
1622
1623 #[tokio::test]
1624 async fn test_parallel_nemotron_format_three_cities() {
1625 let input = r#"<TOOLCALL>[
1626 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1627 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}},
1628 {"name": "get_current_weather", "arguments": {"city": "Seattle", "state": "WA", "unit": "fahrenheit"}}
1629]</TOOLCALL>"#;
1630
1631 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1632 .await
1633 .unwrap();
1634
1635 assert_eq!(content, Some("".to_string()));
1636 validate_weather_tool_calls(
1637 &result,
1638 &[("Dallas", "TX"), ("Orlando", "FL"), ("Seattle", "WA")],
1639 );
1640 }
1641
1642 #[tokio::test]
1643 async fn test_parallel_nemotron_format_with_normal_text() {
1644 let input = r#"I'll help you get the weather for both cities. <TOOLCALL>[
1645 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1646 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}
1647]</TOOLCALL>"#;
1648
1649 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1650 .await
1651 .unwrap();
1652
1653 assert_eq!(
1654 content,
1655 Some("I'll help you get the weather for both cities.".to_string())
1656 );
1657 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1658 }
1659
1660 #[tokio::test]
1665 async fn test_parallel_qwen3coder_format_two_cities() {
1666 let _input = r#"<tool_call>
1667<function=get_current_weather>
1668<parameter=city>
1669Dallas
1670</parameter>
1671<parameter=state>
1672TX
1673</parameter>
1674<parameter=unit>
1675fahrenheit
1676</parameter>
1677</function>
1678</tool_call>
1679<tool_call>
1680<function=get_current_weather>
1681<parameter=city>
1682Orlando
1683</parameter>
1684<parameter=state>
1685FL
1686</parameter>
1687<parameter=unit>
1688fahrenheit
1689</parameter>
1690</function>
1691</tool_call>"#;
1692
1693 let input_hermes_format = r#"<tool_call>{"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}}</tool_call>
1696<tool_call>{"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}</tool_call>"#;
1697
1698 let (result, content) = detect_and_parse_tool_call(input_hermes_format, Some("hermes"))
1699 .await
1700 .unwrap();
1701
1702 assert_eq!(content, Some("".to_string()));
1703 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1704 }
1705
1706 #[tokio::test]
1711 async fn test_parallel_xlam_format_pure_json() {
1712 let input = r#"[{"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}}, {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}]"#;
1713
1714 let (result, content) = detect_and_parse_tool_call(input, Some("mistral"))
1715 .await
1716 .unwrap();
1717
1718 assert_eq!(content, Some("".to_string()));
1719 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1720 }
1721
1722 #[tokio::test]
1723 async fn test_parallel_xlam_format_with_whitespace() {
1724 let input = r#"[
1725 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1726 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}
1727]"#;
1728
1729 let (result, content) = detect_and_parse_tool_call(input, Some("mistral"))
1730 .await
1731 .unwrap();
1732
1733 assert_eq!(content, Some("".to_string()));
1734 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1735 }
1736
1737 #[tokio::test]
1742 async fn test_parallel_minimax_format() {
1743 let _input = r#"<tool_calls>
1744{"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}}
1745{"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}
1746</tool_calls>"#;
1747
1748 let input_nemotron_format = r#"<TOOLCALL>[
1751{"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1752{"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}}
1753]</TOOLCALL>"#;
1754
1755 let (result, content) =
1756 detect_and_parse_tool_call(input_nemotron_format, Some("nemotron_deci"))
1757 .await
1758 .unwrap();
1759
1760 assert_eq!(content, Some("".to_string()));
1761 validate_weather_tool_calls(&result, &[("Dallas", "TX"), ("Orlando", "FL")]);
1762 }
1763
1764 #[tokio::test]
1769 async fn test_parallel_harmony_format_multiple_tools() {
1770 let input = r#"<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"city": "Dallas", "state": "TX", "unit": "fahrenheit"}<|call|><|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"city": "Orlando", "state": "FL", "unit": "fahrenheit"}<|call|>"#;
1772
1773 let (result, _content) = detect_and_parse_tool_call(input, Some("harmony"))
1774 .await
1775 .unwrap();
1776
1777 assert!(!result.is_empty(), "Should parse at least one tool call");
1779
1780 let (name, args) = extract_name_and_args(result[0].clone());
1782 assert_eq!(name, "get_current_weather");
1783 assert!(args.get("city").is_some() || args.get("location").is_some());
1784 }
1785
1786 #[tokio::test]
1791 async fn test_parallel_mixed_tool_types() {
1792 let input = r#"<TOOLCALL>[
1793 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1794 {"name": "web_search", "arguments": {"query": "Orlando Florida attractions", "max_results": 5}}
1795]</TOOLCALL>"#;
1796
1797 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1798 .await
1799 .unwrap();
1800
1801 assert_eq!(content, Some("".to_string()));
1802 assert_eq!(result.len(), 2);
1803
1804 let (name1, args1) = extract_name_and_args(result[0].clone());
1806 assert_eq!(name1, "get_current_weather");
1807 assert_eq!(args1["city"], "Dallas");
1808 assert_eq!(args1["state"], "TX");
1809 assert_eq!(args1["unit"], "fahrenheit");
1810
1811 let (name2, args2) = extract_name_and_args(result[1].clone());
1813 assert_eq!(name2, "web_search");
1814 assert_eq!(args2["query"], "Orlando Florida attractions");
1815 assert_eq!(args2["max_results"], 5);
1816 }
1817
1818 #[tokio::test]
1823 async fn test_parallel_malformed_second_call() {
1824 let input = r#"<TOOLCALL>[
1825 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1826 {"name": "get_current_weather", "arguments": {"city": "Orlando", "invalid_field": 123}}
1827]</TOOLCALL>"#;
1828
1829 let (result, _content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1830 .await
1831 .unwrap();
1832
1833 assert!(
1835 !result.is_empty(),
1836 "Should parse at least the valid tool call"
1837 );
1838
1839 let (name, args) = extract_name_and_args(result[0].clone());
1840 assert_eq!(name, "get_current_weather");
1841 assert_eq!(args["city"], "Dallas");
1842 }
1843
1844 #[tokio::test]
1845 async fn test_parallel_empty_array() {
1846 let input = r#"<TOOLCALL>[]</TOOLCALL>"#;
1847
1848 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1849 .await
1850 .unwrap();
1851
1852 assert_eq!(
1853 result.len(),
1854 0,
1855 "Empty array should result in no tool calls"
1856 );
1857 assert_eq!(content, Some("".to_string()));
1858 }
1859
1860 #[tokio::test]
1861 async fn test_parallel_single_call_in_array() {
1862 let input = r#"<TOOLCALL>[
1863 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}}
1864]</TOOLCALL>"#;
1865
1866 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1867 .await
1868 .unwrap();
1869
1870 assert_eq!(content, Some("".to_string()));
1871 assert_eq!(result.len(), 1);
1872 validate_weather_tool_calls(&result, &[("Dallas", "TX")]);
1873 }
1874
1875 #[tokio::test]
1880 async fn test_parallel_five_cities() {
1881 let input = r#"<TOOLCALL>[
1882 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
1883 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}},
1884 {"name": "get_current_weather", "arguments": {"city": "Seattle", "state": "WA", "unit": "fahrenheit"}},
1885 {"name": "get_current_weather", "arguments": {"city": "Denver", "state": "CO", "unit": "fahrenheit"}},
1886 {"name": "get_current_weather", "arguments": {"city": "Miami", "state": "FL", "unit": "fahrenheit"}}
1887]</TOOLCALL>"#;
1888
1889 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1890 .await
1891 .unwrap();
1892
1893 assert_eq!(content, Some("".to_string()));
1894 validate_weather_tool_calls(
1895 &result,
1896 &[
1897 ("Dallas", "TX"),
1898 ("Orlando", "FL"),
1899 ("Seattle", "WA"),
1900 ("Denver", "CO"),
1901 ("Miami", "FL"),
1902 ],
1903 );
1904 }
1905
1906 #[tokio::test]
1911 async fn test_parallel_complex_arguments() {
1912 let input = r#"<TOOLCALL>[
1913 {
1914 "name": "get_weather_forecast",
1915 "arguments": {
1916 "location": {"city": "Dallas", "state": "TX", "country": "USA"},
1917 "days": 7,
1918 "units": "fahrenheit",
1919 "include_hourly": true,
1920 "alerts": ["severe_weather", "temperature_extreme"]
1921 }
1922 },
1923 {
1924 "name": "get_air_quality",
1925 "arguments": {
1926 "coordinates": {"lat": 32.7767, "lon": -96.7970},
1927 "metrics": ["pm2.5", "pm10", "ozone", "no2"],
1928 "radius_km": 50
1929 }
1930 }
1931]</TOOLCALL>"#;
1932
1933 let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
1934 .await
1935 .unwrap();
1936
1937 assert_eq!(content, Some("".to_string()));
1938 assert_eq!(result.len(), 2);
1939
1940 let (name1, args1) = extract_name_and_args(result[0].clone());
1942 assert_eq!(name1, "get_weather_forecast");
1943 assert_eq!(args1["location"]["city"], "Dallas");
1944 assert_eq!(args1["days"], 7);
1945 assert_eq!(args1["include_hourly"], true);
1946
1947 let (name2, args2) = extract_name_and_args(result[1].clone());
1949 assert_eq!(name2, "get_air_quality");
1950 assert_eq!(args2["coordinates"]["lat"], 32.7767);
1951 assert_eq!(args2["radius_km"], 50);
1952 }
1953
1954 fn validate_tool_call_ids(result: &[ToolCallResponse]) {
1960 let mut ids = std::collections::HashSet::new();
1961 for (i, tool_call) in result.iter().enumerate() {
1962 assert!(
1963 tool_call.id.len() >= 9,
1964 "Tool call {} ID '{}' should be at least 9 characters",
1965 i,
1966 tool_call.id
1967 );
1968
1969 assert!(
1970 ids.insert(&tool_call.id),
1971 "Tool call {} ID '{}' is not unique",
1972 i,
1973 tool_call.id
1974 );
1975 }
1976 }
1977
1978 fn validate_openai_compatibility(result: &[ToolCallResponse]) {
1980 for (i, tool_call) in result.iter().enumerate() {
1981 assert_eq!(
1983 tool_call.tp,
1984 crate::tool_calling::response::ToolCallType::Function,
1985 "Tool call {} type should be 'function', got '{:?}'",
1986 i,
1987 tool_call.tp
1988 );
1989
1990 assert!(
1992 !tool_call.function.name.is_empty(),
1993 "Tool call {} function name should not be empty",
1994 i
1995 );
1996
1997 let _: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)
1999 .unwrap_or_else(|_| panic!("Tool call {} arguments should be valid JSON", i));
2000 }
2001 }
2002
2003 #[tokio::test]
2004 async fn test_parallel_tool_call_id_uniqueness() {
2005 let input = r#"<TOOLCALL>[
2006 {"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}},
2007 {"name": "get_current_weather", "arguments": {"city": "Orlando", "state": "FL", "unit": "fahrenheit"}},
2008 {"name": "web_search", "arguments": {"query": "weather forecast", "max_results": 3}}
2009]</TOOLCALL>"#;
2010
2011 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2012 .await
2013 .unwrap();
2014
2015 assert_eq!(result.len(), 3);
2016 validate_tool_call_ids(&result);
2017 validate_openai_compatibility(&result);
2018 }
2019
2020 #[tokio::test]
2021 async fn test_parallel_openai_compatibility_validation() {
2022 let input = r#"[TOOL_CALLS][
2023 {"name": "function_one", "arguments": {"param1": "value1", "param2": 42}},
2024 {"name": "function_two", "arguments": {"param3": true, "param4": [1, 2, 3]}},
2025 {"name": "function_three", "arguments": {"param5": {"nested": "object"}}}
2026][/TOOL_CALLS]"#;
2027
2028 let (result, _) = detect_and_parse_tool_call(input, Some("mistral"))
2029 .await
2030 .unwrap();
2031
2032 assert_eq!(result.len(), 3);
2033 validate_openai_compatibility(&result);
2034
2035 let names: std::collections::HashSet<_> =
2037 result.iter().map(|tc| &tc.function.name).collect();
2038 assert_eq!(names.len(), 3, "All function names should be unique");
2039 }
2040
2041 #[tokio::test]
2046 async fn test_parallel_performance_many_small_calls() {
2047 let mut tool_calls = Vec::new();
2048 for i in 0..20 {
2049 tool_calls.push(format!(
2050 r#"{{"name": "get_data_{}", "arguments": {{"id": {}, "type": "test"}}}}"#,
2051 i, i
2052 ));
2053 }
2054
2055 let input = format!("<TOOLCALL>[{}]</TOOLCALL>", tool_calls.join(","));
2056
2057 let start = std::time::Instant::now();
2058 let (result, _) = detect_and_parse_tool_call(&input, Some("nemotron_deci"))
2059 .await
2060 .unwrap();
2061 let duration = start.elapsed();
2062
2063 assert_eq!(result.len(), 20);
2064 assert!(
2065 duration < std::time::Duration::from_millis(100),
2066 "Parsing 20 tool calls should take less than 100ms, took {:?}",
2067 duration
2068 );
2069
2070 validate_tool_call_ids(&result);
2071 validate_openai_compatibility(&result);
2072 }
2073
2074 #[tokio::test]
2075 async fn test_parallel_large_arguments() {
2076 let large_data = "x".repeat(1000); let input = format!(
2078 r#"<TOOLCALL>[
2079 {{"name": "process_large_data", "arguments": {{"data": "{}", "size": 1000}}}},
2080 {{"name": "backup_data", "arguments": {{"backup_data": "{}", "timestamp": "2024-01-01T00:00:00Z"}}}}
2081]</TOOLCALL>"#,
2082 large_data, large_data
2083 );
2084
2085 let (result, _) = detect_and_parse_tool_call(&input, Some("nemotron_deci"))
2086 .await
2087 .unwrap();
2088
2089 assert_eq!(result.len(), 2);
2090
2091 for tool_call in &result {
2093 let args: serde_json::Value =
2094 serde_json::from_str(&tool_call.function.arguments).unwrap();
2095 if tool_call.function.name == "process_large_data" {
2096 assert_eq!(args["data"].as_str().unwrap().len(), 1000);
2097 assert_eq!(args["size"], 1000);
2098 }
2099 }
2100 }
2101
2102 #[tokio::test]
2107 async fn test_parallel_unicode_and_special_characters() {
2108 let input = r#"<TOOLCALL>[
2109 {"name": "translate_text", "arguments": {"text": "Hello 世界! 🌍", "from": "en", "to": "zh"}},
2110 {"name": "analyze_emoji", "arguments": {"emoji": "🚀💫⭐", "context": "space exploration"}},
2111 {"name": "process_unicode", "arguments": {"data": "café naïve résumé", "encoding": "utf-8"}}
2112]</TOOLCALL>"#;
2113
2114 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2115 .await
2116 .unwrap();
2117
2118 assert_eq!(result.len(), 3);
2119
2120 let (name1, args1) = extract_name_and_args(result[0].clone());
2122 assert_eq!(name1, "translate_text");
2123 assert_eq!(args1["text"], "Hello 世界! 🌍");
2124
2125 let (name2, args2) = extract_name_and_args(result[1].clone());
2126 assert_eq!(name2, "analyze_emoji");
2127 assert_eq!(args2["emoji"], "🚀💫⭐");
2128
2129 let (name3, args3) = extract_name_and_args(result[2].clone());
2130 assert_eq!(name3, "process_unicode");
2131 assert_eq!(args3["data"], "café naïve résumé");
2132 }
2133
2134 #[tokio::test]
2135 async fn test_parallel_json_escaping_and_quotes() {
2136 let input = r#"<TOOLCALL>[
2139 {"name": "process_json", "arguments": {"json_string": "{\"key\": \"value with \\\"quotes\\\"\"}", "format": "strict"}},
2140 {"name": "handle_paths", "arguments": {"windows_path": "C:\\Users\\Test\\Documents\\file.txt", "unix_path": "/home/user/file.txt"}},
2141 {"name": "regex_pattern", "arguments": {"pattern": "\\d{3}-\\d{3}-\\d{4}", "test_string": "Phone: 123-456-7890"}}
2142]</TOOLCALL>"#;
2143
2144 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2145 .await
2146 .unwrap();
2147
2148 assert_eq!(result.len(), 3);
2150
2151 let (name1, _args1) = extract_name_and_args(result[0].clone());
2153 assert_eq!(name1, "process_json");
2154
2155 let (name2, _args2) = extract_name_and_args(result[1].clone());
2156 assert_eq!(name2, "handle_paths");
2157
2158 let (name3, _args3) = extract_name_and_args(result[2].clone());
2159 assert_eq!(name3, "regex_pattern");
2160 }
2161
2162 #[tokio::test]
2163 async fn test_parallel_mixed_argument_types() {
2164 let input = r#"<TOOLCALL>[
2165 {"name": "type_test", "arguments": {"string": "text", "number": 42, "float": 2.718281828459045, "boolean": true, "null_value": null}},
2166 {"name": "array_test", "arguments": {"empty_array": [], "string_array": ["a", "b", "c"], "mixed_array": [1, "two", true, null]}},
2167 {"name": "object_test", "arguments": {"empty_object": {}, "nested": {"level1": {"level2": {"value": "deep"}}}}}
2168]</TOOLCALL>"#;
2169
2170 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2171 .await
2172 .unwrap();
2173
2174 assert_eq!(result.len(), 3);
2175
2176 let (name1, args1) = extract_name_and_args(result[0].clone());
2178 assert_eq!(name1, "type_test");
2179 assert_eq!(args1["string"], "text");
2180 assert_eq!(args1["number"], 42);
2181 assert_eq!(args1["float"], std::f64::consts::E);
2182 assert_eq!(args1["boolean"], true);
2183 assert!(args1["null_value"].is_null());
2184
2185 let (name2, args2) = extract_name_and_args(result[1].clone());
2186 assert_eq!(name2, "array_test");
2187 assert!(args2["empty_array"].is_array());
2188 assert_eq!(args2["empty_array"].as_array().unwrap().len(), 0);
2189 assert_eq!(args2["string_array"].as_array().unwrap().len(), 3);
2190 assert_eq!(args2["mixed_array"].as_array().unwrap().len(), 4);
2191
2192 let (name3, args3) = extract_name_and_args(result[2].clone());
2193 assert_eq!(name3, "object_test");
2194 assert!(args3["empty_object"].is_object());
2195 assert_eq!(args3["nested"]["level1"]["level2"]["value"], "deep");
2196 }
2197
2198 #[tokio::test]
2199 async fn test_parallel_whitespace_variations() {
2200 let input = r#"<TOOLCALL>[
2202 {
2203 "name": "spaced_function",
2204 "arguments": {
2205 "param1": "value1",
2206 "param2": "value2"
2207 }
2208 },
2209 {"name":"compact_function","arguments":{"param":"value"}},
2210 {
2211 "name" : "weird_spacing",
2212 "arguments" : {
2213 "key" : "value"
2214 }
2215 }
2216]</TOOLCALL>"#;
2217
2218 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2219 .await
2220 .unwrap();
2221
2222 assert_eq!(result.len(), 3);
2223 validate_openai_compatibility(&result);
2224
2225 let names: Vec<_> = result.iter().map(|tc| &tc.function.name).collect();
2227 assert!(names.contains(&&"spaced_function".to_string()));
2228 assert!(names.contains(&&"compact_function".to_string()));
2229 assert!(names.contains(&&"weird_spacing".to_string()));
2230 }
2231
2232 #[tokio::test]
2233 async fn test_parallel_cross_parser_compatibility() {
2234 let base_calls = r#"[
2236 {"name": "get_weather", "arguments": {"city": "Dallas", "unit": "fahrenheit"}},
2237 {"name": "get_weather", "arguments": {"city": "Orlando", "unit": "fahrenheit"}}
2238]"#;
2239
2240 let test_cases = vec![
2242 (
2243 format!("<TOOLCALL>{}</TOOLCALL>", base_calls),
2244 "nemotron_deci",
2245 ),
2246 (
2247 format!("[TOOL_CALLS]{}[/TOOL_CALLS]", base_calls),
2248 "mistral",
2249 ),
2250 (base_calls.to_string(), "mistral"), ];
2252
2253 for (input, parser) in test_cases {
2254 let (result, _) = detect_and_parse_tool_call(&input, Some(parser))
2255 .await
2256 .unwrap_or_else(|e| panic!("Failed to parse with {}: {}", parser, e));
2257 assert_eq!(
2258 result.len(),
2259 2,
2260 "Parser {} should produce 2 tool calls",
2261 parser
2262 );
2263
2264 for tool_call in &result {
2265 assert_eq!(tool_call.function.name, "get_weather");
2266 let args: serde_json::Value =
2267 serde_json::from_str(&tool_call.function.arguments).unwrap();
2268 assert!(args["city"].is_string());
2269 assert_eq!(args["unit"], "fahrenheit");
2270 }
2271 }
2272 }
2273
2274 #[tokio::test]
2275 async fn test_parallel_boundary_conditions() {
2276 let input_single = r#"<TOOLCALL>[
2278 {"name": "single_call", "arguments": {"test": true}}
2279]</TOOLCALL>"#;
2280
2281 let (result, _) = detect_and_parse_tool_call(input_single, Some("nemotron_deci"))
2282 .await
2283 .unwrap();
2284
2285 assert_eq!(result.len(), 1);
2286 assert_eq!(result[0].function.name, "single_call");
2287
2288 let mut many_calls = Vec::new();
2290 for i in 0..50 {
2291 many_calls.push(format!(
2292 r#"{{"name": "call_{}", "arguments": {{"index": {}}}}}"#,
2293 i, i
2294 ));
2295 }
2296
2297 let input_many = format!("<TOOLCALL>[{}]</TOOLCALL>", many_calls.join(","));
2298
2299 let (result, _) = detect_and_parse_tool_call(&input_many, Some("nemotron_deci"))
2300 .await
2301 .unwrap();
2302
2303 assert_eq!(result.len(), 50);
2304 validate_tool_call_ids(&result);
2305
2306 for (i, tool_call) in result.iter().enumerate() {
2308 assert_eq!(tool_call.function.name, format!("call_{}", i));
2309 let args: serde_json::Value =
2310 serde_json::from_str(&tool_call.function.arguments).unwrap();
2311 assert_eq!(args["index"], i);
2312 }
2313 }
2314
2315 #[tokio::test]
2316 async fn test_parallel_malformed_recovery() {
2317 let input = r#"<TOOLCALL>[
2319 {"name": "good_call_1", "arguments": {"param": "value1"}},
2320 {"malformed": "missing_name_and_arguments"},
2321 {"name": "good_call_2", "arguments": {"param": "value2"}},
2322 {"name": "missing_args"},
2323 {"name": "good_call_3", "arguments": {"param": "value3"}},
2324 "completely_invalid_json",
2325 {"name": "good_call_4", "arguments": {"param": "value4"}}
2326]</TOOLCALL>"#;
2327
2328 let (result, _) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
2329 .await
2330 .unwrap();
2331
2332 assert!(
2334 !result.is_empty(),
2335 "Should parse at least some valid tool calls"
2336 );
2337
2338 let valid_calls: Vec<_> = result
2340 .iter()
2341 .filter(|tc| tc.function.name.starts_with("good_call"))
2342 .collect();
2343
2344 assert!(
2345 valid_calls.len() >= 2,
2346 "Should parse at least 2 valid tool calls"
2347 );
2348
2349 for tool_call in valid_calls {
2351 assert!(tool_call.function.name.starts_with("good_call"));
2352 let args: serde_json::Value =
2353 serde_json::from_str(&tool_call.function.arguments).unwrap();
2354 assert!(args["param"].is_string());
2355 }
2356 }
2357}
2358
2359#[cfg(test)]
2360mod detect_parser_tests {
2362 use super::*;
2363
2364 #[test]
2365 fn test_e2e_detect_tool_call_start_harmony() {
2366 let text = r#"<|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json"#;
2367 let result = detect_tool_call_start(text, Some("harmony")).unwrap();
2368 assert!(result);
2369 }
2370
2371 #[test]
2372 fn test_e2e_detect_tool_call_start_hermes() {
2373 let text = r#"{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
2374 let result = detect_tool_call_start(text, Some("hermes")).unwrap();
2375 assert!(result);
2376 }
2377
2378 #[test]
2379 fn test_e2e_detect_tool_call_start_pythonic() {
2380 let text = r#"foo(a=1, b=2), bar(x=3)]"#;
2381 let result = detect_tool_call_start(text, Some("pythonic")).unwrap();
2382 assert!(!result);
2383 }
2384
2385 #[test]
2386 fn test_e2e_detect_tool_call_start_nemotron_deci() {
2387 let text = r#"<TOOLCALL>[{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}]</TOOLCALL>"#;
2388 let result = detect_tool_call_start(text, Some("nemotron_deci")).unwrap();
2389 assert!(result);
2390 }
2391
2392 #[test]
2393 fn test_e2e_detect_tool_call_start_phi4() {
2394 let text =
2395 r#"functools{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
2396 let result = detect_tool_call_start(text, Some("phi4")).unwrap();
2397 assert!(result);
2398 }
2399
2400 #[test]
2401 fn test_e2e_detect_tool_call_start_llama3_json() {
2402 let text = r#"<|python_tag|>{ "name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
2403 let result = detect_tool_call_start(text, Some("llama3_json")).unwrap();
2404 assert!(result);
2405 }
2406
2407 #[test]
2408 fn test_e2e_detect_tool_call_start_mistral() {
2409 let text =
2410 r#"[TOOL_CALLS]{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
2411 let result = detect_tool_call_start(text, Some("mistral")).unwrap();
2412 assert!(result);
2413 }
2414
2415 #[test]
2416 fn test_e2e_detect_incomplete_tool_call_start_deepseek_v3_1() {
2417 let text =
2418 r#"<|tool▁call▁begin|>get_current_weather{"location": "Tokyo"}<|tool▁call▁end|>"#;
2419 let result = detect_tool_call_start(text, Some("deepseek_v3_1")).unwrap();
2420 assert!(!result);
2421 }
2422
2423 #[test]
2424 fn test_e2e_detect_tool_call_start_deepseek_v3_1() {
2425 let text = r#"<|tool▁calls▁begin|><|tool▁call▁begin|>get_current_weather{"location": "Tokyo"}<|tool▁call▁end|>"#;
2426 let result = detect_tool_call_start(text, Some("deepseek_v3_1")).unwrap();
2427 assert!(result);
2428 }
2429}