Skip to content

Commit 2091142

Browse files
Merge branch 'main' into update-versins
2 parents 7b83106 + 11d1cde commit 2091142

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+612
-171
lines changed

.github/workflows/stale.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
name: 'Close stale issues'
1+
name: 'Close stale issues and PRs'
22
on:
33
schedule:
44
- cron: '0 14 * * *'
55

66
permissions:
77
issues: write
8+
pull-requests: write
89

910
jobs:
1011
stale:
1112
runs-on: ubuntu-latest
1213
steps:
1314
- uses: actions/stale@v9
1415
with:
15-
stale-issue-message: 'This issue is stale, and will be closed in 3 days if no reply is received.'
16-
close-issue-message: 'Closing this issue as it has been inactive for 10 days.'
17-
any-of-labels: 'question,more info'
1816
days-before-stale: 7
1917
days-before-close: 3
18+
19+
any-of-issue-labels: 'question,more info'
20+
stale-issue-message: 'This issue is stale, and will be closed in 3 days if no reply is received.'
21+
close-issue-message: 'Closing this issue as it has been inactive for 10 days.'
22+
23+
any-of-pr-labels: 'awaiting author revision'
24+
stale-pr-message: 'This PR is stale, and will be closed in 3 days if no reply is received.'
25+
close-pr-message: 'Closing this PR as it has been inactive for 10 days.'

docs/agents.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ This structure allows you to configure common parameters that influence the mode
467467
`timeout`, and more.
468468

469469
There are two ways to apply these settings:
470+
470471
1. Passing to `run{_sync,_stream}` functions via the `model_settings` argument. This allows for fine-tuning on a per-request basis.
471472
2. Setting during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These settings will be applied by default to all subsequent run calls using said agent. However, `model_settings` provided during a specific run call will override the agent's default settings.
472473

docs/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ PydanticAI is still pre-version 1, so breaking changes will occur, however:
1212
!!! note
1313
Here's a filtered list of the breaking changes for each version to help you upgrade PydanticAI.
1414

15+
### v0.4.0 (2025-07-08)
16+
17+
See [#1799](https://github.com/pydantic/pydantic-ai/pull/1799) - Pydantic Evals `EvaluationReport` and `ReportCase` are now generic dataclasses instead of Pydantic models. If you were serializing them using `model_dump()`, you will now need to use the `EvaluationReportAdapter` and `ReportCaseAdapter` type adapters instead.
18+
19+
See [#1507](https://github.com/pydantic/pydantic-ai/pull/1507) - The `ToolDefinition` `description` argument is now optional and the order of positional arguments has changed from `name, description, parameters_json_schema, ...` to `name, parameters_json_schema, description, ...` to account for this.
20+
1521
### v0.3.0 (2025-06-18)
1622

1723
See [#1142](https://github.com/pydantic/pydantic-ai/pull/1142) — Adds support for thinking parts.

docs/direct.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def main():
6666
function_tools=[
6767
ToolDefinition(
6868
name=Divide.__name__.lower(),
69-
description=Divide.__doc__ or '',
69+
description=Divide.__doc__,
7070
parameters_json_schema=Divide.model_json_schema(),
7171
)
7272
],

docs/tools.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,6 @@ print(test_model.last_model_request_parameters.function_tools)
451451
[
452452
ToolDefinition(
453453
name='foobar',
454-
description='This is a Foobar',
455454
parameters_json_schema={
456455
'properties': {
457456
'x': {'type': 'integer'},
@@ -462,6 +461,7 @@ print(test_model.last_model_request_parameters.function_tools)
462461
'title': 'Foobar',
463462
'type': 'object',
464463
},
464+
description='This is a Foobar',
465465
)
466466
]
467467
"""
@@ -591,7 +591,6 @@ print(test_model.last_model_request_parameters.function_tools)
591591
[
592592
ToolDefinition(
593593
name='greet',
594-
description='',
595594
parameters_json_schema={
596595
'additionalProperties': False,
597596
'properties': {
@@ -721,11 +720,19 @@ def my_flaky_tool(query: str) -> str:
721720
```
722721
Raising `ModelRetry` also generates a `RetryPromptPart` containing the exception message, which is sent back to the LLM to guide its next attempt. Both `ValidationError` and `ModelRetry` respect the `retries` setting configured on the `Tool` or `Agent`.
723722

724-
## Use LangChain Tools {#langchain-tools}
723+
## Third-Party Tools
724+
725+
### MCP Tools {#mcp-tools}
726+
727+
See the [MCP Client](./mcp/client.md) documentation for how to use MCP servers with Pydantic AI.
728+
729+
### LangChain Tools {#langchain-tools}
730+
731+
If you'd like to use a tool from LangChain's [community tool library](https://python.langchain.com/docs/integrations/tools/) with Pydantic AI, you can use the `pydancic_ai.ext.langchain.tool_from_langchain` convenience method. Note that Pydantic AI will not validate the arguments in this case -- it's up to the model to provide arguments matching the schema specified by the LangChain tool, and up to the LangChain tool to raise an error if the arguments are invalid.
725732

726-
If you'd like to use a tool from LangChain's [community tool library](https://python.langchain.com/docs/integrations/tools/) with PydanticAI, you can use the `pydancic_ai.ext.langchain.tool_from_langchain` convenience method. Note that PydanticAI will not validate the arguments in this case -- it's up to the model to provide arguments matching the schema specified by the LangChain tool, and up to the LangChain tool to raise an error if the arguments are invalid.
733+
You will need to install the `langchain-community` package and any others required by the tool in question.
727734

728-
Here is how you can use it to augment model responses using a LangChain web search tool. This tool will need you to install the `langchain-community` and `duckduckgo-search` dependencies to work properly.
735+
Here is how you can use the LangChain `DuckDuckGoSearchRun` tool, which requires the `duckduckgo-search` package:
729736

730737
```python {test="skip"}
731738
from langchain_community.tools import DuckDuckGoSearchRun
@@ -737,15 +744,44 @@ search = DuckDuckGoSearchRun()
737744
search_tool = tool_from_langchain(search)
738745

739746
agent = Agent(
740-
'google-gla:gemini-2.0-flash', # (1)!
747+
'google-gla:gemini-2.0-flash',
741748
tools=[search_tool],
742749
)
743750

744-
result = agent.run_sync('What is the release date of Elden Ring Nightreign?') # (2)!
751+
result = agent.run_sync('What is the release date of Elden Ring Nightreign?') # (1)!
745752
print(result.output)
746753
#> Elden Ring Nightreign is planned to be released on May 30, 2025.
747754
```
748755

756+
1. The release date of this game is the 30th of May 2025, which is after the knowledge cutoff for Gemini 2.0 (August 2024).
757+
758+
### ACI.dev Tools {#aci-tools}
759+
760+
If you'd like to use a tool from the [ACI.dev tool library](https://www.aci.dev/tools) with Pydantic AI, you can use the `pydancic_ai.ext.aci.tool_from_aci` convenience method. Note that Pydantic AI will not validate the arguments in this case -- it's up to the model to provide arguments matching the schema specified by the ACI tool, and up to the ACI tool to raise an error if the arguments are invalid.
761+
762+
You will need to install the `aci-sdk` package, set your ACI API key in the `ACI_API_KEY` environment variable, and pass your ACI "linked account owner ID" to the function.
763+
764+
Here is how you can use the ACI.dev `TAVILY__SEARCH` tool:
765+
766+
```python {test="skip"}
767+
import os
768+
769+
from pydantic_ai import Agent
770+
from pydantic_ai.ext.aci import tool_from_aci
771+
772+
tavily_search = tool_from_aci(
773+
'TAVILY__SEARCH',
774+
linked_account_owner_id=os.getenv('LINKED_ACCOUNT_OWNER_ID')
775+
)
776+
777+
agent = Agent(
778+
'google-gla:gemini-2.0-flash',
779+
tools=[tavily_search]
780+
)
781+
782+
result = agent.run_sync('What is the release date of Elden Ring Nightreign?') # (1)!
783+
print(result.output)
784+
#> Elden Ring Nightreign is planned to be released on May 30, 2025.
785+
```
749786

750-
1. While this task is simple Gemini 1.5 didn't want to use the provided tool. Gemini 2.0 is still fast and cheap.
751-
2. The release date of this game is the 30th of May 2025, which was confirmed after the knowledge cutoff for Gemini 2.0 (August 2024).
787+
1. The release date of this game is the 30th of May 2025, which is after the knowledge cutoff for Gemini 2.0 (August 2024).

pydantic_ai_slim/pydantic_ai/_function_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class FunctionSchema:
3535
"""Internal information about a function schema."""
3636

3737
function: Callable[..., Any]
38-
description: str
38+
description: str | None
3939
validator: SchemaValidator
4040
json_schema: ObjectJsonSchema
4141
# if not None, the function takes a single by that name (besides potentially `info`)

pydantic_ai_slim/pydantic_ai/_griffe.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def doc_descriptions(
1919
sig: Signature,
2020
*,
2121
docstring_format: DocstringFormat,
22-
) -> tuple[str, dict[str, str]]:
22+
) -> tuple[str | None, dict[str, str]]:
2323
"""Extract the function description and parameter descriptions from a function's docstring.
2424
2525
The function parses the docstring using the specified format (or infers it if 'auto')
@@ -35,7 +35,7 @@ def doc_descriptions(
3535
"""
3636
doc = func.__doc__
3737
if doc is None:
38-
return '', {}
38+
return None, {}
3939

4040
# see https://github.com/mkdocstrings/griffe/issues/293
4141
parent = cast(GriffeObject, sig)

pydantic_ai_slim/pydantic_ai/_utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,11 @@ def dataclasses_no_defaults_repr(self: Any) -> str:
315315
return f'{self.__class__.__qualname__}({", ".join(kv_pairs)})'
316316

317317

318+
_datetime_ta = TypeAdapter(datetime)
319+
320+
318321
def number_to_datetime(x: int | float) -> datetime:
319-
return TypeAdapter(datetime).validate_python(x)
322+
return _datetime_ta.validate_python(x)
320323

321324

322325
AwaitableCallable = Callable[..., Awaitable[T]]

pydantic_ai_slim/pydantic_ai/agent.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def __init__(
296296
if 'result_type' in _deprecated_kwargs:
297297
if output_type is not str: # pragma: no cover
298298
raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
299-
warnings.warn('`result_type` is deprecated, use `output_type` instead', DeprecationWarning)
299+
warnings.warn('`result_type` is deprecated, use `output_type` instead', DeprecationWarning, stacklevel=2)
300300
output_type = _deprecated_kwargs.pop('result_type')
301301

302302
self.output_type = output_type
@@ -310,19 +310,23 @@ def __init__(
310310
warnings.warn(
311311
'`result_tool_name` is deprecated, use `output_type` with `ToolOutput` instead',
312312
DeprecationWarning,
313+
stacklevel=2,
313314
)
314315

315316
self._deprecated_result_tool_description = _deprecated_kwargs.pop('result_tool_description', None)
316317
if self._deprecated_result_tool_description is not None:
317318
warnings.warn(
318319
'`result_tool_description` is deprecated, use `output_type` with `ToolOutput` instead',
319320
DeprecationWarning,
321+
stacklevel=2,
320322
)
321323
result_retries = _deprecated_kwargs.pop('result_retries', None)
322324
if result_retries is not None:
323325
if output_retries is not None: # pragma: no cover
324326
raise TypeError('`output_retries` and `result_retries` cannot be set at the same time.')
325-
warnings.warn('`result_retries` is deprecated, use `max_result_retries` instead', DeprecationWarning)
327+
warnings.warn(
328+
'`result_retries` is deprecated, use `max_result_retries` instead', DeprecationWarning, stacklevel=2
329+
)
326330
output_retries = result_retries
327331

328332
default_output_mode = (
@@ -472,7 +476,7 @@ async def main():
472476
if 'result_type' in _deprecated_kwargs: # pragma: no cover
473477
if output_type is not str:
474478
raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
475-
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
479+
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
476480
output_type = _deprecated_kwargs.pop('result_type')
477481

478482
_utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -640,7 +644,7 @@ async def main():
640644
if 'result_type' in _deprecated_kwargs: # pragma: no cover
641645
if output_type is not str:
642646
raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
643-
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
647+
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
644648
output_type = _deprecated_kwargs.pop('result_type')
645649

646650
_utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -879,7 +883,7 @@ def run_sync(
879883
if 'result_type' in _deprecated_kwargs: # pragma: no cover
880884
if output_type is not str:
881885
raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
882-
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
886+
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
883887
output_type = _deprecated_kwargs.pop('result_type')
884888

885889
_utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -997,7 +1001,7 @@ async def main():
9971001
if 'result_type' in _deprecated_kwargs: # pragma: no cover
9981002
if output_type is not str:
9991003
raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
1000-
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
1004+
warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
10011005
output_type = _deprecated_kwargs.pop('result_type')
10021006

10031007
_utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -1336,7 +1340,11 @@ async def output_validator_deps(ctx: RunContext[str], data: str) -> str:
13361340
return func
13371341

13381342
@deprecated('`result_validator` is deprecated, use `output_validator` instead.')
1339-
def result_validator(self, func: Any, /) -> Any: ...
1343+
def result_validator(self, func: Any, /) -> Any:
1344+
warnings.warn(
1345+
'`result_validator` is deprecated, use `output_validator` instead.', DeprecationWarning, stacklevel=2
1346+
)
1347+
return self.output_validator(func) # type: ignore
13401348

13411349
@overload
13421350
def tool(self, func: ToolFuncContext[AgentDepsT, ToolParams], /) -> ToolFuncContext[AgentDepsT, ToolParams]: ...
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Checking whether aci-sdk is installed
2+
try:
3+
from aci import ACI
4+
except ImportError as _import_error:
5+
raise ImportError('Please install `aci-sdk` to use ACI.dev tools') from _import_error
6+
7+
from typing import Any
8+
9+
from aci import ACI
10+
11+
from pydantic_ai import Tool
12+
13+
14+
def _clean_schema(schema):
15+
if isinstance(schema, dict):
16+
# Remove non-standard keys (e.g., 'visible')
17+
return {k: _clean_schema(v) for k, v in schema.items() if k not in {'visible'}}
18+
elif isinstance(schema, list):
19+
return [_clean_schema(item) for item in schema]
20+
else:
21+
return schema
22+
23+
24+
def tool_from_aci(aci_function: str, linked_account_owner_id: str) -> Tool:
25+
"""Creates a Pydantic AI tool proxy from an ACI function.
26+
27+
Args:
28+
aci_function: The ACI function to wrao.
29+
linked_account_owner_id: The ACI user ID to execute the function on behalf of.
30+
31+
Returns:
32+
A Pydantic AI tool that corresponds to the ACI.dev tool.
33+
"""
34+
aci = ACI()
35+
function_definition = aci.functions.get_definition(aci_function)
36+
function_name = function_definition['function']['name']
37+
function_description = function_definition['function']['description']
38+
inputs = function_definition['function']['parameters']
39+
40+
json_schema = {
41+
'additionalProperties': inputs.get('additionalProperties', False),
42+
'properties': inputs.get('properties', {}),
43+
'required': inputs.get('required', []),
44+
# Default to 'object' if not specified
45+
'type': inputs.get('type', 'object'),
46+
}
47+
48+
# Clean the schema
49+
json_schema = _clean_schema(json_schema)
50+
51+
def implementation(*args: Any, **kwargs: Any) -> str:
52+
if args:
53+
raise TypeError('Positional arguments are not allowed')
54+
return aci.handle_function_call(
55+
function_name,
56+
kwargs,
57+
linked_account_owner_id=linked_account_owner_id,
58+
allowed_apps_only=True,
59+
)
60+
61+
return Tool.from_schema(
62+
function=implementation,
63+
name=function_name,
64+
description=function_description,
65+
json_schema=json_schema,
66+
)

0 commit comments

Comments
 (0)