pydantic-ai 0.3.1__tar.gz → 0.3.2__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 (282) hide show
  1. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/.gitignore +1 -0
  2. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/Makefile +3 -3
  3. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/PKG-INFO +3 -3
  4. pydantic_ai-0.3.2/tests/example_modules/mcp_server.py +26 -0
  5. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/mcp_server.py +18 -1
  6. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml +1 -1
  7. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_cohere.py +1 -1
  8. pydantic_ai-0.3.2/tests/models/test_mcp_sampling.py +135 -0
  9. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_examples.py +41 -34
  10. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_mcp.py +96 -13
  11. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/LICENSE +0 -0
  12. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/README.md +0 -0
  13. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/pyproject.toml +0 -0
  14. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/__init__.py +0 -0
  15. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/assets/dummy.pdf +0 -0
  16. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/assets/kiwi.png +0 -0
  17. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/assets/marcelo.mp3 +0 -0
  18. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/assets/small_video.mp4 +0 -0
  19. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_agent_with_stdio_server.yaml +0 -0
  20. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_audio_resource.yaml +0 -0
  21. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_dict.yaml +0 -0
  22. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_error.yaml +0 -0
  23. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_image.yaml +0 -0
  24. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_image_resource.yaml +0 -0
  25. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_multiple_items.yaml +0 -0
  26. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_none.yaml +0 -0
  27. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_str.yaml +0 -0
  28. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_mcp/test_tool_returning_text_resource.yaml +0 -0
  29. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[anthropic].yaml +0 -0
  30. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[bedrock].yaml +0 -0
  31. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[cohere].yaml +0 -0
  32. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[gemini].yaml +0 -0
  33. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[google].yaml +0 -0
  34. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[groq].yaml +0 -0
  35. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[mistral].yaml +0 -0
  36. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/cassettes/test_settings/test_stop_settings[openai].yaml +0 -0
  37. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/conftest.py +0 -0
  38. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/__init__.py +0 -0
  39. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_dataset.py +0 -0
  40. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_evaluator_base.py +0 -0
  41. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_evaluator_common.py +0 -0
  42. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_evaluator_context.py +0 -0
  43. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_evaluator_spec.py +0 -0
  44. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_evaluators.py +0 -0
  45. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_llm_as_a_judge.py +0 -0
  46. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_otel.py +0 -0
  47. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_render_numbers.py +0 -0
  48. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_reporting.py +0 -0
  49. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_reports.py +0 -0
  50. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/test_utils.py +0 -0
  51. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/evals/utils.py +0 -0
  52. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/example_modules/README.md +0 -0
  53. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/example_modules/bank_database.py +0 -0
  54. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/example_modules/fake_database.py +0 -0
  55. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/example_modules/weather_service.py +0 -0
  56. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/ext/__init__.py +0 -0
  57. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/ext/test_langchain.py +0 -0
  58. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/fasta2a/__init__.py +0 -0
  59. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/fasta2a/test_applications.py +0 -0
  60. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/__init__.py +0 -0
  61. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_file_persistence.py +0 -0
  62. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_graph.py +0 -0
  63. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_mermaid.py +0 -0
  64. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_persistence.py +0 -0
  65. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_state.py +0 -0
  66. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/graph/test_utils.py +0 -0
  67. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/import_examples.py +0 -0
  68. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/json_body_serializer.py +0 -0
  69. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/__init__.py +0 -0
  70. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_anthropic_model_empty_message_on_history.yaml +0 -0
  71. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_anthropic_model_instructions.yaml +0 -0
  72. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml +0 -0
  73. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml +0 -0
  74. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_document_binary_content_input.yaml +0 -0
  75. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_document_url_input.yaml +0 -0
  76. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_extra_headers.yaml +0 -0
  77. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_image_as_binary_content_tool_response.yaml +0 -0
  78. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_image_url_input.yaml +0 -0
  79. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_image_url_input_invalid_mime_type.yaml +0 -0
  80. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_multiple_parallel_tool_calls.yaml +0 -0
  81. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_anthropic/test_text_document_url_input.yaml +0 -0
  82. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_empty_system_prompt.yaml +0 -0
  83. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model.yaml +0 -0
  84. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_anthropic_model_without_tools.yaml +0 -0
  85. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_guardrail_config.yaml +0 -0
  86. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_instructions.yaml +0 -0
  87. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_iter_stream.yaml +0 -0
  88. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_max_tokens.yaml +0 -0
  89. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_other_parameters.yaml +0 -0
  90. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_performance_config.yaml +0 -0
  91. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_retry.yaml +0 -0
  92. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_stream.yaml +0 -0
  93. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_structured_response.yaml +0 -0
  94. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml +0 -0
  95. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml +0 -0
  96. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_model_top_p.yaml +0 -0
  97. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_bedrock_multiple_documents_in_history.yaml +0 -0
  98. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_document_url_input.yaml +0 -0
  99. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_image_as_binary_content_input.yaml +0 -0
  100. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_image_url_input.yaml +0 -0
  101. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_text_as_binary_content_input.yaml +0 -0
  102. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_text_document_url_input.yaml +0 -0
  103. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_video_as_binary_content_input.yaml +0 -0
  104. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_bedrock/test_video_url_input.yaml +0 -0
  105. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_cohere/test_cohere_model_instructions.yaml +0 -0
  106. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_cohere/test_request_simple_success_with_vcr.yaml +0 -0
  107. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml +0 -0
  108. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_download_item/test_download_item_application_octet_stream.yaml +0 -0
  109. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_download_item/test_download_item_no_content_type.yaml +0 -0
  110. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_document_url_input.yaml +0 -0
  111. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_false.yaml +0 -0
  112. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_additional_properties_is_true.yaml +0 -0
  113. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml +0 -0
  114. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_exclusive_minimum_and_maximum.yaml +0 -0
  115. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_model_instructions.yaml +0 -0
  116. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml +0 -0
  117. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_tool_config_any_with_tool_without_args.yaml +0 -0
  118. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_gemini_youtube_video_url_input.yaml +0 -0
  119. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_image_as_binary_content_input.yaml +0 -0
  120. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_image_as_binary_content_tool_response.yaml +0 -0
  121. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_image_url_input.yaml +0 -0
  122. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_labels_are_ignored_with_gla_provider.yaml +0 -0
  123. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_video_as_binary_content_input.yaml +0 -0
  124. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini/test_video_url_input.yaml +0 -0
  125. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_labels.yaml +0 -0
  126. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[AudioUrl (gs)].yaml +0 -0
  127. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[AudioUrl].yaml +0 -0
  128. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[DocumentUrl (gs)].yaml +0 -0
  129. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[DocumentUrl].yaml +0 -0
  130. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[ImageUrl (gs)].yaml +0 -0
  131. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[ImageUrl].yaml +0 -0
  132. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl (YouTube)].yaml +0 -0
  133. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl (gs)].yaml +0 -0
  134. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input[VideoUrl].yaml +0 -0
  135. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_gemini_vertex/test_url_input_force_download.yaml +0 -0
  136. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model.yaml +0 -0
  137. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_document_url_input.yaml +0 -0
  138. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_empty_user_prompt.yaml +0 -0
  139. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_image_as_binary_content_input.yaml +0 -0
  140. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_image_url_input.yaml +0 -0
  141. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_instructions.yaml +0 -0
  142. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_iter_stream.yaml +0 -0
  143. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_max_tokens.yaml +0 -0
  144. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_multiple_documents_in_history.yaml +0 -0
  145. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_retry.yaml +0 -0
  146. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_safety_settings.yaml +0 -0
  147. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_stream.yaml +0 -0
  148. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_structured_response.yaml +0 -0
  149. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_text_as_binary_content_input.yaml +0 -0
  150. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_text_document_url_input.yaml +0 -0
  151. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_thinking_config.yaml +0 -0
  152. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_thinking_part.yaml +0 -0
  153. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_thinking_part_iter.yaml +0 -0
  154. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_top_p.yaml +0 -0
  155. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_vertex_labels.yaml +0 -0
  156. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_vertex_provider.yaml +0 -0
  157. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_video_as_binary_content_input.yaml +0 -0
  158. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_model_video_url_input.yaml +0 -0
  159. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_timeout.yaml +0 -0
  160. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_tool_config_any_with_tool_without_args.yaml +0 -0
  161. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[AudioUrl (gs)].yaml +0 -0
  162. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[AudioUrl].yaml +0 -0
  163. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[DocumentUrl (gs)].yaml +0 -0
  164. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[DocumentUrl].yaml +0 -0
  165. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[ImageUrl (gs)].yaml +0 -0
  166. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[ImageUrl].yaml +0 -0
  167. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl (YouTube)].yaml +0 -0
  168. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl (gs)].yaml +0 -0
  169. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input[VideoUrl].yaml +0 -0
  170. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_google/test_google_url_input_force_download.yaml +0 -0
  171. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_extra_headers.yaml +0 -0
  172. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_groq_model_instructions.yaml +0 -0
  173. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml +0 -0
  174. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_image_as_binary_content_input.yaml +0 -0
  175. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_image_as_binary_content_tool_response.yaml +0 -0
  176. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_groq/test_image_url_input.yaml +0 -0
  177. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_mistral/test_image_as_binary_content_tool_response.yaml +0 -0
  178. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_mistral/test_mistral_model_instructions.yaml +0 -0
  179. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml +0 -0
  180. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_model_names/test_known_model_names.yaml +0 -0
  181. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_audio_as_binary_content_input.yaml +0 -0
  182. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_compatible_api_with_tool_calls_without_id.yaml +0 -0
  183. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_document_as_binary_content_input.yaml +0 -0
  184. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_document_url_input.yaml +0 -0
  185. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_extra_headers.yaml +0 -0
  186. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_image_as_binary_content_input.yaml +0 -0
  187. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_image_as_binary_content_tool_response.yaml +0 -0
  188. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_image_url_tool_response.yaml +0 -0
  189. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4.5-preview].yaml +0 -0
  190. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_max_completion_tokens[gpt-4o-mini].yaml +0 -0
  191. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_max_completion_tokens[o3-mini].yaml +0 -0
  192. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_multiple_agent_tool_calls.yaml +0 -0
  193. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_audio_url_input.yaml +0 -0
  194. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_instructions.yaml +0 -0
  195. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_instructions_with_tool_calls_keep_instructions.yaml +0 -0
  196. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml +0 -0
  197. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_model_thinking_part_iter.yaml +0 -0
  198. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_model_without_system_prompt.yaml +0 -0
  199. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[developer].yaml +0 -0
  200. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_o1_mini_system_role[system].yaml +0 -0
  201. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml +0 -0
  202. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_reasoning_model_with_temperature.yaml +0 -0
  203. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai/test_user_id.yaml +0 -0
  204. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_audio_as_binary_content_input.yaml +0 -0
  205. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_input.yaml +0 -0
  206. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_image_as_binary_content_tool_response.yaml +0 -0
  207. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_as_binary_content_input.yaml +0 -0
  208. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_document_url_input.yaml +0 -0
  209. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_image_url_input.yaml +0 -0
  210. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_builtin_tools.yaml +0 -0
  211. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_http_error.yaml +0 -0
  212. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_instructions.yaml +0 -0
  213. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_retry.yaml +0 -0
  214. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response.yaml +0 -0
  215. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_model_simple_response_with_tool_call.yaml +0 -0
  216. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_output_type.yaml +0 -0
  217. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_effort.yaml +0 -0
  218. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_reasoning_generate_summary.yaml +0 -0
  219. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_stream.yaml +0 -0
  220. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_system_prompt.yaml +0 -0
  221. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_openai_responses_text_document_url_input.yaml +0 -0
  222. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/cassettes/test_openai_responses/test_reasoning_model_with_temperature.yaml +0 -0
  223. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/mock_async_stream.py +0 -0
  224. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_anthropic.py +0 -0
  225. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_bedrock.py +0 -0
  226. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_deepseek.py +0 -0
  227. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_download_item.py +0 -0
  228. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_fallback.py +0 -0
  229. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_gemini.py +0 -0
  230. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_gemini_vertex.py +0 -0
  231. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_google.py +0 -0
  232. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_groq.py +0 -0
  233. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_instrumented.py +0 -0
  234. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_mistral.py +0 -0
  235. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_model.py +0 -0
  236. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_model_function.py +0 -0
  237. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_model_names.py +0 -0
  238. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_model_request_parameters.py +0 -0
  239. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_model_test.py +0 -0
  240. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_openai.py +0 -0
  241. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/models/test_openai_responses.py +0 -0
  242. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/__init__.py +0 -0
  243. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/cassettes/test_azure/test_azure_provider_call.yaml +0 -0
  244. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/cassettes/test_google_vertex/test_vertexai_provider.yaml +0 -0
  245. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/cassettes/test_heroku/test_heroku_model_provider_claude_3_7_sonnet.yaml +0 -0
  246. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/cassettes/test_openrouter/test_openrouter_with_google_model.yaml +0 -0
  247. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_anthropic.py +0 -0
  248. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_azure.py +0 -0
  249. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_bedrock.py +0 -0
  250. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_cohere.py +0 -0
  251. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_deepseek.py +0 -0
  252. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_fireworks.py +0 -0
  253. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_google_gla.py +0 -0
  254. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_google_vertex.py +0 -0
  255. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_grok.py +0 -0
  256. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_groq.py +0 -0
  257. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_heroku.py +0 -0
  258. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_mistral.py +0 -0
  259. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_openai.py +0 -0
  260. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_openrouter.py +0 -0
  261. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_provider_names.py +0 -0
  262. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/providers/test_together.py +0 -0
  263. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_a2a.py +0 -0
  264. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_agent.py +0 -0
  265. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_cli.py +0 -0
  266. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_deps.py +0 -0
  267. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_direct.py +0 -0
  268. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_format_as_xml.py +0 -0
  269. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_history_processor.py +0 -0
  270. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_json_body_serializer.py +0 -0
  271. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_live.py +0 -0
  272. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_logfire.py +0 -0
  273. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_messages.py +0 -0
  274. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_parts_manager.py +0 -0
  275. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_settings.py +0 -0
  276. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_streaming.py +0 -0
  277. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_thinking_part.py +0 -0
  278. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_tools.py +0 -0
  279. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_usage_limits.py +0 -0
  280. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/test_utils.py +0 -0
  281. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/typed_agent.py +0 -0
  282. {pydantic_ai-0.3.1 → pydantic_ai-0.3.2}/tests/typed_graph.py +0 -0
@@ -19,3 +19,4 @@ examples/pydantic_ai_examples/.chat_app_messages.sqlite
19
19
  node_modules/
20
20
  **.idea/
21
21
  .coverage*
22
+ /test_tmp/
@@ -64,7 +64,7 @@ test: ## Run tests and collect coverage data
64
64
  @uv run coverage report
65
65
 
66
66
  .PHONY: test-fast
67
- test-fast: ## Same as test except no coverage. ~1/4th the time depending on hardware.
67
+ test-fast: ## Same as test except no coverage and 4x faster depending on hardware
68
68
  uv run pytest -n auto --dist=loadgroup
69
69
 
70
70
  .PHONY: test-all-python
@@ -78,12 +78,12 @@ test-all-python: ## Run tests on Python 3.9 to 3.13
78
78
  @uv run coverage report
79
79
 
80
80
  .PHONY: testcov
81
- testcov: test ## Run tests and generate a coverage report
81
+ testcov: test ## Run tests and generate an HTML coverage report
82
82
  @echo "building coverage html"
83
83
  @uv run coverage html
84
84
 
85
85
  .PHONY: test-mrp
86
- test-mrp: ## Build and tests of mcp-run-python
86
+ test-mrp: ## Build and tests of mcp-run-python
87
87
  cd mcp-run-python && deno task build
88
88
  uv run --package mcp-run-python pytest mcp-run-python -v
89
89
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.3.1
31
+ Requires-Dist: pydantic-ai-slim[a2a,anthropic,bedrock,cli,cohere,evals,google,groq,mcp,mistral,openai,vertexai]==0.3.2
32
32
  Provides-Extra: examples
33
- Requires-Dist: pydantic-ai-examples==0.3.1; extra == 'examples'
33
+ Requires-Dist: pydantic-ai-examples==0.3.2; extra == 'examples'
34
34
  Provides-Extra: logfire
35
35
  Requires-Dist: logfire>=3.11.0; extra == 'logfire'
36
36
  Description-Content-Type: text/markdown
@@ -0,0 +1,26 @@
1
+ from typing import Any
2
+
3
+ from mcp.server.fastmcp import Context, FastMCP
4
+ from mcp.server.session import ServerSessionT
5
+ from mcp.shared.context import LifespanContextT, RequestT
6
+
7
+ mcp = FastMCP('PydanticAI MCP Server')
8
+
9
+
10
+ @mcp.tool()
11
+ async def echo_deps(ctx: Context[ServerSessionT, LifespanContextT, RequestT]) -> dict[str, Any]:
12
+ """Echo the run context.
13
+
14
+ Args:
15
+ ctx: Context object containing request and session information.
16
+
17
+ Returns:
18
+ Dictionary with an echo message and the deps.
19
+ """
20
+
21
+ deps: Any = getattr(ctx.request_context.meta, 'deps')
22
+ return {'echo': 'This is an echo message', 'deps': deps}
23
+
24
+
25
+ if __name__ == '__main__':
26
+ mcp.run()
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from mcp.server.fastmcp import Context, FastMCP, Image
6
6
  from mcp.server.session import ServerSessionT
7
7
  from mcp.shared.context import LifespanContextT, RequestT
8
- from mcp.types import BlobResourceContents, EmbeddedResource, TextResourceContents
8
+ from mcp.types import BlobResourceContents, EmbeddedResource, SamplingMessage, TextContent, TextResourceContents
9
9
  from pydantic import AnyUrl
10
10
 
11
11
  mcp = FastMCP('PydanticAI MCP Server')
@@ -136,6 +136,23 @@ async def echo_deps(ctx: Context[ServerSessionT, LifespanContextT, RequestT]) ->
136
136
  return {'echo': 'This is an echo message', 'deps': deps}
137
137
 
138
138
 
139
+ @mcp.tool()
140
+ async def use_sampling(ctx: Context, foo: str) -> str: # type: ignore
141
+ """Use sampling callback."""
142
+
143
+ result = await ctx.session.create_message(
144
+ [
145
+ SamplingMessage(role='assistant', content=TextContent(type='text', text='')),
146
+ SamplingMessage(role='user', content=TextContent(type='text', text=foo)),
147
+ ],
148
+ max_tokens=1_024,
149
+ system_prompt='this is a test of MCP sampling',
150
+ temperature=0.5,
151
+ stop_sequences=['potato'],
152
+ )
153
+ return result.model_dump_json(indent=2)
154
+
155
+
139
156
  @mcp._mcp_server.set_logging_level() # pyright: ignore[reportPrivateUsage]
140
157
  async def set_logging_level(level: str) -> None:
141
158
  global log_level
@@ -258,7 +258,7 @@ interactions:
258
258
  - **Bridge or Ferry:** If available, use a bridge or a ferry service. These are typically the safest and most reliable methods for crossing a river.
259
259
 
260
260
  3. **Prepare and Pack Essential Items:**
261
- - **Life Jacket/Personal Floatation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat.
261
+ - **Life Jacket/Personal Flotation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat.
262
262
  - **First-Aid Kit:** Carry a basic first-aid kit to handle any minor injuries that might occur during the crossing.
263
263
  - **Map and Compass:** Navigate the river and its surroundings with the help of a map and compass, especially if you're in an unfamiliar area.
264
264
  - **Communication Device:** Have a means of communication, such as a satellite phone or a personal locator beacon, especially in remote areas.
@@ -512,7 +512,7 @@ Crossing a river can be a different challenge compared to crossing a street, and
512
512
  - **Bridge or Ferry:** If available, use a bridge or a ferry service. These are typically the safest and most reliable methods for crossing a river.
513
513
 
514
514
  3. **Prepare and Pack Essential Items:**
515
- - **Life Jacket/Personal Floatation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat.
515
+ - **Life Jacket/Personal Flotation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat.
516
516
  - **First-Aid Kit:** Carry a basic first-aid kit to handle any minor injuries that might occur during the crossing.
517
517
  - **Map and Compass:** Navigate the river and its surroundings with the help of a map and compass, especially if you're in an unfamiliar area.
518
518
  - **Communication Device:** Have a means of communication, such as a satellite phone or a personal locator beacon, especially in remote areas.
@@ -0,0 +1,135 @@
1
+ import base64
2
+ from dataclasses import dataclass
3
+ from datetime import timezone
4
+ from typing import Any
5
+ from unittest.mock import AsyncMock
6
+
7
+ import pytest
8
+ from inline_snapshot import snapshot
9
+
10
+ from pydantic_ai.agent import Agent
11
+ from pydantic_ai.exceptions import UnexpectedModelBehavior
12
+ from pydantic_ai.messages import BinaryContent, ModelRequest, ModelResponse, SystemPromptPart, TextPart, UserPromptPart
13
+ from pydantic_ai.usage import Usage
14
+
15
+ from ..conftest import IsNow, try_import
16
+
17
+ with try_import() as imports_successful:
18
+ from mcp import CreateMessageResult
19
+ from mcp.types import TextContent
20
+
21
+ from pydantic_ai.models.mcp_sampling import MCPSamplingModel
22
+
23
+ pytestmark = pytest.mark.skipif(not imports_successful(), reason='mcp package not installed')
24
+
25
+
26
+ @dataclass
27
+ class FakeSession:
28
+ create_message: Any
29
+
30
+
31
+ def fake_session(create_message: Any) -> Any:
32
+ return FakeSession(create_message)
33
+
34
+
35
+ def test_mcp_sampling_model():
36
+ model = MCPSamplingModel(fake_session(AsyncMock()))
37
+ assert model.model_name == 'mcp-sampling'
38
+ assert model.system == 'MCP'
39
+
40
+
41
+ def test_assistant_text():
42
+ result = CreateMessageResult(
43
+ role='assistant', content=TextContent(type='text', text='text content'), model='test-model'
44
+ )
45
+ create_message = AsyncMock(return_value=result)
46
+ agent = Agent(model=MCPSamplingModel(fake_session(create_message)))
47
+
48
+ result = agent.run_sync('Hello')
49
+ assert result.output == snapshot('text content')
50
+ assert result.all_messages() == snapshot(
51
+ [
52
+ ModelRequest(
53
+ parts=[
54
+ UserPromptPart(
55
+ content='Hello',
56
+ timestamp=IsNow(tz=timezone.utc),
57
+ )
58
+ ]
59
+ ),
60
+ ModelResponse(
61
+ parts=[TextPart(content='text content')],
62
+ usage=Usage(requests=1),
63
+ model_name='test-model',
64
+ timestamp=IsNow(tz=timezone.utc),
65
+ ),
66
+ ]
67
+ )
68
+
69
+
70
+ def test_user_text():
71
+ result = CreateMessageResult(role='user', content=TextContent(type='text', text='text content'), model='test-model')
72
+ create_message = AsyncMock(return_value=result)
73
+ agent = Agent(model=MCPSamplingModel(fake_session(create_message)))
74
+
75
+ expected_match = 'Unexpected result from MCP sampling, expected "assistant" role, got user.'
76
+ with pytest.raises(UnexpectedModelBehavior, match=expected_match):
77
+ agent.run_sync('Hello')
78
+
79
+
80
+ def test_assistant_text_history():
81
+ result = CreateMessageResult(
82
+ role='assistant', content=TextContent(type='text', text='text content'), model='test-model'
83
+ )
84
+ create_message = AsyncMock(return_value=result)
85
+ agent = Agent(model=MCPSamplingModel(fake_session(create_message)), instructions='testing')
86
+
87
+ result = agent.run_sync('1')
88
+ result = agent.run_sync('2', message_history=result.all_messages())
89
+
90
+ assert result.output == snapshot('text content')
91
+ assert result.all_messages() == snapshot(
92
+ [
93
+ ModelRequest(parts=[UserPromptPart(content='1', timestamp=IsNow(tz=timezone.utc))], instructions='testing'),
94
+ ModelResponse(
95
+ parts=[TextPart(content='text content')],
96
+ usage=Usage(requests=1),
97
+ model_name='test-model',
98
+ timestamp=IsNow(tz=timezone.utc),
99
+ ),
100
+ ModelRequest(parts=[UserPromptPart(content='2', timestamp=IsNow(tz=timezone.utc))], instructions='testing'),
101
+ ModelResponse(
102
+ parts=[TextPart(content='text content')],
103
+ usage=Usage(requests=1),
104
+ model_name='test-model',
105
+ timestamp=IsNow(tz=timezone.utc),
106
+ ),
107
+ ]
108
+ )
109
+
110
+
111
+ def test_assistant_text_history_complex():
112
+ history = [
113
+ ModelRequest(
114
+ parts=[
115
+ UserPromptPart(content='1'),
116
+ UserPromptPart(
117
+ content=['a string', BinaryContent(data=base64.b64encode(b'data'), media_type='image/jpeg')]
118
+ ),
119
+ SystemPromptPart(content='system content'),
120
+ ]
121
+ ),
122
+ ModelResponse(
123
+ parts=[TextPart(content='text content')],
124
+ usage=Usage(requests=1),
125
+ model_name='test-model',
126
+ ),
127
+ ]
128
+
129
+ result = CreateMessageResult(
130
+ role='assistant', content=TextContent(type='text', text='text content'), model='test-model'
131
+ )
132
+ create_message = AsyncMock(return_value=result)
133
+ agent = Agent(model=MCPSamplingModel(fake_session(create_message)))
134
+ result = agent.run_sync('1', message_history=history)
135
+ assert result.output == snapshot('text content')
@@ -3,13 +3,13 @@ from __future__ import annotations as _annotations
3
3
  import json
4
4
  import os
5
5
  import re
6
+ import shutil
6
7
  import sys
7
8
  from collections.abc import AsyncIterator, Iterable, Sequence
8
9
  from dataclasses import dataclass
9
10
  from inspect import FrameInfo
10
11
  from io import StringIO
11
12
  from pathlib import Path
12
- from types import ModuleType
13
13
  from typing import Any
14
14
 
15
15
  import httpx
@@ -64,32 +64,44 @@ pytestmark = [
64
64
  reason='google-auth or logfire or google-provider not installed',
65
65
  ),
66
66
  ]
67
+ code_examples: dict[str, CodeExample] = {}
67
68
 
68
69
 
69
70
  def find_filter_examples() -> Iterable[ParameterSet]:
70
71
  # Ensure this is run from the package root regardless of where/how the tests are run
71
- os.chdir(Path(__file__).parent.parent)
72
+ root_dir = Path(__file__).parent.parent
73
+ os.chdir(root_dir)
72
74
 
73
75
  for ex in find_examples('docs', 'pydantic_ai_slim', 'pydantic_graph', 'pydantic_evals'):
74
76
  if ex.path.name != '_utils.py':
75
77
  try:
76
- path = ex.path.relative_to(Path.cwd())
78
+ path = ex.path.relative_to(root_dir)
77
79
  except ValueError:
78
80
  path = ex.path
79
81
  test_id = f'{path}:{ex.start_line}'
80
82
  prefix_settings = ex.prefix_settings()
81
- if opt_title := prefix_settings.get('title'):
82
- test_id += f':{opt_title}'
83
+ if title := prefix_settings.get('title'):
84
+ if title.endswith('.py'):
85
+ code_examples[title] = ex
86
+ test_id += f':{title}'
83
87
  yield pytest.param(ex, id=test_id)
84
88
 
85
89
 
86
90
  @pytest.fixture
87
- def reset_cwd():
88
- original_cwd = os.getcwd()
91
+ def tmp_path_cwd(tmp_path: Path):
92
+ cwd = os.getcwd()
93
+
94
+ root_dir = Path(__file__).parent.parent
95
+ for file in (root_dir / 'tests' / 'example_modules').glob('*.py'):
96
+ shutil.copy(file, tmp_path)
97
+ sys.path.append(str(tmp_path))
98
+ os.chdir(tmp_path)
99
+
89
100
  try:
90
- yield
101
+ yield tmp_path
91
102
  finally:
92
- os.chdir(original_cwd)
103
+ os.chdir(cwd)
104
+ sys.path.remove(str(tmp_path))
93
105
 
94
106
 
95
107
  @pytest.mark.xdist_group(name='doc_tests')
@@ -101,8 +113,7 @@ def test_docs_examples( # noqa: C901
101
113
  client_with_handler: ClientWithHandler,
102
114
  allow_model_requests: None,
103
115
  env: TestEnv,
104
- tmp_path: Path,
105
- reset_cwd: None,
116
+ tmp_path_cwd: Path,
106
117
  ):
107
118
  mocker.patch('pydantic_ai.agent.models.infer_model', side_effect=mock_infer_model)
108
119
  mocker.patch('pydantic_ai._utils.group_by_temporal', side_effect=mock_group_by_temporal)
@@ -142,14 +153,13 @@ def test_docs_examples( # noqa: C901
142
153
  env.set('AWS_SECRET_ACCESS_KEY', 'testing')
143
154
  env.set('AWS_DEFAULT_REGION', 'us-east-1')
144
155
 
145
- sys.path.append('tests/example_modules')
146
-
147
156
  prefix_settings = example.prefix_settings()
148
- opt_title = prefix_settings.get('title')
149
157
  opt_test = prefix_settings.get('test', '')
150
158
  opt_lint = prefix_settings.get('lint', '')
151
159
  noqa = prefix_settings.get('noqa', '')
152
- python_version = prefix_settings.get('py', None)
160
+ python_version = prefix_settings.get('py')
161
+ dunder_name = prefix_settings.get('dunder_name', '__main__')
162
+ requires = prefix_settings.get('requires')
153
163
 
154
164
  if python_version:
155
165
  python_version_info = tuple(int(v) for v in python_version.split('.'))
@@ -159,14 +169,12 @@ def test_docs_examples( # noqa: C901
159
169
  if opt_test.startswith('skip') and opt_lint.startswith('skip'):
160
170
  pytest.skip('both running code and lint skipped')
161
171
 
162
- if opt_title in {
163
- 'ai_q_and_a_run.py',
164
- 'count_down_from_persistence.py',
165
- 'generate_dataset_example.py',
166
- 'generate_dataset_example_json.py',
167
- 'save_load_dataset_example.py',
168
- }:
169
- os.chdir(tmp_path)
172
+ if requires:
173
+ for req in requires.split(','):
174
+ if ex := code_examples.get(req):
175
+ (tmp_path_cwd / req).write_text(ex.source)
176
+ else: # pragma: no cover
177
+ raise KeyError(f'Example {req} not found, check the `requires` header of this example.')
170
178
 
171
179
  ruff_ignore: list[str] = ['D', 'Q001']
172
180
  # `from bank_database import DatabaseConn` wrongly sorted in imports
@@ -196,19 +204,12 @@ def test_docs_examples( # noqa: C901
196
204
  if opt_test.startswith('skip'):
197
205
  print(opt_test[4:].lstrip(' -') or 'running code skipped')
198
206
  else:
199
- test_globals: dict[str, str] = {}
200
- if opt_title == 'mcp_client.py':
201
- test_globals['__name__'] = '__test__'
207
+ test_globals: dict[str, str] = {'__name__': dunder_name}
208
+
202
209
  if eval_example.update_examples: # pragma: lax no cover
203
- module_dict = eval_example.run_print_update(example, call=call_name, module_globals=test_globals)
210
+ eval_example.run_print_update(example, call=call_name, module_globals=test_globals)
204
211
  else:
205
- module_dict = eval_example.run_print_check(example, call=call_name, module_globals=test_globals)
206
-
207
- if title := opt_title:
208
- if title.endswith('.py'):
209
- module_name = title[:-3]
210
- sys.modules[module_name] = module = ModuleType(module_name)
211
- module.__dict__.update(module_dict)
212
+ eval_example.run_print_check(example, call=call_name, module_globals=test_globals)
212
213
 
213
214
 
214
215
  def print_callback(s: str) -> str:
@@ -432,6 +433,10 @@ text_responses: dict[str, str | ToolCallPart] = {
432
433
  'explanation': 'I am not equipped to provide travel information, such as flights from Amsterdam to Mexico City.'
433
434
  },
434
435
  ),
436
+ 'Create an image of a robot in a punk style.': ToolCallPart(
437
+ tool_name='image_generator', args={'subject': 'robot', 'style': 'punk'}, tool_call_id='0001'
438
+ ),
439
+ "subject='robot' style='punk'": '<svg/>',
435
440
  }
436
441
 
437
442
  tool_responses: dict[tuple[str, str], str] = {
@@ -676,6 +681,8 @@ async def model_logic( # noqa: C901
676
681
  )
677
682
  ]
678
683
  )
684
+ elif isinstance(m, ToolReturnPart) and m.tool_name == 'image_generator':
685
+ return ModelResponse(parts=[TextPart('Image file written to robot_punk.svg.')])
679
686
  else:
680
687
  sys.stdout.write(str(debug.format(messages, info)))
681
688
  raise RuntimeError(f'Unexpected message: {m}')
@@ -1,21 +1,24 @@
1
1
  """Tests for the MCP (Model Context Protocol) server implementation."""
2
2
 
3
+ import base64
3
4
  import re
5
+ from datetime import timezone
4
6
  from pathlib import Path
5
- from typing import Any, Final
7
+ from typing import Any
6
8
  from unittest.mock import AsyncMock, patch
7
9
 
8
10
  import pytest
9
11
  from inline_snapshot import snapshot
10
12
 
11
13
  from pydantic_ai.agent import Agent
12
- from pydantic_ai.exceptions import ModelRetry, UserError
14
+ from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UserError
13
15
  from pydantic_ai.messages import (
14
16
  BinaryContent,
15
17
  ModelRequest,
16
18
  ModelResponse,
17
19
  RetryPromptPart,
18
20
  TextPart,
21
+ ThinkingPart,
19
22
  ToolCallPart,
20
23
  ToolReturnPart,
21
24
  UserPromptPart,
@@ -24,19 +27,19 @@ from pydantic_ai.models.test import TestModel
24
27
  from pydantic_ai.tools import RunContext
25
28
  from pydantic_ai.usage import Usage
26
29
 
27
- from .conftest import IsDatetime, IsStr, try_import
30
+ from .conftest import IsDatetime, IsNow, IsStr, try_import
28
31
 
29
32
  with try_import() as imports_successful:
30
- from mcp import ErrorData, McpError
33
+ from mcp import ErrorData, McpError, SamplingMessage
34
+ from mcp.types import CreateMessageRequestParams, ImageContent, TextContent
31
35
 
36
+ from pydantic_ai._mcp import map_from_mcp_params, map_from_model_response
32
37
  from pydantic_ai.mcp import CallToolFunc, MCPServerSSE, MCPServerStdio, ToolResult
33
38
  from pydantic_ai.models.google import GoogleModel
34
39
  from pydantic_ai.models.openai import OpenAIModel
35
40
  from pydantic_ai.providers.google import GoogleProvider
36
41
  from pydantic_ai.providers.openai import OpenAIProvider
37
42
 
38
- TOOL_COUNT: Final[int] = 12
39
-
40
43
  pytestmark = [
41
44
  pytest.mark.skipif(not imports_successful(), reason='mcp and openai not installed'),
42
45
  pytest.mark.anyio,
@@ -55,7 +58,7 @@ async def test_stdio_server():
55
58
  server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
56
59
  async with server:
57
60
  tools = await server.list_tools()
58
- assert len(tools) == TOOL_COUNT
61
+ assert len(tools) == snapshot(13)
59
62
  assert tools[0].name == 'celsius_to_fahrenheit'
60
63
  assert tools[0].description.startswith('Convert Celsius to Fahrenheit.')
61
64
 
@@ -64,6 +67,13 @@ async def test_stdio_server():
64
67
  assert result == snapshot('32.0')
65
68
 
66
69
 
70
+ async def test_reentrant_context_manager():
71
+ server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
72
+ async with server:
73
+ async with server:
74
+ pass
75
+
76
+
67
77
  async def test_stdio_server_with_tool_prefix():
68
78
  server = MCPServerStdio('python', ['-m', 'tests.mcp_server'], tool_prefix='foo')
69
79
  async with server:
@@ -76,7 +86,7 @@ async def test_stdio_server_with_cwd():
76
86
  server = MCPServerStdio('python', ['mcp_server.py'], cwd=test_dir)
77
87
  async with server:
78
88
  tools = await server.list_tools()
79
- assert len(tools) == TOOL_COUNT
89
+ assert len(tools) == snapshot(13)
80
90
 
81
91
 
82
92
  async def test_process_tool_call() -> None:
@@ -104,7 +114,7 @@ async def test_process_tool_call() -> None:
104
114
  def test_sse_server():
105
115
  sse_server = MCPServerSSE(url='http://localhost:8000/sse')
106
116
  assert sse_server.url == 'http://localhost:8000/sse'
107
- assert sse_server._get_log_level() is None # pyright: ignore[reportPrivateUsage]
117
+ assert sse_server.log_level is None
108
118
 
109
119
 
110
120
  def test_sse_server_with_header_and_timeout():
@@ -119,7 +129,7 @@ def test_sse_server_with_header_and_timeout():
119
129
  assert sse_server.headers is not None and sse_server.headers['my-custom-header'] == 'my-header-value'
120
130
  assert sse_server.timeout == 10
121
131
  assert sse_server.sse_read_timeout == 100
122
- assert sse_server._get_log_level() == 'info' # pyright: ignore[reportPrivateUsage]
132
+ assert sse_server.log_level == 'info'
123
133
 
124
134
 
125
135
  @pytest.mark.vcr()
@@ -240,10 +250,10 @@ async def test_agent_with_server_not_running(openai_api_key: str):
240
250
 
241
251
  async def test_log_level_unset():
242
252
  server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
243
- assert server._get_log_level() is None # pyright: ignore[reportPrivateUsage]
253
+ assert server.log_level is None
244
254
  async with server:
245
255
  tools = await server.list_tools()
246
- assert len(tools) == TOOL_COUNT
256
+ assert len(tools) == snapshot(13)
247
257
  assert tools[10].name == 'get_log_level'
248
258
 
249
259
  result = await server.call_tool('get_log_level', {})
@@ -252,7 +262,7 @@ async def test_log_level_unset():
252
262
 
253
263
  async def test_log_level_set():
254
264
  server = MCPServerStdio('python', ['-m', 'tests.mcp_server'], log_level='info')
255
- assert server._get_log_level() == 'info' # pyright: ignore[reportPrivateUsage]
265
+ assert server.log_level == 'info'
256
266
  async with server:
257
267
  result = await server.call_tool('get_log_level', {})
258
268
  assert result == snapshot('info')
@@ -963,6 +973,30 @@ async def test_tool_returning_multiple_items(allow_model_requests: None, agent:
963
973
  )
964
974
 
965
975
 
976
+ async def test_client_sampling():
977
+ server = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
978
+ server.sampling_model = TestModel(custom_output_text='sampling model response')
979
+ async with server:
980
+ result = await server.call_tool('use_sampling', {'foo': 'bar'})
981
+ assert result == snapshot(
982
+ {
983
+ 'meta': None,
984
+ 'role': 'assistant',
985
+ 'content': {'type': 'text', 'text': 'sampling model response', 'annotations': None},
986
+ 'model': 'test',
987
+ 'stopReason': None,
988
+ }
989
+ )
990
+
991
+
992
+ async def test_client_sampling_disabled():
993
+ server = MCPServerStdio('python', ['-m', 'tests.mcp_server'], allow_sampling=False)
994
+ server.sampling_model = TestModel(custom_output_text='sampling model response')
995
+ async with server:
996
+ with pytest.raises(ModelRetry, match='Error executing tool use_sampling: Sampling not supported'):
997
+ await server.call_tool('use_sampling', {'foo': 'bar'})
998
+
999
+
966
1000
  async def test_mcp_server_raises_mcp_error(allow_model_requests: None, agent: Agent) -> None:
967
1001
  server = agent._mcp_servers[0] # pyright: ignore[reportPrivateUsage]
968
1002
 
@@ -976,3 +1010,52 @@ async def test_mcp_server_raises_mcp_error(allow_model_requests: None, agent: Ag
976
1010
  ):
977
1011
  with pytest.raises(ModelRetry, match='Test MCP error conversion'):
978
1012
  await server.call_tool('test_tool', {})
1013
+
1014
+
1015
+ def test_map_from_mcp_params_model_request():
1016
+ params = CreateMessageRequestParams(
1017
+ messages=[
1018
+ SamplingMessage(role='user', content=TextContent(type='text', text='xx')),
1019
+ SamplingMessage(
1020
+ role='user',
1021
+ content=ImageContent(type='image', data=base64.b64encode(b'img').decode(), mimeType='image/png'),
1022
+ ),
1023
+ ],
1024
+ maxTokens=8,
1025
+ )
1026
+ pai_messages = map_from_mcp_params(params)
1027
+ assert pai_messages == snapshot(
1028
+ [
1029
+ ModelRequest(
1030
+ parts=[
1031
+ UserPromptPart(content='xx', timestamp=IsNow(tz=timezone.utc)),
1032
+ UserPromptPart(
1033
+ content=[BinaryContent(data=b'img', media_type='image/png')], timestamp=IsNow(tz=timezone.utc)
1034
+ ),
1035
+ ]
1036
+ )
1037
+ ]
1038
+ )
1039
+
1040
+
1041
+ def test_map_from_mcp_params_model_response():
1042
+ params = CreateMessageRequestParams(
1043
+ messages=[
1044
+ SamplingMessage(role='assistant', content=TextContent(type='text', text='xx')),
1045
+ ],
1046
+ maxTokens=8,
1047
+ )
1048
+ pai_messages = map_from_mcp_params(params)
1049
+ assert pai_messages == snapshot(
1050
+ [
1051
+ ModelResponse(
1052
+ parts=[TextPart(content='xx')],
1053
+ timestamp=IsNow(tz=timezone.utc),
1054
+ )
1055
+ ]
1056
+ )
1057
+
1058
+
1059
+ def test_map_from_model_response():
1060
+ with pytest.raises(UnexpectedModelBehavior, match='Unexpected part type: ThinkingPart, expected TextPart'):
1061
+ map_from_model_response(ModelResponse(parts=[ThinkingPart(content='Thinking...')]))
File without changes
File without changes
File without changes