pydantic-ai-slim 1.0.0b1__py3-none-any.whl → 1.0.2__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/_a2a.py +1 -1
- pydantic_ai/_agent_graph.py +65 -49
- pydantic_ai/_parts_manager.py +3 -1
- pydantic_ai/_tool_manager.py +33 -6
- pydantic_ai/ag_ui.py +75 -43
- pydantic_ai/agent/__init__.py +10 -7
- pydantic_ai/durable_exec/dbos/__init__.py +6 -0
- pydantic_ai/durable_exec/dbos/_agent.py +718 -0
- pydantic_ai/durable_exec/dbos/_mcp_server.py +89 -0
- pydantic_ai/durable_exec/dbos/_model.py +137 -0
- pydantic_ai/durable_exec/dbos/_utils.py +10 -0
- pydantic_ai/durable_exec/temporal/_agent.py +71 -10
- pydantic_ai/exceptions.py +2 -2
- pydantic_ai/mcp.py +14 -26
- pydantic_ai/messages.py +90 -19
- pydantic_ai/models/__init__.py +9 -0
- pydantic_ai/models/anthropic.py +28 -11
- pydantic_ai/models/bedrock.py +6 -14
- pydantic_ai/models/gemini.py +3 -1
- pydantic_ai/models/google.py +58 -5
- pydantic_ai/models/groq.py +122 -34
- pydantic_ai/models/instrumented.py +29 -11
- pydantic_ai/models/openai.py +84 -29
- pydantic_ai/providers/__init__.py +4 -0
- pydantic_ai/providers/bedrock.py +11 -3
- pydantic_ai/providers/google_vertex.py +2 -1
- pydantic_ai/providers/groq.py +21 -2
- pydantic_ai/providers/litellm.py +134 -0
- pydantic_ai/retries.py +42 -2
- pydantic_ai/tools.py +18 -7
- pydantic_ai/toolsets/combined.py +2 -2
- pydantic_ai/toolsets/function.py +54 -19
- pydantic_ai/usage.py +37 -3
- {pydantic_ai_slim-1.0.0b1.dist-info → pydantic_ai_slim-1.0.2.dist-info}/METADATA +9 -8
- {pydantic_ai_slim-1.0.0b1.dist-info → pydantic_ai_slim-1.0.2.dist-info}/RECORD +38 -32
- {pydantic_ai_slim-1.0.0b1.dist-info → pydantic_ai_slim-1.0.2.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-1.0.0b1.dist-info → pydantic_ai_slim-1.0.2.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-1.0.0b1.dist-info → pydantic_ai_slim-1.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from typing import overload
|
|
4
|
+
|
|
5
|
+
from httpx import AsyncClient as AsyncHTTPClient
|
|
6
|
+
from openai import AsyncOpenAI
|
|
7
|
+
|
|
8
|
+
from pydantic_ai.models import cached_async_http_client
|
|
9
|
+
from pydantic_ai.profiles import ModelProfile
|
|
10
|
+
from pydantic_ai.profiles.amazon import amazon_model_profile
|
|
11
|
+
from pydantic_ai.profiles.anthropic import anthropic_model_profile
|
|
12
|
+
from pydantic_ai.profiles.cohere import cohere_model_profile
|
|
13
|
+
from pydantic_ai.profiles.deepseek import deepseek_model_profile
|
|
14
|
+
from pydantic_ai.profiles.google import google_model_profile
|
|
15
|
+
from pydantic_ai.profiles.grok import grok_model_profile
|
|
16
|
+
from pydantic_ai.profiles.groq import groq_model_profile
|
|
17
|
+
from pydantic_ai.profiles.meta import meta_model_profile
|
|
18
|
+
from pydantic_ai.profiles.mistral import mistral_model_profile
|
|
19
|
+
from pydantic_ai.profiles.moonshotai import moonshotai_model_profile
|
|
20
|
+
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer, OpenAIModelProfile, openai_model_profile
|
|
21
|
+
from pydantic_ai.profiles.qwen import qwen_model_profile
|
|
22
|
+
from pydantic_ai.providers import Provider
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from openai import AsyncOpenAI
|
|
26
|
+
except ImportError as _import_error: # pragma: no cover
|
|
27
|
+
raise ImportError(
|
|
28
|
+
'Please install the `openai` package to use the LiteLLM provider, '
|
|
29
|
+
'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
|
|
30
|
+
) from _import_error
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LiteLLMProvider(Provider[AsyncOpenAI]):
|
|
34
|
+
"""Provider for LiteLLM API."""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
return 'litellm'
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def base_url(self) -> str:
|
|
42
|
+
return str(self.client.base_url)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def client(self) -> AsyncOpenAI:
|
|
46
|
+
return self._client
|
|
47
|
+
|
|
48
|
+
def model_profile(self, model_name: str) -> ModelProfile | None:
|
|
49
|
+
# Map provider prefixes to their profile functions
|
|
50
|
+
provider_to_profile = {
|
|
51
|
+
'anthropic': anthropic_model_profile,
|
|
52
|
+
'openai': openai_model_profile,
|
|
53
|
+
'google': google_model_profile,
|
|
54
|
+
'mistralai': mistral_model_profile,
|
|
55
|
+
'mistral': mistral_model_profile,
|
|
56
|
+
'cohere': cohere_model_profile,
|
|
57
|
+
'amazon': amazon_model_profile,
|
|
58
|
+
'bedrock': amazon_model_profile,
|
|
59
|
+
'meta-llama': meta_model_profile,
|
|
60
|
+
'meta': meta_model_profile,
|
|
61
|
+
'groq': groq_model_profile,
|
|
62
|
+
'deepseek': deepseek_model_profile,
|
|
63
|
+
'moonshotai': moonshotai_model_profile,
|
|
64
|
+
'x-ai': grok_model_profile,
|
|
65
|
+
'qwen': qwen_model_profile,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
profile = None
|
|
69
|
+
|
|
70
|
+
# Check if model name contains a provider prefix (e.g., "anthropic/claude-3")
|
|
71
|
+
if '/' in model_name:
|
|
72
|
+
provider_prefix, model_suffix = model_name.split('/', 1)
|
|
73
|
+
if provider_prefix in provider_to_profile:
|
|
74
|
+
profile = provider_to_profile[provider_prefix](model_suffix)
|
|
75
|
+
|
|
76
|
+
# If no profile found, default to OpenAI profile
|
|
77
|
+
if profile is None:
|
|
78
|
+
profile = openai_model_profile(model_name)
|
|
79
|
+
|
|
80
|
+
# As LiteLLMProvider is used with OpenAIModel, which uses OpenAIJsonSchemaTransformer,
|
|
81
|
+
# we maintain that behavior
|
|
82
|
+
return OpenAIModelProfile(json_schema_transformer=OpenAIJsonSchemaTransformer).update(profile)
|
|
83
|
+
|
|
84
|
+
@overload
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
api_key: str | None = None,
|
|
89
|
+
api_base: str | None = None,
|
|
90
|
+
) -> None: ...
|
|
91
|
+
|
|
92
|
+
@overload
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
*,
|
|
96
|
+
api_key: str | None = None,
|
|
97
|
+
api_base: str | None = None,
|
|
98
|
+
http_client: AsyncHTTPClient,
|
|
99
|
+
) -> None: ...
|
|
100
|
+
|
|
101
|
+
@overload
|
|
102
|
+
def __init__(self, *, openai_client: AsyncOpenAI) -> None: ...
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
api_key: str | None = None,
|
|
108
|
+
api_base: str | None = None,
|
|
109
|
+
openai_client: AsyncOpenAI | None = None,
|
|
110
|
+
http_client: AsyncHTTPClient | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Initialize a LiteLLM provider.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
api_key: API key for the model provider. If None, LiteLLM will try to get it from environment variables.
|
|
116
|
+
api_base: Base URL for the model provider. Use this for custom endpoints or self-hosted models.
|
|
117
|
+
openai_client: Pre-configured OpenAI client. If provided, other parameters are ignored.
|
|
118
|
+
http_client: Custom HTTP client to use.
|
|
119
|
+
"""
|
|
120
|
+
if openai_client is not None:
|
|
121
|
+
self._client = openai_client
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Create OpenAI client that will be used with LiteLLM's completion function
|
|
125
|
+
# The actual API calls will be intercepted and routed through LiteLLM
|
|
126
|
+
if http_client is not None:
|
|
127
|
+
self._client = AsyncOpenAI(
|
|
128
|
+
base_url=api_base, api_key=api_key or 'litellm-placeholder', http_client=http_client
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
http_client = cached_async_http_client(provider='litellm')
|
|
132
|
+
self._client = AsyncOpenAI(
|
|
133
|
+
base_url=api_base, api_key=api_key or 'litellm-placeholder', http_client=http_client
|
|
134
|
+
)
|
pydantic_ai/retries.py
CHANGED
|
@@ -13,6 +13,8 @@ The module includes:
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
from types import TracebackType
|
|
17
|
+
|
|
16
18
|
from httpx import (
|
|
17
19
|
AsyncBaseTransport,
|
|
18
20
|
AsyncHTTPTransport,
|
|
@@ -185,11 +187,30 @@ class TenacityTransport(BaseTransport):
|
|
|
185
187
|
response.request = req
|
|
186
188
|
|
|
187
189
|
if self.validate_response:
|
|
188
|
-
|
|
190
|
+
try:
|
|
191
|
+
self.validate_response(response)
|
|
192
|
+
except Exception:
|
|
193
|
+
response.close()
|
|
194
|
+
raise
|
|
189
195
|
return response
|
|
190
196
|
|
|
191
197
|
return handle_request(request)
|
|
192
198
|
|
|
199
|
+
def __enter__(self) -> TenacityTransport:
|
|
200
|
+
self.wrapped.__enter__()
|
|
201
|
+
return self
|
|
202
|
+
|
|
203
|
+
def __exit__(
|
|
204
|
+
self,
|
|
205
|
+
exc_type: type[BaseException] | None = None,
|
|
206
|
+
exc_value: BaseException | None = None,
|
|
207
|
+
traceback: TracebackType | None = None,
|
|
208
|
+
) -> None:
|
|
209
|
+
self.wrapped.__exit__(exc_type, exc_value, traceback)
|
|
210
|
+
|
|
211
|
+
def close(self) -> None:
|
|
212
|
+
self.wrapped.close() # pragma: no cover
|
|
213
|
+
|
|
193
214
|
|
|
194
215
|
class AsyncTenacityTransport(AsyncBaseTransport):
|
|
195
216
|
"""Asynchronous HTTP transport with tenacity-based retry functionality.
|
|
@@ -263,11 +284,30 @@ class AsyncTenacityTransport(AsyncBaseTransport):
|
|
|
263
284
|
response.request = req
|
|
264
285
|
|
|
265
286
|
if self.validate_response:
|
|
266
|
-
|
|
287
|
+
try:
|
|
288
|
+
self.validate_response(response)
|
|
289
|
+
except Exception:
|
|
290
|
+
await response.aclose()
|
|
291
|
+
raise
|
|
267
292
|
return response
|
|
268
293
|
|
|
269
294
|
return await handle_async_request(request)
|
|
270
295
|
|
|
296
|
+
async def __aenter__(self) -> AsyncTenacityTransport:
|
|
297
|
+
await self.wrapped.__aenter__()
|
|
298
|
+
return self
|
|
299
|
+
|
|
300
|
+
async def __aexit__(
|
|
301
|
+
self,
|
|
302
|
+
exc_type: type[BaseException] | None = None,
|
|
303
|
+
exc_value: BaseException | None = None,
|
|
304
|
+
traceback: TracebackType | None = None,
|
|
305
|
+
) -> None:
|
|
306
|
+
await self.wrapped.__aexit__(exc_type, exc_value, traceback)
|
|
307
|
+
|
|
308
|
+
async def aclose(self) -> None:
|
|
309
|
+
await self.wrapped.aclose()
|
|
310
|
+
|
|
271
311
|
|
|
272
312
|
def wait_retry_after(
|
|
273
313
|
fallback_strategy: Callable[[RetryCallState], float] | None = None, max_wait: float = 300
|
pydantic_ai/tools.py
CHANGED
|
@@ -70,7 +70,7 @@ Usage `ToolFuncEither[AgentDepsT, ToolParams]`.
|
|
|
70
70
|
ToolPrepareFunc: TypeAlias = Callable[[RunContext[AgentDepsT], 'ToolDefinition'], Awaitable['ToolDefinition | None']]
|
|
71
71
|
"""Definition of a function that can prepare a tool definition at call time.
|
|
72
72
|
|
|
73
|
-
See [tool docs](../tools.md#tool-prepare) for more information.
|
|
73
|
+
See [tool docs](../tools-advanced.md#tool-prepare) for more information.
|
|
74
74
|
|
|
75
75
|
Example — here `only_if_42` is valid as a `ToolPrepareFunc`:
|
|
76
76
|
|
|
@@ -140,7 +140,7 @@ class DeferredToolRequests:
|
|
|
140
140
|
|
|
141
141
|
Results can be passed to the next agent run using a [`DeferredToolResults`][pydantic_ai.tools.DeferredToolResults] object with the same tool call IDs.
|
|
142
142
|
|
|
143
|
-
See [deferred tools docs](../tools.md#deferred-tools) for more information.
|
|
143
|
+
See [deferred tools docs](../deferred-tools.md#deferred-tools) for more information.
|
|
144
144
|
"""
|
|
145
145
|
|
|
146
146
|
calls: list[ToolCallPart] = field(default_factory=list)
|
|
@@ -204,7 +204,7 @@ class DeferredToolResults:
|
|
|
204
204
|
|
|
205
205
|
The tool call IDs need to match those from the [`DeferredToolRequests`][pydantic_ai.output.DeferredToolRequests] output object from the previous run.
|
|
206
206
|
|
|
207
|
-
See [deferred tools docs](../tools.md#deferred-tools) for more information.
|
|
207
|
+
See [deferred tools docs](../deferred-tools.md#deferred-tools) for more information.
|
|
208
208
|
"""
|
|
209
209
|
|
|
210
210
|
calls: dict[str, DeferredToolCallResult | Any] = field(default_factory=dict)
|
|
@@ -253,6 +253,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
253
253
|
docstring_format: DocstringFormat
|
|
254
254
|
require_parameter_descriptions: bool
|
|
255
255
|
strict: bool | None
|
|
256
|
+
sequential: bool
|
|
256
257
|
requires_approval: bool
|
|
257
258
|
function_schema: _function_schema.FunctionSchema
|
|
258
259
|
"""
|
|
@@ -274,6 +275,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
274
275
|
require_parameter_descriptions: bool = False,
|
|
275
276
|
schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
|
|
276
277
|
strict: bool | None = None,
|
|
278
|
+
sequential: bool = False,
|
|
277
279
|
requires_approval: bool = False,
|
|
278
280
|
function_schema: _function_schema.FunctionSchema | None = None,
|
|
279
281
|
):
|
|
@@ -327,8 +329,9 @@ class Tool(Generic[AgentDepsT]):
|
|
|
327
329
|
schema_generator: The JSON schema generator class to use. Defaults to `GenerateToolJsonSchema`.
|
|
328
330
|
strict: Whether to enforce JSON schema compliance (only affects OpenAI).
|
|
329
331
|
See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info.
|
|
332
|
+
sequential: Whether the function requires a sequential/serial execution environment. Defaults to False.
|
|
330
333
|
requires_approval: Whether this tool requires human-in-the-loop approval. Defaults to False.
|
|
331
|
-
See the [tools documentation](../tools.md#human-in-the-loop-tool-approval) for more info.
|
|
334
|
+
See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
|
|
332
335
|
function_schema: The function schema to use for the tool. If not provided, it will be generated.
|
|
333
336
|
"""
|
|
334
337
|
self.function = function
|
|
@@ -347,6 +350,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
347
350
|
self.docstring_format = docstring_format
|
|
348
351
|
self.require_parameter_descriptions = require_parameter_descriptions
|
|
349
352
|
self.strict = strict
|
|
353
|
+
self.sequential = sequential
|
|
350
354
|
self.requires_approval = requires_approval
|
|
351
355
|
|
|
352
356
|
@classmethod
|
|
@@ -357,6 +361,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
357
361
|
description: str | None,
|
|
358
362
|
json_schema: JsonSchemaValue,
|
|
359
363
|
takes_ctx: bool = False,
|
|
364
|
+
sequential: bool = False,
|
|
360
365
|
) -> Self:
|
|
361
366
|
"""Creates a Pydantic tool from a function and a JSON schema.
|
|
362
367
|
|
|
@@ -370,6 +375,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
370
375
|
json_schema: The schema for the function arguments
|
|
371
376
|
takes_ctx: An optional boolean parameter indicating whether the function
|
|
372
377
|
accepts the context object as an argument.
|
|
378
|
+
sequential: Whether the function requires a sequential/serial execution environment. Defaults to False.
|
|
373
379
|
|
|
374
380
|
Returns:
|
|
375
381
|
A Pydantic tool that calls the function
|
|
@@ -389,6 +395,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
389
395
|
name=name,
|
|
390
396
|
description=description,
|
|
391
397
|
function_schema=function_schema,
|
|
398
|
+
sequential=sequential,
|
|
392
399
|
)
|
|
393
400
|
|
|
394
401
|
@property
|
|
@@ -398,6 +405,7 @@ class Tool(Generic[AgentDepsT]):
|
|
|
398
405
|
description=self.description,
|
|
399
406
|
parameters_json_schema=self.function_schema.json_schema,
|
|
400
407
|
strict=self.strict,
|
|
408
|
+
sequential=self.sequential,
|
|
401
409
|
)
|
|
402
410
|
|
|
403
411
|
async def prepare_tool_def(self, ctx: RunContext[AgentDepsT]) -> ToolDefinition | None:
|
|
@@ -466,22 +474,25 @@ class ToolDefinition:
|
|
|
466
474
|
Note: this is currently only supported by OpenAI models.
|
|
467
475
|
"""
|
|
468
476
|
|
|
477
|
+
sequential: bool = False
|
|
478
|
+
"""Whether this tool requires a sequential/serial execution environment."""
|
|
479
|
+
|
|
469
480
|
kind: ToolKind = field(default='function')
|
|
470
481
|
"""The kind of tool:
|
|
471
482
|
|
|
472
483
|
- `'function'`: a tool that will be executed by Pydantic AI during an agent run and has its result returned to the model
|
|
473
484
|
- `'output'`: a tool that passes through an output value that ends the run
|
|
474
485
|
- `'external'`: a tool whose result will be produced outside of the Pydantic AI agent run in which it was called, because it depends on an upstream service (or user) or could take longer to generate than it's reasonable to keep the agent process running.
|
|
475
|
-
See the [tools documentation](../tools.md#deferred-tools) for more info.
|
|
486
|
+
See the [tools documentation](../deferred-tools.md#deferred-tools) for more info.
|
|
476
487
|
- `'unapproved'`: a tool that requires human-in-the-loop approval.
|
|
477
|
-
See the [tools documentation](../tools.md#human-in-the-loop-tool-approval) for more info.
|
|
488
|
+
See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
|
|
478
489
|
"""
|
|
479
490
|
|
|
480
491
|
@property
|
|
481
492
|
def defer(self) -> bool:
|
|
482
493
|
"""Whether calls to this tool will be deferred.
|
|
483
494
|
|
|
484
|
-
See the [tools documentation](../tools.md#deferred-tools) for more info.
|
|
495
|
+
See the [tools documentation](../deferred-tools.md#deferred-tools) for more info.
|
|
485
496
|
"""
|
|
486
497
|
return self.kind in ('external', 'unapproved')
|
|
487
498
|
|
pydantic_ai/toolsets/combined.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from asyncio import Lock
|
|
4
5
|
from collections.abc import Callable, Sequence
|
|
5
6
|
from contextlib import AsyncExitStack
|
|
6
7
|
from dataclasses import dataclass, field, replace
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
|
-
import anyio
|
|
10
10
|
from typing_extensions import Self
|
|
11
11
|
|
|
12
12
|
from .._run_context import AgentDepsT, RunContext
|
|
@@ -31,7 +31,7 @@ class CombinedToolset(AbstractToolset[AgentDepsT]):
|
|
|
31
31
|
|
|
32
32
|
toolsets: Sequence[AbstractToolset[AgentDepsT]]
|
|
33
33
|
|
|
34
|
-
_enter_lock:
|
|
34
|
+
_enter_lock: Lock = field(compare=False, init=False, default_factory=Lock)
|
|
35
35
|
_entered_count: int = field(init=False, default=0)
|
|
36
36
|
_exit_stack: AsyncExitStack | None = field(init=False, default=None)
|
|
37
37
|
|
pydantic_ai/toolsets/function.py
CHANGED
|
@@ -33,9 +33,12 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
33
33
|
See [toolset docs](../toolsets.md#function-toolset) for more information.
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
-
max_retries: int
|
|
37
36
|
tools: dict[str, Tool[Any]]
|
|
37
|
+
max_retries: int
|
|
38
38
|
_id: str | None
|
|
39
|
+
docstring_format: DocstringFormat
|
|
40
|
+
require_parameter_descriptions: bool
|
|
41
|
+
schema_generator: type[GenerateJsonSchema]
|
|
39
42
|
|
|
40
43
|
def __init__(
|
|
41
44
|
self,
|
|
@@ -43,16 +46,30 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
43
46
|
*,
|
|
44
47
|
max_retries: int = 1,
|
|
45
48
|
id: str | None = None,
|
|
49
|
+
docstring_format: DocstringFormat = 'auto',
|
|
50
|
+
require_parameter_descriptions: bool = False,
|
|
51
|
+
schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema,
|
|
46
52
|
):
|
|
47
53
|
"""Build a new function toolset.
|
|
48
54
|
|
|
49
55
|
Args:
|
|
50
56
|
tools: The tools to add to the toolset.
|
|
51
57
|
max_retries: The maximum number of retries for each tool during a run.
|
|
52
|
-
id: An optional unique ID for the toolset. A toolset needs to have an ID in order to be used in a durable execution environment like Temporal,
|
|
58
|
+
id: An optional unique ID for the toolset. A toolset needs to have an ID in order to be used in a durable execution environment like Temporal,
|
|
59
|
+
in which case the ID will be used to identify the toolset's activities within the workflow.
|
|
60
|
+
docstring_format: Format of tool docstring, see [`DocstringFormat`][pydantic_ai.tools.DocstringFormat].
|
|
61
|
+
Defaults to `'auto'`, such that the format is inferred from the structure of the docstring.
|
|
62
|
+
Applies to all tools, unless overridden when adding a tool.
|
|
63
|
+
require_parameter_descriptions: If True, raise an error if a parameter description is missing. Defaults to False.
|
|
64
|
+
Applies to all tools, unless overridden when adding a tool.
|
|
65
|
+
schema_generator: The JSON schema generator class to use for this tool. Defaults to `GenerateToolJsonSchema`.
|
|
66
|
+
Applies to all tools, unless overridden when adding a tool.
|
|
53
67
|
"""
|
|
54
68
|
self.max_retries = max_retries
|
|
55
69
|
self._id = id
|
|
70
|
+
self.docstring_format = docstring_format
|
|
71
|
+
self.require_parameter_descriptions = require_parameter_descriptions
|
|
72
|
+
self.schema_generator = schema_generator
|
|
56
73
|
|
|
57
74
|
self.tools = {}
|
|
58
75
|
for tool in tools:
|
|
@@ -76,10 +93,11 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
76
93
|
name: str | None = None,
|
|
77
94
|
retries: int | None = None,
|
|
78
95
|
prepare: ToolPrepareFunc[AgentDepsT] | None = None,
|
|
79
|
-
docstring_format: DocstringFormat =
|
|
80
|
-
require_parameter_descriptions: bool =
|
|
81
|
-
schema_generator: type[GenerateJsonSchema] =
|
|
96
|
+
docstring_format: DocstringFormat | None = None,
|
|
97
|
+
require_parameter_descriptions: bool | None = None,
|
|
98
|
+
schema_generator: type[GenerateJsonSchema] | None = None,
|
|
82
99
|
strict: bool | None = None,
|
|
100
|
+
sequential: bool = False,
|
|
83
101
|
requires_approval: bool = False,
|
|
84
102
|
) -> Callable[[ToolFuncEither[AgentDepsT, ToolParams]], ToolFuncEither[AgentDepsT, ToolParams]]: ...
|
|
85
103
|
|
|
@@ -91,10 +109,11 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
91
109
|
name: str | None = None,
|
|
92
110
|
retries: int | None = None,
|
|
93
111
|
prepare: ToolPrepareFunc[AgentDepsT] | None = None,
|
|
94
|
-
docstring_format: DocstringFormat =
|
|
95
|
-
require_parameter_descriptions: bool =
|
|
96
|
-
schema_generator: type[GenerateJsonSchema] =
|
|
112
|
+
docstring_format: DocstringFormat | None = None,
|
|
113
|
+
require_parameter_descriptions: bool | None = None,
|
|
114
|
+
schema_generator: type[GenerateJsonSchema] | None = None,
|
|
97
115
|
strict: bool | None = None,
|
|
116
|
+
sequential: bool = False,
|
|
98
117
|
requires_approval: bool = False,
|
|
99
118
|
) -> Any:
|
|
100
119
|
"""Decorator to register a tool function which takes [`RunContext`][pydantic_ai.tools.RunContext] as its first argument.
|
|
@@ -137,13 +156,16 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
137
156
|
tool from a given step. This is useful if you want to customise a tool at call time,
|
|
138
157
|
or omit it completely from a step. See [`ToolPrepareFunc`][pydantic_ai.tools.ToolPrepareFunc].
|
|
139
158
|
docstring_format: The format of the docstring, see [`DocstringFormat`][pydantic_ai.tools.DocstringFormat].
|
|
140
|
-
|
|
141
|
-
require_parameter_descriptions: If True, raise an error if a parameter description is missing.
|
|
142
|
-
|
|
159
|
+
If `None`, the default value is determined by the toolset.
|
|
160
|
+
require_parameter_descriptions: If True, raise an error if a parameter description is missing.
|
|
161
|
+
If `None`, the default value is determined by the toolset.
|
|
162
|
+
schema_generator: The JSON schema generator class to use for this tool.
|
|
163
|
+
If `None`, the default value is determined by the toolset.
|
|
143
164
|
strict: Whether to enforce JSON schema compliance (only affects OpenAI).
|
|
144
165
|
See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info.
|
|
166
|
+
sequential: Whether the function requires a sequential/serial execution environment. Defaults to False.
|
|
145
167
|
requires_approval: Whether this tool requires human-in-the-loop approval. Defaults to False.
|
|
146
|
-
See the [tools documentation](../tools.md#human-in-the-loop-tool-approval) for more info.
|
|
168
|
+
See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
|
|
147
169
|
"""
|
|
148
170
|
|
|
149
171
|
def tool_decorator(
|
|
@@ -160,6 +182,7 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
160
182
|
require_parameter_descriptions,
|
|
161
183
|
schema_generator,
|
|
162
184
|
strict,
|
|
185
|
+
sequential,
|
|
163
186
|
requires_approval,
|
|
164
187
|
)
|
|
165
188
|
return func_
|
|
@@ -173,10 +196,11 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
173
196
|
name: str | None = None,
|
|
174
197
|
retries: int | None = None,
|
|
175
198
|
prepare: ToolPrepareFunc[AgentDepsT] | None = None,
|
|
176
|
-
docstring_format: DocstringFormat =
|
|
177
|
-
require_parameter_descriptions: bool =
|
|
178
|
-
schema_generator: type[GenerateJsonSchema] =
|
|
199
|
+
docstring_format: DocstringFormat | None = None,
|
|
200
|
+
require_parameter_descriptions: bool | None = None,
|
|
201
|
+
schema_generator: type[GenerateJsonSchema] | None = None,
|
|
179
202
|
strict: bool | None = None,
|
|
203
|
+
sequential: bool = False,
|
|
180
204
|
requires_approval: bool = False,
|
|
181
205
|
) -> None:
|
|
182
206
|
"""Add a function as a tool to the toolset.
|
|
@@ -196,14 +220,24 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
196
220
|
tool from a given step. This is useful if you want to customise a tool at call time,
|
|
197
221
|
or omit it completely from a step. See [`ToolPrepareFunc`][pydantic_ai.tools.ToolPrepareFunc].
|
|
198
222
|
docstring_format: The format of the docstring, see [`DocstringFormat`][pydantic_ai.tools.DocstringFormat].
|
|
199
|
-
|
|
200
|
-
require_parameter_descriptions: If True, raise an error if a parameter description is missing.
|
|
201
|
-
|
|
223
|
+
If `None`, the default value is determined by the toolset.
|
|
224
|
+
require_parameter_descriptions: If True, raise an error if a parameter description is missing.
|
|
225
|
+
If `None`, the default value is determined by the toolset.
|
|
226
|
+
schema_generator: The JSON schema generator class to use for this tool.
|
|
227
|
+
If `None`, the default value is determined by the toolset.
|
|
202
228
|
strict: Whether to enforce JSON schema compliance (only affects OpenAI).
|
|
203
229
|
See [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] for more info.
|
|
230
|
+
sequential: Whether the function requires a sequential/serial execution environment. Defaults to False.
|
|
204
231
|
requires_approval: Whether this tool requires human-in-the-loop approval. Defaults to False.
|
|
205
|
-
See the [tools documentation](../tools.md#human-in-the-loop-tool-approval) for more info.
|
|
232
|
+
See the [tools documentation](../deferred-tools.md#human-in-the-loop-tool-approval) for more info.
|
|
206
233
|
"""
|
|
234
|
+
if docstring_format is None:
|
|
235
|
+
docstring_format = self.docstring_format
|
|
236
|
+
if require_parameter_descriptions is None:
|
|
237
|
+
require_parameter_descriptions = self.require_parameter_descriptions
|
|
238
|
+
if schema_generator is None:
|
|
239
|
+
schema_generator = self.schema_generator
|
|
240
|
+
|
|
207
241
|
tool = Tool[AgentDepsT](
|
|
208
242
|
func,
|
|
209
243
|
takes_ctx=takes_ctx,
|
|
@@ -214,6 +248,7 @@ class FunctionToolset(AbstractToolset[AgentDepsT]):
|
|
|
214
248
|
require_parameter_descriptions=require_parameter_descriptions,
|
|
215
249
|
schema_generator=schema_generator,
|
|
216
250
|
strict=strict,
|
|
251
|
+
sequential=sequential,
|
|
217
252
|
requires_approval=requires_approval,
|
|
218
253
|
)
|
|
219
254
|
self.add_tool(tool)
|
pydantic_ai/usage.py
CHANGED
|
@@ -3,7 +3,9 @@ from __future__ import annotations as _annotations
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
from copy import copy
|
|
5
5
|
from dataclasses import dataclass, fields
|
|
6
|
+
from typing import Annotated
|
|
6
7
|
|
|
8
|
+
from pydantic import AliasChoices, BeforeValidator, Field
|
|
7
9
|
from typing_extensions import deprecated, overload
|
|
8
10
|
|
|
9
11
|
from . import _utils
|
|
@@ -14,7 +16,11 @@ __all__ = 'RequestUsage', 'RunUsage', 'Usage', 'UsageLimits'
|
|
|
14
16
|
|
|
15
17
|
@dataclass(repr=False, kw_only=True)
|
|
16
18
|
class UsageBase:
|
|
17
|
-
input_tokens:
|
|
19
|
+
input_tokens: Annotated[
|
|
20
|
+
int,
|
|
21
|
+
# `request_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
|
|
22
|
+
Field(validation_alias=AliasChoices('input_tokens', 'request_tokens')),
|
|
23
|
+
] = 0
|
|
18
24
|
"""Number of input/prompt tokens."""
|
|
19
25
|
|
|
20
26
|
cache_write_tokens: int = 0
|
|
@@ -22,7 +28,11 @@ class UsageBase:
|
|
|
22
28
|
cache_read_tokens: int = 0
|
|
23
29
|
"""Number of tokens read from the cache."""
|
|
24
30
|
|
|
25
|
-
output_tokens:
|
|
31
|
+
output_tokens: Annotated[
|
|
32
|
+
int,
|
|
33
|
+
# `response_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
|
|
34
|
+
Field(validation_alias=AliasChoices('output_tokens', 'response_tokens')),
|
|
35
|
+
] = 0
|
|
26
36
|
"""Number of output/completion tokens."""
|
|
27
37
|
|
|
28
38
|
input_audio_tokens: int = 0
|
|
@@ -32,7 +42,11 @@ class UsageBase:
|
|
|
32
42
|
output_audio_tokens: int = 0
|
|
33
43
|
"""Number of audio output tokens."""
|
|
34
44
|
|
|
35
|
-
details:
|
|
45
|
+
details: Annotated[
|
|
46
|
+
dict[str, int],
|
|
47
|
+
# `details` can not be `None` any longer, but we still want to support deserializing model responses stored in a DB before this was changed
|
|
48
|
+
BeforeValidator(lambda d: d or {}),
|
|
49
|
+
] = dataclasses.field(default_factory=dict)
|
|
36
50
|
"""Any extra details returned by the model."""
|
|
37
51
|
|
|
38
52
|
@property
|
|
@@ -117,6 +131,9 @@ class RunUsage(UsageBase):
|
|
|
117
131
|
requests: int = 0
|
|
118
132
|
"""Number of requests made to the LLM API."""
|
|
119
133
|
|
|
134
|
+
tool_calls: int = 0
|
|
135
|
+
"""Number of successful tool calls executed during the run."""
|
|
136
|
+
|
|
120
137
|
input_tokens: int = 0
|
|
121
138
|
"""Total number of text input/prompt tokens."""
|
|
122
139
|
|
|
@@ -146,6 +163,7 @@ class RunUsage(UsageBase):
|
|
|
146
163
|
"""
|
|
147
164
|
if isinstance(incr_usage, RunUsage):
|
|
148
165
|
self.requests += incr_usage.requests
|
|
166
|
+
self.tool_calls += incr_usage.tool_calls
|
|
149
167
|
return _incr_usage_tokens(self, incr_usage)
|
|
150
168
|
|
|
151
169
|
def __add__(self, other: RunUsage | RequestUsage) -> RunUsage:
|
|
@@ -194,6 +212,8 @@ class UsageLimits:
|
|
|
194
212
|
|
|
195
213
|
request_limit: int | None = 50
|
|
196
214
|
"""The maximum number of requests allowed to the model."""
|
|
215
|
+
tool_calls_limit: int | None = None
|
|
216
|
+
"""The maximum number of successful tool calls allowed to be executed."""
|
|
197
217
|
input_tokens_limit: int | None = None
|
|
198
218
|
"""The maximum number of input/prompt tokens allowed."""
|
|
199
219
|
output_tokens_limit: int | None = None
|
|
@@ -220,12 +240,14 @@ class UsageLimits:
|
|
|
220
240
|
self,
|
|
221
241
|
*,
|
|
222
242
|
request_limit: int | None = 50,
|
|
243
|
+
tool_calls_limit: int | None = None,
|
|
223
244
|
input_tokens_limit: int | None = None,
|
|
224
245
|
output_tokens_limit: int | None = None,
|
|
225
246
|
total_tokens_limit: int | None = None,
|
|
226
247
|
count_tokens_before_request: bool = False,
|
|
227
248
|
) -> None:
|
|
228
249
|
self.request_limit = request_limit
|
|
250
|
+
self.tool_calls_limit = tool_calls_limit
|
|
229
251
|
self.input_tokens_limit = input_tokens_limit
|
|
230
252
|
self.output_tokens_limit = output_tokens_limit
|
|
231
253
|
self.total_tokens_limit = total_tokens_limit
|
|
@@ -239,12 +261,14 @@ class UsageLimits:
|
|
|
239
261
|
self,
|
|
240
262
|
*,
|
|
241
263
|
request_limit: int | None = 50,
|
|
264
|
+
tool_calls_limit: int | None = None,
|
|
242
265
|
request_tokens_limit: int | None = None,
|
|
243
266
|
response_tokens_limit: int | None = None,
|
|
244
267
|
total_tokens_limit: int | None = None,
|
|
245
268
|
count_tokens_before_request: bool = False,
|
|
246
269
|
) -> None:
|
|
247
270
|
self.request_limit = request_limit
|
|
271
|
+
self.tool_calls_limit = tool_calls_limit
|
|
248
272
|
self.input_tokens_limit = request_tokens_limit
|
|
249
273
|
self.output_tokens_limit = response_tokens_limit
|
|
250
274
|
self.total_tokens_limit = total_tokens_limit
|
|
@@ -254,6 +278,7 @@ class UsageLimits:
|
|
|
254
278
|
self,
|
|
255
279
|
*,
|
|
256
280
|
request_limit: int | None = 50,
|
|
281
|
+
tool_calls_limit: int | None = None,
|
|
257
282
|
input_tokens_limit: int | None = None,
|
|
258
283
|
output_tokens_limit: int | None = None,
|
|
259
284
|
total_tokens_limit: int | None = None,
|
|
@@ -263,6 +288,7 @@ class UsageLimits:
|
|
|
263
288
|
response_tokens_limit: int | None = None,
|
|
264
289
|
):
|
|
265
290
|
self.request_limit = request_limit
|
|
291
|
+
self.tool_calls_limit = tool_calls_limit
|
|
266
292
|
self.input_tokens_limit = input_tokens_limit or request_tokens_limit
|
|
267
293
|
self.output_tokens_limit = output_tokens_limit or response_tokens_limit
|
|
268
294
|
self.total_tokens_limit = total_tokens_limit
|
|
@@ -314,4 +340,12 @@ class UsageLimits:
|
|
|
314
340
|
if self.total_tokens_limit is not None and total_tokens > self.total_tokens_limit:
|
|
315
341
|
raise UsageLimitExceeded(f'Exceeded the total_tokens_limit of {self.total_tokens_limit} ({total_tokens=})')
|
|
316
342
|
|
|
343
|
+
def check_before_tool_call(self, usage: RunUsage) -> None:
|
|
344
|
+
"""Raises a `UsageLimitExceeded` exception if the next tool call would exceed the tool call limit."""
|
|
345
|
+
tool_calls_limit = self.tool_calls_limit
|
|
346
|
+
if tool_calls_limit is not None and usage.tool_calls >= tool_calls_limit:
|
|
347
|
+
raise UsageLimitExceeded(
|
|
348
|
+
f'The next tool call would exceed the tool_calls_limit of {tool_calls_limit} (tool_calls={usage.tool_calls})'
|
|
349
|
+
)
|
|
350
|
+
|
|
317
351
|
__repr__ = _utils.dataclasses_no_defaults_repr
|