pydantic-ai-slim 0.0.11__py3-none-any.whl → 0.0.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- pydantic_ai/_pydantic.py +6 -4
- pydantic_ai/_result.py +18 -22
- pydantic_ai/_system_prompt.py +1 -1
- pydantic_ai/_utils.py +11 -6
- pydantic_ai/agent.py +146 -67
- pydantic_ai/messages.py +5 -2
- pydantic_ai/models/__init__.py +30 -37
- pydantic_ai/models/function.py +8 -14
- pydantic_ai/models/gemini.py +11 -10
- pydantic_ai/models/groq.py +31 -34
- pydantic_ai/models/ollama.py +116 -0
- pydantic_ai/models/openai.py +43 -38
- pydantic_ai/models/test.py +70 -49
- pydantic_ai/models/vertexai.py +7 -6
- pydantic_ai/tools.py +119 -34
- {pydantic_ai_slim-0.0.11.dist-info → pydantic_ai_slim-0.0.12.dist-info}/METADATA +1 -1
- pydantic_ai_slim-0.0.12.dist-info/RECORD +23 -0
- pydantic_ai_slim-0.0.11.dist-info/RECORD +0 -22
- {pydantic_ai_slim-0.0.11.dist-info → pydantic_ai_slim-0.0.12.dist-info}/WHEEL +0 -0
pydantic_ai/models/test.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations as _annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import string
|
|
5
|
-
from collections.abc import AsyncIterator, Iterable, Iterator
|
|
5
|
+
from collections.abc import AsyncIterator, Iterable, Iterator
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from datetime import date, datetime, timedelta
|
|
@@ -21,8 +21,8 @@ from ..messages import (
|
|
|
21
21
|
ToolReturn,
|
|
22
22
|
)
|
|
23
23
|
from ..result import Cost
|
|
24
|
+
from ..tools import ToolDefinition
|
|
24
25
|
from . import (
|
|
25
|
-
AbstractToolDefinition,
|
|
26
26
|
AgentModel,
|
|
27
27
|
EitherStreamedResponse,
|
|
28
28
|
Model,
|
|
@@ -55,25 +55,38 @@ class TestModel(Model):
|
|
|
55
55
|
"""If set, these args will be passed to the result tool."""
|
|
56
56
|
seed: int = 0
|
|
57
57
|
"""Seed for generating random data."""
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
agent_model_function_tools: list[ToolDefinition] | None = field(default=None, init=False)
|
|
59
|
+
"""Definition of function tools passed to the model.
|
|
60
|
+
|
|
61
|
+
This is set when the model is called, so will reflect the function tools from the last step of the last run.
|
|
62
|
+
"""
|
|
60
63
|
agent_model_allow_text_result: bool | None = field(default=None, init=False)
|
|
61
|
-
|
|
64
|
+
"""Whether plain text responses from the model are allowed.
|
|
65
|
+
|
|
66
|
+
This is set when the model is called, so will reflect the value from the last step of the last run.
|
|
67
|
+
"""
|
|
68
|
+
agent_model_result_tools: list[ToolDefinition] | None = field(default=None, init=False)
|
|
69
|
+
"""Definition of result tools passed to the model.
|
|
70
|
+
|
|
71
|
+
This is set when the model is called, so will reflect the result tools from the last step of the last run.
|
|
72
|
+
"""
|
|
62
73
|
|
|
63
74
|
async def agent_model(
|
|
64
75
|
self,
|
|
65
|
-
|
|
76
|
+
*,
|
|
77
|
+
function_tools: list[ToolDefinition],
|
|
66
78
|
allow_text_result: bool,
|
|
67
|
-
result_tools:
|
|
79
|
+
result_tools: list[ToolDefinition],
|
|
68
80
|
) -> AgentModel:
|
|
69
|
-
self.
|
|
81
|
+
self.agent_model_function_tools = function_tools
|
|
70
82
|
self.agent_model_allow_text_result = allow_text_result
|
|
71
|
-
self.agent_model_result_tools =
|
|
83
|
+
self.agent_model_result_tools = result_tools
|
|
72
84
|
|
|
73
85
|
if self.call_tools == 'all':
|
|
74
|
-
tool_calls = [(r.name, r) for r in function_tools
|
|
86
|
+
tool_calls = [(r.name, r) for r in function_tools]
|
|
75
87
|
else:
|
|
76
|
-
|
|
88
|
+
function_tools_lookup = {t.name: t for t in function_tools}
|
|
89
|
+
tools_to_call = (function_tools_lookup[name] for name in self.call_tools)
|
|
77
90
|
tool_calls = [(r.name, r) for r in tools_to_call]
|
|
78
91
|
|
|
79
92
|
if self.custom_result_text is not None:
|
|
@@ -90,11 +103,12 @@ class TestModel(Model):
|
|
|
90
103
|
result = _utils.Either(right=self.custom_result_args)
|
|
91
104
|
elif allow_text_result:
|
|
92
105
|
result = _utils.Either(left=None)
|
|
93
|
-
elif result_tools
|
|
106
|
+
elif result_tools:
|
|
94
107
|
result = _utils.Either(right=None)
|
|
95
108
|
else:
|
|
96
109
|
result = _utils.Either(left=None)
|
|
97
|
-
|
|
110
|
+
|
|
111
|
+
return TestAgentModel(tool_calls, result, result_tools, self.seed)
|
|
98
112
|
|
|
99
113
|
def name(self) -> str:
|
|
100
114
|
return 'test-model'
|
|
@@ -107,13 +121,11 @@ class TestAgentModel(AgentModel):
|
|
|
107
121
|
# NOTE: Avoid test discovery by pytest.
|
|
108
122
|
__test__ = False
|
|
109
123
|
|
|
110
|
-
tool_calls: list[tuple[str,
|
|
124
|
+
tool_calls: list[tuple[str, ToolDefinition]]
|
|
111
125
|
# left means the text is plain text; right means it's a function call
|
|
112
126
|
result: _utils.Either[str | None, Any | None]
|
|
113
|
-
result_tools: list[
|
|
127
|
+
result_tools: list[ToolDefinition]
|
|
114
128
|
seed: int
|
|
115
|
-
step: int = 0
|
|
116
|
-
last_message_count: int = 0
|
|
117
129
|
|
|
118
130
|
async def request(self, messages: list[Message]) -> tuple[ModelAnyResponse, Cost]:
|
|
119
131
|
return self._request(messages), Cost()
|
|
@@ -127,18 +139,19 @@ class TestAgentModel(AgentModel):
|
|
|
127
139
|
else:
|
|
128
140
|
yield TestStreamStructuredResponse(msg, cost)
|
|
129
141
|
|
|
130
|
-
def gen_tool_args(self, tool_def:
|
|
131
|
-
return _JsonSchemaTestData(tool_def.
|
|
142
|
+
def gen_tool_args(self, tool_def: ToolDefinition) -> Any:
|
|
143
|
+
return _JsonSchemaTestData(tool_def.parameters_json_schema, self.seed).generate()
|
|
132
144
|
|
|
133
145
|
def _request(self, messages: list[Message]) -> ModelAnyResponse:
|
|
134
|
-
if
|
|
146
|
+
# if there are tools, the first thing we want to do is call all of them
|
|
147
|
+
if self.tool_calls and not any(m.role == 'model-structured-response' for m in messages):
|
|
135
148
|
calls = [ToolCall.from_dict(name, self.gen_tool_args(args)) for name, args in self.tool_calls]
|
|
136
|
-
self.step += 1
|
|
137
|
-
self.last_message_count = len(messages)
|
|
138
149
|
return ModelStructuredResponse(calls=calls)
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
# get messages since the last model response
|
|
152
|
+
new_messages = _get_new_messages(messages)
|
|
153
|
+
|
|
154
|
+
# check if there are any retry prompts, if so retry them
|
|
142
155
|
new_retry_names = {m.tool_name for m in new_messages if isinstance(m, RetryPrompt)}
|
|
143
156
|
if new_retry_names:
|
|
144
157
|
calls = [
|
|
@@ -146,34 +159,42 @@ class TestAgentModel(AgentModel):
|
|
|
146
159
|
for name, args in self.tool_calls
|
|
147
160
|
if name in new_retry_names
|
|
148
161
|
]
|
|
149
|
-
self.step += 1
|
|
150
162
|
return ModelStructuredResponse(calls=calls)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return ModelTextResponse(content=pydantic_core.to_json(output).decode())
|
|
162
|
-
else:
|
|
163
|
-
return ModelTextResponse(content='success (no tool calls)')
|
|
163
|
+
|
|
164
|
+
if response_text := self.result.left:
|
|
165
|
+
if response_text.value is None:
|
|
166
|
+
# build up details of tool responses
|
|
167
|
+
output: dict[str, Any] = {}
|
|
168
|
+
for message in messages:
|
|
169
|
+
if isinstance(message, ToolReturn):
|
|
170
|
+
output[message.tool_name] = message.content
|
|
171
|
+
if output:
|
|
172
|
+
return ModelTextResponse(content=pydantic_core.to_json(output).decode())
|
|
164
173
|
else:
|
|
165
|
-
return ModelTextResponse(content=
|
|
174
|
+
return ModelTextResponse(content='success (no tool calls)')
|
|
166
175
|
else:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
return ModelTextResponse(content=response_text.value)
|
|
177
|
+
else:
|
|
178
|
+
assert self.result_tools, 'No result tools provided'
|
|
179
|
+
custom_result_args = self.result.right
|
|
180
|
+
result_tool = self.result_tools[self.seed % len(self.result_tools)]
|
|
181
|
+
if custom_result_args is not None:
|
|
182
|
+
return ModelStructuredResponse(calls=[ToolCall.from_dict(result_tool.name, custom_result_args)])
|
|
183
|
+
else:
|
|
184
|
+
response_args = self.gen_tool_args(result_tool)
|
|
185
|
+
return ModelStructuredResponse(calls=[ToolCall.from_dict(result_tool.name, response_args)])
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _get_new_messages(messages: list[Message]) -> list[Message]:
|
|
189
|
+
last_model_index = None
|
|
190
|
+
for i, m in enumerate(messages):
|
|
191
|
+
if m.role in ('model-structured-response', 'model-text-response'):
|
|
192
|
+
last_model_index = i
|
|
193
|
+
|
|
194
|
+
if last_model_index is not None:
|
|
195
|
+
return messages[last_model_index + 1 :]
|
|
196
|
+
else:
|
|
197
|
+
return []
|
|
177
198
|
|
|
178
199
|
|
|
179
200
|
@dataclass
|
pydantic_ai/models/vertexai.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Mapping, Sequence
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
4
|
from datetime import datetime, timedelta
|
|
6
5
|
from pathlib import Path
|
|
@@ -10,7 +9,8 @@ from httpx import AsyncClient as AsyncHTTPClient
|
|
|
10
9
|
|
|
11
10
|
from .._utils import run_in_executor
|
|
12
11
|
from ..exceptions import UserError
|
|
13
|
-
from
|
|
12
|
+
from ..tools import ToolDefinition
|
|
13
|
+
from . import Model, cached_async_http_client
|
|
14
14
|
from .gemini import GeminiAgentModel, GeminiModelName
|
|
15
15
|
|
|
16
16
|
try:
|
|
@@ -18,11 +18,11 @@ try:
|
|
|
18
18
|
from google.auth.credentials import Credentials as BaseCredentials
|
|
19
19
|
from google.auth.transport.requests import Request
|
|
20
20
|
from google.oauth2.service_account import Credentials as ServiceAccountCredentials
|
|
21
|
-
except ImportError as
|
|
21
|
+
except ImportError as _import_error:
|
|
22
22
|
raise ImportError(
|
|
23
23
|
'Please install `google-auth` to use the VertexAI model, '
|
|
24
24
|
"you can use the `vertexai` optional group — `pip install 'pydantic-ai[vertexai]'`"
|
|
25
|
-
) from
|
|
25
|
+
) from _import_error
|
|
26
26
|
|
|
27
27
|
VERTEX_AI_URL_TEMPLATE = (
|
|
28
28
|
'https://{region}-aiplatform.googleapis.com/v1'
|
|
@@ -109,9 +109,10 @@ class VertexAIModel(Model):
|
|
|
109
109
|
|
|
110
110
|
async def agent_model(
|
|
111
111
|
self,
|
|
112
|
-
|
|
112
|
+
*,
|
|
113
|
+
function_tools: list[ToolDefinition],
|
|
113
114
|
allow_text_result: bool,
|
|
114
|
-
result_tools:
|
|
115
|
+
result_tools: list[ToolDefinition],
|
|
115
116
|
) -> GeminiAgentModel:
|
|
116
117
|
url, auth = await self._ainit()
|
|
117
118
|
return GeminiAgentModel(
|
pydantic_ai/tools.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, Union, cast
|
|
|
7
7
|
|
|
8
8
|
from pydantic import ValidationError
|
|
9
9
|
from pydantic_core import SchemaValidator
|
|
10
|
-
from typing_extensions import Concatenate, ParamSpec,
|
|
10
|
+
from typing_extensions import Concatenate, ParamSpec, TypeAlias
|
|
11
11
|
|
|
12
12
|
from . import _pydantic, _utils, messages
|
|
13
13
|
from .exceptions import ModelRetry, UnexpectedModelBehavior
|
|
@@ -27,7 +27,10 @@ __all__ = (
|
|
|
27
27
|
'ToolFuncPlain',
|
|
28
28
|
'ToolFuncEither',
|
|
29
29
|
'ToolParams',
|
|
30
|
+
'ToolPrepareFunc',
|
|
30
31
|
'Tool',
|
|
32
|
+
'ObjectJsonSchema',
|
|
33
|
+
'ToolDefinition',
|
|
31
34
|
)
|
|
32
35
|
|
|
33
36
|
AgentDeps = TypeVar('AgentDeps')
|
|
@@ -42,7 +45,7 @@ class RunContext(Generic[AgentDeps]):
|
|
|
42
45
|
"""Dependencies for the agent."""
|
|
43
46
|
retry: int
|
|
44
47
|
"""Number of retries so far."""
|
|
45
|
-
tool_name: str | None
|
|
48
|
+
tool_name: str | None = None
|
|
46
49
|
"""Name of the tool being called."""
|
|
47
50
|
|
|
48
51
|
|
|
@@ -91,11 +94,37 @@ This is just a union of [`ToolFuncContext`][pydantic_ai.tools.ToolFuncContext] a
|
|
|
91
94
|
|
|
92
95
|
Usage `ToolFuncEither[AgentDeps, ToolParams]`.
|
|
93
96
|
"""
|
|
97
|
+
ToolPrepareFunc: TypeAlias = 'Callable[[RunContext[AgentDeps], ToolDefinition], Awaitable[ToolDefinition | None]]'
|
|
98
|
+
"""Definition of a function that can prepare a tool definition at call time.
|
|
99
|
+
|
|
100
|
+
See [tool docs](../agents.md#tool-prepare) for more information.
|
|
101
|
+
|
|
102
|
+
Example — here `only_if_42` is valid as a `ToolPrepareFunc`:
|
|
103
|
+
|
|
104
|
+
```py
|
|
105
|
+
from typing import Union
|
|
106
|
+
|
|
107
|
+
from pydantic_ai import RunContext, Tool
|
|
108
|
+
from pydantic_ai.tools import ToolDefinition
|
|
109
|
+
|
|
110
|
+
async def only_if_42(
|
|
111
|
+
ctx: RunContext[int], tool_def: ToolDefinition
|
|
112
|
+
) -> Union[ToolDefinition, None]:
|
|
113
|
+
if ctx.deps == 42:
|
|
114
|
+
return tool_def
|
|
115
|
+
|
|
116
|
+
def hitchhiker(ctx: RunContext[int], answer: str) -> str:
|
|
117
|
+
return f'{ctx.deps} {answer}'
|
|
118
|
+
|
|
119
|
+
hitchhiker = Tool(hitchhiker, prepare=only_if_42)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Usage `ToolPrepareFunc[AgentDeps]`.
|
|
123
|
+
"""
|
|
94
124
|
|
|
95
125
|
A = TypeVar('A')
|
|
96
126
|
|
|
97
127
|
|
|
98
|
-
@final
|
|
99
128
|
@dataclass(init=False)
|
|
100
129
|
class Tool(Generic[AgentDeps]):
|
|
101
130
|
"""A tool function for an agent."""
|
|
@@ -105,22 +134,24 @@ class Tool(Generic[AgentDeps]):
|
|
|
105
134
|
max_retries: int | None
|
|
106
135
|
name: str
|
|
107
136
|
description: str
|
|
137
|
+
prepare: ToolPrepareFunc[AgentDeps] | None
|
|
108
138
|
_is_async: bool = field(init=False)
|
|
109
139
|
_single_arg_name: str | None = field(init=False)
|
|
110
140
|
_positional_fields: list[str] = field(init=False)
|
|
111
141
|
_var_positional_field: str | None = field(init=False)
|
|
112
142
|
_validator: SchemaValidator = field(init=False, repr=False)
|
|
113
|
-
|
|
114
|
-
|
|
143
|
+
_parameters_json_schema: ObjectJsonSchema = field(init=False)
|
|
144
|
+
current_retry: int = field(default=0, init=False)
|
|
115
145
|
|
|
116
146
|
def __init__(
|
|
117
147
|
self,
|
|
118
148
|
function: ToolFuncEither[AgentDeps, ...],
|
|
119
|
-
takes_ctx: bool,
|
|
120
149
|
*,
|
|
150
|
+
takes_ctx: bool | None = None,
|
|
121
151
|
max_retries: int | None = None,
|
|
122
152
|
name: str | None = None,
|
|
123
153
|
description: str | None = None,
|
|
154
|
+
prepare: ToolPrepareFunc[AgentDeps] | None = None,
|
|
124
155
|
):
|
|
125
156
|
"""Create a new tool instance.
|
|
126
157
|
|
|
@@ -132,47 +163,77 @@ class Tool(Generic[AgentDeps]):
|
|
|
132
163
|
async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
|
|
133
164
|
return f'{ctx.deps} {x} {y}'
|
|
134
165
|
|
|
135
|
-
agent = Agent('test', tools=[Tool(my_tool
|
|
166
|
+
agent = Agent('test', tools=[Tool(my_tool)])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
or with a custom prepare method:
|
|
170
|
+
|
|
171
|
+
```py
|
|
172
|
+
from typing import Union
|
|
173
|
+
|
|
174
|
+
from pydantic_ai import Agent, RunContext, Tool
|
|
175
|
+
from pydantic_ai.tools import ToolDefinition
|
|
176
|
+
|
|
177
|
+
async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
|
|
178
|
+
return f'{ctx.deps} {x} {y}'
|
|
179
|
+
|
|
180
|
+
async def prep_my_tool(
|
|
181
|
+
ctx: RunContext[int], tool_def: ToolDefinition
|
|
182
|
+
) -> Union[ToolDefinition, None]:
|
|
183
|
+
# only register the tool if `deps == 42`
|
|
184
|
+
if ctx.deps == 42:
|
|
185
|
+
return tool_def
|
|
186
|
+
|
|
187
|
+
agent = Agent('test', tools=[Tool(my_tool, prepare=prep_my_tool)])
|
|
136
188
|
```
|
|
137
189
|
|
|
190
|
+
|
|
138
191
|
Args:
|
|
139
192
|
function: The Python function to call as the tool.
|
|
140
|
-
takes_ctx: Whether the function takes a [`RunContext`][pydantic_ai.tools.RunContext] first argument
|
|
193
|
+
takes_ctx: Whether the function takes a [`RunContext`][pydantic_ai.tools.RunContext] first argument,
|
|
194
|
+
this is inferred if unset.
|
|
141
195
|
max_retries: Maximum number of retries allowed for this tool, set to the agent default if `None`.
|
|
142
196
|
name: Name of the tool, inferred from the function if `None`.
|
|
143
197
|
description: Description of the tool, inferred from the function if `None`.
|
|
198
|
+
prepare: custom method to prepare the tool definition for each step, return `None` to omit this
|
|
199
|
+
tool from a given step. This is useful if you want to customise a tool at call time,
|
|
200
|
+
or omit it completely from a step. See [`ToolPrepareFunc`][pydantic_ai.tools.ToolPrepareFunc].
|
|
144
201
|
"""
|
|
202
|
+
if takes_ctx is None:
|
|
203
|
+
takes_ctx = _pydantic.takes_ctx(function)
|
|
204
|
+
|
|
145
205
|
f = _pydantic.function_schema(function, takes_ctx)
|
|
146
206
|
self.function = function
|
|
147
207
|
self.takes_ctx = takes_ctx
|
|
148
208
|
self.max_retries = max_retries
|
|
149
209
|
self.name = name or function.__name__
|
|
150
210
|
self.description = description or f['description']
|
|
211
|
+
self.prepare = prepare
|
|
151
212
|
self._is_async = inspect.iscoroutinefunction(self.function)
|
|
152
213
|
self._single_arg_name = f['single_arg_name']
|
|
153
214
|
self._positional_fields = f['positional_fields']
|
|
154
215
|
self._var_positional_field = f['var_positional_field']
|
|
155
216
|
self._validator = f['validator']
|
|
156
|
-
self.
|
|
217
|
+
self._parameters_json_schema = f['json_schema']
|
|
157
218
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"""Create a tool from a pure function, inferring whether it takes `RunContext` as its first argument.
|
|
219
|
+
async def prepare_tool_def(self, ctx: RunContext[AgentDeps]) -> ToolDefinition | None:
|
|
220
|
+
"""Get the tool definition.
|
|
161
221
|
|
|
162
|
-
|
|
163
|
-
|
|
222
|
+
By default, this method creates a tool definition, then either returns it, or calls `self.prepare`
|
|
223
|
+
if it's set.
|
|
164
224
|
|
|
165
225
|
Returns:
|
|
166
|
-
|
|
226
|
+
return a `ToolDefinition` or `None` if the tools should not be registered for this run.
|
|
167
227
|
"""
|
|
168
|
-
|
|
169
|
-
|
|
228
|
+
tool_def = ToolDefinition(
|
|
229
|
+
name=self.name,
|
|
230
|
+
description=self.description,
|
|
231
|
+
parameters_json_schema=self._parameters_json_schema,
|
|
232
|
+
)
|
|
233
|
+
if self.prepare is not None:
|
|
234
|
+
return await self.prepare(ctx, tool_def)
|
|
170
235
|
else:
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
def reset(self) -> None:
|
|
174
|
-
"""Reset the current retry count."""
|
|
175
|
-
self._current_retry = 0
|
|
236
|
+
return tool_def
|
|
176
237
|
|
|
177
238
|
async def run(self, deps: AgentDeps, message: messages.ToolCall) -> messages.Message:
|
|
178
239
|
"""Run the tool function asynchronously."""
|
|
@@ -195,28 +256,20 @@ class Tool(Generic[AgentDeps]):
|
|
|
195
256
|
except ModelRetry as e:
|
|
196
257
|
return self._on_error(e, message)
|
|
197
258
|
|
|
198
|
-
self.
|
|
259
|
+
self.current_retry = 0
|
|
199
260
|
return messages.ToolReturn(
|
|
200
261
|
tool_name=message.tool_name,
|
|
201
262
|
content=response_content,
|
|
202
263
|
tool_id=message.tool_id,
|
|
203
264
|
)
|
|
204
265
|
|
|
205
|
-
@property
|
|
206
|
-
def json_schema(self) -> _utils.ObjectJsonSchema:
|
|
207
|
-
return self._json_schema
|
|
208
|
-
|
|
209
|
-
@property
|
|
210
|
-
def outer_typed_dict_key(self) -> str | None:
|
|
211
|
-
return None
|
|
212
|
-
|
|
213
266
|
def _call_args(
|
|
214
267
|
self, deps: AgentDeps, args_dict: dict[str, Any], message: messages.ToolCall
|
|
215
268
|
) -> tuple[list[Any], dict[str, Any]]:
|
|
216
269
|
if self._single_arg_name:
|
|
217
270
|
args_dict = {self._single_arg_name: args_dict}
|
|
218
271
|
|
|
219
|
-
args = [RunContext(deps, self.
|
|
272
|
+
args = [RunContext(deps, self.current_retry, message.tool_name)] if self.takes_ctx else []
|
|
220
273
|
for positional_field in self._positional_fields:
|
|
221
274
|
args.append(args_dict.pop(positional_field))
|
|
222
275
|
if self._var_positional_field:
|
|
@@ -225,8 +278,8 @@ class Tool(Generic[AgentDeps]):
|
|
|
225
278
|
return args, args_dict
|
|
226
279
|
|
|
227
280
|
def _on_error(self, exc: ValidationError | ModelRetry, call_message: messages.ToolCall) -> messages.RetryPrompt:
|
|
228
|
-
self.
|
|
229
|
-
if self.max_retries is None or self.
|
|
281
|
+
self.current_retry += 1
|
|
282
|
+
if self.max_retries is None or self.current_retry > self.max_retries:
|
|
230
283
|
raise UnexpectedModelBehavior(f'Tool exceeded max retries count of {self.max_retries}') from exc
|
|
231
284
|
else:
|
|
232
285
|
if isinstance(exc, ValidationError):
|
|
@@ -238,3 +291,35 @@ class Tool(Generic[AgentDeps]):
|
|
|
238
291
|
content=content,
|
|
239
292
|
tool_id=call_message.tool_id,
|
|
240
293
|
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
ObjectJsonSchema: TypeAlias = dict[str, Any]
|
|
297
|
+
"""Type representing JSON schema of an object, e.g. where `"type": "object"`.
|
|
298
|
+
|
|
299
|
+
This type is used to define tools parameters (aka arguments) in [ToolDefinition][pydantic_ai.tools.ToolDefinition].
|
|
300
|
+
|
|
301
|
+
With PEP-728 this should be a TypedDict with `type: Literal['object']`, and `extra_items=Any`
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class ToolDefinition:
|
|
307
|
+
"""Definition of a tool passed to a model.
|
|
308
|
+
|
|
309
|
+
This is used for both function tools result tools.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
name: str
|
|
313
|
+
"""The name of the tool."""
|
|
314
|
+
|
|
315
|
+
description: str
|
|
316
|
+
"""The description of the tool."""
|
|
317
|
+
|
|
318
|
+
parameters_json_schema: ObjectJsonSchema
|
|
319
|
+
"""The JSON schema for the tool's parameters."""
|
|
320
|
+
|
|
321
|
+
outer_typed_dict_key: str | None = None
|
|
322
|
+
"""The key in the outer [TypedDict] that wraps a result tool.
|
|
323
|
+
|
|
324
|
+
This will only be set for result tools which don't have an `object` JSON schema.
|
|
325
|
+
"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pydantic_ai/__init__.py,sha256=a29NqQz0JyW4BoCjcRh23fBBfwY17_n57moE4QrFWM4,324
|
|
2
|
+
pydantic_ai/_griffe.py,sha256=pRjCJ6B1hhx6k46XJgl9zF6aRYxRmqEZKFok8unp4Iw,3449
|
|
3
|
+
pydantic_ai/_pydantic.py,sha256=0rrns3p6BY8reGT0CVrYpq7VSiO5lDrCyugbMEZyvx8,8600
|
|
4
|
+
pydantic_ai/_result.py,sha256=X8sk5Z_JFXBrs7dJQpT0N3U7KhhKnfVsQBJX9qHOsHU,9732
|
|
5
|
+
pydantic_ai/_system_prompt.py,sha256=l9YD3SU3eu34ibTvLPDGbPsMz62yK4KTaOc3ORcDtKU,1079
|
|
6
|
+
pydantic_ai/_utils.py,sha256=LnPjvL79gsC2vCcwdVdPt0N5bRVz9lr-ypRMYSwcQbg,8349
|
|
7
|
+
pydantic_ai/agent.py,sha256=v_32DqnRKZpMrOvCb-uGveD6lMSr1Ssm5M67HpH55qU,40480
|
|
8
|
+
pydantic_ai/exceptions.py,sha256=ko_47M0k6Rhg9mUC9P1cj7N4LCH6cC0pEsF65A2vL-U,1561
|
|
9
|
+
pydantic_ai/messages.py,sha256=eyZfgk1yNomyEOji-1Z72UH2KeFlu6BK2_QRtUCH8_U,7190
|
|
10
|
+
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
pydantic_ai/result.py,sha256=UB3vFOcDAceeNLXh_f_3PKy2J0A6FIHdgDFJxPH6OJk,13651
|
|
12
|
+
pydantic_ai/tools.py,sha256=o26Nbbn4P6DA6DBzOGm60ghnjGBjR6jBRmq-Of9MO7c,11105
|
|
13
|
+
pydantic_ai/models/__init__.py,sha256=OSBkwXhZCN6I0ofRACkU-D4RHswJqcAZyu94Na5yK44,10056
|
|
14
|
+
pydantic_ai/models/function.py,sha256=oNalT5TyL5Kd-QVtV3ZePw6xqD-pnh3Ycg8-5YIkPzg,9873
|
|
15
|
+
pydantic_ai/models/gemini.py,sha256=qqkAHmlawKSeMAD12mlmunIxdhj783GmfmQSAJblzhA,26517
|
|
16
|
+
pydantic_ai/models/groq.py,sha256=AqqFCwTntdifEhys4GQDE6GEnqz9dzMjJC9mdoIVsa0,14661
|
|
17
|
+
pydantic_ai/models/ollama.py,sha256=nlxI4HNS6mmHointSc-xHa-dMwfzblAiv4hyy8_ZPXA,3966
|
|
18
|
+
pydantic_ai/models/openai.py,sha256=XPsjkiDst8v46FONeVQ-hgC3yt74bQCYVRrtu1POkwQ,14971
|
|
19
|
+
pydantic_ai/models/test.py,sha256=p99eMjzZeBhVdkHQx5pdnBlOnG__hcZnDHfedPut9jI,15042
|
|
20
|
+
pydantic_ai/models/vertexai.py,sha256=DctkdayyIBXvolGWpWUG0YZRDb8M9U-cmZxRWq7ydzI,9126
|
|
21
|
+
pydantic_ai_slim-0.0.12.dist-info/METADATA,sha256=MIZtH_6kLJj81yi7QrT-vtSteGH6gh5sXkoeKVbNTP0,2562
|
|
22
|
+
pydantic_ai_slim-0.0.12.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
23
|
+
pydantic_ai_slim-0.0.12.dist-info/RECORD,,
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
pydantic_ai/__init__.py,sha256=a29NqQz0JyW4BoCjcRh23fBBfwY17_n57moE4QrFWM4,324
|
|
2
|
-
pydantic_ai/_griffe.py,sha256=pRjCJ6B1hhx6k46XJgl9zF6aRYxRmqEZKFok8unp4Iw,3449
|
|
3
|
-
pydantic_ai/_pydantic.py,sha256=oFfcHDv_wuL1NQ7mCzVHvP1HBaVzyvb7xS-_Iiri_tA,8491
|
|
4
|
-
pydantic_ai/_result.py,sha256=wzcfwDpr_sro1Vkn3DkyIhCXMHTReDxL_ZYm50JzdRI,9667
|
|
5
|
-
pydantic_ai/_system_prompt.py,sha256=vFT0y9Wykl5veGMgLLkGRYiHQrgdW2BZ1rwMn4izjjo,1085
|
|
6
|
-
pydantic_ai/_utils.py,sha256=eNb7f3-ZQC8WDEa87iUcXGQ-lyuutFQG-5yBCMD4Vvs,8227
|
|
7
|
-
pydantic_ai/agent.py,sha256=5QOiDVByatRqKlxyRLba0fBUZcuaEkew3eqovFiOB5M,37311
|
|
8
|
-
pydantic_ai/exceptions.py,sha256=ko_47M0k6Rhg9mUC9P1cj7N4LCH6cC0pEsF65A2vL-U,1561
|
|
9
|
-
pydantic_ai/messages.py,sha256=I0_CPXDIGGSy-PXHuKq540oAXYOO9uyylpsfSsE4vLs,7032
|
|
10
|
-
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
pydantic_ai/result.py,sha256=UB3vFOcDAceeNLXh_f_3PKy2J0A6FIHdgDFJxPH6OJk,13651
|
|
12
|
-
pydantic_ai/tools.py,sha256=rzchmsEPYtUzBBFoeeJKC1ct36iqOxFOY6kfKqItCCs,8210
|
|
13
|
-
pydantic_ai/models/__init__.py,sha256=_Mz_32WGlAf4NlxXfdQ-EAaY_bDOk10gIc5HmTAO_ts,10318
|
|
14
|
-
pydantic_ai/models/function.py,sha256=Mzc-zXnb2RayWAA8N9NS7KGF49do1S-VW3U9fkc661o,10045
|
|
15
|
-
pydantic_ai/models/gemini.py,sha256=ruO4tnnpDDuHThg7jUOphs8I_KXBJH7gfDMluliED8E,26606
|
|
16
|
-
pydantic_ai/models/groq.py,sha256=Tx2yU3ysmPLBmWGsjzES-XcumzrsoBtB7spCnJBlLiM,14947
|
|
17
|
-
pydantic_ai/models/openai.py,sha256=5ihH25CrS0tnZNW-BZw4GyPe8V-IxIHWw3B9ulPVjQE,14931
|
|
18
|
-
pydantic_ai/models/test.py,sha256=q1wch_E7TSb4qx9PCcP1YyBGZx567MGlAQhlAlON0S8,14463
|
|
19
|
-
pydantic_ai/models/vertexai.py,sha256=5wI8y2YjeRgSE51uKy5OtevQkks65uEbxIUAs5EGBaI,9161
|
|
20
|
-
pydantic_ai_slim-0.0.11.dist-info/METADATA,sha256=JPixjiAAN-dFKXFk6njU5-cf3_JqkwEBnXh2fUn2Ok4,2562
|
|
21
|
-
pydantic_ai_slim-0.0.11.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
22
|
-
pydantic_ai_slim-0.0.11.dist-info/RECORD,,
|
|
File without changes
|