-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Initial Checks
- I confirm that I'm using the latest version of Pydantic AI
- I confirm that I searched for my issue in https://github.com/pydantic/pydantic-ai/issues before opening this issue
Description
We have a few tool definitions that are wrapped with sync_to_async to make the boundary between pydantic-ai and the underlying Django layer work reliably. With Python 3.13 (and prior) this seems to work fine. Attempting to update to 3.14 causes the tool definitions to fail with some unique errors. From the sample below:
Traceback (most recent call last):
File "script.py", line 38, in <module>
Tool(foo)
~~~~^^^^^
File ".cache/uv/environments-v2/script-b6576b051c3e2f31/lib/python3.14/site-packages/pydantic_ai/tools.py", line 358, in __init__
self.function_schema = function_schema or _function_schema.function_schema(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
function,
^^^^^^^^^
...<3 lines>...
require_parameter_descriptions=require_parameter_descriptions,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File ".cache/uv/environments-v2/script-b6576b051c3e2f31/lib/python3.14/site-packages/pydantic_ai/_function_schema.py", line 137, in function_schema
annotation = type_hints[name]
~~~~~~~~~~^^^^^^
KeyError: 'a'
From tracing down the code, it looks like the behavior of functools.update_wrapper 1 changed between 3.13 and 3.14 - specifically the __annotations__ wrapper assignment was swapped for __annotate__. This causes pydantic._internal._typing_extra.get_function_type_hints to return {} resulting in function_schema to explode.
I don't have an immediate need for 3.14 support, but this is the current issue we are seeing which is preventing any further evaluation.
Example Code
# /// script
# requires-python = ">=3.13"
# dependencies = [
# # Direct dependencies:
# "asgiref==3.11.0",
# "pydantic-ai-slim==1.28.0",
#
# # Transitive dependencies (aka "lock file")
# "annotated-types==0.7.0",
# "anyio==4.12.0",
# "certifi==2025.11.12",
# "colorama==0.4.6",
# "genai-prices==0.0.47",
# "griffe==1.15.0",
# "h11==0.16.0",
# "httpcore==1.0.9",
# "httpx==0.28.1",
# "idna==3.11",
# "importlib-metadata==8.7.0",
# "logfire-api==4.16.0",
# "opentelemetry-api==1.39.0",
# "pydantic==2.12.5",
# "pydantic-core==2.41.5",
# "pydantic-graph==1.28.0",
# "typing-extensions==4.15.0",
# "typing-inspection==0.4.2",
# "zipp==3.23.0",
# ]
# ///
from asgiref.sync import sync_to_async
from pydantic_ai.tools import Tool
@sync_to_async
def foo(a: int, b: str) -> float:
...
Tool(foo)Python, Pydantic AI & LLM client version
pydantic-ai-slim==1.28.0
Python 3.13.11 (works) vs 3.14.2 (fails)
Sample code above has all transitive dependencies spelled out as of right now and should be run with:
$ uv --version
uv 0.9.16 (a63e5b62e 2025-12-06)
$ uv run --python 3.13.11 script.py # No output == good
$ uv run --python 3.14.2 script.py # Exception :(