pydantic-ai-slim 1.5.0__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/PKG-INFO +5 -3
  2. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_output.py +5 -1
  3. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/openai.py +2 -0
  4. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/test.py +6 -3
  5. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/openai.py +7 -0
  6. pydantic_ai_slim-1.6.0/pydantic_ai/toolsets/fastmcp.py +215 -0
  7. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pyproject.toml +2 -0
  8. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/.gitignore +0 -0
  9. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/LICENSE +0 -0
  10. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/README.md +0 -0
  11. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/__init__.py +0 -0
  12. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/__main__.py +0 -0
  13. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_a2a.py +0 -0
  14. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_agent_graph.py +0 -0
  15. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_cli.py +0 -0
  16. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_function_schema.py +0 -0
  17. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_griffe.py +0 -0
  18. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_instrumentation.py +0 -0
  19. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_json_schema.py +0 -0
  20. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_mcp.py +0 -0
  21. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_otel_messages.py +0 -0
  22. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_parts_manager.py +0 -0
  23. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_run_context.py +0 -0
  24. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_system_prompt.py +0 -0
  25. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_thinking_part.py +0 -0
  26. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_tool_manager.py +0 -0
  27. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/_utils.py +0 -0
  28. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ag_ui.py +0 -0
  29. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/__init__.py +0 -0
  30. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/abstract.py +0 -0
  31. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/agent/wrapper.py +0 -0
  32. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/builtin_tools.py +0 -0
  33. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/__init__.py +0 -0
  34. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  35. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/common_tools/tavily.py +0 -0
  36. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/direct.py +0 -0
  37. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/__init__.py +0 -0
  38. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  39. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
  40. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  41. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  42. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  43. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  44. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
  45. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  46. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  47. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  48. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  49. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  50. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  51. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  52. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
  53. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  54. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  55. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  56. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  57. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  58. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  59. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/exceptions.py +0 -0
  60. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/__init__.py +0 -0
  61. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/aci.py +0 -0
  62. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/ext/langchain.py +0 -0
  63. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/format_prompt.py +0 -0
  64. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/mcp.py +0 -0
  65. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/messages.py +0 -0
  66. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/__init__.py +0 -0
  67. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/anthropic.py +0 -0
  68. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/bedrock.py +0 -0
  69. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/cohere.py +0 -0
  70. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/fallback.py +0 -0
  71. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/function.py +0 -0
  72. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/gemini.py +0 -0
  73. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/google.py +0 -0
  74. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/groq.py +0 -0
  75. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/huggingface.py +0 -0
  76. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/instrumented.py +0 -0
  77. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/mcp_sampling.py +0 -0
  78. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/mistral.py +0 -0
  79. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/models/wrapper.py +0 -0
  80. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/output.py +0 -0
  81. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/__init__.py +0 -0
  82. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/amazon.py +0 -0
  83. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/anthropic.py +0 -0
  84. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/cohere.py +0 -0
  85. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/deepseek.py +0 -0
  86. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/google.py +0 -0
  87. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/grok.py +0 -0
  88. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/groq.py +0 -0
  89. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/harmony.py +0 -0
  90. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/meta.py +0 -0
  91. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/mistral.py +0 -0
  92. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/moonshotai.py +0 -0
  93. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/profiles/qwen.py +0 -0
  94. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/__init__.py +0 -0
  95. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/anthropic.py +0 -0
  96. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/azure.py +0 -0
  97. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/bedrock.py +0 -0
  98. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/cerebras.py +0 -0
  99. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/cohere.py +0 -0
  100. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/deepseek.py +0 -0
  101. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/fireworks.py +0 -0
  102. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/gateway.py +0 -0
  103. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/github.py +0 -0
  104. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google.py +0 -0
  105. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google_gla.py +0 -0
  106. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/google_vertex.py +0 -0
  107. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/grok.py +0 -0
  108. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/groq.py +0 -0
  109. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/heroku.py +0 -0
  110. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/huggingface.py +0 -0
  111. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/litellm.py +0 -0
  112. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/mistral.py +0 -0
  113. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/moonshotai.py +0 -0
  114. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/nebius.py +0 -0
  115. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/ollama.py +0 -0
  116. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/openai.py +0 -0
  117. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/openrouter.py +0 -0
  118. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/ovhcloud.py +0 -0
  119. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/together.py +0 -0
  120. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/providers/vercel.py +0 -0
  121. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/py.typed +0 -0
  122. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/result.py +0 -0
  123. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/retries.py +0 -0
  124. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/run.py +0 -0
  125. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/settings.py +0 -0
  126. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/tools.py +0 -0
  127. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/__init__.py +0 -0
  128. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/_dynamic.py +0 -0
  129. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/abstract.py +0 -0
  130. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/approval_required.py +0 -0
  131. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/combined.py +0 -0
  132. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/external.py +0 -0
  133. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/filtered.py +0 -0
  134. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/function.py +0 -0
  135. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/prefixed.py +0 -0
  136. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/prepared.py +0 -0
  137. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/renamed.py +0 -0
  138. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/toolsets/wrapper.py +0 -0
  139. {pydantic_ai_slim-1.5.0 → pydantic_ai_slim-1.6.0}/pydantic_ai/usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Project-URL: Homepage, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
6
6
  Project-URL: Source, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
@@ -33,7 +33,7 @@ Requires-Dist: genai-prices>=0.0.35
33
33
  Requires-Dist: griffe>=1.3.2
34
34
  Requires-Dist: httpx>=0.27
35
35
  Requires-Dist: opentelemetry-api>=1.28.0
36
- Requires-Dist: pydantic-graph==1.5.0
36
+ Requires-Dist: pydantic-graph==1.6.0
37
37
  Requires-Dist: pydantic>=2.10
38
38
  Requires-Dist: typing-inspection>=0.4.0
39
39
  Provides-Extra: a2a
@@ -57,7 +57,9 @@ Requires-Dist: dbos>=1.14.0; extra == 'dbos'
57
57
  Provides-Extra: duckduckgo
58
58
  Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
59
59
  Provides-Extra: evals
60
- Requires-Dist: pydantic-evals==1.5.0; extra == 'evals'
60
+ Requires-Dist: pydantic-evals==1.6.0; extra == 'evals'
61
+ Provides-Extra: fastmcp
62
+ Requires-Dist: fastmcp>=2.12.0; extra == 'fastmcp'
61
63
  Provides-Extra: google
62
64
  Requires-Dist: google-genai>=1.46.0; extra == 'google'
63
65
  Provides-Extra: groq
@@ -2,6 +2,7 @@ from __future__ import annotations as _annotations
2
2
 
3
3
  import inspect
4
4
  import json
5
+ import re
5
6
  from abc import ABC, abstractmethod
6
7
  from collections.abc import Awaitable, Callable, Sequence
7
8
  from dataclasses import dataclass, field
@@ -70,6 +71,7 @@ Usage `OutputValidatorFunc[AgentDepsT, T]`.
70
71
 
71
72
  DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
72
73
  DEFAULT_OUTPUT_TOOL_DESCRIPTION = 'The final response which ends this conversation'
74
+ OUTPUT_TOOL_NAME_SANITIZER = re.compile(r'[^a-zA-Z0-9-_]')
73
75
 
74
76
 
75
77
  async def execute_traced_output_function(
@@ -997,7 +999,9 @@ class OutputToolset(AbstractToolset[AgentDepsT]):
997
999
  if name is None:
998
1000
  name = default_name
999
1001
  if multiple:
1000
- name += f'_{object_def.name}'
1002
+ # strip unsupported characters like "[" and "]" from generic class names
1003
+ safe_name = OUTPUT_TOOL_NAME_SANITIZER.sub('', object_def.name or '')
1004
+ name += f'_{safe_name}'
1001
1005
 
1002
1006
  i = 1
1003
1007
  original_name = name
@@ -1431,6 +1431,8 @@ class OpenAIResponsesModel(Model):
1431
1431
  call_id=call_id,
1432
1432
  type='function_call',
1433
1433
  )
1434
+ if profile.openai_responses_requires_function_call_status_none:
1435
+ param['status'] = None # type: ignore[reportGeneralTypeIssues]
1434
1436
  if id and send_item_ids: # pragma: no branch
1435
1437
  param['id'] = id
1436
1438
  openai_messages.append(param)
@@ -44,11 +44,14 @@ class _WrappedTextOutput:
44
44
  value: str | None
45
45
 
46
46
 
47
- @dataclass
47
+ @dataclass(init=False)
48
48
  class _WrappedToolOutput:
49
49
  """A wrapper class to tag an output that came from the custom_output_args field."""
50
50
 
51
- value: Any | None
51
+ value: dict[str, Any] | None
52
+
53
+ def __init__(self, value: Any | None):
54
+ self.value = pydantic_core.to_jsonable_python(value)
52
55
 
53
56
 
54
57
  @dataclass(init=False)
@@ -364,7 +367,7 @@ class _JsonSchemaTestData:
364
367
  self.defs = schema.get('$defs', {})
365
368
  self.seed = seed
366
369
 
367
- def generate(self) -> Any:
370
+ def generate(self) -> dict[str, Any]:
368
371
  """Generate data for the JSON schema."""
369
372
  return self._gen_any(self.schema)
370
373
 
@@ -44,6 +44,13 @@ class OpenAIModelProfile(ModelProfile):
44
44
  openai_supports_encrypted_reasoning_content: bool = False
45
45
  """Whether the model supports including encrypted reasoning content in the response."""
46
46
 
47
+ openai_responses_requires_function_call_status_none: bool = False
48
+ """Whether the Responses API requires the `status` field on function tool calls to be `None`.
49
+
50
+ This is required by vLLM Responses API versions before https://github.com/vllm-project/vllm/pull/26706.
51
+ See https://github.com/pydantic/pydantic-ai/issues/3245 for more details.
52
+ """
53
+
47
54
  def __post_init__(self): # pragma: no cover
48
55
  if not self.openai_supports_sampling_settings:
49
56
  warnings.warn(
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from asyncio import Lock
5
+ from contextlib import AsyncExitStack
6
+ from dataclasses import KW_ONLY, dataclass
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Literal
9
+
10
+ from pydantic import AnyUrl
11
+ from typing_extensions import Self, assert_never
12
+
13
+ from pydantic_ai import messages
14
+ from pydantic_ai.exceptions import ModelRetry
15
+ from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
16
+ from pydantic_ai.toolsets import AbstractToolset
17
+ from pydantic_ai.toolsets.abstract import ToolsetTool
18
+
19
+ try:
20
+ from fastmcp.client import Client
21
+ from fastmcp.client.transports import ClientTransport
22
+ from fastmcp.exceptions import ToolError
23
+ from fastmcp.mcp_config import MCPConfig
24
+ from fastmcp.server import FastMCP
25
+ from mcp.server.fastmcp import FastMCP as FastMCP1Server
26
+ from mcp.types import (
27
+ AudioContent,
28
+ BlobResourceContents,
29
+ ContentBlock,
30
+ EmbeddedResource,
31
+ ImageContent,
32
+ ResourceLink,
33
+ TextContent,
34
+ TextResourceContents,
35
+ Tool as MCPTool,
36
+ )
37
+
38
+ from pydantic_ai.mcp import TOOL_SCHEMA_VALIDATOR
39
+
40
+ except ImportError as _import_error:
41
+ raise ImportError(
42
+ 'Please install the `fastmcp` package to use the FastMCP server, '
43
+ 'you can use the `fastmcp` optional group — `pip install "pydantic-ai-slim[fastmcp]"`'
44
+ ) from _import_error
45
+
46
+
47
+ if TYPE_CHECKING:
48
+ from fastmcp.client.client import CallToolResult
49
+
50
+
51
+ FastMCPToolResult = messages.BinaryContent | dict[str, Any] | str | None
52
+
53
+ ToolErrorBehavior = Literal['model_retry', 'error']
54
+
55
+ UNKNOWN_BINARY_MEDIA_TYPE = 'application/octet-stream'
56
+
57
+
58
+ @dataclass(init=False)
59
+ class FastMCPToolset(AbstractToolset[AgentDepsT]):
60
+ """A FastMCP Toolset that uses the FastMCP Client to call tools from a local or remote MCP Server.
61
+
62
+ The Toolset can accept a FastMCP Client, a FastMCP Transport, or any other object which a FastMCP Transport can be created from.
63
+
64
+ See https://gofastmcp.com/clients/transports for a full list of transports available.
65
+ """
66
+
67
+ client: Client[Any]
68
+ """The FastMCP client to use."""
69
+
70
+ _: KW_ONLY
71
+
72
+ tool_error_behavior: Literal['model_retry', 'error']
73
+ """The behavior to take when a tool error occurs."""
74
+
75
+ max_retries: int
76
+ """The maximum number of retries to attempt if a tool call fails."""
77
+
78
+ _id: str | None
79
+
80
+ def __init__(
81
+ self,
82
+ client: Client[Any]
83
+ | ClientTransport
84
+ | FastMCP
85
+ | FastMCP1Server
86
+ | AnyUrl
87
+ | Path
88
+ | MCPConfig
89
+ | dict[str, Any]
90
+ | str,
91
+ *,
92
+ max_retries: int = 1,
93
+ tool_error_behavior: Literal['model_retry', 'error'] = 'model_retry',
94
+ id: str | None = None,
95
+ ) -> None:
96
+ if isinstance(client, Client):
97
+ self.client = client
98
+ else:
99
+ self.client = Client[Any](transport=client)
100
+
101
+ self._id = id
102
+ self.max_retries = max_retries
103
+ self.tool_error_behavior = tool_error_behavior
104
+
105
+ self._enter_lock: Lock = Lock()
106
+ self._running_count: int = 0
107
+ self._exit_stack: AsyncExitStack | None = None
108
+
109
+ @property
110
+ def id(self) -> str | None:
111
+ return self._id
112
+
113
+ async def __aenter__(self) -> Self:
114
+ async with self._enter_lock:
115
+ if self._running_count == 0:
116
+ self._exit_stack = AsyncExitStack()
117
+ await self._exit_stack.enter_async_context(self.client)
118
+
119
+ self._running_count += 1
120
+
121
+ return self
122
+
123
+ async def __aexit__(self, *args: Any) -> bool | None:
124
+ async with self._enter_lock:
125
+ self._running_count -= 1
126
+ if self._running_count == 0 and self._exit_stack:
127
+ await self._exit_stack.aclose()
128
+ self._exit_stack = None
129
+
130
+ return None
131
+
132
+ async def get_tools(self, ctx: RunContext[AgentDepsT]) -> dict[str, ToolsetTool[AgentDepsT]]:
133
+ async with self:
134
+ mcp_tools: list[MCPTool] = await self.client.list_tools()
135
+
136
+ return {
137
+ tool.name: _convert_mcp_tool_to_toolset_tool(toolset=self, mcp_tool=tool, retries=self.max_retries)
138
+ for tool in mcp_tools
139
+ }
140
+
141
+ async def call_tool(
142
+ self, name: str, tool_args: dict[str, Any], ctx: RunContext[AgentDepsT], tool: ToolsetTool[AgentDepsT]
143
+ ) -> Any:
144
+ async with self:
145
+ try:
146
+ call_tool_result: CallToolResult = await self.client.call_tool(name=name, arguments=tool_args)
147
+ except ToolError as e:
148
+ if self.tool_error_behavior == 'model_retry':
149
+ raise ModelRetry(message=str(e)) from e
150
+ else:
151
+ raise e
152
+
153
+ # If we have structured content, return that
154
+ if call_tool_result.structured_content:
155
+ return call_tool_result.structured_content
156
+
157
+ # Otherwise, return the content
158
+ return _map_fastmcp_tool_results(parts=call_tool_result.content)
159
+
160
+
161
+ def _convert_mcp_tool_to_toolset_tool(
162
+ toolset: FastMCPToolset[AgentDepsT],
163
+ mcp_tool: MCPTool,
164
+ retries: int,
165
+ ) -> ToolsetTool[AgentDepsT]:
166
+ """Convert an MCP tool to a toolset tool."""
167
+ return ToolsetTool[AgentDepsT](
168
+ tool_def=ToolDefinition(
169
+ name=mcp_tool.name,
170
+ description=mcp_tool.description,
171
+ parameters_json_schema=mcp_tool.inputSchema,
172
+ metadata={
173
+ 'meta': mcp_tool.meta,
174
+ 'annotations': mcp_tool.annotations.model_dump() if mcp_tool.annotations else None,
175
+ 'output_schema': mcp_tool.outputSchema or None,
176
+ },
177
+ ),
178
+ toolset=toolset,
179
+ max_retries=retries,
180
+ args_validator=TOOL_SCHEMA_VALIDATOR,
181
+ )
182
+
183
+
184
+ def _map_fastmcp_tool_results(parts: list[ContentBlock]) -> list[FastMCPToolResult] | FastMCPToolResult:
185
+ """Map FastMCP tool results to toolset tool results."""
186
+ mapped_results = [_map_fastmcp_tool_result(part) for part in parts]
187
+
188
+ if len(mapped_results) == 1:
189
+ return mapped_results[0]
190
+
191
+ return mapped_results
192
+
193
+
194
+ def _map_fastmcp_tool_result(part: ContentBlock) -> FastMCPToolResult:
195
+ if isinstance(part, TextContent):
196
+ return part.text
197
+ elif isinstance(part, ImageContent | AudioContent):
198
+ return messages.BinaryContent(data=base64.b64decode(part.data), media_type=part.mimeType)
199
+ elif isinstance(part, EmbeddedResource):
200
+ if isinstance(part.resource, BlobResourceContents):
201
+ return messages.BinaryContent(
202
+ data=base64.b64decode(part.resource.blob),
203
+ media_type=part.resource.mimeType or UNKNOWN_BINARY_MEDIA_TYPE,
204
+ )
205
+ elif isinstance(part.resource, TextResourceContents):
206
+ return part.resource.text
207
+ else:
208
+ assert_never(part.resource)
209
+ elif isinstance(part, ResourceLink):
210
+ # ResourceLink is not yet supported by the FastMCP toolset as reading resources is not yet supported.
211
+ raise NotImplementedError(
212
+ 'ResourceLink is not supported by the FastMCP toolset as reading resources is not yet supported.'
213
+ )
214
+ else:
215
+ assert_never(part)
@@ -88,6 +88,8 @@ cli = [
88
88
  ]
89
89
  # MCP
90
90
  mcp = ["mcp>=1.12.3"]
91
+ # FastMCP
92
+ fastmcp = ["fastmcp>=2.12.0"]
91
93
  # Evals
92
94
  evals = ["pydantic-evals=={{ version }}"]
93
95
  # A2A