pydantic-ai-slim 1.4.0__tar.gz → 1.6.0__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.
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/PKG-INFO +5 -3
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_agent_graph.py +23 -12
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_output.py +5 -1
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_utils.py +8 -8
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/__init__.py +3 -7
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/__init__.py +11 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_function_toolset.py +2 -1
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/exceptions.py +1 -1
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/messages.py +1 -1
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/openai.py +2 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/test.py +6 -3
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/openai.py +7 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google.py +31 -2
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/run.py +40 -24
- pydantic_ai_slim-1.6.0/pydantic_ai/toolsets/fastmcp.py +215 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pyproject.toml +2 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/.gitignore +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/LICENSE +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/README.md +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/__main__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_a2a.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_cli.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_function_schema.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_griffe.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_instrumentation.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_json_schema.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_mcp.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_otel_messages.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_parts_manager.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_run_context.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_system_prompt.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_thinking_part.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_tool_manager.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ag_ui.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/abstract.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/wrapper.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/builtin_tools.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/tavily.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/direct.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_model.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_types.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_model.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/aci.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/langchain.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/format_prompt.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/mcp.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/anthropic.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/bedrock.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/cohere.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/fallback.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/function.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/gemini.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/google.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/groq.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/huggingface.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/instrumented.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/mcp_sampling.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/mistral.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/wrapper.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/output.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/amazon.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/anthropic.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/cohere.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/deepseek.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/google.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/grok.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/groq.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/harmony.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/meta.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/mistral.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/moonshotai.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/qwen.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/anthropic.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/azure.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/bedrock.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/cerebras.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/cohere.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/deepseek.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/fireworks.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/gateway.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/github.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google_gla.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google_vertex.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/grok.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/groq.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/heroku.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/huggingface.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/litellm.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/mistral.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/moonshotai.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/nebius.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/ollama.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/openai.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/openrouter.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/ovhcloud.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/together.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/vercel.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/py.typed +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/result.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/retries.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/settings.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/tools.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/__init__.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/_dynamic.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/abstract.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/approval_required.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/combined.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/external.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/filtered.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/function.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/prefixed.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/prepared.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/renamed.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/wrapper.py +0 -0
- {pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Project-URL: Homepage, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
|
|
6
6
|
Project-URL: Source, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
|
|
@@ -33,7 +33,7 @@ Requires-Dist: genai-prices>=0.0.35
|
|
|
33
33
|
Requires-Dist: griffe>=1.3.2
|
|
34
34
|
Requires-Dist: httpx>=0.27
|
|
35
35
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
36
|
-
Requires-Dist: pydantic-graph==1.
|
|
36
|
+
Requires-Dist: pydantic-graph==1.6.0
|
|
37
37
|
Requires-Dist: pydantic>=2.10
|
|
38
38
|
Requires-Dist: typing-inspection>=0.4.0
|
|
39
39
|
Provides-Extra: a2a
|
|
@@ -57,7 +57,9 @@ Requires-Dist: dbos>=1.14.0; extra == 'dbos'
|
|
|
57
57
|
Provides-Extra: duckduckgo
|
|
58
58
|
Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
|
|
59
59
|
Provides-Extra: evals
|
|
60
|
-
Requires-Dist: pydantic-evals==1.
|
|
60
|
+
Requires-Dist: pydantic-evals==1.6.0; extra == 'evals'
|
|
61
|
+
Provides-Extra: fastmcp
|
|
62
|
+
Requires-Dist: fastmcp>=2.12.0; extra == 'fastmcp'
|
|
61
63
|
Provides-Extra: google
|
|
62
64
|
Requires-Dist: google-genai>=1.46.0; extra == 'google'
|
|
63
65
|
Provides-Extra: groq
|
|
@@ -20,7 +20,8 @@ from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION
|
|
|
20
20
|
from pydantic_ai._tool_manager import ToolManager
|
|
21
21
|
from pydantic_ai._utils import dataclasses_no_defaults_repr, get_union_args, is_async_callable, run_in_executor
|
|
22
22
|
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
23
|
-
from pydantic_graph import BaseNode,
|
|
23
|
+
from pydantic_graph import BaseNode, GraphRunContext
|
|
24
|
+
from pydantic_graph.beta import Graph, GraphBuilder
|
|
24
25
|
from pydantic_graph.nodes import End, NodeRunEndT
|
|
25
26
|
|
|
26
27
|
from . import _output, _system_prompt, exceptions, messages as _messages, models, result, usage as _usage
|
|
@@ -1162,22 +1163,32 @@ def build_agent_graph(
|
|
|
1162
1163
|
name: str | None,
|
|
1163
1164
|
deps_type: type[DepsT],
|
|
1164
1165
|
output_type: OutputSpec[OutputT],
|
|
1165
|
-
) -> Graph[
|
|
1166
|
+
) -> Graph[
|
|
1167
|
+
GraphAgentState,
|
|
1168
|
+
GraphAgentDeps[DepsT, OutputT],
|
|
1169
|
+
UserPromptNode[DepsT, OutputT],
|
|
1170
|
+
result.FinalResult[OutputT],
|
|
1171
|
+
]:
|
|
1166
1172
|
"""Build the execution [Graph][pydantic_graph.Graph] for a given agent."""
|
|
1167
|
-
|
|
1168
|
-
UserPromptNode[DepsT],
|
|
1169
|
-
ModelRequestNode[DepsT],
|
|
1170
|
-
CallToolsNode[DepsT],
|
|
1171
|
-
SetFinalResult[DepsT],
|
|
1172
|
-
)
|
|
1173
|
-
graph = Graph[GraphAgentState, GraphAgentDeps[DepsT, Any], result.FinalResult[OutputT]](
|
|
1174
|
-
nodes=nodes,
|
|
1173
|
+
g = GraphBuilder(
|
|
1175
1174
|
name=name or 'Agent',
|
|
1176
1175
|
state_type=GraphAgentState,
|
|
1177
|
-
|
|
1176
|
+
deps_type=GraphAgentDeps[DepsT, OutputT],
|
|
1177
|
+
input_type=UserPromptNode[DepsT, OutputT],
|
|
1178
|
+
output_type=result.FinalResult[OutputT],
|
|
1178
1179
|
auto_instrument=False,
|
|
1179
1180
|
)
|
|
1180
|
-
|
|
1181
|
+
|
|
1182
|
+
g.add(
|
|
1183
|
+
g.edge_from(g.start_node).to(UserPromptNode[DepsT, OutputT]),
|
|
1184
|
+
g.node(UserPromptNode[DepsT, OutputT]),
|
|
1185
|
+
g.node(ModelRequestNode[DepsT, OutputT]),
|
|
1186
|
+
g.node(CallToolsNode[DepsT, OutputT]),
|
|
1187
|
+
g.node(
|
|
1188
|
+
SetFinalResult[DepsT, OutputT],
|
|
1189
|
+
),
|
|
1190
|
+
)
|
|
1191
|
+
return g.build(validate_graph_structure=False)
|
|
1181
1192
|
|
|
1182
1193
|
|
|
1183
1194
|
async def _process_message_history(
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations as _annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
|
+
import re
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
6
7
|
from collections.abc import Awaitable, Callable, Sequence
|
|
7
8
|
from dataclasses import dataclass, field
|
|
@@ -70,6 +71,7 @@ Usage `OutputValidatorFunc[AgentDepsT, T]`.
|
|
|
70
71
|
|
|
71
72
|
DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
|
|
72
73
|
DEFAULT_OUTPUT_TOOL_DESCRIPTION = 'The final response which ends this conversation'
|
|
74
|
+
OUTPUT_TOOL_NAME_SANITIZER = re.compile(r'[^a-zA-Z0-9-_]')
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
async def execute_traced_output_function(
|
|
@@ -997,7 +999,9 @@ class OutputToolset(AbstractToolset[AgentDepsT]):
|
|
|
997
999
|
if name is None:
|
|
998
1000
|
name = default_name
|
|
999
1001
|
if multiple:
|
|
1000
|
-
|
|
1002
|
+
# strip unsupported characters like "[" and "]" from generic class names
|
|
1003
|
+
safe_name = OUTPUT_TOOL_NAME_SANITIZER.sub('', object_def.name or '')
|
|
1004
|
+
name += f'_{safe_name}'
|
|
1001
1005
|
|
|
1002
1006
|
i = 1
|
|
1003
1007
|
original_name = name
|
|
@@ -147,7 +147,7 @@ async def group_by_temporal(
|
|
|
147
147
|
aiterable: The async iterable to group.
|
|
148
148
|
soft_max_interval: Maximum interval over which to group items, this should avoid a trickle of items causing
|
|
149
149
|
a group to never be yielded. It's a soft max in the sense that once we're over this time, we yield items
|
|
150
|
-
as soon as `aiter
|
|
150
|
+
as soon as `anext(aiter)` returns. If `None`, no grouping/debouncing is performed
|
|
151
151
|
|
|
152
152
|
Returns:
|
|
153
153
|
A context manager usable as an async iterable of lists of items produced by the input async iterable.
|
|
@@ -171,7 +171,7 @@ async def group_by_temporal(
|
|
|
171
171
|
buffer: list[T] = []
|
|
172
172
|
group_start_time = time.monotonic()
|
|
173
173
|
|
|
174
|
-
aiterator = aiterable
|
|
174
|
+
aiterator = aiter(aiterable)
|
|
175
175
|
while True:
|
|
176
176
|
if group_start_time is None:
|
|
177
177
|
# group hasn't started, we just wait for the maximum interval
|
|
@@ -182,9 +182,9 @@ async def group_by_temporal(
|
|
|
182
182
|
|
|
183
183
|
# if there's no current task, we get the next one
|
|
184
184
|
if task is None:
|
|
185
|
-
# aiter
|
|
185
|
+
# anext(aiter) returns an Awaitable[T], not a Coroutine which asyncio.create_task expects
|
|
186
186
|
# so far, this doesn't seem to be a problem
|
|
187
|
-
task = asyncio.create_task(aiterator
|
|
187
|
+
task = asyncio.create_task(anext(aiterator)) # pyright: ignore[reportArgumentType]
|
|
188
188
|
|
|
189
189
|
# we use asyncio.wait to avoid cancelling the coroutine if it's not done
|
|
190
190
|
done, _ = await asyncio.wait((task,), timeout=wait_time)
|
|
@@ -284,10 +284,10 @@ class PeekableAsyncStream(Generic[T]):
|
|
|
284
284
|
|
|
285
285
|
# Otherwise, we need to fetch the next item from the underlying iterator.
|
|
286
286
|
if self._source_iter is None:
|
|
287
|
-
self._source_iter = self._source
|
|
287
|
+
self._source_iter = aiter(self._source)
|
|
288
288
|
|
|
289
289
|
try:
|
|
290
|
-
self._buffer = await self._source_iter
|
|
290
|
+
self._buffer = await anext(self._source_iter)
|
|
291
291
|
except StopAsyncIteration:
|
|
292
292
|
self._exhausted = True
|
|
293
293
|
return UNSET
|
|
@@ -318,10 +318,10 @@ class PeekableAsyncStream(Generic[T]):
|
|
|
318
318
|
|
|
319
319
|
# Otherwise, fetch the next item from the source.
|
|
320
320
|
if self._source_iter is None:
|
|
321
|
-
self._source_iter = self._source
|
|
321
|
+
self._source_iter = aiter(self._source)
|
|
322
322
|
|
|
323
323
|
try:
|
|
324
|
-
return await self._source_iter
|
|
324
|
+
return await anext(self._source_iter)
|
|
325
325
|
except StopAsyncIteration:
|
|
326
326
|
self._exhausted = True
|
|
327
327
|
raise
|
|
@@ -15,7 +15,6 @@ from pydantic.json_schema import GenerateJsonSchema
|
|
|
15
15
|
from typing_extensions import Self, TypeVar, deprecated
|
|
16
16
|
|
|
17
17
|
from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION, InstrumentationNames
|
|
18
|
-
from pydantic_graph import Graph
|
|
19
18
|
|
|
20
19
|
from .. import (
|
|
21
20
|
_agent_graph,
|
|
@@ -41,7 +40,6 @@ from ..builtin_tools import AbstractBuiltinTool
|
|
|
41
40
|
from ..models.instrumented import InstrumentationSettings, InstrumentedModel, instrument_model
|
|
42
41
|
from ..output import OutputDataT, OutputSpec
|
|
43
42
|
from ..profiles import ModelProfile
|
|
44
|
-
from ..result import FinalResult
|
|
45
43
|
from ..run import AgentRun, AgentRunResult
|
|
46
44
|
from ..settings import ModelSettings, merge_model_settings
|
|
47
45
|
from ..tools import (
|
|
@@ -566,9 +564,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
566
564
|
tool_manager = ToolManager[AgentDepsT](toolset)
|
|
567
565
|
|
|
568
566
|
# Build the graph
|
|
569
|
-
graph
|
|
570
|
-
_agent_graph.build_agent_graph(self.name, self._deps_type, output_type_)
|
|
571
|
-
)
|
|
567
|
+
graph = _agent_graph.build_agent_graph(self.name, self._deps_type, output_type_)
|
|
572
568
|
|
|
573
569
|
# Build the initial state
|
|
574
570
|
usage = usage or _usage.RunUsage()
|
|
@@ -628,7 +624,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
628
624
|
instrumentation_settings=instrumentation_settings,
|
|
629
625
|
)
|
|
630
626
|
|
|
631
|
-
|
|
627
|
+
user_prompt_node = _agent_graph.UserPromptNode[AgentDepsT](
|
|
632
628
|
user_prompt=user_prompt,
|
|
633
629
|
deferred_tool_results=deferred_tool_results,
|
|
634
630
|
instructions=instructions_literal,
|
|
@@ -655,7 +651,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
655
651
|
|
|
656
652
|
try:
|
|
657
653
|
async with graph.iter(
|
|
658
|
-
|
|
654
|
+
inputs=user_prompt_node,
|
|
659
655
|
state=state,
|
|
660
656
|
deps=graph_deps,
|
|
661
657
|
span=use_span(run_span) if run_span.is_recording() else None,
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/__init__.py
RENAMED
|
@@ -36,6 +36,17 @@ __all__ = [
|
|
|
36
36
|
'TemporalWrapperToolset',
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
+
# We need eagerly import the anyio backends or it will happens inside workflow code and temporal has issues
|
|
40
|
+
# Note: It's difficult to add a test that covers this because pytest presumably does these imports itself
|
|
41
|
+
# when you have a @pytest.mark.anyio somewhere.
|
|
42
|
+
# I suppose we could add a test that runs a python script in a separate process, but I have not done that...
|
|
43
|
+
import anyio._backends._asyncio # pyright: ignore[reportUnusedImport]
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import anyio._backends._trio # noqa F401 # pyright: ignore[reportUnusedImport]
|
|
47
|
+
except ImportError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
39
50
|
|
|
40
51
|
class PydanticAIPlugin(ClientPlugin, WorkerPlugin):
|
|
41
52
|
"""Temporal client and worker plugin for Pydantic AI."""
|
|
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Annotated, Any, Literal
|
|
5
|
+
from typing import Annotated, Any, Literal
|
|
6
6
|
|
|
7
7
|
from pydantic import ConfigDict, Discriminator, with_config
|
|
8
8
|
from temporalio import activity, workflow
|
|
9
9
|
from temporalio.workflow import ActivityConfig
|
|
10
|
+
from typing_extensions import assert_never
|
|
10
11
|
|
|
11
12
|
from pydantic_ai import FunctionToolset, ToolsetTool
|
|
12
13
|
from pydantic_ai.exceptions import ApprovalRequired, CallDeferred, ModelRetry, UserError
|
|
@@ -159,7 +159,7 @@ class ModelHTTPError(AgentRunError):
|
|
|
159
159
|
super().__init__(message)
|
|
160
160
|
|
|
161
161
|
|
|
162
|
-
class FallbackExceptionGroup(ExceptionGroup):
|
|
162
|
+
class FallbackExceptionGroup(ExceptionGroup[Any]):
|
|
163
163
|
"""A group of exceptions that can be raised when all fallback models fail."""
|
|
164
164
|
|
|
165
165
|
|
|
@@ -480,7 +480,7 @@ class BinaryContent:
|
|
|
480
480
|
"""
|
|
481
481
|
|
|
482
482
|
_identifier: Annotated[str | None, pydantic.Field(alias='identifier', default=None, exclude=True)] = field(
|
|
483
|
-
compare=False, default=None
|
|
483
|
+
compare=False, default=None, repr=False
|
|
484
484
|
)
|
|
485
485
|
|
|
486
486
|
kind: Literal['binary'] = 'binary'
|
|
@@ -1431,6 +1431,8 @@ class OpenAIResponsesModel(Model):
|
|
|
1431
1431
|
call_id=call_id,
|
|
1432
1432
|
type='function_call',
|
|
1433
1433
|
)
|
|
1434
|
+
if profile.openai_responses_requires_function_call_status_none:
|
|
1435
|
+
param['status'] = None # type: ignore[reportGeneralTypeIssues]
|
|
1434
1436
|
if id and send_item_ids: # pragma: no branch
|
|
1435
1437
|
param['id'] = id
|
|
1436
1438
|
openai_messages.append(param)
|
|
@@ -44,11 +44,14 @@ class _WrappedTextOutput:
|
|
|
44
44
|
value: str | None
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
@dataclass
|
|
47
|
+
@dataclass(init=False)
|
|
48
48
|
class _WrappedToolOutput:
|
|
49
49
|
"""A wrapper class to tag an output that came from the custom_output_args field."""
|
|
50
50
|
|
|
51
|
-
value: Any | None
|
|
51
|
+
value: dict[str, Any] | None
|
|
52
|
+
|
|
53
|
+
def __init__(self, value: Any | None):
|
|
54
|
+
self.value = pydantic_core.to_jsonable_python(value)
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
@dataclass(init=False)
|
|
@@ -364,7 +367,7 @@ class _JsonSchemaTestData:
|
|
|
364
367
|
self.defs = schema.get('$defs', {})
|
|
365
368
|
self.seed = seed
|
|
366
369
|
|
|
367
|
-
def generate(self) -> Any:
|
|
370
|
+
def generate(self) -> dict[str, Any]:
|
|
368
371
|
"""Generate data for the JSON schema."""
|
|
369
372
|
return self._gen_any(self.schema)
|
|
370
373
|
|
|
@@ -44,6 +44,13 @@ class OpenAIModelProfile(ModelProfile):
|
|
|
44
44
|
openai_supports_encrypted_reasoning_content: bool = False
|
|
45
45
|
"""Whether the model supports including encrypted reasoning content in the response."""
|
|
46
46
|
|
|
47
|
+
openai_responses_requires_function_call_status_none: bool = False
|
|
48
|
+
"""Whether the Responses API requires the `status` field on function tool calls to be `None`.
|
|
49
|
+
|
|
50
|
+
This is required by vLLM Responses API versions before https://github.com/vllm-project/vllm/pull/26706.
|
|
51
|
+
See https://github.com/pydantic/pydantic-ai/issues/3245 for more details.
|
|
52
|
+
"""
|
|
53
|
+
|
|
47
54
|
def __post_init__(self): # pragma: no cover
|
|
48
55
|
if not self.openai_supports_sampling_settings:
|
|
49
56
|
warnings.warn(
|
|
@@ -13,7 +13,8 @@ from pydantic_ai.providers import Provider
|
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from google.auth.credentials import Credentials
|
|
16
|
-
from google.genai import
|
|
16
|
+
from google.genai._api_client import BaseApiClient
|
|
17
|
+
from google.genai.client import Client, DebugConfig
|
|
17
18
|
from google.genai.types import HttpOptions
|
|
18
19
|
except ImportError as _import_error:
|
|
19
20
|
raise ImportError(
|
|
@@ -114,7 +115,7 @@ class GoogleProvider(Provider[Client]):
|
|
|
114
115
|
base_url=base_url,
|
|
115
116
|
headers={'User-Agent': get_user_agent()},
|
|
116
117
|
httpx_async_client=http_client,
|
|
117
|
-
# TODO: Remove once https://github.com/googleapis/python-genai/
|
|
118
|
+
# TODO: Remove once https://github.com/googleapis/python-genai/issues/1565 is solved.
|
|
118
119
|
async_client_args={'transport': httpx.AsyncHTTPTransport()},
|
|
119
120
|
)
|
|
120
121
|
if not vertexai:
|
|
@@ -186,9 +187,37 @@ More details [here](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/
|
|
|
186
187
|
|
|
187
188
|
|
|
188
189
|
class _SafelyClosingClient(Client):
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _get_api_client(
|
|
192
|
+
vertexai: bool | None = None,
|
|
193
|
+
api_key: str | None = None,
|
|
194
|
+
credentials: Credentials | None = None,
|
|
195
|
+
project: str | None = None,
|
|
196
|
+
location: str | None = None,
|
|
197
|
+
debug_config: DebugConfig | None = None,
|
|
198
|
+
http_options: HttpOptions | None = None,
|
|
199
|
+
) -> BaseApiClient:
|
|
200
|
+
return _NonClosingApiClient(
|
|
201
|
+
vertexai=vertexai,
|
|
202
|
+
api_key=api_key,
|
|
203
|
+
credentials=credentials,
|
|
204
|
+
project=project,
|
|
205
|
+
location=location,
|
|
206
|
+
http_options=http_options,
|
|
207
|
+
)
|
|
208
|
+
|
|
189
209
|
def close(self) -> None:
|
|
190
210
|
# This is called from `Client.__del__`, even if `Client.__init__` raised an error before `self._api_client` is set, which would raise an `AttributeError` here.
|
|
211
|
+
# TODO: Remove once https://github.com/googleapis/python-genai/issues/1567 is solved.
|
|
191
212
|
try:
|
|
192
213
|
super().close()
|
|
193
214
|
except AttributeError:
|
|
194
215
|
pass
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class _NonClosingApiClient(BaseApiClient):
|
|
219
|
+
async def aclose(self) -> None:
|
|
220
|
+
# The original implementation also calls `await self._async_httpx_client.aclose()`, but we don't want to close our `cached_async_http_client` or the one the user passed in.
|
|
221
|
+
# TODO: Remove once https://github.com/googleapis/python-genai/issues/1566 is solved.
|
|
222
|
+
if self._aiohttp_session:
|
|
223
|
+
await self._aiohttp_session.close() # pragma: no cover
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
from collections.abc import AsyncIterator
|
|
4
|
+
from collections.abc import AsyncIterator, Sequence
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Generic, Literal, overload
|
|
8
8
|
|
|
9
|
-
from pydantic_graph import
|
|
9
|
+
from pydantic_graph import BaseNode, End, GraphRunContext
|
|
10
|
+
from pydantic_graph.beta.graph import EndMarker, GraphRun, GraphTask, JoinItem
|
|
11
|
+
from pydantic_graph.beta.step import NodeStep
|
|
10
12
|
|
|
11
13
|
from . import (
|
|
12
14
|
_agent_graph,
|
|
@@ -112,12 +114,8 @@ class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
|
112
114
|
|
|
113
115
|
This is the next node that will be used during async iteration, or if a node is not passed to `self.next(...)`.
|
|
114
116
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return next_node
|
|
118
|
-
if _agent_graph.is_agent_node(next_node):
|
|
119
|
-
return next_node
|
|
120
|
-
raise exceptions.AgentRunError(f'Unexpected node type: {type(next_node)}') # pragma: no cover
|
|
117
|
+
task = self._graph_run.next_task
|
|
118
|
+
return self._task_to_node(task)
|
|
121
119
|
|
|
122
120
|
@property
|
|
123
121
|
def result(self) -> AgentRunResult[OutputDataT] | None:
|
|
@@ -126,13 +124,13 @@ class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
|
126
124
|
Once the run returns an [`End`][pydantic_graph.nodes.End] node, `result` is populated
|
|
127
125
|
with an [`AgentRunResult`][pydantic_ai.agent.AgentRunResult].
|
|
128
126
|
"""
|
|
129
|
-
|
|
130
|
-
if
|
|
127
|
+
graph_run_output = self._graph_run.output
|
|
128
|
+
if graph_run_output is None:
|
|
131
129
|
return None
|
|
132
130
|
return AgentRunResult(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
graph_run_output.output,
|
|
132
|
+
graph_run_output.tool_name,
|
|
133
|
+
self._graph_run.state,
|
|
136
134
|
self._graph_run.deps.new_message_index,
|
|
137
135
|
self._traceparent(required=False),
|
|
138
136
|
)
|
|
@@ -147,11 +145,28 @@ class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
|
147
145
|
self,
|
|
148
146
|
) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
|
|
149
147
|
"""Advance to the next node automatically based on the last returned node."""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
task = await anext(self._graph_run)
|
|
149
|
+
return self._task_to_node(task)
|
|
150
|
+
|
|
151
|
+
def _task_to_node(
|
|
152
|
+
self, task: EndMarker[FinalResult[OutputDataT]] | JoinItem | Sequence[GraphTask]
|
|
153
|
+
) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
|
|
154
|
+
if isinstance(task, Sequence) and len(task) == 1:
|
|
155
|
+
first_task = task[0]
|
|
156
|
+
if isinstance(first_task.inputs, BaseNode): # pragma: no branch
|
|
157
|
+
base_node: BaseNode[
|
|
158
|
+
_agent_graph.GraphAgentState,
|
|
159
|
+
_agent_graph.GraphAgentDeps[AgentDepsT, OutputDataT],
|
|
160
|
+
FinalResult[OutputDataT],
|
|
161
|
+
] = first_task.inputs # type: ignore[reportUnknownMemberType]
|
|
162
|
+
if _agent_graph.is_agent_node(node=base_node): # pragma: no branch
|
|
163
|
+
return base_node
|
|
164
|
+
if isinstance(task, EndMarker):
|
|
165
|
+
return End(task.value)
|
|
166
|
+
raise exceptions.AgentRunError(f'Unexpected node: {task}') # pragma: no cover
|
|
167
|
+
|
|
168
|
+
def _node_to_task(self, node: _agent_graph.AgentNode[AgentDepsT, OutputDataT]) -> GraphTask:
|
|
169
|
+
return GraphTask(NodeStep(type(node)).id, inputs=node, fork_stack=())
|
|
155
170
|
|
|
156
171
|
async def next(
|
|
157
172
|
self,
|
|
@@ -222,11 +237,12 @@ class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
|
222
237
|
"""
|
|
223
238
|
# Note: It might be nice to expose a synchronous interface for iteration, but we shouldn't do it
|
|
224
239
|
# on this class, or else IDEs won't warn you if you accidentally use `for` instead of `async for` to iterate.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
task = [self._node_to_task(node)]
|
|
241
|
+
try:
|
|
242
|
+
task = await self._graph_run.next(task)
|
|
243
|
+
except StopAsyncIteration:
|
|
244
|
+
pass
|
|
245
|
+
return self._task_to_node(task)
|
|
230
246
|
|
|
231
247
|
# TODO (v2): Make this a property
|
|
232
248
|
def usage(self) -> _usage.RunUsage:
|
|
@@ -234,7 +250,7 @@ class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
|
234
250
|
return self._graph_run.state.usage
|
|
235
251
|
|
|
236
252
|
def __repr__(self) -> str: # pragma: no cover
|
|
237
|
-
result = self._graph_run.
|
|
253
|
+
result = self._graph_run.output
|
|
238
254
|
result_repr = '<run not finished>' if result is None else repr(result.output)
|
|
239
255
|
return f'<{type(self).__name__} result={result_repr} usage={self.usage()}>'
|
|
240
256
|
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from asyncio import Lock
|
|
5
|
+
from contextlib import AsyncExitStack
|
|
6
|
+
from dataclasses import KW_ONLY, dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import AnyUrl
|
|
11
|
+
from typing_extensions import Self, assert_never
|
|
12
|
+
|
|
13
|
+
from pydantic_ai import messages
|
|
14
|
+
from pydantic_ai.exceptions import ModelRetry
|
|
15
|
+
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
|
|
16
|
+
from pydantic_ai.toolsets import AbstractToolset
|
|
17
|
+
from pydantic_ai.toolsets.abstract import ToolsetTool
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from fastmcp.client import Client
|
|
21
|
+
from fastmcp.client.transports import ClientTransport
|
|
22
|
+
from fastmcp.exceptions import ToolError
|
|
23
|
+
from fastmcp.mcp_config import MCPConfig
|
|
24
|
+
from fastmcp.server import FastMCP
|
|
25
|
+
from mcp.server.fastmcp import FastMCP as FastMCP1Server
|
|
26
|
+
from mcp.types import (
|
|
27
|
+
AudioContent,
|
|
28
|
+
BlobResourceContents,
|
|
29
|
+
ContentBlock,
|
|
30
|
+
EmbeddedResource,
|
|
31
|
+
ImageContent,
|
|
32
|
+
ResourceLink,
|
|
33
|
+
TextContent,
|
|
34
|
+
TextResourceContents,
|
|
35
|
+
Tool as MCPTool,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from pydantic_ai.mcp import TOOL_SCHEMA_VALIDATOR
|
|
39
|
+
|
|
40
|
+
except ImportError as _import_error:
|
|
41
|
+
raise ImportError(
|
|
42
|
+
'Please install the `fastmcp` package to use the FastMCP server, '
|
|
43
|
+
'you can use the `fastmcp` optional group — `pip install "pydantic-ai-slim[fastmcp]"`'
|
|
44
|
+
) from _import_error
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from fastmcp.client.client import CallToolResult
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
FastMCPToolResult = messages.BinaryContent | dict[str, Any] | str | None
|
|
52
|
+
|
|
53
|
+
ToolErrorBehavior = Literal['model_retry', 'error']
|
|
54
|
+
|
|
55
|
+
UNKNOWN_BINARY_MEDIA_TYPE = 'application/octet-stream'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(init=False)
|
|
59
|
+
class FastMCPToolset(AbstractToolset[AgentDepsT]):
|
|
60
|
+
"""A FastMCP Toolset that uses the FastMCP Client to call tools from a local or remote MCP Server.
|
|
61
|
+
|
|
62
|
+
The Toolset can accept a FastMCP Client, a FastMCP Transport, or any other object which a FastMCP Transport can be created from.
|
|
63
|
+
|
|
64
|
+
See https://gofastmcp.com/clients/transports for a full list of transports available.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
client: Client[Any]
|
|
68
|
+
"""The FastMCP client to use."""
|
|
69
|
+
|
|
70
|
+
_: KW_ONLY
|
|
71
|
+
|
|
72
|
+
tool_error_behavior: Literal['model_retry', 'error']
|
|
73
|
+
"""The behavior to take when a tool error occurs."""
|
|
74
|
+
|
|
75
|
+
max_retries: int
|
|
76
|
+
"""The maximum number of retries to attempt if a tool call fails."""
|
|
77
|
+
|
|
78
|
+
_id: str | None
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
client: Client[Any]
|
|
83
|
+
| ClientTransport
|
|
84
|
+
| FastMCP
|
|
85
|
+
| FastMCP1Server
|
|
86
|
+
| AnyUrl
|
|
87
|
+
| Path
|
|
88
|
+
| MCPConfig
|
|
89
|
+
| dict[str, Any]
|
|
90
|
+
| str,
|
|
91
|
+
*,
|
|
92
|
+
max_retries: int = 1,
|
|
93
|
+
tool_error_behavior: Literal['model_retry', 'error'] = 'model_retry',
|
|
94
|
+
id: str | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
if isinstance(client, Client):
|
|
97
|
+
self.client = client
|
|
98
|
+
else:
|
|
99
|
+
self.client = Client[Any](transport=client)
|
|
100
|
+
|
|
101
|
+
self._id = id
|
|
102
|
+
self.max_retries = max_retries
|
|
103
|
+
self.tool_error_behavior = tool_error_behavior
|
|
104
|
+
|
|
105
|
+
self._enter_lock: Lock = Lock()
|
|
106
|
+
self._running_count: int = 0
|
|
107
|
+
self._exit_stack: AsyncExitStack | None = None
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def id(self) -> str | None:
|
|
111
|
+
return self._id
|
|
112
|
+
|
|
113
|
+
async def __aenter__(self) -> Self:
|
|
114
|
+
async with self._enter_lock:
|
|
115
|
+
if self._running_count == 0:
|
|
116
|
+
self._exit_stack = AsyncExitStack()
|
|
117
|
+
await self._exit_stack.enter_async_context(self.client)
|
|
118
|
+
|
|
119
|
+
self._running_count += 1
|
|
120
|
+
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
async def __aexit__(self, *args: Any) -> bool | None:
|
|
124
|
+
async with self._enter_lock:
|
|
125
|
+
self._running_count -= 1
|
|
126
|
+
if self._running_count == 0 and self._exit_stack:
|
|
127
|
+
await self._exit_stack.aclose()
|
|
128
|
+
self._exit_stack = None
|
|
129
|
+
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
async def get_tools(self, ctx: RunContext[AgentDepsT]) -> dict[str, ToolsetTool[AgentDepsT]]:
|
|
133
|
+
async with self:
|
|
134
|
+
mcp_tools: list[MCPTool] = await self.client.list_tools()
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
tool.name: _convert_mcp_tool_to_toolset_tool(toolset=self, mcp_tool=tool, retries=self.max_retries)
|
|
138
|
+
for tool in mcp_tools
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async def call_tool(
|
|
142
|
+
self, name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT], tool: ToolsetTool[AgentDepsT]
|
|
143
|
+
) -> Any:
|
|
144
|
+
async with self:
|
|
145
|
+
try:
|
|
146
|
+
call_tool_result: CallToolResult = await self.client.call_tool(name=name, arguments=tool_args)
|
|
147
|
+
except ToolError as e:
|
|
148
|
+
if self.tool_error_behavior == 'model_retry':
|
|
149
|
+
raise ModelRetry(message=str(e)) from e
|
|
150
|
+
else:
|
|
151
|
+
raise e
|
|
152
|
+
|
|
153
|
+
# If we have structured content, return that
|
|
154
|
+
if call_tool_result.structured_content:
|
|
155
|
+
return call_tool_result.structured_content
|
|
156
|
+
|
|
157
|
+
# Otherwise, return the content
|
|
158
|
+
return _map_fastmcp_tool_results(parts=call_tool_result.content)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _convert_mcp_tool_to_toolset_tool(
|
|
162
|
+
toolset: FastMCPToolset[AgentDepsT],
|
|
163
|
+
mcp_tool: MCPTool,
|
|
164
|
+
retries: int,
|
|
165
|
+
) -> ToolsetTool[AgentDepsT]:
|
|
166
|
+
"""Convert an MCP tool to a toolset tool."""
|
|
167
|
+
return ToolsetTool[AgentDepsT](
|
|
168
|
+
tool_def=ToolDefinition(
|
|
169
|
+
name=mcp_tool.name,
|
|
170
|
+
description=mcp_tool.description,
|
|
171
|
+
parameters_json_schema=mcp_tool.inputSchema,
|
|
172
|
+
metadata={
|
|
173
|
+
'meta': mcp_tool.meta,
|
|
174
|
+
'annotations': mcp_tool.annotations.model_dump() if mcp_tool.annotations else None,
|
|
175
|
+
'output_schema': mcp_tool.outputSchema or None,
|
|
176
|
+
},
|
|
177
|
+
),
|
|
178
|
+
toolset=toolset,
|
|
179
|
+
max_retries=retries,
|
|
180
|
+
args_validator=TOOL_SCHEMA_VALIDATOR,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _map_fastmcp_tool_results(parts: list[ContentBlock]) -> list[FastMCPToolResult] | FastMCPToolResult:
|
|
185
|
+
"""Map FastMCP tool results to toolset tool results."""
|
|
186
|
+
mapped_results = [_map_fastmcp_tool_result(part) for part in parts]
|
|
187
|
+
|
|
188
|
+
if len(mapped_results) == 1:
|
|
189
|
+
return mapped_results[0]
|
|
190
|
+
|
|
191
|
+
return mapped_results
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _map_fastmcp_tool_result(part: ContentBlock) -> FastMCPToolResult:
|
|
195
|
+
if isinstance(part, TextContent):
|
|
196
|
+
return part.text
|
|
197
|
+
elif isinstance(part, ImageContent | AudioContent):
|
|
198
|
+
return messages.BinaryContent(data=base64.b64decode(part.data), media_type=part.mimeType)
|
|
199
|
+
elif isinstance(part, EmbeddedResource):
|
|
200
|
+
if isinstance(part.resource, BlobResourceContents):
|
|
201
|
+
return messages.BinaryContent(
|
|
202
|
+
data=base64.b64decode(part.resource.blob),
|
|
203
|
+
media_type=part.resource.mimeType or UNKNOWN_BINARY_MEDIA_TYPE,
|
|
204
|
+
)
|
|
205
|
+
elif isinstance(part.resource, TextResourceContents):
|
|
206
|
+
return part.resource.text
|
|
207
|
+
else:
|
|
208
|
+
assert_never(part.resource)
|
|
209
|
+
elif isinstance(part, ResourceLink):
|
|
210
|
+
# ResourceLink is not yet supported by the FastMCP toolset as reading resources is not yet supported.
|
|
211
|
+
raise NotImplementedError(
|
|
212
|
+
'ResourceLink is not supported by the FastMCP toolset as reading resources is not yet supported.'
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
assert_never(part)
|
|
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_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/__init__.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_model.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_toolset.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_types.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_agent.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_logfire.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_model.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_run_context.py
RENAMED
|
File without changes
|
{pydantic_ai_slim-1.4.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_toolset.py
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
|
|
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
|