ag2 0.9.1a1__py3-none-any.whl → 0.9.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.

Potentially problematic release.


This version of ag2 might be problematic. Click here for more details.

Files changed (371) hide show
  1. {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info}/METADATA +272 -75
  2. ag2-0.9.2.dist-info/RECORD +406 -0
  3. {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info}/WHEEL +1 -2
  4. autogen/__init__.py +89 -0
  5. autogen/_website/__init__.py +3 -0
  6. autogen/_website/generate_api_references.py +427 -0
  7. autogen/_website/generate_mkdocs.py +1174 -0
  8. autogen/_website/notebook_processor.py +476 -0
  9. autogen/_website/process_notebooks.py +656 -0
  10. autogen/_website/utils.py +412 -0
  11. autogen/agentchat/__init__.py +44 -0
  12. autogen/agentchat/agent.py +182 -0
  13. autogen/agentchat/assistant_agent.py +85 -0
  14. autogen/agentchat/chat.py +309 -0
  15. autogen/agentchat/contrib/__init__.py +5 -0
  16. autogen/agentchat/contrib/agent_eval/README.md +7 -0
  17. autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
  18. autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
  19. autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
  20. autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
  21. autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
  22. autogen/agentchat/contrib/agent_eval/task.py +42 -0
  23. autogen/agentchat/contrib/agent_optimizer.py +429 -0
  24. autogen/agentchat/contrib/capabilities/__init__.py +5 -0
  25. autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
  26. autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
  27. autogen/agentchat/contrib/capabilities/teachability.py +393 -0
  28. autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
  29. autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
  30. autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
  31. autogen/agentchat/contrib/capabilities/transforms.py +566 -0
  32. autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
  33. autogen/agentchat/contrib/capabilities/vision_capability.py +214 -0
  34. autogen/agentchat/contrib/captainagent/__init__.py +9 -0
  35. autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
  36. autogen/agentchat/contrib/captainagent/captainagent.py +512 -0
  37. autogen/agentchat/contrib/captainagent/tool_retriever.py +335 -0
  38. autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
  39. autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
  40. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
  41. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
  42. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
  43. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
  44. autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
  45. autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
  46. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
  47. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
  48. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
  49. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
  50. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
  51. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
  52. autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
  53. autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
  54. autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
  55. autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
  56. autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
  57. autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
  58. autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
  59. autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
  60. autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
  61. autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
  62. autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
  63. autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
  64. autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
  65. autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
  66. autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
  67. autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
  68. autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
  69. autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
  70. autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
  71. autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
  72. autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
  73. autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
  74. autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
  75. autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
  76. autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
  77. autogen/agentchat/contrib/graph_rag/document.py +29 -0
  78. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +170 -0
  79. autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
  80. autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
  81. autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
  82. autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +268 -0
  83. autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
  84. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
  85. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
  86. autogen/agentchat/contrib/img_utils.py +397 -0
  87. autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
  88. autogen/agentchat/contrib/llava_agent.py +187 -0
  89. autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
  90. autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
  91. autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +324 -0
  92. autogen/agentchat/contrib/rag/__init__.py +10 -0
  93. autogen/agentchat/contrib/rag/chromadb_query_engine.py +272 -0
  94. autogen/agentchat/contrib/rag/llamaindex_query_engine.py +198 -0
  95. autogen/agentchat/contrib/rag/mongodb_query_engine.py +329 -0
  96. autogen/agentchat/contrib/rag/query_engine.py +74 -0
  97. autogen/agentchat/contrib/retrieve_assistant_agent.py +56 -0
  98. autogen/agentchat/contrib/retrieve_user_proxy_agent.py +703 -0
  99. autogen/agentchat/contrib/society_of_mind_agent.py +199 -0
  100. autogen/agentchat/contrib/swarm_agent.py +1425 -0
  101. autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
  102. autogen/agentchat/contrib/vectordb/__init__.py +5 -0
  103. autogen/agentchat/contrib/vectordb/base.py +232 -0
  104. autogen/agentchat/contrib/vectordb/chromadb.py +315 -0
  105. autogen/agentchat/contrib/vectordb/couchbase.py +407 -0
  106. autogen/agentchat/contrib/vectordb/mongodb.py +550 -0
  107. autogen/agentchat/contrib/vectordb/pgvectordb.py +928 -0
  108. autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
  109. autogen/agentchat/contrib/vectordb/utils.py +126 -0
  110. autogen/agentchat/contrib/web_surfer.py +303 -0
  111. autogen/agentchat/conversable_agent.py +4023 -0
  112. autogen/agentchat/group/__init__.py +64 -0
  113. autogen/agentchat/group/available_condition.py +91 -0
  114. autogen/agentchat/group/context_condition.py +77 -0
  115. autogen/agentchat/group/context_expression.py +238 -0
  116. autogen/agentchat/group/context_str.py +41 -0
  117. autogen/agentchat/group/context_variables.py +192 -0
  118. autogen/agentchat/group/group_tool_executor.py +202 -0
  119. autogen/agentchat/group/group_utils.py +591 -0
  120. autogen/agentchat/group/handoffs.py +244 -0
  121. autogen/agentchat/group/llm_condition.py +93 -0
  122. autogen/agentchat/group/multi_agent_chat.py +237 -0
  123. autogen/agentchat/group/on_condition.py +58 -0
  124. autogen/agentchat/group/on_context_condition.py +54 -0
  125. autogen/agentchat/group/patterns/__init__.py +18 -0
  126. autogen/agentchat/group/patterns/auto.py +159 -0
  127. autogen/agentchat/group/patterns/manual.py +176 -0
  128. autogen/agentchat/group/patterns/pattern.py +288 -0
  129. autogen/agentchat/group/patterns/random.py +106 -0
  130. autogen/agentchat/group/patterns/round_robin.py +117 -0
  131. autogen/agentchat/group/reply_result.py +26 -0
  132. autogen/agentchat/group/speaker_selection_result.py +41 -0
  133. autogen/agentchat/group/targets/__init__.py +4 -0
  134. autogen/agentchat/group/targets/group_chat_target.py +132 -0
  135. autogen/agentchat/group/targets/group_manager_target.py +151 -0
  136. autogen/agentchat/group/targets/transition_target.py +413 -0
  137. autogen/agentchat/group/targets/transition_utils.py +6 -0
  138. autogen/agentchat/groupchat.py +1694 -0
  139. autogen/agentchat/realtime/__init__.py +3 -0
  140. autogen/agentchat/realtime/experimental/__init__.py +20 -0
  141. autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
  142. autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
  143. autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
  144. autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
  145. autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
  146. autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
  147. autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
  148. autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
  149. autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
  150. autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
  151. autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
  152. autogen/agentchat/realtime/experimental/clients/realtime_client.py +190 -0
  153. autogen/agentchat/realtime/experimental/function_observer.py +85 -0
  154. autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
  155. autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
  156. autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
  157. autogen/agentchat/realtime/experimental/realtime_swarm.py +475 -0
  158. autogen/agentchat/realtime/experimental/websockets.py +21 -0
  159. autogen/agentchat/realtime_agent/__init__.py +21 -0
  160. autogen/agentchat/user_proxy_agent.py +111 -0
  161. autogen/agentchat/utils.py +206 -0
  162. autogen/agents/__init__.py +3 -0
  163. autogen/agents/contrib/__init__.py +10 -0
  164. autogen/agents/contrib/time/__init__.py +8 -0
  165. autogen/agents/contrib/time/time_reply_agent.py +73 -0
  166. autogen/agents/contrib/time/time_tool_agent.py +51 -0
  167. autogen/agents/experimental/__init__.py +27 -0
  168. autogen/agents/experimental/deep_research/__init__.py +7 -0
  169. autogen/agents/experimental/deep_research/deep_research.py +52 -0
  170. autogen/agents/experimental/discord/__init__.py +7 -0
  171. autogen/agents/experimental/discord/discord.py +66 -0
  172. autogen/agents/experimental/document_agent/__init__.py +19 -0
  173. autogen/agents/experimental/document_agent/chroma_query_engine.py +316 -0
  174. autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +118 -0
  175. autogen/agents/experimental/document_agent/document_agent.py +461 -0
  176. autogen/agents/experimental/document_agent/document_conditions.py +50 -0
  177. autogen/agents/experimental/document_agent/document_utils.py +380 -0
  178. autogen/agents/experimental/document_agent/inmemory_query_engine.py +220 -0
  179. autogen/agents/experimental/document_agent/parser_utils.py +130 -0
  180. autogen/agents/experimental/document_agent/url_utils.py +426 -0
  181. autogen/agents/experimental/reasoning/__init__.py +7 -0
  182. autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
  183. autogen/agents/experimental/slack/__init__.py +7 -0
  184. autogen/agents/experimental/slack/slack.py +73 -0
  185. autogen/agents/experimental/telegram/__init__.py +7 -0
  186. autogen/agents/experimental/telegram/telegram.py +77 -0
  187. autogen/agents/experimental/websurfer/__init__.py +7 -0
  188. autogen/agents/experimental/websurfer/websurfer.py +62 -0
  189. autogen/agents/experimental/wikipedia/__init__.py +7 -0
  190. autogen/agents/experimental/wikipedia/wikipedia.py +90 -0
  191. autogen/browser_utils.py +309 -0
  192. autogen/cache/__init__.py +10 -0
  193. autogen/cache/abstract_cache_base.py +75 -0
  194. autogen/cache/cache.py +203 -0
  195. autogen/cache/cache_factory.py +88 -0
  196. autogen/cache/cosmos_db_cache.py +144 -0
  197. autogen/cache/disk_cache.py +102 -0
  198. autogen/cache/in_memory_cache.py +58 -0
  199. autogen/cache/redis_cache.py +123 -0
  200. autogen/code_utils.py +596 -0
  201. autogen/coding/__init__.py +22 -0
  202. autogen/coding/base.py +119 -0
  203. autogen/coding/docker_commandline_code_executor.py +268 -0
  204. autogen/coding/factory.py +47 -0
  205. autogen/coding/func_with_reqs.py +202 -0
  206. autogen/coding/jupyter/__init__.py +23 -0
  207. autogen/coding/jupyter/base.py +36 -0
  208. autogen/coding/jupyter/docker_jupyter_server.py +167 -0
  209. autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
  210. autogen/coding/jupyter/import_utils.py +82 -0
  211. autogen/coding/jupyter/jupyter_client.py +231 -0
  212. autogen/coding/jupyter/jupyter_code_executor.py +160 -0
  213. autogen/coding/jupyter/local_jupyter_server.py +172 -0
  214. autogen/coding/local_commandline_code_executor.py +405 -0
  215. autogen/coding/markdown_code_extractor.py +45 -0
  216. autogen/coding/utils.py +56 -0
  217. autogen/doc_utils.py +34 -0
  218. autogen/events/__init__.py +7 -0
  219. autogen/events/agent_events.py +1013 -0
  220. autogen/events/base_event.py +99 -0
  221. autogen/events/client_events.py +167 -0
  222. autogen/events/helpers.py +36 -0
  223. autogen/events/print_event.py +46 -0
  224. autogen/exception_utils.py +73 -0
  225. autogen/extensions/__init__.py +5 -0
  226. autogen/fast_depends/__init__.py +16 -0
  227. autogen/fast_depends/_compat.py +80 -0
  228. autogen/fast_depends/core/__init__.py +14 -0
  229. autogen/fast_depends/core/build.py +225 -0
  230. autogen/fast_depends/core/model.py +576 -0
  231. autogen/fast_depends/dependencies/__init__.py +15 -0
  232. autogen/fast_depends/dependencies/model.py +29 -0
  233. autogen/fast_depends/dependencies/provider.py +39 -0
  234. autogen/fast_depends/library/__init__.py +10 -0
  235. autogen/fast_depends/library/model.py +46 -0
  236. autogen/fast_depends/py.typed +6 -0
  237. autogen/fast_depends/schema.py +66 -0
  238. autogen/fast_depends/use.py +280 -0
  239. autogen/fast_depends/utils.py +187 -0
  240. autogen/formatting_utils.py +83 -0
  241. autogen/function_utils.py +13 -0
  242. autogen/graph_utils.py +178 -0
  243. autogen/import_utils.py +526 -0
  244. autogen/interop/__init__.py +22 -0
  245. autogen/interop/crewai/__init__.py +7 -0
  246. autogen/interop/crewai/crewai.py +88 -0
  247. autogen/interop/interoperability.py +71 -0
  248. autogen/interop/interoperable.py +46 -0
  249. autogen/interop/langchain/__init__.py +8 -0
  250. autogen/interop/langchain/langchain_chat_model_factory.py +155 -0
  251. autogen/interop/langchain/langchain_tool.py +82 -0
  252. autogen/interop/litellm/__init__.py +7 -0
  253. autogen/interop/litellm/litellm_config_factory.py +179 -0
  254. autogen/interop/pydantic_ai/__init__.py +7 -0
  255. autogen/interop/pydantic_ai/pydantic_ai.py +168 -0
  256. autogen/interop/registry.py +69 -0
  257. autogen/io/__init__.py +15 -0
  258. autogen/io/base.py +151 -0
  259. autogen/io/console.py +56 -0
  260. autogen/io/processors/__init__.py +12 -0
  261. autogen/io/processors/base.py +21 -0
  262. autogen/io/processors/console_event_processor.py +56 -0
  263. autogen/io/run_response.py +293 -0
  264. autogen/io/thread_io_stream.py +63 -0
  265. autogen/io/websockets.py +213 -0
  266. autogen/json_utils.py +43 -0
  267. autogen/llm_config.py +382 -0
  268. autogen/logger/__init__.py +11 -0
  269. autogen/logger/base_logger.py +128 -0
  270. autogen/logger/file_logger.py +261 -0
  271. autogen/logger/logger_factory.py +42 -0
  272. autogen/logger/logger_utils.py +57 -0
  273. autogen/logger/sqlite_logger.py +523 -0
  274. autogen/math_utils.py +339 -0
  275. autogen/mcp/__init__.py +7 -0
  276. autogen/mcp/__main__.py +78 -0
  277. autogen/mcp/mcp_client.py +208 -0
  278. autogen/mcp/mcp_proxy/__init__.py +19 -0
  279. autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +63 -0
  280. autogen/mcp/mcp_proxy/mcp_proxy.py +581 -0
  281. autogen/mcp/mcp_proxy/operation_grouping.py +158 -0
  282. autogen/mcp/mcp_proxy/operation_renaming.py +114 -0
  283. autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py +98 -0
  284. autogen/mcp/mcp_proxy/security.py +400 -0
  285. autogen/mcp/mcp_proxy/security_schema_visitor.py +37 -0
  286. autogen/messages/__init__.py +7 -0
  287. autogen/messages/agent_messages.py +948 -0
  288. autogen/messages/base_message.py +107 -0
  289. autogen/messages/client_messages.py +171 -0
  290. autogen/messages/print_message.py +49 -0
  291. autogen/oai/__init__.py +53 -0
  292. autogen/oai/anthropic.py +714 -0
  293. autogen/oai/bedrock.py +628 -0
  294. autogen/oai/cerebras.py +299 -0
  295. autogen/oai/client.py +1444 -0
  296. autogen/oai/client_utils.py +169 -0
  297. autogen/oai/cohere.py +479 -0
  298. autogen/oai/gemini.py +998 -0
  299. autogen/oai/gemini_types.py +155 -0
  300. autogen/oai/groq.py +305 -0
  301. autogen/oai/mistral.py +303 -0
  302. autogen/oai/oai_models/__init__.py +11 -0
  303. autogen/oai/oai_models/_models.py +16 -0
  304. autogen/oai/oai_models/chat_completion.py +87 -0
  305. autogen/oai/oai_models/chat_completion_audio.py +32 -0
  306. autogen/oai/oai_models/chat_completion_message.py +86 -0
  307. autogen/oai/oai_models/chat_completion_message_tool_call.py +37 -0
  308. autogen/oai/oai_models/chat_completion_token_logprob.py +63 -0
  309. autogen/oai/oai_models/completion_usage.py +60 -0
  310. autogen/oai/ollama.py +643 -0
  311. autogen/oai/openai_utils.py +881 -0
  312. autogen/oai/together.py +370 -0
  313. autogen/retrieve_utils.py +491 -0
  314. autogen/runtime_logging.py +160 -0
  315. autogen/token_count_utils.py +267 -0
  316. autogen/tools/__init__.py +20 -0
  317. autogen/tools/contrib/__init__.py +9 -0
  318. autogen/tools/contrib/time/__init__.py +7 -0
  319. autogen/tools/contrib/time/time.py +41 -0
  320. autogen/tools/dependency_injection.py +254 -0
  321. autogen/tools/experimental/__init__.py +48 -0
  322. autogen/tools/experimental/browser_use/__init__.py +7 -0
  323. autogen/tools/experimental/browser_use/browser_use.py +161 -0
  324. autogen/tools/experimental/crawl4ai/__init__.py +7 -0
  325. autogen/tools/experimental/crawl4ai/crawl4ai.py +153 -0
  326. autogen/tools/experimental/deep_research/__init__.py +7 -0
  327. autogen/tools/experimental/deep_research/deep_research.py +328 -0
  328. autogen/tools/experimental/duckduckgo/__init__.py +7 -0
  329. autogen/tools/experimental/duckduckgo/duckduckgo_search.py +109 -0
  330. autogen/tools/experimental/google/__init__.py +14 -0
  331. autogen/tools/experimental/google/authentication/__init__.py +11 -0
  332. autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
  333. autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
  334. autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
  335. autogen/tools/experimental/google/drive/__init__.py +9 -0
  336. autogen/tools/experimental/google/drive/drive_functions.py +124 -0
  337. autogen/tools/experimental/google/drive/toolkit.py +88 -0
  338. autogen/tools/experimental/google/model.py +17 -0
  339. autogen/tools/experimental/google/toolkit_protocol.py +19 -0
  340. autogen/tools/experimental/google_search/__init__.py +8 -0
  341. autogen/tools/experimental/google_search/google_search.py +93 -0
  342. autogen/tools/experimental/google_search/youtube_search.py +181 -0
  343. autogen/tools/experimental/messageplatform/__init__.py +17 -0
  344. autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
  345. autogen/tools/experimental/messageplatform/discord/discord.py +288 -0
  346. autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
  347. autogen/tools/experimental/messageplatform/slack/slack.py +391 -0
  348. autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
  349. autogen/tools/experimental/messageplatform/telegram/telegram.py +275 -0
  350. autogen/tools/experimental/perplexity/__init__.py +7 -0
  351. autogen/tools/experimental/perplexity/perplexity_search.py +260 -0
  352. autogen/tools/experimental/reliable/__init__.py +10 -0
  353. autogen/tools/experimental/reliable/reliable.py +1316 -0
  354. autogen/tools/experimental/tavily/__init__.py +7 -0
  355. autogen/tools/experimental/tavily/tavily_search.py +183 -0
  356. autogen/tools/experimental/web_search_preview/__init__.py +7 -0
  357. autogen/tools/experimental/web_search_preview/web_search_preview.py +114 -0
  358. autogen/tools/experimental/wikipedia/__init__.py +7 -0
  359. autogen/tools/experimental/wikipedia/wikipedia.py +287 -0
  360. autogen/tools/function_utils.py +411 -0
  361. autogen/tools/tool.py +187 -0
  362. autogen/tools/toolkit.py +86 -0
  363. autogen/types.py +29 -0
  364. autogen/version.py +7 -0
  365. templates/client_template/main.jinja2 +69 -0
  366. templates/config_template/config.jinja2 +7 -0
  367. templates/main.jinja2 +61 -0
  368. ag2-0.9.1a1.dist-info/RECORD +0 -6
  369. ag2-0.9.1a1.dist-info/top_level.txt +0 -1
  370. {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info/licenses}/LICENSE +0 -0
  371. {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info/licenses}/NOTICE.md +0 -0
@@ -0,0 +1,1316 @@
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
+
8
+ import asyncio
9
+ import contextlib
10
+ import copy
11
+ import functools
12
+ import inspect
13
+ import json
14
+ import logging
15
+ import time
16
+ import warnings
17
+ from typing import Annotated, Any, Callable, Dict, List, Optional, Tuple, Type, Union
18
+
19
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError
20
+
21
+ from ....agentchat import ChatResult, initiate_group_chat
22
+ from ....agentchat.agent import Agent
23
+ from ....agentchat.conversable_agent import ConversableAgent
24
+ from ....agentchat.group import AgentTarget, ReplyResult, TerminateTarget
25
+ from ....agentchat.group.context_variables import ContextVariables
26
+ from ....agentchat.group.patterns import DefaultPattern
27
+ from ....doc_utils import export_module
28
+ from ....llm_config import LLMConfig
29
+ from ....tools.dependency_injection import Field as AG2Field
30
+ from ....tools.tool import Tool
31
+
32
+ __all__ = ("ReliableTool", "ReliableToolError", "SuccessfulExecutionParameters", "ToolExecutionDetails")
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ HYPOTHESIS_DESCRIPTION = (
37
+ "A clear, concise statement about the expected outcome or result format of the function call "
38
+ "based on the provided inputs. This helps in assessing the relevance and potential success "
39
+ "of the call, and guides validation."
40
+ )
41
+
42
+
43
+ class ValidationResult(BaseModel):
44
+ """Represents the outcome of a single validation step."""
45
+
46
+ model_config = ConfigDict(extra="forbid")
47
+ validation_result: bool
48
+ justification: str
49
+
50
+ def __str__(self) -> str:
51
+ status = "Passed" if self.validation_result else "Failed"
52
+ return f"Validation Result: {status}\nJustification: {self.justification}"
53
+
54
+ def format(self) -> str:
55
+ """Returns the JSON representation for AutoGen compatibility."""
56
+ return self.model_dump_json()
57
+
58
+
59
+ class ExecutionAttempt(BaseModel):
60
+ """Stores the state of a single attempt to execute and validate the function."""
61
+
62
+ model_config = ConfigDict(arbitrary_types_allowed=True)
63
+ timestamp: float = Field(default_factory=time.time)
64
+ attempt_args: List[Any] = Field(default_factory=list)
65
+ attempt_kwargs: Dict[str, Any] = Field(default_factory=dict)
66
+ hypothesis: Optional[str] = None
67
+ error: Optional[str] = None
68
+ result_data: Optional[Any] = None
69
+ result_str: Optional[str] = None
70
+ validation: Optional[ValidationResult] = None
71
+
72
+ @property
73
+ def did_execute_successfully(self) -> bool:
74
+ """Check if the attempt executed without raising an error."""
75
+ return self.error is None
76
+
77
+ @property
78
+ def did_validate_successfully(self) -> bool:
79
+ """Check if the attempt passed validation."""
80
+ return self.validation is not None and self.validation.validation_result
81
+
82
+
83
+ class ReliableToolContext(BaseModel):
84
+ """Main context object holding the overall state and history of attempts."""
85
+
86
+ model_config = ConfigDict(arbitrary_types_allowed=True)
87
+ task: str
88
+ reliable_tool_name: str
89
+ start_time: float = Field(default_factory=time.time)
90
+ dynamic_validation_input: Optional[str] = None
91
+ attempts: List[ExecutionAttempt] = Field(default_factory=list)
92
+ initial_messages: Optional[List[dict[str, Any]]] = Field(
93
+ default=None, description="Initial messages provided to the tool run."
94
+ )
95
+ initial_ground_truth: Optional[List[str]] = Field(
96
+ default=None, description="Initial ground truth strings provided."
97
+ )
98
+
99
+ @property
100
+ def attempt_count(self) -> int:
101
+ """Return the number of attempts made."""
102
+ return len(self.attempts)
103
+
104
+ @property
105
+ def latest_attempt(self) -> Optional[ExecutionAttempt]:
106
+ """Return the most recent attempt, if any."""
107
+ return self.attempts[-1] if self.attempts else None
108
+
109
+ @property
110
+ def is_complete_and_successful(self) -> bool:
111
+ """Check if the process finished with a validated successful attempt."""
112
+ latest = self.latest_attempt
113
+ return latest is not None and latest.did_execute_successfully and latest.did_validate_successfully
114
+
115
+ def get_final_result_data(self) -> Any:
116
+ """Return the result_data from the successful and validated attempt."""
117
+ if self.is_complete_and_successful and self.latest_attempt:
118
+ return self.latest_attempt.result_data
119
+ return None
120
+
121
+ def get_final_result_str(self) -> Any:
122
+ """Return the result_str from the successful and validated attempt."""
123
+ if self.is_complete_and_successful and self.latest_attempt:
124
+ return self.latest_attempt.result_str
125
+ return None
126
+
127
+ def get_failure_summary(self) -> str:
128
+ """Provide a summary of why the overall execution failed."""
129
+ latest = self.latest_attempt
130
+ if latest is None:
131
+ return "No execution attempts were made."
132
+ if not latest.did_execute_successfully:
133
+ return f"Execution failed: {latest.error}"
134
+ if not latest.did_validate_successfully:
135
+ justification = (
136
+ latest.validation.justification if latest.validation else "Validation result missing or invalid"
137
+ )
138
+ return f"Execution succeeded but failed validation (Justification: {justification})"
139
+ return "Execution completed but overall status indicates failure (Internal inconsistency)."
140
+
141
+
142
+ class SuccessfulExecutionParameters(BaseModel):
143
+ """Holds the arguments of a successful tool function execution."""
144
+
145
+ model_config = ConfigDict(arbitrary_types_allowed=True)
146
+ attempt_args: List[Any]
147
+ attempt_kwargs: Dict[str, Any]
148
+
149
+
150
+ class ToolExecutionDetails(BaseModel):
151
+ """Provides detailed information about a ReliableTool execution."""
152
+
153
+ model_config = ConfigDict(arbitrary_types_allowed=True)
154
+ task: str
155
+ is_overall_successful: bool
156
+ failure_reason: Optional[str] = None
157
+ successful_parameters: Optional[SuccessfulExecutionParameters] = None
158
+ final_tool_context: ReliableToolContext
159
+
160
+
161
+ def _configure_llm_for_structured_output(
162
+ llm_config: Optional[Union[LLMConfig, dict[str, Any]]], structured_output_type: Type[BaseModel]
163
+ ) -> Union[LLMConfig, dict[str, Any]]: # Return type changed, False is no longer a valid return
164
+ """Configure LLM config for structured output using a Pydantic model."""
165
+ if llm_config is None or llm_config is False:
166
+ raise ValueError("LLMConfig cannot be None or False for structured output.")
167
+ if not issubclass(structured_output_type, BaseModel):
168
+ raise TypeError(f"{structured_output_type} must be a Pydantic BaseModel subclass.")
169
+
170
+ llm_config_obj = ConversableAgent._validate_llm_config(llm_config)
171
+
172
+ if llm_config_obj is False: # Should not happen if input llm_config is not False
173
+ raise ValueError("Validated LLMConfig resolved to False unexpectedly.")
174
+
175
+ response_format_set = False
176
+
177
+ def _set_format_and_remove_conflicts(config_item: Union[LLMConfig, Dict[str, Any]]) -> None:
178
+ nonlocal response_format_set
179
+ conflicting_keys = ["tools", "tool_choice", "functions"]
180
+ removed_keys = []
181
+
182
+ if isinstance(config_item, dict):
183
+ config_item["response_format"] = structured_output_type
184
+ response_format_set = True
185
+ for key in conflicting_keys:
186
+ if key in config_item:
187
+ del config_item[key]
188
+ removed_keys.append(key)
189
+ elif hasattr(config_item, "response_format"): # LLMConfig object
190
+ setattr(config_item, "response_format", structured_output_type)
191
+ response_format_set = True
192
+ for key in conflicting_keys:
193
+ if hasattr(config_item, key) and getattr(config_item, key, None):
194
+ # Try setting to None or empty list/dict as appropriate
195
+ default_empty: Optional[List[str]] = [] if key in ["tools", "functions"] else None
196
+ setattr(config_item, key, default_empty)
197
+ removed_keys.append(key)
198
+ else:
199
+ # This case implies llm_config_obj is an object not fitting LLMConfig ducktype for response_format
200
+ # or not a dict, which should be caught by _validate_llm_config or earlier checks.
201
+ raise TypeError(f"Unsupported LLM config item type for structured output: {type(config_item)}")
202
+
203
+ if removed_keys:
204
+ logger.debug(
205
+ "Removed conflicting keys %s from LLM config for structured output (response_format=%s)",
206
+ removed_keys,
207
+ structured_output_type.__name__,
208
+ )
209
+
210
+ _set_format_and_remove_conflicts(llm_config_obj)
211
+
212
+ if not response_format_set and not isinstance(llm_config_obj, dict): # Double check if it's an object
213
+ # if it's an object and response_format could not be set, it's an issue.
214
+ # For dicts, it's assumed to be set by _set_format_and_remove_conflicts.
215
+ raise ValueError(
216
+ f"LLMConfig object type ({type(llm_config_obj).__name__}) "
217
+ "could not have 'response_format' set. Structured output may fail."
218
+ )
219
+
220
+ # Handle config_list if present
221
+ config_list_attr_name = "config_list"
222
+ original_config_list = None
223
+
224
+ if isinstance(llm_config_obj, dict):
225
+ original_config_list = llm_config_obj.get(config_list_attr_name)
226
+ elif hasattr(llm_config_obj, config_list_attr_name):
227
+ original_config_list = getattr(llm_config_obj, config_list_attr_name, None)
228
+
229
+ if isinstance(original_config_list, list):
230
+ new_config_list = []
231
+ for item in original_config_list:
232
+ item_copy = copy.deepcopy(item)
233
+ # Assuming items in config_list are dicts or LLMConfig-like objects
234
+ _set_format_and_remove_conflicts(item_copy)
235
+ new_config_list.append(item_copy)
236
+
237
+ if isinstance(llm_config_obj, dict):
238
+ llm_config_obj[config_list_attr_name] = new_config_list
239
+ else: # Must be an object if hasattr was true
240
+ setattr(llm_config_obj, config_list_attr_name, new_config_list)
241
+
242
+ logger.debug("Prepared LLM config for validator (response_format=%s)", structured_output_type.__name__)
243
+ return llm_config_obj
244
+
245
+
246
+ def _get_last_non_empty_message_content(messages: Optional[List[dict[str, Any]]]) -> Optional[str]:
247
+ """Get content of the last message with non-empty content."""
248
+ if not messages:
249
+ return None
250
+ for message in reversed(messages):
251
+ content = message.get("content")
252
+ if isinstance(content, str) and content.strip():
253
+ return content.strip()
254
+ if isinstance(content, list) and content: # Handle multimodal content
255
+ # Prioritize text parts
256
+ text_parts = [
257
+ item["text"].strip()
258
+ for item in content
259
+ if isinstance(item, dict)
260
+ and item.get("type") == "text"
261
+ and isinstance(item.get("text"), str)
262
+ and item["text"].strip()
263
+ ]
264
+ if text_parts:
265
+ return "\n".join(text_parts)
266
+
267
+ # If no text parts, serialize the first non-empty item
268
+ for item in content:
269
+ if item: # Ensure item is not None or empty
270
+ if isinstance(item, dict):
271
+ return json.dumps(item)
272
+ else:
273
+ return str(item).strip()
274
+ return None
275
+
276
+
277
+ def _get_reliable_tool_context(context_variables: ContextVariables, context_key: str) -> ReliableToolContext:
278
+ """Retrieve and validate the ReliableToolContext from ContextVariables."""
279
+ context_data = context_variables.get(context_key)
280
+ if context_data is None:
281
+ raise KeyError(f"ReliableToolContext key '{context_key}' not found in ContextVariables.")
282
+ try:
283
+ if isinstance(context_data, str):
284
+ return ReliableToolContext.model_validate_json(context_data)
285
+ raise TypeError(
286
+ f"Unexpected type {type(context_data)} for context key '{context_key}'. Expected ReliableToolContext, str, or dict."
287
+ )
288
+ except (ValidationError, json.JSONDecodeError, TypeError) as e:
289
+ preview = f" Preview: '{str(context_data)[:100]}...'" if isinstance(context_data, (str, dict)) else ""
290
+ # Logged error level changed to warning as this function re-raises.
291
+ logger.warning(
292
+ "Failed loading ReliableToolContext '%s'. Error: %s. Type: %s.%s",
293
+ context_key,
294
+ e,
295
+ type(context_data).__name__,
296
+ preview,
297
+ )
298
+ raise ValueError(f"Failed loading ReliableToolContext key '{context_key}': {e}") from e
299
+
300
+
301
+ def _set_reliable_tool_context(
302
+ context_variables: ContextVariables, context_key: str, context: ReliableToolContext
303
+ ) -> None:
304
+ """Serialize and store the ReliableToolContext in ContextVariables."""
305
+ if not isinstance(context, ReliableToolContext):
306
+ raise TypeError(f"Object to set must be a ReliableToolContext, got {type(context)}.")
307
+ try:
308
+ context_variables[context_key] = context.model_dump_json(warnings="warn")
309
+ except (ValidationError, TypeError) as e: # More specific exceptions
310
+ context_dict_str = "N/A"
311
+ try: # Best effort to get some context info for logging
312
+ context_dict_str = str(context.model_dump(warnings="warn", exclude={"attempts"}))[:500]
313
+ except Exception:
314
+ contextlib.suppress(Exception)
315
+ logger.error( # Log as error as this is a critical serialization failure
316
+ "Failed serializing ReliableToolContext key '%s': %s. Context (partial): %s",
317
+ context_key,
318
+ e,
319
+ context_dict_str,
320
+ )
321
+ raise ValueError(f"Critical error serializing ReliableToolContext: {e}") from e
322
+
323
+
324
+ def get_runner_prompt(task: str, agent_system_message: str, internal_tool_name: str) -> str:
325
+ """Generate the system prompt for the internal runner agent."""
326
+ return f"""
327
+ You are an AI assistant responsible for invoking a specific function based on the user's task and conversation history.
328
+ Function to call: '{internal_tool_name}'
329
+ Analyze the previous attempt's outcome (if any, visible in history) and adjust the function arguments accordingly for this retry. If this is the first attempt, determine the best initial arguments based on the task and initial context.
330
+
331
+ You MUST invoke the function '{internal_tool_name}' exactly one time per response using a tool call format that the system can execute.
332
+ Do NOT just output text explaining what you would do, or asking for confirmation. Directly make the tool call.
333
+ Analyze the task description and *full conversation history* carefully to determine the correct arguments for the function call.
334
+ You MUST provide a 'hypothesis' argument summarizing the expected outcome or result format of the function call based on the inputs.
335
+
336
+ Base Instructions:
337
+ {agent_system_message}
338
+
339
+ Current Task:
340
+ {task}
341
+ """
342
+
343
+
344
+ def get_validator_prompt(
345
+ task: str, base_validator_system_message: str, dynamic_validation_addition: Optional[str] = None
346
+ ) -> str:
347
+ """Generate the system prompt for the internal validator agent."""
348
+ dynamic_section = (
349
+ f"\n\nAdditional Dynamic Requirements for This Specific Run:\n{dynamic_validation_addition.strip()}"
350
+ if dynamic_validation_addition and dynamic_validation_addition.strip()
351
+ else ""
352
+ )
353
+ return f"""
354
+ You are an AI validation assistant. You will receive a curated message list containing:
355
+ 1. Initial context messages (original request, potentially prior conversation).
356
+ 2. Provided ground truth information (if any).
357
+ 3. The final result of a function call intended to accomplish the task.
358
+
359
+ Your goal is to validate if the *final function call result* meets ALL requirements based on the *entire context provided in the message list*. Consider the base task description, base validation rules, initial context/ground truth, and any dynamic requirements below.
360
+
361
+ Evaluate the *final function call result* (presented at the end of the message list) based on *all* information provided.
362
+
363
+ Base Validation Rules/Context:
364
+ {base_validator_system_message}{dynamic_section}
365
+
366
+ Base Task Description (for reference):
367
+ {task}
368
+ """
369
+
370
+
371
+ def reliable_function_wrapper(
372
+ tool_function: Callable[..., Any], validator: ConversableAgent, runner: ConversableAgent, context_variables_key: str
373
+ ) -> Callable[..., Any]:
374
+ """Wraps the target function, returning a sync or async wrapper.
375
+
376
+ Adds 'hypothesis' and 'context_variables' keyword-only arguments.
377
+ Returns a ReplyResult targeting the validator.
378
+ """
379
+ is_original_func_async = inspect.iscoroutinefunction(tool_function)
380
+ tool_sig = inspect.signature(tool_function)
381
+ wrapper_func: Callable[..., Any] # Declare type for wrapper_func
382
+
383
+ def _handle_execution_error(
384
+ attempt: ExecutionAttempt, context_vars: ContextVariables, context: ReliableToolContext, e: Exception
385
+ ) -> ReplyResult:
386
+ """Shared logic to handle tool_function execution error."""
387
+ err_msg = f"{type(e).__name__}: {e}"
388
+ logger.error( # Log the error from the wrapped function
389
+ "Wrapped function '%s' execution error: %s",
390
+ getattr(tool_function, "__name__", "unknown_func"),
391
+ err_msg,
392
+ exc_info=True, # Include traceback for wrapped function error
393
+ )
394
+ attempt.error = err_msg
395
+ if attempt not in context.attempts:
396
+ context.attempts.append(attempt)
397
+
398
+ _set_reliable_tool_context(context_vars, context_variables_key, context)
399
+
400
+ # Go to runner in this scenario because an error can just be handled by the runner again
401
+ return ReplyResult(
402
+ context_variables=context_vars,
403
+ target=AgentTarget(runner),
404
+ message=f"Function execution failed with error: {err_msg}.",
405
+ )
406
+
407
+ def _process_successful_execution(
408
+ attempt: ExecutionAttempt, result: Any, context_vars: ContextVariables, context: ReliableToolContext
409
+ ) -> ReplyResult:
410
+ value_to_stringify: Any = None
411
+
412
+ if isinstance(result, tuple):
413
+ if len(result) >= 2:
414
+ attempt.result_data = result[0]
415
+ value_to_stringify = result[1]
416
+ elif len(result) == 1:
417
+ attempt.result_data = result[0]
418
+ value_to_stringify = result[0]
419
+ else:
420
+ attempt.result_data = None
421
+ value_to_stringify = ""
422
+
423
+ else:
424
+ attempt.result_data = result
425
+ value_to_stringify = result
426
+
427
+ try:
428
+ attempt.result_str = str(value_to_stringify) if value_to_stringify is not None else ""
429
+ except Exception as str_e:
430
+ logger.warning(
431
+ "Could not convert result string part to string, using repr() \n %s",
432
+ str_e,
433
+ )
434
+ attempt.result_str = repr(value_to_stringify)
435
+
436
+ if attempt not in context.attempts:
437
+ context.attempts.append(attempt)
438
+
439
+ _set_reliable_tool_context(context_vars, context_variables_key, context)
440
+
441
+ return ReplyResult(
442
+ context_variables=context_vars,
443
+ target=AgentTarget(validator),
444
+ message=attempt.result_str,
445
+ )
446
+
447
+ if not is_original_func_async:
448
+
449
+ @functools.wraps(tool_function)
450
+ def sync_wrapper(
451
+ *args: Any, hypothesis: str, context_variables: ContextVariables, **kwargs: Any
452
+ ) -> ReplyResult:
453
+ context = _get_reliable_tool_context(context_variables, context_variables_key)
454
+ attempt = ExecutionAttempt(attempt_args=list(args), attempt_kwargs=kwargs, hypothesis=hypothesis)
455
+ try:
456
+ result = tool_function(*args, **kwargs)
457
+ return _process_successful_execution(attempt, result, context_variables, context)
458
+ except Exception as e:
459
+ return _handle_execution_error(attempt, context_variables, context, e)
460
+
461
+ wrapper_func = sync_wrapper
462
+ else:
463
+
464
+ @functools.wraps(tool_function)
465
+ async def async_wrapper(
466
+ *args: Any, hypothesis: str, context_variables: ContextVariables, **kwargs: Any
467
+ ) -> ReplyResult:
468
+ context = _get_reliable_tool_context(context_variables, context_variables_key)
469
+ attempt = ExecutionAttempt(attempt_args=list(args), attempt_kwargs=kwargs, hypothesis=hypothesis)
470
+ try:
471
+ result = await tool_function(*args, **kwargs)
472
+ return _process_successful_execution(attempt, result, context_variables, context)
473
+ except Exception as e:
474
+ return _handle_execution_error(attempt, context_variables, context, e)
475
+
476
+ wrapper_func = async_wrapper
477
+
478
+ params = list(tool_sig.parameters.values())
479
+ pos_or_kw_params, kw_only_params, var_pos_param, var_kw_param = [], [], None, None
480
+ for p in params:
481
+ if p.kind == inspect.Parameter.POSITIONAL_ONLY or p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
482
+ pos_or_kw_params.append(p)
483
+ elif p.kind == inspect.Parameter.VAR_POSITIONAL:
484
+ var_pos_param = p
485
+ elif p.kind == inspect.Parameter.KEYWORD_ONLY:
486
+ kw_only_params.append(p)
487
+ elif p.kind == inspect.Parameter.VAR_KEYWORD:
488
+ var_kw_param = p
489
+
490
+ new_kw_only_params = [
491
+ inspect.Parameter(
492
+ "hypothesis",
493
+ inspect.Parameter.KEYWORD_ONLY,
494
+ annotation=Annotated[str, AG2Field(description=HYPOTHESIS_DESCRIPTION)],
495
+ default=inspect.Parameter.empty,
496
+ ),
497
+ inspect.Parameter(
498
+ "context_variables",
499
+ inspect.Parameter.KEYWORD_ONLY,
500
+ annotation=ContextVariables,
501
+ default=inspect.Parameter.empty,
502
+ ),
503
+ ]
504
+
505
+ wrapper_params = (
506
+ pos_or_kw_params
507
+ + ([var_pos_param] if var_pos_param else [])
508
+ + kw_only_params
509
+ + new_kw_only_params
510
+ + ([var_kw_param] if var_kw_param else [])
511
+ )
512
+ setattr(wrapper_func, "__signature__", inspect.Signature(parameters=wrapper_params, return_annotation=ReplyResult))
513
+ return wrapper_func
514
+
515
+
516
+ @export_module("autogen.tools.experimental")
517
+ class ReliableToolError(Exception):
518
+ """Custom exception for errors during ReliableTool execution."""
519
+
520
+ def __init__(self, message: str, final_context: Optional[ReliableToolContext] = None):
521
+ super().__init__(message)
522
+ self.final_context = final_context
523
+
524
+
525
+ @export_module("autogen.tools.experimental")
526
+ class ReliableTool(Tool):
527
+ INTERNAL_TOOL_NAME_PREFIX = "execute_"
528
+
529
+ def __init__(
530
+ self,
531
+ name: str,
532
+ func_or_tool: Union[Callable[..., Any], Tool],
533
+ runner_llm_config: Union[LLMConfig, dict[str, Any]],
534
+ validator_llm_config: Union[LLMConfig, dict[str, Any]],
535
+ description: Optional[str] = None,
536
+ system_message_addition_for_tool_calling: str = "",
537
+ system_message_addition_for_result_validation: str = "",
538
+ max_tool_invocations: int = 3,
539
+ enable_dynamic_validation: bool = False,
540
+ messages: Optional[List[dict[str, Any]]] = None,
541
+ ground_truth: Optional[List[str]] = None,
542
+ ) -> None:
543
+ """
544
+ A ReliableTool wraps an existing function or tool.
545
+ When the ReliableTool is invoked, it kicks off an internal Group Chat where a Runner
546
+ and Validator agent will iteratively invoke the wrapped function or tool until
547
+ *the output of a single invocation of the original function or tool satisfies the provided validation criteria.*
548
+ Reliable Tools are best used when the LLM used or the function or tool itself is unreliable.
549
+ Commonly this happens when using small, local LLMs, <32b params
550
+ Or when functions/tools are used to "explore" (doing many web searches, exploring a database with SQL)
551
+ The Reliable Tool allows the user to bake a result validation strategy into the tool itself
552
+ so that the broader group chat/agentic system can be built more clearly around the intended flow
553
+ instead of needing to focus so much on retry and validation loops.
554
+
555
+ Additionally, the .run() and .a_run() methods serve as a way to use LLMs to invoke a specific tool outside
556
+ of a Group Chat or similar structure to provide a more traditional programming method of using LLMs and tools in code.
557
+
558
+ Args:
559
+ name (str):
560
+ A unique and descriptive name for this ReliableTool instance.
561
+ This name is used for logging, internal context management, and can be
562
+ how other agents or systems refer to this specific reliable capability.
563
+ Example: `"AccurateWeatherForecaster"`, `"ValidatedCustomerLookup"`
564
+
565
+ func_or_tool (Union[Callable[..., Any], Tool]):
566
+ The core Python function or an existing AG2 `Tool` instance that this
567
+ `ReliableTool` will manage and execute. This is the underlying capability
568
+ you want to enhance with reliability features like retries and validation.
569
+ The `ReliableTool` will handle calling this function with arguments
570
+ determined by its internal Runner Agent based on the provided `task`.
571
+ Example: `my_api_call_function`, `existing_search_tool_instance`
572
+
573
+ runner_llm_config (Union[LLMConfig, dict[str, Any]]):
574
+ The LLM configuration for the internal "Runner Agent". This agent is
575
+ responsible for interpreting the high-level `task` provided when the
576
+ `ReliableTool` is invoked, deciding the appropriate arguments for the
577
+ `func_or_tool`, and initiating its execution.
578
+ This configuration dictates the model, API keys, temperature, etc., for
579
+ the LLM that attempts to call your function. It must support tool/function calling.
580
+ Example: `LLMConfig(config_list=oai_config_list, model="gpt-4o-mini")`
581
+ `{"config_list": [{"model": "gpt-3.5-turbo", "api_key": "..."}], "temperature": 0.5}`
582
+
583
+ validator_llm_config (Union[LLMConfig, dict[str, Any]]):
584
+ The LLM configuration for the internal "Validator Agent". After the
585
+ `func_or_tool` executes successfully, this agent receives its string output
586
+ and assesses whether it meets defined validation criteria. It is
587
+ configured for structured output (Pydantic model `ValidationResult`)
588
+ to provide a boolean validation status and a justification.
589
+ This configuration dictates the model, etc., for the LLM that validates
590
+ the function's result. It can be the same as `runner_llm_config` or different.
591
+ Example: `LLMConfig(config_list=oai_config_list, model="gpt-4o-mini")`
592
+
593
+ description (Optional[str], default: None):
594
+ A human-readable description of what this `ReliableTool` achieves.
595
+ If `None`, the description is inferred from the docstring of the
596
+ provided `func_or_tool`. This description is primarily for the public-facing
597
+ `ReliableTool` (e.g., when registered with an outer agent for it to decide
598
+ when to use this tool).
599
+ Example: `"Reliably fetches and validates current weather information for a specified city."`
600
+
601
+ system_message_addition_for_tool_calling (str, default: ""):
602
+ Additional text appended to the system message of the internal "Runner Agent".
603
+ This allows you to provide specific instructions, context, or constraints
604
+ to the LLM responsible for deciding *how* to call your underlying `func_or_tool`.
605
+ Use this when the Runner Agent needs more guidance than just the task
606
+ description and the function's signature to correctly formulate arguments.
607
+ Example: `"When calling 'search_products', if the task mentions 'budget', ensure the 'max_price' argument is set accordingly. Prioritize items in stock."`
608
+
609
+ system_message_addition_for_result_validation (str, default: ""):
610
+ Additional text appended to the system message of the internal "Validator Agent".
611
+ This is where you define the *base* or *static* criteria for validating the
612
+ *result* (string representation) of your `func_or_tool`. These criteria
613
+ are applied on every validation attempt unless overridden or supplemented by
614
+ dynamic validation.
615
+ Example: `"The stock price must be a positive number. The company name in the result must match the one in the task. If data is unavailable, the result should explicitly state 'Data not found'."`
616
+
617
+ max_tool_invocations (int, default: 3):
618
+ The maximum number of times the internal "Runner Agent" can attempt to
619
+ call the underlying `func_or_tool`. This limit includes the initial attempt
620
+ and any subsequent retries that occur due to:
621
+ 1. Direct execution errors from `func_or_tool`.
622
+ 2. The Runner Agent failing to generate a valid tool call.
623
+ 3. The Validator Agent deeming a successful execution's result as invalid.
624
+ Adjust this to control retries and prevent excessive LLM calls, considering
625
+ the potential flakiness of the `func_or_tool` or complexity of parameterization.
626
+ Example: `max_tool_invocations=2` (allows one initial attempt and one retry if needed).
627
+
628
+ enable_dynamic_validation (bool, default: False):
629
+ If `True`, the public-facing `run` (or `a_run`) method of this `ReliableTool`
630
+ (accessible via its `func` attribute after initialization) will accept an
631
+ additional optional argument: `validation_prompt_addition: Optional[str]`.
632
+ If a string is provided for this argument during a call, it will be appended
633
+ to the Validator Agent's system message *for that specific run*, allowing
634
+ validation criteria to be tailored on-the-fly based on the task.
635
+ Example: If `True`, `my_tool.func(task="search for AG2 examples", validation_prompt_addition="Result must include Python code snippets.")`
636
+
637
+ messages (Optional[List[dict[str, Any]]], default: None):
638
+ A list of initial messages (e.g., from a prior conversation history) to
639
+ provide context to the internal Runner and Validator agents. These messages
640
+ are prepended to the message history seen by these agents during their
641
+ internal chat, helping them understand the `task` in a broader context.
642
+ Use when the `task` for the `ReliableTool` might refer to entities or
643
+ intentions established in preceding turns of a conversation.
644
+ Example: `messages=[{"role": "user", "content": "I'm interested in large-cap tech stocks."}, {"role": "assistant", "content": "Okay, any specific ones?"}]`
645
+ (Then a task like "Fetch the latest price for 'the one we just discussed'.")
646
+
647
+ ground_truth (Optional[List[str]], default: None):
648
+ A list of strings representing factual information, examples, or specific
649
+ constraints that should be considered by the internal Runner and Validator
650
+ agents. These are injected into the conversation history as distinct user
651
+ messages (e.g., "[[Provided Ground Truth 1]]: ...").
652
+ Use to provide specific, factual data or strong hints that might not fit
653
+ naturally into system messages or prior conversation history, guiding the
654
+ agents towards correct interpretation or validation.
655
+ Example: `ground_truth=["The API rate limit is 10 requests per minute.", "User preference: only show results from the last 7 days."]`
656
+ """
657
+ self._original_func, original_name, original_description = self._extract_func_details(func_or_tool)
658
+ self._is_original_func_async = inspect.iscoroutinefunction(self._original_func)
659
+
660
+ self._runner_llm_config = ConversableAgent._validate_llm_config(runner_llm_config)
661
+ if self._runner_llm_config is False:
662
+ raise ValueError("Runner LLM config failed validation.")
663
+ # Validate validator_llm_config and store it. It can be LLMConfig | dict | False.
664
+ self._validator_llm_config = ConversableAgent._validate_llm_config(validator_llm_config)
665
+ if self._validator_llm_config is False: # Check before use in _setup_validator_agent
666
+ raise ValueError("Validator LLM config failed validation.")
667
+
668
+ self._runner_system_message_addition = system_message_addition_for_tool_calling
669
+ self._validator_system_message_addition = system_message_addition_for_result_validation
670
+ self.max_tool_invocations = max_tool_invocations
671
+ self._context_variables_key = f"{name}_ReliableToolContext_{id(self)}"
672
+
673
+ self._original_func_name = original_name
674
+ self.enable_dynamic_validation = enable_dynamic_validation
675
+
676
+ self._init_messages = copy.deepcopy(messages) if messages is not None else None
677
+ self._init_ground_truth = copy.deepcopy(ground_truth) if ground_truth else None
678
+
679
+ self._tool_description = description if description is not None else original_description
680
+
681
+ public_entry_point_func = self._define_public_entry_point(
682
+ self._is_original_func_async, self.enable_dynamic_validation
683
+ )
684
+
685
+ super().__init__(
686
+ name=name,
687
+ description=self._tool_description,
688
+ func_or_tool=public_entry_point_func,
689
+ )
690
+
691
+ self._validator_name = f"{self.name}_Validator"
692
+ self._runner_name = f"{self.name}_Runner"
693
+
694
+ self._validator = self._setup_validator_agent()
695
+ self._runner = self._setup_runner_agent()
696
+ self._reliable_func_wrapper = reliable_function_wrapper(
697
+ self._original_func, self._validator, self._runner, self._context_variables_key
698
+ )
699
+ self._setup_runner_tool()
700
+ self._register_internal_hooks()
701
+
702
+ def _define_public_entry_point(self, is_async: bool, enable_dynamic: bool) -> Callable[..., Any]:
703
+ if not is_async:
704
+ if enable_dynamic:
705
+
706
+ def sync_entry_point_with_validation(
707
+ task: str, validation_prompt_addition: Optional[str] = None
708
+ ) -> Any:
709
+ return self.run(task=task, validation_prompt_addition=validation_prompt_addition)
710
+
711
+ return sync_entry_point_with_validation
712
+ else:
713
+
714
+ def sync_entry_point_without_validation(task: str) -> Any:
715
+ return self.run(task=task, validation_prompt_addition=None)
716
+
717
+ return sync_entry_point_without_validation
718
+ else:
719
+ if enable_dynamic:
720
+
721
+ async def async_entry_point_with_validation(
722
+ task: str, validation_prompt_addition: Optional[str] = None
723
+ ) -> Any:
724
+ return await self.a_run(task=task, validation_prompt_addition=validation_prompt_addition)
725
+
726
+ return async_entry_point_with_validation
727
+ else:
728
+
729
+ async def async_entry_point_without_validation(task: str) -> Any:
730
+ return await self.a_run(task=task, validation_prompt_addition=None)
731
+
732
+ return async_entry_point_without_validation
733
+
734
+ def _extract_func_details(
735
+ self, func_or_tool: Union[Callable[..., Any], Tool]
736
+ ) -> Tuple[Callable[..., Any], str, str]:
737
+ default_desc_template = "Executes the '{name}' function."
738
+ if isinstance(func_or_tool, Tool):
739
+ func = getattr(func_or_tool, "func", None)
740
+ if not callable(func):
741
+ raise TypeError(
742
+ f"Tool '{func_or_tool.name}' provided but its 'func' attribute is not callable or missing."
743
+ )
744
+ name = func_or_tool.name
745
+ desc = func_or_tool.description
746
+ if not desc or desc == f"Tool '{name}'." or desc == "No description provided.":
747
+ func_doc = inspect.getdoc(func)
748
+ desc = func_doc.strip() if func_doc else f"{default_desc_template.format(name=name)}"
749
+ return func, name, desc
750
+ elif callable(func_or_tool):
751
+ name = getattr(func_or_tool, "__name__", "callable_function")
752
+ doc = inspect.getdoc(func_or_tool)
753
+ desc = doc.strip() if doc else f"{default_desc_template.format(name=name)}"
754
+ # For raw callables, we don't have a pre-computed schema like Tool object might
755
+ return func_or_tool, name, desc
756
+ raise TypeError(
757
+ "Input 'func_or_tool' must be a callable or an autogen.Tool instance with a callable 'func' attribute."
758
+ )
759
+
760
+ def _setup_validator_agent(self) -> ConversableAgent:
761
+ # _configure_llm_for_structured_output will raise ValueError if config is bad
762
+ # Use a local variable for type narrowing after the False check.
763
+ current_validator_config = self._validator_llm_config
764
+ if current_validator_config is False:
765
+ # This case should have been caught in __init__, but as a safeguard:
766
+ raise ValueError("Validator LLM config is False, cannot proceed.")
767
+
768
+ structured_llm_config = _configure_llm_for_structured_output(
769
+ copy.deepcopy(current_validator_config), # current_validator_config is not False here
770
+ ValidationResult,
771
+ )
772
+ return ConversableAgent(
773
+ name=self._validator_name,
774
+ system_message="[Validator Prompt Updated Per Run]",
775
+ llm_config=structured_llm_config,
776
+ human_input_mode="NEVER",
777
+ )
778
+
779
+ def _setup_runner_agent(self) -> ConversableAgent:
780
+ runner_llm_config_copy = copy.deepcopy(self._runner_llm_config)
781
+ runner = ConversableAgent(
782
+ name=self._runner_name,
783
+ system_message="[Runner Prompt Updated Per Run]",
784
+ llm_config=runner_llm_config_copy,
785
+ human_input_mode="NEVER",
786
+ )
787
+ return runner
788
+
789
+ def _setup_runner_tool(self) -> None:
790
+ internal_tool_name = f"{self.INTERNAL_TOOL_NAME_PREFIX}{self._original_func_name}"
791
+ internal_tool = Tool(
792
+ name=internal_tool_name, description=self._tool_description, func_or_tool=self._reliable_func_wrapper
793
+ )
794
+ internal_tool.register_tool(self._runner)
795
+ logger.info(
796
+ "Successfully registered internal tool '%s' with runner '%s'", internal_tool_name, self._runner.name
797
+ )
798
+
799
+ def _register_internal_hooks(self) -> None:
800
+ self._validator.register_hook(
801
+ hookable_method="process_message_before_send", hook=self._validator_structured_output_hook
802
+ )
803
+ self._validator.register_hook(
804
+ hookable_method="process_all_messages_before_reply", hook=self._validator_construct_context_hook
805
+ )
806
+ self._runner.register_hook(hookable_method="process_message_before_send", hook=self._ensure_function_call_hook)
807
+
808
+ def _validator_structured_output_hook(
809
+ self, sender: Agent, message: Union[dict[str, Any], str], recipient: Agent, silent: bool
810
+ ) -> Union[dict[str, Any], str]:
811
+ if not isinstance(message, str):
812
+ logger.error(
813
+ f"Validator Hook: Expected a JSON string message from LLM, but got {type(message)}. Content: {str(message)[:200]}"
814
+ )
815
+ # This indicates a misconfiguration or unexpected LLM output format.
816
+ raise TypeError(f"Validator hook expected str from LLM, got {type(message)}")
817
+
818
+ validation_result_obj: ValidationResult = ValidationResult.model_validate_json(message)
819
+ status = "PASSED" if validation_result_obj.validation_result else "FAILED"
820
+ log_level = logging.INFO if status == "PASSED" else logging.WARNING
821
+ logger.log(
822
+ log_level,
823
+ f"Validator Hook: Parsed Validation - {status}. Justification: {validation_result_obj.justification}",
824
+ )
825
+
826
+ self._try_update_context_validation(sender, validation_result_obj)
827
+ # sender is self._validator in this hook context
828
+ self._set_validator_handoff(self._validator, validation_result_obj.validation_result)
829
+ return validation_result_obj.format() # Return JSON string
830
+
831
+ def _set_validator_handoff(self, validator_agent: ConversableAgent, validation_passed: bool) -> None:
832
+ if not validation_passed:
833
+ logger.info("Validation failed, setting handoff to runner: %s", self._runner_name)
834
+ validator_agent.handoffs.set_after_work(target=AgentTarget(self._runner))
835
+ else:
836
+ logger.info("Validation passed, setting handoff to TerminateTarget.")
837
+ validator_agent.handoffs.set_after_work(target=TerminateTarget())
838
+
839
+ def _try_update_context_validation(self, sender: Agent, validation_result: ValidationResult) -> None:
840
+ """Helper to attempt updating the validation state in the ReliableToolContext."""
841
+ context_vars = getattr(sender, "context_variables")
842
+
843
+ tool_context = _get_reliable_tool_context(context_vars, self._context_variables_key)
844
+ latest_attempt = tool_context.latest_attempt
845
+
846
+ if not latest_attempt:
847
+ # This implies a logical error in the execution flow.
848
+ raise RuntimeError(
849
+ f"Validator hook: No execution attempt found in context '{self._context_variables_key}' to update validation for."
850
+ )
851
+
852
+ latest_attempt.validation = validation_result
853
+ _set_reliable_tool_context(context_vars, self._context_variables_key, tool_context)
854
+ logger.info(
855
+ "Validator hook: Updated validation status in context: %s",
856
+ "Passed" if validation_result.validation_result else "Failed",
857
+ )
858
+
859
+ def _validator_construct_context_hook(self, messages: list[dict[str, Any]], **kwargs: Any) -> list[dict[str, Any]]:
860
+ sender = self._validator # Assuming self._validator is the agent instance
861
+ logger.debug("Validator Construct Context Hook running for agent %s.", sender.name)
862
+
863
+ context_vars = getattr(sender, "context_variables")
864
+
865
+ tool_context = _get_reliable_tool_context(context_vars, self._context_variables_key)
866
+ initial_messages_to_inject = (
867
+ copy.deepcopy(tool_context.initial_messages) if tool_context.initial_messages else []
868
+ )
869
+
870
+ ground_truth_messages_to_inject = []
871
+ if tool_context.initial_ground_truth:
872
+ for i, gt in enumerate(tool_context.initial_ground_truth):
873
+ ground_truth_messages_to_inject.append({
874
+ "role": "user",
875
+ "content": f"[[Provided Ground Truth {i + 1}]]:\n{gt}",
876
+ })
877
+
878
+ last_content = _get_last_non_empty_message_content(messages)
879
+ result_message_dict = {
880
+ "role": "user",
881
+ "content": f"--- Function Result to Validate ---\n```\n{last_content}\n```\n--- End of Result ---",
882
+ }
883
+
884
+ final_messages = initial_messages_to_inject + ground_truth_messages_to_inject + [result_message_dict]
885
+ return final_messages
886
+
887
+ def _ensure_function_call_hook(
888
+ self, sender: Agent, message: Union[dict[str, Any], str], recipient: Agent, silent: bool
889
+ ) -> Union[dict[str, Any], str]:
890
+ if sender.name != self._runner_name:
891
+ return message
892
+
893
+ tool_calls_list = None
894
+ if isinstance(message, dict):
895
+ tool_calls_list = message.get("tool_calls")
896
+
897
+ tool_name_expected = f"{self.INTERNAL_TOOL_NAME_PREFIX}{self._original_func_name}"
898
+ correct_tool_called = False
899
+ if isinstance(tool_calls_list, list):
900
+ for call in tool_calls_list:
901
+ if (
902
+ isinstance(call, dict)
903
+ and call.get("type") == "function"
904
+ and isinstance(call.get("function"), dict)
905
+ and call["function"].get("name") == tool_name_expected
906
+ ):
907
+ correct_tool_called = True
908
+ break
909
+
910
+ if not correct_tool_called:
911
+ if not hasattr(self._runner, "handoffs"):
912
+ raise AttributeError(f"Runner agent '{self._runner.name}' missing 'handoffs' attribute for reminder.")
913
+ self._runner.handoffs.set_after_work(target=AgentTarget(self._runner)) # Retry with runner
914
+
915
+ logger.warning(
916
+ "Runner '%s' did not generate required tool call for '%s'. Appending reminder.",
917
+ self._runner_name,
918
+ tool_name_expected,
919
+ )
920
+ reminder = (
921
+ f"\n\n[[System Reminder: You MUST invoke the function '{tool_name_expected}' using a tool call. "
922
+ "Provide all required arguments including 'hypothesis'.]]\n"
923
+ "Correct your mistake and make a new attempt at invoking the tool."
924
+ )
925
+
926
+ current_content = ""
927
+ if isinstance(message, str):
928
+ current_content = message
929
+ elif isinstance(message, dict):
930
+ current_content = message.get("content") or ""
931
+
932
+ # Return a new message dict to ensure it's processed correctly by the agent
933
+ return {
934
+ "role": "assistant", # The LLM's previous turn was as assistant
935
+ "content": (current_content or "") + reminder,
936
+ "tool_calls": [] if isinstance(message, dict) else None,
937
+ }
938
+ return message
939
+
940
+ def _execute_internal_group_chat(
941
+ self,
942
+ task: str,
943
+ initial_context_vars: ContextVariables, # Renamed for clarity
944
+ dynamic_validation_str: Optional[str] = None,
945
+ ) -> Tuple[ChatResult, ContextVariables, Agent]:
946
+ internal_tool_name = f"{self.INTERNAL_TOOL_NAME_PREFIX}{self._original_func_name}"
947
+
948
+ # update_system_message should not fail if agent is properly initialized
949
+ runner_prompt = get_runner_prompt(task, self._runner_system_message_addition, internal_tool_name)
950
+ self._runner.update_system_message(runner_prompt)
951
+
952
+ validator_prompt = get_validator_prompt(task, self._validator_system_message_addition, dynamic_validation_str)
953
+ self._validator.update_system_message(validator_prompt)
954
+
955
+ # Store context ref on agents for hooks. Crucial for hooks to access shared state.
956
+ self._validator.context_variables = initial_context_vars
957
+ self._runner.context_variables = initial_context_vars
958
+
959
+ messages_for_runner_history = []
960
+ # Retrieve tool_context again to build runner history with potentially updated initial messages/GT
961
+ # This is vital if _process_run (the caller) modifies them in initial_context_vars.
962
+ tool_context = _get_reliable_tool_context(initial_context_vars, self._context_variables_key)
963
+
964
+ if tool_context.initial_messages:
965
+ messages_for_runner_history.extend(copy.deepcopy(tool_context.initial_messages))
966
+ if tool_context.initial_ground_truth:
967
+ for i, gt in enumerate(tool_context.initial_ground_truth):
968
+ messages_for_runner_history.append({
969
+ "role": "user",
970
+ "content": f"[[Provided Ground Truth {i + 1}]]:\n{gt}",
971
+ })
972
+
973
+ task_message = {
974
+ "role": "user",
975
+ "content": f"[[Task Kickoff]]: Please execute the required function call for the task: {task}",
976
+ }
977
+ final_initial_messages_for_runner = messages_for_runner_history + [task_message]
978
+
979
+ agent_pattern = DefaultPattern(
980
+ agents=[self._runner, self._validator],
981
+ initial_agent=self._runner,
982
+ context_variables=initial_context_vars,
983
+ )
984
+
985
+ max_internal_rounds = 1 + (self.max_tool_invocations * 3)
986
+ logger.debug(
987
+ f"Setting max internal chat rounds to {max_internal_rounds} for {self.max_tool_invocations} tool invocations."
988
+ )
989
+
990
+ logger.info(
991
+ f"--- Starting ReliableTool '{self.name}' Internal Chat (Max Invocations: {self.max_tool_invocations}) ---"
992
+ )
993
+
994
+ last_reply, final_context_vars, last_agent = initiate_group_chat(
995
+ pattern=agent_pattern,
996
+ messages=final_initial_messages_for_runner,
997
+ max_rounds=max_internal_rounds,
998
+ )
999
+ logger.info(
1000
+ f"--- ReliableTool '{self.name}' Internal Chat Finished (Last Agent: {getattr(last_agent, 'name', 'N/A')}) ---"
1001
+ )
1002
+ if not isinstance(final_context_vars, ContextVariables):
1003
+ # This would be an unexpected issue with initiate_group_chat or pattern
1004
+ raise TypeError(f"Internal chat returned invalid context_variables type: {type(final_context_vars)}")
1005
+ return last_reply, final_context_vars, last_agent
1006
+
1007
+ def _prepare_tool_context(
1008
+ self,
1009
+ task: str,
1010
+ current_context_variables: ContextVariables,
1011
+ validation_prompt_addition: Optional[str] = None,
1012
+ messages: Optional[list[dict[str, Any]]] = None,
1013
+ ground_truth: Optional[List[str]] = None,
1014
+ ) -> ReliableToolContext:
1015
+ """Initializes or updates the ReliableToolContext for the current run."""
1016
+ effective_messages = copy.deepcopy(messages) if messages is not None else self._init_messages
1017
+ effective_ground_truth = copy.deepcopy(ground_truth) if ground_truth is not None else self._init_ground_truth
1018
+
1019
+ tool_context = ReliableToolContext(task=task, reliable_tool_name=self.name)
1020
+
1021
+ tool_context.task = task
1022
+ tool_context.dynamic_validation_input = validation_prompt_addition
1023
+ tool_context.initial_messages = effective_messages
1024
+ tool_context.initial_ground_truth = effective_ground_truth
1025
+
1026
+ _set_reliable_tool_context(current_context_variables, self._context_variables_key, tool_context)
1027
+ return tool_context
1028
+
1029
+ def _process_run(
1030
+ self,
1031
+ task: str,
1032
+ context_variables: Optional[ContextVariables] = None,
1033
+ validation_prompt_addition: Optional[str] = None,
1034
+ messages: Optional[list[dict[str, Any]]] = None,
1035
+ ground_truth: Optional[List[str]] = None,
1036
+ ) -> Any:
1037
+ current_context_variables = context_variables if context_variables is not None else ContextVariables()
1038
+ if not isinstance(current_context_variables, ContextVariables):
1039
+ raise TypeError(f"Expected context_variables as ContextVariables or None, got {type(context_variables)}")
1040
+
1041
+ self._prepare_tool_context(task, current_context_variables, validation_prompt_addition, messages, ground_truth)
1042
+
1043
+ final_tool_context: ReliableToolContext
1044
+ _, chat_context_variables, _ = self._execute_internal_group_chat(
1045
+ task=task,
1046
+ initial_context_vars=current_context_variables,
1047
+ dynamic_validation_str=validation_prompt_addition,
1048
+ )
1049
+ current_context_variables = chat_context_variables
1050
+
1051
+ final_tool_context = _get_reliable_tool_context(current_context_variables, self._context_variables_key)
1052
+ latest_attempt_obj = final_tool_context.latest_attempt
1053
+
1054
+ if not latest_attempt_obj:
1055
+ raise ReliableToolError(
1056
+ "Critical internal error: No execution attempt recorded after chat cycle.",
1057
+ final_context=final_tool_context,
1058
+ )
1059
+
1060
+ # If execution was successful BUT validation is missing (e.g. validator hook failed to set it)
1061
+ if latest_attempt_obj.did_execute_successfully and latest_attempt_obj.validation is None:
1062
+ logger.warning(
1063
+ "[%s]: Validation result missing after successful execution. Assuming validation failed.", self.name
1064
+ )
1065
+ latest_attempt_obj.validation = ValidationResult(
1066
+ validation_result=False,
1067
+ justification="Validation result was not recorded after successful execution. Usually due to group chat reaching maximum runs",
1068
+ )
1069
+ _set_reliable_tool_context(current_context_variables, self._context_variables_key, final_tool_context)
1070
+
1071
+ if final_tool_context.is_complete_and_successful:
1072
+ logger.info("ReliableTool '%s' succeeded.", self.name)
1073
+ return final_tool_context.get_final_result_data()
1074
+ else:
1075
+ failure_reason = final_tool_context.get_failure_summary()
1076
+ logger.warning("ReliableTool '%s' failed. Reason: %s", self.name, failure_reason)
1077
+ raise ReliableToolError(
1078
+ f"ReliableTool '{self.name}' failed. Last failure: {failure_reason}",
1079
+ final_context=final_tool_context,
1080
+ )
1081
+
1082
+ def run(
1083
+ self,
1084
+ task: str,
1085
+ context_variables: Optional[ContextVariables] = None,
1086
+ validation_prompt_addition: Optional[str] = None,
1087
+ messages: Optional[list[dict[str, Any]]] = None,
1088
+ ground_truth: Optional[List[str]] = None,
1089
+ ) -> Any:
1090
+ if self._is_original_func_async:
1091
+ raise TypeError(f"Sync 'run()' called for async tool '{self.name}'. Use 'a_run()'.")
1092
+ return self._process_run(
1093
+ task=task,
1094
+ context_variables=context_variables,
1095
+ validation_prompt_addition=validation_prompt_addition,
1096
+ messages=messages,
1097
+ ground_truth=ground_truth,
1098
+ )
1099
+
1100
+ async def a_run(
1101
+ self,
1102
+ task: str,
1103
+ context_variables: Optional[ContextVariables] = None,
1104
+ validation_prompt_addition: Optional[str] = None,
1105
+ messages: Optional[list[dict[str, Any]]] = None,
1106
+ ground_truth: Optional[List[str]] = None,
1107
+ ) -> Any:
1108
+ if not self._is_original_func_async:
1109
+ warnings.warn(
1110
+ f"Running sync function '{self._original_func_name}' wrapped by ReliableTool '{self.name}' "
1111
+ f"asynchronously using 'a_run()'. The underlying execution of _process_run will be synchronous "
1112
+ f"within an executor.",
1113
+ UserWarning,
1114
+ )
1115
+
1116
+ loop = asyncio.get_running_loop()
1117
+ func_call = functools.partial(
1118
+ self._process_run,
1119
+ task=task,
1120
+ context_variables=context_variables,
1121
+ validation_prompt_addition=validation_prompt_addition,
1122
+ messages=messages,
1123
+ ground_truth=ground_truth,
1124
+ )
1125
+ return await loop.run_in_executor(None, func_call)
1126
+
1127
+ def _process_run_with_details(
1128
+ self,
1129
+ task: str,
1130
+ context_variables: Optional[ContextVariables] = None,
1131
+ validation_prompt_addition: Optional[str] = None,
1132
+ messages: Optional[list[dict[str, Any]]] = None,
1133
+ ground_truth: Optional[List[str]] = None,
1134
+ ) -> ToolExecutionDetails:
1135
+ current_context_variables = context_variables if context_variables is not None else ContextVariables()
1136
+ if not isinstance(current_context_variables, ContextVariables):
1137
+ err_msg = f"Invalid ContextVariables type: {type(context_variables)}"
1138
+ # Create a minimal context for reporting
1139
+ err_ctx = ReliableToolContext(task=task, reliable_tool_name=self.name)
1140
+ err_ctx.attempts.append(ExecutionAttempt(error=f"Initialization error: {err_msg}"))
1141
+ return ToolExecutionDetails(
1142
+ task=task, is_overall_successful=False, failure_reason=err_msg, final_tool_context=err_ctx
1143
+ )
1144
+
1145
+ tool_context_for_run: ReliableToolContext
1146
+ try:
1147
+ # Initialize or update tool context state. Raises on ser/de errors.
1148
+ tool_context_for_run = self._prepare_tool_context(
1149
+ task, current_context_variables, validation_prompt_addition, messages, ground_truth
1150
+ )
1151
+ except (ValueError, TypeError) as e_ctx_setup:
1152
+ err_msg = f"Error during ReliableToolContext setup: {e_ctx_setup}"
1153
+ logger.error("[%s] %s", self.name, err_msg, exc_info=True)
1154
+ err_ctx = ReliableToolContext(task=task, reliable_tool_name=self.name)
1155
+ err_ctx.attempts.append(ExecutionAttempt(error=f"Context setup error: {e_ctx_setup}"))
1156
+ return ToolExecutionDetails(
1157
+ task=task, is_overall_successful=False, failure_reason=err_msg, final_tool_context=err_ctx
1158
+ )
1159
+
1160
+ # Variables for ToolExecutionDetails
1161
+ is_successful_val = False
1162
+ failure_reason_val = None
1163
+ successful_params_val = None
1164
+ final_tool_context_val: ReliableToolContext = tool_context_for_run # Start with prepared context
1165
+
1166
+ try:
1167
+ _, chat_context_variables, _ = self._execute_internal_group_chat(
1168
+ task=task,
1169
+ initial_context_vars=current_context_variables, # This contains the prepared tool_context_for_run
1170
+ dynamic_validation_str=validation_prompt_addition,
1171
+ )
1172
+ current_context_variables = chat_context_variables # Update with context from chat
1173
+
1174
+ final_tool_context_val = _get_reliable_tool_context(current_context_variables, self._context_variables_key)
1175
+ latest_attempt = final_tool_context_val.latest_attempt
1176
+
1177
+ if not latest_attempt:
1178
+ failure_reason_val = "Critical internal error: No execution attempt recorded after chat cycle."
1179
+ # final_tool_context_val already reflects this state if attempts list is empty
1180
+ elif latest_attempt.did_execute_successfully and latest_attempt.validation is None:
1181
+ logger.warning(
1182
+ "[%s]: Validation result missing after successful execution. Assuming validation failed.", self.name
1183
+ )
1184
+ latest_attempt.validation = ValidationResult(
1185
+ validation_result=False,
1186
+ justification="Validation result was not recorded after successful execution.",
1187
+ )
1188
+ _set_reliable_tool_context(
1189
+ current_context_variables, self._context_variables_key, final_tool_context_val
1190
+ )
1191
+
1192
+ if final_tool_context_val.is_complete_and_successful:
1193
+ is_successful_val = True
1194
+ # latest_attempt must exist if is_complete_and_successful is true
1195
+ # Re-fetch or assert to help Mypy understand it's not None
1196
+ confirmed_latest_attempt = final_tool_context_val.latest_attempt
1197
+ assert confirmed_latest_attempt is not None, (
1198
+ "Internal logic error: is_complete_and_successful is True but latest_attempt is None"
1199
+ )
1200
+ successful_params_val = SuccessfulExecutionParameters(
1201
+ attempt_args=confirmed_latest_attempt.attempt_args,
1202
+ attempt_kwargs=confirmed_latest_attempt.attempt_kwargs,
1203
+ )
1204
+ else:
1205
+ failure_reason_val = final_tool_context_val.get_failure_summary()
1206
+
1207
+ except ReliableToolError as e:
1208
+ is_successful_val = False
1209
+ failure_reason_val = f"ReliableTool execution failed: {e}"
1210
+ logger.warning("[%s] %s", self.name, failure_reason_val) # Log the failure reason from ReliableToolError
1211
+ final_tool_context_val = e.final_context or final_tool_context_val # Use context from error if available
1212
+ if not final_tool_context_val.attempts: # Ensure some attempt is logged if context is minimal
1213
+ final_tool_context_val.attempts.append(ExecutionAttempt(error=str(e)))
1214
+
1215
+ except (KeyError, ValueError, TypeError) as e_ctx_final: # Context errors after chat
1216
+ is_successful_val = False
1217
+ failure_reason_val = f"Critical error involving context after chat: {e_ctx_final}"
1218
+ logger.error("[%s] %s", self.name, failure_reason_val, exc_info=True)
1219
+ try: # Try to get the latest context, otherwise use what we had
1220
+ final_tool_context_val = _get_reliable_tool_context(
1221
+ current_context_variables, self._context_variables_key
1222
+ )
1223
+ except (
1224
+ Exception
1225
+ ): # If still fails, final_tool_context_val remains as tool_context_for_run or from a prior partial update
1226
+ if not final_tool_context_val.attempts or final_tool_context_val.attempts[-1].error is None:
1227
+ final_tool_context_val.attempts.append(ExecutionAttempt(error=failure_reason_val))
1228
+
1229
+ except Exception as e_unexp: # Unexpected errors during the process
1230
+ is_successful_val = False
1231
+ failure_reason_val = f"Unexpected error during reliable execution: {e_unexp}"
1232
+ logger.error("[%s] %s", self.name, failure_reason_val, exc_info=True)
1233
+ try: # Try to get the latest context
1234
+ final_tool_context_val = _get_reliable_tool_context(
1235
+ current_context_variables, self._context_variables_key
1236
+ )
1237
+ except (
1238
+ Exception
1239
+ ): # If still fails, final_tool_context_val remains as tool_context_for_run or from a prior partial update
1240
+ if not final_tool_context_val.attempts or final_tool_context_val.attempts[-1].error is None:
1241
+ final_tool_context_val.attempts.append(ExecutionAttempt(error=failure_reason_val))
1242
+
1243
+ return ToolExecutionDetails(
1244
+ task=task,
1245
+ is_overall_successful=is_successful_val,
1246
+ failure_reason=failure_reason_val,
1247
+ successful_parameters=successful_params_val,
1248
+ final_tool_context=final_tool_context_val,
1249
+ )
1250
+
1251
+ def run_and_get_details(
1252
+ self,
1253
+ task: str,
1254
+ context_variables: Optional[ContextVariables] = None,
1255
+ validation_prompt_addition: Optional[str] = None,
1256
+ messages: Optional[list[dict[str, Any]]] = None,
1257
+ ground_truth: Optional[List[str]] = None,
1258
+ ) -> ToolExecutionDetails:
1259
+ if self._is_original_func_async:
1260
+ raise TypeError(
1261
+ f"Synchronous 'run_and_get_details()' called for an async tool '{self.name}'. "
1262
+ f"Use 'a_run_and_get_details()' instead."
1263
+ )
1264
+ return self._process_run_with_details(
1265
+ task=task,
1266
+ context_variables=context_variables,
1267
+ validation_prompt_addition=validation_prompt_addition,
1268
+ messages=messages,
1269
+ ground_truth=ground_truth,
1270
+ )
1271
+
1272
+ async def a_run_and_get_details(
1273
+ self,
1274
+ task: str,
1275
+ context_variables: Optional[ContextVariables] = None,
1276
+ validation_prompt_addition: Optional[str] = None,
1277
+ messages: Optional[list[dict[str, Any]]] = None,
1278
+ ground_truth: Optional[List[str]] = None,
1279
+ ) -> ToolExecutionDetails:
1280
+ if not self._is_original_func_async:
1281
+ warnings.warn(
1282
+ f"Running sync function '{self._original_func_name}' (wrapped by ReliableTool '{self.name}') "
1283
+ f"asynchronously using 'a_run_and_get_details()'. The underlying execution will be synchronous "
1284
+ f"within an executor.",
1285
+ UserWarning,
1286
+ )
1287
+
1288
+ loop = asyncio.get_running_loop()
1289
+ try:
1290
+ func_call = functools.partial(
1291
+ self._process_run_with_details,
1292
+ task=task,
1293
+ context_variables=context_variables,
1294
+ validation_prompt_addition=validation_prompt_addition,
1295
+ messages=messages,
1296
+ ground_truth=ground_truth,
1297
+ )
1298
+ details: ToolExecutionDetails = await loop.run_in_executor(None, func_call)
1299
+ return details
1300
+ except Exception as e:
1301
+ logger.critical(
1302
+ "[%s] a_run_and_get_details encountered an unhandled exception from executor: %s",
1303
+ self.name,
1304
+ e,
1305
+ exc_info=True,
1306
+ )
1307
+ fallback_ctx = ReliableToolContext(task=task, reliable_tool_name=self.name)
1308
+ fallback_ctx.attempts.append(
1309
+ ExecutionAttempt(error=f"Unhandled executor/process error: {type(e).__name__}: {e}")
1310
+ )
1311
+ return ToolExecutionDetails(
1312
+ task=task,
1313
+ is_overall_successful=False,
1314
+ failure_reason=f"Critical unhandled exception during async execution: {type(e).__name__}: {e}",
1315
+ final_tool_context=fallback_ctx,
1316
+ )