tool_parser_qwen.rs 7.66 KB
Newer Older
1
2
3
4
5
//! Qwen Parser Integration Tests
//!
//! Tests for the Qwen parser which handles <tool_call>...</tool_call> format

use serde_json::json;
6
7
8
9
use sglang_router_rs::tool_parser::{QwenParser, ToolParser};

mod common;
use common::create_test_tools;
10
11
12
13
14
15
16
17

#[tokio::test]
async fn test_qwen_single_tool() {
    let parser = QwenParser::new();
    let input = r#"<tool_call>
{"name": "get_weather", "arguments": {"city": "Beijing", "units": "celsius"}}
</tool_call>"#;

18
19
20
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "get_weather");
21

22
    let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    assert_eq!(args["city"], "Beijing");
    assert_eq!(args["units"], "celsius");
}

#[tokio::test]
async fn test_qwen_multiple_sequential_tools() {
    let parser = QwenParser::new();
    let input = r#"Let me help you with that.
<tool_call>
{"name": "search", "arguments": {"query": "Qwen model"}}
</tool_call>
<tool_call>
{"name": "translate", "arguments": {"text": "Hello", "to": "zh"}}
</tool_call>"#;

38
    let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
39
    assert_eq!(tools.len(), 2);
40
    assert_eq!(normal_text, "Let me help you with that.\n");
41
42
    assert_eq!(tools[0].function.name, "search");
    assert_eq!(tools[1].function.name, "translate");
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
}

#[tokio::test]
async fn test_qwen_pretty_printed_json() {
    let parser = QwenParser::new();
    let input = r#"<tool_call>
{
    "name": "create_document",
    "arguments": {
        "title": "Test Document",
        "content": "This is a test",
        "metadata": {
            "author": "Qwen",
            "tags": ["test", "example"]
        }
    }
}
</tool_call>"#;

62
63
64
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "create_document");
65

66
    let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    assert_eq!(args["metadata"]["author"], "Qwen");
    assert_eq!(args["metadata"]["tags"], json!(["test", "example"]));
}

#[tokio::test]
async fn test_qwen_with_text_between() {
    let parser = QwenParser::new();
    let input = r#"First, let me search for information.
<tool_call>
{"name": "search", "arguments": {"query": "test"}}
</tool_call>

Now I'll translate something.

<tool_call>
{"name": "translate", "arguments": {"text": "world", "to": "es"}}
</tool_call>
Done!"#;

86
    let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
87
    assert_eq!(tools.len(), 2);
88
    assert_eq!(normal_text, "First, let me search for information.\n");
89
90
    assert_eq!(tools[0].function.name, "search");
    assert_eq!(tools[1].function.name, "translate");
91
92
93
94
95
96
97
98
99
}

#[tokio::test]
async fn test_qwen_empty_arguments() {
    let parser = QwenParser::new();
    let input = r#"<tool_call>
{"name": "get_time", "arguments": {}}
</tool_call>"#;

100
101
102
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "get_time");
103
104
105
106
107
108
109
110
111
}

#[tokio::test]
async fn test_qwen_with_newlines_in_strings() {
    let parser = QwenParser::new();
    let input = r#"<tool_call>
{"name": "write_file", "arguments": {"content": "Line 1\nLine 2\nLine 3", "path": "/tmp/test.txt"}}
</tool_call>"#;

112
113
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
114

115
    let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
116
117
118
119
120
121
122
    assert_eq!(args["content"], "Line 1\nLine 2\nLine 3");
}

#[tokio::test]
async fn test_qwen_format_detection() {
    let parser = QwenParser::new();

123
124
125
126
    assert!(parser.has_tool_markers("<tool_call>"));
    assert!(parser.has_tool_markers("Some text <tool_call>\n{"));
    assert!(!parser.has_tool_markers("Just plain text"));
    assert!(!parser.has_tool_markers("{\"name\": \"test\"}")); // Plain JSON
127
128
129
130
131
132
133
134
135
}

#[tokio::test]
async fn test_qwen_incomplete_tags() {
    let parser = QwenParser::new();

    // Missing closing tag
    let input = r#"<tool_call>
{"name": "test", "arguments": {}}"#;
136
137
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 0);
138
139
140
141

    // Missing opening tag
    let input = r#"{"name": "test", "arguments": {}}
</tool_call>"#;
142
143
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 0);
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
}

#[tokio::test]
async fn test_qwen_real_world_output() {
    let parser = QwenParser::new();

    // Actual output from Qwen model
    let input = r#"I'll help you search for information and perform calculations.

<tool_call>
{
    "name": "web_search",
    "arguments": {
        "query": "quantum computing breakthroughs 2024",
        "language": "en",
        "region": "us",
        "safe_search": true
    }
}
</tool_call>

Let me also calculate something for you:

<tool_call>
{
    "name": "calculator",
    "arguments": {
        "expression": "sqrt(144) + 3^2",
        "precision": 2
    }
}
</tool_call>

These tools will provide the information you need."#;

179
    let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
180
    assert_eq!(tools.len(), 2);
181
182
183
184
    assert_eq!(
        normal_text,
        "I'll help you search for information and perform calculations.\n\n"
    );
185
186
    assert_eq!(tools[0].function.name, "web_search");
    assert_eq!(tools[1].function.name, "calculator");
187

188
    let args0: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
189
190
191
192
193
194
    assert_eq!(args0["query"], "quantum computing breakthroughs 2024");
    assert_eq!(args0["safe_search"], true);
}

#[tokio::test]
async fn test_buffer_drain_optimization() {
195
196
197
    let mut parser = QwenParser::new();

    let tools = create_test_tools();
198
199
200

    // First chunk - incomplete tool call
    let chunk1 = "<tool_call>\n{\"name\": \"test1\", ";
201
    let _result = parser.parse_incremental(chunk1, &tools).await.unwrap();
202
203
204
205
    // The important thing is buffer accumulation works

    // Complete first tool and start second
    let chunk2 = "\"arguments\": {}}\n</tool_call><tool_call>\n{\"name\": \"test2\", ";
206
207
208
209
210
211
212
    let result = parser.parse_incremental(chunk2, &tools).await.unwrap();

    if !result.calls.is_empty() {
        if let Some(_name) = &result.calls[0].name {
            assert_eq!(result.calls[0].name.as_ref().unwrap(), "test1");
            // After consuming the first tool, buffer is managed internally
        }
213
214
215
216
    }

    // Complete the second tool
    let chunk3 = "\"arguments\": {\"x\": 1}}\n</tool_call>";
217
    let result = parser.parse_incremental(chunk3, &tools).await.unwrap();
218

219
220
221
222
223
    if !result.calls.is_empty() {
        if let Some(_name) = &result.calls[0].name {
            assert_eq!(result.calls[0].name.as_ref().unwrap(), "test2");
            // Buffer is managed internally
        }
224
225
226
227
228
    }
}

#[tokio::test]
async fn test_buffer_efficiency_with_multiple_tools() {
229
230
231
    let mut parser = QwenParser::new();

    let tools = create_test_tools();
232
233
234
235
236
237
238
239
240
241
242

    // Send multiple complete tools at once
    let input = r#"<tool_call>
{"name": "tool1", "arguments": {"a": 1}}
</tool_call><tool_call>
{"name": "tool2", "arguments": {"b": 2}}
</tool_call><tool_call>
{"name": "tool3", "arguments": {"c": 3}}
</tool_call>"#;

    // This should efficiently process tools using drain() without creating new strings
243
    let result = parser.parse_incremental(input, &tools).await.unwrap();
244
245
246

    // In Phase 2, this will likely parse only the first tool
    // The important thing is that drain() doesn't cause any issues
247
248
249
    if !result.calls.is_empty() {
        if let Some(name) = &result.calls[0].name {
            assert!(["tool1", "tool2", "tool3"].contains(&name.as_str()));
250
251
252
        }
    }
}