pydantic-ai-slim 1.0.8__tar.gz → 1.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/PKG-INFO +5 -5
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_agent_graph.py +20 -14
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_cli.py +1 -1
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_otel_messages.py +2 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_parts_manager.py +82 -12
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_run_context.py +8 -1
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_tool_manager.py +1 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/ag_ui.py +86 -33
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/builtin_tools.py +12 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_model.py +14 -6
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/messages.py +69 -30
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/anthropic.py +119 -45
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/function.py +17 -8
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/google.py +105 -16
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/groq.py +68 -17
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/openai.py +262 -41
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/__init__.py +1 -1
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/result.py +21 -3
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/function.py +8 -2
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pyproject.toml +2 -2
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/.gitignore +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/LICENSE +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/README.md +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/__main__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_a2a.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_function_schema.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_griffe.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_mcp.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_output.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_system_prompt.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_thinking_part.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/_utils.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/agent/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/agent/abstract.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/agent/wrapper.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/common_tools/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/common_tools/tavily.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/direct.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/exceptions.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/ext/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/ext/aci.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/ext/langchain.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/format_prompt.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/mcp.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/bedrock.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/cohere.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/fallback.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/gemini.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/huggingface.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/instrumented.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/mcp_sampling.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/mistral.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/test.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/models/wrapper.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/output.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/_json_schema.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/amazon.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/anthropic.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/cohere.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/deepseek.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/google.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/grok.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/groq.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/harmony.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/meta.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/mistral.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/moonshotai.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/openai.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/profiles/qwen.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/anthropic.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/azure.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/bedrock.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/cerebras.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/cohere.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/deepseek.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/fireworks.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/gateway.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/github.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/google.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/google_gla.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/google_vertex.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/grok.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/groq.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/heroku.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/huggingface.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/litellm.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/mistral.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/moonshotai.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/ollama.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/openai.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/openrouter.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/together.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/providers/vercel.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/py.typed +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/retries.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/run.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/settings.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/tools.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/__init__.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/_dynamic.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/abstract.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/approval_required.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/combined.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/external.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/filtered.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/prefixed.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/prepared.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/renamed.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/toolsets/wrapper.py +0 -0
- {pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/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.0.
|
|
3
|
+
Version: 1.0.9
|
|
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.23
|
|
|
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.0.
|
|
36
|
+
Requires-Dist: pydantic-graph==1.0.9
|
|
37
37
|
Requires-Dist: pydantic>=2.10
|
|
38
38
|
Requires-Dist: typing-inspection>=0.4.0
|
|
39
39
|
Provides-Extra: a2a
|
|
@@ -53,11 +53,11 @@ Requires-Dist: rich>=13; extra == 'cli'
|
|
|
53
53
|
Provides-Extra: cohere
|
|
54
54
|
Requires-Dist: cohere>=5.18.0; (platform_system != 'Emscripten') and extra == 'cohere'
|
|
55
55
|
Provides-Extra: dbos
|
|
56
|
-
Requires-Dist: dbos>=1.
|
|
56
|
+
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.0.
|
|
60
|
+
Requires-Dist: pydantic-evals==1.0.9; extra == 'evals'
|
|
61
61
|
Provides-Extra: google
|
|
62
62
|
Requires-Dist: google-genai>=1.31.0; extra == 'google'
|
|
63
63
|
Provides-Extra: groq
|
|
@@ -71,7 +71,7 @@ Requires-Dist: mcp>=1.12.3; extra == 'mcp'
|
|
|
71
71
|
Provides-Extra: mistral
|
|
72
72
|
Requires-Dist: mistralai>=1.9.10; extra == 'mistral'
|
|
73
73
|
Provides-Extra: openai
|
|
74
|
-
Requires-Dist: openai>=1.
|
|
74
|
+
Requires-Dist: openai>=1.107.2; extra == 'openai'
|
|
75
75
|
Provides-Extra: retries
|
|
76
76
|
Requires-Dist: tenacity>=8.2.3; extra == 'retries'
|
|
77
77
|
Provides-Extra: tavily
|
|
@@ -545,21 +545,22 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
545
545
|
# Ensure that the stream is only run once
|
|
546
546
|
|
|
547
547
|
async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]: # noqa: C901
|
|
548
|
-
|
|
548
|
+
text = ''
|
|
549
549
|
tool_calls: list[_messages.ToolCallPart] = []
|
|
550
550
|
thinking_parts: list[_messages.ThinkingPart] = []
|
|
551
551
|
|
|
552
552
|
for part in self.model_response.parts:
|
|
553
553
|
if isinstance(part, _messages.TextPart):
|
|
554
|
-
|
|
555
|
-
if part.content:
|
|
556
|
-
texts.append(part.content)
|
|
554
|
+
text += part.content
|
|
557
555
|
elif isinstance(part, _messages.ToolCallPart):
|
|
558
556
|
tool_calls.append(part)
|
|
559
557
|
elif isinstance(part, _messages.BuiltinToolCallPart):
|
|
560
|
-
|
|
558
|
+
# Text parts before a built-in tool call are essentially thoughts,
|
|
559
|
+
# not part of the final result output, so we reset the accumulated text
|
|
560
|
+
text = ''
|
|
561
|
+
yield _messages.BuiltinToolCallEvent(part) # pyright: ignore[reportDeprecated]
|
|
561
562
|
elif isinstance(part, _messages.BuiltinToolReturnPart):
|
|
562
|
-
yield _messages.BuiltinToolResultEvent(part)
|
|
563
|
+
yield _messages.BuiltinToolResultEvent(part) # pyright: ignore[reportDeprecated]
|
|
563
564
|
elif isinstance(part, _messages.ThinkingPart):
|
|
564
565
|
thinking_parts.append(part)
|
|
565
566
|
else:
|
|
@@ -572,9 +573,9 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
572
573
|
if tool_calls:
|
|
573
574
|
async for event in self._handle_tool_calls(ctx, tool_calls):
|
|
574
575
|
yield event
|
|
575
|
-
elif
|
|
576
|
+
elif text:
|
|
576
577
|
# No events are emitted during the handling of text responses, so we don't need to yield anything
|
|
577
|
-
self._next_node = await self._handle_text_response(ctx,
|
|
578
|
+
self._next_node = await self._handle_text_response(ctx, text)
|
|
578
579
|
elif thinking_parts:
|
|
579
580
|
# handle thinking-only responses (responses that contain only ThinkingPart instances)
|
|
580
581
|
# this can happen with models that support thinking mode when they don't provide
|
|
@@ -593,9 +594,16 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
593
594
|
if isinstance(ctx.deps.output_schema, _output.TextOutputSchema):
|
|
594
595
|
for message in reversed(ctx.state.message_history):
|
|
595
596
|
if isinstance(message, _messages.ModelResponse):
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
597
|
+
text = ''
|
|
598
|
+
for part in message.parts:
|
|
599
|
+
if isinstance(part, _messages.TextPart):
|
|
600
|
+
text += part.content
|
|
601
|
+
elif isinstance(part, _messages.BuiltinToolCallPart):
|
|
602
|
+
# Text parts before a built-in tool call are essentially thoughts,
|
|
603
|
+
# not part of the final result output, so we reset the accumulated text
|
|
604
|
+
text = '' # pragma: no cover
|
|
605
|
+
if text:
|
|
606
|
+
self._next_node = await self._handle_text_response(ctx, text)
|
|
599
607
|
return
|
|
600
608
|
|
|
601
609
|
raise exceptions.UnexpectedModelBehavior('Received empty model response')
|
|
@@ -655,11 +663,9 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
655
663
|
async def _handle_text_response(
|
|
656
664
|
self,
|
|
657
665
|
ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]],
|
|
658
|
-
|
|
666
|
+
text: str,
|
|
659
667
|
) -> ModelRequestNode[DepsT, NodeRunEndT] | End[result.FinalResult[NodeRunEndT]]:
|
|
660
668
|
output_schema = ctx.deps.output_schema
|
|
661
|
-
|
|
662
|
-
text = '\n\n'.join(texts)
|
|
663
669
|
try:
|
|
664
670
|
run_context = build_run_context(ctx)
|
|
665
671
|
if isinstance(output_schema, _output.TextOutputSchema):
|
|
@@ -356,7 +356,7 @@ def handle_slash_command(
|
|
|
356
356
|
except IndexError:
|
|
357
357
|
console.print('[dim]No output available to copy.[/dim]')
|
|
358
358
|
else:
|
|
359
|
-
text_to_copy = '
|
|
359
|
+
text_to_copy = ''.join(part.content for part in parts if isinstance(part, TextPart))
|
|
360
360
|
text_to_copy = text_to_copy.strip()
|
|
361
361
|
if text_to_copy:
|
|
362
362
|
pyperclip.copy(text_to_copy)
|
|
@@ -21,6 +21,7 @@ class ToolCallPart(TypedDict):
|
|
|
21
21
|
id: str
|
|
22
22
|
name: str
|
|
23
23
|
arguments: NotRequired[JsonValue]
|
|
24
|
+
builtin: NotRequired[bool] # Not (currently?) part of the spec, used by Logfire
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class ToolCallResponsePart(TypedDict):
|
|
@@ -28,6 +29,7 @@ class ToolCallResponsePart(TypedDict):
|
|
|
28
29
|
id: str
|
|
29
30
|
name: str
|
|
30
31
|
result: NotRequired[JsonValue]
|
|
32
|
+
builtin: NotRequired[bool] # Not (currently?) part of the spec, used by Logfire
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class MediaUrlPart(TypedDict):
|
|
@@ -19,6 +19,8 @@ from typing import Any
|
|
|
19
19
|
|
|
20
20
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
21
21
|
from pydantic_ai.messages import (
|
|
22
|
+
BuiltinToolCallPart,
|
|
23
|
+
BuiltinToolReturnPart,
|
|
22
24
|
ModelResponsePart,
|
|
23
25
|
ModelResponseStreamEvent,
|
|
24
26
|
PartDeltaEvent,
|
|
@@ -226,11 +228,11 @@ class ModelResponsePartsManager:
|
|
|
226
228
|
self,
|
|
227
229
|
*,
|
|
228
230
|
vendor_part_id: Hashable | None,
|
|
229
|
-
tool_name: str | None,
|
|
230
|
-
args: str | dict[str, Any] | None,
|
|
231
|
-
tool_call_id: str | None,
|
|
231
|
+
tool_name: str | None = None,
|
|
232
|
+
args: str | dict[str, Any] | None = None,
|
|
233
|
+
tool_call_id: str | None = None,
|
|
232
234
|
) -> ModelResponseStreamEvent | None:
|
|
233
|
-
"""Handle or update a tool call, creating or updating a `ToolCallPart` or `ToolCallPartDelta`.
|
|
235
|
+
"""Handle or update a tool call, creating or updating a `ToolCallPart`, `BuiltinToolCallPart`, or `ToolCallPartDelta`.
|
|
234
236
|
|
|
235
237
|
Managed items remain as `ToolCallPartDelta`s until they have at least a tool_name, at which
|
|
236
238
|
point they are upgraded to `ToolCallPart`s.
|
|
@@ -247,15 +249,17 @@ class ModelResponsePartsManager:
|
|
|
247
249
|
tool_call_id: An optional string representing an identifier for this tool call.
|
|
248
250
|
|
|
249
251
|
Returns:
|
|
250
|
-
- A `PartStartEvent` if a new ToolCallPart is created.
|
|
252
|
+
- A `PartStartEvent` if a new ToolCallPart or BuiltinToolCallPart is created.
|
|
251
253
|
- A `PartDeltaEvent` if an existing part is updated.
|
|
252
254
|
- `None` if no new event is emitted (e.g., the part is still incomplete).
|
|
253
255
|
|
|
254
256
|
Raises:
|
|
255
257
|
UnexpectedModelBehavior: If attempting to apply a tool call delta to a part that is not
|
|
256
|
-
a ToolCallPart or ToolCallPartDelta.
|
|
258
|
+
a ToolCallPart, BuiltinToolCallPart, or ToolCallPartDelta.
|
|
257
259
|
"""
|
|
258
|
-
existing_matching_part_and_index: tuple[ToolCallPartDelta | ToolCallPart, int] | None =
|
|
260
|
+
existing_matching_part_and_index: tuple[ToolCallPartDelta | ToolCallPart | BuiltinToolCallPart, int] | None = (
|
|
261
|
+
None
|
|
262
|
+
)
|
|
259
263
|
|
|
260
264
|
if vendor_part_id is None:
|
|
261
265
|
# vendor_part_id is None, so check if the latest part is a matching tool call or delta to update
|
|
@@ -264,14 +268,14 @@ class ModelResponsePartsManager:
|
|
|
264
268
|
if tool_name is None and self._parts:
|
|
265
269
|
part_index = len(self._parts) - 1
|
|
266
270
|
latest_part = self._parts[part_index]
|
|
267
|
-
if isinstance(latest_part, ToolCallPart | ToolCallPartDelta): # pragma: no branch
|
|
271
|
+
if isinstance(latest_part, ToolCallPart | BuiltinToolCallPart | ToolCallPartDelta): # pragma: no branch
|
|
268
272
|
existing_matching_part_and_index = latest_part, part_index
|
|
269
273
|
else:
|
|
270
274
|
# vendor_part_id is provided, so look up the corresponding part or delta
|
|
271
275
|
part_index = self._vendor_id_to_part_index.get(vendor_part_id)
|
|
272
276
|
if part_index is not None:
|
|
273
277
|
existing_part = self._parts[part_index]
|
|
274
|
-
if not isinstance(existing_part, ToolCallPartDelta | ToolCallPart):
|
|
278
|
+
if not isinstance(existing_part, ToolCallPartDelta | ToolCallPart | BuiltinToolCallPart):
|
|
275
279
|
raise UnexpectedModelBehavior(f'Cannot apply a tool call delta to {existing_part=}')
|
|
276
280
|
existing_matching_part_and_index = existing_part, part_index
|
|
277
281
|
|
|
@@ -284,7 +288,7 @@ class ModelResponsePartsManager:
|
|
|
284
288
|
new_part_index = len(self._parts)
|
|
285
289
|
self._parts.append(part)
|
|
286
290
|
# Only emit a PartStartEvent if we have enough information to produce a full ToolCallPart
|
|
287
|
-
if isinstance(part, ToolCallPart):
|
|
291
|
+
if isinstance(part, ToolCallPart | BuiltinToolCallPart):
|
|
288
292
|
return PartStartEvent(index=new_part_index, part=part)
|
|
289
293
|
else:
|
|
290
294
|
# Update the existing part or delta with the new information
|
|
@@ -292,7 +296,7 @@ class ModelResponsePartsManager:
|
|
|
292
296
|
delta = ToolCallPartDelta(tool_name_delta=tool_name, args_delta=args, tool_call_id=tool_call_id)
|
|
293
297
|
updated_part = delta.apply(existing_part)
|
|
294
298
|
self._parts[part_index] = updated_part
|
|
295
|
-
if isinstance(updated_part, ToolCallPart):
|
|
299
|
+
if isinstance(updated_part, ToolCallPart | BuiltinToolCallPart):
|
|
296
300
|
if isinstance(existing_part, ToolCallPartDelta):
|
|
297
301
|
# We just upgraded a delta to a full part, so emit a PartStartEvent
|
|
298
302
|
return PartStartEvent(index=part_index, part=updated_part)
|
|
@@ -337,7 +341,7 @@ class ModelResponsePartsManager:
|
|
|
337
341
|
else:
|
|
338
342
|
# vendor_part_id is provided, so find and overwrite or create a new ToolCallPart.
|
|
339
343
|
maybe_part_index = self._vendor_id_to_part_index.get(vendor_part_id)
|
|
340
|
-
if maybe_part_index is not None:
|
|
344
|
+
if maybe_part_index is not None and isinstance(self._parts[maybe_part_index], ToolCallPart):
|
|
341
345
|
new_part_index = maybe_part_index
|
|
342
346
|
self._parts[new_part_index] = new_part
|
|
343
347
|
else:
|
|
@@ -345,3 +349,69 @@ class ModelResponsePartsManager:
|
|
|
345
349
|
self._parts.append(new_part)
|
|
346
350
|
self._vendor_id_to_part_index[vendor_part_id] = new_part_index
|
|
347
351
|
return PartStartEvent(index=new_part_index, part=new_part)
|
|
352
|
+
|
|
353
|
+
def handle_builtin_tool_call_part(
|
|
354
|
+
self,
|
|
355
|
+
*,
|
|
356
|
+
vendor_part_id: Hashable | None,
|
|
357
|
+
part: BuiltinToolCallPart,
|
|
358
|
+
) -> ModelResponseStreamEvent:
|
|
359
|
+
"""Create or overwrite a BuiltinToolCallPart.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
vendor_part_id: The vendor's ID for this tool call part. If not
|
|
363
|
+
None and an existing part is found, that part is overwritten.
|
|
364
|
+
part: The BuiltinToolCallPart.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
ModelResponseStreamEvent: A `PartStartEvent` indicating that a new tool call part
|
|
368
|
+
has been added to the manager, or replaced an existing part.
|
|
369
|
+
"""
|
|
370
|
+
if vendor_part_id is None:
|
|
371
|
+
# vendor_part_id is None, so we unconditionally append a new BuiltinToolCallPart to the end of the list
|
|
372
|
+
new_part_index = len(self._parts)
|
|
373
|
+
self._parts.append(part)
|
|
374
|
+
else:
|
|
375
|
+
# vendor_part_id is provided, so find and overwrite or create a new BuiltinToolCallPart.
|
|
376
|
+
maybe_part_index = self._vendor_id_to_part_index.get(vendor_part_id)
|
|
377
|
+
if maybe_part_index is not None and isinstance(self._parts[maybe_part_index], BuiltinToolCallPart):
|
|
378
|
+
new_part_index = maybe_part_index
|
|
379
|
+
self._parts[new_part_index] = part
|
|
380
|
+
else:
|
|
381
|
+
new_part_index = len(self._parts)
|
|
382
|
+
self._parts.append(part)
|
|
383
|
+
self._vendor_id_to_part_index[vendor_part_id] = new_part_index
|
|
384
|
+
return PartStartEvent(index=new_part_index, part=part)
|
|
385
|
+
|
|
386
|
+
def handle_builtin_tool_return_part(
|
|
387
|
+
self,
|
|
388
|
+
*,
|
|
389
|
+
vendor_part_id: Hashable | None,
|
|
390
|
+
part: BuiltinToolReturnPart,
|
|
391
|
+
) -> ModelResponseStreamEvent:
|
|
392
|
+
"""Create or overwrite a BuiltinToolReturnPart.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
vendor_part_id: The vendor's ID for this tool call part. If not
|
|
396
|
+
None and an existing part is found, that part is overwritten.
|
|
397
|
+
part: The BuiltinToolReturnPart.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
ModelResponseStreamEvent: A `PartStartEvent` indicating that a new tool call part
|
|
401
|
+
has been added to the manager, or replaced an existing part.
|
|
402
|
+
"""
|
|
403
|
+
if vendor_part_id is None:
|
|
404
|
+
# vendor_part_id is None, so we unconditionally append a new BuiltinToolReturnPart to the end of the list
|
|
405
|
+
new_part_index = len(self._parts)
|
|
406
|
+
self._parts.append(part)
|
|
407
|
+
else:
|
|
408
|
+
# vendor_part_id is provided, so find and overwrite or create a new BuiltinToolReturnPart.
|
|
409
|
+
maybe_part_index = self._vendor_id_to_part_index.get(vendor_part_id)
|
|
410
|
+
if maybe_part_index is not None and isinstance(self._parts[maybe_part_index], BuiltinToolReturnPart):
|
|
411
|
+
new_part_index = maybe_part_index
|
|
412
|
+
self._parts[new_part_index] = part
|
|
413
|
+
else:
|
|
414
|
+
new_part_index = len(self._parts)
|
|
415
|
+
self._parts.append(part)
|
|
416
|
+
self._vendor_id_to_part_index[vendor_part_id] = new_part_index
|
|
417
|
+
return PartStartEvent(index=new_part_index, part=part)
|
|
@@ -43,10 +43,17 @@ class RunContext(Generic[AgentDepsT]):
|
|
|
43
43
|
tool_name: str | None = None
|
|
44
44
|
"""Name of the tool being called."""
|
|
45
45
|
retry: int = 0
|
|
46
|
-
"""Number of retries so far."""
|
|
46
|
+
"""Number of retries of this tool so far."""
|
|
47
|
+
max_retries: int = 0
|
|
48
|
+
"""The maximum number of retries of this tool."""
|
|
47
49
|
run_step: int = 0
|
|
48
50
|
"""The current step in the run."""
|
|
49
51
|
tool_call_approved: bool = False
|
|
50
52
|
"""Whether a tool call that required approval has now been approved."""
|
|
51
53
|
|
|
54
|
+
@property
|
|
55
|
+
def last_attempt(self) -> bool:
|
|
56
|
+
"""Whether this is the last attempt at running this tool before an error is raised."""
|
|
57
|
+
return self.retry == self.max_retries
|
|
58
|
+
|
|
52
59
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import json
|
|
10
10
|
import uuid
|
|
11
11
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Mapping, Sequence
|
|
12
|
-
from dataclasses import Field, dataclass, replace
|
|
12
|
+
from dataclasses import Field, dataclass, field, replace
|
|
13
13
|
from http import HTTPStatus
|
|
14
14
|
from typing import (
|
|
15
15
|
Any,
|
|
@@ -23,13 +23,15 @@ from typing import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from pydantic import BaseModel, ValidationError
|
|
26
|
-
from typing_extensions import assert_never
|
|
27
26
|
|
|
28
27
|
from . import _utils
|
|
29
28
|
from ._agent_graph import CallToolsNode, ModelRequestNode
|
|
30
29
|
from .agent import AbstractAgent, AgentRun, AgentRunResult
|
|
31
30
|
from .exceptions import UserError
|
|
32
31
|
from .messages import (
|
|
32
|
+
BaseToolCallPart,
|
|
33
|
+
BuiltinToolCallPart,
|
|
34
|
+
BuiltinToolReturnPart,
|
|
33
35
|
FunctionToolResultEvent,
|
|
34
36
|
ModelMessage,
|
|
35
37
|
ModelRequest,
|
|
@@ -123,6 +125,8 @@ SSE_CONTENT_TYPE: Final[str] = 'text/event-stream'
|
|
|
123
125
|
OnCompleteFunc: TypeAlias = Callable[[AgentRunResult[Any]], None] | Callable[[AgentRunResult[Any]], Awaitable[None]]
|
|
124
126
|
"""Callback function type that receives the `AgentRunResult` of the completed run. Can be sync or async."""
|
|
125
127
|
|
|
128
|
+
_BUILTIN_TOOL_CALL_ID_PREFIX: Final[str] = 'pyd_ai_builtin'
|
|
129
|
+
|
|
126
130
|
|
|
127
131
|
class AGUIApp(Generic[AgentDepsT, OutputDataT], Starlette):
|
|
128
132
|
"""ASGI application for running Pydantic AI agents with AG-UI protocol support."""
|
|
@@ -487,20 +491,37 @@ async def _handle_model_request_event( # noqa: C901
|
|
|
487
491
|
stream_ctx.part_end = TextMessageEndEvent(
|
|
488
492
|
message_id=message_id,
|
|
489
493
|
)
|
|
490
|
-
elif isinstance(part,
|
|
494
|
+
elif isinstance(part, BaseToolCallPart):
|
|
495
|
+
tool_call_id = part.tool_call_id
|
|
496
|
+
if isinstance(part, BuiltinToolCallPart):
|
|
497
|
+
builtin_tool_call_id = '|'.join(
|
|
498
|
+
[_BUILTIN_TOOL_CALL_ID_PREFIX, part.provider_name or '', tool_call_id]
|
|
499
|
+
)
|
|
500
|
+
stream_ctx.builtin_tool_call_ids[tool_call_id] = builtin_tool_call_id
|
|
501
|
+
tool_call_id = builtin_tool_call_id
|
|
502
|
+
|
|
491
503
|
message_id = stream_ctx.message_id or stream_ctx.new_message_id()
|
|
492
504
|
yield ToolCallStartEvent(
|
|
493
|
-
tool_call_id=
|
|
505
|
+
tool_call_id=tool_call_id,
|
|
494
506
|
tool_call_name=part.tool_name,
|
|
495
507
|
parent_message_id=message_id,
|
|
496
508
|
)
|
|
497
509
|
if part.args:
|
|
498
510
|
yield ToolCallArgsEvent(
|
|
499
|
-
tool_call_id=
|
|
500
|
-
delta=part.
|
|
511
|
+
tool_call_id=tool_call_id,
|
|
512
|
+
delta=part.args_as_json_str(),
|
|
501
513
|
)
|
|
502
514
|
stream_ctx.part_end = ToolCallEndEvent(
|
|
503
|
-
tool_call_id=
|
|
515
|
+
tool_call_id=tool_call_id,
|
|
516
|
+
)
|
|
517
|
+
elif isinstance(part, BuiltinToolReturnPart): # pragma: no branch
|
|
518
|
+
tool_call_id = stream_ctx.builtin_tool_call_ids[part.tool_call_id]
|
|
519
|
+
yield ToolCallResultEvent(
|
|
520
|
+
message_id=stream_ctx.new_message_id(),
|
|
521
|
+
type=EventType.TOOL_CALL_RESULT,
|
|
522
|
+
role='tool',
|
|
523
|
+
tool_call_id=tool_call_id,
|
|
524
|
+
content=part.model_response_str(),
|
|
504
525
|
)
|
|
505
526
|
|
|
506
527
|
elif isinstance(agent_event, PartDeltaEvent):
|
|
@@ -512,9 +533,12 @@ async def _handle_model_request_event( # noqa: C901
|
|
|
512
533
|
delta=delta.content_delta,
|
|
513
534
|
)
|
|
514
535
|
elif isinstance(delta, ToolCallPartDelta): # pragma: no branch
|
|
515
|
-
|
|
536
|
+
tool_call_id = delta.tool_call_id
|
|
537
|
+
assert tool_call_id, '`ToolCallPartDelta.tool_call_id` must be set'
|
|
538
|
+
if tool_call_id in stream_ctx.builtin_tool_call_ids:
|
|
539
|
+
tool_call_id = stream_ctx.builtin_tool_call_ids[tool_call_id]
|
|
516
540
|
yield ToolCallArgsEvent(
|
|
517
|
-
tool_call_id=
|
|
541
|
+
tool_call_id=tool_call_id,
|
|
518
542
|
delta=delta.args_delta if isinstance(delta.args_delta, str) else json.dumps(delta.args_delta),
|
|
519
543
|
)
|
|
520
544
|
elif isinstance(delta, ThinkingPartDelta): # pragma: no branch
|
|
@@ -550,9 +574,8 @@ async def _handle_tool_result_event(
|
|
|
550
574
|
if not isinstance(result, ToolReturnPart):
|
|
551
575
|
return
|
|
552
576
|
|
|
553
|
-
message_id = stream_ctx.new_message_id()
|
|
554
577
|
yield ToolCallResultEvent(
|
|
555
|
-
message_id=
|
|
578
|
+
message_id=stream_ctx.new_message_id(),
|
|
556
579
|
type=EventType.TOOL_CALL_RESULT,
|
|
557
580
|
role='tool',
|
|
558
581
|
tool_call_id=result.tool_call_id,
|
|
@@ -579,7 +602,9 @@ def _messages_from_ag_ui(messages: list[Message]) -> list[ModelMessage]:
|
|
|
579
602
|
request_parts: list[ModelRequestPart] | None = None
|
|
580
603
|
response_parts: list[ModelResponsePart] | None = None
|
|
581
604
|
for msg in messages:
|
|
582
|
-
if isinstance(msg, UserMessage | SystemMessage | DeveloperMessage
|
|
605
|
+
if isinstance(msg, UserMessage | SystemMessage | DeveloperMessage) or (
|
|
606
|
+
isinstance(msg, ToolMessage) and not msg.tool_call_id.startswith(_BUILTIN_TOOL_CALL_ID_PREFIX)
|
|
607
|
+
):
|
|
583
608
|
if request_parts is None:
|
|
584
609
|
request_parts = []
|
|
585
610
|
result.append(ModelRequest(parts=request_parts))
|
|
@@ -589,44 +614,71 @@ def _messages_from_ag_ui(messages: list[Message]) -> list[ModelMessage]:
|
|
|
589
614
|
request_parts.append(UserPromptPart(content=msg.content))
|
|
590
615
|
elif isinstance(msg, SystemMessage | DeveloperMessage):
|
|
591
616
|
request_parts.append(SystemPromptPart(content=msg.content))
|
|
592
|
-
|
|
593
|
-
|
|
617
|
+
else:
|
|
618
|
+
tool_call_id = msg.tool_call_id
|
|
619
|
+
tool_name = tool_calls.get(tool_call_id)
|
|
594
620
|
if tool_name is None: # pragma: no cover
|
|
595
|
-
raise _ToolCallNotFoundError(tool_call_id=
|
|
621
|
+
raise _ToolCallNotFoundError(tool_call_id=tool_call_id)
|
|
596
622
|
|
|
597
623
|
request_parts.append(
|
|
598
624
|
ToolReturnPart(
|
|
599
625
|
tool_name=tool_name,
|
|
600
626
|
content=msg.content,
|
|
601
|
-
tool_call_id=
|
|
627
|
+
tool_call_id=tool_call_id,
|
|
602
628
|
)
|
|
603
629
|
)
|
|
604
|
-
else:
|
|
605
|
-
assert_never(msg)
|
|
606
630
|
|
|
607
|
-
elif isinstance(msg, AssistantMessage):
|
|
631
|
+
elif isinstance(msg, AssistantMessage) or ( # pragma: no branch
|
|
632
|
+
isinstance(msg, ToolMessage) and msg.tool_call_id.startswith(_BUILTIN_TOOL_CALL_ID_PREFIX)
|
|
633
|
+
):
|
|
608
634
|
if response_parts is None:
|
|
609
635
|
response_parts = []
|
|
610
636
|
result.append(ModelResponse(parts=response_parts))
|
|
611
637
|
request_parts = None
|
|
612
638
|
|
|
613
|
-
if msg
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
639
|
+
if isinstance(msg, AssistantMessage):
|
|
640
|
+
if msg.content:
|
|
641
|
+
response_parts.append(TextPart(content=msg.content))
|
|
642
|
+
|
|
643
|
+
if msg.tool_calls:
|
|
644
|
+
for tool_call in msg.tool_calls:
|
|
645
|
+
tool_call_id = tool_call.id
|
|
646
|
+
tool_name = tool_call.function.name
|
|
647
|
+
tool_calls[tool_call_id] = tool_name
|
|
648
|
+
|
|
649
|
+
if tool_call_id.startswith(_BUILTIN_TOOL_CALL_ID_PREFIX):
|
|
650
|
+
_, provider_name, tool_call_id = tool_call_id.split('|', 2)
|
|
651
|
+
response_parts.append(
|
|
652
|
+
BuiltinToolCallPart(
|
|
653
|
+
tool_name=tool_name,
|
|
654
|
+
args=tool_call.function.arguments,
|
|
655
|
+
tool_call_id=tool_call_id,
|
|
656
|
+
provider_name=provider_name,
|
|
657
|
+
)
|
|
658
|
+
)
|
|
659
|
+
else:
|
|
660
|
+
response_parts.append(
|
|
661
|
+
ToolCallPart(
|
|
662
|
+
tool_name=tool_name,
|
|
663
|
+
tool_call_id=tool_call_id,
|
|
664
|
+
args=tool_call.function.arguments,
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
else:
|
|
668
|
+
tool_call_id = msg.tool_call_id
|
|
669
|
+
tool_name = tool_calls.get(tool_call_id)
|
|
670
|
+
if tool_name is None: # pragma: no cover
|
|
671
|
+
raise _ToolCallNotFoundError(tool_call_id=tool_call_id)
|
|
672
|
+
_, provider_name, tool_call_id = tool_call_id.split('|', 2)
|
|
619
673
|
|
|
620
|
-
response_parts.
|
|
621
|
-
|
|
622
|
-
tool_name=
|
|
623
|
-
|
|
624
|
-
|
|
674
|
+
response_parts.append(
|
|
675
|
+
BuiltinToolReturnPart(
|
|
676
|
+
tool_name=tool_name,
|
|
677
|
+
content=msg.content,
|
|
678
|
+
tool_call_id=tool_call_id,
|
|
679
|
+
provider_name=provider_name,
|
|
625
680
|
)
|
|
626
|
-
for tool_call in msg.tool_calls
|
|
627
681
|
)
|
|
628
|
-
else:
|
|
629
|
-
assert_never(msg)
|
|
630
682
|
|
|
631
683
|
return result
|
|
632
684
|
|
|
@@ -687,6 +739,7 @@ class _RequestStreamContext:
|
|
|
687
739
|
message_id: str = ''
|
|
688
740
|
part_end: BaseEvent | None = None
|
|
689
741
|
thinking: bool = False
|
|
742
|
+
builtin_tool_call_ids: dict[str, str] = field(default_factory=dict)
|
|
690
743
|
|
|
691
744
|
def new_message_id(self) -> str:
|
|
692
745
|
"""Generate a new message ID for the request stream.
|
|
@@ -18,6 +18,9 @@ class AbstractBuiltinTool(ABC):
|
|
|
18
18
|
The builtin tools are passed to the model as part of the `ModelRequestParameters`.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
kind: str = 'unknown_builtin_tool'
|
|
22
|
+
"""Built-in tool identifier, this should be available on all built-in tools as a discriminator."""
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
@dataclass(kw_only=True)
|
|
23
26
|
class WebSearchTool(AbstractBuiltinTool):
|
|
@@ -80,6 +83,9 @@ class WebSearchTool(AbstractBuiltinTool):
|
|
|
80
83
|
* Anthropic
|
|
81
84
|
"""
|
|
82
85
|
|
|
86
|
+
kind: str = 'web_search'
|
|
87
|
+
"""The kind of tool."""
|
|
88
|
+
|
|
83
89
|
|
|
84
90
|
class WebSearchUserLocation(TypedDict, total=False):
|
|
85
91
|
"""Allows you to localize search results based on a user's location.
|
|
@@ -113,6 +119,9 @@ class CodeExecutionTool(AbstractBuiltinTool):
|
|
|
113
119
|
* Google
|
|
114
120
|
"""
|
|
115
121
|
|
|
122
|
+
kind: str = 'code_execution'
|
|
123
|
+
"""The kind of tool."""
|
|
124
|
+
|
|
116
125
|
|
|
117
126
|
class UrlContextTool(AbstractBuiltinTool):
|
|
118
127
|
"""Allows your agent to access contents from URLs.
|
|
@@ -121,3 +130,6 @@ class UrlContextTool(AbstractBuiltinTool):
|
|
|
121
130
|
|
|
122
131
|
* Google
|
|
123
132
|
"""
|
|
133
|
+
|
|
134
|
+
kind: str = 'url_context'
|
|
135
|
+
"""The kind of tool."""
|
{pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_model.py
RENAMED
|
@@ -4,7 +4,7 @@ from collections.abc import AsyncIterator, Callable
|
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from pydantic import ConfigDict, with_config
|
|
10
10
|
from temporalio import activity, workflow
|
|
@@ -30,7 +30,8 @@ from ._run_context import TemporalRunContext
|
|
|
30
30
|
@with_config(ConfigDict(arbitrary_types_allowed=True))
|
|
31
31
|
class _RequestParams:
|
|
32
32
|
messages: list[ModelMessage]
|
|
33
|
-
model_settings
|
|
33
|
+
# `model_settings` can't be a `ModelSettings` because Temporal would end up dropping fields only defined on its subclasses.
|
|
34
|
+
model_settings: dict[str, Any] | None
|
|
34
35
|
model_request_parameters: ModelRequestParameters
|
|
35
36
|
serialized_run_context: Any
|
|
36
37
|
|
|
@@ -82,7 +83,11 @@ class TemporalModel(WrapperModel):
|
|
|
82
83
|
|
|
83
84
|
@activity.defn(name=f'{activity_name_prefix}__model_request')
|
|
84
85
|
async def request_activity(params: _RequestParams) -> ModelResponse:
|
|
85
|
-
return await self.wrapped.request(
|
|
86
|
+
return await self.wrapped.request(
|
|
87
|
+
params.messages,
|
|
88
|
+
cast(ModelSettings | None, params.model_settings),
|
|
89
|
+
params.model_request_parameters,
|
|
90
|
+
)
|
|
86
91
|
|
|
87
92
|
self.request_activity = request_activity
|
|
88
93
|
|
|
@@ -92,7 +97,10 @@ class TemporalModel(WrapperModel):
|
|
|
92
97
|
|
|
93
98
|
run_context = self.run_context_type.deserialize_run_context(params.serialized_run_context, deps=deps)
|
|
94
99
|
async with self.wrapped.request_stream(
|
|
95
|
-
params.messages,
|
|
100
|
+
params.messages,
|
|
101
|
+
cast(ModelSettings | None, params.model_settings),
|
|
102
|
+
params.model_request_parameters,
|
|
103
|
+
run_context,
|
|
96
104
|
) as streamed_response:
|
|
97
105
|
await self.event_stream_handler(run_context, streamed_response)
|
|
98
106
|
|
|
@@ -124,7 +132,7 @@ class TemporalModel(WrapperModel):
|
|
|
124
132
|
activity=self.request_activity,
|
|
125
133
|
arg=_RequestParams(
|
|
126
134
|
messages=messages,
|
|
127
|
-
model_settings=model_settings,
|
|
135
|
+
model_settings=cast(dict[str, Any] | None, model_settings),
|
|
128
136
|
model_request_parameters=model_request_parameters,
|
|
129
137
|
serialized_run_context=None,
|
|
130
138
|
),
|
|
@@ -161,7 +169,7 @@ class TemporalModel(WrapperModel):
|
|
|
161
169
|
args=[
|
|
162
170
|
_RequestParams(
|
|
163
171
|
messages=messages,
|
|
164
|
-
model_settings=model_settings,
|
|
172
|
+
model_settings=cast(dict[str, Any] | None, model_settings),
|
|
165
173
|
model_request_parameters=model_request_parameters,
|
|
166
174
|
serialized_run_context=serialized_run_context,
|
|
167
175
|
),
|
{pydantic_ai_slim-1.0.8 → pydantic_ai_slim-1.0.9}/pydantic_ai/durable_exec/temporal/_run_context.py
RENAMED
|
@@ -9,7 +9,7 @@ from pydantic_ai.tools import AgentDepsT, RunContext
|
|
|
9
9
|
class TemporalRunContext(RunContext[AgentDepsT]):
|
|
10
10
|
"""The [`RunContext`][pydantic_ai.tools.RunContext] subclass to use to serialize and deserialize the run context for use inside a Temporal activity.
|
|
11
11
|
|
|
12
|
-
By default, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry` and `run_step` attributes will be available.
|
|
12
|
+
By default, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry`, `max_retries` and `run_step` attributes will be available.
|
|
13
13
|
To make another attribute available, create a `TemporalRunContext` subclass with a custom `serialize_run_context` class method that returns a dictionary that includes the attribute and pass it to [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent].
|
|
14
14
|
"""
|
|
15
15
|
|
|
@@ -42,6 +42,7 @@ class TemporalRunContext(RunContext[AgentDepsT]):
|
|
|
42
42
|
'tool_name': ctx.tool_name,
|
|
43
43
|
'tool_call_approved': ctx.tool_call_approved,
|
|
44
44
|
'retry': ctx.retry,
|
|
45
|
+
'max_retries': ctx.max_retries,
|
|
45
46
|
'run_step': ctx.run_step,
|
|
46
47
|
}
|
|
47
48
|
|