pydantic-ai 0.0.32__tar.gz → 0.0.34__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 might be problematic. Click here for more details.
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/PKG-INFO +3 -3
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/pyproject.toml +5 -5
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/conftest.py +1 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/json_body_serializer.py +19 -13
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +3 -3
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +9 -9
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_gemini.py +29 -29
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_instrumented.py +15 -16
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_model.py +2 -2
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_model_names.py +11 -2
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_openai.py +24 -21
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_vertexai.py +3 -0
- pydantic_ai-0.0.34/tests/providers/__init__.py +0 -0
- pydantic_ai-0.0.34/tests/providers/test_deepseek.py +48 -0
- pydantic_ai-0.0.34/tests/providers/test_google_gla.py +19 -0
- pydantic_ai-0.0.34/tests/providers/test_google_vertex.py +110 -0
- pydantic_ai-0.0.34/tests/providers/test_provider_names.py +44 -0
- pydantic_ai-0.0.34/tests/test_cli.py +52 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_examples.py +5 -3
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_live.py +14 -5
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_logfire.py +82 -32
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/.gitignore +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/LICENSE +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/Makefile +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/README.md +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/__init__.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/assets/kiwi.png +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/assets/marcelo.mp3 +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/example_modules/README.md +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/example_modules/bank_database.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/example_modules/fake_database.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/example_modules/weather_service.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/__init__.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/test_graph.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/test_history.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/test_mermaid.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/test_state.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/graph/test_utils.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/import_examples.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/__init__.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/mock_async_stream.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_anthropic.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_cohere.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_fallback.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_groq.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_mistral.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_model_function.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/models/test_model_test.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_agent.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_deps.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_format_as_xml.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_json_body_serializer.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_parts_manager.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_streaming.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_tools.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_usage_limits.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/test_utils.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/typed_agent.py +0 -0
- {pydantic_ai-0.0.32 → pydantic_ai-0.0.34}/tests/typed_graph.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.34
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs
|
|
5
5
|
Project-URL: Homepage, https://ai.pydantic.dev
|
|
6
6
|
Project-URL: Source, https://github.com/pydantic/pydantic-ai
|
|
@@ -28,9 +28,9 @@ Classifier: Topic :: Internet
|
|
|
28
28
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
29
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
30
|
Requires-Python: >=3.9
|
|
31
|
-
Requires-Dist: pydantic-ai-slim[anthropic,cohere,groq,mistral,openai,vertexai]==0.0.
|
|
31
|
+
Requires-Dist: pydantic-ai-slim[anthropic,cli,cohere,groq,mistral,openai,vertexai]==0.0.34
|
|
32
32
|
Provides-Extra: examples
|
|
33
|
-
Requires-Dist: pydantic-ai-examples==0.0.
|
|
33
|
+
Requires-Dist: pydantic-ai-examples==0.0.34; extra == 'examples'
|
|
34
34
|
Provides-Extra: logfire
|
|
35
35
|
Requires-Dist: logfire>=2.3; extra == 'logfire'
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pydantic-ai"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.34"
|
|
8
8
|
description = "Agent Framework / shim to use Pydantic with LLMs"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Samuel Colvin", email = "samuel@pydantic.dev" },
|
|
@@ -35,9 +35,8 @@ classifiers = [
|
|
|
35
35
|
"Framework :: Pydantic :: 2",
|
|
36
36
|
]
|
|
37
37
|
requires-python = ">=3.9"
|
|
38
|
-
|
|
39
38
|
dependencies = [
|
|
40
|
-
"pydantic-ai-slim[openai,vertexai,groq,anthropic,mistral,cohere]==0.0.
|
|
39
|
+
"pydantic-ai-slim[openai,vertexai,groq,anthropic,mistral,cohere,cli]==0.0.34",
|
|
41
40
|
]
|
|
42
41
|
|
|
43
42
|
[project.urls]
|
|
@@ -47,7 +46,7 @@ Documentation = "https://ai.pydantic.dev"
|
|
|
47
46
|
Changelog = "https://github.com/pydantic/pydantic-ai/releases"
|
|
48
47
|
|
|
49
48
|
[project.optional-dependencies]
|
|
50
|
-
examples = ["pydantic-ai-examples==0.0.
|
|
49
|
+
examples = ["pydantic-ai-examples==0.0.34"]
|
|
51
50
|
logfire = ["logfire>=2.3"]
|
|
52
51
|
|
|
53
52
|
[tool.uv.sources]
|
|
@@ -165,13 +164,14 @@ exclude_lines = [
|
|
|
165
164
|
'typing.assert_never',
|
|
166
165
|
'$\s*assert_never\(',
|
|
167
166
|
'if __name__ == .__main__.:',
|
|
167
|
+
'except ImportError as _import_error:',
|
|
168
168
|
]
|
|
169
169
|
|
|
170
170
|
[tool.logfire]
|
|
171
171
|
ignore_no_config = true
|
|
172
172
|
|
|
173
173
|
[tool.inline-snapshot]
|
|
174
|
-
format-command="ruff format --stdin-filename {filename}"
|
|
174
|
+
format-command = "ruff format --stdin-filename {filename}"
|
|
175
175
|
|
|
176
176
|
[tool.inline-snapshot.shortcuts]
|
|
177
177
|
snap-fix = ["create", "fix"]
|
|
@@ -200,6 +200,7 @@ def pytest_recording_configure(config: Any, vcr: VCR):
|
|
|
200
200
|
@pytest.fixture(scope='module')
|
|
201
201
|
def vcr_config():
|
|
202
202
|
return {
|
|
203
|
+
'ignore_localhost': True,
|
|
203
204
|
# Note: additional header filtering is done inside the serializer
|
|
204
205
|
'filter_headers': ['authorization', 'x-api-key'],
|
|
205
206
|
'decode_compressed_response': True,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false
|
|
2
2
|
import json
|
|
3
|
+
import urllib.parse
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
import yaml
|
|
@@ -59,19 +60,24 @@ def serialize(cassette_dict: Any):
|
|
|
59
60
|
# update headers on source object
|
|
60
61
|
data['headers'] = headers
|
|
61
62
|
|
|
62
|
-
content_type = headers.get('content-type',
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
content_type = headers.get('content-type', [])
|
|
64
|
+
if any(header.startswith('application/json') for header in content_type):
|
|
65
|
+
# Parse the body as JSON
|
|
66
|
+
body: Any = data.get('body', None)
|
|
67
|
+
assert body is not None, data
|
|
68
|
+
if isinstance(body, dict):
|
|
69
|
+
# Responses will have the body under a field called 'string'
|
|
70
|
+
body = body.get('string')
|
|
71
|
+
if body is not None:
|
|
72
|
+
data['parsed_body'] = json.loads(body)
|
|
73
|
+
if 'access_token' in data['parsed_body']:
|
|
74
|
+
data['parsed_body']['access_token'] = 'scrubbed'
|
|
75
|
+
del data['body']
|
|
76
|
+
if content_type == ['application/x-www-form-urlencoded']:
|
|
77
|
+
query_params = urllib.parse.parse_qs(data['body'])
|
|
78
|
+
if 'client_secret' in query_params:
|
|
79
|
+
query_params['client_secret'] = ['scrubbed']
|
|
80
|
+
data['body'] = urllib.parse.urlencode(query_params)
|
|
75
81
|
|
|
76
82
|
# Use our custom dumper
|
|
77
83
|
return yaml.dump(cassette_dict, Dumper=LiteralDumper, allow_unicode=True, width=120)
|
|
@@ -38,7 +38,7 @@ interactions:
|
|
|
38
38
|
"role": "model"
|
|
39
39
|
},
|
|
40
40
|
"finishReason": "STOP",
|
|
41
|
-
"avgLogprobs": -0.
|
|
41
|
+
"avgLogprobs": -0.031536102294921875
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"usageMetadata": {
|
|
@@ -68,11 +68,11 @@ interactions:
|
|
|
68
68
|
alt-svc:
|
|
69
69
|
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
|
70
70
|
content-length:
|
|
71
|
-
- '
|
|
71
|
+
- '711'
|
|
72
72
|
content-type:
|
|
73
73
|
- application/json; charset=UTF-8
|
|
74
74
|
server-timing:
|
|
75
|
-
- gfet4t7; dur=
|
|
75
|
+
- gfet4t7; dur=2657
|
|
76
76
|
transfer-encoding:
|
|
77
77
|
- chunked
|
|
78
78
|
vary:
|
|
@@ -6793,7 +6793,7 @@ interactions:
|
|
|
6793
6793
|
access-control-allow-origin:
|
|
6794
6794
|
- '*'
|
|
6795
6795
|
age:
|
|
6796
|
-
- '
|
|
6796
|
+
- '88'
|
|
6797
6797
|
alt-svc:
|
|
6798
6798
|
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
|
6799
6799
|
cache-control:
|
|
@@ -6809,7 +6809,7 @@ interactions:
|
|
|
6809
6809
|
cross-origin-resource-policy:
|
|
6810
6810
|
- cross-origin
|
|
6811
6811
|
expires:
|
|
6812
|
-
- Wed,
|
|
6812
|
+
- Wed, 04 Mar 2026 10:32:03 GMT
|
|
6813
6813
|
last-modified:
|
|
6814
6814
|
- Mon, 30 Aug 2123 17:01:05 GMT
|
|
6815
6815
|
report-to:
|
|
@@ -6850,19 +6850,19 @@ interactions:
|
|
|
6850
6850
|
"content": {
|
|
6851
6851
|
"parts": [
|
|
6852
6852
|
{
|
|
6853
|
-
"text": "This is not a fruit
|
|
6853
|
+
"text": "This is not a fruit; it's a pipe organ console."
|
|
6854
6854
|
}
|
|
6855
6855
|
],
|
|
6856
6856
|
"role": "model"
|
|
6857
6857
|
},
|
|
6858
6858
|
"finishReason": "STOP",
|
|
6859
|
-
"avgLogprobs": -0.
|
|
6859
|
+
"avgLogprobs": -0.31288215092250277
|
|
6860
6860
|
}
|
|
6861
6861
|
],
|
|
6862
6862
|
"usageMetadata": {
|
|
6863
6863
|
"promptTokenCount": 1814,
|
|
6864
|
-
"candidatesTokenCount":
|
|
6865
|
-
"totalTokenCount":
|
|
6864
|
+
"candidatesTokenCount": 14,
|
|
6865
|
+
"totalTokenCount": 1828,
|
|
6866
6866
|
"promptTokensDetails": [
|
|
6867
6867
|
{
|
|
6868
6868
|
"modality": "TEXT",
|
|
@@ -6876,7 +6876,7 @@ interactions:
|
|
|
6876
6876
|
"candidatesTokensDetails": [
|
|
6877
6877
|
{
|
|
6878
6878
|
"modality": "TEXT",
|
|
6879
|
-
"tokenCount":
|
|
6879
|
+
"tokenCount": 14
|
|
6880
6880
|
}
|
|
6881
6881
|
]
|
|
6882
6882
|
},
|
|
@@ -6886,11 +6886,11 @@ interactions:
|
|
|
6886
6886
|
alt-svc:
|
|
6887
6887
|
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
|
6888
6888
|
content-length:
|
|
6889
|
-
- '
|
|
6889
|
+
- '730'
|
|
6890
6890
|
content-type:
|
|
6891
6891
|
- application/json; charset=UTF-8
|
|
6892
6892
|
server-timing:
|
|
6893
|
-
- gfet4t7; dur=
|
|
6893
|
+
- gfet4t7; dur=1749
|
|
6894
6894
|
transfer-encoding:
|
|
6895
6895
|
- chunked
|
|
6896
6896
|
vary:
|
|
@@ -45,6 +45,7 @@ from pydantic_ai.models.gemini import (
|
|
|
45
45
|
_GeminiTools,
|
|
46
46
|
_GeminiUsageMetaData,
|
|
47
47
|
)
|
|
48
|
+
from pydantic_ai.providers.google_gla import GoogleGLAProvider
|
|
48
49
|
from pydantic_ai.result import Usage
|
|
49
50
|
from pydantic_ai.tools import ToolDefinition
|
|
50
51
|
|
|
@@ -55,9 +56,8 @@ pytestmark = pytest.mark.anyio
|
|
|
55
56
|
|
|
56
57
|
def test_api_key_arg(env: TestEnv):
|
|
57
58
|
env.set('GEMINI_API_KEY', 'via-env-var')
|
|
58
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
59
|
-
assert
|
|
60
|
-
assert m.auth.api_key == 'via-arg'
|
|
59
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
60
|
+
assert m.client.headers['x-goog-api-key'] == 'via-arg'
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def test_api_key_env_var(env: TestEnv):
|
|
@@ -80,11 +80,10 @@ def test_api_key_empty(env: TestEnv):
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
async def test_model_simple(allow_model_requests: None):
|
|
83
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
84
|
-
assert isinstance(m.
|
|
83
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
84
|
+
assert isinstance(m.client, httpx.AsyncClient)
|
|
85
85
|
assert m.model_name == 'gemini-1.5-flash'
|
|
86
|
-
assert
|
|
87
|
-
assert m.auth.api_key == 'via-arg'
|
|
86
|
+
assert 'x-goog-api-key' in m.client.headers
|
|
88
87
|
|
|
89
88
|
arc = ModelRequestParameters(function_tools=[], allow_text_result=True, result_tools=[])
|
|
90
89
|
tools = m._get_tools(arc)
|
|
@@ -94,7 +93,7 @@ async def test_model_simple(allow_model_requests: None):
|
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
async def test_model_tools(allow_model_requests: None):
|
|
97
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
96
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
98
97
|
tools = [
|
|
99
98
|
ToolDefinition(
|
|
100
99
|
'foo',
|
|
@@ -153,7 +152,7 @@ async def test_model_tools(allow_model_requests: None):
|
|
|
153
152
|
|
|
154
153
|
|
|
155
154
|
async def test_require_response_tool(allow_model_requests: None):
|
|
156
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
155
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
157
156
|
result_tool = ToolDefinition(
|
|
158
157
|
'result',
|
|
159
158
|
'This is the tool for the final Result',
|
|
@@ -212,7 +211,7 @@ async def test_json_def_replaced(allow_model_requests: None):
|
|
|
212
211
|
}
|
|
213
212
|
)
|
|
214
213
|
|
|
215
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
214
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
216
215
|
result_tool = ToolDefinition(
|
|
217
216
|
'result',
|
|
218
217
|
'This is the tool for the final Result',
|
|
@@ -259,7 +258,7 @@ async def test_json_def_replaced_any_of(allow_model_requests: None):
|
|
|
259
258
|
|
|
260
259
|
json_schema = Locations.model_json_schema()
|
|
261
260
|
|
|
262
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
261
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
263
262
|
result_tool = ToolDefinition(
|
|
264
263
|
'result',
|
|
265
264
|
'This is the tool for the final Result',
|
|
@@ -322,7 +321,7 @@ async def test_json_def_recursive(allow_model_requests: None):
|
|
|
322
321
|
}
|
|
323
322
|
)
|
|
324
323
|
|
|
325
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
324
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
326
325
|
result_tool = ToolDefinition(
|
|
327
326
|
'result',
|
|
328
327
|
'This is the tool for the final Result',
|
|
@@ -354,7 +353,7 @@ async def test_json_def_date(allow_model_requests: None):
|
|
|
354
353
|
}
|
|
355
354
|
)
|
|
356
355
|
|
|
357
|
-
m = GeminiModel('gemini-1.5-flash', api_key='via-arg')
|
|
356
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(api_key='via-arg'))
|
|
358
357
|
result_tool = ToolDefinition(
|
|
359
358
|
'result',
|
|
360
359
|
'This is the tool for the final Result',
|
|
@@ -451,7 +450,7 @@ def example_usage() -> _GeminiUsageMetaData:
|
|
|
451
450
|
async def test_text_success(get_gemini_client: GetGeminiClient):
|
|
452
451
|
response = gemini_response(_content_model_response(ModelResponse(parts=[TextPart('Hello world')])))
|
|
453
452
|
gemini_client = get_gemini_client(response)
|
|
454
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
453
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
455
454
|
agent = Agent(m)
|
|
456
455
|
|
|
457
456
|
result = await agent.run('Hello')
|
|
@@ -493,7 +492,7 @@ async def test_request_structured_response(get_gemini_client: GetGeminiClient):
|
|
|
493
492
|
_content_model_response(ModelResponse(parts=[ToolCallPart('final_result', {'response': [1, 2, 123]})]))
|
|
494
493
|
)
|
|
495
494
|
gemini_client = get_gemini_client(response)
|
|
496
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
495
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
497
496
|
agent = Agent(m, result_type=list[int])
|
|
498
497
|
|
|
499
498
|
result = await agent.run('Hello')
|
|
@@ -540,7 +539,7 @@ async def test_request_tool_call(get_gemini_client: GetGeminiClient):
|
|
|
540
539
|
gemini_response(_content_model_response(ModelResponse(parts=[TextPart('final response')]))),
|
|
541
540
|
]
|
|
542
541
|
gemini_client = get_gemini_client(responses)
|
|
543
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
542
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
544
543
|
agent = Agent(m, system_prompt='this is the system prompt')
|
|
545
544
|
|
|
546
545
|
@agent.tool_plain
|
|
@@ -622,7 +621,7 @@ async def test_unexpected_response(client_with_handler: ClientWithHandler, env:
|
|
|
622
621
|
return httpx.Response(401, content='invalid request')
|
|
623
622
|
|
|
624
623
|
gemini_client = client_with_handler(handler)
|
|
625
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
624
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
626
625
|
agent = Agent(m, system_prompt='this is the system prompt')
|
|
627
626
|
|
|
628
627
|
with pytest.raises(ModelHTTPError) as exc_info:
|
|
@@ -639,7 +638,7 @@ async def test_stream_text(get_gemini_client: GetGeminiClient):
|
|
|
639
638
|
json_data = _gemini_streamed_response_ta.dump_json(responses, by_alias=True)
|
|
640
639
|
stream = AsyncByteStreamList([json_data[:100], json_data[100:200], json_data[200:]])
|
|
641
640
|
gemini_client = get_gemini_client(stream)
|
|
642
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
641
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
643
642
|
agent = Agent(m)
|
|
644
643
|
|
|
645
644
|
async with agent.run_stream('Hello') as result:
|
|
@@ -684,7 +683,7 @@ async def test_stream_invalid_unicode_text(get_gemini_client: GetGeminiClient):
|
|
|
684
683
|
|
|
685
684
|
stream = AsyncByteStreamList(parts)
|
|
686
685
|
gemini_client = get_gemini_client(stream)
|
|
687
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
686
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
688
687
|
agent = Agent(m)
|
|
689
688
|
|
|
690
689
|
async with agent.run_stream('Hello') as result:
|
|
@@ -698,7 +697,7 @@ async def test_stream_text_no_data(get_gemini_client: GetGeminiClient):
|
|
|
698
697
|
json_data = _gemini_streamed_response_ta.dump_json(responses, by_alias=True)
|
|
699
698
|
stream = AsyncByteStreamList([json_data[:100], json_data[100:200], json_data[200:]])
|
|
700
699
|
gemini_client = get_gemini_client(stream)
|
|
701
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
700
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
702
701
|
agent = Agent(m)
|
|
703
702
|
with pytest.raises(UnexpectedModelBehavior, match='Streamed response ended without con'):
|
|
704
703
|
async with agent.run_stream('Hello'):
|
|
@@ -714,7 +713,7 @@ async def test_stream_structured(get_gemini_client: GetGeminiClient):
|
|
|
714
713
|
json_data = _gemini_streamed_response_ta.dump_json(responses, by_alias=True)
|
|
715
714
|
stream = AsyncByteStreamList([json_data[:100], json_data[100:200], json_data[200:]])
|
|
716
715
|
gemini_client = get_gemini_client(stream)
|
|
717
|
-
model = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
716
|
+
model = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
718
717
|
agent = Agent(model, result_type=tuple[int, int])
|
|
719
718
|
|
|
720
719
|
async with agent.run_stream('Hello') as result:
|
|
@@ -744,7 +743,7 @@ async def test_stream_structured_tool_calls(get_gemini_client: GetGeminiClient):
|
|
|
744
743
|
second_stream = AsyncByteStreamList([d2[:100], d2[100:]])
|
|
745
744
|
|
|
746
745
|
gemini_client = get_gemini_client([first_stream, second_stream])
|
|
747
|
-
model = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
746
|
+
model = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
748
747
|
agent = Agent(model, result_type=tuple[int, int])
|
|
749
748
|
tool_calls: list[str] = []
|
|
750
749
|
|
|
@@ -817,7 +816,7 @@ async def test_stream_text_heterogeneous(get_gemini_client: GetGeminiClient):
|
|
|
817
816
|
json_data = _gemini_streamed_response_ta.dump_json(responses, by_alias=True)
|
|
818
817
|
stream = AsyncByteStreamList([json_data[:100], json_data[100:200], json_data[200:]])
|
|
819
818
|
gemini_client = get_gemini_client(stream)
|
|
820
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client)
|
|
819
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
|
|
821
820
|
agent = Agent(m)
|
|
822
821
|
|
|
823
822
|
@agent.tool_plain()
|
|
@@ -887,7 +886,7 @@ async def test_model_settings(client_with_handler: ClientWithHandler, env: TestE
|
|
|
887
886
|
)
|
|
888
887
|
|
|
889
888
|
gemini_client = client_with_handler(handler)
|
|
890
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client, api_key='mock')
|
|
889
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client, api_key='mock'))
|
|
891
890
|
agent = Agent(m)
|
|
892
891
|
|
|
893
892
|
result = await agent.run(
|
|
@@ -939,7 +938,8 @@ async def test_safety_settings_unsafe(
|
|
|
939
938
|
)
|
|
940
939
|
|
|
941
940
|
gemini_client = client_with_handler(handler)
|
|
942
|
-
|
|
941
|
+
|
|
942
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client, api_key='mock'))
|
|
943
943
|
agent = Agent(m)
|
|
944
944
|
|
|
945
945
|
await agent.run(
|
|
@@ -975,7 +975,7 @@ async def test_safety_settings_safe(
|
|
|
975
975
|
)
|
|
976
976
|
|
|
977
977
|
gemini_client = client_with_handler(handler)
|
|
978
|
-
m = GeminiModel('gemini-1.5-flash', http_client=gemini_client, api_key='mock')
|
|
978
|
+
m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client, api_key='mock'))
|
|
979
979
|
agent = Agent(m)
|
|
980
980
|
|
|
981
981
|
result = await agent.run(
|
|
@@ -994,7 +994,7 @@ async def test_safety_settings_safe(
|
|
|
994
994
|
async def test_image_as_binary_content_input(
|
|
995
995
|
allow_model_requests: None, gemini_api_key: str, image_content: BinaryContent
|
|
996
996
|
) -> None:
|
|
997
|
-
m = GeminiModel('gemini-2.0-flash', api_key=gemini_api_key)
|
|
997
|
+
m = GeminiModel('gemini-2.0-flash', provider=GoogleGLAProvider(api_key=gemini_api_key))
|
|
998
998
|
agent = Agent(m)
|
|
999
999
|
|
|
1000
1000
|
result = await agent.run(['What is the name of this fruit?', image_content])
|
|
@@ -1003,10 +1003,10 @@ async def test_image_as_binary_content_input(
|
|
|
1003
1003
|
|
|
1004
1004
|
@pytest.mark.vcr()
|
|
1005
1005
|
async def test_image_url_input(allow_model_requests: None, gemini_api_key: str) -> None:
|
|
1006
|
-
m = GeminiModel('gemini-2.0-flash-exp', api_key=gemini_api_key)
|
|
1006
|
+
m = GeminiModel('gemini-2.0-flash-exp', provider=GoogleGLAProvider(api_key=gemini_api_key))
|
|
1007
1007
|
agent = Agent(m)
|
|
1008
1008
|
|
|
1009
1009
|
image_url = ImageUrl(url='https://goo.gle/instrument-img')
|
|
1010
1010
|
|
|
1011
1011
|
result = await agent.run(['What is the name of this fruit?', image_url])
|
|
1012
|
-
assert result.data == snapshot(
|
|
1012
|
+
assert result.data == snapshot("This is not a fruit; it's a pipe organ console.")
|
|
@@ -8,6 +8,8 @@ import pytest
|
|
|
8
8
|
from dirty_equals import IsJson
|
|
9
9
|
from inline_snapshot import snapshot
|
|
10
10
|
from logfire_api import DEFAULT_LOGFIRE_INSTANCE
|
|
11
|
+
from opentelemetry._events import NoOpEventLoggerProvider
|
|
12
|
+
from opentelemetry.trace import NoOpTracerProvider
|
|
11
13
|
|
|
12
14
|
from pydantic_ai.messages import (
|
|
13
15
|
ModelMessage,
|
|
@@ -25,6 +27,7 @@ from pydantic_ai.messages import (
|
|
|
25
27
|
UserPromptPart,
|
|
26
28
|
)
|
|
27
29
|
from pydantic_ai.models import Model, ModelRequestParameters, StreamedResponse
|
|
30
|
+
from pydantic_ai.models.instrumented import InstrumentationSettings, InstrumentedModel
|
|
28
31
|
from pydantic_ai.settings import ModelSettings
|
|
29
32
|
from pydantic_ai.usage import Usage
|
|
30
33
|
|
|
@@ -32,11 +35,6 @@ from ..conftest import try_import
|
|
|
32
35
|
|
|
33
36
|
with try_import() as imports_successful:
|
|
34
37
|
from logfire.testing import CaptureLogfire
|
|
35
|
-
from opentelemetry._events import NoOpEventLoggerProvider
|
|
36
|
-
from opentelemetry.trace import NoOpTracerProvider
|
|
37
|
-
|
|
38
|
-
from pydantic_ai.models.instrumented import InstrumentedModel
|
|
39
|
-
|
|
40
38
|
|
|
41
39
|
pytestmark = [
|
|
42
40
|
pytest.mark.skipif(not imports_successful(), reason='logfire not installed'),
|
|
@@ -103,10 +101,9 @@ class MyResponseStream(StreamedResponse):
|
|
|
103
101
|
return datetime(2022, 1, 1)
|
|
104
102
|
|
|
105
103
|
|
|
106
|
-
@pytest.mark.anyio
|
|
107
104
|
@requires_logfire_events
|
|
108
105
|
async def test_instrumented_model(capfire: CaptureLogfire):
|
|
109
|
-
model = InstrumentedModel(MyModel(), event_mode='logs')
|
|
106
|
+
model = InstrumentedModel(MyModel(), InstrumentationSettings(event_mode='logs'))
|
|
110
107
|
assert model.system == 'my_system'
|
|
111
108
|
assert model.model_name == 'my_model'
|
|
112
109
|
|
|
@@ -193,7 +190,7 @@ async def test_instrumented_model(capfire: CaptureLogfire):
|
|
|
193
190
|
'trace_flags': 1,
|
|
194
191
|
},
|
|
195
192
|
{
|
|
196
|
-
'body': {'content': 'tool_return_content', 'role': 'tool', 'id': 'tool_call_3'},
|
|
193
|
+
'body': {'content': 'tool_return_content', 'role': 'tool', 'id': 'tool_call_3', 'name': 'tool3'},
|
|
197
194
|
'severity_number': 9,
|
|
198
195
|
'severity_text': None,
|
|
199
196
|
'attributes': {
|
|
@@ -216,6 +213,7 @@ Fix the errors and try again.\
|
|
|
216
213
|
""",
|
|
217
214
|
'role': 'tool',
|
|
218
215
|
'id': 'tool_call_4',
|
|
216
|
+
'name': 'tool4',
|
|
219
217
|
},
|
|
220
218
|
'severity_number': 9,
|
|
221
219
|
'severity_text': None,
|
|
@@ -311,9 +309,11 @@ Fix the errors and try again.\
|
|
|
311
309
|
)
|
|
312
310
|
|
|
313
311
|
|
|
314
|
-
@pytest.mark.anyio
|
|
315
312
|
async def test_instrumented_model_not_recording():
|
|
316
|
-
model = InstrumentedModel(
|
|
313
|
+
model = InstrumentedModel(
|
|
314
|
+
MyModel(),
|
|
315
|
+
InstrumentationSettings(tracer_provider=NoOpTracerProvider(), event_logger_provider=NoOpEventLoggerProvider()),
|
|
316
|
+
)
|
|
317
317
|
|
|
318
318
|
messages: list[ModelMessage] = [ModelRequest(parts=[SystemPromptPart('system_prompt')])]
|
|
319
319
|
await model.request(
|
|
@@ -327,10 +327,9 @@ async def test_instrumented_model_not_recording():
|
|
|
327
327
|
)
|
|
328
328
|
|
|
329
329
|
|
|
330
|
-
@pytest.mark.anyio
|
|
331
330
|
@requires_logfire_events
|
|
332
331
|
async def test_instrumented_model_stream(capfire: CaptureLogfire):
|
|
333
|
-
model = InstrumentedModel(MyModel(), event_mode='logs')
|
|
332
|
+
model = InstrumentedModel(MyModel(), InstrumentationSettings(event_mode='logs'))
|
|
334
333
|
|
|
335
334
|
messages: list[ModelMessage] = [
|
|
336
335
|
ModelRequest(
|
|
@@ -410,10 +409,9 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire):
|
|
|
410
409
|
)
|
|
411
410
|
|
|
412
411
|
|
|
413
|
-
@pytest.mark.anyio
|
|
414
412
|
@requires_logfire_events
|
|
415
413
|
async def test_instrumented_model_stream_break(capfire: CaptureLogfire):
|
|
416
|
-
model = InstrumentedModel(MyModel(), event_mode='logs')
|
|
414
|
+
model = InstrumentedModel(MyModel(), InstrumentationSettings(event_mode='logs'))
|
|
417
415
|
|
|
418
416
|
messages: list[ModelMessage] = [
|
|
419
417
|
ModelRequest(
|
|
@@ -505,9 +503,8 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire):
|
|
|
505
503
|
)
|
|
506
504
|
|
|
507
505
|
|
|
508
|
-
@pytest.mark.anyio
|
|
509
506
|
async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire):
|
|
510
|
-
model = InstrumentedModel(MyModel(), event_mode='attributes')
|
|
507
|
+
model = InstrumentedModel(MyModel(), InstrumentationSettings(event_mode='attributes'))
|
|
511
508
|
assert model.system == 'my_system'
|
|
512
509
|
assert model.model_name == 'my_model'
|
|
513
510
|
|
|
@@ -577,6 +574,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire):
|
|
|
577
574
|
'event.name': 'gen_ai.tool.message',
|
|
578
575
|
'content': 'tool_return_content',
|
|
579
576
|
'role': 'tool',
|
|
577
|
+
'name': 'tool3',
|
|
580
578
|
'id': 'tool_call_3',
|
|
581
579
|
'gen_ai.message.index': 0,
|
|
582
580
|
'gen_ai.system': 'my_system',
|
|
@@ -589,6 +587,7 @@ retry_prompt1
|
|
|
589
587
|
Fix the errors and try again.\
|
|
590
588
|
""",
|
|
591
589
|
'role': 'tool',
|
|
590
|
+
'name': 'tool4',
|
|
592
591
|
'id': 'tool_call_4',
|
|
593
592
|
'gen_ai.message.index': 0,
|
|
594
593
|
'gen_ai.system': 'my_system',
|
|
@@ -19,7 +19,7 @@ TEST_CASES = [
|
|
|
19
19
|
'gemini-1.5-flash',
|
|
20
20
|
'google-vertex',
|
|
21
21
|
'vertexai',
|
|
22
|
-
'
|
|
22
|
+
'GeminiModel',
|
|
23
23
|
),
|
|
24
24
|
(
|
|
25
25
|
'GEMINI_API_KEY',
|
|
@@ -27,7 +27,7 @@ TEST_CASES = [
|
|
|
27
27
|
'gemini-1.5-flash',
|
|
28
28
|
'google-vertex',
|
|
29
29
|
'vertexai',
|
|
30
|
-
'
|
|
30
|
+
'GeminiModel',
|
|
31
31
|
),
|
|
32
32
|
(
|
|
33
33
|
'ANTHROPIC_API_KEY',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from collections.abc import Iterator
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from typing_extensions import get_args
|
|
5
6
|
|
|
6
7
|
from pydantic_ai.models import KnownModelName
|
|
7
8
|
|
|
@@ -40,10 +41,18 @@ def test_known_model_names():
|
|
|
40
41
|
openai_names = [f'openai:{n}' for n in get_model_names(OpenAIModelName)] + [
|
|
41
42
|
n for n in get_model_names(OpenAIModelName) if n.startswith('o1') or n.startswith('gpt') or n.startswith('o3')
|
|
42
43
|
]
|
|
44
|
+
deepseek_names = ['deepseek:deepseek-chat', 'deepseek:deepseek-reasoner']
|
|
43
45
|
extra_names = ['test']
|
|
44
46
|
|
|
45
47
|
generated_names = sorted(
|
|
46
|
-
anthropic_names
|
|
48
|
+
anthropic_names
|
|
49
|
+
+ cohere_names
|
|
50
|
+
+ google_names
|
|
51
|
+
+ groq_names
|
|
52
|
+
+ mistral_names
|
|
53
|
+
+ openai_names
|
|
54
|
+
+ deepseek_names
|
|
55
|
+
+ extra_names
|
|
47
56
|
)
|
|
48
57
|
|
|
49
58
|
known_model_names = sorted(get_args(KnownModelName))
|