From da95df8a446858f39e98541e1bfe1d7d4f84f14a Mon Sep 17 00:00:00 2001 From: tarunag10 Date: Mon, 1 Jun 2026 14:11:47 +0100 Subject: [PATCH] improve schema error diagnostics --- src/agents/function_schema.py | 14 ++++++++++++++ src/agents/strict_schema.py | 5 ++++- tests/test_function_schema.py | 19 +++++++++++++++++++ tests/test_strict_schema.py | 14 ++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/agents/function_schema.py b/src/agents/function_schema.py index 8fe52df320..97b5350a75 100644 --- a/src/agents/function_schema.py +++ b/src/agents/function_schema.py @@ -18,6 +18,14 @@ from .strict_schema import ensure_strict_json_schema from .tool_context import ToolContext +_PYDANTIC_PROTECTED_FIELD_NAMES = { + "model_dump", + "model_dump_json", + "model_validate", + "model_validate_json", + "model_validate_strings", +} + @dataclass class FuncSchema: @@ -319,6 +327,12 @@ def function_schema( fields: dict[str, Any] = {} for name, param in filtered_params: + if name in _PYDANTIC_PROTECTED_FIELD_NAMES: + raise UserError( + f"Function parameter {name!r} conflicts with a reserved Pydantic BaseModel " + "method name. Rename the parameter before using it as a function tool." + ) + ann = type_hints.get(name, param.annotation) default = param.default diff --git a/src/agents/strict_schema.py b/src/agents/strict_schema.py index 8ef6701453..30848e3327 100644 --- a/src/agents/strict_schema.py +++ b/src/agents/strict_schema.py @@ -156,7 +156,10 @@ def resolve_ref(*, root: dict[str, object], ref: str) -> object: path = ref[2:].split("/") resolved = root for key in path: - value = resolved[key] + try: + value = resolved[key] + except KeyError as exc: + raise ValueError(f"Unable to resolve $ref {ref!r}: missing key {key!r}") from exc assert is_dict(value), ( f"encountered non-dictionary entry while resolving {ref} - {resolved}" ) diff --git a/tests/test_function_schema.py b/tests/test_function_schema.py index 9771bda99d..aa99c25557 100644 --- a/tests/test_function_schema.py +++ b/tests/test_function_schema.py @@ -399,6 +399,25 @@ def func(a: int, context: RunContextWrapper) -> None: function_schema(func, use_docstring_info=False) +@pytest.mark.parametrize( + "param_name", + [ + "model_dump", + "model_dump_json", + "model_validate", + "model_validate_json", + "model_validate_strings", + ], +) +def test_pydantic_protected_parameter_names_raise_user_error(param_name: str) -> None: + code = f"def protected_param({param_name}: str, query: str) -> str:\n return query" + namespace: dict[str, Any] = {} + exec(code, namespace) + + with pytest.raises(UserError, match=f"Function parameter '{param_name}' conflicts"): + function_schema(namespace["protected_param"], use_docstring_info=False) + + def test_var_positional_tuple_annotation(): # When a function has a var-positional parameter annotated with a tuple type, # function_schema() should convert it into a field with type List[]. diff --git a/tests/test_strict_schema.py b/tests/test_strict_schema.py index e9e5f1541b..e9132d1bac 100644 --- a/tests/test_strict_schema.py +++ b/tests/test_strict_schema.py @@ -145,6 +145,20 @@ def test_invalid_ref_format(): ensure_strict_json_schema(schema) +def test_missing_ref_path_raises_clear_value_error(): + schema = { + "type": "object", + "properties": { + "data": {"$ref": "#/$defs/SomeType", "description": "desc"}, + }, + } + + with pytest.raises( + ValueError, match=r"Unable to resolve \$ref '#/\$defs/SomeType': missing key '\$defs'" + ): + ensure_strict_json_schema(schema) + + def test_chained_ref_with_sibling_keys_is_resolved(): # When a $ref points to a definition that is itself just a $ref (a chained alias), # and the original $ref has sibling keys (like "description"), the chain must be