pydantic-ai-slim 0.4.11__tar.gz → 0.5.1__tar.gz
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_slim-0.4.11 → pydantic_ai_slim-0.5.1}/PKG-INFO +4 -4
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_agent_graph.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_function_schema.py +7 -4
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/exceptions.py +2 -2
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/messages.py +37 -10
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/anthropic.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/bedrock.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/cohere.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/gemini.py +5 -4
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/google.py +4 -2
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/groq.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/instrumented.py +2 -6
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/mistral.py +1 -1
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/openai.py +2 -2
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/_json_schema.py +4 -3
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/openai.py +22 -12
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/google.py +9 -6
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/tools.py +13 -5
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pyproject.toml +4 -3
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/.gitignore +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/LICENSE +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/README.md +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/__main__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_a2a.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_cli.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_griffe.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_mcp.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_output.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_parts_manager.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_run_context.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_system_prompt.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_thinking_part.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_tool_manager.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/_utils.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/ag_ui.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/agent.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/common_tools/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/common_tools/tavily.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/direct.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/ext/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/ext/aci.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/ext/langchain.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/format_as_xml.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/format_prompt.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/mcp.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/fallback.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/function.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/huggingface.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/mcp_sampling.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/test.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/models/wrapper.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/output.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/amazon.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/anthropic.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/cohere.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/deepseek.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/google.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/grok.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/meta.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/mistral.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/moonshotai.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/profiles/qwen.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/anthropic.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/azure.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/bedrock.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/cohere.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/deepseek.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/fireworks.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/github.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/google_gla.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/google_vertex.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/grok.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/groq.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/heroku.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/huggingface.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/mistral.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/moonshotai.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/openai.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/openrouter.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/together.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/providers/vercel.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/py.typed +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/result.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/retries.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/settings.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/__init__.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/abstract.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/combined.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/deferred.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/filtered.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/function.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/prefixed.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/prepared.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/renamed.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/toolsets/wrapper.py +0 -0
- {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.1}/pydantic_ai/usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Author-email: Samuel Colvin <samuel@pydantic.dev>, Marcelo Trylesinski <marcelotryle@gmail.com>, David Montague <david@pydantic.dev>, Alex Hall <alex@pydantic.dev>, Douwe Maan <douwe@pydantic.dev>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,7 +30,7 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
|
|
|
30
30
|
Requires-Dist: griffe>=1.3.2
|
|
31
31
|
Requires-Dist: httpx>=0.27
|
|
32
32
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
33
|
-
Requires-Dist: pydantic-graph==0.
|
|
33
|
+
Requires-Dist: pydantic-graph==0.5.1
|
|
34
34
|
Requires-Dist: pydantic>=2.10
|
|
35
35
|
Requires-Dist: typing-inspection>=0.4.0
|
|
36
36
|
Provides-Extra: a2a
|
|
@@ -51,9 +51,9 @@ Requires-Dist: cohere>=5.16.0; (platform_system != 'Emscripten') and extra == 'c
|
|
|
51
51
|
Provides-Extra: duckduckgo
|
|
52
52
|
Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
|
|
53
53
|
Provides-Extra: evals
|
|
54
|
-
Requires-Dist: pydantic-evals==0.
|
|
54
|
+
Requires-Dist: pydantic-evals==0.5.1; extra == 'evals'
|
|
55
55
|
Provides-Extra: google
|
|
56
|
-
Requires-Dist: google-genai>=1.
|
|
56
|
+
Requires-Dist: google-genai>=1.28.0; extra == 'google'
|
|
57
57
|
Provides-Extra: groq
|
|
58
58
|
Requires-Dist: groq>=0.19.0; extra == 'groq'
|
|
59
59
|
Provides-Extra: huggingface
|
|
@@ -620,7 +620,7 @@ async def process_function_tools( # noqa: C901
|
|
|
620
620
|
result_data = await tool_manager.handle_call(call)
|
|
621
621
|
except exceptions.UnexpectedModelBehavior as e:
|
|
622
622
|
ctx.state.increment_retries(ctx.deps.max_result_retries, e)
|
|
623
|
-
raise e # pragma: no cover
|
|
623
|
+
raise e # pragma: lax no cover
|
|
624
624
|
except ToolRetryError as e:
|
|
625
625
|
ctx.state.increment_retries(ctx.deps.max_result_retries, e)
|
|
626
626
|
yield _messages.FunctionToolCallEvent(call)
|
|
@@ -154,9 +154,13 @@ def function_schema( # noqa: C901
|
|
|
154
154
|
if p.kind == Parameter.VAR_POSITIONAL:
|
|
155
155
|
annotation = list[annotation]
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
required = p.default is Parameter.empty
|
|
158
|
+
# FieldInfo.from_annotated_attribute expects a type, `annotation` is Any
|
|
158
159
|
annotation = cast(type[Any], annotation)
|
|
159
|
-
|
|
160
|
+
if required:
|
|
161
|
+
field_info = FieldInfo.from_annotation(annotation)
|
|
162
|
+
else:
|
|
163
|
+
field_info = FieldInfo.from_annotated_attribute(annotation, p.default)
|
|
160
164
|
if field_info.description is None:
|
|
161
165
|
field_info.description = field_descriptions.get(field_name)
|
|
162
166
|
|
|
@@ -164,7 +168,7 @@ def function_schema( # noqa: C901
|
|
|
164
168
|
field_name,
|
|
165
169
|
field_info,
|
|
166
170
|
decorators,
|
|
167
|
-
required=
|
|
171
|
+
required=required,
|
|
168
172
|
)
|
|
169
173
|
# noinspection PyTypeChecker
|
|
170
174
|
td_schema.setdefault('metadata', {})['is_model_like'] = is_model_like(annotation)
|
|
@@ -281,7 +285,6 @@ def _build_schema(
|
|
|
281
285
|
td_schema = core_schema.typed_dict_schema(
|
|
282
286
|
fields,
|
|
283
287
|
config=core_config,
|
|
284
|
-
total=var_kwargs_schema is None,
|
|
285
288
|
extras_schema=gen_schema.generate_schema(var_kwargs_schema) if var_kwargs_schema else None,
|
|
286
289
|
)
|
|
287
290
|
return td_schema, None
|
|
@@ -5,9 +5,9 @@ import sys
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
if sys.version_info < (3, 11):
|
|
8
|
-
from exceptiongroup import ExceptionGroup
|
|
8
|
+
from exceptiongroup import ExceptionGroup as ExceptionGroup # pragma: lax no cover
|
|
9
9
|
else:
|
|
10
|
-
ExceptionGroup = ExceptionGroup
|
|
10
|
+
ExceptionGroup = ExceptionGroup # pragma: lax no cover
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from .messages import RetryPromptPart
|
|
@@ -106,7 +106,7 @@ class FileUrl(ABC):
|
|
|
106
106
|
- `GoogleModel`: `VideoUrl.vendor_metadata` is used as `video_metadata`: https://ai.google.dev/gemini-api/docs/video-understanding#customize-video-processing
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
|
-
_media_type: str | None = field(init=False, repr=False)
|
|
109
|
+
_media_type: str | None = field(init=False, repr=False, compare=False)
|
|
110
110
|
|
|
111
111
|
def __init__(
|
|
112
112
|
self,
|
|
@@ -120,19 +120,21 @@ class FileUrl(ABC):
|
|
|
120
120
|
self.force_download = force_download
|
|
121
121
|
self._media_type = media_type
|
|
122
122
|
|
|
123
|
-
@abstractmethod
|
|
124
|
-
def _infer_media_type(self) -> str:
|
|
125
|
-
"""Return the media type of the file, based on the url."""
|
|
126
|
-
|
|
127
123
|
@property
|
|
128
124
|
def media_type(self) -> str:
|
|
129
|
-
"""Return the media type of the file, based on the
|
|
125
|
+
"""Return the media type of the file, based on the URL or the provided `media_type`."""
|
|
130
126
|
return self._media_type or self._infer_media_type()
|
|
131
127
|
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def _infer_media_type(self) -> str:
|
|
130
|
+
"""Infer the media type of the file based on the URL."""
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
132
133
|
@property
|
|
133
134
|
@abstractmethod
|
|
134
135
|
def format(self) -> str:
|
|
135
136
|
"""The file format."""
|
|
137
|
+
raise NotImplementedError
|
|
136
138
|
|
|
137
139
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
138
140
|
|
|
@@ -182,7 +184,9 @@ class VideoUrl(FileUrl):
|
|
|
182
184
|
elif self.is_youtube:
|
|
183
185
|
return 'video/mp4'
|
|
184
186
|
else:
|
|
185
|
-
raise ValueError(
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f'Could not infer media type from video URL: {self.url}. Explicitly provide a `media_type` instead.'
|
|
189
|
+
)
|
|
186
190
|
|
|
187
191
|
@property
|
|
188
192
|
def is_youtube(self) -> bool:
|
|
@@ -238,7 +242,9 @@ class AudioUrl(FileUrl):
|
|
|
238
242
|
if self.url.endswith('.aac'):
|
|
239
243
|
return 'audio/aac'
|
|
240
244
|
|
|
241
|
-
raise ValueError(
|
|
245
|
+
raise ValueError(
|
|
246
|
+
f'Could not infer media type from audio URL: {self.url}. Explicitly provide a `media_type` instead.'
|
|
247
|
+
)
|
|
242
248
|
|
|
243
249
|
@property
|
|
244
250
|
def format(self) -> AudioFormat:
|
|
@@ -278,7 +284,9 @@ class ImageUrl(FileUrl):
|
|
|
278
284
|
elif self.url.endswith('.webp'):
|
|
279
285
|
return 'image/webp'
|
|
280
286
|
else:
|
|
281
|
-
raise ValueError(
|
|
287
|
+
raise ValueError(
|
|
288
|
+
f'Could not infer media type from image URL: {self.url}. Explicitly provide a `media_type` instead.'
|
|
289
|
+
)
|
|
282
290
|
|
|
283
291
|
@property
|
|
284
292
|
def format(self) -> ImageFormat:
|
|
@@ -312,9 +320,28 @@ class DocumentUrl(FileUrl):
|
|
|
312
320
|
|
|
313
321
|
def _infer_media_type(self) -> str:
|
|
314
322
|
"""Return the media type of the document, based on the url."""
|
|
323
|
+
# Common document types are hardcoded here as mime-type support for these
|
|
324
|
+
# extensions varies across operating systems.
|
|
325
|
+
if self.url.endswith(('.md', '.mdx', '.markdown')):
|
|
326
|
+
return 'text/markdown'
|
|
327
|
+
elif self.url.endswith('.asciidoc'):
|
|
328
|
+
return 'text/x-asciidoc'
|
|
329
|
+
elif self.url.endswith('.txt'):
|
|
330
|
+
return 'text/plain'
|
|
331
|
+
elif self.url.endswith('.pdf'):
|
|
332
|
+
return 'application/pdf'
|
|
333
|
+
elif self.url.endswith('.rtf'):
|
|
334
|
+
return 'application/rtf'
|
|
335
|
+
elif self.url.endswith('.docx'):
|
|
336
|
+
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
337
|
+
elif self.url.endswith('.xlsx'):
|
|
338
|
+
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
339
|
+
|
|
315
340
|
type_, _ = guess_type(self.url)
|
|
316
341
|
if type_ is None:
|
|
317
|
-
raise ValueError(
|
|
342
|
+
raise ValueError(
|
|
343
|
+
f'Could not infer media type from document URL: {self.url}. Explicitly provide a `media_type` instead.'
|
|
344
|
+
)
|
|
318
345
|
return type_
|
|
319
346
|
|
|
320
347
|
@property
|
|
@@ -256,7 +256,7 @@ class AnthropicModel(Model):
|
|
|
256
256
|
except APIStatusError as e:
|
|
257
257
|
if (status_code := e.status_code) >= 400:
|
|
258
258
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
259
|
-
raise # pragma: no cover
|
|
259
|
+
raise # pragma: lax no cover
|
|
260
260
|
|
|
261
261
|
def _process_response(self, response: BetaMessage) -> ModelResponse:
|
|
262
262
|
"""Process a non-streamed response, and prepare a message to return."""
|
|
@@ -183,7 +183,7 @@ class CohereModel(Model):
|
|
|
183
183
|
except ApiError as e:
|
|
184
184
|
if (status_code := e.status_code) and status_code >= 400:
|
|
185
185
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
186
|
-
raise # pragma: no cover
|
|
186
|
+
raise # pragma: lax no cover
|
|
187
187
|
|
|
188
188
|
def _process_response(self, response: V2ChatResponse) -> ModelResponse:
|
|
189
189
|
"""Process a non-streamed response, and prepare a message to return."""
|
|
@@ -11,7 +11,7 @@ from uuid import uuid4
|
|
|
11
11
|
import httpx
|
|
12
12
|
import pydantic
|
|
13
13
|
from httpx import USE_CLIENT_DEFAULT, Response as HTTPResponse
|
|
14
|
-
from typing_extensions import NotRequired, TypedDict, assert_never
|
|
14
|
+
from typing_extensions import NotRequired, TypedDict, assert_never, deprecated
|
|
15
15
|
|
|
16
16
|
from pydantic_ai.providers import Provider, infer_provider
|
|
17
17
|
|
|
@@ -92,6 +92,7 @@ class GeminiModelSettings(ModelSettings, total=False):
|
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
@deprecated('Use `GoogleModel` instead. See <https://ai.pydantic.dev/models/google/> for more details.')
|
|
95
96
|
@dataclass(init=False)
|
|
96
97
|
class GeminiModel(Model):
|
|
97
98
|
"""A model that uses Gemini via `generativelanguage.googleapis.com` API.
|
|
@@ -236,7 +237,7 @@ class GeminiModel(Model):
|
|
|
236
237
|
|
|
237
238
|
if gemini_labels := model_settings.get('gemini_labels'):
|
|
238
239
|
if self._system == 'google-vertex':
|
|
239
|
-
request_data['labels'] = gemini_labels
|
|
240
|
+
request_data['labels'] = gemini_labels # pragma: lax no cover
|
|
240
241
|
|
|
241
242
|
headers = {'Content-Type': 'application/json', 'User-Agent': get_user_agent()}
|
|
242
243
|
url = f'/{self._model_name}:{"streamGenerateContent" if streamed else "generateContent"}'
|
|
@@ -366,11 +367,11 @@ class GeminiModel(Model):
|
|
|
366
367
|
inline_data={'data': downloaded_item['data'], 'mime_type': downloaded_item['data_type']}
|
|
367
368
|
)
|
|
368
369
|
content.append(inline_data)
|
|
369
|
-
else:
|
|
370
|
+
else: # pragma: lax no cover
|
|
370
371
|
file_data = _GeminiFileDataPart(file_data={'file_uri': item.url, 'mime_type': item.media_type})
|
|
371
372
|
content.append(file_data)
|
|
372
373
|
else:
|
|
373
|
-
assert_never(item)
|
|
374
|
+
assert_never(item) # pragma: lax no cover
|
|
374
375
|
return content
|
|
375
376
|
|
|
376
377
|
def _map_response_schema(self, o: OutputObjectDefinition) -> dict[str, Any]:
|
|
@@ -407,7 +407,7 @@ class GoogleModel(Model):
|
|
|
407
407
|
content.append(inline_data_dict) # type: ignore
|
|
408
408
|
elif isinstance(item, VideoUrl) and item.is_youtube:
|
|
409
409
|
file_data_dict = {'file_data': {'file_uri': item.url, 'mime_type': item.media_type}}
|
|
410
|
-
if item.vendor_metadata:
|
|
410
|
+
if item.vendor_metadata: # pragma: no branch
|
|
411
411
|
file_data_dict['video_metadata'] = item.vendor_metadata
|
|
412
412
|
content.append(file_data_dict) # type: ignore
|
|
413
413
|
elif isinstance(item, FileUrl):
|
|
@@ -421,7 +421,9 @@ class GoogleModel(Model):
|
|
|
421
421
|
inline_data = {'data': downloaded_item['data'], 'mime_type': downloaded_item['data_type']}
|
|
422
422
|
content.append({'inline_data': inline_data}) # type: ignore
|
|
423
423
|
else:
|
|
424
|
-
content.append(
|
|
424
|
+
content.append(
|
|
425
|
+
{'file_data': {'file_uri': item.url, 'mime_type': item.media_type}}
|
|
426
|
+
) # pragma: lax no cover
|
|
425
427
|
else:
|
|
426
428
|
assert_never(item)
|
|
427
429
|
return content
|
|
@@ -249,7 +249,7 @@ class GroqModel(Model):
|
|
|
249
249
|
except APIStatusError as e:
|
|
250
250
|
if (status_code := e.status_code) >= 400:
|
|
251
251
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
252
|
-
raise # pragma: no cover
|
|
252
|
+
raise # pragma: lax no cover
|
|
253
253
|
|
|
254
254
|
def _process_response(self, response: chat.ChatCompletion) -> ModelResponse:
|
|
255
255
|
"""Process a non-streamed response, and prepare a message to return."""
|
|
@@ -18,11 +18,7 @@ from opentelemetry.trace import Span, Tracer, TracerProvider, get_tracer_provide
|
|
|
18
18
|
from opentelemetry.util.types import AttributeValue
|
|
19
19
|
from pydantic import TypeAdapter
|
|
20
20
|
|
|
21
|
-
from ..messages import
|
|
22
|
-
ModelMessage,
|
|
23
|
-
ModelRequest,
|
|
24
|
-
ModelResponse,
|
|
25
|
-
)
|
|
21
|
+
from ..messages import ModelMessage, ModelRequest, ModelResponse
|
|
26
22
|
from ..settings import ModelSettings
|
|
27
23
|
from . import KnownModelName, Model, ModelRequestParameters, StreamedResponse
|
|
28
24
|
from .wrapper import WrapperModel
|
|
@@ -138,7 +134,7 @@ class InstrumentationSettings:
|
|
|
138
134
|
**tokens_histogram_kwargs,
|
|
139
135
|
explicit_bucket_boundaries_advisory=TOKEN_HISTOGRAM_BOUNDARIES,
|
|
140
136
|
)
|
|
141
|
-
except TypeError:
|
|
137
|
+
except TypeError: # pragma: lax no cover
|
|
142
138
|
# Older OTel/logfire versions don't support explicit_bucket_boundaries_advisory
|
|
143
139
|
self.tokens_histogram = self.meter.create_histogram(
|
|
144
140
|
**tokens_histogram_kwargs, # pyright: ignore
|
|
@@ -218,7 +218,7 @@ class MistralModel(Model):
|
|
|
218
218
|
except SDKError as e:
|
|
219
219
|
if (status_code := e.status_code) >= 400:
|
|
220
220
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
221
|
-
raise # pragma: no cover
|
|
221
|
+
raise # pragma: lax no cover
|
|
222
222
|
|
|
223
223
|
assert response, 'A unexpected empty response from Mistral.'
|
|
224
224
|
return response
|
|
@@ -359,7 +359,7 @@ class OpenAIModel(Model):
|
|
|
359
359
|
except APIStatusError as e:
|
|
360
360
|
if (status_code := e.status_code) >= 400:
|
|
361
361
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
362
|
-
raise # pragma: no cover
|
|
362
|
+
raise # pragma: lax no cover
|
|
363
363
|
|
|
364
364
|
def _process_response(self, response: chat.ChatCompletion | str) -> ModelResponse:
|
|
365
365
|
"""Process a non-streamed response, and prepare a message to return."""
|
|
@@ -814,7 +814,7 @@ class OpenAIResponsesModel(Model):
|
|
|
814
814
|
except APIStatusError as e:
|
|
815
815
|
if (status_code := e.status_code) >= 400:
|
|
816
816
|
raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
|
|
817
|
-
raise # pragma: no cover
|
|
817
|
+
raise # pragma: lax no cover
|
|
818
818
|
|
|
819
819
|
def _get_reasoning(self, model_settings: OpenAIResponsesModelSettings) -> Reasoning | NotGiven:
|
|
820
820
|
reasoning_effort = model_settings.get('openai_reasoning_effort', None)
|
|
@@ -137,8 +137,9 @@ class JsonSchemaTransformer(ABC):
|
|
|
137
137
|
return schema
|
|
138
138
|
|
|
139
139
|
def _handle_union(self, schema: JsonSchema, union_kind: Literal['anyOf', 'oneOf']) -> JsonSchema:
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
try:
|
|
141
|
+
members = schema.pop(union_kind)
|
|
142
|
+
except KeyError:
|
|
142
143
|
return schema
|
|
143
144
|
|
|
144
145
|
handled = [self._handle(member) for member in members]
|
|
@@ -149,7 +150,7 @@ class JsonSchemaTransformer(ABC):
|
|
|
149
150
|
|
|
150
151
|
if len(handled) == 1:
|
|
151
152
|
# In this case, no need to retain the union
|
|
152
|
-
return handled[0]
|
|
153
|
+
return handled[0] | schema
|
|
153
154
|
|
|
154
155
|
# If we have keys besides the union kind (such as title or discriminator), keep them without modifications
|
|
155
156
|
schema = schema.copy()
|
|
@@ -47,11 +47,6 @@ def openai_model_profile(model_name: str) -> ModelProfile:
|
|
|
47
47
|
_STRICT_INCOMPATIBLE_KEYS = [
|
|
48
48
|
'minLength',
|
|
49
49
|
'maxLength',
|
|
50
|
-
'pattern',
|
|
51
|
-
'format',
|
|
52
|
-
'minimum',
|
|
53
|
-
'maximum',
|
|
54
|
-
'multipleOf',
|
|
55
50
|
'patternProperties',
|
|
56
51
|
'unevaluatedProperties',
|
|
57
52
|
'propertyNames',
|
|
@@ -61,11 +56,21 @@ _STRICT_INCOMPATIBLE_KEYS = [
|
|
|
61
56
|
'contains',
|
|
62
57
|
'minContains',
|
|
63
58
|
'maxContains',
|
|
64
|
-
'minItems',
|
|
65
|
-
'maxItems',
|
|
66
59
|
'uniqueItems',
|
|
67
60
|
]
|
|
68
61
|
|
|
62
|
+
_STRICT_COMPATIBLE_STRING_FORMATS = [
|
|
63
|
+
'date-time',
|
|
64
|
+
'time',
|
|
65
|
+
'date',
|
|
66
|
+
'duration',
|
|
67
|
+
'email',
|
|
68
|
+
'hostname',
|
|
69
|
+
'ipv4',
|
|
70
|
+
'ipv6',
|
|
71
|
+
'uuid',
|
|
72
|
+
]
|
|
73
|
+
|
|
69
74
|
_sentinel = object()
|
|
70
75
|
|
|
71
76
|
|
|
@@ -127,6 +132,9 @@ class OpenAIJsonSchemaTransformer(JsonSchemaTransformer):
|
|
|
127
132
|
value = schema.get(key, _sentinel)
|
|
128
133
|
if value is not _sentinel:
|
|
129
134
|
incompatible_values[key] = value
|
|
135
|
+
if format := schema.get('format'):
|
|
136
|
+
if format not in _STRICT_COMPATIBLE_STRING_FORMATS:
|
|
137
|
+
incompatible_values['format'] = format
|
|
130
138
|
description = schema.get('description')
|
|
131
139
|
if incompatible_values:
|
|
132
140
|
if self.strict is True:
|
|
@@ -158,11 +166,13 @@ class OpenAIJsonSchemaTransformer(JsonSchemaTransformer):
|
|
|
158
166
|
schema['required'] = list(schema['properties'].keys())
|
|
159
167
|
|
|
160
168
|
elif self.strict is None:
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
if schema.get('additionalProperties', None) not in (None, False):
|
|
170
|
+
self.is_strict_compatible = False
|
|
171
|
+
else:
|
|
172
|
+
# additional properties are disallowed by default
|
|
173
|
+
schema['additionalProperties'] = False
|
|
174
|
+
|
|
175
|
+
if 'properties' not in schema or 'required' not in schema:
|
|
166
176
|
self.is_strict_compatible = False
|
|
167
177
|
else:
|
|
168
178
|
required = schema['required']
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations as _annotations
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Literal, overload
|
|
5
5
|
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
6
8
|
from pydantic_ai.exceptions import UserError
|
|
7
9
|
from pydantic_ai.models import get_user_agent
|
|
8
10
|
from pydantic_ai.profiles import ModelProfile
|
|
@@ -12,6 +14,7 @@ from pydantic_ai.providers import Provider
|
|
|
12
14
|
try:
|
|
13
15
|
from google import genai
|
|
14
16
|
from google.auth.credentials import Credentials
|
|
17
|
+
from google.genai.types import HttpOptionsDict
|
|
15
18
|
except ImportError as _import_error:
|
|
16
19
|
raise ImportError(
|
|
17
20
|
'Please install the `google-genai` package to use the Google provider, '
|
|
@@ -89,17 +92,17 @@ class GoogleProvider(Provider[genai.Client]):
|
|
|
89
92
|
if vertexai is None:
|
|
90
93
|
vertexai = bool(location or project or credentials)
|
|
91
94
|
|
|
95
|
+
http_options: HttpOptionsDict = {
|
|
96
|
+
'headers': {'User-Agent': get_user_agent()},
|
|
97
|
+
'async_client_args': {'transport': httpx.AsyncHTTPTransport()},
|
|
98
|
+
}
|
|
92
99
|
if not vertexai:
|
|
93
100
|
if api_key is None:
|
|
94
101
|
raise UserError( # pragma: no cover
|
|
95
102
|
'Set the `GOOGLE_API_KEY` environment variable or pass it via `GoogleProvider(api_key=...)`'
|
|
96
103
|
'to use the Google Generative Language API.'
|
|
97
104
|
)
|
|
98
|
-
self._client = genai.Client(
|
|
99
|
-
vertexai=vertexai,
|
|
100
|
-
api_key=api_key,
|
|
101
|
-
http_options={'headers': {'User-Agent': get_user_agent()}},
|
|
102
|
-
)
|
|
105
|
+
self._client = genai.Client(vertexai=vertexai, api_key=api_key, http_options=http_options)
|
|
103
106
|
else:
|
|
104
107
|
self._client = genai.Client(
|
|
105
108
|
vertexai=vertexai,
|
|
@@ -111,7 +114,7 @@ class GoogleProvider(Provider[genai.Client]):
|
|
|
111
114
|
# For more details, check: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#available-regions
|
|
112
115
|
location=location or os.environ.get('GOOGLE_CLOUD_LOCATION') or 'us-central1',
|
|
113
116
|
credentials=credentials,
|
|
114
|
-
http_options=
|
|
117
|
+
http_options=http_options,
|
|
115
118
|
)
|
|
116
119
|
else:
|
|
117
120
|
self._client = client
|
|
@@ -133,11 +133,19 @@ A = TypeVar('A')
|
|
|
133
133
|
|
|
134
134
|
class GenerateToolJsonSchema(GenerateJsonSchema):
|
|
135
135
|
def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if 'additionalProperties' not in
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
json_schema = super().typed_dict_schema(schema)
|
|
137
|
+
# Workaround for https://github.com/pydantic/pydantic/issues/12123
|
|
138
|
+
if 'additionalProperties' not in json_schema: # pragma: no branch
|
|
139
|
+
extra = schema.get('extra_behavior') or schema.get('config', {}).get('extra_fields_behavior')
|
|
140
|
+
if extra == 'allow':
|
|
141
|
+
extras_schema = schema.get('extras_schema', None)
|
|
142
|
+
if extras_schema is not None:
|
|
143
|
+
json_schema['additionalProperties'] = self.generate_inner(extras_schema) or True
|
|
144
|
+
else:
|
|
145
|
+
json_schema['additionalProperties'] = True # pragma: no cover
|
|
146
|
+
elif extra == 'forbid':
|
|
147
|
+
json_schema['additionalProperties'] = False
|
|
148
|
+
return json_schema
|
|
141
149
|
|
|
142
150
|
def _named_required_fields_schema(self, named_required_fields: Sequence[tuple[str, bool, Any]]) -> JsonSchemaValue:
|
|
143
151
|
# Remove largely-useless property titles
|
|
@@ -65,7 +65,7 @@ logfire = ["logfire>=3.11.0"]
|
|
|
65
65
|
openai = ["openai>=1.92.0"]
|
|
66
66
|
cohere = ["cohere>=5.16.0; platform_system != 'Emscripten'"]
|
|
67
67
|
vertexai = ["google-auth>=2.36.0", "requests>=2.32.2"]
|
|
68
|
-
google = ["google-genai>=1.
|
|
68
|
+
google = ["google-genai>=1.28.0"]
|
|
69
69
|
anthropic = ["anthropic>=0.52.0"]
|
|
70
70
|
groq = ["groq>=0.19.0"]
|
|
71
71
|
mistral = ["mistralai>=1.9.2"]
|
|
@@ -92,7 +92,7 @@ dev = [
|
|
|
92
92
|
"anyio>=4.5.0",
|
|
93
93
|
"asgi-lifespan>=2.1.0",
|
|
94
94
|
"devtools>=0.12.2",
|
|
95
|
-
"coverage[toml]>=7.
|
|
95
|
+
"coverage[toml]>=7.10.2",
|
|
96
96
|
"dirty-equals>=0.9.0",
|
|
97
97
|
"duckduckgo-search>=7.0.0",
|
|
98
98
|
"inline-snapshot>=0.19.3",
|
|
@@ -103,8 +103,9 @@ dev = [
|
|
|
103
103
|
"pytest-recording>=0.13.2",
|
|
104
104
|
"diff-cover>=9.2.0",
|
|
105
105
|
"boto3-stubs[bedrock-runtime]",
|
|
106
|
-
"strict-no-cover
|
|
106
|
+
"strict-no-cover @ git+https://github.com/pydantic/strict-no-cover.git@7fc59da2c4dff919db2095a0f0e47101b657131d",
|
|
107
107
|
"pytest-xdist>=3.6.1",
|
|
108
|
+
"coverage-enable-subprocess>=0.1.0",
|
|
108
109
|
]
|
|
109
110
|
|
|
110
111
|
[tool.hatch.metadata]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|