ag2 0.10.2__py3-none-any.whl

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 (423) hide show
  1. ag2-0.10.2.dist-info/METADATA +819 -0
  2. ag2-0.10.2.dist-info/RECORD +423 -0
  3. ag2-0.10.2.dist-info/WHEEL +4 -0
  4. ag2-0.10.2.dist-info/licenses/LICENSE +201 -0
  5. ag2-0.10.2.dist-info/licenses/NOTICE.md +19 -0
  6. autogen/__init__.py +88 -0
  7. autogen/_website/__init__.py +3 -0
  8. autogen/_website/generate_api_references.py +426 -0
  9. autogen/_website/generate_mkdocs.py +1216 -0
  10. autogen/_website/notebook_processor.py +475 -0
  11. autogen/_website/process_notebooks.py +656 -0
  12. autogen/_website/utils.py +413 -0
  13. autogen/a2a/__init__.py +36 -0
  14. autogen/a2a/agent_executor.py +86 -0
  15. autogen/a2a/client.py +357 -0
  16. autogen/a2a/errors.py +18 -0
  17. autogen/a2a/httpx_client_factory.py +79 -0
  18. autogen/a2a/server.py +221 -0
  19. autogen/a2a/utils.py +207 -0
  20. autogen/agentchat/__init__.py +47 -0
  21. autogen/agentchat/agent.py +180 -0
  22. autogen/agentchat/assistant_agent.py +86 -0
  23. autogen/agentchat/chat.py +325 -0
  24. autogen/agentchat/contrib/__init__.py +5 -0
  25. autogen/agentchat/contrib/agent_eval/README.md +7 -0
  26. autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
  27. autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
  28. autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
  29. autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
  30. autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
  31. autogen/agentchat/contrib/agent_eval/task.py +42 -0
  32. autogen/agentchat/contrib/agent_optimizer.py +432 -0
  33. autogen/agentchat/contrib/capabilities/__init__.py +5 -0
  34. autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
  35. autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
  36. autogen/agentchat/contrib/capabilities/teachability.py +393 -0
  37. autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
  38. autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
  39. autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
  40. autogen/agentchat/contrib/capabilities/transforms.py +578 -0
  41. autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
  42. autogen/agentchat/contrib/capabilities/vision_capability.py +215 -0
  43. autogen/agentchat/contrib/captainagent/__init__.py +9 -0
  44. autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
  45. autogen/agentchat/contrib/captainagent/captainagent.py +514 -0
  46. autogen/agentchat/contrib/captainagent/tool_retriever.py +334 -0
  47. autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
  48. autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
  49. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
  50. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
  51. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
  52. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
  53. autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
  54. autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
  55. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
  56. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
  57. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
  58. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
  59. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
  60. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
  61. autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
  62. autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
  63. autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
  64. autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
  65. autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
  66. autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
  67. autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
  68. autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
  69. autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
  70. autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
  71. autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
  72. autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
  73. autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
  74. autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
  75. autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
  76. autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
  77. autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
  78. autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
  79. autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
  80. autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
  81. autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
  82. autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
  83. autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
  84. autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
  85. autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
  86. autogen/agentchat/contrib/graph_rag/document.py +29 -0
  87. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +167 -0
  88. autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
  89. autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
  90. autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
  91. autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +263 -0
  92. autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
  93. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
  94. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
  95. autogen/agentchat/contrib/img_utils.py +397 -0
  96. autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
  97. autogen/agentchat/contrib/llava_agent.py +189 -0
  98. autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
  99. autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
  100. autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +325 -0
  101. autogen/agentchat/contrib/rag/__init__.py +10 -0
  102. autogen/agentchat/contrib/rag/chromadb_query_engine.py +268 -0
  103. autogen/agentchat/contrib/rag/llamaindex_query_engine.py +195 -0
  104. autogen/agentchat/contrib/rag/mongodb_query_engine.py +319 -0
  105. autogen/agentchat/contrib/rag/query_engine.py +76 -0
  106. autogen/agentchat/contrib/retrieve_assistant_agent.py +59 -0
  107. autogen/agentchat/contrib/retrieve_user_proxy_agent.py +704 -0
  108. autogen/agentchat/contrib/society_of_mind_agent.py +200 -0
  109. autogen/agentchat/contrib/swarm_agent.py +1404 -0
  110. autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
  111. autogen/agentchat/contrib/vectordb/__init__.py +5 -0
  112. autogen/agentchat/contrib/vectordb/base.py +224 -0
  113. autogen/agentchat/contrib/vectordb/chromadb.py +316 -0
  114. autogen/agentchat/contrib/vectordb/couchbase.py +405 -0
  115. autogen/agentchat/contrib/vectordb/mongodb.py +551 -0
  116. autogen/agentchat/contrib/vectordb/pgvectordb.py +927 -0
  117. autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
  118. autogen/agentchat/contrib/vectordb/utils.py +126 -0
  119. autogen/agentchat/contrib/web_surfer.py +304 -0
  120. autogen/agentchat/conversable_agent.py +4307 -0
  121. autogen/agentchat/group/__init__.py +67 -0
  122. autogen/agentchat/group/available_condition.py +91 -0
  123. autogen/agentchat/group/context_condition.py +77 -0
  124. autogen/agentchat/group/context_expression.py +238 -0
  125. autogen/agentchat/group/context_str.py +39 -0
  126. autogen/agentchat/group/context_variables.py +182 -0
  127. autogen/agentchat/group/events/transition_events.py +111 -0
  128. autogen/agentchat/group/group_tool_executor.py +324 -0
  129. autogen/agentchat/group/group_utils.py +659 -0
  130. autogen/agentchat/group/guardrails.py +179 -0
  131. autogen/agentchat/group/handoffs.py +303 -0
  132. autogen/agentchat/group/llm_condition.py +93 -0
  133. autogen/agentchat/group/multi_agent_chat.py +291 -0
  134. autogen/agentchat/group/on_condition.py +55 -0
  135. autogen/agentchat/group/on_context_condition.py +51 -0
  136. autogen/agentchat/group/patterns/__init__.py +18 -0
  137. autogen/agentchat/group/patterns/auto.py +160 -0
  138. autogen/agentchat/group/patterns/manual.py +177 -0
  139. autogen/agentchat/group/patterns/pattern.py +295 -0
  140. autogen/agentchat/group/patterns/random.py +106 -0
  141. autogen/agentchat/group/patterns/round_robin.py +117 -0
  142. autogen/agentchat/group/reply_result.py +24 -0
  143. autogen/agentchat/group/safeguards/__init__.py +21 -0
  144. autogen/agentchat/group/safeguards/api.py +241 -0
  145. autogen/agentchat/group/safeguards/enforcer.py +1158 -0
  146. autogen/agentchat/group/safeguards/events.py +140 -0
  147. autogen/agentchat/group/safeguards/validator.py +435 -0
  148. autogen/agentchat/group/speaker_selection_result.py +41 -0
  149. autogen/agentchat/group/targets/__init__.py +4 -0
  150. autogen/agentchat/group/targets/function_target.py +245 -0
  151. autogen/agentchat/group/targets/group_chat_target.py +133 -0
  152. autogen/agentchat/group/targets/group_manager_target.py +151 -0
  153. autogen/agentchat/group/targets/transition_target.py +424 -0
  154. autogen/agentchat/group/targets/transition_utils.py +6 -0
  155. autogen/agentchat/groupchat.py +1832 -0
  156. autogen/agentchat/realtime/__init__.py +3 -0
  157. autogen/agentchat/realtime/experimental/__init__.py +20 -0
  158. autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
  159. autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
  160. autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
  161. autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
  162. autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
  163. autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
  164. autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
  165. autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
  166. autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
  167. autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
  168. autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
  169. autogen/agentchat/realtime/experimental/clients/realtime_client.py +191 -0
  170. autogen/agentchat/realtime/experimental/function_observer.py +84 -0
  171. autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
  172. autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
  173. autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
  174. autogen/agentchat/realtime/experimental/realtime_swarm.py +533 -0
  175. autogen/agentchat/realtime/experimental/websockets.py +21 -0
  176. autogen/agentchat/realtime_agent/__init__.py +21 -0
  177. autogen/agentchat/user_proxy_agent.py +114 -0
  178. autogen/agentchat/utils.py +206 -0
  179. autogen/agents/__init__.py +3 -0
  180. autogen/agents/contrib/__init__.py +10 -0
  181. autogen/agents/contrib/time/__init__.py +8 -0
  182. autogen/agents/contrib/time/time_reply_agent.py +74 -0
  183. autogen/agents/contrib/time/time_tool_agent.py +52 -0
  184. autogen/agents/experimental/__init__.py +27 -0
  185. autogen/agents/experimental/deep_research/__init__.py +7 -0
  186. autogen/agents/experimental/deep_research/deep_research.py +52 -0
  187. autogen/agents/experimental/discord/__init__.py +7 -0
  188. autogen/agents/experimental/discord/discord.py +66 -0
  189. autogen/agents/experimental/document_agent/__init__.py +19 -0
  190. autogen/agents/experimental/document_agent/chroma_query_engine.py +301 -0
  191. autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +113 -0
  192. autogen/agents/experimental/document_agent/document_agent.py +643 -0
  193. autogen/agents/experimental/document_agent/document_conditions.py +50 -0
  194. autogen/agents/experimental/document_agent/document_utils.py +376 -0
  195. autogen/agents/experimental/document_agent/inmemory_query_engine.py +214 -0
  196. autogen/agents/experimental/document_agent/parser_utils.py +134 -0
  197. autogen/agents/experimental/document_agent/url_utils.py +417 -0
  198. autogen/agents/experimental/reasoning/__init__.py +7 -0
  199. autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
  200. autogen/agents/experimental/slack/__init__.py +7 -0
  201. autogen/agents/experimental/slack/slack.py +73 -0
  202. autogen/agents/experimental/telegram/__init__.py +7 -0
  203. autogen/agents/experimental/telegram/telegram.py +76 -0
  204. autogen/agents/experimental/websurfer/__init__.py +7 -0
  205. autogen/agents/experimental/websurfer/websurfer.py +70 -0
  206. autogen/agents/experimental/wikipedia/__init__.py +7 -0
  207. autogen/agents/experimental/wikipedia/wikipedia.py +88 -0
  208. autogen/browser_utils.py +309 -0
  209. autogen/cache/__init__.py +10 -0
  210. autogen/cache/abstract_cache_base.py +71 -0
  211. autogen/cache/cache.py +203 -0
  212. autogen/cache/cache_factory.py +88 -0
  213. autogen/cache/cosmos_db_cache.py +144 -0
  214. autogen/cache/disk_cache.py +97 -0
  215. autogen/cache/in_memory_cache.py +54 -0
  216. autogen/cache/redis_cache.py +119 -0
  217. autogen/code_utils.py +598 -0
  218. autogen/coding/__init__.py +30 -0
  219. autogen/coding/base.py +120 -0
  220. autogen/coding/docker_commandline_code_executor.py +283 -0
  221. autogen/coding/factory.py +56 -0
  222. autogen/coding/func_with_reqs.py +203 -0
  223. autogen/coding/jupyter/__init__.py +23 -0
  224. autogen/coding/jupyter/base.py +36 -0
  225. autogen/coding/jupyter/docker_jupyter_server.py +160 -0
  226. autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
  227. autogen/coding/jupyter/import_utils.py +82 -0
  228. autogen/coding/jupyter/jupyter_client.py +224 -0
  229. autogen/coding/jupyter/jupyter_code_executor.py +154 -0
  230. autogen/coding/jupyter/local_jupyter_server.py +164 -0
  231. autogen/coding/local_commandline_code_executor.py +341 -0
  232. autogen/coding/markdown_code_extractor.py +44 -0
  233. autogen/coding/utils.py +55 -0
  234. autogen/coding/yepcode_code_executor.py +197 -0
  235. autogen/doc_utils.py +35 -0
  236. autogen/environments/__init__.py +10 -0
  237. autogen/environments/docker_python_environment.py +365 -0
  238. autogen/environments/python_environment.py +125 -0
  239. autogen/environments/system_python_environment.py +85 -0
  240. autogen/environments/venv_python_environment.py +220 -0
  241. autogen/environments/working_directory.py +74 -0
  242. autogen/events/__init__.py +7 -0
  243. autogen/events/agent_events.py +1016 -0
  244. autogen/events/base_event.py +100 -0
  245. autogen/events/client_events.py +168 -0
  246. autogen/events/helpers.py +44 -0
  247. autogen/events/print_event.py +45 -0
  248. autogen/exception_utils.py +73 -0
  249. autogen/extensions/__init__.py +5 -0
  250. autogen/fast_depends/__init__.py +16 -0
  251. autogen/fast_depends/_compat.py +75 -0
  252. autogen/fast_depends/core/__init__.py +14 -0
  253. autogen/fast_depends/core/build.py +206 -0
  254. autogen/fast_depends/core/model.py +527 -0
  255. autogen/fast_depends/dependencies/__init__.py +15 -0
  256. autogen/fast_depends/dependencies/model.py +30 -0
  257. autogen/fast_depends/dependencies/provider.py +40 -0
  258. autogen/fast_depends/library/__init__.py +10 -0
  259. autogen/fast_depends/library/model.py +46 -0
  260. autogen/fast_depends/py.typed +6 -0
  261. autogen/fast_depends/schema.py +66 -0
  262. autogen/fast_depends/use.py +272 -0
  263. autogen/fast_depends/utils.py +177 -0
  264. autogen/formatting_utils.py +83 -0
  265. autogen/function_utils.py +13 -0
  266. autogen/graph_utils.py +173 -0
  267. autogen/import_utils.py +539 -0
  268. autogen/interop/__init__.py +22 -0
  269. autogen/interop/crewai/__init__.py +7 -0
  270. autogen/interop/crewai/crewai.py +88 -0
  271. autogen/interop/interoperability.py +71 -0
  272. autogen/interop/interoperable.py +46 -0
  273. autogen/interop/langchain/__init__.py +8 -0
  274. autogen/interop/langchain/langchain_chat_model_factory.py +156 -0
  275. autogen/interop/langchain/langchain_tool.py +78 -0
  276. autogen/interop/litellm/__init__.py +7 -0
  277. autogen/interop/litellm/litellm_config_factory.py +178 -0
  278. autogen/interop/pydantic_ai/__init__.py +7 -0
  279. autogen/interop/pydantic_ai/pydantic_ai.py +172 -0
  280. autogen/interop/registry.py +70 -0
  281. autogen/io/__init__.py +15 -0
  282. autogen/io/base.py +151 -0
  283. autogen/io/console.py +56 -0
  284. autogen/io/processors/__init__.py +12 -0
  285. autogen/io/processors/base.py +21 -0
  286. autogen/io/processors/console_event_processor.py +61 -0
  287. autogen/io/run_response.py +294 -0
  288. autogen/io/thread_io_stream.py +63 -0
  289. autogen/io/websockets.py +214 -0
  290. autogen/json_utils.py +42 -0
  291. autogen/llm_clients/MIGRATION_TO_V2.md +782 -0
  292. autogen/llm_clients/__init__.py +77 -0
  293. autogen/llm_clients/client_v2.py +122 -0
  294. autogen/llm_clients/models/__init__.py +55 -0
  295. autogen/llm_clients/models/content_blocks.py +389 -0
  296. autogen/llm_clients/models/unified_message.py +145 -0
  297. autogen/llm_clients/models/unified_response.py +83 -0
  298. autogen/llm_clients/openai_completions_client.py +444 -0
  299. autogen/llm_config/__init__.py +11 -0
  300. autogen/llm_config/client.py +59 -0
  301. autogen/llm_config/config.py +461 -0
  302. autogen/llm_config/entry.py +169 -0
  303. autogen/llm_config/types.py +37 -0
  304. autogen/llm_config/utils.py +223 -0
  305. autogen/logger/__init__.py +11 -0
  306. autogen/logger/base_logger.py +129 -0
  307. autogen/logger/file_logger.py +262 -0
  308. autogen/logger/logger_factory.py +42 -0
  309. autogen/logger/logger_utils.py +57 -0
  310. autogen/logger/sqlite_logger.py +524 -0
  311. autogen/math_utils.py +338 -0
  312. autogen/mcp/__init__.py +7 -0
  313. autogen/mcp/__main__.py +78 -0
  314. autogen/mcp/helpers.py +45 -0
  315. autogen/mcp/mcp_client.py +349 -0
  316. autogen/mcp/mcp_proxy/__init__.py +19 -0
  317. autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +62 -0
  318. autogen/mcp/mcp_proxy/mcp_proxy.py +577 -0
  319. autogen/mcp/mcp_proxy/operation_grouping.py +166 -0
  320. autogen/mcp/mcp_proxy/operation_renaming.py +110 -0
  321. autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py +98 -0
  322. autogen/mcp/mcp_proxy/security.py +399 -0
  323. autogen/mcp/mcp_proxy/security_schema_visitor.py +37 -0
  324. autogen/messages/__init__.py +7 -0
  325. autogen/messages/agent_messages.py +946 -0
  326. autogen/messages/base_message.py +108 -0
  327. autogen/messages/client_messages.py +172 -0
  328. autogen/messages/print_message.py +48 -0
  329. autogen/oai/__init__.py +61 -0
  330. autogen/oai/anthropic.py +1516 -0
  331. autogen/oai/bedrock.py +800 -0
  332. autogen/oai/cerebras.py +302 -0
  333. autogen/oai/client.py +1658 -0
  334. autogen/oai/client_utils.py +196 -0
  335. autogen/oai/cohere.py +494 -0
  336. autogen/oai/gemini.py +1045 -0
  337. autogen/oai/gemini_types.py +156 -0
  338. autogen/oai/groq.py +319 -0
  339. autogen/oai/mistral.py +311 -0
  340. autogen/oai/oai_models/__init__.py +23 -0
  341. autogen/oai/oai_models/_models.py +16 -0
  342. autogen/oai/oai_models/chat_completion.py +86 -0
  343. autogen/oai/oai_models/chat_completion_audio.py +32 -0
  344. autogen/oai/oai_models/chat_completion_message.py +97 -0
  345. autogen/oai/oai_models/chat_completion_message_tool_call.py +60 -0
  346. autogen/oai/oai_models/chat_completion_token_logprob.py +62 -0
  347. autogen/oai/oai_models/completion_usage.py +59 -0
  348. autogen/oai/ollama.py +657 -0
  349. autogen/oai/openai_responses.py +451 -0
  350. autogen/oai/openai_utils.py +897 -0
  351. autogen/oai/together.py +387 -0
  352. autogen/remote/__init__.py +18 -0
  353. autogen/remote/agent.py +199 -0
  354. autogen/remote/agent_service.py +197 -0
  355. autogen/remote/errors.py +17 -0
  356. autogen/remote/httpx_client_factory.py +131 -0
  357. autogen/remote/protocol.py +37 -0
  358. autogen/remote/retry.py +102 -0
  359. autogen/remote/runtime.py +96 -0
  360. autogen/retrieve_utils.py +490 -0
  361. autogen/runtime_logging.py +161 -0
  362. autogen/testing/__init__.py +12 -0
  363. autogen/testing/messages.py +45 -0
  364. autogen/testing/test_agent.py +111 -0
  365. autogen/token_count_utils.py +280 -0
  366. autogen/tools/__init__.py +20 -0
  367. autogen/tools/contrib/__init__.py +9 -0
  368. autogen/tools/contrib/time/__init__.py +7 -0
  369. autogen/tools/contrib/time/time.py +40 -0
  370. autogen/tools/dependency_injection.py +249 -0
  371. autogen/tools/experimental/__init__.py +54 -0
  372. autogen/tools/experimental/browser_use/__init__.py +7 -0
  373. autogen/tools/experimental/browser_use/browser_use.py +154 -0
  374. autogen/tools/experimental/code_execution/__init__.py +7 -0
  375. autogen/tools/experimental/code_execution/python_code_execution.py +86 -0
  376. autogen/tools/experimental/crawl4ai/__init__.py +7 -0
  377. autogen/tools/experimental/crawl4ai/crawl4ai.py +150 -0
  378. autogen/tools/experimental/deep_research/__init__.py +7 -0
  379. autogen/tools/experimental/deep_research/deep_research.py +329 -0
  380. autogen/tools/experimental/duckduckgo/__init__.py +7 -0
  381. autogen/tools/experimental/duckduckgo/duckduckgo_search.py +103 -0
  382. autogen/tools/experimental/firecrawl/__init__.py +7 -0
  383. autogen/tools/experimental/firecrawl/firecrawl_tool.py +836 -0
  384. autogen/tools/experimental/google/__init__.py +14 -0
  385. autogen/tools/experimental/google/authentication/__init__.py +11 -0
  386. autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
  387. autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
  388. autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
  389. autogen/tools/experimental/google/drive/__init__.py +9 -0
  390. autogen/tools/experimental/google/drive/drive_functions.py +124 -0
  391. autogen/tools/experimental/google/drive/toolkit.py +88 -0
  392. autogen/tools/experimental/google/model.py +17 -0
  393. autogen/tools/experimental/google/toolkit_protocol.py +19 -0
  394. autogen/tools/experimental/google_search/__init__.py +8 -0
  395. autogen/tools/experimental/google_search/google_search.py +93 -0
  396. autogen/tools/experimental/google_search/youtube_search.py +181 -0
  397. autogen/tools/experimental/messageplatform/__init__.py +17 -0
  398. autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
  399. autogen/tools/experimental/messageplatform/discord/discord.py +284 -0
  400. autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
  401. autogen/tools/experimental/messageplatform/slack/slack.py +385 -0
  402. autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
  403. autogen/tools/experimental/messageplatform/telegram/telegram.py +271 -0
  404. autogen/tools/experimental/perplexity/__init__.py +7 -0
  405. autogen/tools/experimental/perplexity/perplexity_search.py +249 -0
  406. autogen/tools/experimental/reliable/__init__.py +10 -0
  407. autogen/tools/experimental/reliable/reliable.py +1311 -0
  408. autogen/tools/experimental/searxng/__init__.py +7 -0
  409. autogen/tools/experimental/searxng/searxng_search.py +142 -0
  410. autogen/tools/experimental/tavily/__init__.py +7 -0
  411. autogen/tools/experimental/tavily/tavily_search.py +176 -0
  412. autogen/tools/experimental/web_search_preview/__init__.py +7 -0
  413. autogen/tools/experimental/web_search_preview/web_search_preview.py +120 -0
  414. autogen/tools/experimental/wikipedia/__init__.py +7 -0
  415. autogen/tools/experimental/wikipedia/wikipedia.py +284 -0
  416. autogen/tools/function_utils.py +412 -0
  417. autogen/tools/tool.py +188 -0
  418. autogen/tools/toolkit.py +86 -0
  419. autogen/types.py +29 -0
  420. autogen/version.py +7 -0
  421. templates/client_template/main.jinja2 +72 -0
  422. templates/config_template/config.jinja2 +7 -0
  423. templates/main.jinja2 +61 -0
@@ -0,0 +1,1516 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ # Portions derived from https://github.com/microsoft/autogen are under the MIT License.
6
+ # SPDX-License-Identifier: MIT
7
+ """Create an OpenAI-compatible client for the Anthropic API.
8
+
9
+ Example usage:
10
+ Install the `anthropic` package by running `pip install --upgrade anthropic`.
11
+ - https://docs.anthropic.com/en/docs/quickstart-guide
12
+
13
+ ```python
14
+ import autogen
15
+
16
+ config_list = [
17
+ {
18
+ "model": "claude-3-sonnet-20240229",
19
+ "api_key": os.getenv("ANTHROPIC_API_KEY"),
20
+ "api_type": "anthropic",
21
+ }
22
+ ]
23
+
24
+ assistant = autogen.AssistantAgent("assistant", llm_config={"config_list": config_list})
25
+ ```
26
+
27
+ Example usage for Anthropic Bedrock:
28
+
29
+ Install the `anthropic` package by running `pip install --upgrade anthropic`.
30
+ - https://docs.anthropic.com/en/docs/quickstart-guide
31
+
32
+ ```python
33
+ import autogen
34
+
35
+ config_list = [
36
+ {
37
+ "model": "anthropic.claude-3-5-sonnet-20240620-v1:0",
38
+ "aws_access_key":<accessKey>,
39
+ "aws_secret_key":<secretKey>,
40
+ "aws_session_token":<sessionTok>,
41
+ "aws_region":"us-east-1",
42
+ "api_type": "anthropic",
43
+ }
44
+ ]
45
+
46
+ assistant = autogen.AssistantAgent("assistant", llm_config={"config_list": config_list})
47
+ ```
48
+
49
+ Example usage for Anthropic VertexAI:
50
+
51
+ Install the `anthropic` package by running `pip install anthropic[vertex]`.
52
+ - https://docs.anthropic.com/en/docs/quickstart-guide
53
+
54
+ ```python
55
+
56
+ import autogen
57
+ config_list = [
58
+ {
59
+ "model": "claude-3-5-sonnet-20240620-v1:0",
60
+ "gcp_project_id": "dummy_project_id",
61
+ "gcp_region": "us-west-2",
62
+ "gcp_auth_token": "dummy_auth_token",
63
+ "api_type": "anthropic",
64
+ }
65
+ ]
66
+
67
+ assistant = autogen.AssistantAgent("assistant", llm_config={"config_list": config_list})
68
+ ```python
69
+ """
70
+
71
+ from __future__ import annotations
72
+
73
+ import inspect
74
+ import json
75
+ import logging
76
+ import os
77
+ import re
78
+ import time
79
+ import warnings
80
+ from typing import Any, Literal
81
+
82
+ from pydantic import BaseModel, Field
83
+ from typing_extensions import Unpack
84
+
85
+ from ..code_utils import content_str
86
+ from ..import_utils import optional_import_block, require_optional_import
87
+ from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
88
+
89
+ logger = logging.getLogger(__name__)
90
+ from .client_utils import FormatterProtocol, validate_parameter
91
+ from .oai_models import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall, Choice, CompletionUsage
92
+
93
+ with optional_import_block():
94
+ from anthropic import Anthropic, AnthropicBedrock, AnthropicVertex, BadRequestError
95
+ from anthropic import __version__ as anthropic_version
96
+ from anthropic.types import Message, TextBlock, ThinkingBlock, ToolUseBlock
97
+
98
+ # Import transform_schema for structured outputs (SDK >= 0.74.1)
99
+ try:
100
+ from anthropic import transform_schema
101
+ except ImportError:
102
+ transform_schema = None # type: ignore[misc, assignment]
103
+
104
+ # Beta content block types for structured outputs (SDK >= 0.74.1)
105
+ try:
106
+ from anthropic.types.beta.structured_outputs.beta_text_block import BetaTextBlock
107
+ from anthropic.types.beta.structured_outputs.beta_tool_use_block import BetaToolUseBlock
108
+
109
+ BETA_BLOCKS_AVAILABLE = True
110
+ except ImportError:
111
+ # Beta blocks not available in older SDK versions
112
+ BetaTextBlock = None # type: ignore[misc, assignment]
113
+ BetaToolUseBlock = None # type: ignore[misc, assignment]
114
+ BETA_BLOCKS_AVAILABLE = False
115
+
116
+ TOOL_ENABLED = anthropic_version >= "0.23.1"
117
+ if TOOL_ENABLED:
118
+ pass
119
+
120
+
121
+ ANTHROPIC_PRICING_1k = {
122
+ "claude-3-7-sonnet-20250219": (0.003, 0.015),
123
+ "claude-3-5-sonnet-20241022": (0.003, 0.015),
124
+ "claude-3-5-haiku-20241022": (0.0008, 0.004),
125
+ "claude-3-5-sonnet-20240620": (0.003, 0.015),
126
+ "claude-3-sonnet-20240229": (0.003, 0.015),
127
+ "claude-3-opus-20240229": (0.015, 0.075),
128
+ "claude-3-haiku-20240307": (0.00025, 0.00125),
129
+ "claude-2.1": (0.008, 0.024),
130
+ "claude-2.0": (0.008, 0.024),
131
+ "claude-instant-1.2": (0.008, 0.024),
132
+ }
133
+
134
+ # Models that support native structured outputs via beta API
135
+ # https://docs.anthropic.com/en/docs/build-with-claude/structured-outputs
136
+ STRUCTURED_OUTPUT_MODELS = {
137
+ "claude-sonnet-4-5",
138
+ "claude-sonnet-4-5-20250929", # Versioned Claude Sonnet 4.5
139
+ "claude-3-5-sonnet-20241022",
140
+ "claude-3-7-sonnet-20250219",
141
+ "claude-opus-4-1", # Future model
142
+ }
143
+
144
+
145
+ def supports_native_structured_outputs(model: str) -> bool:
146
+ """Check if a Claude model supports native structured outputs (beta feature).
147
+
148
+ Native structured outputs use constrained decoding to guarantee schema compliance.
149
+ This is more reliable than JSON Mode which relies on prompting.
150
+
151
+ Args:
152
+ model: The Claude model name (e.g., "claude-sonnet-4-5")
153
+
154
+ Returns:
155
+ True if the model supports native structured outputs, False otherwise.
156
+
157
+ Supported models:
158
+ - Claude Sonnet 4.5+ (claude-sonnet-4-5, claude-sonnet-4-5-20250929, claude-3-5-sonnet-20241022+)
159
+ - Claude Sonnet 3.7+ (claude-3-7-sonnet-20250219+)
160
+ - Claude Opus 4.1+ (claude-opus-4-1+)
161
+
162
+ NOT supported (will use JSON Mode fallback):
163
+ - Claude Sonnet 4.0 (claude-sonnet-4-20250514) - older version
164
+ - Claude 3 Haiku models
165
+ - Claude 2.x models
166
+
167
+ Example:
168
+ >>> supports_native_structured_outputs("claude-sonnet-4-5")
169
+ True
170
+ >>> supports_native_structured_outputs("claude-sonnet-4-20250514")
171
+ False # Claude Sonnet 4.0 doesn't support it
172
+ >>> supports_native_structured_outputs("claude-3-haiku-20240307")
173
+ False
174
+ """
175
+ # Exact match for known models
176
+ if model in STRUCTURED_OUTPUT_MODELS:
177
+ return True
178
+
179
+ # Pattern matching for versioned models
180
+ # Support future Sonnet 3.5+ and 3.7+ versions
181
+ if model.startswith(("claude-3-5-sonnet-", "claude-3-7-sonnet-")):
182
+ return True
183
+
184
+ # Support future Sonnet 4.5+ versions (NOT Sonnet 4.0)
185
+ if model.startswith("claude-sonnet-4-5"):
186
+ return True
187
+
188
+ # Support future Opus 4.x versions
189
+ if model.startswith("claude-opus-4"):
190
+ return True
191
+
192
+ return False
193
+
194
+
195
+ def has_beta_messages_api() -> bool:
196
+ """Check if the current Anthropic SDK version supports beta.messages API.
197
+
198
+ The beta.messages API is required for native structured outputs.
199
+ This function performs runtime detection of SDK capabilities.
200
+
201
+ Returns:
202
+ True if beta.messages.parse() is available, False otherwise.
203
+
204
+ Example:
205
+ >>> has_beta_messages_api()
206
+ True # If anthropic>=0.39.0 is installed
207
+ """
208
+ try:
209
+ from anthropic.resources.beta.messages import Messages
210
+
211
+ return hasattr(Messages, "parse")
212
+ except ImportError:
213
+ return False
214
+
215
+
216
+ def validate_structured_outputs_version() -> None:
217
+ """Validate that the Anthropic SDK version supports structured outputs beta.
218
+
219
+ The structured-outputs-2025-11-13 beta header requires anthropic>=0.74.1.
220
+
221
+ Raises:
222
+ ImportError: If the Anthropic SDK version is too old
223
+
224
+ Example:
225
+ >>> validate_structured_outputs_version() # Raises if version < 0.74.1
226
+ """
227
+ try:
228
+ from packaging import version
229
+
230
+ min_version = "0.74.1"
231
+ current_version = anthropic_version # Use module-level import
232
+
233
+ if version.parse(current_version) < version.parse(min_version):
234
+ raise ImportError(
235
+ f"Anthropic structured outputs require anthropic>={min_version}, "
236
+ f"but found version {current_version}. "
237
+ f"Please upgrade: pip install --upgrade 'anthropic>={min_version}'"
238
+ )
239
+ except ImportError as e:
240
+ if "anthropic" in str(e) or "version" in str(e).lower():
241
+ raise
242
+ # If packaging is not available, try manual version comparison
243
+ current_version = anthropic_version # Use module-level import
244
+
245
+ # Simple version comparison (works for major.minor.patch format)
246
+ current_parts = [int(x) for x in anthropic_version.split(".")[:3]]
247
+ min_parts = [0, 74, 1]
248
+
249
+ if current_parts < min_parts:
250
+ raise ImportError(
251
+ f"Anthropic structured outputs require anthropic>=0.74.1, "
252
+ f"but found version {anthropic_version}. "
253
+ f"Please upgrade: pip install --upgrade 'anthropic>=0.74.1'"
254
+ )
255
+
256
+
257
+ def _is_text_block(content: Any) -> bool:
258
+ """Check if a content block is a text block (legacy or beta version).
259
+
260
+ Args:
261
+ content: Content block to check
262
+
263
+ Returns:
264
+ True if content is a TextBlock or BetaTextBlock
265
+ """
266
+ if type(content) == TextBlock:
267
+ return True
268
+ if BETA_BLOCKS_AVAILABLE and type(content) == BetaTextBlock:
269
+ return True
270
+ return False
271
+
272
+
273
+ def _is_tool_use_block(content: Any) -> bool:
274
+ """Check if a content block is a tool use block (legacy or beta version).
275
+
276
+ Args:
277
+ content: Content block to check
278
+
279
+ Returns:
280
+ True if content is a ToolUseBlock or BetaToolUseBlock
281
+ """
282
+ content_type = type(content)
283
+ content_type_name = content_type.__name__
284
+
285
+ if content_type == ToolUseBlock:
286
+ return True
287
+ if BETA_BLOCKS_AVAILABLE and content_type == BetaToolUseBlock:
288
+ return True
289
+
290
+ # Fallback: check by name if type comparison fails
291
+ if content_type_name in ("ToolUseBlock", "BetaToolUseBlock"):
292
+ return True
293
+
294
+ return False
295
+
296
+
297
+ def _is_thinking_block(content: Any) -> bool:
298
+ """Check if a content block is a thinking block (extended thinking).
299
+
300
+ Args:
301
+ content: Content block to check
302
+
303
+ Returns:
304
+ True if content is a ThinkingBlock
305
+ """
306
+ content_type = type(content)
307
+ content_type_name = content_type.__name__
308
+
309
+ if content_type == ThinkingBlock:
310
+ return True
311
+
312
+ # Fallback: check by name if type comparison fails
313
+ if content_type_name == "ThinkingBlock":
314
+ return True
315
+
316
+ return False
317
+
318
+
319
+ def transform_schema_for_anthropic(schema: dict[str, Any]) -> dict[str, Any]:
320
+ """Transform JSON schema to be compatible with Anthropic's structured outputs.
321
+
322
+ Anthropic's structured outputs don't support certain JSON Schema features:
323
+ - Numerical constraints (minimum, maximum, multipleOf)
324
+ - String length constraints (minLength, maxLength, pattern with backreferences)
325
+ - Recursive schemas ($ref loops)
326
+ - Complex regex patterns
327
+
328
+ This function removes unsupported constraints while preserving the core structure.
329
+
330
+ Args:
331
+ schema: A JSON schema dict (typically from Pydantic model_json_schema())
332
+
333
+ Returns:
334
+ Transformed schema compatible with Anthropic's requirements
335
+
336
+ Example:
337
+ >>> schema = {"type": "object", "properties": {"age": {"type": "integer", "minimum": 0, "maximum": 150}}}
338
+ >>> transformed = transform_schema_for_anthropic(schema)
339
+ >>> "minimum" in transformed["properties"]["age"]
340
+ False
341
+ """
342
+ import copy
343
+
344
+ transformed = copy.deepcopy(schema)
345
+
346
+ def remove_unsupported_constraints(obj: Any) -> None:
347
+ """Recursively remove unsupported constraints from schema."""
348
+ if isinstance(obj, dict):
349
+ # Remove numerical constraints
350
+ obj.pop("minimum", None)
351
+ obj.pop("maximum", None)
352
+ obj.pop("multipleOf", None)
353
+
354
+ # Remove string length constraints
355
+ obj.pop("minLength", None)
356
+ obj.pop("maxLength", None)
357
+
358
+ # Remove array length constraints
359
+ obj.pop("minItems", None)
360
+ obj.pop("maxItems", None)
361
+
362
+ # Add additionalProperties: false for ALL objects (Anthropic requirement)
363
+ if obj.get("type") == "object" and "additionalProperties" not in obj:
364
+ obj["additionalProperties"] = False
365
+
366
+ # Recurse into nested objects
367
+ for value in obj.values():
368
+ remove_unsupported_constraints(value)
369
+ elif isinstance(obj, list):
370
+ for item in obj:
371
+ remove_unsupported_constraints(item)
372
+
373
+ # Remove constraints from entire schema
374
+ remove_unsupported_constraints(transformed)
375
+
376
+ return transformed
377
+
378
+
379
+ class AnthropicEntryDict(LLMConfigEntryDict, total=False):
380
+ api_type: Literal["anthropic"]
381
+ timeout: int | None
382
+ stop_sequences: list[str] | None
383
+ stream: bool
384
+ price: list[float] | None
385
+ tool_choice: dict | None
386
+ thinking: dict | None
387
+ gcp_project_id: str | None
388
+ gcp_region: str | None
389
+ gcp_auth_token: str | None
390
+
391
+
392
+ class AnthropicLLMConfigEntry(LLMConfigEntry):
393
+ api_type: Literal["anthropic"] = "anthropic"
394
+
395
+ # Basic options
396
+ max_tokens: int = Field(default=4096, ge=1)
397
+ temperature: float | None = Field(default=None, ge=0.0, le=1.0)
398
+ top_p: float | None = Field(default=None, ge=0.0, le=1.0)
399
+
400
+ # Anthropic-specific options
401
+ timeout: int | None = Field(default=None, ge=1)
402
+ top_k: int | None = Field(default=None, ge=1)
403
+ stop_sequences: list[str] | None = None
404
+ stream: bool = False
405
+ price: list[float] | None = Field(default=None, min_length=2, max_length=2)
406
+ tool_choice: dict | None = None
407
+ thinking: dict | None = None
408
+
409
+ gcp_project_id: str | None = None
410
+ gcp_region: str | None = None
411
+ gcp_auth_token: str | None = None
412
+
413
+ def create_client(self):
414
+ raise NotImplementedError("AnthropicLLMConfigEntry.create_client is not implemented.")
415
+
416
+
417
+ @require_optional_import("anthropic", "anthropic")
418
+ class AnthropicClient:
419
+ RESPONSE_USAGE_KEYS: list[str] = ["prompt_tokens", "completion_tokens", "total_tokens", "cost", "model"]
420
+
421
+ def __init__(self, **kwargs: Unpack[AnthropicEntryDict]):
422
+ """Initialize the Anthropic API client.
423
+
424
+ Args:
425
+ **kwargs: The configuration parameters for the client.
426
+ """
427
+ self._api_key = kwargs.get("api_key") or os.getenv("ANTHROPIC_API_KEY")
428
+ self._aws_access_key = kwargs.get("aws_access_key") or os.getenv("AWS_ACCESS_KEY")
429
+ self._aws_secret_key = kwargs.get("aws_secret_key") or os.getenv("AWS_SECRET_KEY")
430
+ self._aws_session_token = kwargs.get("aws_session_token")
431
+ self._aws_region = kwargs.get("aws_region") or os.getenv("AWS_REGION")
432
+ self._gcp_project_id = kwargs.get("gcp_project_id")
433
+ self._gcp_region = kwargs.get("gcp_region") or os.getenv("GCP_REGION")
434
+ self._gcp_auth_token = kwargs.get("gcp_auth_token")
435
+ self._base_url = kwargs.get("base_url")
436
+
437
+ if self._api_key is None:
438
+ if self._aws_region:
439
+ if self._aws_access_key is None or self._aws_secret_key is None:
440
+ raise ValueError("API key or AWS credentials are required to use the Anthropic API.")
441
+ elif self._gcp_region:
442
+ if self._gcp_project_id is None or self._gcp_region is None:
443
+ raise ValueError("API key or GCP credentials are required to use the Anthropic API.")
444
+ else:
445
+ raise ValueError("API key or AWS credentials or GCP credentials are required to use the Anthropic API.")
446
+
447
+ if self._api_key is not None:
448
+ client_kwargs = {"api_key": self._api_key}
449
+ if self._base_url:
450
+ client_kwargs["base_url"] = self._base_url
451
+ self._client = Anthropic(**client_kwargs)
452
+ elif self._gcp_region is not None:
453
+ kw = {}
454
+ for p in inspect.signature(AnthropicVertex).parameters:
455
+ if hasattr(self, f"_gcp_{p}"):
456
+ kw[p] = getattr(self, f"_gcp_{p}")
457
+ if self._base_url:
458
+ kw["base_url"] = self._base_url
459
+ self._client = AnthropicVertex(**kw)
460
+ else:
461
+ client_kwargs = {
462
+ "aws_access_key": self._aws_access_key,
463
+ "aws_secret_key": self._aws_secret_key,
464
+ "aws_session_token": self._aws_session_token,
465
+ "aws_region": self._aws_region,
466
+ }
467
+ if self._base_url:
468
+ client_kwargs["base_url"] = self._base_url
469
+ self._client = AnthropicBedrock(**client_kwargs)
470
+
471
+ self._last_tooluse_status = {}
472
+
473
+ # Store the response format, if provided (for structured outputs)
474
+ self._response_format: type[BaseModel] | dict | None = kwargs.get("response_format")
475
+
476
+ def load_config(self, params: dict[str, Any]):
477
+ """Load the configuration for the Anthropic API client."""
478
+ anthropic_params = {}
479
+
480
+ anthropic_params["model"] = params.get("model")
481
+ assert anthropic_params["model"], "Please provide a `model` in the config_list to use the Anthropic API."
482
+
483
+ anthropic_params["temperature"] = validate_parameter(
484
+ params, "temperature", (float, int), False, 1.0, (0.0, 1.0), None
485
+ )
486
+ anthropic_params["max_tokens"] = validate_parameter(params, "max_tokens", int, False, 4096, (1, None), None)
487
+ anthropic_params["timeout"] = validate_parameter(params, "timeout", int, True, None, (1, None), None)
488
+ anthropic_params["top_k"] = validate_parameter(params, "top_k", int, True, None, (1, None), None)
489
+ anthropic_params["top_p"] = validate_parameter(params, "top_p", (float, int), True, None, (0.0, 1.0), None)
490
+ anthropic_params["stop_sequences"] = validate_parameter(params, "stop_sequences", list, True, None, None, None)
491
+ anthropic_params["stream"] = validate_parameter(params, "stream", bool, False, False, None, None)
492
+ if "thinking" in params:
493
+ anthropic_params["thinking"] = params["thinking"]
494
+
495
+ if anthropic_params["stream"]:
496
+ warnings.warn(
497
+ "Streaming is not currently supported, streaming will be disabled.",
498
+ UserWarning,
499
+ )
500
+ anthropic_params["stream"] = False
501
+
502
+ # Note the Anthropic API supports "tool" for tool_choice but you must specify the tool name so we will ignore that here
503
+ # Dictionary, see options here: https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview#controlling-claudes-output
504
+ # type = auto, any, tool, none | name = the name of the tool if type=tool
505
+ anthropic_params["tool_choice"] = validate_parameter(params, "tool_choice", dict, True, None, None, None)
506
+
507
+ return anthropic_params
508
+
509
+ def _remove_none_params(self, params: dict[str, Any]) -> None:
510
+ """Remove parameters with None values from the params dict.
511
+
512
+ Anthropic API doesn't accept None values, so we remove them before making requests.
513
+ This method modifies the params dict in-place.
514
+
515
+ Args:
516
+ params: Dictionary of API parameters
517
+ """
518
+ keys_to_remove = [key for key, value in params.items() if value is None]
519
+ for key in keys_to_remove:
520
+ del params[key]
521
+
522
+ def _prepare_anthropic_params(
523
+ self, params: dict[str, Any], anthropic_messages: list[dict[str, Any]]
524
+ ) -> dict[str, Any]:
525
+ """Prepare parameters for Anthropic API call.
526
+
527
+ Consolidates common parameter preparation logic used across all create methods:
528
+ - Loads base configuration
529
+ - Converts tools format if needed
530
+ - Assigns messages, system, and tools
531
+ - Removes None values
532
+
533
+ Args:
534
+ params: Original request parameters
535
+ anthropic_messages: Converted messages in Anthropic format
536
+
537
+ Returns:
538
+ Dictionary of Anthropic API parameters ready for use
539
+ """
540
+ # Load base configuration
541
+ anthropic_params = self.load_config(params)
542
+
543
+ # Convert tools to functions if needed (make a copy to avoid modifying original)
544
+ params_copy = params.copy()
545
+ if "functions" in params_copy:
546
+ tools_configs = params_copy.pop("functions")
547
+ tools_configs = [self.openai_func_to_anthropic(tool) for tool in tools_configs]
548
+ params_copy["tools"] = tools_configs
549
+
550
+ # Assign messages and optional parameters
551
+ anthropic_params["messages"] = anthropic_messages
552
+ if "system" in params_copy:
553
+ anthropic_params["system"] = params_copy["system"]
554
+ if "tools" in params_copy:
555
+ anthropic_params["tools"] = params_copy["tools"]
556
+
557
+ # Remove None values
558
+ self._remove_none_params(anthropic_params)
559
+
560
+ return anthropic_params
561
+
562
+ def _process_response_content(
563
+ self, response: Message, is_native_structured_output: bool = False
564
+ ) -> tuple[str, list[ChatCompletionMessageToolCall] | None, str]:
565
+ """Process Anthropic response content into OpenAI-compatible format.
566
+
567
+ Extracts tool calls, text content, and determines finish reason from the response.
568
+ Handles both standard and native structured output responses.
569
+
570
+ Args:
571
+ response: Anthropic Message response object
572
+ is_native_structured_output: Whether this is a native structured output response
573
+
574
+ Returns:
575
+ Tuple of (message_text, tool_calls, finish_reason)
576
+ """
577
+ tool_calls: list[ChatCompletionMessageToolCall] = []
578
+ message_text = ""
579
+ finish_reason = "stop"
580
+
581
+ if response is None:
582
+ return message_text, None, finish_reason
583
+
584
+ # Determine finish reason
585
+ if response.stop_reason == "tool_use":
586
+ finish_reason = "tool_calls"
587
+
588
+ # Process all content blocks
589
+ thinking_content = ""
590
+ text_content = ""
591
+
592
+ for content in response.content:
593
+ # Extract tool calls (handles both ToolUseBlock and BetaToolUseBlock)
594
+ if _is_tool_use_block(content):
595
+ tool_calls.append(
596
+ ChatCompletionMessageToolCall(
597
+ id=content.id,
598
+ function={"name": content.name, "arguments": json.dumps(content.input)},
599
+ type="function",
600
+ )
601
+ )
602
+ # Extract thinking content (extended thinking feature)
603
+ elif _is_thinking_block(content):
604
+ if thinking_content:
605
+ thinking_content += "\n\n"
606
+ thinking_content += content.thinking
607
+ # Extract text content (handles both TextBlock and BetaTextBlock)
608
+ elif _is_text_block(content):
609
+ # For native structured output, prefer parsed_output from parse() if available
610
+ # Otherwise use the text content from the BetaTextBlock (from create() with dict schema)
611
+ if (
612
+ is_native_structured_output
613
+ and hasattr(response, "parsed_output")
614
+ and response.parsed_output is not None
615
+ ):
616
+ parsed_response = response.parsed_output
617
+ text_content = (
618
+ parsed_response.model_dump_json()
619
+ if hasattr(parsed_response, "model_dump_json")
620
+ else str(parsed_response)
621
+ )
622
+ else:
623
+ # Use text content from BetaTextBlock (when using create() with dict schema)
624
+ # or regular TextBlock (non-SO responses)
625
+ if text_content:
626
+ text_content += "\n\n"
627
+ text_content += content.text
628
+
629
+ # Combine thinking and text content
630
+ if thinking_content and text_content:
631
+ message_text = f"[Thinking]\n{thinking_content}\n\n{text_content}"
632
+ elif thinking_content:
633
+ message_text = f"[Thinking]\n{thinking_content}"
634
+ elif text_content:
635
+ message_text = text_content
636
+
637
+ # Fallback: If using native SO parse() and no text was found in content blocks,
638
+ # extract from parsed_output directly (if it's not None)
639
+ if (
640
+ not message_text
641
+ and is_native_structured_output
642
+ and hasattr(response, "parsed_output")
643
+ and response.parsed_output is not None
644
+ ):
645
+ parsed_response = response.parsed_output
646
+ message_text = (
647
+ parsed_response.model_dump_json()
648
+ if hasattr(parsed_response, "model_dump_json")
649
+ else str(parsed_response)
650
+ )
651
+
652
+ return message_text, tool_calls if tool_calls else None, finish_reason
653
+
654
+ def _log_structured_output_fallback(
655
+ self,
656
+ exception: Exception,
657
+ model: str | None,
658
+ response_format: Any,
659
+ params: dict[str, Any],
660
+ ) -> None:
661
+ """Log detailed error information when native structured output fails and we fallback to JSON Mode.
662
+
663
+ Consolidates error logging logic used in the create() method when native structured
664
+ output encounters errors and needs to fall back to JSON Mode.
665
+
666
+ Args:
667
+ exception: The exception that triggered the fallback
668
+ model: Model name/identifier
669
+ response_format: Response format specification (Pydantic model or dict)
670
+ params: Original request parameters
671
+ """
672
+ # Build error details dictionary
673
+ error_details = {
674
+ "model": model,
675
+ "response_format": str(
676
+ type(response_format).__name__ if isinstance(response_format, type) else type(response_format)
677
+ ),
678
+ "error_type": type(exception).__name__,
679
+ "error_message": str(exception),
680
+ }
681
+
682
+ # Add BadRequestError-specific details if available
683
+ if isinstance(exception, BadRequestError):
684
+ if hasattr(exception, "status_code"):
685
+ error_details["status_code"] = exception.status_code
686
+ if hasattr(exception, "response"):
687
+ error_details["response_body"] = str(
688
+ exception.response.text if hasattr(exception.response, "text") else exception.response
689
+ )
690
+ if hasattr(exception, "body"):
691
+ error_details["error_body"] = str(exception.body)
692
+
693
+ # Log sanitized params (remove sensitive data like API keys, message content)
694
+ sanitized_params = {
695
+ "model": params.get("model"),
696
+ "max_tokens": params.get("max_tokens"),
697
+ "temperature": params.get("temperature"),
698
+ "has_tools": "tools" in params,
699
+ "num_messages": len(params.get("messages", [])),
700
+ }
701
+ error_details["params"] = sanitized_params
702
+
703
+ # Log warning with full error context
704
+ logger.warning(
705
+ f"Native structured output failed for {model}. Error: {error_details}. Falling back to JSON Mode."
706
+ )
707
+
708
+ def cost(self, response) -> float:
709
+ """Calculate the cost of the completion using the Anthropic pricing."""
710
+ return response.cost
711
+
712
+ @property
713
+ def api_key(self):
714
+ return self._api_key
715
+
716
+ @property
717
+ def aws_access_key(self):
718
+ return self._aws_access_key
719
+
720
+ @property
721
+ def aws_secret_key(self):
722
+ return self._aws_secret_key
723
+
724
+ @property
725
+ def aws_session_token(self):
726
+ return self._aws_session_token
727
+
728
+ @property
729
+ def aws_region(self):
730
+ return self._aws_region
731
+
732
+ @property
733
+ def gcp_project_id(self):
734
+ return self._gcp_project_id
735
+
736
+ @property
737
+ def gcp_region(self):
738
+ return self._gcp_region
739
+
740
+ @property
741
+ def gcp_auth_token(self):
742
+ return self._gcp_auth_token
743
+
744
+ def create(self, params: dict[str, Any]) -> ChatCompletion:
745
+ """Creates a completion using the Anthropic API.
746
+
747
+ Automatically selects the best structured output method:
748
+ - Native structured outputs for Claude Sonnet 4.5+ (guaranteed schema compliance)
749
+ - JSON Mode for older models (prompt-based with <json_response> tags)
750
+ - Standard completion for requests without response_format
751
+
752
+ Args:
753
+ params: Request parameters including model, messages, and optional response_format
754
+
755
+ Returns:
756
+ ChatCompletion object compatible with OpenAI format
757
+ """
758
+ model = params.get("model")
759
+ response_format = params.get("response_format") or self._response_format
760
+
761
+ # Route to appropriate implementation based on model and response_format
762
+ if response_format:
763
+ self._response_format = response_format
764
+ params["response_format"] = response_format # Ensure response_format is in params for methods
765
+
766
+ # Try native structured outputs if model supports it
767
+ if supports_native_structured_outputs(model) and has_beta_messages_api():
768
+ try:
769
+ return self._create_with_native_structured_output(params)
770
+ except (BadRequestError, AttributeError, ValueError) as e:
771
+ # Fallback to JSON Mode if native API not supported or schema invalid
772
+ # BadRequestError: Model doesn't support output_format
773
+ # AttributeError: SDK doesn't have beta API
774
+ # ValueError: Invalid schema format
775
+ self._log_structured_output_fallback(e, model, response_format, params)
776
+ return self._create_with_json_mode(params)
777
+ else:
778
+ # Use JSON Mode for older models or when beta API unavailable
779
+ return self._create_with_json_mode(params)
780
+ else:
781
+ # Standard completion without structured outputs
782
+ return self._create_standard(params)
783
+
784
+ def _create_standard(self, params: dict[str, Any]) -> ChatCompletion:
785
+ """Create a standard completion without structured outputs."""
786
+ # Convert tools to functions format if needed
787
+ if "tools" in params:
788
+ converted_functions = self.convert_tools_to_functions(params["tools"])
789
+ params["functions"] = params.get("functions", []) + converted_functions
790
+
791
+ # Convert AG2 messages to Anthropic messages
792
+ anthropic_messages = oai_messages_to_anthropic_messages(params)
793
+
794
+ # Prepare Anthropic API parameters using helper (handles tool conversion, None removal, etc.)
795
+ anthropic_params = self._prepare_anthropic_params(params, anthropic_messages)
796
+
797
+ # Check if any tools use strict mode (requires beta API)
798
+ has_strict_tools = any(tool.get("strict") for tool in anthropic_params.get("tools", []))
799
+
800
+ if has_strict_tools:
801
+ # Validate SDK version supports structured outputs beta
802
+ validate_structured_outputs_version()
803
+ # Use beta API for strict tools
804
+ anthropic_params["betas"] = ["structured-outputs-2025-11-13"]
805
+ response = self._client.beta.messages.create(**anthropic_params)
806
+ else:
807
+ # Standard API for legacy tools
808
+ response = self._client.messages.create(**anthropic_params)
809
+
810
+ # Process response content using helper (extracts tool calls, text, finish reason)
811
+ message_text, tool_calls, anthropic_finish = self._process_response_content(response)
812
+
813
+ # Build and return ChatCompletion
814
+ return self._build_chat_completion(response, message_text, tool_calls, anthropic_finish, anthropic_params)
815
+
816
+ def _create_with_native_structured_output(self, params: dict[str, Any]) -> ChatCompletion:
817
+ """Create completion using native structured outputs (beta API).
818
+
819
+ This method uses Anthropic's beta structured outputs feature for guaranteed
820
+ schema compliance via constrained decoding.
821
+
822
+ Args:
823
+ params: Request parameters
824
+
825
+ Returns:
826
+ ChatCompletion with structured JSON output
827
+
828
+ Raises:
829
+ AttributeError: If SDK doesn't support beta API
830
+ Exception: If native structured output fails
831
+ """
832
+ # Check if Anthropic's transform_schema is available
833
+ if transform_schema is None:
834
+ raise ImportError("Anthropic transform_schema not available. Please upgrade to anthropic>=0.74.1")
835
+
836
+ # Get schema from response_format and transform it using Anthropic's function
837
+ if isinstance(self._response_format, type) and issubclass(self._response_format, BaseModel):
838
+ # For Pydantic models, use Anthropic's transform_schema directly
839
+ transformed_schema = transform_schema(self._response_format)
840
+ elif isinstance(self._response_format, dict):
841
+ # For dict schemas, use as-is (already in correct format)
842
+ schema = self._response_format
843
+ # Still apply our transformation for additionalProperties
844
+ transformed_schema = transform_schema_for_anthropic(schema)
845
+ else:
846
+ raise ValueError(f"Invalid response format: {self._response_format}")
847
+
848
+ # Convert AG2 messages to Anthropic messages
849
+ anthropic_messages = oai_messages_to_anthropic_messages(params)
850
+
851
+ # Prepare Anthropic API parameters using helper
852
+ anthropic_params = self._prepare_anthropic_params(params, anthropic_messages)
853
+
854
+ # Validate SDK version supports structured outputs beta
855
+ validate_structured_outputs_version()
856
+
857
+ # Add native structured output parameters
858
+ anthropic_params["betas"] = ["structured-outputs-2025-11-13"]
859
+
860
+ # Use beta API
861
+ if not hasattr(self._client, "beta"):
862
+ raise AttributeError(
863
+ "Anthropic SDK does not support beta.messages API. Please upgrade to anthropic>=0.39.0"
864
+ )
865
+
866
+ # When both tools and structured output are configured, must use create() (not parse())
867
+ # parse() doesn't support tools, so we convert Pydantic models to dict schemas
868
+ has_tools = "tools" in anthropic_params and anthropic_params["tools"]
869
+
870
+ if has_tools or isinstance(self._response_format, dict):
871
+ # Use create() with output_format for:
872
+ # 1. Dict schemas (always)
873
+ # 2. Pydantic models when tools are present (parse() doesn't support tools)
874
+ anthropic_params["output_format"] = {
875
+ "type": "json_schema",
876
+ "schema": transformed_schema,
877
+ }
878
+ response = self._client.beta.messages.create(**anthropic_params)
879
+ else:
880
+ # Pydantic model without tools - use parse() for automatic validation
881
+ # parse() provides parsed_output attribute for direct model access
882
+ anthropic_params["output_format"] = self._response_format
883
+ response = self._client.beta.messages.parse(**anthropic_params)
884
+
885
+ # Process response content using helper (extracts tool calls, text, finish reason)
886
+ # Pass is_native_structured_output=True to handle parsed_output correctly
887
+ message_text, tool_calls, anthropic_finish = self._process_response_content(
888
+ response, is_native_structured_output=True
889
+ )
890
+
891
+ # Build and return ChatCompletion
892
+ return self._build_chat_completion(response, message_text, tool_calls, anthropic_finish, anthropic_params)
893
+
894
+ def _create_with_json_mode(self, params: dict[str, Any]) -> ChatCompletion:
895
+ """Create completion using legacy JSON Mode with <json_response> tags.
896
+
897
+ This method uses prompt-based structured outputs for older Claude models
898
+ that don't support native structured outputs.
899
+
900
+ Args:
901
+ params: Request parameters
902
+
903
+ Returns:
904
+ ChatCompletion with JSON output extracted from tags
905
+ """
906
+ # Convert tools to functions format if needed
907
+ if "tools" in params:
908
+ converted_functions = self.convert_tools_to_functions(params["tools"])
909
+ params["functions"] = params.get("functions", []) + converted_functions
910
+
911
+ # Add response format instructions to system message before message conversion
912
+ self._add_response_format_to_system(params)
913
+
914
+ # Convert AG2 messages to Anthropic messages
915
+ anthropic_messages = oai_messages_to_anthropic_messages(params)
916
+
917
+ # Prepare Anthropic API parameters using helper
918
+ anthropic_params = self._prepare_anthropic_params(params, anthropic_messages)
919
+
920
+ # Call Anthropic API
921
+ response = self._client.messages.create(**anthropic_params)
922
+
923
+ # Extract JSON from <json_response> tags
924
+ parsed_response = self._extract_json_response(response)
925
+ # Keep as JSON - FormatterProtocol formatting will be applied in message_retrieval()
926
+ message_text = (
927
+ parsed_response.model_dump_json() if hasattr(parsed_response, "model_dump_json") else str(parsed_response)
928
+ )
929
+
930
+ # Build and return ChatCompletion
931
+ return self._build_chat_completion(
932
+ response, message_text, tool_calls=None, finish_reason="stop", anthropic_params=anthropic_params
933
+ )
934
+
935
+ def _build_chat_completion(
936
+ self,
937
+ response: Message,
938
+ message_text: str,
939
+ tool_calls: list[ChatCompletionMessageToolCall] | None,
940
+ finish_reason: str,
941
+ anthropic_params: dict[str, Any],
942
+ ) -> ChatCompletion:
943
+ """Build OpenAI-compatible ChatCompletion from Anthropic response.
944
+
945
+ Args:
946
+ response: Anthropic Message response
947
+ message_text: Processed message content
948
+ tool_calls: List of tool calls if any
949
+ finish_reason: Completion finish reason
950
+ anthropic_params: Original request parameters
951
+
952
+ Returns:
953
+ ChatCompletion object
954
+ """
955
+ # Calculate token usage
956
+ prompt_tokens = response.usage.input_tokens
957
+ completion_tokens = response.usage.output_tokens
958
+
959
+ # Build message
960
+ message = ChatCompletionMessage(
961
+ role="assistant",
962
+ content=message_text,
963
+ function_call=None,
964
+ tool_calls=tool_calls,
965
+ )
966
+
967
+ choices = [Choice(finish_reason=finish_reason, index=0, message=message)]
968
+
969
+ # Build and return ChatCompletion
970
+ return ChatCompletion(
971
+ id=response.id,
972
+ model=anthropic_params["model"],
973
+ created=int(time.time()),
974
+ object="chat.completion",
975
+ choices=choices,
976
+ usage=CompletionUsage(
977
+ prompt_tokens=prompt_tokens,
978
+ completion_tokens=completion_tokens,
979
+ total_tokens=prompt_tokens + completion_tokens,
980
+ ),
981
+ cost=_calculate_cost(prompt_tokens, completion_tokens, anthropic_params["model"]),
982
+ )
983
+
984
+ def message_retrieval(self, response) -> list[str] | list[ChatCompletionMessage]:
985
+ """Retrieve and return a list of strings or a list of Choice.Message from the response.
986
+
987
+ This method handles structured outputs with FormatterProtocol:
988
+ - If tool/function calls present: returns full message objects
989
+ - If structured output with format(): applies custom formatting
990
+ - Otherwise: returns content as-is
991
+
992
+ NOTE: if a list of Choice.Message is returned, it currently needs to contain the fields of OpenAI's ChatCompletion Message object,
993
+ since that is expected for function or tool calling in the rest of the codebase at the moment, unless a custom agent is being used.
994
+ """
995
+ choices = response.choices
996
+
997
+ def _format_content(content: str | list[dict[str, Any]] | None) -> str:
998
+ """Format content using FormatterProtocol if available."""
999
+ normalized_content = content_str(content) # type: ignore [arg-type]
1000
+ # If response_format implements FormatterProtocol (has format() method), use it
1001
+ if isinstance(self._response_format, FormatterProtocol):
1002
+ try:
1003
+ return self._response_format.model_validate_json(normalized_content).format() # type: ignore [union-attr]
1004
+ except Exception:
1005
+ # If parsing fails (e.g., content is error message), return as-is
1006
+ return normalized_content
1007
+ else:
1008
+ return normalized_content
1009
+
1010
+ # Handle tool/function calls - return full message object
1011
+ if TOOL_ENABLED:
1012
+ return [ # type: ignore [return-value]
1013
+ (choice.message if choice.message.tool_calls is not None else _format_content(choice.message.content))
1014
+ for choice in choices
1015
+ ]
1016
+ else:
1017
+ return [_format_content(choice.message.content) for choice in choices] # type: ignore [return-value]
1018
+
1019
+ @staticmethod
1020
+ def openai_func_to_anthropic(openai_func: dict) -> dict:
1021
+ res = openai_func.copy()
1022
+ res["input_schema"] = res.pop("parameters")
1023
+
1024
+ # Preserve strict field if present (for Anthropic structured outputs)
1025
+ # strict=True enables guaranteed schema validation for tool inputs
1026
+ if "strict" in openai_func:
1027
+ res["strict"] = openai_func["strict"]
1028
+ # Transform schema to add required additionalProperties: false for all objects
1029
+ # Anthropic requires this for strict tools
1030
+ res["input_schema"] = transform_schema_for_anthropic(res["input_schema"])
1031
+
1032
+ return res
1033
+
1034
+ @staticmethod
1035
+ def get_usage(response: ChatCompletion) -> dict:
1036
+ """Get the usage of tokens and their cost information."""
1037
+ return {
1038
+ "prompt_tokens": response.usage.prompt_tokens if response.usage is not None else 0,
1039
+ "completion_tokens": response.usage.completion_tokens if response.usage is not None else 0,
1040
+ "total_tokens": response.usage.total_tokens if response.usage is not None else 0,
1041
+ "cost": response.cost if hasattr(response, "cost") else 0.0,
1042
+ "model": response.model,
1043
+ }
1044
+
1045
+ @staticmethod
1046
+ def convert_tools_to_functions(tools: list) -> list:
1047
+ """Convert tool definitions into Anthropic-compatible functions,
1048
+ updating nested $ref paths in property schemas.
1049
+
1050
+ Args:
1051
+ tools (list): List of tool definitions.
1052
+
1053
+ Returns:
1054
+ list: List of functions with updated $ref paths.
1055
+ """
1056
+
1057
+ def update_refs(obj, defs_keys, prop_name):
1058
+ """Recursively update $ref values that start with "#/$defs/"."""
1059
+ if isinstance(obj, dict):
1060
+ for key, value in obj.items():
1061
+ if key == "$ref" and isinstance(value, str) and value.startswith("#/$defs/"):
1062
+ ref_key = value[len("#/$defs/") :]
1063
+ if ref_key in defs_keys:
1064
+ obj[key] = f"#/properties/{prop_name}/$defs/{ref_key}"
1065
+ else:
1066
+ update_refs(value, defs_keys, prop_name)
1067
+ elif isinstance(obj, list):
1068
+ for item in obj:
1069
+ update_refs(item, defs_keys, prop_name)
1070
+
1071
+ functions = []
1072
+ for tool in tools:
1073
+ if tool.get("type") == "function" and "function" in tool:
1074
+ function = tool["function"]
1075
+ parameters = function.get("parameters", {})
1076
+ properties = parameters.get("properties", {})
1077
+ for prop_name, prop_schema in properties.items():
1078
+ if "$defs" in prop_schema:
1079
+ defs_keys = set(prop_schema["$defs"].keys())
1080
+ update_refs(prop_schema, defs_keys, prop_name)
1081
+ functions.append(function)
1082
+ return functions
1083
+
1084
+ def _resolve_schema_refs(self, schema: dict[str, Any], defs: dict[str, Any]) -> dict[str, Any]:
1085
+ """Recursively resolve $ref references in a JSON schema.
1086
+
1087
+ Args:
1088
+ schema: The schema to resolve
1089
+ defs: The definitions dict from $defs
1090
+
1091
+ Returns:
1092
+ Schema with all $ref references resolved inline
1093
+ """
1094
+ if isinstance(schema, dict):
1095
+ if "$ref" in schema:
1096
+ # Extract the reference name (e.g., "#/$defs/Step" -> "Step")
1097
+ ref_name = schema["$ref"].split("/")[-1]
1098
+ # Replace with the actual definition
1099
+ return self._resolve_schema_refs(defs[ref_name].copy(), defs)
1100
+ else:
1101
+ # Recursively resolve all nested schemas
1102
+ return {k: self._resolve_schema_refs(v, defs) for k, v in schema.items()}
1103
+ elif isinstance(schema, list):
1104
+ return [self._resolve_schema_refs(item, defs) for item in schema]
1105
+ else:
1106
+ return schema
1107
+
1108
+ def _add_response_format_to_system(self, params: dict[str, Any]):
1109
+ """Add prompt that will generate properly formatted JSON for structured outputs to system parameter.
1110
+
1111
+ Based on Anthropic's JSON Mode cookbook, we ask the LLM to put the JSON within <json_response> tags.
1112
+
1113
+ Args:
1114
+ params (dict): The client parameters
1115
+ """
1116
+ # Get the schema of the Pydantic model
1117
+ if isinstance(self._response_format, dict):
1118
+ schema = self._response_format
1119
+ else:
1120
+ # Use mode='serialization' and ref_template='{model}' to get a flatter, more LLM-friendly schema
1121
+ schema = self._response_format.model_json_schema(mode="serialization", ref_template="{model}")
1122
+
1123
+ # Resolve $ref references for simpler schema
1124
+ if "$defs" in schema:
1125
+ defs = schema.pop("$defs")
1126
+ schema = self._resolve_schema_refs(schema, defs)
1127
+
1128
+ # Add instructions for JSON formatting
1129
+ # Generate an example based on the actual schema
1130
+ def generate_example(schema_dict: dict[str, Any]) -> dict[str, Any]:
1131
+ """Generate example data from schema."""
1132
+ example = {}
1133
+ properties = schema_dict.get("properties", {})
1134
+ for prop_name, prop_schema in properties.items():
1135
+ prop_type = prop_schema.get("type", "string")
1136
+ if prop_type == "string":
1137
+ example[prop_name] = f"example {prop_name}"
1138
+ elif prop_type == "integer":
1139
+ example[prop_name] = 42
1140
+ elif prop_type == "number":
1141
+ example[prop_name] = 42.0
1142
+ elif prop_type == "boolean":
1143
+ example[prop_name] = True
1144
+ elif prop_type == "array":
1145
+ items_schema = prop_schema.get("items", {})
1146
+ items_type = items_schema.get("type", "string")
1147
+ if items_type == "string":
1148
+ example[prop_name] = ["item1", "item2"]
1149
+ elif items_type == "object":
1150
+ example[prop_name] = [generate_example(items_schema)]
1151
+ else:
1152
+ example[prop_name] = []
1153
+ elif prop_type == "object":
1154
+ example[prop_name] = generate_example(prop_schema)
1155
+ else:
1156
+ example[prop_name] = f"example {prop_name}"
1157
+ return example
1158
+
1159
+ example_data = generate_example(schema)
1160
+ example_json = json.dumps(example_data, indent=2)
1161
+
1162
+ format_content = f"""You must respond with a valid JSON object that matches this structure (do NOT return the schema itself):
1163
+ {json.dumps(schema, indent=2)}
1164
+
1165
+ IMPORTANT: Put your actual response data (not the schema) inside <json_response> tags.
1166
+
1167
+ Correct example format:
1168
+ <json_response>
1169
+ {example_json}
1170
+ </json_response>
1171
+
1172
+ WRONG: Do not return the schema definition itself.
1173
+
1174
+ Your JSON must:
1175
+ 1. Match the schema structure above
1176
+ 2. Contain actual data values, not schema descriptions
1177
+ 3. Be valid, parseable JSON"""
1178
+
1179
+ # Add formatting to system message (create one if it doesn't exist)
1180
+ if "system" in params:
1181
+ params["system"] = params["system"] + "\n\n" + format_content
1182
+ else:
1183
+ params["system"] = format_content
1184
+
1185
+ def _extract_json_response(self, response: Message) -> Any:
1186
+ """Extract and validate JSON response from the output for structured outputs.
1187
+
1188
+ Args:
1189
+ response (Message): The response from the API.
1190
+
1191
+ Returns:
1192
+ Any: The parsed JSON response.
1193
+ """
1194
+ if not self._response_format:
1195
+ return response
1196
+
1197
+ # Extract content from response - check both thinking and text blocks
1198
+ content = ""
1199
+ if response.content:
1200
+ for block in response.content:
1201
+ if _is_thinking_block(block):
1202
+ content = block.thinking
1203
+ break
1204
+ elif _is_text_block(block):
1205
+ content = block.text
1206
+ break
1207
+
1208
+ # Try to extract JSON from tags first
1209
+ json_match = re.search(r"<json_response>(.*?)</json_response>", content, re.DOTALL)
1210
+ if json_match:
1211
+ json_str = json_match.group(1).strip()
1212
+ else:
1213
+ # Fallback to finding first JSON object
1214
+ json_start = content.find("{")
1215
+ json_end = content.rfind("}")
1216
+ if json_start == -1 or json_end == -1:
1217
+ raise ValueError("No valid JSON found in response for Structured Output.")
1218
+ json_str = content[json_start : json_end + 1]
1219
+
1220
+ try:
1221
+ # Parse JSON and validate against the Pydantic model if Pydantic model was provided
1222
+ json_data = json.loads(json_str)
1223
+ if isinstance(self._response_format, dict):
1224
+ return json_str
1225
+ else:
1226
+ return self._response_format.model_validate(json_data)
1227
+
1228
+ except Exception as e:
1229
+ raise ValueError(f"Failed to parse response as valid JSON matching the schema for Structured Output: {e!s}")
1230
+
1231
+
1232
+ def _format_json_response(response: Any) -> str:
1233
+ """Formats the JSON response for structured outputs using the format method if it exists."""
1234
+ if isinstance(response, str):
1235
+ return response
1236
+ elif isinstance(response, FormatterProtocol):
1237
+ return response.format()
1238
+ else:
1239
+ return response.model_dump_json()
1240
+
1241
+
1242
+ def process_image_content(content_item: dict[str, Any]) -> dict[str, Any]:
1243
+ """Process an OpenAI image content item into Claude format."""
1244
+ if content_item["type"] != "image_url":
1245
+ return content_item
1246
+
1247
+ url = content_item["image_url"]["url"]
1248
+ try:
1249
+ # Handle data URLs
1250
+ if url.startswith("data:"):
1251
+ data_url_pattern = r"data:image/([a-zA-Z]+);base64,(.+)"
1252
+ match = re.match(data_url_pattern, url)
1253
+ if match:
1254
+ media_type, base64_data = match.groups()
1255
+ return {
1256
+ "type": "image",
1257
+ "source": {"type": "base64", "media_type": f"image/{media_type}", "data": base64_data},
1258
+ }
1259
+
1260
+ else:
1261
+ print("Error processing image.")
1262
+ # Return original content if image processing fails
1263
+ return content_item
1264
+
1265
+ except Exception as e:
1266
+ print(f"Error processing image image: {e}")
1267
+ # Return original content if image processing fails
1268
+ return content_item
1269
+
1270
+
1271
+ def process_message_content(message: dict[str, Any]) -> str | list[dict[str, Any]]:
1272
+ """Process message content, handling both string and list formats with images."""
1273
+ content = message.get("content", "")
1274
+
1275
+ # Handle empty content
1276
+ if content == "":
1277
+ return content
1278
+
1279
+ # If content is already a string, return as is
1280
+ if isinstance(content, str):
1281
+ return content
1282
+
1283
+ # Handle list content (mixed text and images)
1284
+ if isinstance(content, list):
1285
+ processed_content = []
1286
+ for item in content:
1287
+ if item["type"] == "text":
1288
+ processed_content.append({"type": "text", "text": item["text"]})
1289
+ elif item["type"] == "image_url":
1290
+ processed_content.append(process_image_content(item))
1291
+ return processed_content
1292
+
1293
+ return content
1294
+
1295
+
1296
+ def _extract_system_message(message: dict[str, Any], params: dict[str, Any]) -> None:
1297
+ """Extract system message content and add to params['system'].
1298
+
1299
+ System messages are handled specially in Anthropic API - they're passed as a separate
1300
+ 'system' parameter rather than in the messages list.
1301
+
1302
+ Args:
1303
+ message: Message dict with role='system'
1304
+ params: Params dict to update with system content (modified in place)
1305
+ """
1306
+ content = process_message_content(message)
1307
+ if isinstance(content, list):
1308
+ # For system messages with images, concatenate only the text portions
1309
+ text_content = " ".join(item.get("text", "") for item in content if item.get("type") == "text")
1310
+ params["system"] = params.get("system", "") + (" " if "system" in params else "") + text_content
1311
+ else:
1312
+ params["system"] = params.get("system", "") + ("\n" if "system" in params else "") + content
1313
+
1314
+
1315
+ def _convert_tool_call_message(
1316
+ message: dict[str, Any],
1317
+ has_tools: bool,
1318
+ expected_role: str,
1319
+ user_continue_message: dict[str, str],
1320
+ processed_messages: list[dict[str, Any]],
1321
+ ) -> tuple[int, int | None]:
1322
+ """Convert OpenAI tool_calls format to Anthropic ToolUseBlock format.
1323
+
1324
+ Args:
1325
+ message: Message dict containing 'tool_calls'
1326
+ has_tools: Whether tools parameter is present in request
1327
+ expected_role: Expected role based on message alternation ("user" or "assistant")
1328
+ user_continue_message: Standard continue message for role alternation
1329
+ processed_messages: List to append converted messages to (modified in place)
1330
+
1331
+ Returns:
1332
+ Tuple of (tool_use_messages_count, last_tool_use_index)
1333
+ - tool_use_messages_count: Number of tool use messages added (0 or count)
1334
+ - last_tool_use_index: Index of last tool use message, or None if not using tools
1335
+ """
1336
+ # Map the tool call options to Anthropic's ToolUseBlock
1337
+ tool_uses = []
1338
+ tool_names = []
1339
+ tool_use_count = 0
1340
+
1341
+ for tool_call in message["tool_calls"]:
1342
+ tool_uses.append(
1343
+ ToolUseBlock(
1344
+ type="tool_use",
1345
+ id=tool_call["id"],
1346
+ name=tool_call["function"]["name"],
1347
+ input=json.loads(tool_call["function"]["arguments"]),
1348
+ )
1349
+ )
1350
+ if has_tools:
1351
+ tool_use_count += 1
1352
+ tool_names.append(tool_call["function"]["name"])
1353
+
1354
+ # Ensure role alternation: if we expect user, insert user continue message
1355
+ if expected_role == "user":
1356
+ processed_messages.append(user_continue_message)
1357
+
1358
+ # Add tool use message (format depends on whether tools are enabled)
1359
+ if has_tools:
1360
+ processed_messages.append({"role": "assistant", "content": tool_uses})
1361
+ last_tool_use_index = len(processed_messages) - 1
1362
+ return tool_use_count, last_tool_use_index
1363
+ else:
1364
+ # Not using tools, so put in a plain text message
1365
+ processed_messages.append({
1366
+ "role": "assistant",
1367
+ "content": f"Some internal function(s) that could be used: [{', '.join(tool_names)}]",
1368
+ })
1369
+ return 0, None
1370
+
1371
+
1372
+ def _convert_tool_result_message(
1373
+ message: dict[str, Any],
1374
+ has_tools: bool,
1375
+ expected_role: str,
1376
+ assistant_continue_message: dict[str, str],
1377
+ processed_messages: list[dict[str, Any]],
1378
+ last_tool_result_index: int,
1379
+ ) -> tuple[int, int]:
1380
+ """Convert OpenAI tool result format to Anthropic tool_result format.
1381
+
1382
+ Args:
1383
+ message: Message dict containing 'tool_call_id'
1384
+ has_tools: Whether tools parameter is present in request
1385
+ expected_role: Expected role based on message alternation ("user" or "assistant")
1386
+ assistant_continue_message: Standard continue message for role alternation
1387
+ processed_messages: List to append converted messages to (modified in place)
1388
+ last_tool_result_index: Index of last tool result message (-1 if none)
1389
+
1390
+ Returns:
1391
+ Tuple of (tool_result_messages_count, updated_last_tool_result_index)
1392
+ - tool_result_messages_count: 1 if tool result added, 0 otherwise
1393
+ - updated_last_tool_result_index: New index of last tool result message
1394
+ """
1395
+ if has_tools:
1396
+ # Map the tool usage call to tool_result for Anthropic
1397
+ tool_result = {
1398
+ "type": "tool_result",
1399
+ "tool_use_id": message["tool_call_id"],
1400
+ "content": message["content"],
1401
+ }
1402
+
1403
+ # If the previous message also had a tool_result, add it to that
1404
+ # Otherwise append a new message
1405
+ if last_tool_result_index == len(processed_messages) - 1:
1406
+ processed_messages[-1]["content"].append(tool_result)
1407
+ else:
1408
+ if expected_role == "assistant":
1409
+ # Insert an extra assistant message as we will append a user message
1410
+ processed_messages.append(assistant_continue_message)
1411
+
1412
+ processed_messages.append({"role": "user", "content": [tool_result]})
1413
+ last_tool_result_index = len(processed_messages) - 1
1414
+
1415
+ return 1, last_tool_result_index
1416
+ else:
1417
+ # Not using tools, so put in a plain text message
1418
+ processed_messages.append({
1419
+ "role": "user",
1420
+ "content": f"Running the function returned: {message['content']}",
1421
+ })
1422
+ return 0, last_tool_result_index
1423
+
1424
+
1425
+ @require_optional_import("anthropic", "anthropic")
1426
+ def oai_messages_to_anthropic_messages(params: dict[str, Any]) -> list[dict[str, Any]]:
1427
+ """Convert messages from OAI format to Anthropic format.
1428
+ We correct for any specific role orders and types, etc.
1429
+ """
1430
+ # Track whether we have tools passed in. If not, tool use / result messages should be converted to text messages.
1431
+ # Anthropic requires a tools parameter with the tools listed, if there are other messages with tool use or tool results.
1432
+ # This can occur when we don't need tool calling, such as for group chat speaker selection.
1433
+ has_tools = "tools" in params
1434
+
1435
+ # Convert messages to Anthropic compliant format
1436
+ processed_messages = []
1437
+
1438
+ # Used to interweave user messages to ensure user/assistant alternating
1439
+ user_continue_message = {"content": "Please continue.", "role": "user"}
1440
+ assistant_continue_message = {"content": "Please continue.", "role": "assistant"}
1441
+
1442
+ tool_use_messages = 0
1443
+ tool_result_messages = 0
1444
+ last_tool_use_index = -1
1445
+ last_tool_result_index = -1
1446
+ for message in params["messages"]:
1447
+ if message["role"] == "system":
1448
+ _extract_system_message(message, params)
1449
+ else:
1450
+ # New messages will be added here, manage role alternations
1451
+ expected_role = "user" if len(processed_messages) % 2 == 0 else "assistant"
1452
+
1453
+ if "tool_calls" in message:
1454
+ # Convert OpenAI tool_calls to Anthropic ToolUseBlock format
1455
+ count, index = _convert_tool_call_message(
1456
+ message, has_tools, expected_role, user_continue_message, processed_messages
1457
+ )
1458
+ tool_use_messages += count
1459
+ if index is not None:
1460
+ last_tool_use_index = index
1461
+ elif "tool_call_id" in message:
1462
+ # Convert OpenAI tool result to Anthropic tool_result format
1463
+ count, last_tool_result_index = _convert_tool_result_message(
1464
+ message,
1465
+ has_tools,
1466
+ expected_role,
1467
+ assistant_continue_message,
1468
+ processed_messages,
1469
+ last_tool_result_index,
1470
+ )
1471
+ tool_result_messages += count
1472
+ elif message["content"] == "":
1473
+ # Ignoring empty messages
1474
+ pass
1475
+ else:
1476
+ if expected_role != message["role"]:
1477
+ # Inserting the alternating continue message
1478
+ processed_messages.append(
1479
+ user_continue_message if expected_role == "user" else assistant_continue_message
1480
+ )
1481
+ # Process messages for images
1482
+ processed_content = process_message_content(message)
1483
+ processed_message = message.copy()
1484
+ processed_message["content"] = processed_content
1485
+ processed_messages.append(processed_message)
1486
+
1487
+ # We'll replace the last tool_use if there's no tool_result (occurs if we finish the conversation before running the function)
1488
+ if has_tools and tool_use_messages != tool_result_messages:
1489
+ processed_messages[last_tool_use_index] = assistant_continue_message
1490
+
1491
+ # name is not a valid field on messages
1492
+ for message in processed_messages:
1493
+ if "name" in message:
1494
+ message.pop("name", None)
1495
+
1496
+ # Note: When using reflection_with_llm we may end up with an "assistant" message as the last message and that may cause a blank response
1497
+ # So, if the last role is not user, add a 'user' continue message at the end
1498
+ if processed_messages[-1]["role"] != "user":
1499
+ processed_messages.append(user_continue_message)
1500
+
1501
+ return processed_messages
1502
+
1503
+
1504
+ def _calculate_cost(input_tokens: int, output_tokens: int, model: str) -> float:
1505
+ """Calculate the cost of the completion using the Anthropic pricing."""
1506
+ total = 0.0
1507
+
1508
+ if model in ANTHROPIC_PRICING_1k:
1509
+ input_cost_per_1k, output_cost_per_1k = ANTHROPIC_PRICING_1k[model]
1510
+ input_cost = (input_tokens / 1000) * input_cost_per_1k
1511
+ output_cost = (output_tokens / 1000) * output_cost_per_1k
1512
+ total = input_cost + output_cost
1513
+ else:
1514
+ warnings.warn(f"Cost calculation not available for model {model}", UserWarning)
1515
+
1516
+ return total