From ffc9dab57d4b3dcd88c3344f3e1ccc805fbbafd9 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Sun, 13 Jul 2025 19:42:43 +0500 Subject: [PATCH 1/4] feat(function_schema): add enforce_type_annotations flag for stricter schema validation with type enforcement, clearer errors, and updated docstrings Problem: Previously, the `function_schema` function silently defaulted unannotated parameters to `Any`, leading to: - Ambiguous JSON schemas (`"type": "any"`) - Potential runtime errors due to invalid inputs - Reduced reliability of LLM tool calls This made it harder to ensure type safety without manual validation. Solution: This PR introduces the `enforce_type_annotations` flag to `function_schema`, allowing developers to: 1. Enforce type annotations by setting `enforce_type_annotations=True`, which raises a `ValueError` for unannotated parameters. 2. Maintain backward compatibility by defaulting to `Any` when `enforce_type_annotations=False` (default). Example error message: ```python raise ValueError( f"Parameter '{name}' must be type-annotated. Example: def func({name}: str)" ) ``` Changes Made 1. New Parameter: ```python def function_schema( func: Callable[..., Any], enforce_type_annotations: bool = False, # New parameter ... ): ``` 2. Validation Logic: - If `enforce_type_annotations=True`, unannotated parameters now raise a `ValueError` with a clear example. - Falls back to `Any` if disabled (default behavior preserved). 3. Docstring Updates: - Added detailed documentation for `enforce_type_annotations`: ```python Args: enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. If False (default), unannotated parameters are assumed to be of type `Any`. ``` 4. Error Message Improvements: - Clear guidance for developers: `Example: def func(x: str)` Backward Compatibility: - Preserved: Existing code continues to work as-is (default `enforce_type_annotations=False`). - Opt-in: Type annotation enforcement is optional, allowing gradual adoption. Testing Guidance: To verify the change: ```python def test_enforce_type_annotations(): def func(x): ... with pytest.raises(ValueError): function_schema(func, enforce_type_annotations=True) # Should not raise function_schema(func, enforce_type_annotations=False) ``` Impact: - Type Safety: Prevents ambiguous schemas and improves LLM tool reliability. - Developer Experience: Clear error messages guide users to fix missing annotations. - Flexibility: Maintains backward compatibility while enabling stricter validation. Example Usage: ```python # Strict mode: raises error for unannotated params schema = function_schema(my_func, enforce_type_annotations=True) # Default mode: works as before schema = function_schema(my_func) # silently defaults to Any ``` --- src/agents/function_schema.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/agents/function_schema.py b/src/agents/function_schema.py index e1a91e189..e3041670b 100644 --- a/src/agents/function_schema.py +++ b/src/agents/function_schema.py @@ -186,6 +186,7 @@ def generate_func_documentation( def function_schema( func: Callable[..., Any], + enforce_type_annotations: bool = False, docstring_style: DocstringStyle | None = None, name_override: str | None = None, description_override: str | None = None, @@ -198,6 +199,8 @@ def function_schema( Args: func: The function to extract the schema from. + enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. + If False (default), unannotated parameters are assumed to be of type `Any`. docstring_style: The style of the docstring to use for parsing. If not provided, we will attempt to auto-detect the style. name_override: If provided, use this name instead of the function's `__name__`. @@ -266,9 +269,14 @@ def function_schema( ann = type_hints.get(name, param.annotation) default = param.default - # If there's no type hint, assume `Any` + # Raise an error for unannotated parameters if enforcement is on if ann == inspect._empty: - ann = Any + if enforce_type_annotations: + raise ValueError( + f"Parameter '{name}' must be type-annotated. Example: def func({name}: str)" + ) + else: + ann = Any # Fallback only if enforcement is off # If a docstring param description exists, use it field_description = param_descs.get(name, None) From 00327d991710050fc3e1d63dafdce119fe8f7ceb Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:35:07 +0500 Subject: [PATCH 2/4] feat(function_schema): add enforce_type_annotations flag for type safety Adds a new enforce_type_annotations flag to function_schema to improve type safety: - When enabled (True): Raises an error for unannotated parameters (e.g., def func(x): ...). - When disabled (False): Falls back to Any (default behavior remains unchanged). - Backward compatibility: Positional arguments still work (e.g., function_schema(my_func, "sphinx")). Example Error Message: Parameter 'x' must be type-annotated. Example: def func(x: str) Test Coverage: https://github.com/openai/openai-agents-python/blob/main/tests/test_function_schema.py --- src/agents/function_schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agents/function_schema.py b/src/agents/function_schema.py index e3041670b..aadd803dd 100644 --- a/src/agents/function_schema.py +++ b/src/agents/function_schema.py @@ -186,12 +186,12 @@ def generate_func_documentation( def function_schema( func: Callable[..., Any], - enforce_type_annotations: bool = False, docstring_style: DocstringStyle | None = None, name_override: str | None = None, description_override: str | None = None, use_docstring_info: bool = True, strict_json_schema: bool = True, + enforce_type_annotations: bool = False, ) -> FuncSchema: """ Given a python function, extracts a `FuncSchema` from it, capturing the name, description, @@ -199,8 +199,6 @@ def function_schema( Args: func: The function to extract the schema from. - enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. - If False (default), unannotated parameters are assumed to be of type `Any`. docstring_style: The style of the docstring to use for parsing. If not provided, we will attempt to auto-detect the style. name_override: If provided, use this name instead of the function's `__name__`. @@ -212,6 +210,8 @@ def function_schema( the schema adheres to the "strict" standard the OpenAI API expects. We **strongly** recommend setting this to True, as it increases the likelihood of the LLM providing correct JSON input. + enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. + If False (default), unannotated parameters are assumed to be of type `Any`. Returns: A `FuncSchema` object containing the function's name, description, parameter descriptions, From e1e75aa4e292a96c6e8cb2a95f6281239c3140f2 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:09:55 +0500 Subject: [PATCH 3/4] feat(function_schema): add enforce_type_annotations flag for type safety --- src/agents/function_schema.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/agents/function_schema.py b/src/agents/function_schema.py index aadd803dd..a45579a98 100644 --- a/src/agents/function_schema.py +++ b/src/agents/function_schema.py @@ -5,7 +5,8 @@ import logging import re from dataclasses import dataclass -from typing import Any, Callable, Literal, get_args, get_origin, get_type_hints +from typing import Union, Optional, List, Dict, Any, Callable, Literal, get_args, get_origin, get_type_hints +from typing_extensions import Literal from griffe import Docstring, DocstringSectionKind from pydantic import BaseModel, Field, create_model @@ -24,7 +25,7 @@ class FuncSchema: name: str """The name of the function.""" - description: str | None + description: Union[str, None] """The description of the function.""" params_pydantic_model: type[BaseModel] """A Pydantic model that represents the function's parameters.""" @@ -186,9 +187,9 @@ def generate_func_documentation( def function_schema( func: Callable[..., Any], - docstring_style: DocstringStyle | None = None, - name_override: str | None = None, - description_override: str | None = None, + docstring_style: Union[DocstringStyle, None] = None, + name_override: Union[str, None] = None, + description_override: Union[str, None] = None, use_docstring_info: bool = True, strict_json_schema: bool = True, enforce_type_annotations: bool = False, @@ -210,7 +211,8 @@ def function_schema( the schema adheres to the "strict" standard the OpenAI API expects. We **strongly** recommend setting this to True, as it increases the likelihood of the LLM providing correct JSON input. - enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. + enforce_type_annotations: bool = False + If True, raises a ValueError for any unannotated parameters. If False (default), unannotated parameters are assumed to be of type `Any`. Returns: @@ -277,6 +279,7 @@ def function_schema( ) else: ann = Any # Fallback only if enforcement is off + field_info["type"] = "any" # If a docstring param description exists, use it field_description = param_descs.get(name, None) From 20ec8985ffd6f3aad970b1e374b280644f21a810 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:14:50 +0500 Subject: [PATCH 4/4] feat(function_schema): add enforce_type_annotations flag for type safety Adds a new enforce_type_annotations flag to function_schema to improve type safety: - When enabled (True): Raises an error for unannotated parameters (e.g., def func(x): ...). - When disabled (False): Falls back to Any (default behavior remains unchanged). - Backward compatibility: Positional arguments still work (e.g., function_schema(my_func, "sphinx")). Example Error Message: Parameter 'x' must be type-annotated. Example: def func(x: str) Test Coverage: https://github.com/openai/openai-agents-python/blob/main/tests/test_function_schema.py --- src/agents/function_schema.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/agents/function_schema.py b/src/agents/function_schema.py index a45579a98..aadd803dd 100644 --- a/src/agents/function_schema.py +++ b/src/agents/function_schema.py @@ -5,8 +5,7 @@ import logging import re from dataclasses import dataclass -from typing import Union, Optional, List, Dict, Any, Callable, Literal, get_args, get_origin, get_type_hints -from typing_extensions import Literal +from typing import Any, Callable, Literal, get_args, get_origin, get_type_hints from griffe import Docstring, DocstringSectionKind from pydantic import BaseModel, Field, create_model @@ -25,7 +24,7 @@ class FuncSchema: name: str """The name of the function.""" - description: Union[str, None] + description: str | None """The description of the function.""" params_pydantic_model: type[BaseModel] """A Pydantic model that represents the function's parameters.""" @@ -187,9 +186,9 @@ def generate_func_documentation( def function_schema( func: Callable[..., Any], - docstring_style: Union[DocstringStyle, None] = None, - name_override: Union[str, None] = None, - description_override: Union[str, None] = None, + docstring_style: DocstringStyle | None = None, + name_override: str | None = None, + description_override: str | None = None, use_docstring_info: bool = True, strict_json_schema: bool = True, enforce_type_annotations: bool = False, @@ -211,8 +210,7 @@ def function_schema( the schema adheres to the "strict" standard the OpenAI API expects. We **strongly** recommend setting this to True, as it increases the likelihood of the LLM providing correct JSON input. - enforce_type_annotations: bool = False - If True, raises a ValueError for any unannotated parameters. + enforce_type_annotations: If True, raises a ValueError for any unannotated parameters. If False (default), unannotated parameters are assumed to be of type `Any`. Returns: @@ -279,7 +277,6 @@ def function_schema( ) else: ann = Any # Fallback only if enforcement is off - field_info["type"] = "any" # If a docstring param description exists, use it field_description = param_descs.get(name, None)