pydantic-ai-slim 1.3.0__tar.gz → 1.4.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.

Potentially problematic release.


This version of pydantic-ai-slim might be problematic. Click here for more details.

Files changed (138) hide show
  1. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/PKG-INFO +3 -3
  2. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/__init__.py +2 -0
  3. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_agent_graph.py +10 -2
  4. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/__init__.py +3 -11
  5. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/builtin_tools.py +71 -0
  6. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/__init__.py +11 -3
  7. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/anthropic.py +130 -9
  8. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/google.py +26 -14
  9. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/openai.py +190 -13
  10. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/.gitignore +0 -0
  11. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/LICENSE +0 -0
  12. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/README.md +0 -0
  13. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/__main__.py +0 -0
  14. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_a2a.py +0 -0
  15. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_cli.py +0 -0
  16. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_function_schema.py +0 -0
  17. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_griffe.py +0 -0
  18. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_instrumentation.py +0 -0
  19. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_json_schema.py +0 -0
  20. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_mcp.py +0 -0
  21. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_otel_messages.py +0 -0
  22. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_output.py +0 -0
  23. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_parts_manager.py +0 -0
  24. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_run_context.py +0 -0
  25. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_system_prompt.py +0 -0
  26. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_thinking_part.py +0 -0
  27. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_tool_manager.py +0 -0
  28. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/_utils.py +0 -0
  29. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/ag_ui.py +0 -0
  30. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/abstract.py +0 -0
  31. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/wrapper.py +0 -0
  32. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/__init__.py +0 -0
  33. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  34. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/tavily.py +0 -0
  35. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/direct.py +0 -0
  36. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/__init__.py +0 -0
  37. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  38. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
  39. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  40. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  41. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  42. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  43. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
  44. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  45. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  46. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  47. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  48. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  49. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  50. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  51. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
  52. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  53. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  54. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  55. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  56. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  57. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  58. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/exceptions.py +0 -0
  59. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/__init__.py +0 -0
  60. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/aci.py +0 -0
  61. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/langchain.py +0 -0
  62. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/format_prompt.py +0 -0
  63. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/mcp.py +0 -0
  64. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/messages.py +0 -0
  65. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/bedrock.py +0 -0
  66. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/cohere.py +0 -0
  67. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/fallback.py +0 -0
  68. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/function.py +0 -0
  69. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/gemini.py +0 -0
  70. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/groq.py +0 -0
  71. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/huggingface.py +0 -0
  72. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/instrumented.py +0 -0
  73. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/mcp_sampling.py +0 -0
  74. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/mistral.py +0 -0
  75. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/test.py +0 -0
  76. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/wrapper.py +0 -0
  77. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/output.py +0 -0
  78. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/__init__.py +0 -0
  79. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/amazon.py +0 -0
  80. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/anthropic.py +0 -0
  81. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/cohere.py +0 -0
  82. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/deepseek.py +0 -0
  83. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/google.py +0 -0
  84. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/grok.py +0 -0
  85. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/groq.py +0 -0
  86. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/harmony.py +0 -0
  87. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/meta.py +0 -0
  88. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/mistral.py +0 -0
  89. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/moonshotai.py +0 -0
  90. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/openai.py +0 -0
  91. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/qwen.py +0 -0
  92. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/__init__.py +0 -0
  93. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/anthropic.py +0 -0
  94. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/azure.py +0 -0
  95. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/bedrock.py +0 -0
  96. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/cerebras.py +0 -0
  97. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/cohere.py +0 -0
  98. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/deepseek.py +0 -0
  99. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/fireworks.py +0 -0
  100. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/gateway.py +0 -0
  101. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/github.py +0 -0
  102. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google.py +0 -0
  103. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google_gla.py +0 -0
  104. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google_vertex.py +0 -0
  105. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/grok.py +0 -0
  106. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/groq.py +0 -0
  107. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/heroku.py +0 -0
  108. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/huggingface.py +0 -0
  109. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/litellm.py +0 -0
  110. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/mistral.py +0 -0
  111. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/moonshotai.py +0 -0
  112. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/nebius.py +0 -0
  113. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/ollama.py +0 -0
  114. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/openai.py +0 -0
  115. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/openrouter.py +0 -0
  116. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/ovhcloud.py +0 -0
  117. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/together.py +0 -0
  118. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/vercel.py +0 -0
  119. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/py.typed +0 -0
  120. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/result.py +0 -0
  121. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/retries.py +0 -0
  122. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/run.py +0 -0
  123. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/settings.py +0 -0
  124. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/tools.py +0 -0
  125. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/__init__.py +0 -0
  126. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/_dynamic.py +0 -0
  127. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/abstract.py +0 -0
  128. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/approval_required.py +0 -0
  129. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/combined.py +0 -0
  130. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/external.py +0 -0
  131. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/filtered.py +0 -0
  132. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/function.py +0 -0
  133. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/prefixed.py +0 -0
  134. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/prepared.py +0 -0
  135. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/renamed.py +0 -0
  136. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/wrapper.py +0 -0
  137. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pydantic_ai/usage.py +0 -0
  138. {pydantic_ai_slim-1.3.0 → pydantic_ai_slim-1.4.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 1.3.0
3
+ Version: 1.4.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.3.0
36
+ Requires-Dist: pydantic-graph==1.4.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,7 @@ 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.3.0; extra == 'evals'
60
+ Requires-Dist: pydantic-evals==1.4.0; extra == 'evals'
61
61
  Provides-Extra: google
62
62
  Requires-Dist: google-genai>=1.46.0; extra == 'google'
63
63
  Provides-Extra: groq
@@ -12,6 +12,7 @@ from .agent import (
12
12
  from .builtin_tools import (
13
13
  CodeExecutionTool,
14
14
  ImageGenerationTool,
15
+ MCPServerTool,
15
16
  MemoryTool,
16
17
  UrlContextTool,
17
18
  WebSearchTool,
@@ -213,6 +214,7 @@ __all__ = (
213
214
  'CodeExecutionTool',
214
215
  'ImageGenerationTool',
215
216
  'MemoryTool',
217
+ 'MCPServerTool',
216
218
  # output
217
219
  'ToolOutput',
218
220
  'NativeOutput',
@@ -588,7 +588,11 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
588
588
  # as the empty response and request will not create any items in the API payload,
589
589
  # in the hope the model will return a non-empty response this time.
590
590
  ctx.state.increment_retries(ctx.deps.max_result_retries, model_settings=ctx.deps.model_settings)
591
- self._next_node = ModelRequestNode[DepsT, NodeRunEndT](_messages.ModelRequest(parts=[]))
591
+ run_context = build_run_context(ctx)
592
+ instructions = await ctx.deps.get_instructions(run_context)
593
+ self._next_node = ModelRequestNode[DepsT, NodeRunEndT](
594
+ _messages.ModelRequest(parts=[], instructions=instructions)
595
+ )
592
596
  return
593
597
 
594
598
  text = ''
@@ -652,7 +656,11 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
652
656
  ctx.state.increment_retries(
653
657
  ctx.deps.max_result_retries, error=e, model_settings=ctx.deps.model_settings
654
658
  )
655
- self._next_node = ModelRequestNode[DepsT, NodeRunEndT](_messages.ModelRequest(parts=[e.tool_retry]))
659
+ run_context = build_run_context(ctx)
660
+ instructions = await ctx.deps.get_instructions(run_context)
661
+ self._next_node = ModelRequestNode[DepsT, NodeRunEndT](
662
+ _messages.ModelRequest(parts=[e.tool_retry], instructions=instructions)
663
+ )
656
664
 
657
665
  self._events_iterator = _run_stream()
658
666
 
@@ -542,6 +542,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
542
542
  """
543
543
  if infer_name and self.name is None:
544
544
  self._infer_name(inspect.currentframe())
545
+
545
546
  model_used = self._get_model(model)
546
547
  del model
547
548
 
@@ -607,16 +608,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
607
608
  else:
608
609
  instrumentation_settings = None
609
610
  tracer = NoOpTracer()
610
- if builtin_tools:
611
- # Deduplicate builtin tools passed to the agent and the run based on type
612
- builtin_tools = list(
613
- {
614
- **({type(tool): tool for tool in self._builtin_tools or []}),
615
- **({type(tool): tool for tool in builtin_tools}),
616
- }.values()
617
- )
618
- else:
619
- builtin_tools = list(self._builtin_tools)
611
+
620
612
  graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, RunOutputDataT](
621
613
  user_deps=deps,
622
614
  prompt=user_prompt,
@@ -629,7 +621,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
629
621
  output_schema=output_schema,
630
622
  output_validators=output_validators,
631
623
  history_processors=self.history_processors,
632
- builtin_tools=builtin_tools,
624
+ builtin_tools=[*self._builtin_tools, *(builtin_tools or [])],
633
625
  tool_manager=tool_manager,
634
626
  tracer=tracer,
635
627
  get_instructions=get_instructions,
@@ -16,6 +16,7 @@ __all__ = (
16
16
  'UrlContextTool',
17
17
  'ImageGenerationTool',
18
18
  'MemoryTool',
19
+ 'MCPServerTool',
19
20
  )
20
21
 
21
22
  _BUILTIN_TOOL_TYPES: dict[str, type[AbstractBuiltinTool]] = {}
@@ -33,6 +34,14 @@ class AbstractBuiltinTool(ABC):
33
34
  kind: str = 'unknown_builtin_tool'
34
35
  """Built-in tool identifier, this should be available on all built-in tools as a discriminator."""
35
36
 
37
+ @property
38
+ def unique_id(self) -> str:
39
+ """A unique identifier for the builtin tool.
40
+
41
+ If multiple instances of the same builtin tool can be passed to the model, subclasses should override this property to allow them to be distinguished.
42
+ """
43
+ return self.kind
44
+
36
45
  def __init_subclass__(cls, **kwargs: Any) -> None:
37
46
  super().__init_subclass__(**kwargs)
38
47
  _BUILTIN_TOOL_TYPES[cls.kind] = cls
@@ -263,6 +272,68 @@ class MemoryTool(AbstractBuiltinTool):
263
272
  """The kind of tool."""
264
273
 
265
274
 
275
+ @dataclass(kw_only=True)
276
+ class MCPServerTool(AbstractBuiltinTool):
277
+ """A builtin tool that allows your agent to use MCP servers.
278
+
279
+ Supported by:
280
+
281
+ * OpenAI Responses
282
+ * Anthropic
283
+ """
284
+
285
+ id: str
286
+ """A unique identifier for the MCP server."""
287
+
288
+ url: str
289
+ """The URL of the MCP server to use.
290
+
291
+ For OpenAI Responses, it is possible to use `connector_id` by providing it as `x-openai-connector:<connector_id>`.
292
+ """
293
+
294
+ authorization_token: str | None = None
295
+ """Authorization header to use when making requests to the MCP server.
296
+
297
+ Supported by:
298
+
299
+ * OpenAI Responses
300
+ * Anthropic
301
+ """
302
+
303
+ description: str | None = None
304
+ """A description of the MCP server.
305
+
306
+ Supported by:
307
+
308
+ * OpenAI Responses
309
+ """
310
+
311
+ allowed_tools: list[str] | None = None
312
+ """A list of tools that the MCP server can use.
313
+
314
+ Supported by:
315
+
316
+ * OpenAI Responses
317
+ * Anthropic
318
+ """
319
+
320
+ headers: dict[str, str] | None = None
321
+ """Optional HTTP headers to send to the MCP server.
322
+
323
+ Use for authentication or other purposes.
324
+
325
+ Supported by:
326
+
327
+ * OpenAI Responses
328
+ """
329
+
330
+ kind: str = 'mcp_server'
331
+
332
+ @property
333
+ def unique_id(self) -> str:
334
+ return ':'.join([self.kind, self.id])
335
+
336
+
266
337
  def _tool_discriminator(tool_data: dict[str, Any] | AbstractBuiltinTool) -> str:
267
338
  if isinstance(tool_data, dict):
268
339
  return tool_data.get('kind', AbstractBuiltinTool.kind)
@@ -410,9 +410,17 @@ class Model(ABC):
410
410
  they need to customize the preparation flow further, but most implementations should simply call
411
411
  ``self.prepare_request(...)`` at the start of their ``request`` (and related) methods.
412
412
  """
413
- merged_settings = merge_model_settings(self.settings, model_settings)
414
- customized_parameters = self.customize_request_parameters(model_request_parameters)
415
- return merged_settings, customized_parameters
413
+ model_settings = merge_model_settings(self.settings, model_settings)
414
+
415
+ if builtin_tools := model_request_parameters.builtin_tools:
416
+ # Deduplicate builtin tools
417
+ model_request_parameters = replace(
418
+ model_request_parameters,
419
+ builtin_tools=list({tool.unique_id: tool for tool in builtin_tools}.values()),
420
+ )
421
+
422
+ model_request_parameters = self.customize_request_parameters(model_request_parameters)
423
+ return model_settings, model_request_parameters
416
424
 
417
425
  @property
418
426
  @abstractmethod
@@ -3,7 +3,7 @@ from __future__ import annotations as _annotations
3
3
  import io
4
4
  from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator
5
5
  from contextlib import asynccontextmanager
6
- from dataclasses import dataclass, field
6
+ from dataclasses import dataclass, field, replace
7
7
  from datetime import datetime
8
8
  from typing import Any, Literal, cast, overload
9
9
 
@@ -13,7 +13,7 @@ from typing_extensions import assert_never
13
13
  from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage
14
14
  from .._run_context import RunContext
15
15
  from .._utils import guard_tool_call_id as _guard_tool_call_id
16
- from ..builtin_tools import CodeExecutionTool, MemoryTool, WebSearchTool
16
+ from ..builtin_tools import CodeExecutionTool, MCPServerTool, MemoryTool, WebSearchTool
17
17
  from ..exceptions import UserError
18
18
  from ..messages import (
19
19
  BinaryContent,
@@ -68,6 +68,9 @@ try:
68
68
  BetaContentBlockParam,
69
69
  BetaImageBlockParam,
70
70
  BetaInputJSONDelta,
71
+ BetaMCPToolResultBlock,
72
+ BetaMCPToolUseBlock,
73
+ BetaMCPToolUseBlockParam,
71
74
  BetaMemoryTool20250818Param,
72
75
  BetaMessage,
73
76
  BetaMessageParam,
@@ -82,6 +85,8 @@ try:
82
85
  BetaRawMessageStreamEvent,
83
86
  BetaRedactedThinkingBlock,
84
87
  BetaRedactedThinkingBlockParam,
88
+ BetaRequestMCPServerToolConfigurationParam,
89
+ BetaRequestMCPServerURLDefinitionParam,
85
90
  BetaServerToolUseBlock,
86
91
  BetaServerToolUseBlockParam,
87
92
  BetaSignatureDelta,
@@ -264,7 +269,7 @@ class AnthropicModel(Model):
264
269
  ) -> BetaMessage | AsyncStream[BetaRawMessageStreamEvent]:
265
270
  # standalone function to make it easier to override
266
271
  tools = self._get_tools(model_request_parameters)
267
- tools, beta_features = self._add_builtin_tools(tools, model_request_parameters)
272
+ tools, mcp_servers, beta_features = self._add_builtin_tools(tools, model_request_parameters)
268
273
 
269
274
  tool_choice: BetaToolChoiceParam | None
270
275
 
@@ -300,6 +305,7 @@ class AnthropicModel(Model):
300
305
  model=self._model_name,
301
306
  tools=tools or OMIT,
302
307
  tool_choice=tool_choice or OMIT,
308
+ mcp_servers=mcp_servers or OMIT,
303
309
  stream=stream,
304
310
  thinking=model_settings.get('anthropic_thinking', OMIT),
305
311
  stop_sequences=model_settings.get('stop_sequences', OMIT),
@@ -318,11 +324,14 @@ class AnthropicModel(Model):
318
324
  def _process_response(self, response: BetaMessage) -> ModelResponse:
319
325
  """Process a non-streamed response, and prepare a message to return."""
320
326
  items: list[ModelResponsePart] = []
327
+ builtin_tool_calls: dict[str, BuiltinToolCallPart] = {}
321
328
  for item in response.content:
322
329
  if isinstance(item, BetaTextBlock):
323
330
  items.append(TextPart(content=item.text))
324
331
  elif isinstance(item, BetaServerToolUseBlock):
325
- items.append(_map_server_tool_use_block(item, self.system))
332
+ call_part = _map_server_tool_use_block(item, self.system)
333
+ builtin_tool_calls[call_part.tool_call_id] = call_part
334
+ items.append(call_part)
326
335
  elif isinstance(item, BetaWebSearchToolResultBlock):
327
336
  items.append(_map_web_search_tool_result_block(item, self.system))
328
337
  elif isinstance(item, BetaCodeExecutionToolResultBlock):
@@ -333,6 +342,13 @@ class AnthropicModel(Model):
333
342
  )
334
343
  elif isinstance(item, BetaThinkingBlock):
335
344
  items.append(ThinkingPart(content=item.thinking, signature=item.signature, provider_name=self.system))
345
+ elif isinstance(item, BetaMCPToolUseBlock):
346
+ call_part = _map_mcp_server_use_block(item, self.system)
347
+ builtin_tool_calls[call_part.tool_call_id] = call_part
348
+ items.append(call_part)
349
+ elif isinstance(item, BetaMCPToolResultBlock):
350
+ call_part = builtin_tool_calls.get(item.tool_use_id)
351
+ items.append(_map_mcp_server_result_block(item, call_part, self.system))
336
352
  else:
337
353
  assert isinstance(item, BetaToolUseBlock), f'unexpected item type {type(item)}'
338
354
  items.append(
@@ -383,8 +399,9 @@ class AnthropicModel(Model):
383
399
 
384
400
  def _add_builtin_tools(
385
401
  self, tools: list[BetaToolUnionParam], model_request_parameters: ModelRequestParameters
386
- ) -> tuple[list[BetaToolUnionParam], list[str]]:
402
+ ) -> tuple[list[BetaToolUnionParam], list[BetaRequestMCPServerURLDefinitionParam], list[str]]:
387
403
  beta_features: list[str] = []
404
+ mcp_servers: list[BetaRequestMCPServerURLDefinitionParam] = []
388
405
  for tool in model_request_parameters.builtin_tools:
389
406
  if isinstance(tool, WebSearchTool):
390
407
  user_location = UserLocation(type='approximate', **tool.user_location) if tool.user_location else None
@@ -408,11 +425,26 @@ class AnthropicModel(Model):
408
425
  tools = [tool for tool in tools if tool['name'] != 'memory']
409
426
  tools.append(BetaMemoryTool20250818Param(name='memory', type='memory_20250818'))
410
427
  beta_features.append('context-management-2025-06-27')
428
+ elif isinstance(tool, MCPServerTool) and tool.url:
429
+ mcp_server_url_definition_param = BetaRequestMCPServerURLDefinitionParam(
430
+ type='url',
431
+ name=tool.id,
432
+ url=tool.url,
433
+ )
434
+ if tool.allowed_tools is not None: # pragma: no branch
435
+ mcp_server_url_definition_param['tool_configuration'] = BetaRequestMCPServerToolConfigurationParam(
436
+ enabled=bool(tool.allowed_tools),
437
+ allowed_tools=tool.allowed_tools,
438
+ )
439
+ if tool.authorization_token: # pragma: no cover
440
+ mcp_server_url_definition_param['authorization_token'] = tool.authorization_token
441
+ mcp_servers.append(mcp_server_url_definition_param)
442
+ beta_features.append('mcp-client-2025-04-04')
411
443
  else: # pragma: no cover
412
444
  raise UserError(
413
445
  f'`{tool.__class__.__name__}` is not supported by `AnthropicModel`. If it should be, please file an issue.'
414
446
  )
415
- return tools, beta_features
447
+ return tools, mcp_servers, beta_features
416
448
 
417
449
  async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[BetaMessageParam]]: # noqa: C901
418
450
  """Just maps a `pydantic_ai.Message` to a `anthropic.types.MessageParam`."""
@@ -458,6 +490,8 @@ class AnthropicModel(Model):
458
490
  | BetaCodeExecutionToolResultBlockParam
459
491
  | BetaThinkingBlockParam
460
492
  | BetaRedactedThinkingBlockParam
493
+ | BetaMCPToolUseBlockParam
494
+ | BetaMCPToolResultBlock
461
495
  ] = []
462
496
  for response_part in m.parts:
463
497
  if isinstance(response_part, TextPart):
@@ -508,7 +542,7 @@ class AnthropicModel(Model):
508
542
  input=response_part.args_as_dict(),
509
543
  )
510
544
  assistant_content_params.append(server_tool_use_block_param)
511
- elif response_part.tool_name == CodeExecutionTool.kind: # pragma: no branch
545
+ elif response_part.tool_name == CodeExecutionTool.kind:
512
546
  server_tool_use_block_param = BetaServerToolUseBlockParam(
513
547
  id=tool_use_id,
514
548
  type='server_tool_use',
@@ -516,6 +550,21 @@ class AnthropicModel(Model):
516
550
  input=response_part.args_as_dict(),
517
551
  )
518
552
  assistant_content_params.append(server_tool_use_block_param)
553
+ elif (
554
+ response_part.tool_name.startswith(MCPServerTool.kind)
555
+ and (server_id := response_part.tool_name.split(':', 1)[1])
556
+ and (args := response_part.args_as_dict())
557
+ and (tool_name := args.get('tool_name'))
558
+ and (tool_args := args.get('tool_args'))
559
+ ): # pragma: no branch
560
+ mcp_tool_use_block_param = BetaMCPToolUseBlockParam(
561
+ id=tool_use_id,
562
+ type='mcp_tool_use',
563
+ server_name=server_id,
564
+ name=tool_name,
565
+ input=tool_args,
566
+ )
567
+ assistant_content_params.append(mcp_tool_use_block_param)
519
568
  elif isinstance(response_part, BuiltinToolReturnPart):
520
569
  if response_part.provider_name == self.system:
521
570
  tool_use_id = _guard_tool_call_id(t=response_part)
@@ -547,6 +596,16 @@ class AnthropicModel(Model):
547
596
  ),
548
597
  )
549
598
  )
599
+ elif response_part.tool_name.startswith(MCPServerTool.kind) and isinstance(
600
+ response_part.content, dict
601
+ ): # pragma: no branch
602
+ assistant_content_params.append(
603
+ BetaMCPToolResultBlock(
604
+ tool_use_id=tool_use_id,
605
+ type='mcp_tool_result',
606
+ **cast(dict[str, Any], response_part.content), # pyright: ignore[reportUnknownMemberType]
607
+ )
608
+ )
550
609
  elif isinstance(response_part, FilePart): # pragma: no cover
551
610
  # Files generated by models are not sent back to models that don't themselves generate files.
552
611
  pass
@@ -661,6 +720,7 @@ class AnthropicStreamedResponse(StreamedResponse):
661
720
  async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: # noqa: C901
662
721
  current_block: BetaContentBlock | None = None
663
722
 
723
+ builtin_tool_calls: dict[str, BuiltinToolCallPart] = {}
664
724
  async for event in self._response:
665
725
  if isinstance(event, BetaRawMessageStartEvent):
666
726
  self._usage = _map_usage(event, self._provider_name, self._provider_url, self._model_name)
@@ -698,9 +758,11 @@ class AnthropicStreamedResponse(StreamedResponse):
698
758
  if maybe_event is not None: # pragma: no branch
699
759
  yield maybe_event
700
760
  elif isinstance(current_block, BetaServerToolUseBlock):
761
+ call_part = _map_server_tool_use_block(current_block, self.provider_name)
762
+ builtin_tool_calls[call_part.tool_call_id] = call_part
701
763
  yield self._parts_manager.handle_part(
702
764
  vendor_part_id=event.index,
703
- part=_map_server_tool_use_block(current_block, self.provider_name),
765
+ part=call_part,
704
766
  )
705
767
  elif isinstance(current_block, BetaWebSearchToolResultBlock):
706
768
  yield self._parts_manager.handle_part(
@@ -712,6 +774,32 @@ class AnthropicStreamedResponse(StreamedResponse):
712
774
  vendor_part_id=event.index,
713
775
  part=_map_code_execution_tool_result_block(current_block, self.provider_name),
714
776
  )
777
+ elif isinstance(current_block, BetaMCPToolUseBlock):
778
+ call_part = _map_mcp_server_use_block(current_block, self.provider_name)
779
+ builtin_tool_calls[call_part.tool_call_id] = call_part
780
+
781
+ args_json = call_part.args_as_json_str()
782
+ # Drop the final `{}}` so that we can add tool args deltas
783
+ args_json_delta = args_json[:-3]
784
+ assert args_json_delta.endswith('"tool_args":'), (
785
+ f'Expected {args_json_delta!r} to end in `"tool_args":`'
786
+ )
787
+
788
+ yield self._parts_manager.handle_part(
789
+ vendor_part_id=event.index, part=replace(call_part, args=None)
790
+ )
791
+ maybe_event = self._parts_manager.handle_tool_call_delta(
792
+ vendor_part_id=event.index,
793
+ args=args_json_delta,
794
+ )
795
+ if maybe_event is not None: # pragma: no branch
796
+ yield maybe_event
797
+ elif isinstance(current_block, BetaMCPToolResultBlock):
798
+ call_part = builtin_tool_calls.get(current_block.tool_use_id)
799
+ yield self._parts_manager.handle_part(
800
+ vendor_part_id=event.index,
801
+ part=_map_mcp_server_result_block(current_block, call_part, self.provider_name),
802
+ )
715
803
 
716
804
  elif isinstance(event, BetaRawContentBlockDeltaEvent):
717
805
  if isinstance(event.delta, BetaTextDelta):
@@ -749,7 +837,16 @@ class AnthropicStreamedResponse(StreamedResponse):
749
837
  self.provider_details = {'finish_reason': raw_finish_reason}
750
838
  self.finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
751
839
 
752
- elif isinstance(event, BetaRawContentBlockStopEvent | BetaRawMessageStopEvent): # pragma: no branch
840
+ elif isinstance(event, BetaRawContentBlockStopEvent): # pragma: no branch
841
+ if isinstance(current_block, BetaMCPToolUseBlock):
842
+ maybe_event = self._parts_manager.handle_tool_call_delta(
843
+ vendor_part_id=event.index,
844
+ args='}',
845
+ )
846
+ if maybe_event is not None: # pragma: no branch
847
+ yield maybe_event
848
+ current_block = None
849
+ elif isinstance(event, BetaRawMessageStopEvent): # pragma: no branch
753
850
  current_block = None
754
851
 
755
852
  @property
@@ -817,3 +914,27 @@ def _map_code_execution_tool_result_block(
817
914
  content=code_execution_tool_result_content_ta.dump_python(item.content, mode='json'),
818
915
  tool_call_id=item.tool_use_id,
819
916
  )
917
+
918
+
919
+ def _map_mcp_server_use_block(item: BetaMCPToolUseBlock, provider_name: str) -> BuiltinToolCallPart:
920
+ return BuiltinToolCallPart(
921
+ provider_name=provider_name,
922
+ tool_name=':'.join([MCPServerTool.kind, item.server_name]),
923
+ args={
924
+ 'action': 'call_tool',
925
+ 'tool_name': item.name,
926
+ 'tool_args': cast(dict[str, Any], item.input),
927
+ },
928
+ tool_call_id=item.id,
929
+ )
930
+
931
+
932
+ def _map_mcp_server_result_block(
933
+ item: BetaMCPToolResultBlock, call_part: BuiltinToolCallPart | None, provider_name: str
934
+ ) -> BuiltinToolReturnPart:
935
+ return BuiltinToolReturnPart(
936
+ provider_name=provider_name,
937
+ tool_name=call_part.tool_name if call_part else MCPServerTool.kind,
938
+ content=item.model_dump(mode='json', include={'content', 'is_error'}),
939
+ tool_call_id=item.tool_use_id,
940
+ )
@@ -126,6 +126,8 @@ _FINISH_REASON_MAP: dict[GoogleFinishReason, FinishReason | None] = {
126
126
  GoogleFinishReason.MALFORMED_FUNCTION_CALL: 'error',
127
127
  GoogleFinishReason.IMAGE_SAFETY: 'content_filter',
128
128
  GoogleFinishReason.UNEXPECTED_TOOL_CALL: 'error',
129
+ GoogleFinishReason.IMAGE_PROHIBITED_CONTENT: 'content_filter',
130
+ GoogleFinishReason.NO_IMAGE: 'error',
129
131
  }
130
132
 
131
133
 
@@ -453,23 +455,28 @@ class GoogleModel(Model):
453
455
  def _process_response(self, response: GenerateContentResponse) -> ModelResponse:
454
456
  if not response.candidates:
455
457
  raise UnexpectedModelBehavior('Expected at least one candidate in Gemini response') # pragma: no cover
458
+
456
459
  candidate = response.candidates[0]
457
- if candidate.content is None or candidate.content.parts is None:
458
- if candidate.finish_reason == 'SAFETY':
459
- raise UnexpectedModelBehavior('Safety settings triggered', str(response))
460
- else:
461
- raise UnexpectedModelBehavior(
462
- 'Content field missing from Gemini response', str(response)
463
- ) # pragma: no cover
464
- parts = candidate.content.parts or []
465
460
 
466
461
  vendor_id = response.response_id
467
462
  vendor_details: dict[str, Any] | None = None
468
463
  finish_reason: FinishReason | None = None
469
- if raw_finish_reason := candidate.finish_reason: # pragma: no branch
464
+ raw_finish_reason = candidate.finish_reason
465
+ if raw_finish_reason: # pragma: no branch
470
466
  vendor_details = {'finish_reason': raw_finish_reason.value}
471
467
  finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
472
468
 
469
+ if candidate.content is None or candidate.content.parts is None:
470
+ if finish_reason == 'content_filter' and raw_finish_reason:
471
+ raise UnexpectedModelBehavior(
472
+ f'Content filter {raw_finish_reason.value!r} triggered', response.model_dump_json()
473
+ )
474
+ else:
475
+ raise UnexpectedModelBehavior(
476
+ 'Content field missing from Gemini response', response.model_dump_json()
477
+ ) # pragma: no cover
478
+ parts = candidate.content.parts or []
479
+
473
480
  usage = _metadata_as_usage(response)
474
481
  return _process_response_from_parts(
475
482
  parts,
@@ -623,7 +630,8 @@ class GeminiStreamedResponse(StreamedResponse):
623
630
  if chunk.response_id: # pragma: no branch
624
631
  self.provider_response_id = chunk.response_id
625
632
 
626
- if raw_finish_reason := candidate.finish_reason:
633
+ raw_finish_reason = candidate.finish_reason
634
+ if raw_finish_reason:
627
635
  self.provider_details = {'finish_reason': raw_finish_reason.value}
628
636
  self.finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
629
637
 
@@ -641,13 +649,17 @@ class GeminiStreamedResponse(StreamedResponse):
641
649
  # )
642
650
 
643
651
  if candidate.content is None or candidate.content.parts is None:
644
- if candidate.finish_reason == 'STOP': # pragma: no cover
652
+ if self.finish_reason == 'stop': # pragma: no cover
645
653
  # Normal completion - skip this chunk
646
654
  continue
647
- elif candidate.finish_reason == 'SAFETY': # pragma: no cover
648
- raise UnexpectedModelBehavior('Safety settings triggered', str(chunk))
655
+ elif self.finish_reason == 'content_filter' and raw_finish_reason: # pragma: no cover
656
+ raise UnexpectedModelBehavior(
657
+ f'Content filter {raw_finish_reason.value!r} triggered', chunk.model_dump_json()
658
+ )
649
659
  else: # pragma: no cover
650
- raise UnexpectedModelBehavior('Content field missing from streaming Gemini response', str(chunk))
660
+ raise UnexpectedModelBehavior(
661
+ 'Content field missing from streaming Gemini response', chunk.model_dump_json()
662
+ )
651
663
 
652
664
  parts = candidate.content.parts
653
665
  if not parts: