Skip to content

Commit aed6359

Browse files
authored
feat: openai#1731 Enable developers to use Annotated types for function tool param description (openai#1753)
This pull request resolves openai#1731
1 parent e486b3a commit aed6359

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

examples/basic/tools.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import asyncio
2+
from typing import Annotated
23

3-
from pydantic import BaseModel
4+
from pydantic import BaseModel, Field
45

56
from agents import Agent, Runner, function_tool
67

78

89
class Weather(BaseModel):
9-
city: str
10-
temperature_range: str
11-
conditions: str
10+
city: str = Field(description="The city name")
11+
temperature_range: str = Field(description="The temperature range in Celsius")
12+
conditions: str = Field(description="The weather conditions")
1213

1314

1415
@function_tool
15-
def get_weather(city: str) -> Weather:
16+
def get_weather(city: Annotated[str, "The city to get the weather for"]) -> Weather:
1617
"""Get the current weather information for a specified city."""
1718
print("[debug] get_weather called")
1819
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
1920

20-
2121
agent = Agent(
2222
name="Hello world",
2323
instructions="You are a helpful agent.",

src/agents/function_schema.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import re
77
from dataclasses import dataclass
8-
from typing import Any, Callable, Literal, get_args, get_origin, get_type_hints
8+
from typing import Annotated, Any, Callable, Literal, get_args, get_origin, get_type_hints
99

1010
from griffe import Docstring, DocstringSectionKind
1111
from pydantic import BaseModel, Field, create_model
@@ -185,6 +185,31 @@ def generate_func_documentation(
185185
)
186186

187187

188+
def _strip_annotated(annotation: Any) -> tuple[Any, tuple[Any, ...]]:
189+
"""Returns the underlying annotation and any metadata from typing.Annotated."""
190+
191+
metadata: tuple[Any, ...] = ()
192+
ann = annotation
193+
194+
while get_origin(ann) is Annotated:
195+
args = get_args(ann)
196+
if not args:
197+
break
198+
ann = args[0]
199+
metadata = (*metadata, *args[1:])
200+
201+
return ann, metadata
202+
203+
204+
def _extract_description_from_metadata(metadata: tuple[Any, ...]) -> str | None:
205+
"""Extracts a human readable description from Annotated metadata if present."""
206+
207+
for item in metadata:
208+
if isinstance(item, str):
209+
return item
210+
return None
211+
212+
188213
def function_schema(
189214
func: Callable[..., Any],
190215
docstring_style: DocstringStyle | None = None,
@@ -219,17 +244,34 @@ def function_schema(
219244
# 1. Grab docstring info
220245
if use_docstring_info:
221246
doc_info = generate_func_documentation(func, docstring_style)
222-
param_descs = doc_info.param_descriptions or {}
247+
param_descs = dict(doc_info.param_descriptions or {})
223248
else:
224249
doc_info = None
225250
param_descs = {}
226251

252+
type_hints_with_extras = get_type_hints(func, include_extras=True)
253+
type_hints: dict[str, Any] = {}
254+
annotated_param_descs: dict[str, str] = {}
255+
256+
for name, annotation in type_hints_with_extras.items():
257+
if name == "return":
258+
continue
259+
260+
stripped_ann, metadata = _strip_annotated(annotation)
261+
type_hints[name] = stripped_ann
262+
263+
description = _extract_description_from_metadata(metadata)
264+
if description is not None:
265+
annotated_param_descs[name] = description
266+
267+
for name, description in annotated_param_descs.items():
268+
param_descs.setdefault(name, description)
269+
227270
# Ensure name_override takes precedence even if docstring info is disabled.
228271
func_name = name_override or (doc_info.name if doc_info else func.__name__)
229272

230273
# 2. Inspect function signature and get type hints
231274
sig = inspect.signature(func)
232-
type_hints = get_type_hints(func)
233275
params = list(sig.parameters.items())
234276
takes_context = False
235277
filtered_params = []

tests/test_function_schema.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections.abc import Mapping
22
from enum import Enum
3-
from typing import Any, Literal
3+
from typing import Annotated, Any, Literal
44

55
import pytest
66
from pydantic import BaseModel, Field, ValidationError
@@ -521,6 +521,44 @@ def func_with_optional_field(
521521
fs.params_pydantic_model(**{"required_param": "test", "optional_param": -1.0})
522522

523523

524+
def test_function_uses_annotated_descriptions_without_docstring() -> None:
525+
"""Test that Annotated metadata populates parameter descriptions when docstrings are ignored."""
526+
527+
def add(
528+
a: Annotated[int, "First number to add"],
529+
b: Annotated[int, "Second number to add"],
530+
) -> int:
531+
return a + b
532+
533+
fs = function_schema(add, use_docstring_info=False)
534+
535+
properties = fs.params_json_schema.get("properties", {})
536+
assert properties["a"].get("description") == "First number to add"
537+
assert properties["b"].get("description") == "Second number to add"
538+
539+
540+
def test_function_prefers_docstring_descriptions_over_annotated_metadata() -> None:
541+
"""Test that docstring parameter descriptions take precedence over Annotated metadata."""
542+
543+
def add(
544+
a: Annotated[int, "Annotated description for a"],
545+
b: Annotated[int, "Annotated description for b"],
546+
) -> int:
547+
"""Adds two integers.
548+
549+
Args:
550+
a: Docstring provided description.
551+
"""
552+
553+
return a + b
554+
555+
fs = function_schema(add)
556+
557+
properties = fs.params_json_schema.get("properties", {})
558+
assert properties["a"].get("description") == "Docstring provided description."
559+
assert properties["b"].get("description") == "Annotated description for b"
560+
561+
524562
def test_function_with_field_description_merge():
525563
"""Test that Field descriptions are merged with docstring descriptions."""
526564

0 commit comments

Comments
 (0)