fix(anthropic): strip top-level oneOf/allOf/anyOf from tool input_schema
Extends the existing _normalize_tool_input_schema to also drop top-level union keywords that Anthropic's tool schema validator rejects with HTTP 400. Several upstream and plugin tools ship schemas with a top-level oneOf/ allOf/anyOf (common for Pydantic discriminated unions). The existing strip_nullable_unions pass only handles anyOf-with-null patterns; a non-null top-level union keyword sails through and hits the API. Salvage of #16471 — approach folded into the existing normalize helper rather than introducing a parallel _sanitize_input_schema function, to avoid two schema-munging code paths running against the same input. Co-authored-by: Grey0202 <grey0202@users.noreply.github.com>
This commit is contained in:
@@ -1225,6 +1225,14 @@ def _normalize_tool_input_schema(schema: Any) -> Dict[str, Any]:
|
|||||||
``keep_nullable_hint=False`` because the Anthropic validator does not
|
``keep_nullable_hint=False`` because the Anthropic validator does not
|
||||||
recognize the OpenAPI-style ``nullable: true`` extension and strict
|
recognize the OpenAPI-style ``nullable: true`` extension and strict
|
||||||
schema-to-grammar converters may reject unknown keywords.
|
schema-to-grammar converters may reject unknown keywords.
|
||||||
|
|
||||||
|
Top-level ``oneOf``/``allOf``/``anyOf`` are also stripped here: the
|
||||||
|
Anthropic API rejects union keywords at the schema root with a generic
|
||||||
|
HTTP 400. Several upstream and plugin tools ship schemas with one of
|
||||||
|
these keywords at the top level (commonly for Pydantic discriminated
|
||||||
|
unions). If we land here with those keywords still present after
|
||||||
|
nullable-union stripping, drop them and fall back to a plain object
|
||||||
|
schema so the tool still validates at the Anthropic boundary.
|
||||||
"""
|
"""
|
||||||
if not schema:
|
if not schema:
|
||||||
return {"type": "object", "properties": {}}
|
return {"type": "object", "properties": {}}
|
||||||
@@ -1234,6 +1242,12 @@ def _normalize_tool_input_schema(schema: Any) -> Dict[str, Any]:
|
|||||||
normalized = strip_nullable_unions(schema, keep_nullable_hint=False)
|
normalized = strip_nullable_unions(schema, keep_nullable_hint=False)
|
||||||
if not isinstance(normalized, dict):
|
if not isinstance(normalized, dict):
|
||||||
return {"type": "object", "properties": {}}
|
return {"type": "object", "properties": {}}
|
||||||
|
# Strip top-level union keywords that Anthropic's validator rejects.
|
||||||
|
banned = {"oneOf", "allOf", "anyOf"}
|
||||||
|
if banned & normalized.keys():
|
||||||
|
normalized = {k: v for k, v in normalized.items() if k not in banned}
|
||||||
|
if "type" not in normalized:
|
||||||
|
normalized["type"] = "object"
|
||||||
if normalized.get("type") == "object" and not isinstance(normalized.get("properties"), dict):
|
if normalized.get("type") == "object" and not isinstance(normalized.get("properties"), dict):
|
||||||
normalized = {**normalized, "properties": {}}
|
normalized = {**normalized, "properties": {}}
|
||||||
return normalized
|
return normalized
|
||||||
|
|||||||
Reference in New Issue
Block a user