mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 10:14:56 +08:00
fix(proxy): mark converted function tools as non-strict for Responses (#7717)
Anthropic tool schemas treat `required` as an arbitrary subset of `properties`, but the Responses backend strictifies a function tool when `strict` is omitted: optional attributes land in `required` and the model is forced to emit fields the client deliberately left optional (#7713 - Claude Code Agent always emitting the optional `isolation` and losing non-worktree teammates). Set `strict: false` explicitly on the converted function tool in anthropic_to_responses. The hosted web-search branch is untouched, and `parameters` still passes through clean_schema unchanged, so the client-side optionality semantics are preserved end to end. This branch serves both plain Responses providers and the Codex OAuth backend. Validation: with the production hunk reverted, the new test asserting strict=false fails (field absent); restored, the transform_responses suite and the full --lib run are green. Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
This commit is contained in:
1 parent
3af55c3973
commit
02759c67a1
1 file changed
+48
@@ -1905,6 +1905,12 @@ pub fn anthropic_to_responses(
|
||||
}
|
||||
let mut response_tool = json!({
|
||||
"type": "function",
|
||||
// Anthropic 工具 schema 允许可选参数(required 是任意子集),而
|
||||
// Responses 后端把省略的 `strict` 按 true 处理:可选属性会被
|
||||
// 严格化进 required,模型被迫输出它本应能省略的字段(#7713 中
|
||||
// Claude Code Agent 的可选 isolation 被强制输出即此形态)。
|
||||
// 显式 false 保留客户端 schema 的可选语义。
|
||||
"strict": false,
|
||||
"name": tool.get("name").and_then(Value::as_str).unwrap_or(""),
|
||||
});
|
||||
// 同 transform.rs:缺失的 description 省略而非输出 null,
|
||||
@@ -3341,6 +3347,8 @@ mod tests {
|
||||
|
||||
let result = anthropic_to_responses(input, None, false, false).unwrap();
|
||||
assert_eq!(result["tools"][0]["type"], "function");
|
||||
// 省略 strict 会被 Responses 后端按 true 严格化,可选参数被强制输出(#7713)
|
||||
assert_eq!(result["tools"][0]["strict"], json!(false));
|
||||
assert_eq!(result["tools"][0]["name"], "get_weather");
|
||||
assert!(result["tools"][0].get("parameters").is_some());
|
||||
assert_eq!(result["tools"][0]["parameters"]["type"], json!("object"));
|
||||
@@ -3352,6 +3360,46 @@ mod tests {
|
||||
assert!(result["tools"][0].get("input_schema").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anthropic_to_responses_marks_function_tools_not_strict() {
|
||||
// #7713:可选参数(required 是任意子集)必须保留可选语义。
|
||||
// 省略 strict 时 Responses 后端按 true 严格化,模型被迫输出 isolation。
|
||||
let input = json!({
|
||||
"model": "gpt-5.6-luna",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "Call the agent"}],
|
||||
"tools": [{
|
||||
"name": "Agent",
|
||||
"description": "Launch a sub-agent",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {"type": "string"},
|
||||
"prompt": {"type": "string"},
|
||||
"isolation": {"type": "string", "enum": ["worktree", "remote"]}
|
||||
},
|
||||
"required": ["description", "prompt"]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
let plain = anthropic_to_responses(input.clone(), None, false, false).unwrap();
|
||||
assert_eq!(plain["tools"][0]["strict"], json!(false));
|
||||
// required 保持为原有子集,strict 语义交由后端保留
|
||||
assert_eq!(
|
||||
plain["tools"][0]["parameters"]["required"],
|
||||
json!(["description", "prompt"])
|
||||
);
|
||||
|
||||
// 同一转换分支也服务 Codex OAuth 后端
|
||||
let oauth = anthropic_to_responses(input, None, true, false).unwrap();
|
||||
assert_eq!(oauth["tools"][0]["strict"], json!(false));
|
||||
assert_eq!(
|
||||
oauth["tools"][0]["parameters"]["required"],
|
||||
json!(["description", "prompt"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anthropic_to_responses_omits_missing_tool_description() {
|
||||
let input = json!({
|
||||
|
||||
Reference in new issue
Block a user