atomic-agents 2.6.0__tar.gz → 2.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. atomic_agents-2.7.3/.claude/skills/release/SKILL.md +104 -0
  2. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.gitignore +2 -0
  3. atomic_agents-2.7.3/.pre-commit-config.yaml +20 -0
  4. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/PKG-INFO +1 -1
  5. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/context/chat_history.py +72 -44
  6. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/utils/token_counter.py +5 -2
  7. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/connectors/mcp/test_mcp_factory.py +1 -3
  8. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/connectors/mcp/test_schema_transformer.py +3 -9
  9. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/context/test_chat_history.py +327 -0
  10. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/utils/test_token_counter.py +63 -25
  11. atomic_agents-2.7.3/atomic-examples/nested-multimodal/README.md +68 -0
  12. atomic_agents-2.7.3/atomic-examples/nested-multimodal/nested_multimodal/main.py +179 -0
  13. atomic_agents-2.7.3/atomic-examples/nested-multimodal/pyproject.toml +25 -0
  14. atomic_agents-2.7.3/atomic-examples/nested-multimodal/test_images/nutrition_label_1.png +0 -0
  15. atomic_agents-2.7.3/atomic-forge/tools/bocha_search/README.md +85 -0
  16. atomic_agents-2.7.3/atomic-forge/tools/bocha_search/pyproject.toml +34 -0
  17. atomic_agents-2.7.3/atomic-forge/tools/bocha_search/requirements.txt +3 -0
  18. atomic_agents-2.7.3/atomic-forge/tools/bocha_search/tests/test_bocha_search.py +254 -0
  19. atomic_agents-2.7.3/atomic-forge/tools/bocha_search/tool/bocha_search.py +196 -0
  20. atomic_agents-2.7.3/atomic-forge/tools/youtube_transcript_scraper/.coveragerc +8 -0
  21. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/tools.md +1 -0
  22. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/pyproject.toml +2 -1
  23. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/uv.lock +61 -1
  24. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/architecture_highlevel_overview.png +0 -0
  25. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/atomic-cli-tool-menu.png +0 -0
  26. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/atomic-cli.png +0 -0
  27. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/docs.png +0 -0
  28. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/logo.png +0 -0
  29. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/video-thumbnail-1.png +0 -0
  30. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/video-thumbnail-2.png +0 -0
  31. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/video-thumbnail.png +0 -0
  32. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.assets/what_is_sent_in_prompt.png +0 -0
  33. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.claude-plugin/marketplace.json +0 -0
  34. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.coveragerc +0 -0
  35. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.flake8 +0 -0
  36. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.github/funding.yml +0 -0
  37. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.github/workflows/code-quality.yml +0 -0
  38. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/.github/workflows/docs.yml +0 -0
  39. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/LICENSE +0 -0
  40. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/README.md +0 -0
  41. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/UPGRADE_DOC.md +0 -0
  42. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/agents.md +0 -0
  43. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/LICENSE +0 -0
  44. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/MANIFEST.in +0 -0
  45. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/__init__.py +0 -0
  46. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/agents/__init__.py +0 -0
  47. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/agents/atomic_agent.py +0 -0
  48. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/base/__init__.py +0 -0
  49. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/base/base_io_schema.py +0 -0
  50. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/base/base_prompt.py +0 -0
  51. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/base/base_resource.py +0 -0
  52. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/base/base_tool.py +0 -0
  53. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/connectors/__init__.py +0 -0
  54. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/connectors/mcp/__init__.py +0 -0
  55. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/connectors/mcp/mcp_definition_service.py +0 -0
  56. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/connectors/mcp/mcp_factory.py +0 -0
  57. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/connectors/mcp/schema_transformer.py +0 -0
  58. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/context/__init__.py +0 -0
  59. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/context/system_prompt_generator.py +0 -0
  60. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/utils/__init__.py +0 -0
  61. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/atomic_agents/utils/format_tool_message.py +0 -0
  62. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/agents/test_atomic_agent.py +0 -0
  63. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/base/test_base_tool.py +0 -0
  64. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/connectors/mcp/test_mcp_definition_service.py +0 -0
  65. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/context/test_system_prompt_generator.py +0 -0
  66. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/files/audio_sample.mp3 +0 -0
  67. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/files/image_sample.jpg +0 -0
  68. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/files/pdf_sample.pdf +0 -0
  69. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-agents/tests/utils/test_format_tool_message.py +0 -0
  70. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/README.md +0 -0
  71. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/__init__.py +0 -0
  72. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/app.py +0 -0
  73. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/color_utils.py +0 -0
  74. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/constants.py +0 -0
  75. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/main.py +0 -0
  76. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/screens/atomic_tool_explorer.py +0 -0
  77. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/screens/file_explorer.py +0 -0
  78. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/screens/main_menu.py +0 -0
  79. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/screens/tool_info_screen.py +0 -0
  80. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/utils.py +0 -0
  81. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/confirmation_modal.py +0 -0
  82. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/generic_list.py +0 -0
  83. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/gradient_title.py +0 -0
  84. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/icon_selection_list.py +0 -0
  85. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/menu.py +0 -0
  86. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/new_item_input.py +0 -0
  87. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-assembler/atomic_assembler/widgets/tool_info_display.py +0 -0
  88. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-multimodal/README.md +0 -0
  89. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-multimodal/basic_multimodal/main.py +0 -0
  90. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-multimodal/pyproject.toml +0 -0
  91. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-multimodal/test_images/nutrition_label_1.png +0 -0
  92. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-multimodal/test_images/nutrition_label_2.jpg +0 -0
  93. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-pdf-analysis/README.md +0 -0
  94. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-pdf-analysis/basic_pdf_analysis/main.py +0 -0
  95. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-pdf-analysis/pyproject.toml +0 -0
  96. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/basic-pdf-analysis/test_media/pdf_sample.pdf +0 -0
  97. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/README.md +0 -0
  98. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/agents/choice_agent.py +0 -0
  99. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/agents/qa_agent.py +0 -0
  100. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/agents/query_agent.py +0 -0
  101. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/config.py +0 -0
  102. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/context_providers.py +0 -0
  103. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/main.py +0 -0
  104. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/tools/searxng_search.py +0 -0
  105. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/deep_research/tools/webpage_scraper.py +0 -0
  106. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/mermaid.md +0 -0
  107. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/deep-research/pyproject.toml +0 -0
  108. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/README.md +0 -0
  109. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/__init__.py +0 -0
  110. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/bridge.py +0 -0
  111. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/domain/__init__.py +0 -0
  112. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/domain/datasets.py +0 -0
  113. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/domain/evaluation.py +0 -0
  114. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/domain/models.py +0 -0
  115. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/main.py +0 -0
  116. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/presentation/__init__.py +0 -0
  117. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/presentation/console.py +0 -0
  118. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/schemas.py +0 -0
  119. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/stages/__init__.py +0 -0
  120. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/stages/stage1_dspy.py +0 -0
  121. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/stages/stage2_atomic.py +0 -0
  122. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/dspy_integration/stages/stage3_combined.py +0 -0
  123. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/dspy-integration/pyproject.toml +0 -0
  124. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/README.md +0 -0
  125. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/__init__.py +0 -0
  126. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/client.py +0 -0
  127. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/lib/__init__.py +0 -0
  128. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/lib/agents/__init__.py +0 -0
  129. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/lib/agents/chat_agent.py +0 -0
  130. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/lib/config.py +0 -0
  131. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/lib/schemas.py +0 -0
  132. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/fastapi_memory/main.py +0 -0
  133. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/pyproject.toml +0 -0
  134. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/fastapi-memory/test_api.py +0 -0
  135. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/hooks-example/README.md +0 -0
  136. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/hooks-example/hooks_example/main.py +0 -0
  137. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/hooks-example/pyproject.toml +0 -0
  138. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/README.md +0 -0
  139. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main.py +0 -0
  140. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main_fastapi.py +0 -0
  141. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main_http.py +0 -0
  142. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main_sse.py +0 -0
  143. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main_stdio.py +0 -0
  144. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/example_client/main_stdio_async.py +0 -0
  145. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-client/pyproject.toml +0 -0
  146. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/README.md +0 -0
  147. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/demo_tools.py +0 -0
  148. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/__init__.py +0 -0
  149. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/__init__.py +0 -0
  150. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/prompt.py +0 -0
  151. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/resource.py +0 -0
  152. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/interfaces/tool.py +0 -0
  153. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/prompts/sample_prompts.py +0 -0
  154. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/resources/__init__.py +0 -0
  155. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/resources/sample_resources.py +0 -0
  156. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server.py +0 -0
  157. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server_http.py +0 -0
  158. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server_sse.py +0 -0
  159. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/server_stdio.py +0 -0
  160. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/services/__init__.py +0 -0
  161. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/services/prompt_service.py +0 -0
  162. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/services/resource_service.py +0 -0
  163. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/services/tool_service.py +0 -0
  164. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/__init__.py +0 -0
  165. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/add_numbers.py +0 -0
  166. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/batch_operations.py +0 -0
  167. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/divide_numbers.py +0 -0
  168. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/multiply_numbers.py +0 -0
  169. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/example_mcp_server/tools/subtract_numbers.py +0 -0
  170. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/mcp-agent/example-mcp-server/pyproject.toml +0 -0
  171. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/orchestration-agent/README.md +0 -0
  172. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/orchestration-agent/orchestration_agent/orchestrator.py +0 -0
  173. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/orchestration-agent/orchestration_agent/tools/calculator.py +0 -0
  174. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/orchestration-agent/orchestration_agent/tools/searxng_search.py +0 -0
  175. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/orchestration-agent/pyproject.toml +0 -0
  176. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/README.md +0 -0
  177. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/__init__.py +0 -0
  178. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/agents/__init__.py +0 -0
  179. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/agents/orchestrator_agent.py +0 -0
  180. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/agents/tool_finder_agent.py +0 -0
  181. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/main.py +0 -0
  182. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/registry/__init__.py +0 -0
  183. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/registry/tool_registry.py +0 -0
  184. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/tools/__init__.py +0 -0
  185. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/progressive_disclosure/tools/search_tools.py +0 -0
  186. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/pyproject.toml +0 -0
  187. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/data_server/data_server/__init__.py +0 -0
  188. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/data_server/data_server/server.py +0 -0
  189. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/data_server/pyproject.toml +0 -0
  190. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/math_server/math_server/__init__.py +0 -0
  191. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/math_server/math_server/server.py +0 -0
  192. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/math_server/pyproject.toml +0 -0
  193. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/text_server/pyproject.toml +0 -0
  194. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/text_server/text_server/__init__.py +0 -0
  195. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/progressive-disclosure/servers/text_server/text_server/server.py +0 -0
  196. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/README.md +0 -0
  197. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/pyproject.toml +0 -0
  198. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/1_0_basic_chatbot.py +0 -0
  199. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/1_1_basic_chatbot_streaming.py +0 -0
  200. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/1_2_basic_chatbot_async_streaming.py +0 -0
  201. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/2_basic_custom_chatbot.py +0 -0
  202. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/3_0_basic_custom_chatbot_with_custom_schema.py +0 -0
  203. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/3_1_basic_custom_chatbot_with_custom_schema_streaming.py +0 -0
  204. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/4_basic_chatbot_different_providers.py +0 -0
  205. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/5_custom_system_role_for_reasoning_models.py +0 -0
  206. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/6_0_asynchronous_processing.py +0 -0
  207. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/quickstart/quickstart/6_1_asynchronous_processing_streaming.py +0 -0
  208. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/.gitignore +0 -0
  209. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/README.md +0 -0
  210. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/pyproject.toml +0 -0
  211. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/agents/qa_agent.py +0 -0
  212. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/agents/query_agent.py +0 -0
  213. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/config.py +0 -0
  214. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/context_providers.py +0 -0
  215. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/main.py +0 -0
  216. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/services/__init__.py +0 -0
  217. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/services/base.py +0 -0
  218. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/services/chroma_db.py +0 -0
  219. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/services/factory.py +0 -0
  220. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/rag-chatbot/rag_chatbot/services/qdrant_db.py +0 -0
  221. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/README.md +0 -0
  222. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/pyproject.toml +0 -0
  223. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/web_search_agent/agents/query_agent.py +0 -0
  224. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/web_search_agent/agents/question_answering_agent.py +0 -0
  225. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/web_search_agent/main.py +0 -0
  226. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/web-search-agent/web_search_agent/tools/searxng_search.py +0 -0
  227. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-summarizer/README.md +0 -0
  228. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-summarizer/pyproject.toml +0 -0
  229. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-summarizer/youtube_summarizer/agent.py +0 -0
  230. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-summarizer/youtube_summarizer/main.py +0 -0
  231. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-summarizer/youtube_summarizer/tools/youtube_transcript_scraper.py +0 -0
  232. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-to-recipe/README.md +0 -0
  233. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-to-recipe/pyproject.toml +0 -0
  234. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-to-recipe/youtube_to_recipe/agent.py +0 -0
  235. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-to-recipe/youtube_to_recipe/main.py +0 -0
  236. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-examples/youtube-to-recipe/youtube_to_recipe/tools/youtube_transcript_scraper.py +0 -0
  237. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/README.md +0 -0
  238. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/guides/tool_structure.md +0 -0
  239. {atomic_agents-2.6.0/atomic-forge/tools/calculator → atomic_agents-2.7.3/atomic-forge/tools/bocha_search}/.coveragerc +0 -0
  240. {atomic_agents-2.6.0/atomic-forge/tools/searxng_search → atomic_agents-2.7.3/atomic-forge/tools/calculator}/.coveragerc +0 -0
  241. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/calculator/README.md +0 -0
  242. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/calculator/pyproject.toml +0 -0
  243. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/calculator/requirements.txt +0 -0
  244. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/calculator/tests/test_calculator.py +0 -0
  245. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/calculator/tool/calculator.py +0 -0
  246. {atomic_agents-2.6.0/atomic-forge/tools/tavily_search → atomic_agents-2.7.3/atomic-forge/tools/searxng_search}/.coveragerc +0 -0
  247. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/README.md +0 -0
  248. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/pyproject.toml +0 -0
  249. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/pytest.ini +0 -0
  250. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/requirements.txt +0 -0
  251. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/tests/test_searxng_search.py +0 -0
  252. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/searxng_search/tool/searxng_search.py +0 -0
  253. {atomic_agents-2.6.0/atomic-forge/tools/youtube_transcript_scraper → atomic_agents-2.7.3/atomic-forge/tools/tavily_search}/.coveragerc +0 -0
  254. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/tavily_search/README.md +0 -0
  255. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/tavily_search/pyproject.toml +0 -0
  256. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/tavily_search/requirements.txt +0 -0
  257. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/tavily_search/tests/test_tavily_seach.py +0 -0
  258. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/tavily_search/tool/tavily_search.py +0 -0
  259. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/README.md +0 -0
  260. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/pyproject.toml +0 -0
  261. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/requirements.txt +0 -0
  262. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/tests/conftest.py +0 -0
  263. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/tests/test_webpage_scraper.py +0 -0
  264. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/webpage_scraper/tool/webpage_scraper.py +0 -0
  265. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/youtube_transcript_scraper/README.md +0 -0
  266. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/youtube_transcript_scraper/pyproject.toml +0 -0
  267. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/youtube_transcript_scraper/requirements.txt +0 -0
  268. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/youtube_transcript_scraper/tests/test_youtube_transcript_scraper.py +0 -0
  269. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/atomic-forge/tools/youtube_transcript_scraper/tool/youtube_transcript_scraper.py +0 -0
  270. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/build_and_deploy.ps1 +0 -0
  271. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/.claude-plugin/plugin.json +0 -0
  272. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/CHANGELOG.md +0 -0
  273. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/LICENSE +0 -0
  274. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/README.md +0 -0
  275. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/agents/atomic-architect.md +0 -0
  276. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/agents/atomic-explorer.md +0 -0
  277. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/agents/atomic-reviewer.md +0 -0
  278. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/agents/schema-designer.md +0 -0
  279. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/commands/atomic-agent.md +0 -0
  280. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/commands/atomic-create.md +0 -0
  281. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/commands/atomic-tool.md +0 -0
  282. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-agents/SKILL.md +0 -0
  283. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-agents/examples/basic-agent.py +0 -0
  284. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-context/SKILL.md +0 -0
  285. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-context/examples/rag-context.py +0 -0
  286. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-prompts/SKILL.md +0 -0
  287. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-schemas/SKILL.md +0 -0
  288. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-schemas/examples/common-schemas.py +0 -0
  289. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-structure/SKILL.md +0 -0
  290. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-tools/SKILL.md +0 -0
  291. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/claude-plugin/atomic-agents/skills/atomic-tools/examples/api-tool.py +0 -0
  292. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/.nojekyll +0 -0
  293. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/Makefile +0 -0
  294. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/_static/logo.png +0 -0
  295. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/api/agents.md +0 -0
  296. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/api/context.md +0 -0
  297. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/api/index.md +0 -0
  298. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/api/utils.md +0 -0
  299. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/conf.py +0 -0
  300. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/contributing.md +0 -0
  301. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/examples/index.md +0 -0
  302. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/examples/mcp_agent.md +0 -0
  303. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/cookbook.md +0 -0
  304. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/deployment.md +0 -0
  305. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/error-handling.md +0 -0
  306. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/faq.md +0 -0
  307. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/hooks.md +0 -0
  308. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/index.md +0 -0
  309. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/logging.md +0 -0
  310. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/memory.md +0 -0
  311. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/orchestration.md +0 -0
  312. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/performance.md +0 -0
  313. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/quickstart.md +0 -0
  314. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/security.md +0 -0
  315. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/guides/testing.md +0 -0
  316. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/index.md +0 -0
  317. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/docs/make.bat +0 -0
  318. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/greptile.json +0 -0
  319. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/guides/DEV_GUIDE.md +0 -0
  320. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/pytest.ini +0 -0
  321. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/requirements.txt +0 -0
  322. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/scripts/generate_llms_files.py +0 -0
  323. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/scripts/sync_version.py +0 -0
  324. {atomic_agents-2.6.0 → atomic_agents-2.7.3}/setup.py +0 -0
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: release
3
+ description: Release a new version of atomic-agents to PyPI and GitHub. Use when the user asks to "release", "publish", "deploy", or "bump version" for atomic-agents.
4
+ allowed-tools: Read, Bash, Grep, Glob
5
+ ---
6
+
7
+ # Release Process for atomic-agents
8
+
9
+ ## Overview
10
+
11
+ This skill guides the release process for atomic-agents, including version bumping, PyPI publishing, and GitHub release creation.
12
+
13
+ ## Prerequisites
14
+
15
+ - Must be on `main` branch with clean working directory
16
+ - `.env` file must contain `PYPI_TOKEN` environment variable
17
+ - Must have push access to the repository
18
+
19
+ ## Release Types
20
+
21
+ | Type | When to Use | Example |
22
+ |------|-------------|---------|
23
+ | `major` | Breaking changes | 2.5.0 → 3.0.0 |
24
+ | `minor` | New features (backwards compatible) | 2.5.0 → 2.6.0 |
25
+ | `patch` | Bug fixes only | 2.5.0 → 2.5.1 |
26
+
27
+ ## Step-by-Step Process
28
+
29
+ ### 1. Prepare the Branch
30
+
31
+ ```bash
32
+ git checkout main
33
+ git pull
34
+ git status # Ensure clean working directory
35
+ ```
36
+
37
+ ### 2. Run Build and Deploy Script
38
+
39
+ **Important**: The script bumps versions, so if it fails partway through, reset to main before retrying.
40
+
41
+ ```powershell
42
+ powershell -ExecutionPolicy Bypass -File build_and_deploy.ps1 <major|minor|patch>
43
+ ```
44
+
45
+ This script will:
46
+ - Read current version from `pyproject.toml`
47
+ - Increment version based on release type
48
+ - Update `pyproject.toml` with new version
49
+ - Run `uv sync` to update dependencies
50
+ - Run `uv build` to create distribution packages
51
+ - Run `uv publish` to upload to PyPI
52
+
53
+ ### 3. If Script Fails - Reset and Retry
54
+
55
+ ```bash
56
+ git checkout main
57
+ git reset --hard origin/main
58
+ # Fix the issue, then run script again
59
+ ```
60
+
61
+ ### 4. Commit and Push Version Bump
62
+
63
+ ```bash
64
+ git add pyproject.toml uv.lock
65
+ git commit -m "Release vX.Y.Z"
66
+ git push
67
+ ```
68
+
69
+ ### 5. Create GitHub Release
70
+
71
+ ```bash
72
+ gh release create vX.Y.Z --title "vX.Y.Z" --notes "RELEASE_NOTES_HERE"
73
+ ```
74
+
75
+ ## Release Notes Template
76
+
77
+ ```markdown
78
+ ## What's New
79
+
80
+ ### Feature Name
81
+ Brief description of the feature.
82
+
83
+ #### Features
84
+ - Feature 1
85
+ - Feature 2
86
+
87
+ #### Improvements
88
+ - Improvement 1
89
+
90
+ #### Bug Fixes
91
+ - Fix 1
92
+
93
+ ### Full Changelog
94
+ https://github.com/BrainBlend-AI/atomic-agents/compare/vOLD...vNEW
95
+ ```
96
+
97
+ ## Checklist
98
+
99
+ - [ ] On main branch with clean working directory
100
+ - [ ] `.env` file has `PYPI_TOKEN`
101
+ - [ ] Determined correct release type (major/minor/patch)
102
+ - [ ] Build and deploy script completed successfully
103
+ - [ ] Version bump committed and pushed
104
+ - [ ] GitHub release created with release notes
@@ -153,6 +153,8 @@ debug.log
153
153
 
154
154
  # AI tools
155
155
  .claude
156
+ !.claude/skills/
157
+ !.claude/skills/**
156
158
  CLAUDE.md
157
159
  CLAUDE.local.md
158
160
  .serena
@@ -0,0 +1,20 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+
10
+ - repo: https://github.com/psf/black
11
+ rev: 24.10.0
12
+ hooks:
13
+ - id: black
14
+ args: [--line-length=127]
15
+
16
+ - repo: https://github.com/pycqa/flake8
17
+ rev: 7.1.1
18
+ hooks:
19
+ - id: flake8
20
+ args: [--max-line-length=127]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomic-agents
3
- Version: 2.6.0
3
+ Version: 2.7.3
4
4
  Summary: A versatile framework for creating and managing intelligent agents.
5
5
  Project-URL: Homepage, https://github.com/BrainBlend-AI/atomic-agents
6
6
  Project-URL: Repository, https://github.com/BrainBlend-AI/atomic-agents
@@ -97,64 +97,92 @@ class ChatHistory:
97
97
  either a single JSON string or a mixed array of JSON and multimodal objects.
98
98
 
99
99
  Note:
100
- This method supports multimodal content by keeping multimodal objects
101
- separate while generating cohesive JSON for text-based fields.
100
+ This method supports multimodal content at any nesting depth by
101
+ recursively extracting multimodal objects and using Pydantic's
102
+ model_dump_json(exclude=...) for proper serialization of remaining fields.
102
103
  """
103
104
  history = []
104
105
  for message in self.history:
105
106
  input_content = message.content
107
+ multimodal_objects, exclude_spec = self._extract_multimodal_info(input_content)
106
108
 
107
- # Check if content has any multimodal fields
108
- multimodal_objects = []
109
- has_multimodal = False
110
-
111
- # Extract multimodal content first
112
- for field_name, field in input_content.__class__.model_fields.items():
113
- field_value = getattr(input_content, field_name)
114
-
115
- if isinstance(field_value, list):
116
- for item in field_value:
117
- if isinstance(item, INSTRUCTOR_MULTIMODAL_TYPES):
118
- multimodal_objects.append(item)
119
- has_multimodal = True
120
- elif isinstance(field_value, INSTRUCTOR_MULTIMODAL_TYPES):
121
- multimodal_objects.append(field_value)
122
- has_multimodal = True
123
-
124
- if has_multimodal:
125
- # For multimodal content: create mixed array with JSON + multimodal objects
109
+ if multimodal_objects:
126
110
  processed_content = []
127
-
128
- # Add single cohesive JSON for all non-multimodal fields
129
- non_multimodal_data = {}
130
- for field_name, field in input_content.__class__.model_fields.items():
131
- field_value = getattr(input_content, field_name)
132
-
133
- if isinstance(field_value, list):
134
- # Only include non-multimodal items from lists
135
- non_multimodal_items = [
136
- item for item in field_value if not isinstance(item, INSTRUCTOR_MULTIMODAL_TYPES)
137
- ]
138
- if non_multimodal_items:
139
- non_multimodal_data[field_name] = non_multimodal_items
140
- elif not isinstance(field_value, INSTRUCTOR_MULTIMODAL_TYPES):
141
- non_multimodal_data[field_name] = field_value
142
-
143
- # Add single JSON string if there are non-multimodal fields
144
- if non_multimodal_data:
145
- processed_content.append(json.dumps(non_multimodal_data, ensure_ascii=False))
146
-
147
- # Add all multimodal objects
111
+ content_json = input_content.model_dump_json(exclude=exclude_spec)
112
+ if content_json and content_json != "{}":
113
+ processed_content.append(content_json)
148
114
  processed_content.extend(multimodal_objects)
149
-
150
115
  history.append({"role": message.role, "content": processed_content})
151
116
  else:
152
- # No multimodal content: generate single cohesive JSON string
153
117
  content_json = input_content.model_dump_json()
154
118
  history.append({"role": message.role, "content": content_json})
155
119
 
156
120
  return history
157
121
 
122
+ @staticmethod
123
+ def _extract_multimodal_info(obj):
124
+ """
125
+ Recursively extract multimodal objects and build a Pydantic-compatible exclude spec.
126
+
127
+ Walks the object tree to find all Instructor multimodal types (Image, Audio, PDF)
128
+ at any nesting depth, collecting them into a flat list and building an exclude
129
+ specification that can be passed to model_dump_json(exclude=...).
130
+
131
+ Args:
132
+ obj: The object to inspect (BaseIOSchema, list, dict, or primitive).
133
+
134
+ Returns:
135
+ tuple: (multimodal_objects, exclude_spec) where:
136
+ - multimodal_objects: flat list of all multimodal objects found
137
+ - exclude_spec: Pydantic exclude dict, True (exclude entirely), or None
138
+ """
139
+ if isinstance(obj, INSTRUCTOR_MULTIMODAL_TYPES):
140
+ return [obj], True
141
+
142
+ if hasattr(obj, "__class__") and hasattr(obj.__class__, "model_fields"):
143
+ all_objects = []
144
+ exclude = {}
145
+ for field_name in obj.__class__.model_fields:
146
+ if hasattr(obj, field_name):
147
+ field_value = getattr(obj, field_name)
148
+ objects, sub_exclude = ChatHistory._extract_multimodal_info(field_value)
149
+ if objects:
150
+ all_objects.extend(objects)
151
+ exclude[field_name] = sub_exclude
152
+ return all_objects, (exclude if exclude else None)
153
+
154
+ if isinstance(obj, (list, tuple)):
155
+ all_objects = []
156
+ exclude = {}
157
+ for i, item in enumerate(obj):
158
+ objects, sub_exclude = ChatHistory._extract_multimodal_info(item)
159
+ if objects:
160
+ all_objects.extend(objects)
161
+ exclude[i] = sub_exclude
162
+ if not all_objects:
163
+ return [], None
164
+ # If every item in the list is fully multimodal, exclude the entire field
165
+ if len(exclude) == len(obj) and all(v is True for v in exclude.values()):
166
+ return all_objects, True
167
+ return all_objects, exclude
168
+
169
+ if isinstance(obj, dict):
170
+ all_objects = []
171
+ exclude = {}
172
+ for k, v in obj.items():
173
+ objects, sub_exclude = ChatHistory._extract_multimodal_info(v)
174
+ if objects:
175
+ all_objects.extend(objects)
176
+ exclude[k] = sub_exclude
177
+ if not all_objects:
178
+ return [], None
179
+ # If every value in the dict is fully multimodal, exclude the entire field
180
+ if len(exclude) == len(obj) and all(v is True for v in exclude.values()):
181
+ return all_objects, True
182
+ return all_objects, exclude
183
+
184
+ return [], None
185
+
158
186
  def copy(self) -> "ChatHistory":
159
187
  """
160
188
  Creates a copy of the chat history.
@@ -147,12 +147,15 @@ class TokenCounter:
147
147
  raise TypeError(f"model must be a string, got {type(model).__name__}")
148
148
 
149
149
  try:
150
- from litellm import get_max_tokens
150
+ from litellm import get_model_info
151
151
  except ImportError as e:
152
152
  raise ImportError("litellm is required for token counting. " "Install it with: pip install litellm") from e
153
153
 
154
154
  try:
155
- return get_max_tokens(model)
155
+ info = get_model_info(model)
156
+ # Use max_input_tokens (context window) not max_tokens (output limit)
157
+ max_input = info.get("max_input_tokens")
158
+ return max_input if max_input is not None else info.get("max_tokens")
156
159
  except Exception as e:
157
160
  logger.warning(f"Could not determine max tokens for model '{model}': {e}")
158
161
  return None
@@ -207,9 +207,7 @@ async def test_typed_output_schema_with_structured_content_dict(monkeypatch):
207
207
  "required": ["results", "count"],
208
208
  }
209
209
  definitions = [
210
- MCPToolDefinition(
211
- name="SearchTool", description="Search tool", input_schema=input_schema, output_schema=output_schema
212
- )
210
+ MCPToolDefinition(name="SearchTool", description="Search tool", input_schema=input_schema, output_schema=output_schema)
213
211
  ]
214
212
  monkeypatch.setattr(MCPFactory, "_fetch_tool_definitions", lambda self: definitions)
215
213
  tools = fetch_mcp_tools("http://example.com", MCPTransportType.HTTP_STREAM)
@@ -218,9 +218,7 @@ class TestCreateModelFromSchema:
218
218
  },
219
219
  "required": ["results", "count"],
220
220
  }
221
- model = SchemaTransformer.create_model_from_schema(
222
- schema, "OutputModel", "my_tool", is_output_schema=True
223
- )
221
+ model = SchemaTransformer.create_model_from_schema(schema, "OutputModel", "my_tool", is_output_schema=True)
224
222
 
225
223
  # Output schema should NOT have tool_name field
226
224
  assert "tool_name" not in model.model_fields
@@ -243,9 +241,7 @@ class TestCreateModelFromSchema:
243
241
  },
244
242
  "required": ["query"],
245
243
  }
246
- model = SchemaTransformer.create_model_from_schema(
247
- schema, "InputModel", "my_tool", is_output_schema=False
248
- )
244
+ model = SchemaTransformer.create_model_from_schema(schema, "InputModel", "my_tool", is_output_schema=False)
249
245
 
250
246
  # Input schema SHOULD have tool_name field
251
247
  assert "tool_name" in model.model_fields
@@ -271,9 +267,7 @@ class TestCreateModelFromSchema:
271
267
 
272
268
  # Output schema for resource - should not have resource_name
273
269
  model = SchemaTransformer.create_model_from_schema(
274
- schema, "ResourceOutput", "my_resource",
275
- attribute_type=MCPAttributeType.RESOURCE,
276
- is_output_schema=True
270
+ schema, "ResourceOutput", "my_resource", attribute_type=MCPAttributeType.RESOURCE, is_output_schema=True
277
271
  )
278
272
 
279
273
  assert "resource_name" not in model.model_fields
@@ -538,3 +538,330 @@ def test_process_multimodal_paths_comprehensive():
538
538
  assert loaded_content.images[0].source == Path("test/sample.jpg")
539
539
  assert isinstance(loaded_content.pdfs[0].source, Path)
540
540
  assert loaded_content.pdfs[0].source == Path("test/doc.pdf")
541
+
542
+
543
+ def test_get_history_nested_pydantic_with_toplevel_multimodal(history):
544
+ """Issue #208: nested Pydantic model + top-level multimodal causes json.dumps TypeError"""
545
+
546
+ class ContextInfo(BaseIOSchema):
547
+ """Nested context info."""
548
+
549
+ label: str = Field(..., description="Label")
550
+ value: str = Field(..., description="Value")
551
+
552
+ class AgentInput(BaseIOSchema):
553
+ """Input with multimodal and nested schema."""
554
+
555
+ instruction: str = Field(..., description="Instruction text")
556
+ images: List[instructor.Image] = Field(..., description="Images")
557
+ context: ContextInfo = Field(..., description="Nested context")
558
+
559
+ mock_image = instructor.Image(source="test_url", media_type="image/jpeg", detail="low")
560
+ context = ContextInfo(label="example", value="nested")
561
+ content = AgentInput(instruction="Do something", images=[mock_image], context=context)
562
+
563
+ history.add_message("user", content)
564
+ result = history.get_history()
565
+
566
+ assert len(result) == 1
567
+ assert result[0]["role"] == "user"
568
+ assert isinstance(result[0]["content"], list)
569
+
570
+ json_part = json.loads(result[0]["content"][0])
571
+ assert json_part["instruction"] == "Do something"
572
+ assert json_part["context"] == {"label": "example", "value": "nested"}
573
+ assert "images" not in json_part
574
+ assert result[0]["content"][1] == mock_image
575
+
576
+
577
+ def test_get_history_deeply_nested_multimodal_only(history):
578
+ """Issue #141: multimodal inside nested schema with no top-level multimodal"""
579
+
580
+ class Document(BaseIOSchema):
581
+ """PDF document with owner."""
582
+
583
+ pdf: instructor.multimodal.PDF = Field(..., description="The PDF data")
584
+ owner: str = Field(..., description="The PDF owner")
585
+
586
+ class InputSchema(BaseIOSchema):
587
+ """A list of documents to analyze."""
588
+
589
+ documents: List[Document] = Field(..., description="List of documents")
590
+ instruction: str = Field(..., description="What to do")
591
+
592
+ mock_pdf = instructor.multimodal.PDF(source="test_pdf_url", media_type="application/pdf", detail="low")
593
+ content = InputSchema(
594
+ documents=[Document(pdf=mock_pdf, owner="Alice")],
595
+ instruction="Analyze these",
596
+ )
597
+
598
+ history.add_message("user", content)
599
+ result = history.get_history()
600
+
601
+ assert len(result) == 1
602
+ assert isinstance(result[0]["content"], list)
603
+
604
+ json_part = json.loads(result[0]["content"][0])
605
+ assert json_part["instruction"] == "Analyze these"
606
+ assert json_part["documents"] == [{"owner": "Alice"}]
607
+ assert result[0]["content"][1] == mock_pdf
608
+
609
+
610
+ def test_get_history_mixed_nested_and_toplevel_multimodal(history):
611
+ """Both nested and top-level multimodal content"""
612
+
613
+ class Attachment(BaseIOSchema):
614
+ """An attachment with an image."""
615
+
616
+ image: instructor.Image = Field(..., description="Attached image")
617
+ caption: str = Field(..., description="Caption")
618
+
619
+ class MessageInput(BaseIOSchema):
620
+ """Message with both nested and top-level multimodal."""
621
+
622
+ text: str = Field(..., description="Message text")
623
+ inline_image: instructor.Image = Field(..., description="Inline image")
624
+ attachment: Attachment = Field(..., description="An attachment")
625
+
626
+ img1 = instructor.Image(source="inline_url", media_type="image/jpeg", detail="low")
627
+ img2 = instructor.Image(source="attached_url", media_type="image/png", detail="low")
628
+ content = MessageInput(
629
+ text="Check this out",
630
+ inline_image=img1,
631
+ attachment=Attachment(image=img2, caption="See here"),
632
+ )
633
+
634
+ history.add_message("user", content)
635
+ result = history.get_history()
636
+
637
+ assert len(result) == 1
638
+ assert isinstance(result[0]["content"], list)
639
+
640
+ assert len(result[0]["content"]) == 3 # JSON + 2 images
641
+ json_part = json.loads(result[0]["content"][0])
642
+ assert json_part["text"] == "Check this out"
643
+ assert json_part["attachment"] == {"caption": "See here"}
644
+ assert "inline_image" not in json_part
645
+ assert img1 in result[0]["content"]
646
+ assert img2 in result[0]["content"]
647
+
648
+
649
+ def test_get_history_list_of_nested_schemas_with_multimodal(history):
650
+ """Multiple nested schemas each containing multimodal objects"""
651
+
652
+ class Document(BaseIOSchema):
653
+ """A document with PDF."""
654
+
655
+ pdf: instructor.multimodal.PDF = Field(..., description="The PDF")
656
+ title: str = Field(..., description="Document title")
657
+
658
+ class BatchInput(BaseIOSchema):
659
+ """Batch of documents."""
660
+
661
+ documents: List[Document] = Field(..., description="Documents to process")
662
+
663
+ pdf1 = instructor.multimodal.PDF(source="doc1.pdf", media_type="application/pdf", detail="low")
664
+ pdf2 = instructor.multimodal.PDF(source="doc2.pdf", media_type="application/pdf", detail="low")
665
+ content = BatchInput(
666
+ documents=[
667
+ Document(pdf=pdf1, title="First"),
668
+ Document(pdf=pdf2, title="Second"),
669
+ ]
670
+ )
671
+
672
+ history.add_message("user", content)
673
+ result = history.get_history()
674
+
675
+ assert len(result) == 1
676
+ assert isinstance(result[0]["content"], list)
677
+
678
+ assert len(result[0]["content"]) == 3 # JSON + 2 PDFs
679
+ json_part = json.loads(result[0]["content"][0])
680
+ assert json_part["documents"] == [{"title": "First"}, {"title": "Second"}]
681
+ assert pdf1 in result[0]["content"]
682
+ assert pdf2 in result[0]["content"]
683
+
684
+
685
+ def test_get_history_only_multimodal_fields(history):
686
+ """Schema where ALL fields are multimodal - JSON should be omitted"""
687
+
688
+ class ImagesOnly(BaseIOSchema):
689
+ """Only images."""
690
+
691
+ images: List[instructor.Image] = Field(..., description="Images")
692
+
693
+ img1 = instructor.Image(source="url1", media_type="image/jpeg", detail="low")
694
+ img2 = instructor.Image(source="url2", media_type="image/jpeg", detail="low")
695
+ content = ImagesOnly(images=[img1, img2])
696
+
697
+ history.add_message("user", content)
698
+ result = history.get_history()
699
+
700
+ assert len(result) == 1
701
+ assert isinstance(result[0]["content"], list)
702
+ # No JSON string should be present since all fields are multimodal
703
+ assert len(result[0]["content"]) == 2
704
+ assert all(not isinstance(item, str) for item in result[0]["content"])
705
+ assert img1 in result[0]["content"]
706
+ assert img2 in result[0]["content"]
707
+
708
+
709
+ def test_get_history_no_multimodal_unchanged(history):
710
+ """Non-multimodal schemas should work exactly as before"""
711
+
712
+ class SimpleInput(BaseIOSchema):
713
+ """Simple input."""
714
+
715
+ text: str = Field(..., description="Text")
716
+ count: int = Field(..., description="Count")
717
+
718
+ content = SimpleInput(text="hello", count=42)
719
+ history.add_message("user", content)
720
+ result = history.get_history()
721
+
722
+ assert len(result) == 1
723
+ assert result[0]["role"] == "user"
724
+ assert isinstance(result[0]["content"], str)
725
+ assert json.loads(result[0]["content"]) == {"text": "hello", "count": 42}
726
+
727
+
728
+ # ---------------------------------------------------------------------------
729
+ # Direct unit tests for _extract_multimodal_info
730
+ # ---------------------------------------------------------------------------
731
+
732
+
733
+ def test_extract_multimodal_info_plain_schema():
734
+ """No multimodal content returns empty list and None exclude spec"""
735
+
736
+ class Plain(BaseIOSchema):
737
+ """Plain schema."""
738
+
739
+ text: str = Field(..., description="Text")
740
+
741
+ objs, spec = ChatHistory._extract_multimodal_info(Plain(text="hello"))
742
+ assert objs == []
743
+ assert spec is None
744
+
745
+
746
+ def test_extract_multimodal_info_toplevel_image():
747
+ """Top-level Image returns the object and True exclude spec"""
748
+ img = instructor.Image(source="url", media_type="image/jpeg", detail="low")
749
+ objs, spec = ChatHistory._extract_multimodal_info(img)
750
+ assert objs == [img]
751
+ assert spec is True
752
+
753
+
754
+ def test_extract_multimodal_info_nested_schema():
755
+ """Nested schema with multimodal returns correct exclude spec shape"""
756
+
757
+ class Inner(BaseIOSchema):
758
+ """Inner schema."""
759
+
760
+ image: instructor.Image = Field(..., description="Image")
761
+ label: str = Field(..., description="Label")
762
+
763
+ class Outer(BaseIOSchema):
764
+ """Outer schema."""
765
+
766
+ inner: Inner = Field(..., description="Inner")
767
+ text: str = Field(..., description="Text")
768
+
769
+ img = instructor.Image(source="url", media_type="image/jpeg", detail="low")
770
+ objs, spec = ChatHistory._extract_multimodal_info(Outer(inner=Inner(image=img, label="test"), text="hello"))
771
+
772
+ assert objs == [img]
773
+ # Exclude spec should be {"inner": {"image": True}}
774
+ assert spec == {"inner": {"image": True}}
775
+
776
+
777
+ def test_extract_multimodal_info_list_all_multimodal():
778
+ """List where every item is multimodal collapses to True"""
779
+
780
+ class Schema(BaseIOSchema):
781
+ """Schema with all-multimodal list."""
782
+
783
+ images: List[instructor.Image] = Field(..., description="Images")
784
+
785
+ img1 = instructor.Image(source="url1", media_type="image/jpeg", detail="low")
786
+ img2 = instructor.Image(source="url2", media_type="image/jpeg", detail="low")
787
+ objs, spec = ChatHistory._extract_multimodal_info(Schema(images=[img1, img2]))
788
+
789
+ assert objs == [img1, img2]
790
+ # All items multimodal → field excluded entirely
791
+ assert spec == {"images": True}
792
+
793
+
794
+ def test_extract_multimodal_info_list_partial_multimodal():
795
+ """List with mixed content returns index-based exclude spec"""
796
+
797
+ class Schema(BaseIOSchema):
798
+ """Schema with mixed list."""
799
+
800
+ items: List[Union[str, instructor.Image]] = Field(..., description="Items")
801
+
802
+ img = instructor.Image(source="url", media_type="image/jpeg", detail="low")
803
+ objs, spec = ChatHistory._extract_multimodal_info(Schema(items=["text", img]))
804
+
805
+ assert objs == [img]
806
+ # Only index 1 is multimodal
807
+ assert spec == {"items": {1: True}}
808
+
809
+
810
+ def test_extract_multimodal_info_tuple_support():
811
+ """Tuples are handled identically to lists"""
812
+ img = instructor.Image(source="url", media_type="image/jpeg", detail="low")
813
+ objs, spec = ChatHistory._extract_multimodal_info((img, "text"))
814
+ assert objs == [img]
815
+ assert spec == {0: True}
816
+
817
+
818
+ def test_extract_multimodal_info_dict_with_multimodal():
819
+ """Dict values containing multimodal objects return correct exclude spec"""
820
+ img = instructor.Image(source="url", media_type="image/jpeg", detail="low")
821
+ objs, spec = ChatHistory._extract_multimodal_info({"key1": img, "key2": "text"})
822
+
823
+ assert objs == [img]
824
+ assert spec == {"key1": True}
825
+
826
+
827
+ def test_extract_multimodal_info_dict_all_multimodal():
828
+ """Dict where all values are multimodal collapses to True"""
829
+ img1 = instructor.Image(source="url1", media_type="image/jpeg", detail="low")
830
+ img2 = instructor.Image(source="url2", media_type="image/jpeg", detail="low")
831
+ objs, spec = ChatHistory._extract_multimodal_info({"a": img1, "b": img2})
832
+
833
+ assert objs == [img1, img2]
834
+ assert spec is True
835
+
836
+
837
+ def test_extract_multimodal_info_dict_no_multimodal():
838
+ """Dict with no multimodal returns empty list and None"""
839
+ objs, spec = ChatHistory._extract_multimodal_info({"key1": "text", "key2": 42})
840
+ assert objs == []
841
+ assert spec is None
842
+
843
+
844
+ def test_get_history_dict_of_images(history):
845
+ """Dict[str, Image] field exercises the dict code path in get_history"""
846
+
847
+ class DictImageSchema(BaseIOSchema):
848
+ """Schema with dict of images."""
849
+
850
+ image_map: Dict[str, instructor.Image] = Field(..., description="Named images")
851
+ note: str = Field(..., description="A note")
852
+
853
+ img_a = instructor.Image(source="url_a", media_type="image/jpeg", detail="low")
854
+ img_b = instructor.Image(source="url_b", media_type="image/jpeg", detail="low")
855
+ content = DictImageSchema(image_map={"front": img_a, "back": img_b}, note="Two views")
856
+
857
+ history.add_message("user", content)
858
+ result = history.get_history()
859
+
860
+ assert len(result) == 1
861
+ assert isinstance(result[0]["content"], list)
862
+ assert len(result[0]["content"]) == 3 # JSON + 2 images
863
+ json_part = json.loads(result[0]["content"][0])
864
+ assert json_part["note"] == "Two views"
865
+ assert "image_map" not in json_part
866
+ assert img_a in result[0]["content"]
867
+ assert img_b in result[0]["content"]