Skip to content

feat(function_schema): add enforce_type_annotations flag for stricter schema validation with type enforcement, clearer errors, and updated docstrings #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

mshsheikh
Copy link
Contributor

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:

raise ValueError(
    f"Parameter '{name}' must be type-annotated. Example: def func({name}: str)"
)

Changes Made

  1. New Parameter:
   def function_schema(
       func: Callable[..., Any],
       enforce_type_annotations: bool = False,  # New parameter
       ...
   ):
  1. 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).
  1. Docstring Updates:
  • Added detailed documentation for enforce_type_annotations:
     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`.
  1. 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:

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:

# 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

… 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
```
@seratch seratch added enhancement New feature or request feature:core labels Jul 14, 2025
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having unit tests would be appreciated too

@@ -186,6 +186,7 @@ def generate_func_documentation(

def function_schema(
func: Callable[..., Any],
enforce_type_annotations: bool = False,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding this option may be a good one, but adding here (=2nd arg) is a breaking change. new arguments must be added as the last one when a method accepts both args and keyword args.

@mshsheikh mshsheikh requested a review from seratch July 14, 2025 15:05
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feature:core
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants