fix(tools): keep an empty required: [] in sanitized tool schemas
`_sanitize_node` deleted the `required` key whenever the pruned list came
out empty. Four built-in tools (skills_list, todo, delegate_task,
session_search) declare `required: []`, so they left the sanitizer with no
key at all. Strict OpenAI-compatible proxies read the missing key as
`null` and 400 the whole request ("null is not of type array"), which is
non-retryable and kills the session on its first call.
An empty array is valid for every backend; the pruning was added (34c3e67)
to drop names that are not in `properties`, not to delete the key. Keep the
key with the filtered list, even when that list is empty.
Fixes #111684
Fixes #59386
Co-authored-by: Cr4ckMe <jiqing.liu@whu.edu.cn>
This commit is contained in:
@@ -156,6 +156,20 @@ def test_required_pruned_to_existing_properties():
|
||||
assert out[0]["function"]["parameters"]["required"] == ["name"]
|
||||
|
||||
|
||||
def test_empty_required_key_survives_sanitization():
|
||||
"""A ``required`` list that is empty (or emptied by pruning) is kept as ``[]`` — strict
|
||||
OpenAI-compatible proxies read a missing key as ``null`` and 400 the whole request."""
|
||||
declared_empty = _tool("t", {"type": "object", "properties": {}, "required": []})
|
||||
all_pruned = _tool("u", {
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["missing_field"],
|
||||
})
|
||||
out = sanitize_tool_schemas([declared_empty, all_pruned])
|
||||
assert out[0]["function"]["parameters"]["required"] == []
|
||||
assert out[1]["function"]["parameters"]["required"] == []
|
||||
|
||||
|
||||
def test_well_formed_schema_unchanged():
|
||||
schema = {
|
||||
"type": "object",
|
||||
|
||||
@@ -315,11 +315,9 @@ def _sanitize_node(node: Any, path: str) -> Any:
|
||||
if not isinstance(out.get("properties"), dict):
|
||||
out["properties"] = {}
|
||||
if isinstance(out.get("required"), list):
|
||||
valid = [r for r in out["required"] if isinstance(r, str) and r in out["properties"]]
|
||||
if valid:
|
||||
out["required"] = valid
|
||||
else:
|
||||
del out["required"]
|
||||
# Keep the key even when nothing survives: ``required: []`` is valid everywhere,
|
||||
# while a missing key reads as ``null`` on strict OpenAI-compatible proxies.
|
||||
out["required"] = [r for r in out["required"] if isinstance(r, str) and r in out["properties"]]
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user