pydantic-ai-slim 0.0.11__py3-none-any.whl → 0.0.13__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/tools.py CHANGED
@@ -7,9 +7,9 @@ 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, final
10
+ from typing_extensions import Concatenate, ParamSpec, TypeAlias
11
11
 
12
- from . import _pydantic, _utils, messages
12
+ from . import _pydantic, _utils, messages as _messages
13
13
  from .exceptions import ModelRetry, UnexpectedModelBehavior
14
14
 
15
15
  if TYPE_CHECKING:
@@ -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,6 +45,8 @@ class RunContext(Generic[AgentDeps]):
42
45
  """Dependencies for the agent."""
43
46
  retry: int
44
47
  """Number of retries so far."""
48
+ messages: list[_messages.ModelMessage]
49
+ """Messages exchanged in the conversation so far."""
45
50
  tool_name: str | None
46
51
  """Name of the tool being called."""
47
52
 
@@ -84,18 +89,44 @@ ToolFuncPlain = Callable[ToolParams, Any]
84
89
  Usage `ToolPlainFunc[ToolParams]`.
85
90
  """
86
91
  ToolFuncEither = Union[ToolFuncContext[AgentDeps, ToolParams], ToolFuncPlain[ToolParams]]
87
- """Either kind of tool function.
92
+ """Either part_kind of tool function.
88
93
 
89
94
  This is just a union of [`ToolFuncContext`][pydantic_ai.tools.ToolFuncContext] and
90
95
  [`ToolFuncPlain`][pydantic_ai.tools.ToolFuncPlain].
91
96
 
92
97
  Usage `ToolFuncEither[AgentDeps, ToolParams]`.
93
98
  """
99
+ ToolPrepareFunc: TypeAlias = 'Callable[[RunContext[AgentDeps], ToolDefinition], Awaitable[ToolDefinition | None]]'
100
+ """Definition of a function that can prepare a tool definition at call time.
101
+
102
+ See [tool docs](../tools.md#tool-prepare) for more information.
103
+
104
+ Example — here `only_if_42` is valid as a `ToolPrepareFunc`:
105
+
106
+ ```python {lint="not-imports"}
107
+ from typing import Union
108
+
109
+ from pydantic_ai import RunContext, Tool
110
+ from pydantic_ai.tools import ToolDefinition
111
+
112
+ async def only_if_42(
113
+ ctx: RunContext[int], tool_def: ToolDefinition
114
+ ) -> Union[ToolDefinition, None]:
115
+ if ctx.deps == 42:
116
+ return tool_def
117
+
118
+ def hitchhiker(ctx: RunContext[int], answer: str) -> str:
119
+ return f'{ctx.deps} {answer}'
120
+
121
+ hitchhiker = Tool(hitchhiker, prepare=only_if_42)
122
+ ```
123
+
124
+ Usage `ToolPrepareFunc[AgentDeps]`.
125
+ """
94
126
 
95
127
  A = TypeVar('A')
96
128
 
97
129
 
98
- @final
99
130
  @dataclass(init=False)
100
131
  class Tool(Generic[AgentDeps]):
101
132
  """A tool function for an agent."""
@@ -105,86 +136,120 @@ class Tool(Generic[AgentDeps]):
105
136
  max_retries: int | None
106
137
  name: str
107
138
  description: str
139
+ prepare: ToolPrepareFunc[AgentDeps] | None
108
140
  _is_async: bool = field(init=False)
109
141
  _single_arg_name: str | None = field(init=False)
110
142
  _positional_fields: list[str] = field(init=False)
111
143
  _var_positional_field: str | None = field(init=False)
112
144
  _validator: SchemaValidator = field(init=False, repr=False)
113
- _json_schema: _utils.ObjectJsonSchema = field(init=False)
114
- _current_retry: int = field(default=0, init=False)
145
+ _parameters_json_schema: ObjectJsonSchema = field(init=False)
146
+ current_retry: int = field(default=0, init=False)
115
147
 
116
148
  def __init__(
117
149
  self,
118
150
  function: ToolFuncEither[AgentDeps, ...],
119
- takes_ctx: bool,
120
151
  *,
152
+ takes_ctx: bool | None = None,
121
153
  max_retries: int | None = None,
122
154
  name: str | None = None,
123
155
  description: str | None = None,
156
+ prepare: ToolPrepareFunc[AgentDeps] | None = None,
124
157
  ):
125
158
  """Create a new tool instance.
126
159
 
127
160
  Example usage:
128
161
 
129
- ```py
162
+ ```python {lint="not-imports"}
163
+ from pydantic_ai import Agent, RunContext, Tool
164
+
165
+ async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
166
+ return f'{ctx.deps} {x} {y}'
167
+
168
+ agent = Agent('test', tools=[Tool(my_tool)])
169
+ ```
170
+
171
+ or with a custom prepare method:
172
+
173
+ ```python {lint="not-imports"}
174
+ from typing import Union
175
+
130
176
  from pydantic_ai import Agent, RunContext, Tool
177
+ from pydantic_ai.tools import ToolDefinition
131
178
 
132
179
  async def my_tool(ctx: RunContext[int], x: int, y: int) -> str:
133
180
  return f'{ctx.deps} {x} {y}'
134
181
 
135
- agent = Agent('test', tools=[Tool(my_tool, True)])
182
+ async def prep_my_tool(
183
+ ctx: RunContext[int], tool_def: ToolDefinition
184
+ ) -> Union[ToolDefinition, None]:
185
+ # only register the tool if `deps == 42`
186
+ if ctx.deps == 42:
187
+ return tool_def
188
+
189
+ agent = Agent('test', tools=[Tool(my_tool, prepare=prep_my_tool)])
136
190
  ```
137
191
 
192
+
138
193
  Args:
139
194
  function: The Python function to call as the tool.
140
- takes_ctx: Whether the function takes a [`RunContext`][pydantic_ai.tools.RunContext] first argument.
195
+ takes_ctx: Whether the function takes a [`RunContext`][pydantic_ai.tools.RunContext] first argument,
196
+ this is inferred if unset.
141
197
  max_retries: Maximum number of retries allowed for this tool, set to the agent default if `None`.
142
198
  name: Name of the tool, inferred from the function if `None`.
143
199
  description: Description of the tool, inferred from the function if `None`.
200
+ prepare: custom method to prepare the tool definition for each step, return `None` to omit this
201
+ tool from a given step. This is useful if you want to customise a tool at call time,
202
+ or omit it completely from a step. See [`ToolPrepareFunc`][pydantic_ai.tools.ToolPrepareFunc].
144
203
  """
204
+ if takes_ctx is None:
205
+ takes_ctx = _pydantic.takes_ctx(function)
206
+
145
207
  f = _pydantic.function_schema(function, takes_ctx)
146
208
  self.function = function
147
209
  self.takes_ctx = takes_ctx
148
210
  self.max_retries = max_retries
149
211
  self.name = name or function.__name__
150
212
  self.description = description or f['description']
213
+ self.prepare = prepare
151
214
  self._is_async = inspect.iscoroutinefunction(self.function)
152
215
  self._single_arg_name = f['single_arg_name']
153
216
  self._positional_fields = f['positional_fields']
154
217
  self._var_positional_field = f['var_positional_field']
155
218
  self._validator = f['validator']
156
- self._json_schema = f['json_schema']
219
+ self._parameters_json_schema = f['json_schema']
157
220
 
158
- @staticmethod
159
- def infer(function: ToolFuncEither[A, ...] | Tool[A]) -> Tool[A]:
160
- """Create a tool from a pure function, inferring whether it takes `RunContext` as its first argument.
221
+ async def prepare_tool_def(self, ctx: RunContext[AgentDeps]) -> ToolDefinition | None:
222
+ """Get the tool definition.
161
223
 
162
- Args:
163
- function: The tool function to wrap; or for convenience, a `Tool` instance.
224
+ By default, this method creates a tool definition, then either returns it, or calls `self.prepare`
225
+ if it's set.
164
226
 
165
227
  Returns:
166
- A new `Tool` instance.
228
+ return a `ToolDefinition` or `None` if the tools should not be registered for this run.
167
229
  """
168
- if isinstance(function, Tool):
169
- return function
230
+ tool_def = ToolDefinition(
231
+ name=self.name,
232
+ description=self.description,
233
+ parameters_json_schema=self._parameters_json_schema,
234
+ )
235
+ if self.prepare is not None:
236
+ return await self.prepare(ctx, tool_def)
170
237
  else:
171
- return Tool(function, takes_ctx=_pydantic.takes_ctx(function))
172
-
173
- def reset(self) -> None:
174
- """Reset the current retry count."""
175
- self._current_retry = 0
238
+ return tool_def
176
239
 
177
- async def run(self, deps: AgentDeps, message: messages.ToolCall) -> messages.Message:
240
+ async def run(
241
+ self, deps: AgentDeps, message: _messages.ToolCallPart, conv_messages: list[_messages.ModelMessage]
242
+ ) -> _messages.ModelRequestPart:
178
243
  """Run the tool function asynchronously."""
179
244
  try:
180
- if isinstance(message.args, messages.ArgsJson):
245
+ if isinstance(message.args, _messages.ArgsJson):
181
246
  args_dict = self._validator.validate_json(message.args.args_json)
182
247
  else:
183
248
  args_dict = self._validator.validate_python(message.args.args_dict)
184
249
  except ValidationError as e:
185
250
  return self._on_error(e, message)
186
251
 
187
- args, kwargs = self._call_args(deps, args_dict, message)
252
+ args, kwargs = self._call_args(deps, args_dict, message, conv_messages)
188
253
  try:
189
254
  if self._is_async:
190
255
  function = cast(Callable[[Any], Awaitable[str]], self.function)
@@ -195,28 +260,24 @@ class Tool(Generic[AgentDeps]):
195
260
  except ModelRetry as e:
196
261
  return self._on_error(e, message)
197
262
 
198
- self._current_retry = 0
199
- return messages.ToolReturn(
263
+ self.current_retry = 0
264
+ return _messages.ToolReturnPart(
200
265
  tool_name=message.tool_name,
201
266
  content=response_content,
202
- tool_id=message.tool_id,
267
+ tool_call_id=message.tool_call_id,
203
268
  )
204
269
 
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
270
  def _call_args(
214
- self, deps: AgentDeps, args_dict: dict[str, Any], message: messages.ToolCall
271
+ self,
272
+ deps: AgentDeps,
273
+ args_dict: dict[str, Any],
274
+ message: _messages.ToolCallPart,
275
+ conv_messages: list[_messages.ModelMessage],
215
276
  ) -> tuple[list[Any], dict[str, Any]]:
216
277
  if self._single_arg_name:
217
278
  args_dict = {self._single_arg_name: args_dict}
218
279
 
219
- args = [RunContext(deps, self._current_retry, message.tool_name)] if self.takes_ctx else []
280
+ args = [RunContext(deps, self.current_retry, conv_messages, message.tool_name)] if self.takes_ctx else []
220
281
  for positional_field in self._positional_fields:
221
282
  args.append(args_dict.pop(positional_field))
222
283
  if self._var_positional_field:
@@ -224,17 +285,51 @@ class Tool(Generic[AgentDeps]):
224
285
 
225
286
  return args, args_dict
226
287
 
227
- def _on_error(self, exc: ValidationError | ModelRetry, call_message: messages.ToolCall) -> messages.RetryPrompt:
228
- self._current_retry += 1
229
- if self.max_retries is None or self._current_retry > self.max_retries:
288
+ def _on_error(
289
+ self, exc: ValidationError | ModelRetry, call_message: _messages.ToolCallPart
290
+ ) -> _messages.RetryPromptPart:
291
+ self.current_retry += 1
292
+ if self.max_retries is None or self.current_retry > self.max_retries:
230
293
  raise UnexpectedModelBehavior(f'Tool exceeded max retries count of {self.max_retries}') from exc
231
294
  else:
232
295
  if isinstance(exc, ValidationError):
233
296
  content = exc.errors(include_url=False)
234
297
  else:
235
298
  content = exc.message
236
- return messages.RetryPrompt(
299
+ return _messages.RetryPromptPart(
237
300
  tool_name=call_message.tool_name,
238
301
  content=content,
239
- tool_id=call_message.tool_id,
302
+ tool_call_id=call_message.tool_call_id,
240
303
  )
304
+
305
+
306
+ ObjectJsonSchema: TypeAlias = dict[str, Any]
307
+ """Type representing JSON schema of an object, e.g. where `"type": "object"`.
308
+
309
+ This type is used to define tools parameters (aka arguments) in [ToolDefinition][pydantic_ai.tools.ToolDefinition].
310
+
311
+ With PEP-728 this should be a TypedDict with `type: Literal['object']`, and `extra_parts=Any`
312
+ """
313
+
314
+
315
+ @dataclass
316
+ class ToolDefinition:
317
+ """Definition of a tool passed to a model.
318
+
319
+ This is used for both function tools result tools.
320
+ """
321
+
322
+ name: str
323
+ """The name of the tool."""
324
+
325
+ description: str
326
+ """The description of the tool."""
327
+
328
+ parameters_json_schema: ObjectJsonSchema
329
+ """The JSON schema for the tool's parameters."""
330
+
331
+ outer_typed_dict_key: str | None = None
332
+ """The key in the outer [TypedDict] that wraps a result tool.
333
+
334
+ This will only be set for result tools which don't have an `object` JSON schema.
335
+ """
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Classifier: Development Status :: 4 - Beta
8
8
  Classifier: Environment :: Console
9
9
  Classifier: Environment :: MacOS X
@@ -29,10 +29,15 @@ Requires-Dist: griffe>=1.3.2
29
29
  Requires-Dist: httpx>=0.27.2
30
30
  Requires-Dist: logfire-api>=1.2.0
31
31
  Requires-Dist: pydantic>=2.10
32
+ Provides-Extra: anthropic
33
+ Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
32
34
  Provides-Extra: groq
33
35
  Requires-Dist: groq>=0.12.0; extra == 'groq'
34
36
  Provides-Extra: logfire
35
37
  Requires-Dist: logfire>=2.3; extra == 'logfire'
38
+ Provides-Extra: mistral
39
+ Requires-Dist: json-repair>=0.30.3; extra == 'mistral'
40
+ Requires-Dist: mistralai>=1.2.5; extra == 'mistral'
36
41
  Provides-Extra: openai
37
42
  Requires-Dist: openai>=1.54.3; extra == 'openai'
38
43
  Provides-Extra: vertexai
@@ -0,0 +1,26 @@
1
+ pydantic_ai/__init__.py,sha256=a29NqQz0JyW4BoCjcRh23fBBfwY17_n57moE4QrFWM4,324
2
+ pydantic_ai/_griffe.py,sha256=pRjCJ6B1hhx6k46XJgl9zF6aRYxRmqEZKFok8unp4Iw,3449
3
+ pydantic_ai/_pydantic.py,sha256=qXi5IsyiYOHeg_-qozCdxkfeqw2z0gBTjqgywBCiJWo,8125
4
+ pydantic_ai/_result.py,sha256=LycNJR_Whc9P7sz2uD-Ce5bs9kQBU6eYqQxCUDNiNxU,10453
5
+ pydantic_ai/_system_prompt.py,sha256=2Ui7fYAXxR_aZZLJdwMnOAecBOIbrKwx1yV4Qz523WQ,1089
6
+ pydantic_ai/_utils.py,sha256=skWNgm89US_x1EpxdRy5wCkghBrm1XgxFCiEh6wAkAo,8753
7
+ pydantic_ai/agent.py,sha256=sDQE20lQXyWO-SrodqSNlzzGwtaLNSkla6NgyJXPKTU,48568
8
+ pydantic_ai/exceptions.py,sha256=ko_47M0k6Rhg9mUC9P1cj7N4LCH6cC0pEsF65A2vL-U,1561
9
+ pydantic_ai/messages.py,sha256=Qa9H5kf9qeI1NqB-XgRjPJ-wwgVKvDZxqam7gnsLtrc,8106
10
+ pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ pydantic_ai/result.py,sha256=ZhaYArCiVl9JlrTllaeFIl2vU2foiIgpQYGby55G4cQ,13664
12
+ pydantic_ai/settings.py,sha256=3sUaDMVMBX9Pku4Bh7lpv6VizX1utenHd5kVIRJvHyY,1908
13
+ pydantic_ai/tools.py,sha256=hhhe5_ELeyYpaRoETMLjml83FAbMY7cpo5us7qwbOWg,11532
14
+ pydantic_ai/models/__init__.py,sha256=y1tkHgWjIGLhZX95dIeOXcljSiAiRGma2T55hNi8EwA,10897
15
+ pydantic_ai/models/anthropic.py,sha256=YBqiYjvOVqsSHPNT2Vt2aMaAAa8nMK57IMPONrtBCyc,13430
16
+ pydantic_ai/models/function.py,sha256=i54ce97lqmy1p7Vqc162EiF_72oA3khc7z2uBGZbuWg,10731
17
+ pydantic_ai/models/gemini.py,sha256=3oy0FVHLOP8SEOvpvoWtlUhRCJpddRj4-J_IPXaEkLE,28277
18
+ pydantic_ai/models/groq.py,sha256=dorqZk8xbZ4ZDzZothJoWbUkoD8TWHb6lftdkNDlsu0,15821
19
+ pydantic_ai/models/mistral.py,sha256=S0K73J5AGKwJc0UU0ifCrPmcxR2QMvVS6L1Cq19xDrk,26793
20
+ pydantic_ai/models/ollama.py,sha256=i3mMXkXu9xL6f4c52Eyx3j4aHKfYoloFondlGHPtkS4,3971
21
+ pydantic_ai/models/openai.py,sha256=fo3ocIOylD8YTuJMTJR1eXcRAQDGFKWFIYYrSOQS1C0,16569
22
+ pydantic_ai/models/test.py,sha256=mBQ0vJYjEMHv01A3yyHR2porkxekpmqIUBkK-W8d-L8,15530
23
+ pydantic_ai/models/vertexai.py,sha256=DBCBfpvpIhZaMG7cKvRl5rugCZqJqqEFm74uBc45weo,9259
24
+ pydantic_ai_slim-0.0.13.dist-info/METADATA,sha256=57JLefiQcRnSOVvkDHcik6wQ24FZ3HOmPrfCaBUe4X8,2785
25
+ pydantic_ai_slim-0.0.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ pydantic_ai_slim-0.0.13.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,