Unverified Commit 0ba80f6a authored by Neal Vaidya's avatar Neal Vaidya Committed by GitHub
Browse files

fix: Handle anyOf/oneOf parameter schemas in Qwen3Coder tool parser (#7847)


Signed-off-by: default avatarNeal Vaidya <nealv@nvidia.com>
parent 6e0d62d7
...@@ -341,13 +341,25 @@ fn convert_param_value( ...@@ -341,13 +341,25 @@ fn convert_param_value(
return Value::String(param_value); return Value::String(param_value);
} }
// Get the type from schema // Get the type from schema.
let param_type = param_config // If a parameter uses "anyOf"/"oneOf" instead of a direct "type", there is no
.get(param_name) // top-level "type" key. Treat it as "object" so the value goes through JSON
// parsing rather than being returned as a double-encoded string.
let param_schema = param_config.get(param_name);
let param_type = param_schema
.and_then(|v| v.get("type")) .and_then(|v| v.get("type"))
.and_then(|t| t.as_str()) .and_then(|t| t.as_str())
.unwrap_or("string") .map(|t| t.to_lowercase())
.to_lowercase(); .unwrap_or_else(|| {
if param_schema
.map(|v| v.get("anyOf").is_some() || v.get("oneOf").is_some())
.unwrap_or(false)
{
"object".to_string()
} else {
"string".to_string()
}
});
// The follow `match` block follows this rough pattern for each block: // The follow `match` block follows this rough pattern for each block:
// 1. Match `param_type` against predefined string representations of each type, // 1. Match `param_type` against predefined string representations of each type,
...@@ -924,6 +936,60 @@ rust programming ...@@ -924,6 +936,60 @@ rust programming
assert_eq!(args["bool_param"], false); assert_eq!(args["bool_param"], false);
} }
#[test]
fn test_anyof_param_parsed_as_object_not_string() {
// When a tool parameter uses "anyOf" instead of a direct "type", the value
// should be JSON-parsed (treated as object), not double-encoded as a string.
// Regression test for: https://github.com/vllm-project/vllm/pull/36032
let tools = vec![ToolDefinition {
name: "get_weather".to_string(),
parameters: Some(serde_json::json!({
"type": "object",
"required": ["location"],
"properties": {
"location": {
"anyOf": [
{
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
},
{
"type": "object",
"properties": {
"lat": {"type": "number"},
"lon": {"type": "number"}
},
"required": ["lat", "lon"]
}
]
}
}
})),
}];
let input = r#"<tool_call>
<function=get_weather>
<parameter=location>
{"city": "Paris"}
</parameter>
</function>
</tool_call>"#;
let (calls, _) =
try_tool_call_parse_xml(input, &XmlParserConfig::default(), Some(&tools)).unwrap();
assert_eq!(calls.len(), 1);
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
// Must be a proper object, not a double-encoded string like "{\"city\": \"Paris\"}"
assert!(
args["location"].is_object(),
"Expected location to be an object, got: {}",
args["location"]
);
assert_eq!(args["location"]["city"], "Paris");
}
#[test] #[test]
fn test_no_schema_fallback_behavior() { fn test_no_schema_fallback_behavior() {
// Without schema, behavior should match old safe_parse_value logic // Without schema, behavior should match old safe_parse_value logic
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment