pydantic-ai 0.0.43__tar.gz → 0.0.44__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.43 → pydantic_ai-0.0.44}/PKG-INFO +3 -3
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/pyproject.toml +3 -3
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/conftest.py +5 -0
- pydantic_ai-0.0.44/tests/graph/test_utils.py +20 -0
- pydantic_ai-0.0.44/tests/models/cassettes/test_cohere/test_request_simple_success_with_vcr.yaml +64 -0
- pydantic_ai-0.0.44/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4.5-preview].yaml +78 -0
- pydantic_ai-0.0.44/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4o-mini].yaml +79 -0
- pydantic_ai-0.0.44/tests/models/cassettes/test_openai/test_max_completion_tokens[o3-mini].yaml +78 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_cohere.py +16 -7
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_fallback.py +4 -2
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_instrumented.py +8 -1
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_openai.py +11 -1
- pydantic_ai-0.0.44/tests/providers/test_cohere.py +54 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_provider_names.py +2 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_examples.py +2 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_logfire.py +23 -2
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_tools.py +148 -8
- pydantic_ai-0.0.43/tests/graph/test_utils.py +0 -13
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/.gitignore +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/LICENSE +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/Makefile +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/README.md +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/__init__.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/assets/dummy.pdf +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/assets/kiwi.png +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/assets/marcelo.mp3 +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/example_modules/README.md +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/example_modules/bank_database.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/example_modules/fake_database.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/example_modules/weather_service.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/__init__.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/test_file_persistence.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/test_graph.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/test_mermaid.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/test_persistence.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/graph/test_state.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/import_examples.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/json_body_serializer.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/mcp_server.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/__init__.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_document_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_anthropic/test_text_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_anthropic_model_without_tools.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_iter_stream.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_max_tokens.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_retry.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_stream.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_structured_response.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_bedrock_model_top_p.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_text_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_bedrock/test_text_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_gemini/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_openai/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/mock_async_stream.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_anthropic.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_bedrock.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_gemini.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_groq.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_mistral.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_model.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_model_function.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_model_names.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_model_test.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/test_vertexai.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/__init__.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/cassettes/test_azure/test_azure_provider_call.yaml +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_anthropic.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_azure.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_bedrock.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_deepseek.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_google_gla.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_google_vertex.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_groq.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/providers/test_mistral.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_agent.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_cli.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_deps.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_format_as_xml.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_json_body_serializer.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_live.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_mcp.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_messages.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_parts_manager.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_streaming.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_usage_limits.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/test_utils.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/typed_agent.py +0 -0
- {pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/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.44
|
|
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,bedrock,cli,cohere,groq,mcp,mistral,openai,vertexai]==0.0.
|
|
31
|
+
Requires-Dist: pydantic-ai-slim[anthropic,bedrock,cli,cohere,groq,mcp,mistral,openai,vertexai]==0.0.44
|
|
32
32
|
Provides-Extra: examples
|
|
33
|
-
Requires-Dist: pydantic-ai-examples==0.0.
|
|
33
|
+
Requires-Dist: pydantic-ai-examples==0.0.44; 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.44"
|
|
8
8
|
description = "Agent Framework / shim to use Pydantic with LLMs"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Samuel Colvin", email = "samuel@pydantic.dev" },
|
|
@@ -36,7 +36,7 @@ classifiers = [
|
|
|
36
36
|
]
|
|
37
37
|
requires-python = ">=3.9"
|
|
38
38
|
dependencies = [
|
|
39
|
-
"pydantic-ai-slim[openai,vertexai,groq,anthropic,mistral,cohere,bedrock,cli,mcp]==0.0.
|
|
39
|
+
"pydantic-ai-slim[openai,vertexai,groq,anthropic,mistral,cohere,bedrock,cli,mcp]==0.0.44",
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[project.urls]
|
|
@@ -46,7 +46,7 @@ Documentation = "https://ai.pydantic.dev"
|
|
|
46
46
|
Changelog = "https://github.com/pydantic/pydantic-ai/releases"
|
|
47
47
|
|
|
48
48
|
[project.optional-dependencies]
|
|
49
|
-
examples = ["pydantic-ai-examples==0.0.
|
|
49
|
+
examples = ["pydantic-ai-examples==0.0.44"]
|
|
50
50
|
logfire = ["logfire>=2.3"]
|
|
51
51
|
|
|
52
52
|
[tool.uv.sources]
|
|
@@ -246,6 +246,11 @@ def anthropic_api_key() -> str:
|
|
|
246
246
|
return os.getenv('ANTHROPIC_API_KEY', 'mock-api-key')
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
@pytest.fixture(scope='session')
|
|
250
|
+
def co_api_key() -> str:
|
|
251
|
+
return os.getenv('CO_API_KEY', 'mock-api-key')
|
|
252
|
+
|
|
253
|
+
|
|
249
254
|
@pytest.fixture
|
|
250
255
|
def mock_snapshot_id(mocker: MockerFixture):
|
|
251
256
|
i = 0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
|
|
3
|
+
from pydantic_graph._utils import run_until_complete
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_run_until_complete_in_main_thread():
|
|
7
|
+
async def run(): ...
|
|
8
|
+
|
|
9
|
+
run_until_complete(run())
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_run_until_complete_in_thread():
|
|
13
|
+
async def run(): ...
|
|
14
|
+
|
|
15
|
+
def get_and_close_event_loop():
|
|
16
|
+
run_until_complete(run())
|
|
17
|
+
|
|
18
|
+
thread = Thread(target=get_and_close_event_loop)
|
|
19
|
+
thread.start()
|
|
20
|
+
thread.join()
|
pydantic_ai-0.0.44/tests/models/cassettes/test_cohere/test_request_simple_success_with_vcr.yaml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
headers:
|
|
4
|
+
accept:
|
|
5
|
+
- '*/*'
|
|
6
|
+
accept-encoding:
|
|
7
|
+
- gzip, deflate
|
|
8
|
+
connection:
|
|
9
|
+
- keep-alive
|
|
10
|
+
content-length:
|
|
11
|
+
- '93'
|
|
12
|
+
content-type:
|
|
13
|
+
- application/json
|
|
14
|
+
host:
|
|
15
|
+
- api.cohere.com
|
|
16
|
+
method: POST
|
|
17
|
+
parsed_body:
|
|
18
|
+
messages:
|
|
19
|
+
- content: hello
|
|
20
|
+
role: user
|
|
21
|
+
model: command-r7b-12-2024
|
|
22
|
+
stream: false
|
|
23
|
+
uri: https://api.cohere.com/v2/chat
|
|
24
|
+
response:
|
|
25
|
+
headers:
|
|
26
|
+
access-control-expose-headers:
|
|
27
|
+
- X-Debug-Trace-ID
|
|
28
|
+
alt-svc:
|
|
29
|
+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
|
30
|
+
cache-control:
|
|
31
|
+
- no-cache, no-store, no-transform, must-revalidate, private, max-age=0
|
|
32
|
+
content-length:
|
|
33
|
+
- '286'
|
|
34
|
+
content-type:
|
|
35
|
+
- application/json
|
|
36
|
+
expires:
|
|
37
|
+
- Thu, 01 Jan 1970 00:00:00 UTC
|
|
38
|
+
num_chars:
|
|
39
|
+
- '2583'
|
|
40
|
+
num_tokens:
|
|
41
|
+
- '10'
|
|
42
|
+
pragma:
|
|
43
|
+
- no-cache
|
|
44
|
+
vary:
|
|
45
|
+
- Origin
|
|
46
|
+
parsed_body:
|
|
47
|
+
finish_reason: COMPLETE
|
|
48
|
+
id: f17a5f6c-1734-4098-bd0d-733ef000ac7b
|
|
49
|
+
message:
|
|
50
|
+
content:
|
|
51
|
+
- text: Hello! How can I assist you today?
|
|
52
|
+
type: text
|
|
53
|
+
role: assistant
|
|
54
|
+
usage:
|
|
55
|
+
billed_units:
|
|
56
|
+
input_tokens: 1
|
|
57
|
+
output_tokens: 9
|
|
58
|
+
tokens:
|
|
59
|
+
input_tokens: 496
|
|
60
|
+
output_tokens: 11
|
|
61
|
+
status:
|
|
62
|
+
code: 200
|
|
63
|
+
message: OK
|
|
64
|
+
version: 1
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
headers:
|
|
4
|
+
accept:
|
|
5
|
+
- application/json
|
|
6
|
+
accept-encoding:
|
|
7
|
+
- gzip, deflate
|
|
8
|
+
connection:
|
|
9
|
+
- keep-alive
|
|
10
|
+
content-length:
|
|
11
|
+
- '123'
|
|
12
|
+
content-type:
|
|
13
|
+
- application/json
|
|
14
|
+
host:
|
|
15
|
+
- api.openai.com
|
|
16
|
+
method: POST
|
|
17
|
+
parsed_body:
|
|
18
|
+
max_completion_tokens: 100
|
|
19
|
+
messages:
|
|
20
|
+
- content: hello
|
|
21
|
+
role: user
|
|
22
|
+
model: gpt-4.5-preview
|
|
23
|
+
n: 1
|
|
24
|
+
stream: false
|
|
25
|
+
uri: https://api.openai.com/v1/chat/completions
|
|
26
|
+
response:
|
|
27
|
+
headers:
|
|
28
|
+
access-control-expose-headers:
|
|
29
|
+
- X-Request-ID
|
|
30
|
+
alt-svc:
|
|
31
|
+
- h3=":443"; ma=86400
|
|
32
|
+
connection:
|
|
33
|
+
- keep-alive
|
|
34
|
+
content-length:
|
|
35
|
+
- '807'
|
|
36
|
+
content-type:
|
|
37
|
+
- application/json
|
|
38
|
+
openai-organization:
|
|
39
|
+
- pydantic-28gund
|
|
40
|
+
openai-processing-ms:
|
|
41
|
+
- '1408'
|
|
42
|
+
openai-version:
|
|
43
|
+
- '2020-10-01'
|
|
44
|
+
strict-transport-security:
|
|
45
|
+
- max-age=31536000; includeSubDomains; preload
|
|
46
|
+
transfer-encoding:
|
|
47
|
+
- chunked
|
|
48
|
+
parsed_body:
|
|
49
|
+
choices:
|
|
50
|
+
- finish_reason: stop
|
|
51
|
+
index: 0
|
|
52
|
+
message:
|
|
53
|
+
annotations: []
|
|
54
|
+
content: Hello! How can I help you today?
|
|
55
|
+
refusal: null
|
|
56
|
+
role: assistant
|
|
57
|
+
created: 1742636225
|
|
58
|
+
id: chatcmpl-BDpZplWguNLn40wA5mIpCR3OIzvYP
|
|
59
|
+
model: gpt-4.5-preview-2025-02-27
|
|
60
|
+
object: chat.completion
|
|
61
|
+
service_tier: default
|
|
62
|
+
system_fingerprint: null
|
|
63
|
+
usage:
|
|
64
|
+
completion_tokens: 10
|
|
65
|
+
completion_tokens_details:
|
|
66
|
+
accepted_prediction_tokens: 0
|
|
67
|
+
audio_tokens: 0
|
|
68
|
+
reasoning_tokens: 0
|
|
69
|
+
rejected_prediction_tokens: 0
|
|
70
|
+
prompt_tokens: 8
|
|
71
|
+
prompt_tokens_details:
|
|
72
|
+
audio_tokens: 0
|
|
73
|
+
cached_tokens: 0
|
|
74
|
+
total_tokens: 18
|
|
75
|
+
status:
|
|
76
|
+
code: 200
|
|
77
|
+
message: OK
|
|
78
|
+
version: 1
|
pydantic_ai-0.0.44/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4o-mini].yaml
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
headers:
|
|
4
|
+
accept:
|
|
5
|
+
- application/json
|
|
6
|
+
accept-encoding:
|
|
7
|
+
- gzip, deflate
|
|
8
|
+
connection:
|
|
9
|
+
- keep-alive
|
|
10
|
+
content-length:
|
|
11
|
+
- '119'
|
|
12
|
+
content-type:
|
|
13
|
+
- application/json
|
|
14
|
+
host:
|
|
15
|
+
- api.openai.com
|
|
16
|
+
method: POST
|
|
17
|
+
parsed_body:
|
|
18
|
+
max_completion_tokens: 100
|
|
19
|
+
messages:
|
|
20
|
+
- content: hello
|
|
21
|
+
role: user
|
|
22
|
+
model: gpt-4o-mini
|
|
23
|
+
n: 1
|
|
24
|
+
stream: false
|
|
25
|
+
uri: https://api.openai.com/v1/chat/completions
|
|
26
|
+
response:
|
|
27
|
+
headers:
|
|
28
|
+
access-control-expose-headers:
|
|
29
|
+
- X-Request-ID
|
|
30
|
+
alt-svc:
|
|
31
|
+
- h3=":443"; ma=86400
|
|
32
|
+
connection:
|
|
33
|
+
- keep-alive
|
|
34
|
+
content-length:
|
|
35
|
+
- '840'
|
|
36
|
+
content-type:
|
|
37
|
+
- application/json
|
|
38
|
+
openai-organization:
|
|
39
|
+
- pydantic-28gund
|
|
40
|
+
openai-processing-ms:
|
|
41
|
+
- '278'
|
|
42
|
+
openai-version:
|
|
43
|
+
- '2020-10-01'
|
|
44
|
+
strict-transport-security:
|
|
45
|
+
- max-age=31536000; includeSubDomains; preload
|
|
46
|
+
transfer-encoding:
|
|
47
|
+
- chunked
|
|
48
|
+
parsed_body:
|
|
49
|
+
choices:
|
|
50
|
+
- finish_reason: stop
|
|
51
|
+
index: 0
|
|
52
|
+
logprobs: null
|
|
53
|
+
message:
|
|
54
|
+
annotations: []
|
|
55
|
+
content: Hello! How can I assist you today?
|
|
56
|
+
refusal: null
|
|
57
|
+
role: assistant
|
|
58
|
+
created: 1742636224
|
|
59
|
+
id: chatcmpl-BDpZoxw4i90ZesN8iyrwLmGWRJ5lz
|
|
60
|
+
model: gpt-4o-mini-2024-07-18
|
|
61
|
+
object: chat.completion
|
|
62
|
+
service_tier: default
|
|
63
|
+
system_fingerprint: fp_b8bc95a0ac
|
|
64
|
+
usage:
|
|
65
|
+
completion_tokens: 10
|
|
66
|
+
completion_tokens_details:
|
|
67
|
+
accepted_prediction_tokens: 0
|
|
68
|
+
audio_tokens: 0
|
|
69
|
+
reasoning_tokens: 0
|
|
70
|
+
rejected_prediction_tokens: 0
|
|
71
|
+
prompt_tokens: 8
|
|
72
|
+
prompt_tokens_details:
|
|
73
|
+
audio_tokens: 0
|
|
74
|
+
cached_tokens: 0
|
|
75
|
+
total_tokens: 18
|
|
76
|
+
status:
|
|
77
|
+
code: 200
|
|
78
|
+
message: OK
|
|
79
|
+
version: 1
|
pydantic_ai-0.0.44/tests/models/cassettes/test_openai/test_max_completion_tokens[o3-mini].yaml
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
headers:
|
|
4
|
+
accept:
|
|
5
|
+
- application/json
|
|
6
|
+
accept-encoding:
|
|
7
|
+
- gzip, deflate
|
|
8
|
+
connection:
|
|
9
|
+
- keep-alive
|
|
10
|
+
content-length:
|
|
11
|
+
- '115'
|
|
12
|
+
content-type:
|
|
13
|
+
- application/json
|
|
14
|
+
host:
|
|
15
|
+
- api.openai.com
|
|
16
|
+
method: POST
|
|
17
|
+
parsed_body:
|
|
18
|
+
max_completion_tokens: 100
|
|
19
|
+
messages:
|
|
20
|
+
- content: hello
|
|
21
|
+
role: user
|
|
22
|
+
model: o3-mini
|
|
23
|
+
n: 1
|
|
24
|
+
stream: false
|
|
25
|
+
uri: https://api.openai.com/v1/chat/completions
|
|
26
|
+
response:
|
|
27
|
+
headers:
|
|
28
|
+
access-control-expose-headers:
|
|
29
|
+
- X-Request-ID
|
|
30
|
+
alt-svc:
|
|
31
|
+
- h3=":443"; ma=86400
|
|
32
|
+
connection:
|
|
33
|
+
- keep-alive
|
|
34
|
+
content-length:
|
|
35
|
+
- '817'
|
|
36
|
+
content-type:
|
|
37
|
+
- application/json
|
|
38
|
+
openai-organization:
|
|
39
|
+
- pydantic-28gund
|
|
40
|
+
openai-processing-ms:
|
|
41
|
+
- '1895'
|
|
42
|
+
openai-version:
|
|
43
|
+
- '2020-10-01'
|
|
44
|
+
strict-transport-security:
|
|
45
|
+
- max-age=31536000; includeSubDomains; preload
|
|
46
|
+
transfer-encoding:
|
|
47
|
+
- chunked
|
|
48
|
+
parsed_body:
|
|
49
|
+
choices:
|
|
50
|
+
- finish_reason: stop
|
|
51
|
+
index: 0
|
|
52
|
+
message:
|
|
53
|
+
annotations: []
|
|
54
|
+
content: Hello there! How can I help you today?
|
|
55
|
+
refusal: null
|
|
56
|
+
role: assistant
|
|
57
|
+
created: 1742636222
|
|
58
|
+
id: chatcmpl-BDpZm1SiItIXIcDA0xRV9QGZhD97e
|
|
59
|
+
model: o3-mini-2025-01-31
|
|
60
|
+
object: chat.completion
|
|
61
|
+
service_tier: default
|
|
62
|
+
system_fingerprint: fp_617f206dd9
|
|
63
|
+
usage:
|
|
64
|
+
completion_tokens: 85
|
|
65
|
+
completion_tokens_details:
|
|
66
|
+
accepted_prediction_tokens: 0
|
|
67
|
+
audio_tokens: 0
|
|
68
|
+
reasoning_tokens: 64
|
|
69
|
+
rejected_prediction_tokens: 0
|
|
70
|
+
prompt_tokens: 7
|
|
71
|
+
prompt_tokens_details:
|
|
72
|
+
audio_tokens: 0
|
|
73
|
+
cached_tokens: 0
|
|
74
|
+
total_tokens: 92
|
|
75
|
+
status:
|
|
76
|
+
code: 200
|
|
77
|
+
message: OK
|
|
78
|
+
version: 1
|
|
@@ -38,6 +38,7 @@ with try_import() as imports_successful:
|
|
|
38
38
|
from cohere.core.api_error import ApiError
|
|
39
39
|
|
|
40
40
|
from pydantic_ai.models.cohere import CohereModel
|
|
41
|
+
from pydantic_ai.providers.cohere import CohereProvider
|
|
41
42
|
|
|
42
43
|
# note: we use Union here for compatibility with Python 3.9
|
|
43
44
|
MockChatResponse = Union[ChatResponse, Exception]
|
|
@@ -49,7 +50,7 @@ pytestmark = [
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def test_init():
|
|
52
|
-
m = CohereModel('command-r7b-12-2024', api_key='foobar')
|
|
53
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(api_key='foobar'))
|
|
53
54
|
assert m.model_name == 'command-r7b-12-2024'
|
|
54
55
|
assert m.system == 'cohere'
|
|
55
56
|
assert m.base_url == 'https://api.cohere.com'
|
|
@@ -96,7 +97,7 @@ async def test_request_simple_success(allow_model_requests: None):
|
|
|
96
97
|
)
|
|
97
98
|
)
|
|
98
99
|
mock_client = MockAsyncClientV2.create_mock(c)
|
|
99
|
-
m = CohereModel('command-r7b-12-2024', cohere_client=mock_client)
|
|
100
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(cohere_client=mock_client))
|
|
100
101
|
agent = Agent(m)
|
|
101
102
|
|
|
102
103
|
result = await agent.run('hello')
|
|
@@ -135,7 +136,7 @@ async def test_request_simple_usage(allow_model_requests: None):
|
|
|
135
136
|
),
|
|
136
137
|
)
|
|
137
138
|
mock_client = MockAsyncClientV2.create_mock(c)
|
|
138
|
-
m = CohereModel('command-r7b-12-2024', cohere_client=mock_client)
|
|
139
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(cohere_client=mock_client))
|
|
139
140
|
agent = Agent(m)
|
|
140
141
|
|
|
141
142
|
result = await agent.run('Hello')
|
|
@@ -169,7 +170,7 @@ async def test_request_structured_response(allow_model_requests: None):
|
|
|
169
170
|
)
|
|
170
171
|
)
|
|
171
172
|
mock_client = MockAsyncClientV2.create_mock(c)
|
|
172
|
-
m = CohereModel('command-r7b-12-2024', cohere_client=mock_client)
|
|
173
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(cohere_client=mock_client))
|
|
173
174
|
agent = Agent(m, result_type=list[int])
|
|
174
175
|
|
|
175
176
|
result = await agent.run('Hello')
|
|
@@ -243,7 +244,7 @@ async def test_request_tool_call(allow_model_requests: None):
|
|
|
243
244
|
),
|
|
244
245
|
]
|
|
245
246
|
mock_client = MockAsyncClientV2.create_mock(responses)
|
|
246
|
-
m = CohereModel('command-r7b-12-2024', cohere_client=mock_client)
|
|
247
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(cohere_client=mock_client))
|
|
247
248
|
agent = Agent(m, system_prompt='this is the system prompt')
|
|
248
249
|
|
|
249
250
|
@agent.tool_plain
|
|
@@ -326,7 +327,7 @@ async def test_request_tool_call(allow_model_requests: None):
|
|
|
326
327
|
async def test_multimodal(allow_model_requests: None):
|
|
327
328
|
c = completion_message(AssistantMessageResponse(content=[TextAssistantMessageResponseContentItem(text='world')]))
|
|
328
329
|
mock_client = MockAsyncClientV2.create_mock(c)
|
|
329
|
-
m = CohereModel('command-r7b-12-2024', cohere_client=mock_client)
|
|
330
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(cohere_client=mock_client))
|
|
330
331
|
agent = Agent(m)
|
|
331
332
|
|
|
332
333
|
with pytest.raises(RuntimeError, match='Cohere does not yet support multi-modal inputs.'):
|
|
@@ -347,8 +348,16 @@ def test_model_status_error(allow_model_requests: None) -> None:
|
|
|
347
348
|
body={'error': 'test error'},
|
|
348
349
|
)
|
|
349
350
|
)
|
|
350
|
-
m = CohereModel('command-r', cohere_client=mock_client)
|
|
351
|
+
m = CohereModel('command-r', provider=CohereProvider(cohere_client=mock_client))
|
|
351
352
|
agent = Agent(m)
|
|
352
353
|
with pytest.raises(ModelHTTPError) as exc_info:
|
|
353
354
|
agent.run_sync('hello')
|
|
354
355
|
assert str(exc_info.value) == snapshot("status_code: 500, model_name: command-r, body: {'error': 'test error'}")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@pytest.mark.vcr()
|
|
359
|
+
async def test_request_simple_success_with_vcr(allow_model_requests: None, co_api_key: str):
|
|
360
|
+
m = CohereModel('command-r7b-12-2024', provider=CohereProvider(api_key=co_api_key))
|
|
361
|
+
agent = Agent(m)
|
|
362
|
+
result = await agent.run('hello')
|
|
363
|
+
assert result.data == snapshot('Hello! How can I assist you today?')
|
|
@@ -136,6 +136,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None:
|
|
|
136
136
|
'end_time': 5000000000,
|
|
137
137
|
'attributes': {
|
|
138
138
|
'gen_ai.operation.name': 'chat',
|
|
139
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
139
140
|
'logfire.span_type': 'span',
|
|
140
141
|
'logfire.msg': 'chat fallback:function:failure_response:,function:success_response:',
|
|
141
142
|
'gen_ai.usage.input_tokens': 51,
|
|
@@ -160,7 +161,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None:
|
|
|
160
161
|
},
|
|
161
162
|
]
|
|
162
163
|
),
|
|
163
|
-
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}',
|
|
164
|
+
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}',
|
|
164
165
|
},
|
|
165
166
|
},
|
|
166
167
|
{
|
|
@@ -233,6 +234,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None
|
|
|
233
234
|
'end_time': 5000000000,
|
|
234
235
|
'attributes': {
|
|
235
236
|
'gen_ai.operation.name': 'chat',
|
|
237
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
236
238
|
'logfire.span_type': 'span',
|
|
237
239
|
'logfire.msg': 'chat fallback:function::failure_response_stream,function::success_response_stream',
|
|
238
240
|
'gen_ai.system': 'function',
|
|
@@ -241,7 +243,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None
|
|
|
241
243
|
'gen_ai.usage.output_tokens': 2,
|
|
242
244
|
'gen_ai.response.model': 'function::success_response_stream',
|
|
243
245
|
'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]',
|
|
244
|
-
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}',
|
|
246
|
+
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}',
|
|
245
247
|
},
|
|
246
248
|
},
|
|
247
249
|
{
|
|
@@ -152,6 +152,8 @@ async def test_instrumented_model(capfire: CaptureLogfire):
|
|
|
152
152
|
'gen_ai.request.model': 'my_model',
|
|
153
153
|
'server.address': 'example.com',
|
|
154
154
|
'server.port': 8000,
|
|
155
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
156
|
+
'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}',
|
|
155
157
|
'gen_ai.request.temperature': 1,
|
|
156
158
|
'logfire.msg': 'chat my_model',
|
|
157
159
|
'logfire.span_type': 'span',
|
|
@@ -374,6 +376,8 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire):
|
|
|
374
376
|
'gen_ai.request.model': 'my_model',
|
|
375
377
|
'server.address': 'example.com',
|
|
376
378
|
'server.port': 8000,
|
|
379
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
380
|
+
'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}',
|
|
377
381
|
'gen_ai.request.temperature': 1,
|
|
378
382
|
'logfire.msg': 'chat my_model',
|
|
379
383
|
'logfire.span_type': 'span',
|
|
@@ -457,6 +461,8 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire):
|
|
|
457
461
|
'gen_ai.request.model': 'my_model',
|
|
458
462
|
'server.address': 'example.com',
|
|
459
463
|
'server.port': 8000,
|
|
464
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
465
|
+
'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}',
|
|
460
466
|
'gen_ai.request.temperature': 1,
|
|
461
467
|
'logfire.msg': 'chat my_model',
|
|
462
468
|
'logfire.span_type': 'span',
|
|
@@ -559,6 +565,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire):
|
|
|
559
565
|
'gen_ai.request.model': 'my_model',
|
|
560
566
|
'server.address': 'example.com',
|
|
561
567
|
'server.port': 8000,
|
|
568
|
+
'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}',
|
|
562
569
|
'gen_ai.request.temperature': 1,
|
|
563
570
|
'logfire.msg': 'chat my_model',
|
|
564
571
|
'logfire.span_type': 'span',
|
|
@@ -652,7 +659,7 @@ Fix the errors and try again.\
|
|
|
652
659
|
]
|
|
653
660
|
)
|
|
654
661
|
),
|
|
655
|
-
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}',
|
|
662
|
+
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}',
|
|
656
663
|
},
|
|
657
664
|
},
|
|
658
665
|
]
|
|
@@ -28,7 +28,7 @@ from pydantic_ai.messages import (
|
|
|
28
28
|
from pydantic_ai.result import Usage
|
|
29
29
|
from pydantic_ai.settings import ModelSettings
|
|
30
30
|
|
|
31
|
-
from ..conftest import IsNow, TestEnv, raise_if_exception, try_import
|
|
31
|
+
from ..conftest import IsNow, IsStr, TestEnv, raise_if_exception, try_import
|
|
32
32
|
from .mock_async_stream import MockAsyncStream
|
|
33
33
|
|
|
34
34
|
with try_import() as imports_successful:
|
|
@@ -684,3 +684,13 @@ def test_model_status_error(allow_model_requests: None) -> None:
|
|
|
684
684
|
with pytest.raises(ModelHTTPError) as exc_info:
|
|
685
685
|
agent.run_sync('hello')
|
|
686
686
|
assert str(exc_info.value) == snapshot("status_code: 500, model_name: gpt-4o, body: {'error': 'test error'}")
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@pytest.mark.vcr()
|
|
690
|
+
@pytest.mark.parametrize('model_name', ['o3-mini', 'gpt-4o-mini', 'gpt-4.5-preview'])
|
|
691
|
+
async def test_max_completion_tokens(allow_model_requests: None, model_name: str, openai_api_key: str):
|
|
692
|
+
m = OpenAIModel(model_name, provider=OpenAIProvider(api_key=openai_api_key))
|
|
693
|
+
agent = Agent(m, model_settings=ModelSettings(max_tokens=100))
|
|
694
|
+
|
|
695
|
+
result = await agent.run('hello')
|
|
696
|
+
assert result.data == IsStr()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from ..conftest import try_import
|
|
10
|
+
|
|
11
|
+
with try_import() as imports_successful:
|
|
12
|
+
from cohere import AsyncClientV2
|
|
13
|
+
from cohere.core.http_client import AsyncHttpClient
|
|
14
|
+
|
|
15
|
+
from pydantic_ai.providers.cohere import CohereProvider
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
pytestmark = pytest.mark.skipif(not imports_successful(), reason='cohere not installed')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_cohere_provider() -> None:
|
|
22
|
+
provider = CohereProvider(api_key='api-key')
|
|
23
|
+
assert provider.name == 'cohere'
|
|
24
|
+
assert provider.base_url == 'https://api.cohere.com'
|
|
25
|
+
assert isinstance(provider.client, AsyncClientV2)
|
|
26
|
+
assert provider.client._client_wrapper._token == 'api-key' # type: ignore[reportPrivateUsage]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_cohere_provider_need_api_key() -> None:
|
|
30
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
31
|
+
with pytest.raises(ValueError, match='CO_API_KEY'):
|
|
32
|
+
CohereProvider()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_cohere_provider_pass_http_client() -> None:
|
|
36
|
+
http_client = httpx.AsyncClient()
|
|
37
|
+
provider = CohereProvider(http_client=http_client, api_key='api-key')
|
|
38
|
+
# The AsyncClientV2 wraps our httpx client in an AsyncHttpClient
|
|
39
|
+
# So we just check that the httpx_client is an instance of AsyncHttpClient
|
|
40
|
+
assert isinstance(provider.client._client_wrapper.httpx_client, AsyncHttpClient) # type: ignore[reportPrivateUsage]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_cohere_provider_pass_cohere_client() -> None:
|
|
44
|
+
cohere_client = AsyncClientV2(api_key='test-api-key')
|
|
45
|
+
provider = CohereProvider(cohere_client=cohere_client)
|
|
46
|
+
assert provider.client == cohere_client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_cohere_provider_with_env_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
50
|
+
custom_base_url = 'https://custom.cohere.com/'
|
|
51
|
+
# Test with environment variable for base_url
|
|
52
|
+
monkeypatch.setenv('CO_BASE_URL', custom_base_url)
|
|
53
|
+
provider = CohereProvider(api_key='api-key')
|
|
54
|
+
assert provider.base_url == custom_base_url
|
|
@@ -12,6 +12,7 @@ from ..conftest import try_import
|
|
|
12
12
|
|
|
13
13
|
with try_import() as imports_successful:
|
|
14
14
|
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
15
|
+
from pydantic_ai.providers.cohere import CohereProvider
|
|
15
16
|
from pydantic_ai.providers.deepseek import DeepSeekProvider
|
|
16
17
|
from pydantic_ai.providers.google_gla import GoogleGLAProvider
|
|
17
18
|
from pydantic_ai.providers.google_vertex import GoogleVertexProvider
|
|
@@ -21,6 +22,7 @@ with try_import() as imports_successful:
|
|
|
21
22
|
|
|
22
23
|
test_infer_provider_params = [
|
|
23
24
|
('anthropic', AnthropicProvider, 'ANTHROPIC_API_KEY'),
|
|
25
|
+
('cohere', CohereProvider, 'CO_API_KEY'),
|
|
24
26
|
('deepseek', DeepSeekProvider, 'DEEPSEEK_API_KEY'),
|
|
25
27
|
('openai', OpenAIProvider, None),
|
|
26
28
|
('google-vertex', GoogleVertexProvider, None),
|
|
@@ -86,6 +86,8 @@ def test_docs_examples( # noqa: C901
|
|
|
86
86
|
env.set('GEMINI_API_KEY', 'testing')
|
|
87
87
|
env.set('GROQ_API_KEY', 'testing')
|
|
88
88
|
env.set('CO_API_KEY', 'testing')
|
|
89
|
+
env.set('MISTRAL_API_KEY', 'testing')
|
|
90
|
+
env.set('ANTHROPIC_API_KEY', 'testing')
|
|
89
91
|
|
|
90
92
|
sys.path.append('tests/example_modules')
|
|
91
93
|
|
|
@@ -167,7 +167,7 @@ def test_logfire(get_logfire_summary: Callable[[], LogfireSummary], instrument:
|
|
|
167
167
|
)
|
|
168
168
|
chat_span_attributes = summary.attributes[2]
|
|
169
169
|
if instrument is True or instrument.event_mode == 'attributes':
|
|
170
|
-
attribute_mode_attributes = {k: chat_span_attributes.pop(k) for k in ['events'
|
|
170
|
+
attribute_mode_attributes = {k: chat_span_attributes.pop(k) for k in ['events']}
|
|
171
171
|
assert attribute_mode_attributes == snapshot(
|
|
172
172
|
{
|
|
173
173
|
'events': IsJson(
|
|
@@ -198,7 +198,6 @@ def test_logfire(get_logfire_summary: Callable[[], LogfireSummary], instrument:
|
|
|
198
198
|
]
|
|
199
199
|
)
|
|
200
200
|
),
|
|
201
|
-
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}',
|
|
202
201
|
}
|
|
203
202
|
)
|
|
204
203
|
|
|
@@ -207,6 +206,28 @@ def test_logfire(get_logfire_summary: Callable[[], LogfireSummary], instrument:
|
|
|
207
206
|
'gen_ai.operation.name': 'chat',
|
|
208
207
|
'gen_ai.system': 'test',
|
|
209
208
|
'gen_ai.request.model': 'test',
|
|
209
|
+
'model_request_parameters': IsJson(
|
|
210
|
+
snapshot(
|
|
211
|
+
{
|
|
212
|
+
'function_tools': [
|
|
213
|
+
{
|
|
214
|
+
'name': 'my_ret',
|
|
215
|
+
'description': '',
|
|
216
|
+
'parameters_json_schema': {
|
|
217
|
+
'additionalProperties': False,
|
|
218
|
+
'properties': {'x': {'type': 'integer'}},
|
|
219
|
+
'required': ['x'],
|
|
220
|
+
'type': 'object',
|
|
221
|
+
},
|
|
222
|
+
'outer_typed_dict_key': None,
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
'allow_text_result': True,
|
|
226
|
+
'result_tools': [],
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
),
|
|
230
|
+
'logfire.json_schema': IsJson(),
|
|
210
231
|
'logfire.span_type': 'span',
|
|
211
232
|
'logfire.msg': 'chat test',
|
|
212
233
|
'gen_ai.response.model': 'test',
|
|
@@ -122,7 +122,6 @@ def sphinx_style_docstring(foo: int, /) -> str: # pragma: no cover
|
|
|
122
122
|
"""Sphinx style docstring.
|
|
123
123
|
|
|
124
124
|
:param foo: The foo thing.
|
|
125
|
-
:return: The result.
|
|
126
125
|
"""
|
|
127
126
|
return str(foo)
|
|
128
127
|
|
|
@@ -187,6 +186,152 @@ def test_docstring_numpy(docstring_format: Literal['numpy', 'auto']):
|
|
|
187
186
|
)
|
|
188
187
|
|
|
189
188
|
|
|
189
|
+
def test_google_style_with_returns():
|
|
190
|
+
agent = Agent(FunctionModel(get_json_schema))
|
|
191
|
+
|
|
192
|
+
def my_tool(x: int) -> str: # pragma: no cover
|
|
193
|
+
"""A function that does something.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
x: The input value.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
str: The result as a string.
|
|
200
|
+
"""
|
|
201
|
+
return str(x)
|
|
202
|
+
|
|
203
|
+
agent.tool_plain(my_tool)
|
|
204
|
+
result = agent.run_sync('Hello')
|
|
205
|
+
json_schema = json.loads(result.data)
|
|
206
|
+
assert json_schema == snapshot(
|
|
207
|
+
{
|
|
208
|
+
'name': 'my_tool',
|
|
209
|
+
'description': """\
|
|
210
|
+
<summary>A function that does something.</summary>
|
|
211
|
+
<returns>
|
|
212
|
+
<type>str</type>
|
|
213
|
+
<description>The result as a string.</description>
|
|
214
|
+
</returns>\
|
|
215
|
+
""",
|
|
216
|
+
'parameters_json_schema': {
|
|
217
|
+
'additionalProperties': False,
|
|
218
|
+
'properties': {'x': {'description': 'The input value.', 'type': 'integer'}},
|
|
219
|
+
'required': ['x'],
|
|
220
|
+
'type': 'object',
|
|
221
|
+
},
|
|
222
|
+
'outer_typed_dict_key': None,
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_sphinx_style_with_returns():
|
|
228
|
+
agent = Agent(FunctionModel(get_json_schema))
|
|
229
|
+
|
|
230
|
+
def my_tool(x: int) -> str: # pragma: no cover
|
|
231
|
+
"""A sphinx function with returns.
|
|
232
|
+
|
|
233
|
+
:param x: The input value.
|
|
234
|
+
:rtype: str
|
|
235
|
+
:return: The result as a string with type.
|
|
236
|
+
"""
|
|
237
|
+
return str(x)
|
|
238
|
+
|
|
239
|
+
agent.tool_plain(docstring_format='sphinx')(my_tool)
|
|
240
|
+
result = agent.run_sync('Hello')
|
|
241
|
+
json_schema = json.loads(result.data)
|
|
242
|
+
assert json_schema == snapshot(
|
|
243
|
+
{
|
|
244
|
+
'name': 'my_tool',
|
|
245
|
+
'description': """\
|
|
246
|
+
<summary>A sphinx function with returns.</summary>
|
|
247
|
+
<returns>
|
|
248
|
+
<type>str</type>
|
|
249
|
+
<description>The result as a string with type.</description>
|
|
250
|
+
</returns>\
|
|
251
|
+
""",
|
|
252
|
+
'parameters_json_schema': {
|
|
253
|
+
'additionalProperties': False,
|
|
254
|
+
'properties': {'x': {'description': 'The input value.', 'type': 'integer'}},
|
|
255
|
+
'required': ['x'],
|
|
256
|
+
'type': 'object',
|
|
257
|
+
},
|
|
258
|
+
'outer_typed_dict_key': None,
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_numpy_style_with_returns():
|
|
264
|
+
agent = Agent(FunctionModel(get_json_schema))
|
|
265
|
+
|
|
266
|
+
def my_tool(x: int) -> str: # pragma: no cover
|
|
267
|
+
"""A numpy function with returns.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
x : int
|
|
272
|
+
The input value.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
str
|
|
277
|
+
The result as a string with type.
|
|
278
|
+
"""
|
|
279
|
+
return str(x)
|
|
280
|
+
|
|
281
|
+
agent.tool_plain(docstring_format='numpy')(my_tool)
|
|
282
|
+
result = agent.run_sync('Hello')
|
|
283
|
+
json_schema = json.loads(result.data)
|
|
284
|
+
assert json_schema == snapshot(
|
|
285
|
+
{
|
|
286
|
+
'name': 'my_tool',
|
|
287
|
+
'description': """\
|
|
288
|
+
<summary>A numpy function with returns.</summary>
|
|
289
|
+
<returns>
|
|
290
|
+
<type>str</type>
|
|
291
|
+
<description>The result as a string with type.</description>
|
|
292
|
+
</returns>\
|
|
293
|
+
""",
|
|
294
|
+
'parameters_json_schema': {
|
|
295
|
+
'additionalProperties': False,
|
|
296
|
+
'properties': {'x': {'description': 'The input value.', 'type': 'integer'}},
|
|
297
|
+
'required': ['x'],
|
|
298
|
+
'type': 'object',
|
|
299
|
+
},
|
|
300
|
+
'outer_typed_dict_key': None,
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def only_returns_type() -> str: # pragma: no cover
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
str: The result as a string.
|
|
310
|
+
"""
|
|
311
|
+
return 'foo'
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_only_returns_type():
|
|
315
|
+
agent = Agent(FunctionModel(get_json_schema))
|
|
316
|
+
agent.tool_plain(only_returns_type)
|
|
317
|
+
|
|
318
|
+
result = agent.run_sync('Hello')
|
|
319
|
+
json_schema = json.loads(result.data)
|
|
320
|
+
assert json_schema == snapshot(
|
|
321
|
+
{
|
|
322
|
+
'name': 'only_returns_type',
|
|
323
|
+
'description': """\
|
|
324
|
+
<returns>
|
|
325
|
+
<type>str</type>
|
|
326
|
+
<description>The result as a string.</description>
|
|
327
|
+
</returns>\
|
|
328
|
+
""",
|
|
329
|
+
'parameters_json_schema': {'additionalProperties': False, 'properties': {}, 'type': 'object'},
|
|
330
|
+
'outer_typed_dict_key': None,
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
190
335
|
def unknown_docstring(**kwargs: int) -> str: # pragma: no cover
|
|
191
336
|
"""Unknown style docstring."""
|
|
192
337
|
return str(kwargs)
|
|
@@ -572,11 +717,7 @@ agent = Agent('test', tools=[ctx_tool], deps_type=int)
|
|
|
572
717
|
|
|
573
718
|
|
|
574
719
|
async def tool_without_return_annotation_in_docstring() -> str: # pragma: no cover
|
|
575
|
-
"""A tool that documents what it returns but doesn't have a return annotation in the docstring.
|
|
576
|
-
|
|
577
|
-
Returns:
|
|
578
|
-
A value.
|
|
579
|
-
"""
|
|
720
|
+
"""A tool that documents what it returns but doesn't have a return annotation in the docstring."""
|
|
580
721
|
|
|
581
722
|
return ''
|
|
582
723
|
|
|
@@ -591,8 +732,7 @@ def test_suppress_griffe_logging(caplog: LogCaptureFixture):
|
|
|
591
732
|
json_schema = json.loads(result.data)
|
|
592
733
|
assert json_schema == snapshot(
|
|
593
734
|
{
|
|
594
|
-
'description': "A tool that documents what it returns but doesn't have a "
|
|
595
|
-
'return annotation in the docstring.',
|
|
735
|
+
'description': "A tool that documents what it returns but doesn't have a return annotation in the docstring.",
|
|
596
736
|
'name': 'tool_without_return_annotation_in_docstring',
|
|
597
737
|
'outer_typed_dict_key': None,
|
|
598
738
|
'parameters_json_schema': {'additionalProperties': False, 'properties': {}, 'type': 'object'},
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from threading import Thread
|
|
2
|
-
|
|
3
|
-
from pydantic_graph._utils import get_event_loop
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_get_event_loop_in_thread():
|
|
7
|
-
def get_and_close_event_loop():
|
|
8
|
-
event_loop = get_event_loop()
|
|
9
|
-
event_loop.close()
|
|
10
|
-
|
|
11
|
-
thread = Thread(target=get_and_close_event_loop)
|
|
12
|
-
thread.start()
|
|
13
|
-
thread.join()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml
RENAMED
|
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
|
{pydantic_ai-0.0.43 → pydantic_ai-0.0.44}/tests/models/cassettes/test_groq/test_image_url_input.yaml
RENAMED
|
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
|