pydantic-ai 0.4.8__tar.gz → 0.4.10__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 might be problematic. Click here for more details.

Files changed (346) hide show
  1. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/PKG-INFO +3 -3
  2. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_gemini.py +24 -8
  3. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_ag_ui.py +110 -12
  4. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_agent.py +42 -1
  5. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_streaming.py +36 -0
  6. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_tools.py +100 -2
  7. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_toolsets.py +133 -1
  8. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/.gitignore +0 -0
  9. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/LICENSE +0 -0
  10. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/Makefile +0 -0
  11. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/README.md +0 -0
  12. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/pyproject.toml +0 -0
  13. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/__init__.py +0 -0
  14. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/assets/dummy.pdf +0 -0
  15. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/assets/kiwi.png +0 -0
  16. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/assets/marcelo.mp3 +0 -0
  17. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/assets/product_name.txt +0 -0
  18. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/assets/small_video.mp4 +0 -0
  19. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_agent_with_server_not_running.yaml +0 -0
  20. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml +0 -0
  21. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_audio_resource.yaml +0 -0
  22. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_audio_resource_link.yaml +0 -0
  23. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_dict.yaml +0 -0
  24. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_error.yaml +0 -0
  25. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_image.yaml +0 -0
  26. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_image_resource.yaml +0 -0
  27. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_image_resource_link.yaml +0 -0
  28. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_multiple_items.yaml +0 -0
  29. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_none.yaml +0 -0
  30. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_str.yaml +0 -0
  31. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_text_resource.yaml +0 -0
  32. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_mcp/test_tool_returning_text_resource_link.yaml +0 -0
  33. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[anthropic].yaml +0 -0
  34. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[bedrock].yaml +0 -0
  35. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[cohere].yaml +0 -0
  36. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[gemini].yaml +0 -0
  37. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[google].yaml +0 -0
  38. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[groq].yaml +0 -0
  39. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[mistral].yaml +0 -0
  40. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/cassettes/test_settings/test_stop_settings[openai].yaml +0 -0
  41. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/conftest.py +0 -0
  42. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/__init__.py +0 -0
  43. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_dataset.py +0 -0
  44. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_evaluator_base.py +0 -0
  45. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_evaluator_common.py +0 -0
  46. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_evaluator_context.py +0 -0
  47. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_evaluator_spec.py +0 -0
  48. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_evaluators.py +0 -0
  49. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_llm_as_a_judge.py +0 -0
  50. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_otel.py +0 -0
  51. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_render_numbers.py +0 -0
  52. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_reporting.py +0 -0
  53. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_reports.py +0 -0
  54. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/test_utils.py +0 -0
  55. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/evals/utils.py +0 -0
  56. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/example_modules/README.md +0 -0
  57. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/example_modules/bank_database.py +0 -0
  58. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/example_modules/fake_database.py +0 -0
  59. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/example_modules/mcp_server.py +0 -0
  60. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/example_modules/weather_service.py +0 -0
  61. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/ext/__init__.py +0 -0
  62. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/ext/test_langchain.py +0 -0
  63. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/__init__.py +0 -0
  64. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_file_persistence.py +0 -0
  65. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_graph.py +0 -0
  66. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_mermaid.py +0 -0
  67. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_persistence.py +0 -0
  68. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_state.py +0 -0
  69. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/graph/test_utils.py +0 -0
  70. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/import_examples.py +0 -0
  71. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/json_body_serializer.py +0 -0
  72. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/mcp_server.py +0 -0
  73. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/__init__.py +0 -0
  74. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_model_empty_message_on_history.yaml +0 -0
  75. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_model_instructions.yaml +0 -0
  76. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml +0 -0
  77. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml +0 -0
  78. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_prompted_output.yaml +0 -0
  79. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_prompted_output_multiple.yaml +0 -0
  80. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_text_output_function.yaml +0 -0
  81. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_tool_output.yaml +0 -0
  82. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_anthropic_tool_with_thinking.yaml +0 -0
  83. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_document_binary_content_input.yaml +0 -0
  84. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_document_url_input.yaml +0 -0
  85. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_extra_headers.yaml +0 -0
  86. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_image_as_binary_content_tool_response.yaml +0 -0
  87. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
  88. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
  89. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
  90. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_anthropic/test_text_document_url_input.yaml +0 -0
  91. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_empty_system_prompt.yaml +0 -0
  92. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model.yaml +0 -0
  93. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_anthropic_model_without_tools.yaml +0 -0
  94. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_guardrail_config.yaml +0 -0
  95. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_instructions.yaml +0 -0
  96. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_iter_stream.yaml +0 -0
  97. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_max_tokens.yaml +0 -0
  98. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_other_parameters.yaml +0 -0
  99. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_performance_config.yaml +0 -0
  100. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_retry.yaml +0 -0
  101. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_stream.yaml +0 -0
  102. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_structured_output.yaml +0 -0
  103. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml +0 -0
  104. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml +0 -0
  105. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_model_top_p.yaml +0 -0
  106. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_bedrock_multiple_documents_in_history.yaml +0 -0
  107. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_document_url_input.yaml +0 -0
  108. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_image_as_binary_content_input.yaml +0 -0
  109. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_image_url_input.yaml +0 -0
  110. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_text_as_binary_content_input.yaml +0 -0
  111. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_text_document_url_input.yaml +0 -0
  112. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_video_as_binary_content_input.yaml +0 -0
  113. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_bedrock/test_video_url_input.yaml +0 -0
  114. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_cohere/test_cohere_model_instructions.yaml +0 -0
  115. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml +0 -0
  116. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_cohere/test_request_simple_success_with_vcr.yaml +0 -0
  117. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml +0 -0
  118. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_stream.yaml +0 -0
  119. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_download_item/test_download_item_application_octet_stream.yaml +0 -0
  120. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_download_item/test_download_item_audio_mpeg.yaml +0 -0
  121. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_download_item/test_download_item_no_content_type.yaml +0 -0
  122. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_document_url_input.yaml +0 -0
  123. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_false.yaml +0 -0
  124. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_true.yaml +0 -0
  125. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml +0 -0
  126. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_exclusive_minimum_and_maximum.yaml +0 -0
  127. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_model_instructions.yaml +0 -0
  128. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml +0 -0
  129. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_native_output.yaml +0 -0
  130. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_native_output_multiple.yaml +0 -0
  131. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_prompted_output.yaml +0 -0
  132. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_prompted_output_multiple.yaml +0 -0
  133. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_prompted_output_with_tools.yaml +0 -0
  134. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_text_output_function.yaml +0 -0
  135. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_tool_config_any_with_tool_without_args.yaml +0 -0
  136. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_tool_output.yaml +0 -0
  137. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_gemini_youtube_video_url_input.yaml +0 -0
  138. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +0 -0
  139. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_image_as_binary_content_tool_response.yaml +0 -0
  140. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +0 -0
  141. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_labels_are_ignored_with_gla_provider.yaml +0 -0
  142. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_video_as_binary_content_input.yaml +0 -0
  143. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini/test_video_url_input.yaml +0 -0
  144. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_labels.yaml +0 -0
  145. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[AudioUrl (gs)].yaml +0 -0
  146. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[AudioUrl].yaml +0 -0
  147. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[DocumentUrl (gs)].yaml +0 -0
  148. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[DocumentUrl].yaml +0 -0
  149. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[ImageUrl (gs)].yaml +0 -0
  150. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[ImageUrl].yaml +0 -0
  151. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl (YouTube)].yaml +0 -0
  152. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl (gs)].yaml +0 -0
  153. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl].yaml +0 -0
  154. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_gemini_vertex/test_url_input_force_download.yaml +0 -0
  155. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model.yaml +0 -0
  156. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_document_url_input.yaml +0 -0
  157. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_empty_assistant_response.yaml +0 -0
  158. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_empty_user_prompt.yaml +0 -0
  159. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_image_as_binary_content_input.yaml +0 -0
  160. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_image_url_input.yaml +0 -0
  161. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_instructions.yaml +0 -0
  162. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_iter_stream.yaml +0 -0
  163. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_max_tokens.yaml +0 -0
  164. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_multiple_documents_in_history.yaml +0 -0
  165. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_retry.yaml +0 -0
  166. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_safety_settings.yaml +0 -0
  167. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_stream.yaml +0 -0
  168. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_structured_output.yaml +0 -0
  169. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_text_as_binary_content_input.yaml +0 -0
  170. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_text_document_url_input.yaml +0 -0
  171. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_thinking_config.yaml +0 -0
  172. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_thinking_part.yaml +0 -0
  173. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_thinking_part_iter.yaml +0 -0
  174. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_top_p.yaml +0 -0
  175. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_vertex_labels.yaml +0 -0
  176. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_vertex_provider.yaml +0 -0
  177. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_video_as_binary_content_input.yaml +0 -0
  178. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_video_as_binary_content_input_with_vendor_metadata.yaml +0 -0
  179. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_video_url_input.yaml +0 -0
  180. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_model_youtube_video_url_input_with_vendor_metadata.yaml +0 -0
  181. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_native_output.yaml +0 -0
  182. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_native_output_multiple.yaml +0 -0
  183. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_prompted_output.yaml +0 -0
  184. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_prompted_output_multiple.yaml +0 -0
  185. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_prompted_output_with_tools.yaml +0 -0
  186. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_text_output_function.yaml +0 -0
  187. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_timeout.yaml +0 -0
  188. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_tool_config_any_with_tool_without_args.yaml +0 -0
  189. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_tool_output.yaml +0 -0
  190. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[AudioUrl (gs)].yaml +0 -0
  191. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[AudioUrl].yaml +0 -0
  192. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[DocumentUrl (gs)].yaml +0 -0
  193. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[DocumentUrl].yaml +0 -0
  194. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[ImageUrl (gs)].yaml +0 -0
  195. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[ImageUrl].yaml +0 -0
  196. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl (YouTube)].yaml +0 -0
  197. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl (gs)].yaml +0 -0
  198. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl].yaml +0 -0
  199. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_google/test_google_url_input_force_download.yaml +0 -0
  200. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_extra_headers.yaml +0 -0
  201. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_groq_model_instructions.yaml +0 -0
  202. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml +0 -0
  203. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_groq_model_thinking_part_iter.yaml +0 -0
  204. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
  205. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_image_as_binary_content_tool_response.yaml +0 -0
  206. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
  207. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_hf_model_instructions.yaml +0 -0
  208. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_hf_model_thinking_part.yaml +0 -0
  209. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_hf_model_thinking_part_iter.yaml +0 -0
  210. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_image_as_binary_content_input.yaml +0 -0
  211. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_image_url_input.yaml +0 -0
  212. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_max_completion_tokens[Qwen-Qwen2.5-72B-Instruct].yaml +0 -0
  213. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_max_completion_tokens[deepseek-ai-DeepSeek-R1-0528].yaml +0 -0
  214. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_max_completion_tokens[meta-llama-Llama-3.3-70B-Instruct].yaml +0 -0
  215. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_request_simple_success_with_vcr.yaml +0 -0
  216. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_request_simple_usage.yaml +0 -0
  217. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_simple_completion.yaml +0 -0
  218. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_huggingface/test_stream_completion.yaml +0 -0
  219. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_mistral/test_image_as_binary_content_tool_response.yaml +0 -0
  220. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_mistral/test_mistral_model_instructions.yaml +0 -0
  221. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml +0 -0
  222. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_model_names/test_known_model_names.yaml +0 -0
  223. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
  224. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_compatible_api_with_tool_calls_without_id.yaml +0 -0
  225. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_document_as_binary_content_input.yaml +0 -0
  226. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_document_url_input.yaml +0 -0
  227. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_extra_headers.yaml +0 -0
  228. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
  229. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_image_as_binary_content_tool_response.yaml +0 -0
  230. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_image_url_tool_response.yaml +0 -0
  231. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_invalid_response.yaml +0 -0
  232. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4.5-preview].yaml +0 -0
  233. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4o-mini].yaml +0 -0
  234. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_max_completion_tokens[o3-mini].yaml +0 -0
  235. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_multiple_agent_tool_calls.yaml +0 -0
  236. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_audio_url_input.yaml +0 -0
  237. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_instructions.yaml +0 -0
  238. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_instructions_with_tool_calls_keep_instructions.yaml +0 -0
  239. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml +0 -0
  240. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_model_thinking_part_iter.yaml +0 -0
  241. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_model_without_system_prompt.yaml +0 -0
  242. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_native_output.yaml +0 -0
  243. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_native_output_multiple.yaml +0 -0
  244. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
  245. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
  246. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_prompted_output.yaml +0 -0
  247. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_prompted_output_multiple.yaml +0 -0
  248. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml +0 -0
  249. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_text_output_function.yaml +0 -0
  250. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_openai_tool_output.yaml +0 -0
  251. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_reasoning_model_with_temperature.yaml +0 -0
  252. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_text_response.yaml +0 -0
  253. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_user_id.yaml +0 -0
  254. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai/test_valid_response.yaml +0 -0
  255. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_audio_as_binary_content_input.yaml +0 -0
  256. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_input.yaml +0 -0
  257. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_tool_response.yaml +0 -0
  258. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_native_output.yaml +0 -0
  259. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_native_output_multiple.yaml +0 -0
  260. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_as_binary_content_input.yaml +0 -0
  261. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_url_input.yaml +0 -0
  262. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_image_url_input.yaml +0 -0
  263. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_builtin_tools.yaml +0 -0
  264. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_http_error.yaml +0 -0
  265. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_instructions.yaml +0 -0
  266. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_retry.yaml +0 -0
  267. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response.yaml +0 -0
  268. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response_with_tool_call.yaml +0 -0
  269. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_output_type.yaml +0 -0
  270. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_effort.yaml +0 -0
  271. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_generate_summary.yaml +0 -0
  272. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_stream.yaml +0 -0
  273. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_system_prompt.yaml +0 -0
  274. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_openai_responses_text_document_url_input.yaml +0 -0
  275. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_prompted_output.yaml +0 -0
  276. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_prompted_output_multiple.yaml +0 -0
  277. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_reasoning_model_with_temperature.yaml +0 -0
  278. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_text_output_function.yaml +0 -0
  279. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/cassettes/test_openai_responses/test_tool_output.yaml +0 -0
  280. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/mock_async_stream.py +0 -0
  281. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_anthropic.py +0 -0
  282. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_bedrock.py +0 -0
  283. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_cohere.py +0 -0
  284. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_deepseek.py +0 -0
  285. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_download_item.py +0 -0
  286. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_fallback.py +0 -0
  287. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_gemini_vertex.py +0 -0
  288. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_google.py +0 -0
  289. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_groq.py +0 -0
  290. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_huggingface.py +0 -0
  291. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_instrumented.py +0 -0
  292. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_mcp_sampling.py +0 -0
  293. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_mistral.py +0 -0
  294. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model.py +0 -0
  295. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model_function.py +0 -0
  296. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model_names.py +0 -0
  297. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model_request_parameters.py +0 -0
  298. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model_settings.py +0 -0
  299. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_model_test.py +0 -0
  300. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_openai.py +0 -0
  301. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/models/test_openai_responses.py +0 -0
  302. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/__init__.py +0 -0
  303. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/cassettes/test_azure/test_azure_provider_call.yaml +0 -0
  304. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/cassettes/test_google_vertex/test_vertexai_provider.yaml +0 -0
  305. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/cassettes/test_heroku/test_heroku_model_provider_claude_3_7_sonnet.yaml +0 -0
  306. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/cassettes/test_openrouter/test_openrouter_with_google_model.yaml +0 -0
  307. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_anthropic.py +0 -0
  308. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_azure.py +0 -0
  309. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_bedrock.py +0 -0
  310. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_cohere.py +0 -0
  311. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_deepseek.py +0 -0
  312. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_fireworks.py +0 -0
  313. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_github.py +0 -0
  314. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_google_gla.py +0 -0
  315. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_google_vertex.py +0 -0
  316. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_grok.py +0 -0
  317. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_groq.py +0 -0
  318. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_heroku.py +0 -0
  319. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_huggingface.py +0 -0
  320. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_mistral.py +0 -0
  321. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_moonshotai.py +0 -0
  322. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_openai.py +0 -0
  323. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_openrouter.py +0 -0
  324. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_provider_names.py +0 -0
  325. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_together.py +0 -0
  326. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/providers/test_vercel.py +0 -0
  327. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_a2a.py +0 -0
  328. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_cli.py +0 -0
  329. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_deps.py +0 -0
  330. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_direct.py +0 -0
  331. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_examples.py +0 -0
  332. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_format_as_xml.py +0 -0
  333. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_history_processor.py +0 -0
  334. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_json_body_serializer.py +0 -0
  335. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_live.py +0 -0
  336. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_logfire.py +0 -0
  337. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_mcp.py +0 -0
  338. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_messages.py +0 -0
  339. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_parts_manager.py +0 -0
  340. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_settings.py +0 -0
  341. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_tenacity.py +0 -0
  342. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_thinking_part.py +0 -0
  343. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_usage_limits.py +0 -0
  344. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/test_utils.py +0 -0
  345. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/typed_agent.py +0 -0
  346. {pydantic_ai-0.4.8 → pydantic_ai-0.4.10}/tests/typed_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai
3
- Version: 0.4.8
3
+ Version: 0.4.10
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs
5
5
  Project-URL: Homepage, https://ai.pydantic.dev
6
6
  Project-URL: Source, https://github.com/pydantic/pydantic-ai
@@ -28,11 +28,11 @@ Classifier: Topic :: Internet
28
28
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Requires-Python: >=3.9
31
- Requires-Dist: pydantic-ai-slim[ag-ui,anthropic,bedrock,cli,cohere,evals,google,groq,huggingface,mcp,mistral,openai,retries,vertexai]==0.4.8
31
+ Requires-Dist: pydantic-ai-slim[ag-ui,anthropic,bedrock,cli,cohere,evals,google,groq,huggingface,mcp,mistral,openai,retries,vertexai]==0.4.10
32
32
  Provides-Extra: a2a
33
33
  Requires-Dist: fasta2a>=0.4.1; extra == 'a2a'
34
34
  Provides-Extra: examples
35
- Requires-Dist: pydantic-ai-examples==0.4.8; extra == 'examples'
35
+ Requires-Dist: pydantic-ai-examples==0.4.10; extra == 'examples'
36
36
  Provides-Extra: logfire
37
37
  Requires-Dist: logfire>=3.11.0; extra == 'logfire'
38
38
  Description-Content-Type: text/markdown
@@ -194,7 +194,7 @@ async def test_require_response_tool(allow_model_requests: None):
194
194
 
195
195
  async def test_json_def_replaced(allow_model_requests: None):
196
196
  class Axis(BaseModel):
197
- label: str
197
+ label: str = Field(default='<unlabeled axis>', description='The label of the axis')
198
198
 
199
199
  class Chart(BaseModel):
200
200
  x_axis: Axis
@@ -213,8 +213,14 @@ async def test_json_def_replaced(allow_model_requests: None):
213
213
  {
214
214
  '$defs': {
215
215
  'Axis': {
216
- 'properties': {'label': {'title': 'Label', 'type': 'string'}},
217
- 'required': ['label'],
216
+ 'properties': {
217
+ 'label': {
218
+ 'default': '<unlabeled axis>',
219
+ 'description': 'The label of the axis',
220
+ 'title': 'Label',
221
+ 'type': 'string',
222
+ }
223
+ },
218
224
  'title': 'Axis',
219
225
  'type': 'object',
220
226
  },
@@ -268,17 +274,27 @@ async def test_json_def_replaced(allow_model_requests: None):
268
274
  'items': {
269
275
  'properties': {
270
276
  'lat': {'type': 'number'},
271
- 'lng': {'type': 'number'},
277
+ 'lng': {'default': 1.1, 'type': 'number'},
272
278
  'chart': {
273
279
  'properties': {
274
280
  'x_axis': {
275
- 'properties': {'label': {'type': 'string'}},
276
- 'required': ['label'],
281
+ 'properties': {
282
+ 'label': {
283
+ 'default': '<unlabeled axis>',
284
+ 'description': 'The label of the axis',
285
+ 'type': 'string',
286
+ }
287
+ },
277
288
  'type': 'object',
278
289
  },
279
290
  'y_axis': {
280
- 'properties': {'label': {'type': 'string'}},
281
- 'required': ['label'],
291
+ 'properties': {
292
+ 'label': {
293
+ 'default': '<unlabeled axis>',
294
+ 'description': 'The label of the axis',
295
+ 'type': 'string',
296
+ }
297
+ },
282
298
  'type': 'object',
283
299
  },
284
300
  },
@@ -7,6 +7,7 @@ import contextlib
7
7
  import json
8
8
  import uuid
9
9
  from collections.abc import AsyncIterator
10
+ from dataclasses import dataclass
10
11
  from http import HTTPStatus
11
12
  from typing import Any
12
13
 
@@ -17,7 +18,9 @@ from dirty_equals import IsStr
17
18
  from inline_snapshot import snapshot
18
19
  from pydantic import BaseModel
19
20
 
21
+ from pydantic_ai._run_context import RunContext
20
22
  from pydantic_ai.agent import Agent
23
+ from pydantic_ai.exceptions import UserError
21
24
  from pydantic_ai.messages import ModelMessage
22
25
  from pydantic_ai.models.function import (
23
26
  AgentInfo,
@@ -27,8 +30,9 @@ from pydantic_ai.models.function import (
27
30
  DeltaToolCalls,
28
31
  FunctionModel,
29
32
  )
33
+ from pydantic_ai.models.test import TestModel
30
34
  from pydantic_ai.output import OutputDataT
31
- from pydantic_ai.tools import AgentDepsT
35
+ from pydantic_ai.tools import AgentDepsT, ToolDefinition
32
36
 
33
37
  from .conftest import IsSameStr
34
38
 
@@ -180,7 +184,7 @@ def create_input(
180
184
  thread_id=thread_id,
181
185
  run_id=uuid_str(),
182
186
  messages=list(messages),
183
- state=state,
187
+ state=dict(state) if state else {},
184
188
  context=[],
185
189
  tools=tools or [],
186
190
  forwarded_props=None,
@@ -1050,9 +1054,19 @@ async def test_tool_local_then_ag_ui() -> None:
1050
1054
  async def test_request_with_state() -> None:
1051
1055
  """Test request with state modification."""
1052
1056
 
1057
+ seen_states: list[int] = []
1058
+
1059
+ async def store_state(
1060
+ ctx: RunContext[StateDeps[StateInt]], tool_defs: list[ToolDefinition]
1061
+ ) -> list[ToolDefinition]:
1062
+ seen_states.append(ctx.deps.state.value)
1063
+ ctx.deps.state.value += 1
1064
+ return tool_defs
1065
+
1053
1066
  agent: Agent[StateDeps[StateInt], str] = Agent(
1054
1067
  model=FunctionModel(stream_function=simple_stream),
1055
1068
  deps_type=StateDeps[StateInt], # type: ignore[reportUnknownArgumentType]
1069
+ prepare_tools=store_state,
1056
1070
  )
1057
1071
  adapter = _Adapter(agent=agent)
1058
1072
  run_inputs = [
@@ -1074,32 +1088,101 @@ async def test_request_with_state() -> None:
1074
1088
  id='msg_3',
1075
1089
  content='Hello, how are you?',
1076
1090
  ),
1091
+ ),
1092
+ create_input(
1093
+ UserMessage(
1094
+ id='msg_4',
1095
+ content='Hello, how are you?',
1096
+ ),
1077
1097
  state=StateInt(value=42),
1078
1098
  ),
1079
1099
  ]
1080
1100
 
1081
- deps = StateDeps(StateInt())
1101
+ deps = StateDeps(StateInt(value=0))
1082
1102
 
1083
- last_value = deps.state.value
1084
1103
  for run_input in run_inputs:
1085
1104
  events = list[dict[str, Any]]()
1086
1105
  async for event in adapter.run(run_input, deps=deps):
1087
1106
  events.append(json.loads(event.removeprefix('data: ')))
1088
1107
 
1089
1108
  assert events == simple_result()
1090
- assert deps.state.value == run_input.state.value if run_input.state is not None else last_value
1091
- last_value = deps.state.value
1109
+ assert seen_states == snapshot(
1110
+ [
1111
+ 41, # run msg_1, prepare_tools call 1
1112
+ 42, # run msg_1, prepare_tools call 2
1113
+ 0, # run msg_2, prepare_tools call 1
1114
+ 1, # run msg_2, prepare_tools call 2
1115
+ 0, # run msg_3, prepare_tools call 1
1116
+ 1, # run msg_3, prepare_tools call 2
1117
+ 42, # run msg_4, prepare_tools call 1
1118
+ 43, # run msg_4, prepare_tools call 2
1119
+ ]
1120
+ )
1121
+
1122
+
1123
+ async def test_request_with_state_without_handler() -> None:
1124
+ agent = Agent(model=FunctionModel(stream_function=simple_stream))
1125
+ adapter = _Adapter(agent=agent)
1126
+ run_input = create_input(
1127
+ UserMessage(
1128
+ id='msg_1',
1129
+ content='Hello, how are you?',
1130
+ ),
1131
+ state=StateInt(value=41),
1132
+ )
1133
+
1134
+ with pytest.raises(
1135
+ UserError,
1136
+ match='AG-UI state is provided but `deps` of type `NoneType` does not implement the `StateHandler` protocol: it needs to be a dataclass with a non-optional `state` field.',
1137
+ ):
1138
+ async for _ in adapter.run(run_input):
1139
+ pass
1140
+
1141
+
1142
+ async def test_request_with_state_with_custom_handler() -> None:
1143
+ @dataclass
1144
+ class CustomStateDeps:
1145
+ state: dict[str, Any]
1146
+
1147
+ seen_states: list[dict[str, Any]] = []
1148
+
1149
+ async def store_state(ctx: RunContext[CustomStateDeps], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
1150
+ seen_states.append(ctx.deps.state)
1151
+ return tool_defs
1152
+
1153
+ agent: Agent[CustomStateDeps, str] = Agent(
1154
+ model=FunctionModel(stream_function=simple_stream),
1155
+ deps_type=CustomStateDeps,
1156
+ prepare_tools=store_state,
1157
+ )
1158
+ adapter = _Adapter(agent=agent)
1159
+ run_input = create_input(
1160
+ UserMessage(
1161
+ id='msg_1',
1162
+ content='Hello, how are you?',
1163
+ ),
1164
+ state={'value': 42},
1165
+ )
1166
+
1167
+ async for _ in adapter.run(run_input, deps=CustomStateDeps(state={'value': 0})):
1168
+ pass
1092
1169
 
1093
- assert deps.state.value == 42
1170
+ assert seen_states[-1] == {'value': 42}
1094
1171
 
1095
1172
 
1096
1173
  async def test_concurrent_runs() -> None:
1097
1174
  """Test concurrent execution of multiple runs."""
1098
1175
  import asyncio
1099
1176
 
1100
- agent = Agent(
1101
- model=FunctionModel(stream_function=simple_stream),
1177
+ agent: Agent[StateDeps[StateInt], str] = Agent(
1178
+ model=TestModel(),
1179
+ deps_type=StateDeps[StateInt], # type: ignore[reportUnknownArgumentType]
1102
1180
  )
1181
+
1182
+ @agent.tool
1183
+ async def get_state(ctx: RunContext[StateDeps[StateInt]]) -> int:
1184
+ return ctx.deps.state.value
1185
+
1103
1186
  adapter = _Adapter(agent=agent)
1104
1187
  concurrent_tasks: list[asyncio.Task[list[dict[str, Any]]]] = []
1105
1188
 
@@ -1109,10 +1192,11 @@ async def test_concurrent_runs() -> None:
1109
1192
  id=f'msg_{i}',
1110
1193
  content=f'Message {i}',
1111
1194
  ),
1195
+ state=StateInt(value=i),
1112
1196
  thread_id=f'test_thread_{i}',
1113
1197
  )
1114
1198
 
1115
- task = asyncio.create_task(collect_events_from_adapter(adapter, run_input))
1199
+ task = asyncio.create_task(collect_events_from_adapter(adapter, run_input, deps=StateDeps(StateInt())))
1116
1200
  concurrent_tasks.append(task)
1117
1201
 
1118
1202
  results = await asyncio.gather(*concurrent_tasks)
@@ -1121,9 +1205,23 @@ async def test_concurrent_runs() -> None:
1121
1205
  for i, events in enumerate(results):
1122
1206
  assert events == [
1123
1207
  {'type': 'RUN_STARTED', 'threadId': f'test_thread_{i}', 'runId': (run_id := IsSameStr())},
1208
+ {
1209
+ 'type': 'TOOL_CALL_START',
1210
+ 'toolCallId': (tool_call_id := IsSameStr()),
1211
+ 'toolCallName': 'get_state',
1212
+ 'parentMessageId': IsStr(),
1213
+ },
1214
+ {'type': 'TOOL_CALL_END', 'toolCallId': tool_call_id},
1215
+ {
1216
+ 'type': 'TOOL_CALL_RESULT',
1217
+ 'messageId': IsStr(),
1218
+ 'toolCallId': tool_call_id,
1219
+ 'content': str(i),
1220
+ 'role': 'tool',
1221
+ },
1124
1222
  {'type': 'TEXT_MESSAGE_START', 'messageId': (message_id := IsSameStr()), 'role': 'assistant'},
1125
- {'type': 'TEXT_MESSAGE_CONTENT', 'messageId': message_id, 'delta': 'success '},
1126
- {'type': 'TEXT_MESSAGE_CONTENT', 'messageId': message_id, 'delta': '(no tool calls)'},
1223
+ {'type': 'TEXT_MESSAGE_CONTENT', 'messageId': message_id, 'delta': '{"get_s'},
1224
+ {'type': 'TEXT_MESSAGE_CONTENT', 'messageId': message_id, 'delta': 'tate":' + str(i) + '}'},
1127
1225
  {'type': 'TEXT_MESSAGE_END', 'messageId': message_id},
1128
1226
  {'type': 'RUN_FINISHED', 'threadId': f'test_thread_{i}', 'runId': run_id},
1129
1227
  ]
@@ -3866,7 +3866,7 @@ def test_prepare_output_tools():
3866
3866
  )
3867
3867
 
3868
3868
 
3869
- async def test_context_manager():
3869
+ async def test_explicit_context_manager():
3870
3870
  try:
3871
3871
  from pydantic_ai.mcp import MCPServerStdio
3872
3872
  except ImportError: # pragma: lax no cover
@@ -3886,6 +3886,47 @@ async def test_context_manager():
3886
3886
  assert server2.is_running
3887
3887
 
3888
3888
 
3889
+ async def test_implicit_context_manager():
3890
+ try:
3891
+ from pydantic_ai.mcp import MCPServerStdio
3892
+ except ImportError: # pragma: lax no cover
3893
+ pytest.skip('mcp is not installed')
3894
+
3895
+ server1 = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
3896
+ server2 = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
3897
+ toolset = CombinedToolset([server1, PrefixedToolset(server2, 'prefix')])
3898
+ agent = Agent('test', toolsets=[toolset])
3899
+
3900
+ async with agent.iter(
3901
+ user_prompt='Hello',
3902
+ ):
3903
+ assert server1.is_running
3904
+ assert server2.is_running
3905
+
3906
+
3907
+ def test_parallel_mcp_calls():
3908
+ try:
3909
+ from pydantic_ai.mcp import MCPServerStdio
3910
+ except ImportError: # pragma: lax no cover
3911
+ pytest.skip('mcp is not installed')
3912
+
3913
+ async def call_tools_parallel(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
3914
+ if len(messages) == 1:
3915
+ return ModelResponse(
3916
+ parts=[
3917
+ ToolCallPart(tool_name='get_none'),
3918
+ ToolCallPart(tool_name='get_multiple_items'),
3919
+ ]
3920
+ )
3921
+ else:
3922
+ return ModelResponse(parts=[TextPart('finished')])
3923
+
3924
+ server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
3925
+ agent = Agent(FunctionModel(call_tools_parallel), toolsets=[server])
3926
+ result = agent.run_sync()
3927
+ assert result.output == snapshot('finished')
3928
+
3929
+
3889
3930
  def test_set_mcp_sampling_model():
3890
3931
  try:
3891
3932
  from pydantic_ai.mcp import MCPServerStdio
@@ -1108,6 +1108,42 @@ async def test_iter_stream_structured_output():
1108
1108
  )
1109
1109
 
1110
1110
 
1111
+ async def test_iter_stream_output_tool_dont_hit_retry_limit():
1112
+ class CityLocation(BaseModel):
1113
+ city: str
1114
+ country: str | None = None
1115
+
1116
+ async def text_stream(_messages: list[ModelMessage], agent_info: AgentInfo) -> AsyncIterator[DeltaToolCalls]:
1117
+ """Stream partial JSON data that will initially fail validation."""
1118
+ assert agent_info.output_tools is not None
1119
+ assert len(agent_info.output_tools) == 1
1120
+ name = agent_info.output_tools[0].name
1121
+
1122
+ yield {0: DeltaToolCall(name=name)}
1123
+ yield {0: DeltaToolCall(json_args='{"c')}
1124
+ yield {0: DeltaToolCall(json_args='ity":')}
1125
+ yield {0: DeltaToolCall(json_args=' "Mex')}
1126
+ yield {0: DeltaToolCall(json_args='ico City",')}
1127
+ yield {0: DeltaToolCall(json_args=' "cou')}
1128
+ yield {0: DeltaToolCall(json_args='ntry": "Mexico"}')}
1129
+
1130
+ agent = Agent(FunctionModel(stream_function=text_stream), output_type=CityLocation)
1131
+
1132
+ async with agent.iter('Generate city info') as run:
1133
+ async for node in run:
1134
+ if agent.is_model_request_node(node):
1135
+ async with node.stream(run.ctx) as stream:
1136
+ assert [c async for c in stream.stream_output(debounce_by=None)] == snapshot(
1137
+ [
1138
+ CityLocation(city='Mex'),
1139
+ CityLocation(city='Mexico City'),
1140
+ CityLocation(city='Mexico City'),
1141
+ CityLocation(city='Mexico City', country='Mexico'),
1142
+ CityLocation(city='Mexico City', country='Mexico'),
1143
+ ]
1144
+ )
1145
+
1146
+
1111
1147
  def test_function_tool_event_tool_call_id_properties():
1112
1148
  """Ensure that the `tool_call_id` property on function tool events mirrors the underlying part's ID."""
1113
1149
  # Prepare a ToolCallPart with a fixed ID
@@ -13,7 +13,16 @@ from typing_extensions import TypedDict
13
13
 
14
14
  from pydantic_ai import Agent, RunContext, Tool, UserError
15
15
  from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior
16
- from pydantic_ai.messages import ModelMessage, ModelRequest, ModelResponse, TextPart, ToolCallPart, ToolReturnPart
16
+ from pydantic_ai.messages import (
17
+ ModelMessage,
18
+ ModelRequest,
19
+ ModelResponse,
20
+ TextPart,
21
+ ToolCallPart,
22
+ ToolReturn,
23
+ ToolReturnPart,
24
+ UserPromptPart,
25
+ )
17
26
  from pydantic_ai.models.function import AgentInfo, FunctionModel
18
27
  from pydantic_ai.models.test import TestModel
19
28
  from pydantic_ai.output import DeferredToolCalls, ToolOutput
@@ -21,8 +30,9 @@ from pydantic_ai.tools import ToolDefinition
21
30
  from pydantic_ai.toolsets.deferred import DeferredToolset
22
31
  from pydantic_ai.toolsets.function import FunctionToolset
23
32
  from pydantic_ai.toolsets.prefixed import PrefixedToolset
33
+ from pydantic_ai.usage import Usage
24
34
 
25
- from .conftest import IsStr
35
+ from .conftest import IsDatetime, IsStr
26
36
 
27
37
 
28
38
  def test_tool_no_ctx():
@@ -1321,3 +1331,91 @@ def test_output_type_deferred_tool_calls_by_itself():
1321
1331
  def test_output_type_empty():
1322
1332
  with pytest.raises(UserError, match='At least one output type must be provided.'):
1323
1333
  Agent(TestModel(), output_type=[])
1334
+
1335
+
1336
+ def test_parallel_tool_return():
1337
+ def llm(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
1338
+ if len(messages) == 1:
1339
+ return ModelResponse(
1340
+ parts=[ToolCallPart('get_price', {'fruit': 'apple'}), ToolCallPart('get_price', {'fruit': 'banana'})]
1341
+ )
1342
+ else:
1343
+ return ModelResponse(
1344
+ parts=[
1345
+ TextPart('Done!'),
1346
+ ]
1347
+ )
1348
+
1349
+ agent = Agent(FunctionModel(llm))
1350
+
1351
+ @agent.tool_plain
1352
+ def get_price(fruit: str) -> ToolReturn:
1353
+ return ToolReturn(
1354
+ return_value=10.0,
1355
+ content=f'The price of {fruit} is 10.0',
1356
+ metadata={'foo': 'bar'},
1357
+ )
1358
+
1359
+ result = agent.run_sync('What do an apple and a banana cost?')
1360
+
1361
+ assert result.all_messages() == snapshot(
1362
+ [
1363
+ ModelRequest(
1364
+ parts=[
1365
+ UserPromptPart(
1366
+ content='What do an apple and a banana cost?',
1367
+ timestamp=IsDatetime(),
1368
+ )
1369
+ ]
1370
+ ),
1371
+ ModelResponse(
1372
+ parts=[
1373
+ ToolCallPart(
1374
+ tool_name='get_price',
1375
+ args={'fruit': 'apple'},
1376
+ tool_call_id=IsStr(),
1377
+ ),
1378
+ ToolCallPart(
1379
+ tool_name='get_price',
1380
+ args={'fruit': 'banana'},
1381
+ tool_call_id=IsStr(),
1382
+ ),
1383
+ ],
1384
+ usage=Usage(requests=1, request_tokens=58, response_tokens=10, total_tokens=68),
1385
+ model_name='function:llm:',
1386
+ timestamp=IsDatetime(),
1387
+ ),
1388
+ ModelRequest(
1389
+ parts=[
1390
+ ToolReturnPart(
1391
+ tool_name='get_price',
1392
+ content=10.0,
1393
+ tool_call_id=IsStr(),
1394
+ metadata={'foo': 'bar'},
1395
+ timestamp=IsDatetime(),
1396
+ ),
1397
+ ToolReturnPart(
1398
+ tool_name='get_price',
1399
+ content=10.0,
1400
+ tool_call_id=IsStr(),
1401
+ metadata={'foo': 'bar'},
1402
+ timestamp=IsDatetime(),
1403
+ ),
1404
+ UserPromptPart(
1405
+ content='The price of apple is 10.0',
1406
+ timestamp=IsDatetime(),
1407
+ ),
1408
+ UserPromptPart(
1409
+ content='The price of banana is 10.0',
1410
+ timestamp=IsDatetime(),
1411
+ ),
1412
+ ]
1413
+ ),
1414
+ ModelResponse(
1415
+ parts=[TextPart(content='Done!')],
1416
+ usage=Usage(requests=1, request_tokens=76, response_tokens=11, total_tokens=87),
1417
+ model_name='function:llm:',
1418
+ timestamp=IsDatetime(),
1419
+ ),
1420
+ ]
1421
+ )
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ from collections import defaultdict
4
5
  from dataclasses import dataclass, replace
5
6
  from typing import TypeVar
6
7
  from unittest.mock import AsyncMock
@@ -10,7 +11,7 @@ from inline_snapshot import snapshot
10
11
 
11
12
  from pydantic_ai._run_context import RunContext
12
13
  from pydantic_ai._tool_manager import ToolManager
13
- from pydantic_ai.exceptions import UserError
14
+ from pydantic_ai.exceptions import ModelRetry, ToolRetryError, UnexpectedModelBehavior, UserError
14
15
  from pydantic_ai.messages import ToolCallPart
15
16
  from pydantic_ai.models.test import TestModel
16
17
  from pydantic_ai.tools import ToolDefinition
@@ -494,3 +495,134 @@ async def test_context_manager_failed_initialization():
494
495
  pass
495
496
 
496
497
  assert server1.is_running is False
498
+
499
+
500
+ async def test_tool_manager_retry_logic():
501
+ """Test the retry logic with failed_tools and for_run_step method."""
502
+
503
+ @dataclass
504
+ class TestDeps:
505
+ pass
506
+
507
+ # Create a toolset with tools that can fail
508
+ toolset = FunctionToolset[TestDeps](max_retries=2)
509
+ call_count: defaultdict[str, int] = defaultdict(int)
510
+
511
+ @toolset.tool
512
+ def failing_tool(x: int) -> int:
513
+ """A tool that always fails"""
514
+ call_count['failing_tool'] += 1
515
+ raise ModelRetry('This tool always fails')
516
+
517
+ @toolset.tool
518
+ def other_tool(x: int) -> int:
519
+ """A tool that works"""
520
+ call_count['other_tool'] += 1
521
+ return x * 2
522
+
523
+ # Create initial context and tool manager
524
+ initial_context = build_run_context(TestDeps())
525
+ tool_manager = await ToolManager[TestDeps].build(toolset, initial_context)
526
+
527
+ # Initially no failed tools
528
+ assert tool_manager.failed_tools == set()
529
+ assert initial_context.retries == {}
530
+
531
+ # Call the failing tool - should add to failed_tools
532
+ with pytest.raises(ToolRetryError):
533
+ await tool_manager.handle_call(ToolCallPart(tool_name='failing_tool', args={'x': 1}))
534
+
535
+ assert tool_manager.failed_tools == {'failing_tool'}
536
+ assert call_count['failing_tool'] == 1
537
+
538
+ # Call the working tool - should not add to failed_tools
539
+ result = await tool_manager.handle_call(ToolCallPart(tool_name='other_tool', args={'x': 3}))
540
+ assert result == 6
541
+ assert tool_manager.failed_tools == {'failing_tool'} # unchanged
542
+ assert call_count['other_tool'] == 1
543
+
544
+ # Test for_run_step - should create new tool manager with updated retry counts
545
+ new_context = build_run_context(TestDeps())
546
+ new_tool_manager = await tool_manager.for_run_step(new_context)
547
+
548
+ # The new tool manager should have retry count for the failed tool
549
+ assert new_tool_manager.ctx.retries == {'failing_tool': 1}
550
+ assert new_tool_manager.failed_tools == set() # reset for new run step
551
+
552
+ # Call the failing tool again in the new manager - should have retry=1
553
+ with pytest.raises(ToolRetryError):
554
+ await new_tool_manager.handle_call(ToolCallPart(tool_name='failing_tool', args={'x': 1}))
555
+
556
+ # Call the failing tool another time in the new manager
557
+ with pytest.raises(ToolRetryError):
558
+ await new_tool_manager.handle_call(ToolCallPart(tool_name='failing_tool', args={'x': 1}))
559
+
560
+ # Call the failing tool a third time in the new manager
561
+ with pytest.raises(ToolRetryError):
562
+ await new_tool_manager.handle_call(ToolCallPart(tool_name='failing_tool', args={'x': 1}))
563
+
564
+ assert new_tool_manager.failed_tools == {'failing_tool'}
565
+ assert call_count['failing_tool'] == 4
566
+
567
+ # Create another run step
568
+ another_context = build_run_context(TestDeps())
569
+ another_tool_manager = await new_tool_manager.for_run_step(another_context)
570
+
571
+ # Should now have retry count of 2 for failing_tool
572
+ assert another_tool_manager.ctx.retries == {'failing_tool': 2}
573
+ assert another_tool_manager.failed_tools == set()
574
+
575
+ # Call the failing tool _again_, now we should finally hit the limit
576
+ with pytest.raises(UnexpectedModelBehavior, match="Tool 'failing_tool' exceeded max retries count of 2"):
577
+ await another_tool_manager.handle_call(ToolCallPart(tool_name='failing_tool', args={'x': 1}))
578
+
579
+
580
+ async def test_tool_manager_multiple_failed_tools():
581
+ """Test retry logic when multiple tools fail in the same run step."""
582
+
583
+ @dataclass
584
+ class TestDeps:
585
+ pass
586
+
587
+ toolset = FunctionToolset[TestDeps]()
588
+
589
+ @toolset.tool
590
+ def tool_a(x: int) -> int:
591
+ """Tool A that fails"""
592
+ raise ModelRetry('Tool A fails')
593
+
594
+ @toolset.tool
595
+ def tool_b(x: int) -> int:
596
+ """Tool B that fails"""
597
+ raise ModelRetry('Tool B fails')
598
+
599
+ @toolset.tool
600
+ def tool_c(x: int) -> int:
601
+ """Tool C that works"""
602
+ return x * 3
603
+
604
+ # Create tool manager
605
+ context = build_run_context(TestDeps())
606
+ tool_manager = await ToolManager[TestDeps].build(toolset, context)
607
+
608
+ # Call tool_a - should fail and be added to failed_tools
609
+ with pytest.raises(ToolRetryError):
610
+ await tool_manager.handle_call(ToolCallPart(tool_name='tool_a', args={'x': 1}))
611
+ assert tool_manager.failed_tools == {'tool_a'}
612
+
613
+ # Call tool_b - should also fail and be added to failed_tools
614
+ with pytest.raises(ToolRetryError):
615
+ await tool_manager.handle_call(ToolCallPart(tool_name='tool_b', args={'x': 1}))
616
+ assert tool_manager.failed_tools == {'tool_a', 'tool_b'}
617
+
618
+ # Call tool_c - should succeed and not be added to failed_tools
619
+ result = await tool_manager.handle_call(ToolCallPart(tool_name='tool_c', args={'x': 2}))
620
+ assert result == 6
621
+ assert tool_manager.failed_tools == {'tool_a', 'tool_b'} # unchanged
622
+
623
+ # Create next run step - should have retry counts for both failed tools
624
+ new_context = build_run_context(TestDeps())
625
+ new_tool_manager = await tool_manager.for_run_step(new_context)
626
+
627
+ assert new_tool_manager.ctx.retries == {'tool_a': 1, 'tool_b': 1}
628
+ assert new_tool_manager.failed_tools == set() # reset for new run step
File without changes
File without changes
File without changes
File without changes
File without changes