pydantic-ai-slim 0.0.6a4__py3-none-any.whl → 0.0.8__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/__init__.py +2 -2
- pydantic_ai/_pydantic.py +10 -10
- pydantic_ai/_result.py +4 -4
- pydantic_ai/_system_prompt.py +2 -2
- pydantic_ai/{_retriever.py → _tool.py} +13 -15
- pydantic_ai/_utils.py +9 -5
- pydantic_ai/agent.py +130 -128
- pydantic_ai/dependencies.py +20 -20
- pydantic_ai/exceptions.py +1 -1
- pydantic_ai/messages.py +16 -12
- pydantic_ai/models/__init__.py +3 -3
- pydantic_ai/models/function.py +10 -14
- pydantic_ai/models/gemini.py +12 -30
- pydantic_ai/models/groq.py +2 -2
- pydantic_ai/models/openai.py +2 -2
- pydantic_ai/models/test.py +34 -36
- pydantic_ai/models/vertexai.py +4 -59
- pydantic_ai/result.py +9 -7
- {pydantic_ai_slim-0.0.6a4.dist-info → pydantic_ai_slim-0.0.8.dist-info}/METADATA +7 -3
- pydantic_ai_slim-0.0.8.dist-info/RECORD +23 -0
- pydantic_ai_slim-0.0.6a4.dist-info/RECORD +0 -23
- {pydantic_ai_slim-0.0.6a4.dist-info → pydantic_ai_slim-0.0.8.dist-info}/WHEEL +0 -0
pydantic_ai/messages.py
CHANGED
|
@@ -55,7 +55,7 @@ json_ta: TypeAdapter[JsonData] = TypeAdapter(JsonData)
|
|
|
55
55
|
|
|
56
56
|
@dataclass
|
|
57
57
|
class ToolReturn:
|
|
58
|
-
"""A tool return message, this encodes the result of running a
|
|
58
|
+
"""A tool return message, this encodes the result of running a tool."""
|
|
59
59
|
|
|
60
60
|
tool_name: str
|
|
61
61
|
"""The name of the "tool" was called."""
|
|
@@ -89,10 +89,10 @@ class RetryPrompt:
|
|
|
89
89
|
|
|
90
90
|
This can be sent for a number of reasons:
|
|
91
91
|
|
|
92
|
-
* Pydantic validation of
|
|
92
|
+
* Pydantic validation of tool arguments failed, here content is derived from a Pydantic
|
|
93
93
|
[`ValidationError`][pydantic_core.ValidationError]
|
|
94
|
-
* a
|
|
95
|
-
* no
|
|
94
|
+
* a tool raised a [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] exception
|
|
95
|
+
* no tool was found for the tool name
|
|
96
96
|
* the model returned plain text when a structured response was expected
|
|
97
97
|
* Pydantic validation of a structured response failed, here content is derived from a Pydantic
|
|
98
98
|
[`ValidationError`][pydantic_core.ValidationError]
|
|
@@ -139,13 +139,17 @@ class ModelTextResponse:
|
|
|
139
139
|
|
|
140
140
|
@dataclass
|
|
141
141
|
class ArgsJson:
|
|
142
|
+
"""Tool arguments as a JSON string."""
|
|
143
|
+
|
|
142
144
|
args_json: str
|
|
143
145
|
"""A JSON string of arguments."""
|
|
144
146
|
|
|
145
147
|
|
|
146
148
|
@dataclass
|
|
147
|
-
class
|
|
148
|
-
|
|
149
|
+
class ArgsDict:
|
|
150
|
+
"""Tool arguments as a Python dictionary."""
|
|
151
|
+
|
|
152
|
+
args_dict: dict[str, Any]
|
|
149
153
|
"""A python dictionary of arguments."""
|
|
150
154
|
|
|
151
155
|
|
|
@@ -155,7 +159,7 @@ class ToolCall:
|
|
|
155
159
|
|
|
156
160
|
tool_name: str
|
|
157
161
|
"""The name of the tool to call."""
|
|
158
|
-
args: ArgsJson |
|
|
162
|
+
args: ArgsJson | ArgsDict
|
|
159
163
|
"""The arguments to pass to the tool.
|
|
160
164
|
|
|
161
165
|
Either as JSON or a Python dictionary depending on how data was returned.
|
|
@@ -168,12 +172,12 @@ class ToolCall:
|
|
|
168
172
|
return cls(tool_name, ArgsJson(args_json), tool_id)
|
|
169
173
|
|
|
170
174
|
@classmethod
|
|
171
|
-
def
|
|
172
|
-
return cls(tool_name,
|
|
175
|
+
def from_dict(cls, tool_name: str, args_dict: dict[str, Any]) -> ToolCall:
|
|
176
|
+
return cls(tool_name, ArgsDict(args_dict))
|
|
173
177
|
|
|
174
178
|
def has_content(self) -> bool:
|
|
175
|
-
if isinstance(self.args,
|
|
176
|
-
return any(self.args.
|
|
179
|
+
if isinstance(self.args, ArgsDict):
|
|
180
|
+
return any(self.args.args_dict.values())
|
|
177
181
|
else:
|
|
178
182
|
return bool(self.args.args_json)
|
|
179
183
|
|
|
@@ -182,7 +186,7 @@ class ToolCall:
|
|
|
182
186
|
class ModelStructuredResponse:
|
|
183
187
|
"""A structured response from a model.
|
|
184
188
|
|
|
185
|
-
This is used either to call a
|
|
189
|
+
This is used either to call a tool or to return a structured response from an agent run.
|
|
186
190
|
"""
|
|
187
191
|
|
|
188
192
|
calls: list[ToolCall]
|
pydantic_ai/models/__init__.py
CHANGED
|
@@ -63,7 +63,7 @@ class Model(ABC):
|
|
|
63
63
|
@abstractmethod
|
|
64
64
|
async def agent_model(
|
|
65
65
|
self,
|
|
66
|
-
|
|
66
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
67
67
|
allow_text_result: bool,
|
|
68
68
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
69
69
|
) -> AgentModel:
|
|
@@ -72,7 +72,7 @@ class Model(ABC):
|
|
|
72
72
|
This is async in case slow/async config checks need to be performed that can't be done in `__init__`.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
|
-
|
|
75
|
+
function_tools: The tools available to the agent.
|
|
76
76
|
allow_text_result: Whether a plain text final response/result is permitted.
|
|
77
77
|
result_tools: Tool definitions for the final result tool(s), if any.
|
|
78
78
|
|
|
@@ -259,7 +259,7 @@ def infer_model(model: Model | KnownModelName) -> Model:
|
|
|
259
259
|
class AbstractToolDefinition(Protocol):
|
|
260
260
|
"""Abstract definition of a function/tool.
|
|
261
261
|
|
|
262
|
-
This is used for both
|
|
262
|
+
This is used for both tools and result tools.
|
|
263
263
|
"""
|
|
264
264
|
|
|
265
265
|
name: str
|
pydantic_ai/models/function.py
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
"""A model controlled by a local function.
|
|
2
|
-
|
|
3
|
-
[FunctionModel][pydantic_ai.models.function.FunctionModel] is similar to [TestModel][pydantic_ai.models.test.TestModel],
|
|
4
|
-
but allows greater control over the model's behavior.
|
|
5
|
-
|
|
6
|
-
It's primary use case for more advanced unit testing than is possible with `TestModel`.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
1
|
from __future__ import annotations as _annotations
|
|
10
2
|
|
|
11
3
|
import inspect
|
|
@@ -67,13 +59,13 @@ class FunctionModel(Model):
|
|
|
67
59
|
|
|
68
60
|
async def agent_model(
|
|
69
61
|
self,
|
|
70
|
-
|
|
62
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
71
63
|
allow_text_result: bool,
|
|
72
64
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
73
65
|
) -> AgentModel:
|
|
74
66
|
result_tools = list(result_tools) if result_tools is not None else None
|
|
75
67
|
return FunctionAgentModel(
|
|
76
|
-
self.function, self.stream_function, AgentInfo(
|
|
68
|
+
self.function, self.stream_function, AgentInfo(function_tools, allow_text_result, result_tools)
|
|
77
69
|
)
|
|
78
70
|
|
|
79
71
|
def name(self) -> str:
|
|
@@ -89,11 +81,15 @@ class FunctionModel(Model):
|
|
|
89
81
|
class AgentInfo:
|
|
90
82
|
"""Information about an agent.
|
|
91
83
|
|
|
92
|
-
This is passed as the second to functions.
|
|
84
|
+
This is passed as the second to functions used within [`FunctionModel`][pydantic_ai.models.function.FunctionModel].
|
|
93
85
|
"""
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
"""The
|
|
87
|
+
function_tools: Mapping[str, AbstractToolDefinition]
|
|
88
|
+
"""The function tools available on this agent.
|
|
89
|
+
|
|
90
|
+
These are the tools registered via the [`tool`][pydantic_ai.Agent.tool] and
|
|
91
|
+
[`tool_plain`][pydantic_ai.Agent.tool_plain] decorators.
|
|
92
|
+
"""
|
|
97
93
|
allow_text_result: bool
|
|
98
94
|
"""Whether a plain text result is allowed."""
|
|
99
95
|
result_tools: list[AbstractToolDefinition] | None
|
|
@@ -254,7 +250,7 @@ def _estimate_cost(messages: Iterable[Message]) -> result.Cost:
|
|
|
254
250
|
if isinstance(call.args, ArgsJson):
|
|
255
251
|
args_str = call.args.args_json
|
|
256
252
|
else:
|
|
257
|
-
args_str = pydantic_core.to_json(call.args.
|
|
253
|
+
args_str = pydantic_core.to_json(call.args.args_dict).decode()
|
|
258
254
|
|
|
259
255
|
response_tokens += 1 + _string_cost(args_str)
|
|
260
256
|
else:
|
pydantic_ai/models/gemini.py
CHANGED
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
"""Custom interface to the `generativelanguage.googleapis.com` API using [HTTPX] and [Pydantic].
|
|
2
|
-
|
|
3
|
-
The Google SDK for interacting with the `generativelanguage.googleapis.com` API
|
|
4
|
-
[`google-generativeai`](https://ai.google.dev/gemini-api/docs/quickstart?lang=python) reads like it was written by a
|
|
5
|
-
Java developer who thought they knew everything about OOP, spent 30 minutes trying to learn Python,
|
|
6
|
-
gave up and decided to build the library to prove how horrible Python is. It also doesn't use httpx for HTTP requests,
|
|
7
|
-
and tries to implement tool calling itself, but doesn't use Pydantic or equivalent for validation.
|
|
8
|
-
|
|
9
|
-
We could also use the Google Vertex SDK,
|
|
10
|
-
[`google-cloud-aiplatform`](https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk)
|
|
11
|
-
which uses the `*-aiplatform.googleapis.com` API, but that requires a service account for authentication
|
|
12
|
-
which is a faff to set up and manage.
|
|
13
|
-
|
|
14
|
-
Both APIs claim compatibility with OpenAI's API, but that breaks down with even the simplest of requests,
|
|
15
|
-
hence this custom interface.
|
|
16
|
-
|
|
17
|
-
Despite these limitations, the Gemini model is actually quite powerful and very fast.
|
|
18
|
-
|
|
19
|
-
[HTTPX]: https://www.python-httpx.org/
|
|
20
|
-
[Pydantic]: https://docs.pydantic.dev/latest/
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
1
|
from __future__ import annotations as _annotations
|
|
24
2
|
|
|
25
3
|
import os
|
|
@@ -38,7 +16,7 @@ from typing_extensions import NotRequired, TypedDict, TypeGuard, assert_never
|
|
|
38
16
|
|
|
39
17
|
from .. import UnexpectedModelBehavior, _pydantic, _utils, exceptions, result
|
|
40
18
|
from ..messages import (
|
|
41
|
-
|
|
19
|
+
ArgsDict,
|
|
42
20
|
Message,
|
|
43
21
|
ModelAnyResponse,
|
|
44
22
|
ModelStructuredResponse,
|
|
@@ -112,7 +90,7 @@ class GeminiModel(Model):
|
|
|
112
90
|
|
|
113
91
|
async def agent_model(
|
|
114
92
|
self,
|
|
115
|
-
|
|
93
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
116
94
|
allow_text_result: bool,
|
|
117
95
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
118
96
|
) -> GeminiAgentModel:
|
|
@@ -121,7 +99,7 @@ class GeminiModel(Model):
|
|
|
121
99
|
model_name=self.model_name,
|
|
122
100
|
auth=self.auth,
|
|
123
101
|
url=self.url,
|
|
124
|
-
|
|
102
|
+
function_tools=function_tools,
|
|
125
103
|
allow_text_result=allow_text_result,
|
|
126
104
|
result_tools=result_tools,
|
|
127
105
|
)
|
|
@@ -131,11 +109,15 @@ class GeminiModel(Model):
|
|
|
131
109
|
|
|
132
110
|
|
|
133
111
|
class AuthProtocol(Protocol):
|
|
112
|
+
"""Abstract definition for Gemini authentication."""
|
|
113
|
+
|
|
134
114
|
async def headers(self) -> dict[str, str]: ...
|
|
135
115
|
|
|
136
116
|
|
|
137
117
|
@dataclass
|
|
138
118
|
class ApiKeyAuth:
|
|
119
|
+
"""Authentication using an API key for the `X-Goog-Api-Key` header."""
|
|
120
|
+
|
|
139
121
|
api_key: str
|
|
140
122
|
|
|
141
123
|
async def headers(self) -> dict[str, str]:
|
|
@@ -160,12 +142,12 @@ class GeminiAgentModel(AgentModel):
|
|
|
160
142
|
model_name: GeminiModelName,
|
|
161
143
|
auth: AuthProtocol,
|
|
162
144
|
url: str,
|
|
163
|
-
|
|
145
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
164
146
|
allow_text_result: bool,
|
|
165
147
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
166
148
|
):
|
|
167
149
|
check_allow_model_requests()
|
|
168
|
-
tools = [_function_from_abstract_tool(t) for t in
|
|
150
|
+
tools = [_function_from_abstract_tool(t) for t in function_tools.values()]
|
|
169
151
|
if result_tools is not None:
|
|
170
152
|
tools += [_function_from_abstract_tool(t) for t in result_tools]
|
|
171
153
|
|
|
@@ -442,15 +424,15 @@ class _GeminiFunctionCallPart(TypedDict):
|
|
|
442
424
|
|
|
443
425
|
|
|
444
426
|
def _function_call_part_from_call(tool: ToolCall) -> _GeminiFunctionCallPart:
|
|
445
|
-
assert isinstance(tool.args,
|
|
446
|
-
return _GeminiFunctionCallPart(function_call=_GeminiFunctionCall(name=tool.tool_name, args=tool.args.
|
|
427
|
+
assert isinstance(tool.args, ArgsDict), f'Expected ArgsObject, got {tool.args}'
|
|
428
|
+
return _GeminiFunctionCallPart(function_call=_GeminiFunctionCall(name=tool.tool_name, args=tool.args.args_dict))
|
|
447
429
|
|
|
448
430
|
|
|
449
431
|
def _structured_response_from_parts(
|
|
450
432
|
parts: list[_GeminiFunctionCallPart], timestamp: datetime | None = None
|
|
451
433
|
) -> ModelStructuredResponse:
|
|
452
434
|
return ModelStructuredResponse(
|
|
453
|
-
calls=[ToolCall.
|
|
435
|
+
calls=[ToolCall.from_dict(part['function_call']['name'], part['function_call']['args']) for part in parts],
|
|
454
436
|
timestamp=timestamp or _utils.now_utc(),
|
|
455
437
|
)
|
|
456
438
|
|
pydantic_ai/models/groq.py
CHANGED
|
@@ -109,12 +109,12 @@ class GroqModel(Model):
|
|
|
109
109
|
|
|
110
110
|
async def agent_model(
|
|
111
111
|
self,
|
|
112
|
-
|
|
112
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
113
113
|
allow_text_result: bool,
|
|
114
114
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
115
115
|
) -> AgentModel:
|
|
116
116
|
check_allow_model_requests()
|
|
117
|
-
tools = [self._map_tool_definition(r) for r in
|
|
117
|
+
tools = [self._map_tool_definition(r) for r in function_tools.values()]
|
|
118
118
|
if result_tools is not None:
|
|
119
119
|
tools += [self._map_tool_definition(r) for r in result_tools]
|
|
120
120
|
return GroqAgentModel(
|
pydantic_ai/models/openai.py
CHANGED
|
@@ -89,12 +89,12 @@ class OpenAIModel(Model):
|
|
|
89
89
|
|
|
90
90
|
async def agent_model(
|
|
91
91
|
self,
|
|
92
|
-
|
|
92
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
93
93
|
allow_text_result: bool,
|
|
94
94
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
95
95
|
) -> AgentModel:
|
|
96
96
|
check_allow_model_requests()
|
|
97
|
-
tools = [self._map_tool_definition(r) for r in
|
|
97
|
+
tools = [self._map_tool_definition(r) for r in function_tools.values()]
|
|
98
98
|
if result_tools is not None:
|
|
99
99
|
tools += [self._map_tool_definition(r) for r in result_tools]
|
|
100
100
|
return OpenAIAgentModel(
|
pydantic_ai/models/test.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Utilities for testing apps built with PydanticAI."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations as _annotations
|
|
4
2
|
|
|
5
3
|
import re
|
|
@@ -7,7 +5,7 @@ import string
|
|
|
7
5
|
from collections.abc import AsyncIterator, Iterable, Iterator, Mapping, Sequence
|
|
8
6
|
from contextlib import asynccontextmanager
|
|
9
7
|
from dataclasses import dataclass, field
|
|
10
|
-
from datetime import datetime
|
|
8
|
+
from datetime import date, datetime, timedelta
|
|
11
9
|
from typing import Any, Literal
|
|
12
10
|
|
|
13
11
|
import pydantic_core
|
|
@@ -33,22 +31,14 @@ from . import (
|
|
|
33
31
|
)
|
|
34
32
|
|
|
35
33
|
|
|
36
|
-
class UnSetType:
|
|
37
|
-
def __repr__(self):
|
|
38
|
-
return 'UnSet'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
UnSet = UnSetType()
|
|
42
|
-
|
|
43
|
-
|
|
44
34
|
@dataclass
|
|
45
35
|
class TestModel(Model):
|
|
46
36
|
"""A model specifically for testing purposes.
|
|
47
37
|
|
|
48
|
-
This will (by default) call all
|
|
38
|
+
This will (by default) call all tools in the agent, then return a tool response if possible,
|
|
49
39
|
otherwise a plain response.
|
|
50
40
|
|
|
51
|
-
How useful this
|
|
41
|
+
How useful this model is will vary significantly.
|
|
52
42
|
|
|
53
43
|
Apart from `__init__` derived by the `dataclass` decorator, all methods are private or match those
|
|
54
44
|
of the base class.
|
|
@@ -57,8 +47,8 @@ class TestModel(Model):
|
|
|
57
47
|
# NOTE: Avoid test discovery by pytest.
|
|
58
48
|
__test__ = False
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
"""List of
|
|
50
|
+
call_tools: list[str] | Literal['all'] = 'all'
|
|
51
|
+
"""List of tools to call. If `'all'`, all tools will be called."""
|
|
62
52
|
custom_result_text: str | None = None
|
|
63
53
|
"""If set, this text is return as the final result."""
|
|
64
54
|
custom_result_args: Any | None = None
|
|
@@ -66,25 +56,25 @@ class TestModel(Model):
|
|
|
66
56
|
seed: int = 0
|
|
67
57
|
"""Seed for generating random data."""
|
|
68
58
|
# these fields are set when the model is called by the agent
|
|
69
|
-
|
|
59
|
+
agent_model_tools: Mapping[str, AbstractToolDefinition] | None = field(default=None, init=False)
|
|
70
60
|
agent_model_allow_text_result: bool | None = field(default=None, init=False)
|
|
71
61
|
agent_model_result_tools: list[AbstractToolDefinition] | None = field(default=None, init=False)
|
|
72
62
|
|
|
73
63
|
async def agent_model(
|
|
74
64
|
self,
|
|
75
|
-
|
|
65
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
76
66
|
allow_text_result: bool,
|
|
77
67
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
78
68
|
) -> AgentModel:
|
|
79
|
-
self.
|
|
69
|
+
self.agent_model_tools = function_tools
|
|
80
70
|
self.agent_model_allow_text_result = allow_text_result
|
|
81
71
|
self.agent_model_result_tools = list(result_tools) if result_tools is not None else None
|
|
82
72
|
|
|
83
|
-
if self.
|
|
84
|
-
|
|
73
|
+
if self.call_tools == 'all':
|
|
74
|
+
tool_calls = [(r.name, r) for r in function_tools.values()]
|
|
85
75
|
else:
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
tools_to_call = (function_tools[name] for name in self.call_tools)
|
|
77
|
+
tool_calls = [(r.name, r) for r in tools_to_call]
|
|
88
78
|
|
|
89
79
|
if self.custom_result_text is not None:
|
|
90
80
|
assert allow_text_result, 'Plain response not allowed, but `custom_result_text` is set.'
|
|
@@ -104,7 +94,7 @@ class TestModel(Model):
|
|
|
104
94
|
result = _utils.Either(right=None)
|
|
105
95
|
else:
|
|
106
96
|
result = _utils.Either(left=None)
|
|
107
|
-
return TestAgentModel(
|
|
97
|
+
return TestAgentModel(tool_calls, result, self.agent_model_result_tools, self.seed)
|
|
108
98
|
|
|
109
99
|
def name(self) -> str:
|
|
110
100
|
return 'test-model'
|
|
@@ -117,7 +107,7 @@ class TestAgentModel(AgentModel):
|
|
|
117
107
|
# NOTE: Avoid test discovery by pytest.
|
|
118
108
|
__test__ = False
|
|
119
109
|
|
|
120
|
-
|
|
110
|
+
tool_calls: list[tuple[str, AbstractToolDefinition]]
|
|
121
111
|
# left means the text is plain text; right means it's a function call
|
|
122
112
|
result: _utils.Either[str | None, Any | None]
|
|
123
113
|
result_tools: list[AbstractToolDefinition] | None
|
|
@@ -137,12 +127,12 @@ class TestAgentModel(AgentModel):
|
|
|
137
127
|
else:
|
|
138
128
|
yield TestStreamStructuredResponse(msg, cost)
|
|
139
129
|
|
|
140
|
-
def
|
|
130
|
+
def gen_tool_args(self, tool_def: AbstractToolDefinition) -> Any:
|
|
141
131
|
return _JsonSchemaTestData(tool_def.json_schema, self.seed).generate()
|
|
142
132
|
|
|
143
133
|
def _request(self, messages: list[Message]) -> ModelAnyResponse:
|
|
144
|
-
if self.step == 0 and self.
|
|
145
|
-
calls = [ToolCall.
|
|
134
|
+
if self.step == 0 and self.tool_calls:
|
|
135
|
+
calls = [ToolCall.from_dict(name, self.gen_tool_args(args)) for name, args in self.tool_calls]
|
|
146
136
|
self.step += 1
|
|
147
137
|
self.last_message_count = len(messages)
|
|
148
138
|
return ModelStructuredResponse(calls=calls)
|
|
@@ -152,8 +142,8 @@ class TestAgentModel(AgentModel):
|
|
|
152
142
|
new_retry_names = {m.tool_name for m in new_messages if isinstance(m, RetryPrompt)}
|
|
153
143
|
if new_retry_names:
|
|
154
144
|
calls = [
|
|
155
|
-
ToolCall.
|
|
156
|
-
for name, args in self.
|
|
145
|
+
ToolCall.from_dict(name, self.gen_tool_args(args))
|
|
146
|
+
for name, args in self.tool_calls
|
|
157
147
|
if name in new_retry_names
|
|
158
148
|
]
|
|
159
149
|
self.step += 1
|
|
@@ -162,7 +152,7 @@ class TestAgentModel(AgentModel):
|
|
|
162
152
|
if response_text := self.result.left:
|
|
163
153
|
self.step += 1
|
|
164
154
|
if response_text.value is None:
|
|
165
|
-
# build up details of
|
|
155
|
+
# build up details of tool responses
|
|
166
156
|
output: dict[str, Any] = {}
|
|
167
157
|
for message in messages:
|
|
168
158
|
if isinstance(message, ToolReturn):
|
|
@@ -170,7 +160,7 @@ class TestAgentModel(AgentModel):
|
|
|
170
160
|
if output:
|
|
171
161
|
return ModelTextResponse(content=pydantic_core.to_json(output).decode())
|
|
172
162
|
else:
|
|
173
|
-
return ModelTextResponse(content='success (no
|
|
163
|
+
return ModelTextResponse(content='success (no tool calls)')
|
|
174
164
|
else:
|
|
175
165
|
return ModelTextResponse(content=response_text.value)
|
|
176
166
|
else:
|
|
@@ -179,15 +169,17 @@ class TestAgentModel(AgentModel):
|
|
|
179
169
|
result_tool = self.result_tools[self.seed % len(self.result_tools)]
|
|
180
170
|
if custom_result_args is not None:
|
|
181
171
|
self.step += 1
|
|
182
|
-
return ModelStructuredResponse(calls=[ToolCall.
|
|
172
|
+
return ModelStructuredResponse(calls=[ToolCall.from_dict(result_tool.name, custom_result_args)])
|
|
183
173
|
else:
|
|
184
|
-
response_args = self.
|
|
174
|
+
response_args = self.gen_tool_args(result_tool)
|
|
185
175
|
self.step += 1
|
|
186
|
-
return ModelStructuredResponse(calls=[ToolCall.
|
|
176
|
+
return ModelStructuredResponse(calls=[ToolCall.from_dict(result_tool.name, response_args)])
|
|
187
177
|
|
|
188
178
|
|
|
189
179
|
@dataclass
|
|
190
180
|
class TestStreamTextResponse(StreamTextResponse):
|
|
181
|
+
"""A text response that streams test data."""
|
|
182
|
+
|
|
191
183
|
_text: str
|
|
192
184
|
_cost: Cost
|
|
193
185
|
_iter: Iterator[str] = field(init=False)
|
|
@@ -219,6 +211,8 @@ class TestStreamTextResponse(StreamTextResponse):
|
|
|
219
211
|
|
|
220
212
|
@dataclass
|
|
221
213
|
class TestStreamStructuredResponse(StreamStructuredResponse):
|
|
214
|
+
"""A structured response that streams test data."""
|
|
215
|
+
|
|
222
216
|
_structured_response: ModelStructuredResponse
|
|
223
217
|
_cost: Cost
|
|
224
218
|
_iter: Iterator[None] = field(default_factory=lambda: iter([None]))
|
|
@@ -320,8 +314,12 @@ class _JsonSchemaTestData:
|
|
|
320
314
|
|
|
321
315
|
if schema.get('maxLength') == 0:
|
|
322
316
|
return ''
|
|
323
|
-
|
|
324
|
-
|
|
317
|
+
|
|
318
|
+
if fmt := schema.get('format'):
|
|
319
|
+
if fmt == 'date':
|
|
320
|
+
return (date(2024, 1, 1) + timedelta(days=self.seed)).isoformat()
|
|
321
|
+
|
|
322
|
+
return self._char()
|
|
325
323
|
|
|
326
324
|
def _int_gen(self, schema: dict[str, Any]) -> int:
|
|
327
325
|
"""Generate an integer from a JSON Schema integer."""
|
pydantic_ai/models/vertexai.py
CHANGED
|
@@ -1,60 +1,3 @@
|
|
|
1
|
-
"""Custom interface to the `*-aiplatform.googleapis.com` API for Gemini models.
|
|
2
|
-
|
|
3
|
-
This model uses [`GeminiAgentModel`][pydantic_ai.models.gemini.GeminiAgentModel] with just the URL and auth method
|
|
4
|
-
changed from the default `GeminiModel`, it relies on the VertexAI
|
|
5
|
-
[`generateContent` function endpoint](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.endpoints/generateContent)
|
|
6
|
-
and `streamGenerateContent` function endpoints
|
|
7
|
-
having the same schemas as the equivalent [Gemini endpoints][pydantic_ai.models.gemini.GeminiModel].
|
|
8
|
-
|
|
9
|
-
There are four advantages of using this API over the `generativelanguage.googleapis.com` API which
|
|
10
|
-
[`GeminiModel`][pydantic_ai.models.gemini.GeminiModel] uses, and one big disadvantage.
|
|
11
|
-
|
|
12
|
-
Advantages:
|
|
13
|
-
|
|
14
|
-
1. The VertexAI API seems to be less flakey, less likely to occasionally return a 503 response.
|
|
15
|
-
2. You can
|
|
16
|
-
[purchase provisioned throughput](https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput#purchase-provisioned-throughput)
|
|
17
|
-
with VertexAI.
|
|
18
|
-
3. If you're running PydanticAI inside GCP, you don't need to set up authentication, it should "just work".
|
|
19
|
-
4. You can decide which region to use, which might be important from a regulatory perspective,
|
|
20
|
-
and might improve latency.
|
|
21
|
-
|
|
22
|
-
Disadvantage:
|
|
23
|
-
|
|
24
|
-
1. When authorization doesn't just work, it's much more painful to set up than an API key.
|
|
25
|
-
|
|
26
|
-
## Example Usage
|
|
27
|
-
|
|
28
|
-
With the default google project already configured in your environment:
|
|
29
|
-
|
|
30
|
-
```py title="vertex_example_env.py"
|
|
31
|
-
from pydantic_ai import Agent
|
|
32
|
-
from pydantic_ai.models.vertexai import VertexAIModel
|
|
33
|
-
|
|
34
|
-
model = VertexAIModel('gemini-1.5-flash')
|
|
35
|
-
agent = Agent(model)
|
|
36
|
-
result = agent.run_sync('Tell me a joke.')
|
|
37
|
-
print(result.data)
|
|
38
|
-
#> Did you hear about the toothpaste scandal? They called it Colgate.
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Or using a service account JSON file:
|
|
42
|
-
|
|
43
|
-
```py title="vertex_example_service_account.py"
|
|
44
|
-
from pydantic_ai import Agent
|
|
45
|
-
from pydantic_ai.models.vertexai import VertexAIModel
|
|
46
|
-
|
|
47
|
-
model = VertexAIModel(
|
|
48
|
-
'gemini-1.5-flash',
|
|
49
|
-
service_account_file='path/to/service-account.json',
|
|
50
|
-
)
|
|
51
|
-
agent = Agent(model)
|
|
52
|
-
result = agent.run_sync('Tell me a joke.')
|
|
53
|
-
print(result.data)
|
|
54
|
-
#> Did you hear about the toothpaste scandal? They called it Colgate.
|
|
55
|
-
```
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
1
|
from __future__ import annotations as _annotations
|
|
59
2
|
|
|
60
3
|
from collections.abc import Mapping, Sequence
|
|
@@ -166,7 +109,7 @@ class VertexAIModel(Model):
|
|
|
166
109
|
|
|
167
110
|
async def agent_model(
|
|
168
111
|
self,
|
|
169
|
-
|
|
112
|
+
function_tools: Mapping[str, AbstractToolDefinition],
|
|
170
113
|
allow_text_result: bool,
|
|
171
114
|
result_tools: Sequence[AbstractToolDefinition] | None,
|
|
172
115
|
) -> GeminiAgentModel:
|
|
@@ -176,7 +119,7 @@ class VertexAIModel(Model):
|
|
|
176
119
|
model_name=self.model_name,
|
|
177
120
|
auth=auth,
|
|
178
121
|
url=url,
|
|
179
|
-
|
|
122
|
+
function_tools=function_tools,
|
|
180
123
|
allow_text_result=allow_text_result,
|
|
181
124
|
result_tools=result_tools,
|
|
182
125
|
)
|
|
@@ -239,6 +182,8 @@ MAX_TOKEN_AGE = timedelta(seconds=3000)
|
|
|
239
182
|
|
|
240
183
|
@dataclass
|
|
241
184
|
class BearerTokenAuth:
|
|
185
|
+
"""Authentication using a bearer token generated by google-auth."""
|
|
186
|
+
|
|
242
187
|
credentials: BaseCredentials | ServiceAccountCredentials
|
|
243
188
|
token_created: datetime | None = field(default=None, init=False)
|
|
244
189
|
|
pydantic_ai/result.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from collections.abc import AsyncIterator
|
|
5
|
-
from dataclasses import dataclass
|
|
4
|
+
from collections.abc import AsyncIterator, Callable
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import Generic, TypeVar, cast
|
|
8
8
|
|
|
@@ -49,11 +49,11 @@ class Cost:
|
|
|
49
49
|
This is provided so it's trivial to sum costs from multiple requests and runs.
|
|
50
50
|
"""
|
|
51
51
|
counts: dict[str, int] = {}
|
|
52
|
-
for
|
|
53
|
-
self_value = getattr(self,
|
|
54
|
-
other_value = getattr(other,
|
|
52
|
+
for f in 'request_tokens', 'response_tokens', 'total_tokens':
|
|
53
|
+
self_value = getattr(self, f)
|
|
54
|
+
other_value = getattr(other, f)
|
|
55
55
|
if self_value is not None or other_value is not None:
|
|
56
|
-
counts[
|
|
56
|
+
counts[f] = (self_value or 0) + (other_value or 0)
|
|
57
57
|
|
|
58
58
|
details = self.details.copy() if self.details is not None else None
|
|
59
59
|
if other.details is not None:
|
|
@@ -122,7 +122,8 @@ class StreamedRunResult(_BaseRunResult[ResultData], Generic[AgentDeps, ResultDat
|
|
|
122
122
|
_result_schema: _result.ResultSchema[ResultData] | None
|
|
123
123
|
_deps: AgentDeps
|
|
124
124
|
_result_validators: list[_result.ResultValidator[AgentDeps, ResultData]]
|
|
125
|
-
|
|
125
|
+
_on_complete: Callable[[list[messages.Message]], None]
|
|
126
|
+
is_complete: bool = field(default=False, init=False)
|
|
126
127
|
"""Whether the stream has all been received.
|
|
127
128
|
|
|
128
129
|
This is set to `True` when one of
|
|
@@ -312,3 +313,4 @@ class StreamedRunResult(_BaseRunResult[ResultData], Generic[AgentDeps, ResultDat
|
|
|
312
313
|
else:
|
|
313
314
|
assert structured_message is not None, 'Either text or structured_message should provided, not both'
|
|
314
315
|
self._all_messages.append(structured_message)
|
|
316
|
+
self._on_complete(self._all_messages)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Agent Framework / shim to use Pydantic with LLMs
|
|
3
|
+
Version: 0.0.8
|
|
4
|
+
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Author-email: Samuel Colvin <samuel@pydantic.dev>
|
|
6
6
|
License: MIT
|
|
7
7
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -40,10 +40,14 @@ Requires-Dist: google-auth>=2.36.0; extra == 'vertexai'
|
|
|
40
40
|
Requires-Dist: requests>=2.32.3; extra == 'vertexai'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# PydanticAI Slim
|
|
44
44
|
|
|
45
45
|
[](https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml?query=branch%3Amain)
|
|
46
46
|
[](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic-ai)
|
|
47
47
|
[](https://pypi.python.org/pypi/pydantic-ai)
|
|
48
48
|
[](https://github.com/pydantic/pydantic-ai)
|
|
49
49
|
[](https://github.com/pydantic/pydantic-ai/blob/main/LICENSE)
|
|
50
|
+
|
|
51
|
+
PydanticAI core logic with minimal required dependencies.
|
|
52
|
+
|
|
53
|
+
For more information on how to use this package see [ai.pydantic.dev/install](https://ai.pydantic.dev/install/).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pydantic_ai/__init__.py,sha256=KaTzG8uBSEpfxk_y2a_O_R2Xa53GXYfiigjWtD4PCeI,317
|
|
2
|
+
pydantic_ai/_griffe.py,sha256=pRjCJ6B1hhx6k46XJgl9zF6aRYxRmqEZKFok8unp4Iw,3449
|
|
3
|
+
pydantic_ai/_pydantic.py,sha256=j1kObPIUDwn1VOHbJBwMFbWLxHM_OYRH7GFAda68ZC0,8010
|
|
4
|
+
pydantic_ai/_result.py,sha256=cAqfPipK39cz-p-ftlJ83Q5_LI1TRb3-HH-iivb5rEM,9674
|
|
5
|
+
pydantic_ai/_system_prompt.py,sha256=63egOej8zHsDVOInPayn0EEEDXKd0HVAbbrqXUTV96s,1092
|
|
6
|
+
pydantic_ai/_tool.py,sha256=5Q9XaGOEXbyOLS644osB1AA5EMoJkr4eYK60MVZo0Z8,4528
|
|
7
|
+
pydantic_ai/_utils.py,sha256=eNb7f3-ZQC8WDEa87iUcXGQ-lyuutFQG-5yBCMD4Vvs,8227
|
|
8
|
+
pydantic_ai/agent.py,sha256=r5DI4ZBqYE67GOMEEu-LXrTa5ty2AchW4szotwm5Qis,34338
|
|
9
|
+
pydantic_ai/dependencies.py,sha256=EHvD68AFkItxMnfHzJLG7T_AD1RGI2MZOfzm1v89hGQ,2399
|
|
10
|
+
pydantic_ai/exceptions.py,sha256=ko_47M0k6Rhg9mUC9P1cj7N4LCH6cC0pEsF65A2vL-U,1561
|
|
11
|
+
pydantic_ai/messages.py,sha256=FFTQ9Bo2Ct4bLuyJF-M9xkeraw05I--NC_ieR6oGtTM,7587
|
|
12
|
+
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
pydantic_ai/result.py,sha256=qsanb7v4qJ4pJdkdsqpy68kuZ1WNCpQB5jcXeTwGpe0,13658
|
|
14
|
+
pydantic_ai/models/__init__.py,sha256=Cx8PjEsi5gkNOVQic32sf4CmM-A3pRu1LcjpM6poiBI,10138
|
|
15
|
+
pydantic_ai/models/function.py,sha256=Mzc-zXnb2RayWAA8N9NS7KGF49do1S-VW3U9fkc661o,10045
|
|
16
|
+
pydantic_ai/models/gemini.py,sha256=ruO4tnnpDDuHThg7jUOphs8I_KXBJH7gfDMluliED8E,26606
|
|
17
|
+
pydantic_ai/models/groq.py,sha256=Tx2yU3ysmPLBmWGsjzES-XcumzrsoBtB7spCnJBlLiM,14947
|
|
18
|
+
pydantic_ai/models/openai.py,sha256=5ihH25CrS0tnZNW-BZw4GyPe8V-IxIHWw3B9ulPVjQE,14931
|
|
19
|
+
pydantic_ai/models/test.py,sha256=q1wch_E7TSb4qx9PCcP1YyBGZx567MGlAQhlAlON0S8,14463
|
|
20
|
+
pydantic_ai/models/vertexai.py,sha256=5wI8y2YjeRgSE51uKy5OtevQkks65uEbxIUAs5EGBaI,9161
|
|
21
|
+
pydantic_ai_slim-0.0.8.dist-info/METADATA,sha256=CmpvlEAUyaWaPbdRCbFuypVLJ8yNC3TwZ0jgvlR9yps,2561
|
|
22
|
+
pydantic_ai_slim-0.0.8.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
23
|
+
pydantic_ai_slim-0.0.8.dist-info/RECORD,,
|