pydantic-ai-slim 1.7.0__tar.gz → 1.9.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 (157) hide show
  1. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/.gitignore +1 -1
  2. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/PKG-INFO +5 -3
  3. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/__init__.py +2 -0
  4. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_agent_graph.py +3 -0
  5. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_cli.py +2 -2
  6. pydantic_ai_slim-1.9.0/pydantic_ai/ag_ui.py +163 -0
  7. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/agent/abstract.py +17 -6
  8. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/direct.py +16 -4
  9. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/dbos/_agent.py +3 -0
  10. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_agent.py +3 -0
  11. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_agent.py +3 -0
  12. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/messages.py +39 -7
  13. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/__init__.py +42 -1
  14. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/groq.py +9 -1
  15. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/openai.py +2 -3
  16. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/result.py +19 -7
  17. pydantic_ai_slim-1.9.0/pydantic_ai/ui/__init__.py +16 -0
  18. pydantic_ai_slim-1.9.0/pydantic_ai/ui/_adapter.py +386 -0
  19. pydantic_ai_slim-1.9.0/pydantic_ai/ui/_event_stream.py +591 -0
  20. pydantic_ai_slim-1.9.0/pydantic_ai/ui/_messages_builder.py +28 -0
  21. pydantic_ai_slim-1.9.0/pydantic_ai/ui/ag_ui/__init__.py +9 -0
  22. pydantic_ai_slim-1.9.0/pydantic_ai/ui/ag_ui/_adapter.py +187 -0
  23. pydantic_ai_slim-1.9.0/pydantic_ai/ui/ag_ui/_event_stream.py +227 -0
  24. pydantic_ai_slim-1.9.0/pydantic_ai/ui/ag_ui/app.py +141 -0
  25. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/__init__.py +16 -0
  26. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/_adapter.py +199 -0
  27. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/_event_stream.py +187 -0
  28. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/_utils.py +16 -0
  29. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/request_types.py +275 -0
  30. pydantic_ai_slim-1.9.0/pydantic_ai/ui/vercel_ai/response_types.py +230 -0
  31. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pyproject.toml +2 -0
  32. pydantic_ai_slim-1.7.0/pydantic_ai/ag_ui.py +0 -809
  33. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/LICENSE +0 -0
  34. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/README.md +0 -0
  35. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/__main__.py +0 -0
  36. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_a2a.py +0 -0
  37. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_function_schema.py +0 -0
  38. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_griffe.py +0 -0
  39. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_instrumentation.py +0 -0
  40. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_json_schema.py +0 -0
  41. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_mcp.py +0 -0
  42. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_otel_messages.py +0 -0
  43. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_output.py +0 -0
  44. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_parts_manager.py +0 -0
  45. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_run_context.py +0 -0
  46. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_system_prompt.py +0 -0
  47. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_thinking_part.py +0 -0
  48. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_tool_manager.py +0 -0
  49. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/_utils.py +0 -0
  50. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/agent/__init__.py +0 -0
  51. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/agent/wrapper.py +0 -0
  52. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/builtin_tools.py +0 -0
  53. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/common_tools/__init__.py +0 -0
  54. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  55. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/common_tools/tavily.py +0 -0
  56. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/__init__.py +0 -0
  57. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  58. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  59. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  60. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  61. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  62. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  63. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  64. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  65. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  66. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  67. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  68. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  69. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  70. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  71. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  72. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  73. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  74. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  75. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/exceptions.py +0 -0
  76. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/ext/__init__.py +0 -0
  77. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/ext/aci.py +0 -0
  78. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/ext/langchain.py +0 -0
  79. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/format_prompt.py +0 -0
  80. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/mcp.py +0 -0
  81. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/anthropic.py +0 -0
  82. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/bedrock.py +0 -0
  83. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/cohere.py +0 -0
  84. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/fallback.py +0 -0
  85. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/function.py +0 -0
  86. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/gemini.py +0 -0
  87. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/google.py +0 -0
  88. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/huggingface.py +0 -0
  89. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/instrumented.py +0 -0
  90. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/mcp_sampling.py +0 -0
  91. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/mistral.py +0 -0
  92. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/outlines.py +0 -0
  93. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/test.py +0 -0
  94. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/models/wrapper.py +0 -0
  95. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/output.py +0 -0
  96. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/__init__.py +0 -0
  97. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/amazon.py +0 -0
  98. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/anthropic.py +0 -0
  99. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/cohere.py +0 -0
  100. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/deepseek.py +0 -0
  101. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/google.py +0 -0
  102. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/grok.py +0 -0
  103. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/groq.py +0 -0
  104. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/harmony.py +0 -0
  105. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/meta.py +0 -0
  106. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/mistral.py +0 -0
  107. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/moonshotai.py +0 -0
  108. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/openai.py +0 -0
  109. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/profiles/qwen.py +0 -0
  110. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/__init__.py +0 -0
  111. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/anthropic.py +0 -0
  112. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/azure.py +0 -0
  113. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/bedrock.py +0 -0
  114. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/cerebras.py +0 -0
  115. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/cohere.py +0 -0
  116. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/deepseek.py +0 -0
  117. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/fireworks.py +0 -0
  118. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/gateway.py +0 -0
  119. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/github.py +0 -0
  120. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/google.py +0 -0
  121. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/google_gla.py +0 -0
  122. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/google_vertex.py +0 -0
  123. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/grok.py +0 -0
  124. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/groq.py +0 -0
  125. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/heroku.py +0 -0
  126. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/huggingface.py +0 -0
  127. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/litellm.py +0 -0
  128. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/mistral.py +0 -0
  129. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/moonshotai.py +0 -0
  130. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/nebius.py +0 -0
  131. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/ollama.py +0 -0
  132. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/openai.py +0 -0
  133. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/openrouter.py +0 -0
  134. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/outlines.py +0 -0
  135. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/ovhcloud.py +0 -0
  136. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/together.py +0 -0
  137. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/providers/vercel.py +0 -0
  138. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/py.typed +0 -0
  139. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/retries.py +0 -0
  140. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/run.py +0 -0
  141. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/settings.py +0 -0
  142. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/tools.py +0 -0
  143. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/__init__.py +0 -0
  144. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/_dynamic.py +0 -0
  145. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/abstract.py +0 -0
  146. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/approval_required.py +0 -0
  147. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/combined.py +0 -0
  148. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/external.py +0 -0
  149. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/fastmcp.py +0 -0
  150. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/filtered.py +0 -0
  151. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/function.py +0 -0
  152. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/prefixed.py +0 -0
  153. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/prepared.py +0 -0
  154. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/renamed.py +0 -0
  155. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/toolsets/wrapper.py +0 -0
  156. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/pydantic_ai/usage.py +0 -0
  157. {pydantic_ai_slim-1.7.0 → pydantic_ai_slim-1.9.0}/venv +0 -0
@@ -10,7 +10,7 @@ env*/
10
10
  /TODO.md
11
11
  /postgres-data/
12
12
  .DS_Store
13
- examples/pydantic_ai_examples/.chat_app_messages.sqlite
13
+ .chat_app_messages.sqlite
14
14
  .cache/
15
15
  .vscode/
16
16
  /question_graph_history.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 1.7.0
3
+ Version: 1.9.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.7.0
36
+ Requires-Dist: pydantic-graph==1.9.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.7.0; extra == 'evals'
60
+ Requires-Dist: pydantic-evals==1.9.0; extra == 'evals'
61
61
  Provides-Extra: fastmcp
62
62
  Requires-Dist: fastmcp>=2.12.0; extra == 'fastmcp'
63
63
  Provides-Extra: google
@@ -96,6 +96,8 @@ Provides-Extra: tavily
96
96
  Requires-Dist: tavily-python>=0.5.0; extra == 'tavily'
97
97
  Provides-Extra: temporal
98
98
  Requires-Dist: temporalio==1.18.0; extra == 'temporal'
99
+ Provides-Extra: ui
100
+ Requires-Dist: starlette>=0.45.3; extra == 'ui'
99
101
  Provides-Extra: vertexai
100
102
  Requires-Dist: google-auth>=2.36.0; extra == 'vertexai'
101
103
  Requires-Dist: requests>=2.32.2; extra == 'vertexai'
@@ -65,6 +65,7 @@ from .messages import (
65
65
  ModelResponseStreamEvent,
66
66
  MultiModalContent,
67
67
  PartDeltaEvent,
68
+ PartEndEvent,
68
69
  PartStartEvent,
69
70
  RetryPromptPart,
70
71
  SystemPromptPart,
@@ -164,6 +165,7 @@ __all__ = (
164
165
  'ModelResponseStreamEvent',
165
166
  'MultiModalContent',
166
167
  'PartDeltaEvent',
168
+ 'PartEndEvent',
167
169
  'PartStartEvent',
168
170
  'RetryPromptPart',
169
171
  'SystemPromptPart',
@@ -267,6 +267,9 @@ class UserPromptNode(AgentNode[DepsT, NodeRunEndT]):
267
267
 
268
268
  next_message.instructions = await ctx.deps.get_instructions(run_context)
269
269
 
270
+ if not messages and not next_message.parts and not next_message.instructions:
271
+ raise exceptions.UserError('No message history, user prompt, or instructions provided')
272
+
270
273
  return ModelRequestNode[DepsT, NodeRunEndT](request=next_message)
271
274
 
272
275
  async def _handle_deferred_tool_results( # noqa: C901
@@ -103,7 +103,7 @@ def cli_exit(prog_name: str = 'pai'): # pragma: no cover
103
103
 
104
104
 
105
105
  def cli( # noqa: C901
106
- args_list: Sequence[str] | None = None, *, prog_name: str = 'pai', default_model: str = 'openai:gpt-4.1'
106
+ args_list: Sequence[str] | None = None, *, prog_name: str = 'pai', default_model: str = 'openai:gpt-5'
107
107
  ) -> int:
108
108
  """Run the CLI and return the exit code for the process."""
109
109
  parser = argparse.ArgumentParser(
@@ -124,7 +124,7 @@ Special prompts:
124
124
  '-m',
125
125
  '--model',
126
126
  nargs='?',
127
- help=f'Model to use, in format "<provider>:<model>" e.g. "openai:gpt-4.1" or "anthropic:claude-sonnet-4-0". Defaults to "{default_model}".',
127
+ help=f'Model to use, in format "<provider>:<model>" e.g. "openai:gpt-5" or "anthropic:claude-sonnet-4-5". Defaults to "{default_model}".',
128
128
  )
129
129
  # we don't want to autocomplete or list models that don't include the provider,
130
130
  # e.g. we want to show `openai:gpt-4o` but not `gpt-4o`
@@ -0,0 +1,163 @@
1
+ """Provides an AG-UI protocol adapter for the Pydantic AI agent.
2
+
3
+ This package provides seamless integration between pydantic-ai agents and ag-ui
4
+ for building interactive AI applications with streaming event-based communication.
5
+ """
6
+
7
+ # TODO (v2): Remove this module in favor of `pydantic_ai.ui.ag_ui`
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import AsyncIterator, Sequence
12
+ from typing import Any
13
+
14
+ from . import DeferredToolResults
15
+ from .agent import AbstractAgent
16
+ from .messages import ModelMessage
17
+ from .models import KnownModelName, Model
18
+ from .output import OutputSpec
19
+ from .settings import ModelSettings
20
+ from .tools import AgentDepsT
21
+ from .toolsets import AbstractToolset
22
+ from .usage import RunUsage, UsageLimits
23
+
24
+ try:
25
+ from ag_ui.core import BaseEvent
26
+ from ag_ui.core.types import RunAgentInput
27
+ from starlette.requests import Request
28
+ from starlette.responses import Response
29
+
30
+ from .ui import SSE_CONTENT_TYPE, OnCompleteFunc, StateDeps, StateHandler
31
+ from .ui.ag_ui import AGUIAdapter
32
+ from .ui.ag_ui.app import AGUIApp
33
+ except ImportError as e: # pragma: no cover
34
+ raise ImportError(
35
+ 'Please install the `ag-ui-protocol` and `starlette` packages to use `AGUIAdapter`, '
36
+ 'you can use the `ag-ui` optional group — `pip install "pydantic-ai-slim[ag-ui]"`'
37
+ ) from e
38
+
39
+
40
+ __all__ = [
41
+ 'SSE_CONTENT_TYPE',
42
+ 'StateDeps',
43
+ 'StateHandler',
44
+ 'AGUIApp',
45
+ 'OnCompleteFunc',
46
+ 'handle_ag_ui_request',
47
+ 'run_ag_ui',
48
+ ]
49
+
50
+
51
+ async def handle_ag_ui_request(
52
+ agent: AbstractAgent[AgentDepsT, Any],
53
+ request: Request,
54
+ *,
55
+ output_type: OutputSpec[Any] | None = None,
56
+ message_history: Sequence[ModelMessage] | None = None,
57
+ deferred_tool_results: DeferredToolResults | None = None,
58
+ model: Model | KnownModelName | str | None = None,
59
+ deps: AgentDepsT = None,
60
+ model_settings: ModelSettings | None = None,
61
+ usage_limits: UsageLimits | None = None,
62
+ usage: RunUsage | None = None,
63
+ infer_name: bool = True,
64
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
65
+ on_complete: OnCompleteFunc[BaseEvent] | None = None,
66
+ ) -> Response:
67
+ """Handle an AG-UI request by running the agent and returning a streaming response.
68
+
69
+ Args:
70
+ agent: The agent to run.
71
+ request: The Starlette request (e.g. from FastAPI) containing the AG-UI run input.
72
+
73
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
74
+ output validators since output validators would expect an argument that matches the agent's output type.
75
+ message_history: History of the conversation so far.
76
+ deferred_tool_results: Optional results for deferred tool calls in the message history.
77
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
78
+ deps: Optional dependencies to use for this run.
79
+ model_settings: Optional settings to use for this model's request.
80
+ usage_limits: Optional limits on model request count or token usage.
81
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
82
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
83
+ toolsets: Optional additional toolsets for this run.
84
+ on_complete: Optional callback function called when the agent run completes successfully.
85
+ The callback receives the completed [`AgentRunResult`][pydantic_ai.agent.AgentRunResult] and can access `all_messages()` and other result data.
86
+
87
+ Returns:
88
+ A streaming Starlette response with AG-UI protocol events.
89
+ """
90
+ return await AGUIAdapter[AgentDepsT].dispatch_request(
91
+ request,
92
+ agent=agent,
93
+ deps=deps,
94
+ output_type=output_type,
95
+ message_history=message_history,
96
+ deferred_tool_results=deferred_tool_results,
97
+ model=model,
98
+ model_settings=model_settings,
99
+ usage_limits=usage_limits,
100
+ usage=usage,
101
+ infer_name=infer_name,
102
+ toolsets=toolsets,
103
+ on_complete=on_complete,
104
+ )
105
+
106
+
107
+ def run_ag_ui(
108
+ agent: AbstractAgent[AgentDepsT, Any],
109
+ run_input: RunAgentInput,
110
+ accept: str = SSE_CONTENT_TYPE,
111
+ *,
112
+ output_type: OutputSpec[Any] | None = None,
113
+ message_history: Sequence[ModelMessage] | None = None,
114
+ deferred_tool_results: DeferredToolResults | None = None,
115
+ model: Model | KnownModelName | str | None = None,
116
+ deps: AgentDepsT = None,
117
+ model_settings: ModelSettings | None = None,
118
+ usage_limits: UsageLimits | None = None,
119
+ usage: RunUsage | None = None,
120
+ infer_name: bool = True,
121
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
122
+ on_complete: OnCompleteFunc[BaseEvent] | None = None,
123
+ ) -> AsyncIterator[str]:
124
+ """Run the agent with the AG-UI run input and stream AG-UI protocol events.
125
+
126
+ Args:
127
+ agent: The agent to run.
128
+ run_input: The AG-UI run input containing thread_id, run_id, messages, etc.
129
+ accept: The accept header value for the run.
130
+
131
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
132
+ output validators since output validators would expect an argument that matches the agent's output type.
133
+ message_history: History of the conversation so far.
134
+ deferred_tool_results: Optional results for deferred tool calls in the message history.
135
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
136
+ deps: Optional dependencies to use for this run.
137
+ model_settings: Optional settings to use for this model's request.
138
+ usage_limits: Optional limits on model request count or token usage.
139
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
140
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
141
+ toolsets: Optional additional toolsets for this run.
142
+ on_complete: Optional callback function called when the agent run completes successfully.
143
+ The callback receives the completed [`AgentRunResult`][pydantic_ai.agent.AgentRunResult] and can access `all_messages()` and other result data.
144
+
145
+ Yields:
146
+ Streaming event chunks encoded as strings according to the accept header value.
147
+ """
148
+ adapter = AGUIAdapter(agent=agent, run_input=run_input, accept=accept)
149
+ return adapter.encode_stream(
150
+ adapter.run_stream(
151
+ output_type=output_type,
152
+ message_history=message_history,
153
+ deferred_tool_results=deferred_tool_results,
154
+ model=model,
155
+ deps=deps,
156
+ model_settings=model_settings,
157
+ usage_limits=usage_limits,
158
+ usage=usage,
159
+ infer_name=infer_name,
160
+ toolsets=toolsets,
161
+ on_complete=on_complete,
162
+ ),
163
+ )
@@ -49,7 +49,7 @@ if TYPE_CHECKING:
49
49
  from starlette.routing import BaseRoute, Route
50
50
  from starlette.types import ExceptionHandler, Lifespan
51
51
 
52
- from ..ag_ui import AGUIApp
52
+ from pydantic_ai.ui.ag_ui.app import AGUIApp
53
53
 
54
54
 
55
55
  T = TypeVar('T')
@@ -654,6 +654,9 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
654
654
  PartStartEvent(index=0, part=TextPart(content='The capital of ')),
655
655
  FinalResultEvent(tool_name=None, tool_call_id=None),
656
656
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
657
+ PartEndEvent(
658
+ index=0, part=TextPart(content='The capital of France is Paris. ')
659
+ ),
657
660
  AgentRunResultEvent(
658
661
  result=AgentRunResult(output='The capital of France is Paris. ')
659
662
  ),
@@ -683,6 +686,9 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
683
686
  An async iterable of stream events `AgentStreamEvent` and finally a `AgentRunResultEvent` with the final
684
687
  run result.
685
688
  """
689
+ if infer_name and self.name is None:
690
+ self._infer_name(inspect.currentframe())
691
+
686
692
  # unfortunately this hack of returning a generator rather than defining it right here is
687
693
  # required to allow overloads of this method to work in python's typing system, or at least with pyright
688
694
  # or at least I couldn't make it work without
@@ -696,7 +702,6 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
696
702
  model_settings=model_settings,
697
703
  usage_limits=usage_limits,
698
704
  usage=usage,
699
- infer_name=infer_name,
700
705
  toolsets=toolsets,
701
706
  builtin_tools=builtin_tools,
702
707
  )
@@ -713,7 +718,6 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
713
718
  model_settings: ModelSettings | None = None,
714
719
  usage_limits: _usage.UsageLimits | None = None,
715
720
  usage: _usage.RunUsage | None = None,
716
- infer_name: bool = True,
717
721
  toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
718
722
  builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
719
723
  ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]:
@@ -739,7 +743,7 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
739
743
  model_settings=model_settings,
740
744
  usage_limits=usage_limits,
741
745
  usage=usage,
742
- infer_name=infer_name,
746
+ infer_name=False,
743
747
  toolsets=toolsets,
744
748
  builtin_tools=builtin_tools,
745
749
  event_stream_handler=event_stream_handler,
@@ -989,11 +993,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
989
993
  async def __aexit__(self, *args: Any) -> bool | None:
990
994
  raise NotImplementedError
991
995
 
996
+ # TODO (v2): Remove in favor of using `AGUIApp` directly -- we don't have `to_temporal()` or `to_vercel_ai()` either.
992
997
  def to_ag_ui(
993
998
  self,
994
999
  *,
995
1000
  # Agent.iter parameters
996
1001
  output_type: OutputSpec[OutputDataT] | None = None,
1002
+ message_history: Sequence[_messages.ModelMessage] | None = None,
1003
+ deferred_tool_results: DeferredToolResults | None = None,
997
1004
  model: models.Model | models.KnownModelName | str | None = None,
998
1005
  deps: AgentDepsT = None,
999
1006
  model_settings: ModelSettings | None = None,
@@ -1034,12 +1041,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
1034
1041
  uvicorn app:app --host 0.0.0.0 --port 8000
1035
1042
  ```
1036
1043
 
1037
- See [AG-UI docs](../ag-ui.md) for more information.
1044
+ See [AG-UI docs](../ui/ag-ui.md) for more information.
1038
1045
 
1039
1046
  Args:
1040
1047
  output_type: Custom output type to use for this run, `output_type` may only be used if the agent has
1041
1048
  no output validators since output validators would expect an argument that matches the agent's
1042
1049
  output type.
1050
+ message_history: History of the conversation so far.
1051
+ deferred_tool_results: Optional results for deferred tool calls in the message history.
1043
1052
  model: Optional model to use for this run, required if `model` was not set when creating the agent.
1044
1053
  deps: Optional dependencies to use for this run.
1045
1054
  model_settings: Optional settings to use for this model's request.
@@ -1069,12 +1078,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
1069
1078
  Returns:
1070
1079
  An ASGI application for running Pydantic AI agents with AG-UI protocol support.
1071
1080
  """
1072
- from ..ag_ui import AGUIApp
1081
+ from pydantic_ai.ui.ag_ui.app import AGUIApp
1073
1082
 
1074
1083
  return AGUIApp(
1075
1084
  agent=self,
1076
1085
  # Agent.iter parameters
1077
1086
  output_type=output_type,
1087
+ message_history=message_history,
1088
+ deferred_tool_results=deferred_tool_results,
1078
1089
  model=model,
1079
1090
  deps=deps,
1080
1091
  model_settings=model_settings,
@@ -50,7 +50,7 @@ async def model_request(
50
50
 
51
51
  async def main():
52
52
  model_response = await model_request(
53
- 'anthropic:claude-3-5-haiku-latest',
53
+ 'anthropic:claude-haiku-4-5',
54
54
  [ModelRequest.user_text_prompt('What is the capital of France?')] # (1)!
55
55
  )
56
56
  print(model_response)
@@ -58,7 +58,7 @@ async def model_request(
58
58
  ModelResponse(
59
59
  parts=[TextPart(content='The capital of France is Paris.')],
60
60
  usage=RequestUsage(input_tokens=56, output_tokens=7),
61
- model_name='claude-3-5-haiku-latest',
61
+ model_name='claude-haiku-4-5',
62
62
  timestamp=datetime.datetime(...),
63
63
  )
64
64
  '''
@@ -103,7 +103,7 @@ def model_request_sync(
103
103
  from pydantic_ai.direct import model_request_sync
104
104
 
105
105
  model_response = model_request_sync(
106
- 'anthropic:claude-3-5-haiku-latest',
106
+ 'anthropic:claude-haiku-4-5',
107
107
  [ModelRequest.user_text_prompt('What is the capital of France?')] # (1)!
108
108
  )
109
109
  print(model_response)
@@ -111,7 +111,7 @@ def model_request_sync(
111
111
  ModelResponse(
112
112
  parts=[TextPart(content='The capital of France is Paris.')],
113
113
  usage=RequestUsage(input_tokens=56, output_tokens=7),
114
- model_name='claude-3-5-haiku-latest',
114
+ model_name='claude-haiku-4-5',
115
115
  timestamp=datetime.datetime(...),
116
116
  )
117
117
  '''
@@ -172,6 +172,12 @@ def model_request_stream(
172
172
  index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
173
173
  ),
174
174
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
175
+ PartEndEvent(
176
+ index=0,
177
+ part=TextPart(
178
+ content='Albert Einstein was a German-born theoretical physicist.'
179
+ ),
180
+ ),
175
181
  ]
176
182
  '''
177
183
  ```
@@ -229,6 +235,12 @@ def model_request_stream_sync(
229
235
  index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
230
236
  ),
231
237
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
238
+ PartEndEvent(
239
+ index=0,
240
+ part=TextPart(
241
+ content='Albert Einstein was a German-born theoretical physicist.'
242
+ ),
243
+ ),
232
244
  ]
233
245
  '''
234
246
  ```
@@ -640,6 +640,9 @@ class DBOSAgent(WrapperAgent[AgentDepsT, OutputDataT], DBOSConfiguredInstance):
640
640
  PartStartEvent(index=0, part=TextPart(content='The capital of ')),
641
641
  FinalResultEvent(tool_name=None, tool_call_id=None),
642
642
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
643
+ PartEndEvent(
644
+ index=0, part=TextPart(content='The capital of France is Paris. ')
645
+ ),
643
646
  AgentRunResultEvent(
644
647
  result=AgentRunResult(output='The capital of France is Paris. ')
645
648
  ),
@@ -598,6 +598,9 @@ class PrefectAgent(WrapperAgent[AgentDepsT, OutputDataT]):
598
598
  PartStartEvent(index=0, part=TextPart(content='The capital of ')),
599
599
  FinalResultEvent(tool_name=None, tool_call_id=None),
600
600
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
601
+ PartEndEvent(
602
+ index=0, part=TextPart(content='The capital of France is Paris. ')
603
+ ),
601
604
  AgentRunResultEvent(
602
605
  result=AgentRunResult(output='The capital of France is Paris. ')
603
606
  ),
@@ -669,6 +669,9 @@ class TemporalAgent(WrapperAgent[AgentDepsT, OutputDataT]):
669
669
  PartStartEvent(index=0, part=TextPart(content='The capital of ')),
670
670
  FinalResultEvent(tool_name=None, tool_call_id=None),
671
671
  PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
672
+ PartEndEvent(
673
+ index=0, part=TextPart(content='The capital of France is Paris. ')
674
+ ),
672
675
  AgentRunResultEvent(
673
676
  result=AgentRunResult(output='The capital of France is Paris. ')
674
677
  ),
@@ -13,7 +13,7 @@ import pydantic
13
13
  import pydantic_core
14
14
  from genai_prices import calc_price, types as genai_types
15
15
  from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
16
- from typing_extensions import Self, deprecated
16
+ from typing_extensions import deprecated
17
17
 
18
18
  from . import _otel_messages, _utils
19
19
  from ._utils import generate_tool_call_id as _generate_tool_call_id, now_utc as _now_utc
@@ -514,16 +514,16 @@ class BinaryContent:
514
514
  vendor_metadata=bc.vendor_metadata,
515
515
  )
516
516
  else:
517
- return bc # pragma: no cover
517
+ return bc
518
518
 
519
519
  @classmethod
520
- def from_data_uri(cls, data_uri: str) -> Self:
520
+ def from_data_uri(cls, data_uri: str) -> BinaryContent:
521
521
  """Create a `BinaryContent` from a data URI."""
522
522
  prefix = 'data:'
523
523
  if not data_uri.startswith(prefix):
524
- raise ValueError('Data URI must start with "data:"') # pragma: no cover
524
+ raise ValueError('Data URI must start with "data:"')
525
525
  media_type, data = data_uri[len(prefix) :].split(';base64,', 1)
526
- return cls(data=base64.b64decode(data), media_type=media_type)
526
+ return cls.narrow_type(cls(data=base64.b64decode(data), media_type=media_type))
527
527
 
528
528
  @pydantic.computed_field
529
529
  @property
@@ -1612,6 +1612,14 @@ class PartStartEvent:
1612
1612
  part: ModelResponsePart
1613
1613
  """The newly started `ModelResponsePart`."""
1614
1614
 
1615
+ previous_part_kind: (
1616
+ Literal['text', 'thinking', 'tool-call', 'builtin-tool-call', 'builtin-tool-return', 'file'] | None
1617
+ ) = None
1618
+ """The kind of the previous part, if any.
1619
+
1620
+ This is useful for UI event streams to know whether to group parts of the same kind together when emitting events.
1621
+ """
1622
+
1615
1623
  event_kind: Literal['part_start'] = 'part_start'
1616
1624
  """Event type identifier, used as a discriminator."""
1617
1625
 
@@ -1634,6 +1642,30 @@ class PartDeltaEvent:
1634
1642
  __repr__ = _utils.dataclasses_no_defaults_repr
1635
1643
 
1636
1644
 
1645
+ @dataclass(repr=False, kw_only=True)
1646
+ class PartEndEvent:
1647
+ """An event indicating that a part is complete."""
1648
+
1649
+ index: int
1650
+ """The index of the part within the overall response parts list."""
1651
+
1652
+ part: ModelResponsePart
1653
+ """The complete `ModelResponsePart`."""
1654
+
1655
+ next_part_kind: (
1656
+ Literal['text', 'thinking', 'tool-call', 'builtin-tool-call', 'builtin-tool-return', 'file'] | None
1657
+ ) = None
1658
+ """The kind of the next part, if any.
1659
+
1660
+ This is useful for UI event streams to know whether to group parts of the same kind together when emitting events.
1661
+ """
1662
+
1663
+ event_kind: Literal['part_end'] = 'part_end'
1664
+ """Event type identifier, used as a discriminator."""
1665
+
1666
+ __repr__ = _utils.dataclasses_no_defaults_repr
1667
+
1668
+
1637
1669
  @dataclass(repr=False, kw_only=True)
1638
1670
  class FinalResultEvent:
1639
1671
  """An event indicating the response to the current model request matches the output schema and will produce a result."""
@@ -1649,9 +1681,9 @@ class FinalResultEvent:
1649
1681
 
1650
1682
 
1651
1683
  ModelResponseStreamEvent = Annotated[
1652
- PartStartEvent | PartDeltaEvent | FinalResultEvent, pydantic.Discriminator('event_kind')
1684
+ PartStartEvent | PartDeltaEvent | PartEndEvent | FinalResultEvent, pydantic.Discriminator('event_kind')
1653
1685
  ]
1654
- """An event in the model response stream, starting a new part, applying a delta to an existing one, or indicating the final result."""
1686
+ """An event in the model response stream, starting a new part, applying a delta to an existing one, indicating a part is complete, or indicating the final result."""
1655
1687
 
1656
1688
 
1657
1689
  @dataclass(repr=False)
@@ -27,6 +27,7 @@ from .._run_context import RunContext
27
27
  from ..builtin_tools import AbstractBuiltinTool
28
28
  from ..exceptions import UserError
29
29
  from ..messages import (
30
+ BaseToolCallPart,
30
31
  BinaryImage,
31
32
  FilePart,
32
33
  FileUrl,
@@ -35,9 +36,12 @@ from ..messages import (
35
36
  ModelMessage,
36
37
  ModelRequest,
37
38
  ModelResponse,
39
+ ModelResponsePart,
38
40
  ModelResponseStreamEvent,
41
+ PartEndEvent,
39
42
  PartStartEvent,
40
43
  TextPart,
44
+ ThinkingPart,
41
45
  ToolCallPart,
42
46
  VideoUrl,
43
47
  )
@@ -543,7 +547,44 @@ class StreamedResponse(ABC):
543
547
  async for event in iterator:
544
548
  yield event
545
549
 
546
- self._event_iterator = iterator_with_final_event(self._get_event_iterator())
550
+ async def iterator_with_part_end(
551
+ iterator: AsyncIterator[ModelResponseStreamEvent],
552
+ ) -> AsyncIterator[ModelResponseStreamEvent]:
553
+ last_start_event: PartStartEvent | None = None
554
+
555
+ def part_end_event(next_part: ModelResponsePart | None = None) -> PartEndEvent | None:
556
+ if not last_start_event:
557
+ return None
558
+
559
+ index = last_start_event.index
560
+ part = self._parts_manager.get_parts()[index]
561
+ if not isinstance(part, TextPart | ThinkingPart | BaseToolCallPart):
562
+ # Parts other than these 3 don't have deltas, so don't need an end part.
563
+ return None
564
+
565
+ return PartEndEvent(
566
+ index=index,
567
+ part=part,
568
+ next_part_kind=next_part.part_kind if next_part else None,
569
+ )
570
+
571
+ async for event in iterator:
572
+ if isinstance(event, PartStartEvent):
573
+ if last_start_event:
574
+ end_event = part_end_event(event.part)
575
+ if end_event:
576
+ yield end_event
577
+
578
+ event.previous_part_kind = last_start_event.part.part_kind
579
+ last_start_event = event
580
+
581
+ yield event
582
+
583
+ end_event = part_end_event()
584
+ if end_event:
585
+ yield end_event
586
+
587
+ self._event_iterator = iterator_with_part_end(iterator_with_final_event(self._get_event_iterator()))
547
588
  return self._event_iterator
548
589
 
549
590
  @abstractmethod
@@ -524,6 +524,8 @@ class GroqStreamedResponse(StreamedResponse):
524
524
  async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: # noqa: C901
525
525
  try:
526
526
  executed_tool_call_id: str | None = None
527
+ reasoning_index = 0
528
+ reasoning = False
527
529
  async for chunk in self._response:
528
530
  self._usage += _map_usage(chunk)
529
531
 
@@ -540,10 +542,16 @@ class GroqStreamedResponse(StreamedResponse):
540
542
  self.finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
541
543
 
542
544
  if choice.delta.reasoning is not None:
545
+ if not reasoning:
546
+ reasoning_index += 1
547
+ reasoning = True
548
+
543
549
  # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`.
544
550
  yield self._parts_manager.handle_thinking_delta(
545
- vendor_part_id='reasoning', content=choice.delta.reasoning
551
+ vendor_part_id=f'reasoning-{reasoning_index}', content=choice.delta.reasoning
546
552
  )
553
+ else:
554
+ reasoning = False
547
555
 
548
556
  if choice.delta.executed_tools:
549
557
  for tool in choice.delta.executed_tools:
@@ -1148,10 +1148,10 @@ class OpenAIResponsesModel(Model):
1148
1148
  + list(model_settings.get('openai_builtin_tools', []))
1149
1149
  + self._get_tools(model_request_parameters)
1150
1150
  )
1151
-
1151
+ profile = OpenAIModelProfile.from_profile(self.profile)
1152
1152
  if not tools:
1153
1153
  tool_choice: Literal['none', 'required', 'auto'] | None = None
1154
- elif not model_request_parameters.allow_text_output:
1154
+ elif not model_request_parameters.allow_text_output and profile.openai_supports_tool_choice_required:
1155
1155
  tool_choice = 'required'
1156
1156
  else:
1157
1157
  tool_choice = 'auto'
@@ -1184,7 +1184,6 @@ class OpenAIResponsesModel(Model):
1184
1184
  text = text or {}
1185
1185
  text['verbosity'] = verbosity
1186
1186
 
1187
- profile = OpenAIModelProfile.from_profile(self.profile)
1188
1187
  unsupported_model_settings = profile.openai_unsupported_model_settings
1189
1188
  for setting in unsupported_model_settings:
1190
1189
  model_settings.pop(setting, None)