pydantic-ai-slim 1.2.1__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.2.1 → pydantic_ai_slim-1.4.0}/PKG-INFO +5 -5
  2. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/__init__.py +4 -0
  3. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_agent_graph.py +41 -8
  4. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/__init__.py +11 -19
  5. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/builtin_tools.py +106 -4
  6. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/exceptions.py +5 -0
  7. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/mcp.py +1 -22
  8. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/__init__.py +45 -37
  9. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/anthropic.py +132 -11
  10. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/bedrock.py +4 -4
  11. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/cohere.py +0 -7
  12. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/gemini.py +9 -2
  13. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/google.py +31 -21
  14. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/groq.py +4 -4
  15. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/huggingface.py +2 -2
  16. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/openai.py +243 -49
  17. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/__init__.py +21 -12
  18. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/bedrock.py +60 -16
  19. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/gateway.py +60 -72
  20. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google.py +61 -23
  21. pydantic_ai_slim-1.4.0/pydantic_ai/providers/ovhcloud.py +95 -0
  22. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/usage.py +13 -2
  23. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pyproject.toml +2 -2
  24. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/.gitignore +0 -0
  25. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/LICENSE +0 -0
  26. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/README.md +0 -0
  27. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/__main__.py +0 -0
  28. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_a2a.py +0 -0
  29. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_cli.py +0 -0
  30. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_function_schema.py +0 -0
  31. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_griffe.py +0 -0
  32. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_instrumentation.py +0 -0
  33. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_json_schema.py +0 -0
  34. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_mcp.py +0 -0
  35. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_otel_messages.py +0 -0
  36. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_output.py +0 -0
  37. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_parts_manager.py +0 -0
  38. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_run_context.py +0 -0
  39. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_system_prompt.py +0 -0
  40. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_thinking_part.py +0 -0
  41. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_tool_manager.py +0 -0
  42. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/_utils.py +0 -0
  43. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/ag_ui.py +0 -0
  44. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/abstract.py +0 -0
  45. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/agent/wrapper.py +0 -0
  46. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/__init__.py +0 -0
  47. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  48. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/common_tools/tavily.py +0 -0
  49. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/direct.py +0 -0
  50. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/__init__.py +0 -0
  51. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  52. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
  53. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  54. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  55. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  56. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  57. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
  58. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  59. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  60. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  61. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  62. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  63. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  64. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  65. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
  66. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  67. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  68. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  69. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  70. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  71. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  72. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/__init__.py +0 -0
  73. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/aci.py +0 -0
  74. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/ext/langchain.py +0 -0
  75. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/format_prompt.py +0 -0
  76. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/messages.py +0 -0
  77. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/fallback.py +0 -0
  78. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/function.py +0 -0
  79. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/instrumented.py +0 -0
  80. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/mcp_sampling.py +0 -0
  81. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/mistral.py +0 -0
  82. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/test.py +0 -0
  83. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/models/wrapper.py +0 -0
  84. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/output.py +0 -0
  85. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/__init__.py +0 -0
  86. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/amazon.py +0 -0
  87. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/anthropic.py +0 -0
  88. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/cohere.py +0 -0
  89. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/deepseek.py +0 -0
  90. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/google.py +0 -0
  91. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/grok.py +0 -0
  92. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/groq.py +0 -0
  93. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/harmony.py +0 -0
  94. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/meta.py +0 -0
  95. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/mistral.py +0 -0
  96. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/moonshotai.py +0 -0
  97. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/openai.py +0 -0
  98. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/profiles/qwen.py +0 -0
  99. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/anthropic.py +0 -0
  100. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/azure.py +0 -0
  101. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/cerebras.py +0 -0
  102. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/cohere.py +0 -0
  103. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/deepseek.py +0 -0
  104. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/fireworks.py +0 -0
  105. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/github.py +0 -0
  106. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google_gla.py +0 -0
  107. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/google_vertex.py +0 -0
  108. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/grok.py +0 -0
  109. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/groq.py +0 -0
  110. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/heroku.py +0 -0
  111. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/huggingface.py +0 -0
  112. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/litellm.py +0 -0
  113. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/mistral.py +0 -0
  114. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/moonshotai.py +0 -0
  115. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/nebius.py +0 -0
  116. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/ollama.py +0 -0
  117. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/openai.py +0 -0
  118. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/openrouter.py +0 -0
  119. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/together.py +0 -0
  120. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/providers/vercel.py +0 -0
  121. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/py.typed +0 -0
  122. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/result.py +0 -0
  123. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/retries.py +0 -0
  124. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/run.py +0 -0
  125. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/settings.py +0 -0
  126. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/tools.py +0 -0
  127. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/__init__.py +0 -0
  128. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/_dynamic.py +0 -0
  129. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/abstract.py +0 -0
  130. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/approval_required.py +0 -0
  131. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/combined.py +0 -0
  132. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/external.py +0 -0
  133. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/filtered.py +0 -0
  134. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/function.py +0 -0
  135. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/prefixed.py +0 -0
  136. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/prepared.py +0 -0
  137. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/renamed.py +0 -0
  138. {pydantic_ai_slim-1.2.1 → pydantic_ai_slim-1.4.0}/pydantic_ai/toolsets/wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 1.2.1
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
@@ -29,11 +29,11 @@ Classifier: Topic :: Internet
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Requires-Python: >=3.10
31
31
  Requires-Dist: exceptiongroup; python_version < '3.11'
32
- Requires-Dist: genai-prices>=0.0.31
32
+ 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.2.1
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,9 +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.2.1; extra == 'evals'
60
+ Requires-Dist: pydantic-evals==1.4.0; extra == 'evals'
61
61
  Provides-Extra: google
62
- Requires-Dist: google-genai>=1.31.0; extra == 'google'
62
+ Requires-Dist: google-genai>=1.46.0; extra == 'google'
63
63
  Provides-Extra: groq
64
64
  Requires-Dist: groq>=0.25.0; extra == 'groq'
65
65
  Provides-Extra: huggingface
@@ -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,
@@ -22,6 +23,7 @@ from .exceptions import (
22
23
  ApprovalRequired,
23
24
  CallDeferred,
24
25
  FallbackExceptionGroup,
26
+ IncompleteToolCall,
25
27
  ModelHTTPError,
26
28
  ModelRetry,
27
29
  UnexpectedModelBehavior,
@@ -124,6 +126,7 @@ __all__ = (
124
126
  'ModelRetry',
125
127
  'ModelHTTPError',
126
128
  'FallbackExceptionGroup',
129
+ 'IncompleteToolCall',
127
130
  'UnexpectedModelBehavior',
128
131
  'UsageLimitExceeded',
129
132
  'UserError',
@@ -211,6 +214,7 @@ __all__ = (
211
214
  'CodeExecutionTool',
212
215
  'ImageGenerationTool',
213
216
  'MemoryTool',
217
+ 'MCPServerTool',
214
218
  # output
215
219
  'ToolOutput',
216
220
  'NativeOutput',
@@ -92,9 +92,28 @@ class GraphAgentState:
92
92
  retries: int = 0
93
93
  run_step: int = 0
94
94
 
95
- def increment_retries(self, max_result_retries: int, error: BaseException | None = None) -> None:
95
+ def increment_retries(
96
+ self,
97
+ max_result_retries: int,
98
+ error: BaseException | None = None,
99
+ model_settings: ModelSettings | None = None,
100
+ ) -> None:
96
101
  self.retries += 1
97
102
  if self.retries > max_result_retries:
103
+ if (
104
+ self.message_history
105
+ and isinstance(model_response := self.message_history[-1], _messages.ModelResponse)
106
+ and model_response.finish_reason == 'length'
107
+ and model_response.parts
108
+ and isinstance(tool_call := model_response.parts[-1], _messages.ToolCallPart)
109
+ ):
110
+ try:
111
+ tool_call.args_as_dict()
112
+ except Exception:
113
+ max_tokens = (model_settings or {}).get('max_tokens') if model_settings else None
114
+ raise exceptions.IncompleteToolCall(
115
+ f'Model token limit ({max_tokens if max_tokens is not None else "provider default"}) exceeded while emitting a tool call, resulting in incomplete arguments. Increase max tokens or simplify tool call arguments to fit within limit.'
116
+ )
98
117
  message = f'Exceeded maximum retries ({max_result_retries}) for output validation'
99
118
  if error:
100
119
  if isinstance(error, exceptions.UnexpectedModelBehavior) and error.__cause__ is not None:
@@ -568,8 +587,12 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
568
587
  # resubmit the most recent request that resulted in an empty response,
569
588
  # as the empty response and request will not create any items in the API payload,
570
589
  # in the hope the model will return a non-empty response this time.
571
- ctx.state.increment_retries(ctx.deps.max_result_retries)
572
- self._next_node = ModelRequestNode[DepsT, NodeRunEndT](_messages.ModelRequest(parts=[]))
590
+ ctx.state.increment_retries(ctx.deps.max_result_retries, model_settings=ctx.deps.model_settings)
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
+ )
573
596
  return
574
597
 
575
598
  text = ''
@@ -630,8 +653,14 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
630
653
  )
631
654
  raise ToolRetryError(m)
632
655
  except ToolRetryError as e:
633
- ctx.state.increment_retries(ctx.deps.max_result_retries, e)
634
- self._next_node = ModelRequestNode[DepsT, NodeRunEndT](_messages.ModelRequest(parts=[e.tool_retry]))
656
+ ctx.state.increment_retries(
657
+ ctx.deps.max_result_retries, error=e, model_settings=ctx.deps.model_settings
658
+ )
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
+ )
635
664
 
636
665
  self._events_iterator = _run_stream()
637
666
 
@@ -788,10 +817,14 @@ async def process_tool_calls( # noqa: C901
788
817
  try:
789
818
  result_data = await tool_manager.handle_call(call)
790
819
  except exceptions.UnexpectedModelBehavior as e:
791
- ctx.state.increment_retries(ctx.deps.max_result_retries, e)
820
+ ctx.state.increment_retries(
821
+ ctx.deps.max_result_retries, error=e, model_settings=ctx.deps.model_settings
822
+ )
792
823
  raise e # pragma: lax no cover
793
824
  except ToolRetryError as e:
794
- ctx.state.increment_retries(ctx.deps.max_result_retries, e)
825
+ ctx.state.increment_retries(
826
+ ctx.deps.max_result_retries, error=e, model_settings=ctx.deps.model_settings
827
+ )
795
828
  yield _messages.FunctionToolCallEvent(call)
796
829
  output_parts.append(e.tool_retry)
797
830
  yield _messages.FunctionToolResultEvent(e.tool_retry)
@@ -820,7 +853,7 @@ async def process_tool_calls( # noqa: C901
820
853
 
821
854
  # Then, we handle unknown tool calls
822
855
  if tool_calls_by_kind['unknown']:
823
- ctx.state.increment_retries(ctx.deps.max_result_retries)
856
+ ctx.state.increment_retries(ctx.deps.max_result_retries, model_settings=ctx.deps.model_settings)
824
857
  calls_to_run.extend(tool_calls_by_kind['unknown'])
825
858
 
826
859
  calls_to_run_results: dict[str, DeferredToolResult] = {}
@@ -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,
@@ -662,14 +654,14 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
662
654
  )
663
655
 
664
656
  try:
665
- async with toolset:
666
- async with graph.iter(
667
- start_node,
668
- state=state,
669
- deps=graph_deps,
670
- span=use_span(run_span) if run_span.is_recording() else None,
671
- infer_name=False,
672
- ) as graph_run:
657
+ async with graph.iter(
658
+ start_node,
659
+ state=state,
660
+ deps=graph_deps,
661
+ span=use_span(run_span) if run_span.is_recording() else None,
662
+ infer_name=False,
663
+ ) as graph_run:
664
+ async with toolset:
673
665
  agent_run = AgentRun(graph_run)
674
666
  yield agent_run
675
667
  if (final_result := agent_run.result) is not None and run_span.is_recording():
@@ -2,13 +2,12 @@ from __future__ import annotations as _annotations
2
2
 
3
3
  from abc import ABC
4
4
  from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, Literal
5
+ from typing import Annotated, Any, Literal, Union
6
6
 
7
+ import pydantic
8
+ from pydantic_core import core_schema
7
9
  from typing_extensions import TypedDict
8
10
 
9
- if TYPE_CHECKING:
10
- from .builtin_tools import AbstractBuiltinTool
11
-
12
11
  __all__ = (
13
12
  'AbstractBuiltinTool',
14
13
  'WebSearchTool',
@@ -17,8 +16,11 @@ __all__ = (
17
16
  'UrlContextTool',
18
17
  'ImageGenerationTool',
19
18
  'MemoryTool',
19
+ 'MCPServerTool',
20
20
  )
21
21
 
22
+ _BUILTIN_TOOL_TYPES: dict[str, type[AbstractBuiltinTool]] = {}
23
+
22
24
 
23
25
  @dataclass(kw_only=True)
24
26
  class AbstractBuiltinTool(ABC):
@@ -32,6 +34,34 @@ class AbstractBuiltinTool(ABC):
32
34
  kind: str = 'unknown_builtin_tool'
33
35
  """Built-in tool identifier, this should be available on all built-in tools as a discriminator."""
34
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
+
45
+ def __init_subclass__(cls, **kwargs: Any) -> None:
46
+ super().__init_subclass__(**kwargs)
47
+ _BUILTIN_TOOL_TYPES[cls.kind] = cls
48
+
49
+ @classmethod
50
+ def __get_pydantic_core_schema__(
51
+ cls, _source_type: Any, handler: pydantic.GetCoreSchemaHandler
52
+ ) -> core_schema.CoreSchema:
53
+ if cls is not AbstractBuiltinTool:
54
+ return handler(cls)
55
+
56
+ tools = _BUILTIN_TOOL_TYPES.values()
57
+ if len(tools) == 1: # pragma: no cover
58
+ tools_type = next(iter(tools))
59
+ else:
60
+ tools_annotated = [Annotated[tool, pydantic.Tag(tool.kind)] for tool in tools]
61
+ tools_type = Annotated[Union[tuple(tools_annotated)], pydantic.Discriminator(_tool_discriminator)] # noqa: UP007
62
+
63
+ return handler(tools_type)
64
+
35
65
 
36
66
  @dataclass(kw_only=True)
37
67
  class WebSearchTool(AbstractBuiltinTool):
@@ -120,6 +150,7 @@ class WebSearchUserLocation(TypedDict, total=False):
120
150
  """The timezone of the user's location."""
121
151
 
122
152
 
153
+ @dataclass(kw_only=True)
123
154
  class CodeExecutionTool(AbstractBuiltinTool):
124
155
  """A builtin tool that allows your agent to execute code.
125
156
 
@@ -134,6 +165,7 @@ class CodeExecutionTool(AbstractBuiltinTool):
134
165
  """The kind of tool."""
135
166
 
136
167
 
168
+ @dataclass(kw_only=True)
137
169
  class UrlContextTool(AbstractBuiltinTool):
138
170
  """Allows your agent to access contents from URLs.
139
171
 
@@ -227,6 +259,7 @@ class ImageGenerationTool(AbstractBuiltinTool):
227
259
  """The kind of tool."""
228
260
 
229
261
 
262
+ @dataclass(kw_only=True)
230
263
  class MemoryTool(AbstractBuiltinTool):
231
264
  """A builtin tool that allows your agent to use memory.
232
265
 
@@ -237,3 +270,72 @@ class MemoryTool(AbstractBuiltinTool):
237
270
 
238
271
  kind: str = 'memory'
239
272
  """The kind of tool."""
273
+
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
+
337
+ def _tool_discriminator(tool_data: dict[str, Any] | AbstractBuiltinTool) -> str:
338
+ if isinstance(tool_data, dict):
339
+ return tool_data.get('kind', AbstractBuiltinTool.kind)
340
+ else:
341
+ return tool_data.kind
@@ -23,6 +23,7 @@ __all__ = (
23
23
  'UnexpectedModelBehavior',
24
24
  'UsageLimitExceeded',
25
25
  'ModelHTTPError',
26
+ 'IncompleteToolCall',
26
27
  'FallbackExceptionGroup',
27
28
  )
28
29
 
@@ -168,3 +169,7 @@ class ToolRetryError(Exception):
168
169
  def __init__(self, tool_retry: RetryPromptPart):
169
170
  self.tool_retry = tool_retry
170
171
  super().__init__()
172
+
173
+
174
+ class IncompleteToolCall(UnexpectedModelBehavior):
175
+ """Error raised when a model stops due to token limit while emitting a tool call."""
@@ -441,14 +441,9 @@ class MCPServerStdio(MCPServer):
441
441
  'uv', args=['run', 'mcp-run-python', 'stdio'], timeout=10
442
442
  )
443
443
  agent = Agent('openai:gpt-4o', toolsets=[server])
444
-
445
- async def main():
446
- async with agent: # (2)!
447
- ...
448
444
  ```
449
445
 
450
446
  1. See [MCP Run Python](https://github.com/pydantic/mcp-run-python) for more information.
451
- 2. This will start the server as a subprocess and connect to it.
452
447
  """
453
448
 
454
449
  command: str
@@ -788,13 +783,7 @@ class MCPServerSSE(_MCPServerHTTP):
788
783
 
789
784
  server = MCPServerSSE('http://localhost:3001/sse')
790
785
  agent = Agent('openai:gpt-4o', toolsets=[server])
791
-
792
- async def main():
793
- async with agent: # (1)!
794
- ...
795
786
  ```
796
-
797
- 1. This will connect to a server running on `localhost:3001`.
798
787
  """
799
788
 
800
789
  @classmethod
@@ -837,13 +826,7 @@ class MCPServerHTTP(MCPServerSSE):
837
826
 
838
827
  server = MCPServerHTTP('http://localhost:3001/sse')
839
828
  agent = Agent('openai:gpt-4o', toolsets=[server])
840
-
841
- async def main():
842
- async with agent: # (2)!
843
- ...
844
829
  ```
845
-
846
- 1. This will connect to a server running on `localhost:3001`.
847
830
  """
848
831
 
849
832
 
@@ -862,12 +845,8 @@ class MCPServerStreamableHTTP(_MCPServerHTTP):
862
845
  from pydantic_ai import Agent
863
846
  from pydantic_ai.mcp import MCPServerStreamableHTTP
864
847
 
865
- server = MCPServerStreamableHTTP('http://localhost:8000/mcp') # (1)!
848
+ server = MCPServerStreamableHTTP('http://localhost:8000/mcp')
866
849
  agent = Agent('openai:gpt-4o', toolsets=[server])
867
-
868
- async def main():
869
- async with agent: # (2)!
870
- ...
871
850
  ```
872
851
  """
873
852
 
@@ -43,6 +43,7 @@ from ..messages import (
43
43
  )
44
44
  from ..output import OutputMode
45
45
  from ..profiles import DEFAULT_PROFILE, ModelProfile, ModelProfileSpec
46
+ from ..providers import infer_provider
46
47
  from ..settings import ModelSettings, merge_model_settings
47
48
  from ..tools import ToolDefinition
48
49
  from ..usage import RequestUsage
@@ -129,15 +130,8 @@ KnownModelName = TypeAliasType(
129
130
  'cerebras:qwen-3-235b-a22b-thinking-2507',
130
131
  'cohere:c4ai-aya-expanse-32b',
131
132
  'cohere:c4ai-aya-expanse-8b',
132
- 'cohere:command',
133
- 'cohere:command-light',
134
- 'cohere:command-light-nightly',
135
133
  'cohere:command-nightly',
136
- 'cohere:command-r',
137
- 'cohere:command-r-03-2024',
138
134
  'cohere:command-r-08-2024',
139
- 'cohere:command-r-plus',
140
- 'cohere:command-r-plus-04-2024',
141
135
  'cohere:command-r-plus-08-2024',
142
136
  'cohere:command-r7b-12-2024',
143
137
  'deepseek:deepseek-chat',
@@ -416,9 +410,17 @@ class Model(ABC):
416
410
  they need to customize the preparation flow further, but most implementations should simply call
417
411
  ``self.prepare_request(...)`` at the start of their ``request`` (and related) methods.
418
412
  """
419
- merged_settings = merge_model_settings(self.settings, model_settings)
420
- customized_parameters = self.customize_request_parameters(model_request_parameters)
421
- 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
422
424
 
423
425
  @property
424
426
  @abstractmethod
@@ -644,41 +646,39 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
644
646
  return TestModel()
645
647
 
646
648
  try:
647
- provider, model_name = model.split(':', maxsplit=1)
649
+ provider_name, model_name = model.split(':', maxsplit=1)
648
650
  except ValueError:
649
- provider = None
651
+ provider_name = None
650
652
  model_name = model
651
653
  if model_name.startswith(('gpt', 'o1', 'o3')):
652
- provider = 'openai'
654
+ provider_name = 'openai'
653
655
  elif model_name.startswith('claude'):
654
- provider = 'anthropic'
656
+ provider_name = 'anthropic'
655
657
  elif model_name.startswith('gemini'):
656
- provider = 'google-gla'
658
+ provider_name = 'google-gla'
657
659
 
658
- if provider is not None:
660
+ if provider_name is not None:
659
661
  warnings.warn(
660
- f"Specifying a model name without a provider prefix is deprecated. Instead of {model_name!r}, use '{provider}:{model_name}'.",
662
+ f"Specifying a model name without a provider prefix is deprecated. Instead of {model_name!r}, use '{provider_name}:{model_name}'.",
661
663
  DeprecationWarning,
662
664
  )
663
665
  else:
664
666
  raise UserError(f'Unknown model: {model}')
665
667
 
666
- if provider == 'vertexai': # pragma: no cover
668
+ if provider_name == 'vertexai': # pragma: no cover
667
669
  warnings.warn(
668
670
  "The 'vertexai' provider name is deprecated. Use 'google-vertex' instead.",
669
671
  DeprecationWarning,
670
672
  )
671
- provider = 'google-vertex'
672
-
673
- if provider == 'gateway':
674
- from ..providers.gateway import infer_model as infer_model_from_gateway
673
+ provider_name = 'google-vertex'
675
674
 
676
- return infer_model_from_gateway(model_name)
677
- elif provider == 'cohere':
678
- from .cohere import CohereModel
675
+ provider = infer_provider(provider_name)
679
676
 
680
- return CohereModel(model_name, provider=provider)
681
- elif provider in (
677
+ model_kind = provider_name
678
+ if model_kind.startswith('gateway/'):
679
+ model_kind = provider_name.removeprefix('gateway/')
680
+ if model_kind in (
681
+ 'openai',
682
682
  'azure',
683
683
  'deepseek',
684
684
  'cerebras',
@@ -688,42 +688,50 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
688
688
  'heroku',
689
689
  'moonshotai',
690
690
  'ollama',
691
- 'openai',
692
- 'openai-chat',
693
691
  'openrouter',
694
692
  'together',
695
693
  'vercel',
696
694
  'litellm',
697
695
  'nebius',
696
+ 'ovhcloud',
698
697
  ):
698
+ model_kind = 'openai-chat'
699
+ elif model_kind in ('google-gla', 'google-vertex'):
700
+ model_kind = 'google'
701
+
702
+ if model_kind == 'openai-chat':
699
703
  from .openai import OpenAIChatModel
700
704
 
701
705
  return OpenAIChatModel(model_name, provider=provider)
702
- elif provider == 'openai-responses':
706
+ elif model_kind == 'openai-responses':
703
707
  from .openai import OpenAIResponsesModel
704
708
 
705
- return OpenAIResponsesModel(model_name, provider='openai')
706
- elif provider in ('google-gla', 'google-vertex'):
709
+ return OpenAIResponsesModel(model_name, provider=provider)
710
+ elif model_kind == 'google':
707
711
  from .google import GoogleModel
708
712
 
709
713
  return GoogleModel(model_name, provider=provider)
710
- elif provider == 'groq':
714
+ elif model_kind == 'groq':
711
715
  from .groq import GroqModel
712
716
 
713
717
  return GroqModel(model_name, provider=provider)
714
- elif provider == 'mistral':
718
+ elif model_kind == 'cohere':
719
+ from .cohere import CohereModel
720
+
721
+ return CohereModel(model_name, provider=provider)
722
+ elif model_kind == 'mistral':
715
723
  from .mistral import MistralModel
716
724
 
717
725
  return MistralModel(model_name, provider=provider)
718
- elif provider == 'anthropic':
726
+ elif model_kind == 'anthropic':
719
727
  from .anthropic import AnthropicModel
720
728
 
721
729
  return AnthropicModel(model_name, provider=provider)
722
- elif provider == 'bedrock':
730
+ elif model_kind == 'bedrock':
723
731
  from .bedrock import BedrockConverseModel
724
732
 
725
733
  return BedrockConverseModel(model_name, provider=provider)
726
- elif provider == 'huggingface':
734
+ elif model_kind == 'huggingface':
727
735
  from .huggingface import HuggingFaceModel
728
736
 
729
737
  return HuggingFaceModel(model_name, provider=provider)