parsers.rs 65.8 KB
Newer Older
1
2
3
// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

4
use super::config::{ToolCallConfig, ToolCallParserType};
5
6
7
use super::harmony::{detect_tool_call_start_harmony, parse_tool_calls_harmony};
use super::json::{detect_tool_call_start_json, try_tool_call_parse_json};
use super::pythonic::{detect_tool_call_start_pythonic, try_tool_call_parse_pythonic};
8
use super::response::ToolCallResponse;
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::collections::HashMap;
use std::sync::OnceLock;

static PARSER_MAP: OnceLock<HashMap<&'static str, ToolCallConfig>> = OnceLock::new();

// Always update this parsermap when adding a new parser
pub fn get_tool_parser_map() -> &'static HashMap<&'static str, ToolCallConfig> {
    PARSER_MAP.get_or_init(|| {
        let mut map = HashMap::new();
        map.insert("hermes", ToolCallConfig::hermes());
        map.insert("nemotron_deci", ToolCallConfig::nemotron_deci());
        map.insert("llama3_json", ToolCallConfig::llama3_json());
        map.insert("mistral", ToolCallConfig::mistral());
        map.insert("phi4", ToolCallConfig::phi4());
        map.insert("pythonic", ToolCallConfig::pythonic());
        map.insert("harmony", ToolCallConfig::harmony());
25
        map.insert("deepseek_v3_1", ToolCallConfig::deepseek_v3_1());
26
27
28
29
30
31
32
33
        map.insert("default", ToolCallConfig::default());
        map
    })
}

pub fn get_available_tool_parsers() -> Vec<&'static str> {
    get_tool_parser_map().keys().copied().collect()
}
34

35
pub async fn try_tool_call_parse(
36
37
    message: &str,
    config: &ToolCallConfig,
38
) -> anyhow::Result<(Vec<ToolCallResponse>, Option<String>)> {
39
40
    // Use match statement (Rust's switch statement) to call the appropriate parser
    match config.format {
41
42
43
44
        ToolCallParserType::Json => {
            let (results, normal_content) = try_tool_call_parse_json(message, &config.json)?;
            Ok((results, normal_content))
        }
45
        ToolCallParserType::Harmony => {
46
            let (results, normal_content) = parse_tool_calls_harmony(message, &config.json).await?;
47
            Ok((results, normal_content))
48
49
        }
        ToolCallParserType::Pythonic => {
50
51
            let (results, normal_content) = try_tool_call_parse_pythonic(message)?;
            Ok((results, normal_content))
52
53
54
55
56
57
58
59
60
61
        }
        ToolCallParserType::Typescript => {
            anyhow::bail!("Typescript parser not implemented");
        }
        ToolCallParserType::Xml => {
            anyhow::bail!("Xml parser not implemented");
        }
    }
}

62
// Base Detector to call for all tool parsing
63
pub async fn detect_and_parse_tool_call(
64
65
    message: &str,
    parser_str: Option<&str>,
66
) -> anyhow::Result<(Vec<ToolCallResponse>, Option<String>)> {
67
68
    // Get the tool parser map
    let parser_map = get_tool_parser_map();
69
70
71
72
73
74
75
76

    // Handle None or empty string by defaulting to "default"
    let parser_key = match parser_str {
        Some(s) if !s.is_empty() => s,
        _ => "default", // None or empty string
    };

    match parser_map.get(parser_key) {
77
        Some(config) => {
78
            let (results, normal_content) = try_tool_call_parse(message, config).await?;
79
80
            Ok((results, normal_content))
        }
81
82
83
84
85
        None => anyhow::bail!(
            "Parser '{}' is not implemented. Available parsers: {:?}",
            parser_key,
            get_available_tool_parsers()
        ),
86
87
88
    }
}

89
90
91
92
93
94
95
96
97
98
pub fn detect_tool_call_start(chunk: &str, parser_str: Option<&str>) -> anyhow::Result<bool> {
    let parser_map = get_tool_parser_map();
    let parser_key = match parser_str {
        Some(s) if !s.is_empty() => s,
        _ => "default", // None or empty string
    };

    match parser_map.get(parser_key) {
        Some(config) => match config.format {
            ToolCallParserType::Json => Ok(detect_tool_call_start_json(chunk, &config.json)),
99
100
101
            ToolCallParserType::Harmony => {
                Ok(detect_tool_call_start_harmony(chunk, &config.json, false))
            }
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
            ToolCallParserType::Pythonic => Ok(detect_tool_call_start_pythonic(chunk)),
            ToolCallParserType::Typescript => {
                anyhow::bail!("Typescript parser not implemented");
            }
            ToolCallParserType::Xml => {
                anyhow::bail!("Xml parser not implemented");
            }
        },
        None => anyhow::bail!(
            "Parser '{}' is not implemented. Available parsers: {:?}",
            parser_key,
            get_available_tool_parsers()
        ),
    }
}

118
119
120
121
// Tests
// cargo test postprocessor::tool_calling::parsers
#[cfg(test)]
mod tests {
122
    use super::super::config::JsonParserConfig;
123
124
125
126
127
128
129
    use super::*;

    fn extract_name_and_args(call: ToolCallResponse) -> (String, serde_json::Value) {
        let args: serde_json::Value = serde_json::from_str(&call.function.arguments).unwrap();
        (call.function.name, args)
    }

130
131
132
133
134
135
136
137
138
139
140
141
142
143
    #[test]
    fn test_get_available_tool_parsers() {
        let parsers = get_available_tool_parsers();
        assert!(!parsers.is_empty());
        // Update this list when adding a new parser
        let available_parsers = [
            "hermes",
            "llama3_json",
            "harmony",
            "nemotron_deci",
            "mistral",
            "phi4",
            "default",
            "pythonic",
144
            "deepseek_v3_1",
145
146
147
148
149
150
        ];
        for parser in available_parsers {
            assert!(parsers.contains(&parser));
        }
    }

151
152
    #[tokio::test]
    async fn parses_single_parameters_object() {
153
        let input = r#"{ "name": "hello", "parameters": { "x": 1, "y": 2 } }"#;
154
155
156
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
157
        assert_eq!(content, Some("".to_string()));
158
159
160
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
161
162
163
164
165
        assert_eq!(name, "hello");
        assert_eq!(args["x"], 1);
        assert_eq!(args["y"], 2);
    }

166
167
    #[tokio::test]
    async fn parses_single_arguments_object() {
168
        let input = r#"{ "name": "world", "arguments": { "a": "abc", "b": 42 } }"#;
169
170
171
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
172
        assert_eq!(content, Some("".to_string()));
173
174
175
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
176
177
178
179
180
        assert_eq!(name, "world");
        assert_eq!(args["a"], "abc");
        assert_eq!(args["b"], 42);
    }

181
182
    #[tokio::test]
    async fn parses_vec_of_parameters() {
183
        let input = r#"[{ "name": "first", "parameters": { "a": 1 } }, { "name": "second", "parameters": { "b": 2 } }]"#;
184
185
186
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
187
        assert_eq!(content, Some("".to_string()));
188
189
190
191
192
193
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "first");
        assert_eq!(args["a"], 1);
        let (name, args) = extract_name_and_args(result[1].clone());
194
195
196
197
        assert_eq!(name, "second");
        assert_eq!(args["b"], 2);
    }

198
199
    #[tokio::test]
    async fn parses_vec_of_arguments() {
200
        let input = r#"[{ "name": "alpha", "arguments": { "a": "x" } }, { "name": "omega", "arguments": { "z": "y" } }]"#;
201
202
203
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
204
        assert_eq!(content, Some("".to_string()));
205
206
207
208
209
210
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "alpha");
        assert_eq!(args["a"], "x");
        let (name, args) = extract_name_and_args(result[1].clone());
211
212
213
214
        assert_eq!(name, "omega");
        assert_eq!(args["z"], "y");
    }

215
216
    #[tokio::test]
    async fn parses_toolcall_wrapped_payload() {
217
218
        let input =
            r#"<TOOLCALL>[{ "name": "wrapped", "parameters": { "foo": "bar" } }]</TOOLCALL>"#;
219
220
221
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
222
        assert_eq!(content, Some("".to_string()));
223
224
225
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
226
227
228
229
        assert_eq!(name, "wrapped");
        assert_eq!(args["foo"], "bar");
    }

230
231
    #[tokio::test]
    async fn parses_python_tag_prefixed_payload() {
232
        let input = r#"<|python_tag|>{ "name": "pyfunc", "arguments": { "k": "v" } }"#;
233
        let (result, content) = try_tool_call_parse(
234
235
236
237
238
239
240
241
242
243
            input,
            &ToolCallConfig {
                format: ToolCallParserType::Json,
                json: JsonParserConfig {
                    tool_call_start_tokens: vec!["<|python_tag|>".to_string()],
                    tool_call_end_tokens: vec!["".to_string()],
                    ..Default::default()
                },
            },
        )
244
        .await
245
        .unwrap();
246
        assert_eq!(content, Some("".to_string()));
247
248
249
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
250
251
252
253
        assert_eq!(name, "pyfunc");
        assert_eq!(args["k"], "v");
    }

254
255
    #[tokio::test]
    async fn returns_none_on_invalid_input() {
256
        let input = r#"not even json"#;
257
258
259
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
260
        assert_eq!(content, Some("not even json".to_string()));
261
        assert!(result.is_empty());
262
263
    }

264
265
    #[tokio::test]
    async fn returns_none_on_valid_json_wrong_shape() {
266
        let input = r#"{ "foo": "bar" }"#;
267
268
269
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
270
        assert_eq!(content, Some("{ \"foo\": \"bar\" }".to_string()));
271
        assert!(result.is_empty());
272
273
274
    }

    // Tests for real model outputs - disabled by default
275
276
    #[tokio::test]
    async fn test_nvidia_llama3_nemotron_super_49b_simple() {
277
278
279
280
281
        let input = r#"<think>
Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
</think>

<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
282
283
284
        let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
            .await
            .unwrap();
285
286
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
287
288
289
290
291
292
293
        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()));
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

294
295
    #[tokio::test]
    async fn test_nvidia_llama3_nemotron_super_49b_simple_with_no_think() {
296
        let input = r#"<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
297
298
299
        let (result, content) = detect_and_parse_tool_call(input, Some("nemotron_deci"))
            .await
            .unwrap();
300
301
302
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        assert_eq!(content, Some("".to_string()));
303
        let (name, args) = extract_name_and_args(result[0].clone());
304
305
306
307
308
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

309
310
    #[tokio::test]
    async fn test_nvidia_llama3_nemotron_super_49b_with_function_array() {
311
312
313
314
315
316
        let input = r#"<think>
Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
</think>

<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]</TOOLCALL>"#;
        let config = ToolCallConfig::nemotron_deci();
317
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
318
        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()));
319
320
321
322
323
324
325
326
327
328
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
329
330
    }

331
332
    #[tokio::test]
    async fn test_nvidia_llama3_nemotron_super_49b_with_function_array_with_new_lines() {
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
        let input = r#"<think>
Okay, the user is asking for the weather in San Francisco in Fahrenheit. Let me check the tools available.
</think>

<TOOLCALL>
[{"name": "get_weather",
 "arguments": {"location": "San Francisco, CA",
  "unit": "fahrenheit"}},
  {"name": "get_weather",
   "arguments":
  {"location": "New York, NY",
  "unit": "fahrenheit"}}]
  </TOOLCALL>
  "#;
        let config = ToolCallConfig::nemotron_deci();
348
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
349
        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()));
350
351
352
353
354
355
356
357
358
359
360
361
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

362
363
    #[tokio::test]
    async fn test_qwen_qwq_32b_simple() {
364
365
366
        let input = r#"<tool_call>
{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
</tool_call>"#;
367
368
369
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
370
        assert_eq!(content, Some("".to_string()));
371
372
373
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
374
375
376
377
378
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

379
380
    #[tokio::test]
    async fn test_qwen_qwq_32b_simple_with_normal_text() {
381
382
383
        let input = r#"Hey How are you? <tool_call>
{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
</tool_call>"#;
384
385
386
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
387
388
389
390
391
        assert_eq!(content, Some("Hey How are you?".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
    }

392
393
    #[tokio::test]
    async fn test_nousresearch_hermes3_llama31_8b_simple() {
394
395
396
        let input = r#"<tool_call>
{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
</tool_call>"#;
397
398
399
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
400
        assert_eq!(content, Some("".to_string()));
401
402
403
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
404
405
406
407
408
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

409
410
    #[tokio::test]
    async fn test_qwen_qwq_32b_multiple_tool_calls() {
411
412
413
414
415
416
417
418
        let input = r#"<tool_call>
{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
</tool_call>
<tool_call>
{"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}
</tool_call>
"#;
        let config = ToolCallConfig::hermes();
419
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
420
421
422
423
424
425
426
427
428
429
430
431
432
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

433
434
    #[tokio::test]
    async fn test_qwen_qwq_32b_multiple_tool_calls_with_normal_text() {
435
436
437
438
439
440
441
442
        let input = r#"Hey How are you? <tool_call>
{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
</tool_call>
<tool_call>
{"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}
</tool_call>
"#;
        let config = ToolCallConfig::hermes();
443
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
444
        assert_eq!(content, Some("Hey How are you?".to_string()));
445
446
447
448
449
450
451
452
453
454
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
455
456
    }

457
458
    #[tokio::test]
    async fn test_qwen_qwq_32b_multiple_tool_calls_with_new_lines() {
459
460
461
462
463
464
465
466
467
468
469
470
        let input = r#"<tool_call>
{"name": "get_weather",
"arguments": {"location": "San Francisco, CA",
"unit": "fahrenheit"}}
</tool_call>
<tool_call>
{"name": "get_weather", "arguments":
{"location": "New York, NY", "unit":
"fahrenheit"}}
</tool_call>
"#;
        let config = ToolCallConfig::hermes();
471
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
472
        assert_eq!(content, Some("".to_string()));
473
474
475
476
477
478
479
480
481
482
483
484
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

485
    #[tokio::test]
486
    #[ignore]
487
    async fn test_ibm_granite_40_tiny_preview_simple() {
488
489
490
491
492
493
494
495
496
497
        let input = r#"[{"arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}, "name": "get_weather"}]"#;
        let config = ToolCallConfig {
            format: ToolCallParserType::Json,
            json: JsonParserConfig {
                tool_call_start_tokens: vec![],
                tool_call_end_tokens: vec![],
                arguments_keys: vec!["arguments".to_string()],
                ..Default::default()
            },
        };
498
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
499
        assert_eq!(content, Some("".to_string()));
500
501
502
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
503
504
505
506
507
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

508
509
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_simple() {
510
        let input = r#" [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
511
        let config = ToolCallConfig::mistral();
512
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
513
514
515
516
517
518
519
520
521
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

522
523
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_simple_with_normal_text() {
524
525
        let input = r#"Hey How are you? [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
        let config = ToolCallConfig::mistral();
526
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
527
        assert_eq!(content, Some("Hey How are you?".to_string()));
528
529
530
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
531
532
533
534
535
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

536
537
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_simple_with_new_lines() {
538
539
540
541
542
543
        let input = r#"
        [{"name": "get_weather",
        "arguments": {"location":
        "San Francisco, CA",
        "unit": "fahrenheit"}}]
        "#;
544
        let config = ToolCallConfig::mistral();
545
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
546
        assert_eq!(content, Some("".to_string()));
547
548
549
550
551
552
553
554
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

555
556
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_multiple() {
557
558
        let input = r#" [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}, {"name": "get_weather", "arguments": {"location": "New York, NY", "unit": "fahrenheit"}}]"#;
        let config = ToolCallConfig::mistral();
559
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
560
561
562
563
564
565
566
567
568
569
570
571
572
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

573
574
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_multiple_with_normal_text() {
575
576
        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"}}]"#;
        let config = ToolCallConfig::mistral();
577
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
578
        assert_eq!(content, Some("Hey How are you?".to_string()));
579
580
581
582
583
584
585
586
587
588
589
590
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

591
592
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_multiple_with_new_lines() {
593
594
        let input = r#"
        [{"name": "get_weather",
595
596
597
598
599
600
        "arguments": {"location":
        "San Francisco, CA",
        "unit": "fahrenheit"}},
        {"name": "get_weather", "arguments":
        {"location": "New York, NY", "unit":
        "fahrenheit"}}]
601
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
606
607
608
609
610
611
612
613
614
615
616
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

617
618
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token() {
619
620
        let input = r#"[TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
        let config = ToolCallConfig::mistral();
621
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
622
623
624
625
626
627
628
629
630
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

631
632
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_with_normal_text() {
633
634
        let input = r#"Hey How are you? [TOOL_CALLS] [{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
        let config = ToolCallConfig::mistral();
635
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
636
        assert_eq!(content, Some("Hey How are you?".to_string()));
637
638
639
640
641
642
643
644
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

645
646
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_tokenwith_new_lines() {
647
648
649
650
651
652
653
654
        let input = r#"
        [TOOL_CALLS]
        [{"name": "get_weather",
        "arguments": {"location":
        "San Francisco, CA",
        "unit": "fahrenheit"}}]
        "#;
        let config = ToolCallConfig::mistral();
655
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
656
        assert_eq!(content, Some("".to_string()));
657
658
659
660
661
662
663
664
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

665
666
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple() {
667
668
        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"}}]"#;
        let config = ToolCallConfig::mistral();
669
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
670
671
672
673
674
675
676
677
678
679
680
681
682
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

683
684
685
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple_with_normal_text()
     {
686
687
        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"}}]"#;
        let config = ToolCallConfig::mistral();
688
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
689
        assert_eq!(content, Some("Hey How are you?".to_string()));
690
691
692
693
694
695
696
697
698
699
700
701
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

702
703
704
    #[tokio::test]
    async fn test_mistralai_mistral_7b_instruct_v03_single_with_start_token_multiple_with_new_lines()
     {
705
706
707
708
709
710
711
712
713
714
715
        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"}}]
        "#;
        let config = ToolCallConfig::mistral();
716
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
717
        assert_eq!(content, Some("".to_string()));
718
719
720
721
722
723
724
725
726
727
728
729
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

730
731
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_simple() {
732
        let input = r#"{"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}"#;
733
734
735
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
            .await
            .unwrap();
736
737
738
739
740
741
742
743
744
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

745
746
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_simple_with_normal_text() {
747
        let input = r#"Hey How are you? {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}"#;
748
749
750
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
            .await
            .unwrap();
751
        assert_eq!(content, Some("Hey How are you?".to_string()));
752
753
754
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
755
756
757
758
759
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

760
761
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_with_new_lines() {
762
763
764
765
        let input = r#"
        {"name": "get_weather",
        "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
        "#;
766
767
768
        let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
            .await
            .unwrap();
769
        assert_eq!(content, Some("".to_string()));
770
771
772
773
774
775
776
777
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

778
779
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_with_python_tag() {
780
        let input = r#"<|python_tag|>{ "name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
781
782
783
        let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
            .await
            .unwrap();
784
785
786
787
788
789
790
791
792
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

793
794
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_with_python_tag_with_normal_text() {
795
        let input = r#"Hey How are you? <|python_tag|>{ "name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
796
797
798
        let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
            .await
            .unwrap();
799
        assert_eq!(content, Some("Hey How are you?".to_string()));
800
801
802
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
803
804
805
806
807
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

808
809
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_with_python_tag_with_new_lines() {
810
811
812
813
        let input = r#"
        <|python_tag|>
        {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
        "#;
814
815
816
        let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
            .await
            .unwrap();
817
        assert_eq!(content, Some("".to_string()));
818
819
820
821
822
823
824
825
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

826
827
    #[tokio::test]
    async fn test_meta_llama_llama31_8b_instruct_with_python_tag_multiple_with_new_lines() {
828
829
830
831
832
833
        let input = r#"
        <|python_tag|>
        {"name": "get_weather", "parameters": {"location": "San Francisco, CA", "unit": "fahrenheit" }}
        <|python_tag|>
        {"name": "get_weather", "parameters": {"location": "New York, NY", "unit": "fahrenheit" }}
        "#;
834
835
836
        let (result, content) = detect_and_parse_tool_call(input, Some("llama3_json"))
            .await
            .unwrap();
837
        assert_eq!(content, Some("".to_string()));
838
839
840
841
842
843
844
845
846
847
848
849
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

850
851
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_error_handling() {
852
853
        // Unknown parser string should return an error
        let input = r#"{"name": "get_weather", "arguments": {"location": "San Francisco, CA"}}"#;
854
        let result = detect_and_parse_tool_call(input, Some("unknown_parser")).await;
855
856
857
858
859
860
861
862
863
864
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("is not implemented"),
            "Unexpected error message: {}",
            err
        );

        // Known parser, but invalid input (not JSON) should return Ok(None)
        let input = "not a json";
865
866
867
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
868
869
        assert_eq!(content, Some("not a json".to_string()));
        assert!(result.is_empty());
870
871
872

        // Known parser, but valid JSON with wrong shape should return Ok(None)
        let input = r#"{"foo": "bar"}"#;
873
874
875
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
876
877
        assert_eq!(content, Some(r#"{"foo": "bar"}"#.to_string()));
        assert!(result.is_empty());
878
879
    }

880
    #[tokio::test]
881
    #[ignore]
882
    async fn test_internlm_internlm2_5_7b_chat_simple() {
883
884
885
886
887
        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:

- **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.

Remember, 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."#;
888
889
890
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::default())
            .await
            .unwrap();
891
        assert_eq!(content, Some(input.to_string()));
892
        assert!(result.is_empty()); // This model doesn't produce tool calls
893
894
    }

895
    #[tokio::test]
896
    #[ignore]
897
    async fn test_ai21labs_ai21_jamba_15_mini_simple() {
898
899
900
901
902
903
904
905
906
907
908
909
        let input = r#" [
    {"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}
]"#;
        let config = ToolCallConfig {
            format: ToolCallParserType::Json,
            json: JsonParserConfig {
                tool_call_start_tokens: vec![],
                tool_call_end_tokens: vec![],
                arguments_keys: vec!["arguments".to_string()],
                ..Default::default()
            },
        };
910
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
911
        assert_eq!(content, Some("".to_string()));
912
913
914
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
915
916
917
918
919
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

920
    #[tokio::test]
921
    #[ignore]
922
    async fn test_salesforce_llama_xlam_2_8b_fc_r_simple() {
923
924
925
926
927
928
929
930
931
932
        let input = r#"[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]"#;
        let config = ToolCallConfig {
            format: ToolCallParserType::Json,
            json: JsonParserConfig {
                tool_call_start_tokens: vec![],
                tool_call_end_tokens: vec![],
                arguments_keys: vec!["arguments".to_string()],
                ..Default::default()
            },
        };
933
        let (result, content) = try_tool_call_parse(input, &config).await.unwrap();
934
        assert_eq!(content, Some("".to_string()));
935
936
937
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
938
939
940
941
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }
942

943
944
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci() {
945
        let input = r#"<TOOLCALL>[{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}}]</TOOLCALL>"#;
946
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
947
        assert_eq!(content, Some("".to_string()));
948
949
950
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
951
952
953
954
955
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

956
957
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci_multiple() {
958
        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>"#;
959
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
960
961
962
963
964
965
966
967
968
969
970
971
972
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

973
974
975
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_nemotron_deci_multiple_with_normal_text()
     {
976
        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>"#;
977
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
978
        assert_eq!(content, Some("Hey How are you?".to_string()));
979
980
981
982
983
984
985
986
987
988
989
990
        assert!(!result.is_empty());
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York, NY");
        assert_eq!(args["unit"], "fahrenheit");
    }

991
992
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag() {
993
        let input = r#"<|python_tag|>{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
994
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
995
996
997
998
999
1000
1001
1002
1003
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

1004
1005
1006
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag_with_normal_text()
     {
1007
        let input = r#"Hey How are you? <|python_tag|>{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1008
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1009
        assert_eq!(content, Some("Hey How are you?".to_string()));
1010
1011
1012
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
1013
1014
1015
1016
1017
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

1018
1019
1020
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_with_python_tag_with_new_lines()
     {
1021
1022
1023
1024
1025
1026
1027
1028
        let input = r#"
        <|python_tag|>
        {"name":
        "get_weather",
         "arguments":
          {"location": "San Francisco, CA",
          "unit": "fahrenheit" }}
        "#;
1029
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1030
        assert_eq!(content, Some("".to_string()));
1031
1032
1033
1034
1035
1036
1037
1038
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

1039
1040
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag_multiple_with_new_lines()
1041
     {
1042
1043
1044
1045
1046
        let input = r#"
        {"name": "get_weather", "arguments":
         {"location": "San Francisco, CA",
          "unit": "fahrenheit" }}
        "#;
1047
        let (result, content) = detect_and_parse_tool_call(input, None).await.unwrap();
1048
        assert_eq!(content, Some("".to_string()));
1049
1050
1051
1052
1053
1054
1055
1056
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

1057
1058
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag() {
1059
        let input = r#"{ "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1060
1061
1062
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
            .await
            .unwrap();
1063
1064
1065
1066
1067
1068
1069
1070
1071
        assert_eq!(content, Some("".to_string()));
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }

1072
1073
    #[tokio::test]
    async fn test_detect_and_parse_tool_call_default_parser_llama3_json_without_python_tag_with_normal_text()
1074
1075
     {
        let input = r#"Hey How are you? { "name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "fahrenheit" } }"#;
1076
1077
1078
        let (result, content) = try_tool_call_parse(input, &ToolCallConfig::mistral())
            .await
            .unwrap();
1079
        assert_eq!(content, Some("Hey How are you?".to_string()));
1080
1081
1082
        assert!(!result.is_empty());
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
1083
1084
1085
1086
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "fahrenheit");
    }
1087

1088
1089
    #[tokio::test]
    async fn test_phi4_single_function_call() {
1090
1091
        let input =
            r#"functools[{"name": "get_country_capital", "arguments": {"country": "Poland"}}]"#;
1092
1093
1094
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1095
1096
1097
1098
1099
1100
1101
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_country_capital");
        assert_eq!(args["country"], "Poland");
    }

1102
1103
    #[tokio::test]
    async fn test_phi4_single_function_call_with_normal_text() {
1104
        let input = r#"Hey How are you? functools[{"name": "get_country_capital", "arguments": {"country": "Poland"}}]"#;
1105
1106
1107
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1108
        assert_eq!(content, Some("Hey How are you?".to_string()));
1109
1110
1111
1112
1113
1114
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_country_capital");
        assert_eq!(args["country"], "Poland");
    }

1115
1116
    #[tokio::test]
    async fn test_phi4_multiple_function_calls_simple_arguments() {
1117
1118
1119
1120
        let input = r#"functools[
  {"name": "get_country_capital", "arguments": {"country": "Poland"}},
  {"name": "get_population", "arguments": {"city": "Warsaw"}}
]"#;
1121
1122
1123
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 2);

        let (name1, args1) = extract_name_and_args(result[0].clone());
        assert_eq!(name1, "get_country_capital");
        assert_eq!(args1["country"], "Poland");

        let (name2, args2) = extract_name_and_args(result[1].clone());
        assert_eq!(name2, "get_population");
        assert_eq!(args2["city"], "Warsaw");
    }

1136
1137
    #[tokio::test]
    async fn test_phi4_multiple_function_calls_simple_arguments_with_normal_text() {
1138
1139
1140
1141
        let input = r#"Hey How are you? functools[
  {"name": "get_country_capital", "arguments": {"country": "Poland"}},
  {"name": "get_population", "arguments": {"city": "Warsaw"}}
]"#;
1142
1143
1144
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1145
        assert_eq!(content, Some("Hey How are you?".to_string()));
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
        assert_eq!(result.len(), 2);

        let (name1, args1) = extract_name_and_args(result[0].clone());
        assert_eq!(name1, "get_country_capital");
        assert_eq!(args1["country"], "Poland");

        let (name2, args2) = extract_name_and_args(result[1].clone());
        assert_eq!(name2, "get_population");
        assert_eq!(args2["city"], "Warsaw");
    }

1157
1158
    #[tokio::test]
    async fn test_phi4_single_function_call_nested_json_arguments() {
1159
1160
1161
        let input = r#"functools[{"name": "get_weather_forecast", "arguments":
        {"location": {"city": "San Francisco",
        "state": "CA"}, "date": "2023-10-05"}}]"#;
1162
1163
1164
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1165
1166
1167
1168
1169
1170
1171
1172
1173
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather_forecast");
        assert_eq!(args["date"], "2023-10-05");
        assert_eq!(args["location"]["city"], "San Francisco");
        assert_eq!(args["location"]["state"], "CA");
    }

1174
1175
    #[tokio::test]
    async fn test_phi4_single_function_call_nested_json_arguments_with_normal_text() {
1176
1177
1178
        let input = r#"Hey How are you? functools[{"name": "get_weather_forecast", "arguments":
        {"location": {"city": "San Francisco",
        "state": "CA"}, "date": "2023-10-05"}}]"#;
1179
1180
1181
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1182
        assert_eq!(content, Some("Hey How are you?".to_string()));
1183
1184
1185
1186
1187
1188
1189
1190
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather_forecast");
        assert_eq!(args["date"], "2023-10-05");
        assert_eq!(args["location"]["city"], "San Francisco");
        assert_eq!(args["location"]["state"], "CA");
    }

1191
1192
    #[tokio::test]
    async fn test_phi4_function_call_with_parameters_instead_of_arguments() {
1193
1194
        let input = r#"functools[{"name": "calculate_distance",
         "parameters": {"from": "New York", "to": "Los Angeles"}}]"#;
1195
1196
1197
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1198
1199
1200
1201
1202
1203
1204
1205
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "calculate_distance");
        assert_eq!(args["from"], "New York");
        assert_eq!(args["to"], "Los Angeles");
    }

1206
1207
    #[tokio::test]
    async fn test_phi4_function_call_with_parameters_instead_of_arguments_with_normal_text() {
1208
1209
        let input = r#"Hey How are you? functools[{"name": "calculate_distance",
         "parameters": {"from": "New York", "to": "Los Angeles"}}]"#;
1210
1211
1212
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
1213
        assert_eq!(content, Some("Hey How are you?".to_string()));
1214
1215
1216
1217
1218
1219
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "calculate_distance");
        assert_eq!(args["from"], "New York");
        assert_eq!(args["to"], "Los Angeles");
    }
1220

1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
    #[tokio::test]
    async fn test_phi4_token_leak_reproduction() {
        // Reproduce the issue where "functools" appears in content field
        // This might happen when there's malformed JSON or parsing issues
        let input = r#"functools{"name": "get_weather","arguments":{"location":"San Francisco"}}"#;
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        // Content should be empty, not contain "functools"
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco");
    }

    #[tokio::test]
    async fn test_phi4_token_leak_edge_case() {
        // Test the case where only the token appears without JSON
        // This case is less critical but shouldn't leak the full token
        let input = r#"functools"#;
        let (result, _content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        // Content may contain the token if no valid JSON follows, but shouldn't crash
        // The important thing is that no tool calls are returned
        assert_eq!(result.len(), 0); // No tool calls found
        // Content behavior is less critical for this edge case
    }

    #[tokio::test]
    async fn test_phi4_token_with_invalid_json() {
        // Test the case where token is followed by invalid JSON
        let input = r#"functools{invalid json}"#;
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        // Content should be empty, not contain "functools" or leak the token
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 0); // No tool calls found due to invalid JSON
    }

    #[tokio::test]
    async fn test_phi4_streaming_partial_tokens() {
        // Test that our fix handles the actual streaming scenario described by the user
        // Where "fun", "ct", "ools" arrive as separate chunks

        // Test that "fun" is detected as a potential tool call start (for streaming jailing)
        let config = super::get_tool_parser_map().get("phi4").unwrap();

        // Test detection of partial tokens
        use super::super::json::detect_tool_call_start_json;
        assert!(
            detect_tool_call_start_json("fun", &config.json),
            "'fun' should be detected as potential start"
        );
        assert!(
            detect_tool_call_start_json("f", &config.json),
            "'f' should be detected as potential start"
        );
        assert!(
            detect_tool_call_start_json("func", &config.json),
            "'func' should be detected as potential start"
        );
        assert!(
            detect_tool_call_start_json("functo", &config.json),
            "'functo' should be detected as potential start"
        );

        // Test that unrelated text is not detected
        assert!(
            !detect_tool_call_start_json("hello", &config.json),
            "'hello' should not be detected"
        );
        assert!(
            !detect_tool_call_start_json("xyz", &config.json),
            "'xyz' should not be detected"
        );
    }

    #[tokio::test]
    async fn test_phi4_false_positive_words() {
        // Test that words like "funk" or text starting with "func" but not "functools"
        // are correctly treated as normal content, not tool calls

        let input = r#"funk music is great"#;
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        // Should be treated as normal content, not tool call
        assert_eq!(
            result.len(),
            0,
            "No tool calls should be found in 'funk music is great'"
        );
        assert_eq!(
            content,
            Some("funk music is great".to_string()),
            "Content should contain the original text"
        );
    }

    #[tokio::test]
    async fn test_phi4_partial_but_complete_words() {
        // Test words that start with "func" but are not "functools"

        let input = r#"The function works well"#;
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        assert_eq!(
            result.len(),
            0,
            "No tool calls should be found in 'The function works well'"
        );
        assert_eq!(content, Some("The function works well".to_string()));

        let input = r#"functional programming"#;
        let (result, content) = detect_and_parse_tool_call(input, Some("phi4"))
            .await
            .unwrap();
        assert_eq!(
            result.len(),
            0,
            "No tool calls should be found in 'functional programming'"
        );
        assert_eq!(content, Some("functional programming".to_string()));
    }

    #[tokio::test]
    async fn test_phi4_funk_variations() {
        // Test various "funk" related words to ensure they're not treated as tool calls

        let test_cases = vec![
            "funk",
            "funky",
            "funktion", // German word for function
            "funked",
            "I love funk music",
            "This is funky stuff",
        ];

        for test_input in test_cases {
            let (result, content) = detect_and_parse_tool_call(test_input, Some("phi4"))
                .await
                .unwrap();
            assert_eq!(
                result.len(),
                0,
                "No tool calls should be found in '{}'",
                test_input
            );
            assert_eq!(
                content,
                Some(test_input.to_string()),
                "Content should match input for '{}'",
                test_input
            );
        }
    }

    #[tokio::test]
    async fn test_phi4_func_but_not_functools() {
        // Test words starting with "func" that are complete words, not partial "functools"

        let test_cases = vec![
            "func()",  // Programming syntax
            "funcdef", // Python keyword variant
            "functions are useful",
            "functionally speaking",
        ];

        for test_input in test_cases {
            let (result, content) = detect_and_parse_tool_call(test_input, Some("phi4"))
                .await
                .unwrap();
            assert_eq!(
                result.len(),
                0,
                "No tool calls should be found in '{}'",
                test_input
            );
            assert_eq!(
                content,
                Some(test_input.to_string()),
                "Content should match input for '{}'",
                test_input
            );
        }
    }

1412
1413
    #[tokio::test]
    async fn test_pythonic_parser_basic_with_constants() {
1414
        let input = r#"[get_weather(location="San Francisco", unit="fahrenheit"), get_weather(location="New York", unit="fahrenheit")]"#;
1415
1416
1417
        let (result, content) = detect_and_parse_tool_call(input, Some("pythonic"))
            .await
            .unwrap();
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York");
        assert_eq!(args["unit"], "fahrenheit");
    }

1430
    #[tokio::test]
1431
    #[ignore]
1432
    async fn test_pythonic_parser_with_constants_and_normal_text() {
1433
        let input = r#"Hey How are you? [get_weather(location="San Francisco", unit="fahrenheit"), get_weather(location="New York", unit="fahrenheit")]"#;
1434
1435
1436
        let (result, content) = detect_and_parse_tool_call(input, Some("pythonic"))
            .await
            .unwrap();
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
        assert_eq!(content, Some("Hey How are you?".to_string()));
        assert_eq!(result.len(), 2);

        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco");
        assert_eq!(args["unit"], "fahrenheit");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "New York");
        assert_eq!(args["unit"], "fahrenheit");
    }
1449

1450
1451
    #[tokio::test]
    async fn test_harmony_parser_basic() {
1452
1453
1454
1455
1456
        let input = r#"
        <|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"}<|call|>
        "#;
1457
1458
1459
        let (result, content) = detect_and_parse_tool_call(input, Some("harmony"))
            .await
            .unwrap();
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
        assert_eq!(
            content,
            Some("Need to use function get_current_weather.".to_string())
        );
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_current_weather");
        assert_eq!(args["location"], "San Francisco");
        assert_eq!(args["unit"], "fahrenheit");
    }
1470

1471
1472
    #[tokio::test]
    async fn test_deepseek_v3_1_parser_basic() {
1473
        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|>"#;
1474
1475
1476
        let (result, content) = detect_and_parse_tool_call(input, Some("deepseek_v3_1"))
            .await
            .unwrap();
1477
1478
1479
1480
1481
1482
1483
1484
1485
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 2);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_current_weather");
        assert_eq!(args["location"], "Tokyo");
        let (name, args) = extract_name_and_args(result[1].clone());
        assert_eq!(name, "get_current_weather");
        assert_eq!(args["location"], "Paris");
    }
1486

1487
1488
    #[tokio::test]
    async fn test_hermes_parser_without_new_line() {
1489
1490
        let input = r#"<tool_call>{"name": "get_weather", "arguments": {"location": "San Francisco, CA", "unit": "celsius"}}</tool_call>"
        "#;
1491
1492
1493
        let (result, content) = detect_and_parse_tool_call(input, Some("hermes"))
            .await
            .unwrap();
1494
1495
1496
1497
1498
1499
1500
        assert_eq!(content, Some("".to_string()));
        assert_eq!(result.len(), 1);
        let (name, args) = extract_name_and_args(result[0].clone());
        assert_eq!(name, "get_weather");
        assert_eq!(args["location"], "San Francisco, CA");
        assert_eq!(args["unit"], "celsius");
    }
1501
}
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565

#[cfg(test)]
// Just e2e tests to test the flow. Detailed tests are covered in the individual parsers
mod detect_parser_tests {
    use super::*;

    #[test]
    fn test_e2e_detect_tool_call_start_harmony() {
        let text = r#"<|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json"#;
        let result = detect_tool_call_start(text, Some("harmony")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_hermes() {
        let text = r#"{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
        let result = detect_tool_call_start(text, Some("hermes")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_pythonic() {
        let text = r#"foo(a=1, b=2), bar(x=3)]"#;
        let result = detect_tool_call_start(text, Some("pythonic")).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_nemotron_deci() {
        let text = r#"<TOOLCALL>[{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}]</TOOLCALL>"#;
        let result = detect_tool_call_start(text, Some("nemotron_deci")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_phi4() {
        let text =
            r#"functools{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
        let result = detect_tool_call_start(text, Some("phi4")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_llama3_json() {
        let text = r#"<|python_tag|>{ "name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
        let result = detect_tool_call_start(text, Some("llama3_json")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_mistral() {
        let text =
            r#"[TOOL_CALLS]{"name": "get_current_weather", "parameters": {"location": "Tokyo"}}"#;
        let result = detect_tool_call_start(text, Some("mistral")).unwrap();
        assert!(result);
    }

    #[test]
    fn test_e2e_detect_tool_call_start_deepseek_v3_1() {
        let text = r#"<|tool▁calls▁begin|><|tool▁call▁begin|>get_current_weather{"location": "Tokyo"}<|tool▁call▁end|>"#;
        let result = detect_tool_call_start(text, Some("deepseek_v3_1")).unwrap();
        assert!(result);
    }
}