pydantic-ai 0.0.41__tar.gz → 0.0.42__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.41 → pydantic_ai-0.0.42}/.gitignore +1 -1
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/Makefile +9 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/PKG-INFO +3 -3
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/pyproject.toml +17 -7
- pydantic_ai-0.0.42/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml +205 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_graph.py +1 -1
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_persistence.py +1 -1
- pydantic_ai-0.0.42/tests/mcp_server.py +19 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_examples.py +25 -3
- pydantic_ai-0.0.42/tests/test_mcp.py +93 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_tools.py +61 -16
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/LICENSE +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/README.md +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/__init__.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/assets/dummy.pdf +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/assets/kiwi.png +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/assets/marcelo.mp3 +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/conftest.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/example_modules/README.md +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/example_modules/bank_database.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/example_modules/fake_database.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/example_modules/weather_service.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/__init__.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_file_persistence.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_mermaid.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_state.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/graph/test_utils.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/import_examples.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/json_body_serializer.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/__init__.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_document_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_anthropic/test_text_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_anthropic_model_without_tools.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_iter_stream.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_max_tokens.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_retry.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_stream.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_structured_response.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_bedrock_model_top_p.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_text_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_bedrock/test_text_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_gemini/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_openai/test_document_url_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/mock_async_stream.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_anthropic.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_bedrock.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_cohere.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_fallback.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_gemini.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_groq.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_instrumented.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_mistral.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_model.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_model_function.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_model_names.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_model_test.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_openai.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/models/test_vertexai.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/__init__.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/cassettes/test_azure/test_azure_provider_call.yaml +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_anthropic.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_azure.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_bedrock.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_deepseek.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_google_gla.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_google_vertex.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_groq.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_mistral.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/providers/test_provider_names.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_agent.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_cli.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_deps.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_format_as_xml.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_json_body_serializer.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_live.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_logfire.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_messages.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_parts_manager.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_streaming.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_usage_limits.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/test_utils.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/typed_agent.py +0 -0
- {pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/tests/typed_graph.py +0 -0
|
@@ -27,6 +27,10 @@ lint: ## Lint the code
|
|
|
27
27
|
uv run ruff format --check
|
|
28
28
|
uv run ruff check
|
|
29
29
|
|
|
30
|
+
.PHONY: lint-js
|
|
31
|
+
lint-js: ## Lint JS and TS code
|
|
32
|
+
cd mcp-run-python && npm run lint
|
|
33
|
+
|
|
30
34
|
.PHONY: typecheck-pyright
|
|
31
35
|
typecheck-pyright:
|
|
32
36
|
@# PYRIGHT_PYTHON_IGNORE_WARNINGS avoids the overhead of making a request to github on every invocation
|
|
@@ -62,6 +66,11 @@ testcov: test ## Run tests and generate a coverage report
|
|
|
62
66
|
@echo "building coverage html"
|
|
63
67
|
@uv run coverage html
|
|
64
68
|
|
|
69
|
+
.PHONY: test-mrp
|
|
70
|
+
test-mrp: ## Build and tests of mcp-run-python
|
|
71
|
+
cd mcp-run-python && npm run prepare
|
|
72
|
+
uv run --package mcp-run-python pytest mcp-run-python -v
|
|
73
|
+
|
|
65
74
|
.PHONY: update-examples
|
|
66
75
|
update-examples: ## Update documentation examples
|
|
67
76
|
uv run -m pytest --update-examples tests/test_examples.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.42
|
|
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,mistral,openai,vertexai]==0.0.
|
|
31
|
+
Requires-Dist: pydantic-ai-slim[anthropic,bedrock,cli,cohere,groq,mcp,mistral,openai,vertexai]==0.0.42
|
|
32
32
|
Provides-Extra: examples
|
|
33
|
-
Requires-Dist: pydantic-ai-examples==0.0.
|
|
33
|
+
Requires-Dist: pydantic-ai-examples==0.0.42; 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.42"
|
|
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]==0.0.
|
|
39
|
+
"pydantic-ai-slim[openai,vertexai,groq,anthropic,mistral,cohere,bedrock,cli,mcp]==0.0.42",
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[project.urls]
|
|
@@ -46,16 +46,17 @@ 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.42"]
|
|
50
50
|
logfire = ["logfire>=2.3"]
|
|
51
51
|
|
|
52
52
|
[tool.uv.sources]
|
|
53
53
|
pydantic-ai-slim = { workspace = true }
|
|
54
54
|
pydantic-graph = { workspace = true }
|
|
55
55
|
pydantic-ai-examples = { workspace = true }
|
|
56
|
+
mcp-run-python = { workspace = true }
|
|
56
57
|
|
|
57
58
|
[tool.uv.workspace]
|
|
58
|
-
members = ["pydantic_ai_slim", "pydantic_graph", "examples"]
|
|
59
|
+
members = ["pydantic_ai_slim", "pydantic_graph", "examples", "mcp-run-python"]
|
|
59
60
|
|
|
60
61
|
[dependency-groups]
|
|
61
62
|
# dev dependencies are defined in `pydantic-ai-slim/pyproject.toml` to allow for minimal testing
|
|
@@ -82,6 +83,7 @@ line-length = 120
|
|
|
82
83
|
target-version = "py39"
|
|
83
84
|
include = [
|
|
84
85
|
"pydantic_ai_slim/**/*.py",
|
|
86
|
+
"mcp-run-python/**/*.py",
|
|
85
87
|
"pydantic_graph/**/*.py",
|
|
86
88
|
"examples/**/*.py",
|
|
87
89
|
"tests/**/*.py",
|
|
@@ -116,18 +118,22 @@ quote-style = "single"
|
|
|
116
118
|
"tests/**/*.py" = ["D"]
|
|
117
119
|
"docs/**/*.py" = ["D"]
|
|
118
120
|
"examples/**/*.py" = ["D101", "D103"]
|
|
121
|
+
"mcp-run-python/**/*.py" = ["D", "TID251"]
|
|
119
122
|
|
|
120
123
|
[tool.pyright]
|
|
124
|
+
pythonVersion = "3.12"
|
|
121
125
|
typeCheckingMode = "strict"
|
|
122
126
|
reportMissingTypeStubs = false
|
|
123
127
|
reportUnnecessaryIsInstance = false
|
|
124
128
|
reportUnnecessaryTypeIgnoreComment = true
|
|
125
|
-
|
|
129
|
+
reportMissingModuleSource = false
|
|
130
|
+
include = ["pydantic_ai_slim", "mcp-run-python", "pydantic_graph", "tests", "examples"]
|
|
126
131
|
venvPath = ".venv"
|
|
127
132
|
# see https://github.com/microsoft/pyright/issues/7771 - we don't want to error on decorated functions in tests
|
|
128
133
|
# which are not otherwise used
|
|
129
134
|
executionEnvironments = [{ root = "tests", reportUnusedFunction = false }]
|
|
130
|
-
exclude = ["examples/pydantic_ai_examples/weather_agent_gradio.py"]
|
|
135
|
+
exclude = ["examples/pydantic_ai_examples/weather_agent_gradio.py", "mcp-run-python/node_modules"]
|
|
136
|
+
extraPaths = ["mcp-run-python/stubs"]
|
|
131
137
|
|
|
132
138
|
[tool.mypy]
|
|
133
139
|
files = "tests/typed_agent.py,tests/typed_graph.py"
|
|
@@ -139,7 +145,11 @@ xfail_strict = true
|
|
|
139
145
|
filterwarnings = [
|
|
140
146
|
"error",
|
|
141
147
|
# boto3
|
|
142
|
-
"ignore::DeprecationWarning:botocore.*"
|
|
148
|
+
"ignore::DeprecationWarning:botocore.*",
|
|
149
|
+
"ignore::RuntimeWarning:pydantic_ai.mcp",
|
|
150
|
+
# uvicorn (mcp server)
|
|
151
|
+
"ignore:websockets.legacy is deprecated.*:DeprecationWarning:websockets.legacy",
|
|
152
|
+
"ignore:websockets.server.WebSocketServerProtocol is deprecated:DeprecationWarning"
|
|
143
153
|
]
|
|
144
154
|
|
|
145
155
|
# https://coverage.readthedocs.io/en/latest/config.html#run
|
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
- '530'
|
|
12
|
+
content-type:
|
|
13
|
+
- application/json
|
|
14
|
+
host:
|
|
15
|
+
- api.openai.com
|
|
16
|
+
method: POST
|
|
17
|
+
parsed_body:
|
|
18
|
+
messages:
|
|
19
|
+
- content: What is 0 degrees Celsius in Fahrenheit?
|
|
20
|
+
role: user
|
|
21
|
+
model: gpt-4o
|
|
22
|
+
n: 1
|
|
23
|
+
stream: false
|
|
24
|
+
tool_choice: auto
|
|
25
|
+
tools:
|
|
26
|
+
- function:
|
|
27
|
+
description: "Convert Celsius to Fahrenheit.\n\n Args:\n celsius: Temperature in Celsius\n\n Returns:\n
|
|
28
|
+
\ Temperature in Fahrenheit\n "
|
|
29
|
+
name: celsius_to_fahrenheit
|
|
30
|
+
parameters:
|
|
31
|
+
properties:
|
|
32
|
+
celsius:
|
|
33
|
+
title: Celsius
|
|
34
|
+
type: number
|
|
35
|
+
required:
|
|
36
|
+
- celsius
|
|
37
|
+
title: celsius_to_fahrenheitArguments
|
|
38
|
+
type: object
|
|
39
|
+
type: function
|
|
40
|
+
uri: https://api.openai.com/v1/chat/completions
|
|
41
|
+
response:
|
|
42
|
+
headers:
|
|
43
|
+
access-control-expose-headers:
|
|
44
|
+
- X-Request-ID
|
|
45
|
+
alt-svc:
|
|
46
|
+
- h3=":443"; ma=86400
|
|
47
|
+
connection:
|
|
48
|
+
- keep-alive
|
|
49
|
+
content-length:
|
|
50
|
+
- '1085'
|
|
51
|
+
content-type:
|
|
52
|
+
- application/json
|
|
53
|
+
openai-organization:
|
|
54
|
+
- pydantic-28gund
|
|
55
|
+
openai-processing-ms:
|
|
56
|
+
- '594'
|
|
57
|
+
openai-version:
|
|
58
|
+
- '2020-10-01'
|
|
59
|
+
strict-transport-security:
|
|
60
|
+
- max-age=31536000; includeSubDomains; preload
|
|
61
|
+
transfer-encoding:
|
|
62
|
+
- chunked
|
|
63
|
+
parsed_body:
|
|
64
|
+
choices:
|
|
65
|
+
- finish_reason: tool_calls
|
|
66
|
+
index: 0
|
|
67
|
+
logprobs: null
|
|
68
|
+
message:
|
|
69
|
+
annotations: []
|
|
70
|
+
content: null
|
|
71
|
+
refusal: null
|
|
72
|
+
role: assistant
|
|
73
|
+
tool_calls:
|
|
74
|
+
- function:
|
|
75
|
+
arguments: '{"celsius":0}'
|
|
76
|
+
name: celsius_to_fahrenheit
|
|
77
|
+
id: call_UNesABTXfwIkYdh3HzXWw2wD
|
|
78
|
+
type: function
|
|
79
|
+
created: 1741776872
|
|
80
|
+
id: chatcmpl-BAE1IwTZc7FHM4TkNeBoPylR7rtCT
|
|
81
|
+
model: gpt-4o-2024-08-06
|
|
82
|
+
object: chat.completion
|
|
83
|
+
service_tier: default
|
|
84
|
+
system_fingerprint: fp_eb9dce56a8
|
|
85
|
+
usage:
|
|
86
|
+
completion_tokens: 19
|
|
87
|
+
completion_tokens_details:
|
|
88
|
+
accepted_prediction_tokens: 0
|
|
89
|
+
audio_tokens: 0
|
|
90
|
+
reasoning_tokens: 0
|
|
91
|
+
rejected_prediction_tokens: 0
|
|
92
|
+
prompt_tokens: 82
|
|
93
|
+
prompt_tokens_details:
|
|
94
|
+
audio_tokens: 0
|
|
95
|
+
cached_tokens: 0
|
|
96
|
+
total_tokens: 101
|
|
97
|
+
status:
|
|
98
|
+
code: 200
|
|
99
|
+
message: OK
|
|
100
|
+
- request:
|
|
101
|
+
headers:
|
|
102
|
+
accept:
|
|
103
|
+
- application/json
|
|
104
|
+
accept-encoding:
|
|
105
|
+
- gzip, deflate
|
|
106
|
+
connection:
|
|
107
|
+
- keep-alive
|
|
108
|
+
content-length:
|
|
109
|
+
- '879'
|
|
110
|
+
content-type:
|
|
111
|
+
- application/json
|
|
112
|
+
cookie:
|
|
113
|
+
- __cf_bm=GeO8TCYhlEUIV63eLxM4nKUU2OLlG.f8tMvM9shFTc8-1741776873-1.0.1.1-zxkkWGCAPhJIA05Uwt3Ii3DCg9da6owy45bo_yaZ1YmsoihITJCgZzpA6H4eL0xzFRDWrWkEIQYaFEXLYcrLePwDMsgwNUJbEf6sg1vm2YQ;
|
|
114
|
+
_cfuvid=AI06nwzbBcwVRHXv_BRehX1K7p9oe1qUXFkzXBWEUW0-1741776873043-0.0.1.1-604800000
|
|
115
|
+
host:
|
|
116
|
+
- api.openai.com
|
|
117
|
+
method: POST
|
|
118
|
+
parsed_body:
|
|
119
|
+
messages:
|
|
120
|
+
- content: What is 0 degrees Celsius in Fahrenheit?
|
|
121
|
+
role: user
|
|
122
|
+
- role: assistant
|
|
123
|
+
tool_calls:
|
|
124
|
+
- function:
|
|
125
|
+
arguments: '{"celsius":0}'
|
|
126
|
+
name: celsius_to_fahrenheit
|
|
127
|
+
id: call_UNesABTXfwIkYdh3HzXWw2wD
|
|
128
|
+
type: function
|
|
129
|
+
- content: '{"meta":null,"content":[{"type":"text","text":"32.0","annotations":null}],"isError":false}'
|
|
130
|
+
role: tool
|
|
131
|
+
tool_call_id: call_UNesABTXfwIkYdh3HzXWw2wD
|
|
132
|
+
model: gpt-4o
|
|
133
|
+
n: 1
|
|
134
|
+
stream: false
|
|
135
|
+
tool_choice: auto
|
|
136
|
+
tools:
|
|
137
|
+
- function:
|
|
138
|
+
description: "Convert Celsius to Fahrenheit.\n\n Args:\n celsius: Temperature in Celsius\n\n Returns:\n
|
|
139
|
+
\ Temperature in Fahrenheit\n "
|
|
140
|
+
name: celsius_to_fahrenheit
|
|
141
|
+
parameters:
|
|
142
|
+
properties:
|
|
143
|
+
celsius:
|
|
144
|
+
title: Celsius
|
|
145
|
+
type: number
|
|
146
|
+
required:
|
|
147
|
+
- celsius
|
|
148
|
+
title: celsius_to_fahrenheitArguments
|
|
149
|
+
type: object
|
|
150
|
+
type: function
|
|
151
|
+
uri: https://api.openai.com/v1/chat/completions
|
|
152
|
+
response:
|
|
153
|
+
headers:
|
|
154
|
+
access-control-expose-headers:
|
|
155
|
+
- X-Request-ID
|
|
156
|
+
alt-svc:
|
|
157
|
+
- h3=":443"; ma=86400
|
|
158
|
+
connection:
|
|
159
|
+
- keep-alive
|
|
160
|
+
content-length:
|
|
161
|
+
- '849'
|
|
162
|
+
content-type:
|
|
163
|
+
- application/json
|
|
164
|
+
openai-organization:
|
|
165
|
+
- pydantic-28gund
|
|
166
|
+
openai-processing-ms:
|
|
167
|
+
- '415'
|
|
168
|
+
openai-version:
|
|
169
|
+
- '2020-10-01'
|
|
170
|
+
strict-transport-security:
|
|
171
|
+
- max-age=31536000; includeSubDomains; preload
|
|
172
|
+
transfer-encoding:
|
|
173
|
+
- chunked
|
|
174
|
+
parsed_body:
|
|
175
|
+
choices:
|
|
176
|
+
- finish_reason: stop
|
|
177
|
+
index: 0
|
|
178
|
+
logprobs: null
|
|
179
|
+
message:
|
|
180
|
+
annotations: []
|
|
181
|
+
content: 0 degrees Celsius is 32.0 degrees Fahrenheit.
|
|
182
|
+
refusal: null
|
|
183
|
+
role: assistant
|
|
184
|
+
created: 1741776873
|
|
185
|
+
id: chatcmpl-BAE1Jy3AN974xW1pziTxd6wrxliCE
|
|
186
|
+
model: gpt-4o-2024-08-06
|
|
187
|
+
object: chat.completion
|
|
188
|
+
service_tier: default
|
|
189
|
+
system_fingerprint: fp_eb9dce56a8
|
|
190
|
+
usage:
|
|
191
|
+
completion_tokens: 13
|
|
192
|
+
completion_tokens_details:
|
|
193
|
+
accepted_prediction_tokens: 0
|
|
194
|
+
audio_tokens: 0
|
|
195
|
+
reasoning_tokens: 0
|
|
196
|
+
rejected_prediction_tokens: 0
|
|
197
|
+
prompt_tokens: 139
|
|
198
|
+
prompt_tokens_details:
|
|
199
|
+
audio_tokens: 0
|
|
200
|
+
cached_tokens: 0
|
|
201
|
+
total_tokens: 152
|
|
202
|
+
status:
|
|
203
|
+
code: 200
|
|
204
|
+
message: OK
|
|
205
|
+
version: 1
|
|
@@ -393,7 +393,7 @@ async def test_iter_next_error(mock_snapshot_id: object):
|
|
|
393
393
|
|
|
394
394
|
assert isinstance(n, BaseNode)
|
|
395
395
|
n = await run.next()
|
|
396
|
-
assert n == snapshot(End(None))
|
|
396
|
+
assert n == snapshot(End(data=None))
|
|
397
397
|
|
|
398
398
|
with pytest.raises(TypeError, match=r'`next` must be called with a `BaseNode` instance, got End\(data=None\).'):
|
|
399
399
|
await run.next()
|
|
@@ -287,7 +287,7 @@ async def test_rerun_node(mock_snapshot_id: object):
|
|
|
287
287
|
node = Foo()
|
|
288
288
|
async with graph.iter(node, persistence=sp) as run:
|
|
289
289
|
end = await run.next()
|
|
290
|
-
assert end == snapshot(End(123))
|
|
290
|
+
assert end == snapshot(End(data=123))
|
|
291
291
|
|
|
292
292
|
msg = "Incorrect snapshot status 'success', must be 'created' or 'pending'."
|
|
293
293
|
with pytest.raises(GraphNodeStatusError, match=msg):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from mcp.server.fastmcp import FastMCP
|
|
2
|
+
|
|
3
|
+
mcp = FastMCP('PydanticAI MCP Server')
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@mcp.tool()
|
|
7
|
+
async def celsius_to_fahrenheit(celsius: float) -> float:
|
|
8
|
+
"""Convert Celsius to Fahrenheit.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
celsius: Temperature in Celsius
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Temperature in Fahrenheit
|
|
15
|
+
"""
|
|
16
|
+
return (celsius * 9 / 5) + 32
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
mcp.run()
|
|
@@ -59,7 +59,7 @@ def find_filter_examples() -> Iterable[CodeExample]:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
@pytest.mark.parametrize('example', find_filter_examples(), ids=str)
|
|
62
|
-
def test_docs_examples(
|
|
62
|
+
def test_docs_examples( # noqa: C901
|
|
63
63
|
example: CodeExample,
|
|
64
64
|
eval_example: EvalExample,
|
|
65
65
|
mocker: MockerFixture,
|
|
@@ -78,6 +78,10 @@ def test_docs_examples(
|
|
|
78
78
|
mocker.patch('random.randint', return_value=4)
|
|
79
79
|
mocker.patch('rich.prompt.Prompt.ask', side_effect=rich_prompt_ask)
|
|
80
80
|
|
|
81
|
+
if sys.version_info >= (3, 10):
|
|
82
|
+
mocker.patch('pydantic_ai.mcp.MCPServerHTTP', return_value=MockMCPServer())
|
|
83
|
+
mocker.patch('mcp.server.fastmcp.FastMCP')
|
|
84
|
+
|
|
81
85
|
env.set('OPENAI_API_KEY', 'testing')
|
|
82
86
|
env.set('GEMINI_API_KEY', 'testing')
|
|
83
87
|
env.set('GROQ_API_KEY', 'testing')
|
|
@@ -136,10 +140,13 @@ def test_docs_examples(
|
|
|
136
140
|
if opt_test.startswith('skip'):
|
|
137
141
|
print(opt_test[4:].lstrip(' -') or 'running code skipped')
|
|
138
142
|
else:
|
|
143
|
+
test_globals: dict[str, str] = {}
|
|
144
|
+
if opt_title == 'mcp_client.py':
|
|
145
|
+
test_globals['__name__'] = '__test__'
|
|
139
146
|
if eval_example.update_examples: # pragma: no cover
|
|
140
|
-
module_dict = eval_example.run_print_update(example, call=call_name)
|
|
147
|
+
module_dict = eval_example.run_print_update(example, call=call_name, module_globals=test_globals)
|
|
141
148
|
else:
|
|
142
|
-
module_dict = eval_example.run_print_check(example, call=call_name)
|
|
149
|
+
module_dict = eval_example.run_print_check(example, call=call_name, module_globals=test_globals)
|
|
143
150
|
|
|
144
151
|
os.chdir(cwd)
|
|
145
152
|
if title := opt_title:
|
|
@@ -182,7 +189,22 @@ def rich_prompt_ask(prompt: str, *_args: Any, **_kwargs: Any) -> str:
|
|
|
182
189
|
raise ValueError(f'Unexpected prompt: {prompt}')
|
|
183
190
|
|
|
184
191
|
|
|
192
|
+
class MockMCPServer:
|
|
193
|
+
is_running = True
|
|
194
|
+
|
|
195
|
+
async def __aenter__(self) -> MockMCPServer:
|
|
196
|
+
return self
|
|
197
|
+
|
|
198
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
async def list_tools() -> list[None]:
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
|
|
185
206
|
text_responses: dict[str, str | ToolCallPart] = {
|
|
207
|
+
'How many days between 2000-01-01 and 2025-03-18?': 'There are 9,208 days between January 1, 2000, and March 18, 2025.',
|
|
186
208
|
'What is the weather like in West London and in Wiltshire?': (
|
|
187
209
|
'The weather in West London is raining, while in Wiltshire it is sunny.'
|
|
188
210
|
),
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Tests for the MCP (Model Context Protocol) server implementation."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from dirty_equals import IsInstance
|
|
5
|
+
from inline_snapshot import snapshot
|
|
6
|
+
|
|
7
|
+
from pydantic_ai.agent import Agent
|
|
8
|
+
from pydantic_ai.exceptions import UserError
|
|
9
|
+
from pydantic_ai.messages import ModelRequest, ModelResponse, TextPart, ToolCallPart, ToolReturnPart, UserPromptPart
|
|
10
|
+
|
|
11
|
+
from .conftest import IsDatetime, try_import
|
|
12
|
+
|
|
13
|
+
with try_import() as imports_successful:
|
|
14
|
+
from mcp.types import CallToolResult, TextContent
|
|
15
|
+
|
|
16
|
+
from pydantic_ai.mcp import MCPServerHTTP, MCPServerStdio
|
|
17
|
+
from pydantic_ai.models.openai import OpenAIModel
|
|
18
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
pytestmark = [
|
|
22
|
+
pytest.mark.skipif(not imports_successful(), reason='mcp and openai not installed'),
|
|
23
|
+
pytest.mark.anyio,
|
|
24
|
+
pytest.mark.vcr,
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def test_stdio_server():
|
|
29
|
+
server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
|
|
30
|
+
async with server:
|
|
31
|
+
tools = await server.list_tools()
|
|
32
|
+
assert len(tools) == 1
|
|
33
|
+
assert tools[0].name == 'celsius_to_fahrenheit'
|
|
34
|
+
assert tools[0].description.startswith('Convert Celsius to Fahrenheit.')
|
|
35
|
+
|
|
36
|
+
# Test calling the temperature conversion tool
|
|
37
|
+
result = await server.call_tool('celsius_to_fahrenheit', {'celsius': 0})
|
|
38
|
+
assert result.content == snapshot([TextContent(type='text', text='32.0')])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_sse_server():
|
|
42
|
+
sse_server = MCPServerHTTP(url='http://localhost:8000/sse')
|
|
43
|
+
assert sse_server.url == 'http://localhost:8000/sse'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def test_agent_with_stdio_server(allow_model_requests: None, openai_api_key: str):
|
|
47
|
+
server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
|
|
48
|
+
model = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
49
|
+
agent = Agent(model, mcp_servers=[server])
|
|
50
|
+
async with agent.run_mcp_servers():
|
|
51
|
+
result = await agent.run('What is 0 degrees Celsius in Fahrenheit?')
|
|
52
|
+
assert result.data == snapshot('0 degrees Celsius is 32.0 degrees Fahrenheit.')
|
|
53
|
+
assert result.all_messages() == snapshot(
|
|
54
|
+
[
|
|
55
|
+
ModelRequest(
|
|
56
|
+
parts=[UserPromptPart(content='What is 0 degrees Celsius in Fahrenheit?', timestamp=IsDatetime())]
|
|
57
|
+
),
|
|
58
|
+
ModelResponse(
|
|
59
|
+
parts=[
|
|
60
|
+
ToolCallPart(
|
|
61
|
+
tool_name='celsius_to_fahrenheit',
|
|
62
|
+
args='{"celsius":0}',
|
|
63
|
+
tool_call_id='call_UNesABTXfwIkYdh3HzXWw2wD',
|
|
64
|
+
)
|
|
65
|
+
],
|
|
66
|
+
model_name='gpt-4o-2024-08-06',
|
|
67
|
+
timestamp=IsDatetime(),
|
|
68
|
+
),
|
|
69
|
+
ModelRequest(
|
|
70
|
+
parts=[
|
|
71
|
+
ToolReturnPart(
|
|
72
|
+
tool_name='celsius_to_fahrenheit',
|
|
73
|
+
content=IsInstance(CallToolResult),
|
|
74
|
+
tool_call_id='call_UNesABTXfwIkYdh3HzXWw2wD',
|
|
75
|
+
timestamp=IsDatetime(),
|
|
76
|
+
)
|
|
77
|
+
]
|
|
78
|
+
),
|
|
79
|
+
ModelResponse(
|
|
80
|
+
parts=[TextPart(content='0 degrees Celsius is 32.0 degrees Fahrenheit.')],
|
|
81
|
+
model_name='gpt-4o-2024-08-06',
|
|
82
|
+
timestamp=IsDatetime(),
|
|
83
|
+
),
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def test_agent_with_server_not_running(openai_api_key: str):
|
|
89
|
+
server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
|
|
90
|
+
model = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
91
|
+
agent = Agent(model, mcp_servers=[server])
|
|
92
|
+
with pytest.raises(UserError, match='MCP server is not running'):
|
|
93
|
+
await agent.run('What is 0 degrees Celsius in Fahrenheit?')
|
|
@@ -6,8 +6,9 @@ import pydantic_core
|
|
|
6
6
|
import pytest
|
|
7
7
|
from _pytest.logging import LogCaptureFixture
|
|
8
8
|
from inline_snapshot import snapshot
|
|
9
|
-
from pydantic import BaseModel, Field
|
|
10
|
-
from
|
|
9
|
+
from pydantic import BaseModel, Field, WithJsonSchema
|
|
10
|
+
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
|
|
11
|
+
from pydantic_core import PydanticSerializationError, core_schema
|
|
11
12
|
|
|
12
13
|
from pydantic_ai import Agent, RunContext, Tool, UserError
|
|
13
14
|
from pydantic_ai.messages import (
|
|
@@ -100,8 +101,8 @@ def test_docstring_google(docstring_format: Literal['google', 'auto']):
|
|
|
100
101
|
'description': 'Do foobar stuff, a lot.',
|
|
101
102
|
'parameters_json_schema': {
|
|
102
103
|
'properties': {
|
|
103
|
-
'foo': {'description': 'The foo thing.', '
|
|
104
|
-
'bar': {'description': 'The bar thing.', '
|
|
104
|
+
'foo': {'description': 'The foo thing.', 'type': 'integer'},
|
|
105
|
+
'bar': {'description': 'The bar thing.', 'type': 'string'},
|
|
105
106
|
},
|
|
106
107
|
'required': ['foo', 'bar'],
|
|
107
108
|
'type': 'object',
|
|
@@ -138,7 +139,7 @@ def test_docstring_sphinx(docstring_format: Literal['sphinx', 'auto']):
|
|
|
138
139
|
'name': 'sphinx_style_docstring',
|
|
139
140
|
'description': 'Sphinx style docstring.',
|
|
140
141
|
'parameters_json_schema': {
|
|
141
|
-
'properties': {'foo': {'description': 'The foo thing.', '
|
|
142
|
+
'properties': {'foo': {'description': 'The foo thing.', 'type': 'integer'}},
|
|
142
143
|
'required': ['foo'],
|
|
143
144
|
'type': 'object',
|
|
144
145
|
'additionalProperties': False,
|
|
@@ -174,8 +175,8 @@ def test_docstring_numpy(docstring_format: Literal['numpy', 'auto']):
|
|
|
174
175
|
'description': 'Numpy style docstring.',
|
|
175
176
|
'parameters_json_schema': {
|
|
176
177
|
'properties': {
|
|
177
|
-
'foo': {'description': 'The foo thing.', '
|
|
178
|
-
'bar': {'description': 'The bar thing.', '
|
|
178
|
+
'foo': {'description': 'The foo thing.', 'type': 'integer'},
|
|
179
|
+
'bar': {'description': 'The bar thing.', 'type': 'string'},
|
|
179
180
|
},
|
|
180
181
|
'required': ['foo', 'bar'],
|
|
181
182
|
'type': 'object',
|
|
@@ -234,8 +235,8 @@ def test_docstring_google_no_body(docstring_format: Literal['google', 'auto']):
|
|
|
234
235
|
'description': '',
|
|
235
236
|
'parameters_json_schema': {
|
|
236
237
|
'properties': {
|
|
237
|
-
'foo': {'description': 'The foo thing.', '
|
|
238
|
-
'bar': {'description': 'from fields', '
|
|
238
|
+
'foo': {'description': 'The foo thing.', 'type': 'integer'},
|
|
239
|
+
'bar': {'description': 'from fields', 'type': 'string'},
|
|
239
240
|
},
|
|
240
241
|
'required': ['foo', 'bar'],
|
|
241
242
|
'type': 'object',
|
|
@@ -266,8 +267,8 @@ def test_takes_just_model():
|
|
|
266
267
|
'description': None,
|
|
267
268
|
'parameters_json_schema': {
|
|
268
269
|
'properties': {
|
|
269
|
-
'x': {'
|
|
270
|
-
'y': {'
|
|
270
|
+
'x': {'type': 'integer'},
|
|
271
|
+
'y': {'type': 'string'},
|
|
271
272
|
},
|
|
272
273
|
'required': ['x', 'y'],
|
|
273
274
|
'title': 'Foo',
|
|
@@ -298,8 +299,8 @@ def test_takes_model_and_int():
|
|
|
298
299
|
'$defs': {
|
|
299
300
|
'Foo': {
|
|
300
301
|
'properties': {
|
|
301
|
-
'x': {'
|
|
302
|
-
'y': {'
|
|
302
|
+
'x': {'type': 'integer'},
|
|
303
|
+
'y': {'type': 'string'},
|
|
303
304
|
},
|
|
304
305
|
'required': ['x', 'y'],
|
|
305
306
|
'title': 'Foo',
|
|
@@ -308,7 +309,7 @@ def test_takes_model_and_int():
|
|
|
308
309
|
},
|
|
309
310
|
'properties': {
|
|
310
311
|
'model': {'$ref': '#/$defs/Foo'},
|
|
311
|
-
'z': {'
|
|
312
|
+
'z': {'type': 'integer'},
|
|
312
313
|
},
|
|
313
314
|
'required': ['model', 'z'],
|
|
314
315
|
'type': 'object',
|
|
@@ -645,7 +646,7 @@ def test_json_schema_required_parameters(set_event_loop: None):
|
|
|
645
646
|
'outer_typed_dict_key': None,
|
|
646
647
|
'parameters_json_schema': {
|
|
647
648
|
'additionalProperties': False,
|
|
648
|
-
'properties': {'a': {'
|
|
649
|
+
'properties': {'a': {'type': 'integer'}, 'b': {'type': 'integer'}},
|
|
649
650
|
'required': ['a'],
|
|
650
651
|
'type': 'object',
|
|
651
652
|
},
|
|
@@ -656,7 +657,7 @@ def test_json_schema_required_parameters(set_event_loop: None):
|
|
|
656
657
|
'outer_typed_dict_key': None,
|
|
657
658
|
'parameters_json_schema': {
|
|
658
659
|
'additionalProperties': False,
|
|
659
|
-
'properties': {'a': {'
|
|
660
|
+
'properties': {'a': {'type': 'integer'}, 'b': {'type': 'integer'}},
|
|
660
661
|
'required': ['b'],
|
|
661
662
|
'type': 'object',
|
|
662
663
|
},
|
|
@@ -706,3 +707,47 @@ def test_call_tool_without_unrequired_parameters(set_event_loop: None):
|
|
|
706
707
|
]
|
|
707
708
|
)
|
|
708
709
|
assert tool_returns == snapshot([15, 17, 51, 68])
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def test_schema_generator():
|
|
713
|
+
class MyGenerateJsonSchema(GenerateJsonSchema):
|
|
714
|
+
def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
|
|
715
|
+
# Add useless property titles just to show we can
|
|
716
|
+
s = super().typed_dict_schema(schema)
|
|
717
|
+
for p in s.get('properties', {}):
|
|
718
|
+
s['properties'][p]['title'] = f'{s["properties"][p].get("title")} title'
|
|
719
|
+
return s
|
|
720
|
+
|
|
721
|
+
agent = Agent(FunctionModel(get_json_schema))
|
|
722
|
+
|
|
723
|
+
def my_tool(x: Annotated[Union[str, None], WithJsonSchema({'type': 'string'})] = None, **kwargs: Any):
|
|
724
|
+
return x # pragma: no cover
|
|
725
|
+
|
|
726
|
+
agent.tool_plain(name='my_tool_1')(my_tool)
|
|
727
|
+
agent.tool_plain(name='my_tool_2', schema_generator=MyGenerateJsonSchema)(my_tool)
|
|
728
|
+
|
|
729
|
+
result = agent.run_sync('Hello')
|
|
730
|
+
json_schema = json.loads(result.data)
|
|
731
|
+
assert json_schema == snapshot(
|
|
732
|
+
[
|
|
733
|
+
{
|
|
734
|
+
'description': '',
|
|
735
|
+
'name': 'my_tool_1',
|
|
736
|
+
'outer_typed_dict_key': None,
|
|
737
|
+
'parameters_json_schema': {
|
|
738
|
+
'additionalProperties': True,
|
|
739
|
+
'properties': {'x': {'type': 'string'}},
|
|
740
|
+
'type': 'object',
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
'description': '',
|
|
745
|
+
'name': 'my_tool_2',
|
|
746
|
+
'outer_typed_dict_key': None,
|
|
747
|
+
'parameters_json_schema': {
|
|
748
|
+
'properties': {'x': {'type': 'string', 'title': 'X title'}},
|
|
749
|
+
'type': 'object',
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
]
|
|
753
|
+
)
|
|
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
|
{pydantic_ai-0.0.41 → pydantic_ai-0.0.42}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|