tool_parser_gpt_oss.rs 6.41 KB
Newer Older
1
2
//! GPT-OSS Parser Integration Tests

3
4
5
6
use sglang_router_rs::tool_parser::{GptOssParser, ToolParser};

mod common;
use common::create_test_tools;
7
8
9
10
11
12
13
14
15

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

    let input = r#"Let me search for that information.
<|channel|>commentary to=functions.search<|constrain|>json<|message|>{"query": "rust programming", "limit": 10}<|call|>
Here are the results..."#;

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

20
    let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
21
22
23
24
25
26
27
28
29
30
31
    assert_eq!(args["query"], "rust programming");
    assert_eq!(args["limit"], 10);
}

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

    let input = r#"<|channel|>commentary to=functions.get_weather<|constrain|>json<|message|>{"location": "Paris"}<|call|>commentary
<|channel|>commentary to=functions.search<|constrain|>json<|message|>{"query": "Paris tourism"}<|call|>"#;

32
33
34
35
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 2);
    assert_eq!(tools[0].function.name, "get_weather");
    assert_eq!(tools[1].function.name, "search");
36
37
38
39
40
41
42
43
44
}

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

    let input = r#"<|channel|>commentary to=api.users.create<|constrain|>json<|message|>{"name": "John", "email": "john@example.com"}<|call|>
<|channel|>commentary to=tools.calculator.add<|constrain|>json<|message|>{"x": 10, "y": 20}<|call|>"#;

45
46
47
48
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 2);
    assert_eq!(tools[0].function.name, "create"); // Should extract last part
    assert_eq!(tools[1].function.name, "add");
49
50
51
52
53
54
55
56
}

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

    let input = r#"<|start|>assistant<|channel|>commentary to=functions.test<|constrain|>json<|message|>{"key": "value"}<|call|>"#;

57
58
59
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "test");
60
61
62
63
64
65
66
67
68
}

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

    let input =
        r#"<|channel|>commentary to=functions.get_time<|constrain|>json<|message|>{}<|call|>"#;

69
70
71
72
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "get_time");
    assert_eq!(tools[0].function.arguments, "{}");
73
74
75
76
}

#[tokio::test]
async fn test_gpt_oss_streaming() {
77
78
79
    let tools = create_test_tools();

    let mut parser = GptOssParser::new();
80
81
82
83
84
85
86
87
88
89
90
91
92
93

    // Simulate streaming chunks
    let chunks = vec![
        "<|channel|>commentary to=",
        "functions.calculate",
        "<|constrain|>json<|message|>",
        r#"{"x": 10"#,
        r#", "y": 20}"#,
        "<|call|>",
    ];

    let mut found_complete = false;

    for chunk in chunks {
94
        let result = parser.parse_incremental(chunk, &tools).await.unwrap();
95

96
97
        if !result.calls.is_empty() {
            if let Some(name) = &result.calls[0].name {
98
99
100
101
102
103
                assert_eq!(name, "calculate");
                found_complete = true;
            }
        }
    }

104
    assert!(found_complete);
105
106
107
108
109
110
111
}

#[test]
fn test_gpt_oss_format_detection() {
    let parser = GptOssParser::new();

    // Should detect GPT-OSS format
112
113
114
    assert!(parser.has_tool_markers("<|channel|>commentary to="));
    assert!(parser.has_tool_markers("<|channel|>commentary"));
    assert!(parser.has_tool_markers("text with <|channel|>commentary to= marker"));
115
116

    // Should not detect other formats
117
118
119
    assert!(!parser.has_tool_markers("[TOOL_CALLS]"));
    assert!(!parser.has_tool_markers("<tool_call>"));
    assert!(!parser.has_tool_markers("plain text"));
120
121
122
123
124
125
126
127
}

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

    let input = r#"<|channel|>commentary to=functions.test  <|constrain|>json<|message|>{"key": "value"}<|call|>"#;

128
129
130
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "test");
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
}

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

    let input = r#"<|channel|>commentary to=functions.process<|constrain|>json<|message|>{
    "nested": {
        "data": [1, 2, 3],
        "config": {
            "enabled": true
        }
    }
}<|call|>"#;

146
147
148
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "process");
149

150
    let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
151
152
153
154
155
156
157
158
159
160
161
    assert!(args["nested"]["data"].is_array());
    assert_eq!(args["nested"]["config"]["enabled"], true);
}

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

    // Python should extract commentary as normal text
    let input = r#"<|channel|>commentary<|message|>**Action plan**: 1. Do X 2. Do Y<|end|>"#;

162
163
164
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 0); // No tool calls
                                // TODO: Verify normal text = "**Action plan**: 1. Do X 2. Do Y"
165
166
167
168
169
170
171
172
173
}

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

    let input = r#"<|channel|>commentary to=functions.test<|constrain|>json<|message|>{"x": 1}<|call|>
<|channel|>final<|message|>The result is calculated.<|return|>"#;

174
175
176
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "test");
177
178
179
180
181
182
183
184
185
186
187
    // TODO: Verify normal text = "The result is calculated."
}

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

    let input = r#"<|channel|>commentary<|message|>Let me think<|end|>
<|channel|>commentary to=functions.calc<|constrain|>json<|message|>{"x": 5}<|call|>
<|channel|>commentary<|message|>Processing...<|end|>"#;

188
189
190
    let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
    assert_eq!(tools.len(), 1);
    assert_eq!(tools[0].function.name, "calc");
191
192
    // TODO: Verify normal text = "Let me think Processing..."
}