pydantic-ai 0.2.11__tar.gz → 0.2.12__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 (229) hide show
  1. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/PKG-INFO +3 -3
  2. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_bedrock.py +95 -0
  3. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_gemini.py +52 -9
  4. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_google.py +60 -8
  5. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_openai.py +125 -16
  6. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_openai_responses.py +43 -0
  7. pydantic_ai-0.2.12/tests/providers/test_azure.py +141 -0
  8. pydantic_ai-0.2.12/tests/providers/test_bedrock.py +95 -0
  9. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_deepseek.py +8 -1
  10. pydantic_ai-0.2.12/tests/providers/test_fireworks.py +104 -0
  11. pydantic_ai-0.2.12/tests/providers/test_grok.py +57 -0
  12. pydantic_ai-0.2.12/tests/providers/test_groq.py +107 -0
  13. pydantic_ai-0.2.12/tests/providers/test_openrouter.py +154 -0
  14. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_provider_names.py +6 -0
  15. pydantic_ai-0.2.12/tests/providers/test_together.py +100 -0
  16. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_agent.py +601 -3
  17. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_examples.py +94 -1
  18. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_tools.py +1 -1
  19. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/typed_agent.py +50 -22
  20. pydantic_ai-0.2.11/tests/providers/test_azure.py +0 -72
  21. pydantic_ai-0.2.11/tests/providers/test_bedrock.py +0 -34
  22. pydantic_ai-0.2.11/tests/providers/test_groq.py +0 -57
  23. pydantic_ai-0.2.11/tests/providers/test_openrouter.py +0 -67
  24. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/.gitignore +0 -0
  25. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/LICENSE +0 -0
  26. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/Makefile +0 -0
  27. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/README.md +0 -0
  28. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/pyproject.toml +0 -0
  29. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/__init__.py +0 -0
  30. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/assets/dummy.pdf +0 -0
  31. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/assets/kiwi.png +0 -0
  32. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/assets/marcelo.mp3 +0 -0
  33. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/assets/small_video.mp4 +0 -0
  34. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml +0 -0
  35. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_dict.yaml +0 -0
  36. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_error.yaml +0 -0
  37. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_image.yaml +0 -0
  38. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_image_resource.yaml +0 -0
  39. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_multiple_items.yaml +0 -0
  40. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_none.yaml +0 -0
  41. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_str.yaml +0 -0
  42. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_mcp/test_tool_returning_text_resource.yaml +0 -0
  43. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[anthropic].yaml +0 -0
  44. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[bedrock].yaml +0 -0
  45. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[cohere].yaml +0 -0
  46. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[gemini].yaml +0 -0
  47. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[groq].yaml +0 -0
  48. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[mistral].yaml +0 -0
  49. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/cassettes/test_settings/test_stop_settings[openai].yaml +0 -0
  50. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/conftest.py +0 -0
  51. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/__init__.py +0 -0
  52. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_dataset.py +0 -0
  53. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_evaluator_base.py +0 -0
  54. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_evaluator_common.py +0 -0
  55. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_evaluator_context.py +0 -0
  56. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_evaluator_spec.py +0 -0
  57. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_evaluators.py +0 -0
  58. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_llm_as_a_judge.py +0 -0
  59. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_otel.py +0 -0
  60. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_render_numbers.py +0 -0
  61. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_reporting.py +0 -0
  62. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_reports.py +0 -0
  63. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/test_utils.py +0 -0
  64. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/evals/utils.py +0 -0
  65. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/example_modules/README.md +0 -0
  66. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/example_modules/bank_database.py +0 -0
  67. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/example_modules/fake_database.py +0 -0
  68. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/example_modules/weather_service.py +0 -0
  69. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/fasta2a/__init__.py +0 -0
  70. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/fasta2a/test_applications.py +0 -0
  71. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/__init__.py +0 -0
  72. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_file_persistence.py +0 -0
  73. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_graph.py +0 -0
  74. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_mermaid.py +0 -0
  75. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_persistence.py +0 -0
  76. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_state.py +0 -0
  77. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/graph/test_utils.py +0 -0
  78. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/import_examples.py +0 -0
  79. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/json_body_serializer.py +0 -0
  80. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/mcp_server.py +0 -0
  81. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/__init__.py +0 -0
  82. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_anthropic_model_empty_message_on_history.yaml +0 -0
  83. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_anthropic_model_instructions.yaml +0 -0
  84. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_document_binary_content_input.yaml +0 -0
  85. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_document_url_input.yaml +0 -0
  86. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_extra_headers.yaml +0 -0
  87. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_image_as_binary_content_tool_response.yaml +0 -0
  88. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
  89. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
  90. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
  91. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_anthropic/test_text_document_url_input.yaml +0 -0
  92. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_empty_system_prompt.yaml +0 -0
  93. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model.yaml +0 -0
  94. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_anthropic_model_without_tools.yaml +0 -0
  95. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_guardrail_config.yaml +0 -0
  96. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_instructions.yaml +0 -0
  97. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_iter_stream.yaml +0 -0
  98. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_max_tokens.yaml +0 -0
  99. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_other_parameters.yaml +0 -0
  100. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_performance_config.yaml +0 -0
  101. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_retry.yaml +0 -0
  102. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_stream.yaml +0 -0
  103. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_structured_response.yaml +0 -0
  104. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_model_top_p.yaml +0 -0
  105. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_bedrock_multiple_documents_in_history.yaml +0 -0
  106. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_document_url_input.yaml +0 -0
  107. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_image_as_binary_content_input.yaml +0 -0
  108. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_image_url_input.yaml +0 -0
  109. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_text_as_binary_content_input.yaml +0 -0
  110. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_text_document_url_input.yaml +0 -0
  111. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_video_as_binary_content_input.yaml +0 -0
  112. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_bedrock/test_video_url_input.yaml +0 -0
  113. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_cohere/test_cohere_model_instructions.yaml +0 -0
  114. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_cohere/test_request_simple_success_with_vcr.yaml +0 -0
  115. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_document_url_input.yaml +0 -0
  116. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_false.yaml +0 -0
  117. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_true.yaml +0 -0
  118. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml +0 -0
  119. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_gemini_exclusive_minimum_and_maximum.yaml +0 -0
  120. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_gemini_model_instructions.yaml +0 -0
  121. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +0 -0
  122. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_image_as_binary_content_tool_response.yaml +0 -0
  123. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +0 -0
  124. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_labels_are_ignored_with_gla_provider.yaml +0 -0
  125. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_video_as_binary_content_input.yaml +0 -0
  126. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini/test_video_url_input.yaml +0 -0
  127. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_gemini_vertexai/test_labels.yaml +0 -0
  128. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model.yaml +0 -0
  129. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_document_url_input.yaml +0 -0
  130. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_image_as_binary_content_input.yaml +0 -0
  131. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_image_url_input.yaml +0 -0
  132. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_instructions.yaml +0 -0
  133. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_iter_stream.yaml +0 -0
  134. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_max_tokens.yaml +0 -0
  135. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_multiple_documents_in_history.yaml +0 -0
  136. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_retry.yaml +0 -0
  137. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_safety_settings.yaml +0 -0
  138. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_stream.yaml +0 -0
  139. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_structured_response.yaml +0 -0
  140. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_text_as_binary_content_input.yaml +0 -0
  141. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_text_document_url_input.yaml +0 -0
  142. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_thinking_config.yaml +0 -0
  143. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_top_p.yaml +0 -0
  144. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_vertex_labels.yaml +0 -0
  145. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_vertex_provider.yaml +0 -0
  146. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_video_as_binary_content_input.yaml +0 -0
  147. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_google/test_google_model_video_url_input.yaml +0 -0
  148. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_groq/test_extra_headers.yaml +0 -0
  149. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_groq/test_groq_model_instructions.yaml +0 -0
  150. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
  151. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_groq/test_image_as_binary_content_tool_response.yaml +0 -0
  152. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
  153. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_mistral/test_image_as_binary_content_tool_response.yaml +0 -0
  154. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_mistral/test_mistral_model_instructions.yaml +0 -0
  155. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
  156. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_document_as_binary_content_input.yaml +0 -0
  157. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_document_url_input.yaml +0 -0
  158. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_extra_headers.yaml +0 -0
  159. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
  160. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_image_as_binary_content_tool_response.yaml +0 -0
  161. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_image_url_tool_response.yaml +0 -0
  162. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4.5-preview].yaml +0 -0
  163. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4o-mini].yaml +0 -0
  164. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_max_completion_tokens[o3-mini].yaml +0 -0
  165. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_multiple_agent_tool_calls.yaml +0 -0
  166. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_audio_url_input.yaml +0 -0
  167. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_instructions.yaml +0 -0
  168. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_instructions_with_tool_calls_keep_instructions.yaml +0 -0
  169. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_model_without_system_prompt.yaml +0 -0
  170. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
  171. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
  172. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai/test_user_id.yaml +0 -0
  173. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_audio_as_binary_content_input.yaml +0 -0
  174. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_input.yaml +0 -0
  175. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_tool_response.yaml +0 -0
  176. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_as_binary_content_input.yaml +0 -0
  177. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_url_input.yaml +0 -0
  178. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_image_url_input.yaml +0 -0
  179. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_builtin_tools.yaml +0 -0
  180. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_http_error.yaml +0 -0
  181. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_instructions.yaml +0 -0
  182. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_retry.yaml +0 -0
  183. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response.yaml +0 -0
  184. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response_with_tool_call.yaml +0 -0
  185. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_output_type.yaml +0 -0
  186. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_effort.yaml +0 -0
  187. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_generate_summary.yaml +0 -0
  188. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_stream.yaml +0 -0
  189. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_system_prompt.yaml +0 -0
  190. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/cassettes/test_openai_responses/test_openai_responses_text_document_url_input.yaml +0 -0
  191. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/mock_async_stream.py +0 -0
  192. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_anthropic.py +0 -0
  193. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_cohere.py +0 -0
  194. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_fallback.py +0 -0
  195. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_gemini_vertexai.py +0 -0
  196. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_groq.py +0 -0
  197. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_instrumented.py +0 -0
  198. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_mistral.py +0 -0
  199. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_model.py +0 -0
  200. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_model_function.py +0 -0
  201. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_model_names.py +0 -0
  202. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_model_request_parameters.py +0 -0
  203. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/models/test_model_test.py +0 -0
  204. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/__init__.py +0 -0
  205. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/cassettes/test_azure/test_azure_provider_call.yaml +0 -0
  206. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/cassettes/test_google_vertex/test_vertexai_provider.yaml +0 -0
  207. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/cassettes/test_openrouter/test_openrouter_with_google_model.yaml +0 -0
  208. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_anthropic.py +0 -0
  209. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_cohere.py +0 -0
  210. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_google_gla.py +0 -0
  211. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_google_vertex.py +0 -0
  212. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_mistral.py +0 -0
  213. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/providers/test_openai.py +0 -0
  214. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_a2a.py +0 -0
  215. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_cli.py +0 -0
  216. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_deps.py +0 -0
  217. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_direct.py +0 -0
  218. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_format_as_xml.py +0 -0
  219. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_json_body_serializer.py +0 -0
  220. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_live.py +0 -0
  221. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_logfire.py +0 -0
  222. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_mcp.py +0 -0
  223. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_messages.py +0 -0
  224. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_parts_manager.py +0 -0
  225. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_settings.py +0 -0
  226. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_streaming.py +0 -0
  227. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_usage_limits.py +0 -0
  228. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/test_utils.py +0 -0
  229. {pydantic_ai-0.2.11 → pydantic_ai-0.2.12}/tests/typed_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai
3
- Version: 0.2.11
3
+ Version: 0.2.12
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,9 +28,9 @@ 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[a2a,anthropic,bedrock,cli,cohere,evals,google,groq,mcp,mistral,openai,vertexai]==0.2.11
31
+ Requires-Dist: pydantic-ai-slim[a2a,anthropic,bedrock,cli,cohere,evals,google,groq,mcp,mistral,openai,vertexai]==0.2.12
32
32
  Provides-Extra: examples
33
- Requires-Dist: pydantic-ai-examples==0.2.11; extra == 'examples'
33
+ Requires-Dist: pydantic-ai-examples==0.2.12; extra == 'examples'
34
34
  Provides-Extra: logfire
35
35
  Requires-Dist: logfire>=3.11.0; extra == 'logfire'
36
36
  Description-Content-Type: text/markdown
@@ -31,6 +31,8 @@ from pydantic_ai.messages import (
31
31
  UserPromptPart,
32
32
  VideoUrl,
33
33
  )
34
+ from pydantic_ai.models import ModelRequestParameters
35
+ from pydantic_ai.tools import ToolDefinition
34
36
  from pydantic_ai.usage import Usage
35
37
 
36
38
  from ..conftest import IsDatetime, try_import
@@ -631,3 +633,96 @@ async def test_bedrock_group_consecutive_tool_return_parts(bedrock_provider: Bed
631
633
  },
632
634
  ]
633
635
  )
636
+
637
+
638
+ async def test_bedrock_mistral_tool_result_format(bedrock_provider: BedrockProvider):
639
+ now = datetime.datetime.now()
640
+ req = [
641
+ ModelRequest(
642
+ parts=[
643
+ ToolReturnPart(tool_name='tool1', content={'foo': 'bar'}, tool_call_id='id1', timestamp=now),
644
+ ]
645
+ ),
646
+ ]
647
+
648
+ # Models other than Mistral support toolResult.content with text, not json
649
+ model = BedrockConverseModel('us.amazon.nova-micro-v1:0', provider=bedrock_provider)
650
+ # Call the mapping function directly
651
+ _, bedrock_messages = await model._map_messages(req) # type: ignore[reportPrivateUsage]
652
+
653
+ assert bedrock_messages == snapshot(
654
+ [
655
+ {
656
+ 'role': 'user',
657
+ 'content': [
658
+ {'toolResult': {'toolUseId': 'id1', 'content': [{'text': '{"foo":"bar"}'}], 'status': 'success'}},
659
+ ],
660
+ },
661
+ ]
662
+ )
663
+
664
+ # Mistral requires toolResult.content to hold json, not text
665
+ model = BedrockConverseModel('mistral.mistral-7b-instruct-v0:2', provider=bedrock_provider)
666
+ # Call the mapping function directly
667
+ _, bedrock_messages = await model._map_messages(req) # type: ignore[reportPrivateUsage]
668
+
669
+ assert bedrock_messages == snapshot(
670
+ [
671
+ {
672
+ 'role': 'user',
673
+ 'content': [
674
+ {'toolResult': {'toolUseId': 'id1', 'content': [{'json': {'foo': 'bar'}}], 'status': 'success'}},
675
+ ],
676
+ },
677
+ ]
678
+ )
679
+
680
+
681
+ async def test_bedrock_anthropic_no_tool_choice(bedrock_provider: BedrockProvider):
682
+ my_tool = ToolDefinition(
683
+ 'my_tool',
684
+ 'This is my tool',
685
+ {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
686
+ )
687
+ mrp = ModelRequestParameters(function_tools=[my_tool], allow_text_output=False, output_tools=[])
688
+
689
+ # Models other than Anthropic support tool_choice
690
+ model = BedrockConverseModel('us.amazon.nova-micro-v1:0', provider=bedrock_provider)
691
+ tool_config = model._map_tool_config(mrp) # type: ignore[reportPrivateUsage]
692
+
693
+ assert tool_config == snapshot(
694
+ {
695
+ 'tools': [
696
+ {
697
+ 'toolSpec': {
698
+ 'name': 'my_tool',
699
+ 'description': 'This is my tool',
700
+ 'inputSchema': {
701
+ 'json': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}}
702
+ },
703
+ }
704
+ }
705
+ ],
706
+ 'toolChoice': {'any': {}},
707
+ }
708
+ )
709
+
710
+ # Anthropic models don't support tool_choice
711
+ model = BedrockConverseModel('us.anthropic.claude-3-7-sonnet-20250219-v1:0', provider=bedrock_provider)
712
+ tool_config = model._map_tool_config(mrp) # type: ignore[reportPrivateUsage]
713
+
714
+ assert tool_config == snapshot(
715
+ {
716
+ 'tools': [
717
+ {
718
+ 'toolSpec': {
719
+ 'name': 'my_tool',
720
+ 'description': 'This is my tool',
721
+ 'inputSchema': {
722
+ 'json': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}}
723
+ },
724
+ }
725
+ }
726
+ ]
727
+ }
728
+ )
@@ -540,6 +540,7 @@ async def test_text_success(get_gemini_client: GetGeminiClient):
540
540
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
541
541
  model_name='gemini-1.5-flash-123',
542
542
  timestamp=IsNow(tz=timezone.utc),
543
+ vendor_details={'finish_reason': 'STOP'},
543
544
  ),
544
545
  ]
545
546
  )
@@ -555,6 +556,7 @@ async def test_text_success(get_gemini_client: GetGeminiClient):
555
556
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
556
557
  model_name='gemini-1.5-flash-123',
557
558
  timestamp=IsNow(tz=timezone.utc),
559
+ vendor_details={'finish_reason': 'STOP'},
558
560
  ),
559
561
  ModelRequest(parts=[UserPromptPart(content='Hello', timestamp=IsNow(tz=timezone.utc))]),
560
562
  ModelResponse(
@@ -562,6 +564,7 @@ async def test_text_success(get_gemini_client: GetGeminiClient):
562
564
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
563
565
  model_name='gemini-1.5-flash-123',
564
566
  timestamp=IsNow(tz=timezone.utc),
567
+ vendor_details={'finish_reason': 'STOP'},
565
568
  ),
566
569
  ]
567
570
  )
@@ -585,6 +588,7 @@ async def test_request_structured_response(get_gemini_client: GetGeminiClient):
585
588
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
586
589
  model_name='gemini-1.5-flash-123',
587
590
  timestamp=IsNow(tz=timezone.utc),
591
+ vendor_details={'finish_reason': 'STOP'},
588
592
  ),
589
593
  ModelRequest(
590
594
  parts=[
@@ -647,6 +651,7 @@ async def test_request_tool_call(get_gemini_client: GetGeminiClient):
647
651
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
648
652
  model_name='gemini-1.5-flash-123',
649
653
  timestamp=IsNow(tz=timezone.utc),
654
+ vendor_details={'finish_reason': 'STOP'},
650
655
  ),
651
656
  ModelRequest(
652
657
  parts=[
@@ -666,6 +671,7 @@ async def test_request_tool_call(get_gemini_client: GetGeminiClient):
666
671
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
667
672
  model_name='gemini-1.5-flash-123',
668
673
  timestamp=IsNow(tz=timezone.utc),
674
+ vendor_details={'finish_reason': 'STOP'},
669
675
  ),
670
676
  ModelRequest(
671
677
  parts=[
@@ -688,6 +694,7 @@ async def test_request_tool_call(get_gemini_client: GetGeminiClient):
688
694
  usage=Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3, details={}),
689
695
  model_name='gemini-1.5-flash-123',
690
696
  timestamp=IsNow(tz=timezone.utc),
697
+ vendor_details={'finish_reason': 'STOP'},
691
698
  ),
692
699
  ]
693
700
  )
@@ -732,12 +739,12 @@ async def test_stream_text(get_gemini_client: GetGeminiClient):
732
739
  'Hello world',
733
740
  ]
734
741
  )
735
- assert result.usage() == snapshot(Usage(requests=1, request_tokens=2, response_tokens=4, total_tokens=6))
742
+ assert result.usage() == snapshot(Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3))
736
743
 
737
744
  async with agent.run_stream('Hello') as result:
738
745
  chunks = [chunk async for chunk in result.stream_text(delta=True, debounce_by=None)]
739
746
  assert chunks == snapshot(['Hello ', 'world'])
740
- assert result.usage() == snapshot(Usage(requests=1, request_tokens=2, response_tokens=4, total_tokens=6))
747
+ assert result.usage() == snapshot(Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3))
741
748
 
742
749
 
743
750
  async def test_stream_invalid_unicode_text(get_gemini_client: GetGeminiClient):
@@ -769,7 +776,7 @@ async def test_stream_invalid_unicode_text(get_gemini_client: GetGeminiClient):
769
776
  async with agent.run_stream('Hello') as result:
770
777
  chunks = [chunk async for chunk in result.stream(debounce_by=None)]
771
778
  assert chunks == snapshot(['abc', 'abc€def', 'abc€def'])
772
- assert result.usage() == snapshot(Usage(requests=1, request_tokens=2, response_tokens=4, total_tokens=6))
779
+ assert result.usage() == snapshot(Usage(requests=1, request_tokens=1, response_tokens=2, total_tokens=3))
773
780
 
774
781
 
775
782
  async def test_stream_text_no_data(get_gemini_client: GetGeminiClient):
@@ -840,7 +847,7 @@ async def test_stream_structured_tool_calls(get_gemini_client: GetGeminiClient):
840
847
  async with agent.run_stream('Hello') as result:
841
848
  response = await result.get_output()
842
849
  assert response == snapshot((1, 2))
843
- assert result.usage() == snapshot(Usage(requests=2, request_tokens=3, response_tokens=6, total_tokens=9))
850
+ assert result.usage() == snapshot(Usage(requests=2, request_tokens=2, response_tokens=4, total_tokens=6))
844
851
  assert result.all_messages() == snapshot(
845
852
  [
846
853
  ModelRequest(parts=[UserPromptPart(content='Hello', timestamp=IsNow(tz=timezone.utc))]),
@@ -849,7 +856,7 @@ async def test_stream_structured_tool_calls(get_gemini_client: GetGeminiClient):
849
856
  ToolCallPart(tool_name='foo', args={'x': 'a'}, tool_call_id=IsStr()),
850
857
  ToolCallPart(tool_name='bar', args={'y': 'b'}, tool_call_id=IsStr()),
851
858
  ],
852
- usage=Usage(request_tokens=2, response_tokens=4, total_tokens=6),
859
+ usage=Usage(request_tokens=1, response_tokens=2, total_tokens=3, details={}),
853
860
  model_name='gemini-1.5-flash',
854
861
  timestamp=IsNow(tz=timezone.utc),
855
862
  ),
@@ -865,7 +872,7 @@ async def test_stream_structured_tool_calls(get_gemini_client: GetGeminiClient):
865
872
  ),
866
873
  ModelResponse(
867
874
  parts=[ToolCallPart(tool_name='final_result', args={'response': [1, 2]}, tool_call_id=IsStr())],
868
- usage=Usage(request_tokens=1, response_tokens=2, total_tokens=3),
875
+ usage=Usage(request_tokens=1, response_tokens=2, total_tokens=3, details={}),
869
876
  model_name='gemini-1.5-flash',
870
877
  timestamp=IsNow(tz=timezone.utc),
871
878
  ),
@@ -1096,9 +1103,16 @@ I need to use the `get_image` tool to see the image first.
1096
1103
  ),
1097
1104
  ToolCallPart(tool_name='get_image', args={}, tool_call_id=IsStr()),
1098
1105
  ],
1099
- usage=Usage(requests=1, request_tokens=38, response_tokens=28, total_tokens=427, details={}),
1106
+ usage=Usage(
1107
+ requests=1,
1108
+ request_tokens=38,
1109
+ response_tokens=28,
1110
+ total_tokens=427,
1111
+ details={'thoughts_tokens': 361, 'text_prompt_tokens': 38},
1112
+ ),
1100
1113
  model_name='gemini-2.5-pro-preview-03-25',
1101
1114
  timestamp=IsDatetime(),
1115
+ vendor_details={'finish_reason': 'STOP'},
1102
1116
  ),
1103
1117
  ModelRequest(
1104
1118
  parts=[
@@ -1119,9 +1133,16 @@ I need to use the `get_image` tool to see the image first.
1119
1133
  ),
1120
1134
  ModelResponse(
1121
1135
  parts=[TextPart(content='The image shows a kiwi fruit, sliced in half.')],
1122
- usage=Usage(requests=1, request_tokens=360, response_tokens=11, total_tokens=572, details={}),
1136
+ usage=Usage(
1137
+ requests=1,
1138
+ request_tokens=360,
1139
+ response_tokens=11,
1140
+ total_tokens=572,
1141
+ details={'thoughts_tokens': 201, 'text_prompt_tokens': 102, 'image_prompt_tokens': 258},
1142
+ ),
1123
1143
  model_name='gemini-2.5-pro-preview-03-25',
1124
1144
  timestamp=IsDatetime(),
1145
+ vendor_details={'finish_reason': 'STOP'},
1125
1146
  ),
1126
1147
  ]
1127
1148
  )
@@ -1241,9 +1262,16 @@ async def test_gemini_model_instructions(allow_model_requests: None, gemini_api_
1241
1262
  ),
1242
1263
  ModelResponse(
1243
1264
  parts=[TextPart(content='The capital of France is Paris.\n')],
1244
- usage=Usage(requests=1, request_tokens=13, response_tokens=8, total_tokens=21, details={}),
1265
+ usage=Usage(
1266
+ requests=1,
1267
+ request_tokens=13,
1268
+ response_tokens=8,
1269
+ total_tokens=21,
1270
+ details={'text_prompt_tokens': 13, 'text_candidates_tokens': 8},
1271
+ ),
1245
1272
  model_name='gemini-1.5-flash',
1246
1273
  timestamp=IsDatetime(),
1274
+ vendor_details={'finish_reason': 'STOP'},
1247
1275
  ),
1248
1276
  ]
1249
1277
  )
@@ -1284,3 +1312,18 @@ async def test_gemini_additional_properties_is_true(allow_model_requests: None,
1284
1312
  assert result.output == snapshot(
1285
1313
  'I need a location dictionary to use the `get_temperature` function. I cannot provide the temperature in Tokyo without more information.\n'
1286
1314
  )
1315
+
1316
+
1317
+ async def test_gemini_no_finish_reason(get_gemini_client: GetGeminiClient):
1318
+ response = gemini_response(
1319
+ _content_model_response(ModelResponse(parts=[TextPart('Hello world')])), finish_reason=None
1320
+ )
1321
+ gemini_client = get_gemini_client(response)
1322
+ m = GeminiModel('gemini-1.5-flash', provider=GoogleGLAProvider(http_client=gemini_client))
1323
+ agent = Agent(m)
1324
+
1325
+ result = await agent.run('Hello World')
1326
+
1327
+ for message in result.all_messages():
1328
+ if isinstance(message, ModelResponse):
1329
+ assert message.vendor_details is None
@@ -65,7 +65,15 @@ async def test_google_model(allow_model_requests: None, google_provider: GoogleP
65
65
 
66
66
  result = await agent.run('Hello!')
67
67
  assert result.output == snapshot('Hello there! How can I help you today?\n')
68
- assert result.usage() == snapshot(Usage(requests=1, request_tokens=7, response_tokens=11, total_tokens=18))
68
+ assert result.usage() == snapshot(
69
+ Usage(
70
+ requests=1,
71
+ request_tokens=7,
72
+ response_tokens=11,
73
+ total_tokens=18,
74
+ details={'text_prompt_tokens': 7, 'text_candidates_tokens': 11},
75
+ )
76
+ )
69
77
  assert result.all_messages() == snapshot(
70
78
  [
71
79
  ModelRequest(
@@ -82,9 +90,16 @@ async def test_google_model(allow_model_requests: None, google_provider: GoogleP
82
90
  ),
83
91
  ModelResponse(
84
92
  parts=[TextPart(content='Hello there! How can I help you today?\n')],
85
- usage=Usage(requests=1, request_tokens=7, response_tokens=11, total_tokens=18, details={}),
93
+ usage=Usage(
94
+ requests=1,
95
+ request_tokens=7,
96
+ response_tokens=11,
97
+ total_tokens=18,
98
+ details={'text_prompt_tokens': 7, 'text_candidates_tokens': 11},
99
+ ),
86
100
  model_name='gemini-1.5-flash',
87
101
  timestamp=IsDatetime(),
102
+ vendor_details={'finish_reason': 'STOP'},
88
103
  ),
89
104
  ]
90
105
  )
@@ -114,7 +129,15 @@ async def test_google_model_structured_response(allow_model_requests: None, goog
114
129
 
115
130
  result = await agent.run('What was the temperature in London 1st January 2022?', output_type=Response)
116
131
  assert result.output == snapshot({'temperature': '30°C', 'date': datetime.date(2022, 1, 1), 'city': 'London'})
117
- assert result.usage() == snapshot(Usage(requests=2, request_tokens=224, response_tokens=35, total_tokens=259))
132
+ assert result.usage() == snapshot(
133
+ Usage(
134
+ requests=2,
135
+ request_tokens=224,
136
+ response_tokens=35,
137
+ total_tokens=259,
138
+ details={'text_prompt_tokens': 224, 'text_candidates_tokens': 35},
139
+ )
140
+ )
118
141
  assert result.all_messages() == snapshot(
119
142
  [
120
143
  ModelRequest(
@@ -135,9 +158,16 @@ async def test_google_model_structured_response(allow_model_requests: None, goog
135
158
  tool_name='temperature', args={'date': '2022-01-01', 'city': 'London'}, tool_call_id=IsStr()
136
159
  )
137
160
  ],
138
- usage=Usage(requests=1, request_tokens=101, response_tokens=14, total_tokens=115, details={}),
161
+ usage=Usage(
162
+ requests=1,
163
+ request_tokens=101,
164
+ response_tokens=14,
165
+ total_tokens=115,
166
+ details={'text_prompt_tokens': 101, 'text_candidates_tokens': 14},
167
+ ),
139
168
  model_name='gemini-1.5-flash',
140
169
  timestamp=IsDatetime(),
170
+ vendor_details={'finish_reason': 'STOP'},
141
171
  ),
142
172
  ModelRequest(
143
173
  parts=[
@@ -154,9 +184,16 @@ async def test_google_model_structured_response(allow_model_requests: None, goog
154
184
  tool_call_id=IsStr(),
155
185
  )
156
186
  ],
157
- usage=Usage(requests=1, request_tokens=123, response_tokens=21, total_tokens=144, details={}),
187
+ usage=Usage(
188
+ requests=1,
189
+ request_tokens=123,
190
+ response_tokens=21,
191
+ total_tokens=144,
192
+ details={'text_prompt_tokens': 123, 'text_candidates_tokens': 21},
193
+ ),
158
194
  model_name='gemini-1.5-flash',
159
195
  timestamp=IsDatetime(),
196
+ vendor_details={'finish_reason': 'STOP'},
160
197
  ),
161
198
  ModelRequest(
162
199
  parts=[
@@ -211,10 +248,11 @@ async def test_google_model_retry(allow_model_requests: None, google_provider: G
211
248
  request_tokens=57,
212
249
  response_tokens=15,
213
250
  total_tokens=173,
214
- details={'thoughts_token_count': 101},
251
+ details={'thoughts_tokens': 101, 'text_prompt_tokens': 57},
215
252
  ),
216
253
  model_name='models/gemini-2.5-pro-preview-05-06',
217
254
  timestamp=IsDatetime(),
255
+ vendor_details={'finish_reason': 'STOP'},
218
256
  ),
219
257
  ModelRequest(
220
258
  parts=[
@@ -232,9 +270,16 @@ async def test_google_model_retry(allow_model_requests: None, google_provider: G
232
270
  content='I am sorry, I cannot fulfill this request. The country you provided is not supported.'
233
271
  )
234
272
  ],
235
- usage=Usage(requests=1, request_tokens=104, response_tokens=18, total_tokens=122, details={}),
273
+ usage=Usage(
274
+ requests=1,
275
+ request_tokens=104,
276
+ response_tokens=18,
277
+ total_tokens=122,
278
+ details={'text_prompt_tokens': 104},
279
+ ),
236
280
  model_name='models/gemini-2.5-pro-preview-05-06',
237
281
  timestamp=IsDatetime(),
282
+ vendor_details={'finish_reason': 'STOP'},
238
283
  ),
239
284
  ]
240
285
  )
@@ -488,9 +533,16 @@ async def test_google_model_instructions(allow_model_requests: None, google_prov
488
533
  ),
489
534
  ModelResponse(
490
535
  parts=[TextPart(content='The capital of France is Paris.\n')],
491
- usage=Usage(requests=1, request_tokens=13, response_tokens=8, total_tokens=21, details={}),
536
+ usage=Usage(
537
+ requests=1,
538
+ request_tokens=13,
539
+ response_tokens=8,
540
+ total_tokens=21,
541
+ details={'text_prompt_tokens': 13, 'text_candidates_tokens': 8},
542
+ ),
492
543
  model_name='gemini-2.0-flash',
493
544
  timestamp=IsDatetime(),
545
+ vendor_details={'finish_reason': 'STOP'},
494
546
  ),
495
547
  ]
496
548
  )
@@ -30,9 +30,13 @@ from pydantic_ai.messages import (
30
30
  UserPromptPart,
31
31
  )
32
32
  from pydantic_ai.models.gemini import GeminiModel
33
+ from pydantic_ai.profiles import ModelProfile
34
+ from pydantic_ai.profiles._json_schema import InlineDefsJsonSchemaTransformer
35
+ from pydantic_ai.profiles.openai import OpenAIModelProfile, openai_model_profile
33
36
  from pydantic_ai.providers.google_gla import GoogleGLAProvider
34
37
  from pydantic_ai.result import Usage
35
38
  from pydantic_ai.settings import ModelSettings
39
+ from pydantic_ai.tools import ToolDefinition
36
40
 
37
41
  from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import
38
42
  from .mock_async_stream import MockAsyncStream
@@ -56,8 +60,8 @@ with try_import() as imports_successful:
56
60
  OpenAIModel,
57
61
  OpenAIModelSettings,
58
62
  OpenAISystemPromptRole,
59
- _OpenAIJsonSchema, # pyright: ignore[reportPrivateUsage]
60
63
  )
64
+ from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer
61
65
  from pydantic_ai.providers.openai import OpenAIProvider
62
66
 
63
67
  # note: we use Union here so that casting works with Python 3.9
@@ -1356,23 +1360,33 @@ async def test_strict_mode_cannot_infer_strict(
1356
1360
  # Create a mock completion for testing
1357
1361
  c = completion_message(ChatCompletionMessage(content='world', role='assistant'))
1358
1362
 
1359
- # Test 1: Default behavior (strict setting not explicitly specified; function is strict-mode-compatible)
1360
- mock_client = MockOpenAI.create_mock(c)
1361
- m = OpenAIModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
1362
- agent = Agent(m)
1363
+ async def assert_strict(expected_strict: bool | None, profile: ModelProfile | None = None):
1364
+ mock_client = MockOpenAI.create_mock(c)
1365
+ m = OpenAIModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client), profile=profile)
1366
+ agent = Agent(m)
1363
1367
 
1364
- agent.tool_plain(strict=tool_strict)(tool)
1368
+ agent.tool_plain(strict=tool_strict)(tool)
1365
1369
 
1366
- await agent.run('hello')
1367
- kwargs = get_mock_chat_completion_kwargs(mock_client)[0]
1368
- assert 'tools' in kwargs, kwargs
1370
+ await agent.run('hello')
1371
+ kwargs = get_mock_chat_completion_kwargs(mock_client)[0]
1372
+ assert 'tools' in kwargs, kwargs
1373
+
1374
+ assert kwargs['tools'][0]['function']['parameters'] == expected_params
1375
+ actual_strict = kwargs['tools'][0]['function'].get('strict')
1376
+ assert actual_strict == expected_strict
1377
+ if actual_strict is None:
1378
+ # If strict is included, it should be non-None
1379
+ assert 'strict' not in kwargs['tools'][0]['function']
1380
+
1381
+ await assert_strict(expected_strict)
1369
1382
 
1370
- assert kwargs['tools'][0]['function']['parameters'] == expected_params
1371
- actual_strict = kwargs['tools'][0]['function'].get('strict')
1372
- assert actual_strict == expected_strict
1373
- if actual_strict is None:
1374
- # If strict is included, it should be non-None
1375
- assert 'strict' not in kwargs['tools'][0]['function']
1383
+ # If the model profile says strict is not supported, we never pass strict
1384
+ await assert_strict(
1385
+ None,
1386
+ profile=OpenAIModelProfile(openai_supports_strict_tool_definition=False).update(
1387
+ openai_model_profile('test-model')
1388
+ ),
1389
+ )
1376
1390
 
1377
1391
 
1378
1392
  def test_strict_schema():
@@ -1390,7 +1404,7 @@ def test_strict_schema():
1390
1404
  my_list: list[float]
1391
1405
  my_discriminated_union: Annotated[Apple | Banana, Discriminator('kind')]
1392
1406
 
1393
- assert _OpenAIJsonSchema(MyModel.model_json_schema(), strict=True).walk() == snapshot(
1407
+ assert OpenAIJsonSchemaTransformer(MyModel.model_json_schema(), strict=True).walk() == snapshot(
1394
1408
  {
1395
1409
  '$defs': {
1396
1410
  'Apple': {
@@ -1601,3 +1615,98 @@ async def test_openai_instructions_with_logprobs(allow_model_requests: None):
1601
1615
  'top_logprobs': [],
1602
1616
  }
1603
1617
  ]
1618
+
1619
+
1620
+ def test_openai_model_profile():
1621
+ m = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key='foobar'))
1622
+ assert isinstance(m.profile, OpenAIModelProfile)
1623
+
1624
+
1625
+ def test_openai_model_profile_custom():
1626
+ m = OpenAIModel(
1627
+ 'gpt-4o',
1628
+ provider=OpenAIProvider(api_key='foobar'),
1629
+ profile=ModelProfile(json_schema_transformer=InlineDefsJsonSchemaTransformer),
1630
+ )
1631
+ assert isinstance(m.profile, ModelProfile)
1632
+ assert m.profile.json_schema_transformer is InlineDefsJsonSchemaTransformer
1633
+
1634
+ m = OpenAIModel(
1635
+ 'gpt-4o',
1636
+ provider=OpenAIProvider(api_key='foobar'),
1637
+ profile=OpenAIModelProfile(openai_supports_strict_tool_definition=False),
1638
+ )
1639
+ assert isinstance(m.profile, OpenAIModelProfile)
1640
+ assert m.profile.openai_supports_strict_tool_definition is False
1641
+
1642
+
1643
+ def test_openai_model_profile_function():
1644
+ def model_profile(model_name: str) -> ModelProfile:
1645
+ return ModelProfile(json_schema_transformer=InlineDefsJsonSchemaTransformer if model_name == 'gpt-4o' else None)
1646
+
1647
+ m = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key='foobar'), profile=model_profile)
1648
+ assert isinstance(m.profile, ModelProfile)
1649
+ assert m.profile.json_schema_transformer is InlineDefsJsonSchemaTransformer
1650
+
1651
+ m = OpenAIModel('gpt-4o-mini', provider=OpenAIProvider(api_key='foobar'), profile=model_profile)
1652
+ assert isinstance(m.profile, ModelProfile)
1653
+ assert m.profile.json_schema_transformer is None
1654
+
1655
+
1656
+ def test_openai_model_profile_from_provider():
1657
+ class CustomProvider(OpenAIProvider):
1658
+ def model_profile(self, model_name: str) -> ModelProfile:
1659
+ return ModelProfile(
1660
+ json_schema_transformer=InlineDefsJsonSchemaTransformer if model_name == 'gpt-4o' else None
1661
+ )
1662
+
1663
+ m = OpenAIModel('gpt-4o', provider=CustomProvider(api_key='foobar'))
1664
+ assert isinstance(m.profile, ModelProfile)
1665
+ assert m.profile.json_schema_transformer is InlineDefsJsonSchemaTransformer
1666
+
1667
+ m = OpenAIModel('gpt-4o-mini', provider=CustomProvider(api_key='foobar'))
1668
+ assert isinstance(m.profile, ModelProfile)
1669
+ assert m.profile.json_schema_transformer is None
1670
+
1671
+
1672
+ def test_model_profile_strict_not_supported():
1673
+ my_tool = ToolDefinition(
1674
+ 'my_tool',
1675
+ 'This is my tool',
1676
+ {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
1677
+ strict=True,
1678
+ )
1679
+
1680
+ m = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key='foobar'))
1681
+ tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
1682
+
1683
+ assert tool_param == snapshot(
1684
+ {
1685
+ 'type': 'function',
1686
+ 'function': {
1687
+ 'name': 'my_tool',
1688
+ 'description': 'This is my tool',
1689
+ 'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
1690
+ 'strict': True,
1691
+ },
1692
+ }
1693
+ )
1694
+
1695
+ # Some models don't support strict tool definitions
1696
+ m = OpenAIModel(
1697
+ 'gpt-4o',
1698
+ provider=OpenAIProvider(api_key='foobar'),
1699
+ profile=OpenAIModelProfile(openai_supports_strict_tool_definition=False).update(openai_model_profile('gpt-4o')),
1700
+ )
1701
+ tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
1702
+
1703
+ assert tool_param == snapshot(
1704
+ {
1705
+ 'type': 'function',
1706
+ 'function': {
1707
+ 'name': 'my_tool',
1708
+ 'description': 'This is my tool',
1709
+ 'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
1710
+ },
1711
+ }
1712
+ )
@@ -1,4 +1,5 @@
1
1
  import json
2
+ from dataclasses import replace
2
3
 
3
4
  import pytest
4
5
  from inline_snapshot import snapshot
@@ -18,6 +19,8 @@ from pydantic_ai.messages import (
18
19
  ToolReturnPart,
19
20
  UserPromptPart,
20
21
  )
22
+ from pydantic_ai.profiles.openai import openai_model_profile
23
+ from pydantic_ai.tools import ToolDefinition
21
24
  from pydantic_ai.usage import Usage
22
25
 
23
26
  from ..conftest import IsDatetime, IsStr, TestEnv, try_import
@@ -462,3 +465,43 @@ async def test_openai_responses_model_instructions(allow_model_requests: None, o
462
465
  ),
463
466
  ]
464
467
  )
468
+
469
+
470
+ def test_model_profile_strict_not_supported():
471
+ my_tool = ToolDefinition(
472
+ 'my_tool',
473
+ 'This is my tool',
474
+ {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
475
+ strict=True,
476
+ )
477
+
478
+ m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key='foobar'))
479
+ tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
480
+
481
+ assert tool_param == snapshot(
482
+ {
483
+ 'name': 'my_tool',
484
+ 'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
485
+ 'type': 'function',
486
+ 'description': 'This is my tool',
487
+ 'strict': True,
488
+ }
489
+ )
490
+
491
+ # Some models don't support strict tool definitions
492
+ m = OpenAIResponsesModel(
493
+ 'gpt-4o',
494
+ provider=OpenAIProvider(api_key='foobar'),
495
+ profile=replace(openai_model_profile('gpt-4o'), openai_supports_strict_tool_definition=False),
496
+ )
497
+ tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
498
+
499
+ assert tool_param == snapshot(
500
+ {
501
+ 'name': 'my_tool',
502
+ 'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
503
+ 'type': 'function',
504
+ 'description': 'This is my tool',
505
+ 'strict': False,
506
+ }
507
+ )