2025-08-27 06:18:24 -07:00
|
|
|
|
//! DeepSeek V3 Parser Integration Tests
|
|
|
|
|
|
|
2025-10-02 03:18:50 -07:00
|
|
|
|
use sglang_router_rs::tool_parser::{DeepSeekParser, ToolParser};
|
|
|
|
|
|
|
|
|
|
|
|
mod common;
|
|
|
|
|
|
use common::create_test_tools;
|
2025-08-27 06:18:24 -07:00
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_deepseek_complete_parsing() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
let input = r#"Let me help you with that.
|
|
|
|
|
|
<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>get_weather
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"location": "Tokyo", "units": "celsius"}
|
|
|
|
|
|
```<|tool▁call▁end|><|tool▁calls▁end|>
|
|
|
|
|
|
The weather in Tokyo is..."#;
|
|
|
|
|
|
|
2025-09-29 00:19:30 -07:00
|
|
|
|
let (normal_text, tools) = parser.parse_complete(input).await.unwrap();
|
2025-09-27 15:10:17 -07:00
|
|
|
|
assert_eq!(tools.len(), 1);
|
2025-09-29 00:19:30 -07:00
|
|
|
|
assert_eq!(normal_text, "Let me help you with that.\n");
|
2025-09-27 15:10:17 -07:00
|
|
|
|
assert_eq!(tools[0].function.name, "get_weather");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
|
2025-08-27 06:18:24 -07:00
|
|
|
|
assert_eq!(args["location"], "Tokyo");
|
|
|
|
|
|
assert_eq!(args["units"], "celsius");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_deepseek_multiple_tools() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
let input = r#"<|tool▁calls▁begin|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>search
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"query": "rust programming"}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>translate
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"text": "Hello World", "to": "ja"}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁calls▁end|>"#;
|
|
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
|
|
|
|
|
|
assert_eq!(tools.len(), 2);
|
|
|
|
|
|
assert_eq!(tools[0].function.name, "search");
|
|
|
|
|
|
assert_eq!(tools[1].function.name, "translate");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_deepseek_streaming() {
|
2025-10-02 03:18:50 -07:00
|
|
|
|
let tools = create_test_tools();
|
|
|
|
|
|
|
|
|
|
|
|
let mut parser = DeepSeekParser::new();
|
2025-08-27 06:18:24 -07:00
|
|
|
|
|
|
|
|
|
|
// Simulate streaming chunks
|
|
|
|
|
|
let chunks = vec![
|
|
|
|
|
|
"<|tool▁calls▁begin|><|tool▁call▁begin|>",
|
|
|
|
|
|
"function<|tool▁sep|>get_weather\n",
|
|
|
|
|
|
"```json\n",
|
|
|
|
|
|
r#"{"location": "#,
|
|
|
|
|
|
r#""Beijing", "#,
|
|
|
|
|
|
r#""units": "metric"}"#,
|
|
|
|
|
|
"\n```<|tool▁call▁end|><|tool▁calls▁end|>",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let mut found_name = false;
|
|
|
|
|
|
|
|
|
|
|
|
for chunk in chunks {
|
2025-10-02 03:18:50 -07:00
|
|
|
|
let result = parser.parse_incremental(chunk, &tools).await.unwrap();
|
2025-08-27 06:18:24 -07:00
|
|
|
|
|
2025-10-02 03:18:50 -07:00
|
|
|
|
for call in result.calls {
|
|
|
|
|
|
if let Some(name) = call.name {
|
2025-08-27 06:18:24 -07:00
|
|
|
|
assert_eq!(name, "get_weather");
|
|
|
|
|
|
found_name = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-02 03:18:50 -07:00
|
|
|
|
assert!(found_name, "Should have found tool name during streaming");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_deepseek_nested_json() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
let input = r#"<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>process
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"nested": {
|
|
|
|
|
|
"deep": [1, 2, 3]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```<|tool▁call▁end|><|tool▁calls▁end|>"#;
|
|
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
|
|
|
|
|
|
assert_eq!(tools.len(), 1);
|
|
|
|
|
|
assert_eq!(tools[0].function.name, "process");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
let args: serde_json::Value = serde_json::from_str(&tools[0].function.arguments).unwrap();
|
2025-08-27 06:18:24 -07:00
|
|
|
|
assert!(args["data"]["nested"]["deep"].is_array());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_deepseek_format_detection() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
// Should detect DeepSeek format
|
|
|
|
|
|
assert!(parser.detect_format("<|tool▁calls▁begin|>"));
|
|
|
|
|
|
assert!(parser.detect_format("text with <|tool▁calls▁begin|> marker"));
|
|
|
|
|
|
|
|
|
|
|
|
// Should not detect other formats
|
|
|
|
|
|
assert!(!parser.detect_format("[TOOL_CALLS]"));
|
|
|
|
|
|
assert!(!parser.detect_format("<tool_call>"));
|
|
|
|
|
|
assert!(!parser.detect_format("plain text"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_deepseek_malformed_json_handling() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
// Malformed JSON should be skipped
|
|
|
|
|
|
let input = r#"<|tool▁calls▁begin|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>broken
|
|
|
|
|
|
```json
|
|
|
|
|
|
{invalid json}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>valid
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"key": "value"}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁calls▁end|>"#;
|
|
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
let (_normal_text, tools) = parser.parse_complete(input).await.unwrap();
|
2025-08-27 06:18:24 -07:00
|
|
|
|
// Only the valid tool call should be parsed
|
2025-09-27 15:10:17 -07:00
|
|
|
|
assert_eq!(tools.len(), 1);
|
|
|
|
|
|
assert_eq!(tools[0].function.name, "valid");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
|
async fn test_multiple_tool_calls() {
|
|
|
|
|
|
let parser = DeepSeekParser::new();
|
|
|
|
|
|
|
|
|
|
|
|
let input = r#"<|tool▁calls▁begin|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>get_weather
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"location": "Tokyo"}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁call▁begin|>function<|tool▁sep|>get_weather
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"location": "Paris"}
|
|
|
|
|
|
```<|tool▁call▁end|>
|
|
|
|
|
|
<|tool▁calls▁end|><|end▁of▁sentence|>"#;
|
|
|
|
|
|
|
2025-09-27 15:10:17 -07:00
|
|
|
|
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, "get_weather");
|
2025-08-27 06:18:24 -07:00
|
|
|
|
}
|