ag2 0.9.1__py3-none-any.whl → 0.9.1.post0__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 (357) hide show
  1. {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info}/METADATA +264 -73
  2. ag2-0.9.1.post0.dist-info/RECORD +392 -0
  3. {ag2-0.9.1.dist-info → ag2-0.9.1.post0.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 +4020 -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 +1010 -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 +113 -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 +379 -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/mcp_client.py +208 -0
  277. autogen/messages/__init__.py +7 -0
  278. autogen/messages/agent_messages.py +948 -0
  279. autogen/messages/base_message.py +107 -0
  280. autogen/messages/client_messages.py +171 -0
  281. autogen/messages/print_message.py +49 -0
  282. autogen/oai/__init__.py +53 -0
  283. autogen/oai/anthropic.py +714 -0
  284. autogen/oai/bedrock.py +628 -0
  285. autogen/oai/cerebras.py +299 -0
  286. autogen/oai/client.py +1435 -0
  287. autogen/oai/client_utils.py +169 -0
  288. autogen/oai/cohere.py +479 -0
  289. autogen/oai/gemini.py +990 -0
  290. autogen/oai/gemini_types.py +129 -0
  291. autogen/oai/groq.py +305 -0
  292. autogen/oai/mistral.py +303 -0
  293. autogen/oai/oai_models/__init__.py +11 -0
  294. autogen/oai/oai_models/_models.py +16 -0
  295. autogen/oai/oai_models/chat_completion.py +87 -0
  296. autogen/oai/oai_models/chat_completion_audio.py +32 -0
  297. autogen/oai/oai_models/chat_completion_message.py +86 -0
  298. autogen/oai/oai_models/chat_completion_message_tool_call.py +37 -0
  299. autogen/oai/oai_models/chat_completion_token_logprob.py +63 -0
  300. autogen/oai/oai_models/completion_usage.py +60 -0
  301. autogen/oai/ollama.py +643 -0
  302. autogen/oai/openai_utils.py +881 -0
  303. autogen/oai/together.py +370 -0
  304. autogen/retrieve_utils.py +491 -0
  305. autogen/runtime_logging.py +160 -0
  306. autogen/token_count_utils.py +267 -0
  307. autogen/tools/__init__.py +20 -0
  308. autogen/tools/contrib/__init__.py +9 -0
  309. autogen/tools/contrib/time/__init__.py +7 -0
  310. autogen/tools/contrib/time/time.py +41 -0
  311. autogen/tools/dependency_injection.py +254 -0
  312. autogen/tools/experimental/__init__.py +43 -0
  313. autogen/tools/experimental/browser_use/__init__.py +7 -0
  314. autogen/tools/experimental/browser_use/browser_use.py +161 -0
  315. autogen/tools/experimental/crawl4ai/__init__.py +7 -0
  316. autogen/tools/experimental/crawl4ai/crawl4ai.py +153 -0
  317. autogen/tools/experimental/deep_research/__init__.py +7 -0
  318. autogen/tools/experimental/deep_research/deep_research.py +328 -0
  319. autogen/tools/experimental/duckduckgo/__init__.py +7 -0
  320. autogen/tools/experimental/duckduckgo/duckduckgo_search.py +109 -0
  321. autogen/tools/experimental/google/__init__.py +14 -0
  322. autogen/tools/experimental/google/authentication/__init__.py +11 -0
  323. autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
  324. autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
  325. autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
  326. autogen/tools/experimental/google/drive/__init__.py +9 -0
  327. autogen/tools/experimental/google/drive/drive_functions.py +124 -0
  328. autogen/tools/experimental/google/drive/toolkit.py +88 -0
  329. autogen/tools/experimental/google/model.py +17 -0
  330. autogen/tools/experimental/google/toolkit_protocol.py +19 -0
  331. autogen/tools/experimental/google_search/__init__.py +8 -0
  332. autogen/tools/experimental/google_search/google_search.py +93 -0
  333. autogen/tools/experimental/google_search/youtube_search.py +181 -0
  334. autogen/tools/experimental/messageplatform/__init__.py +17 -0
  335. autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
  336. autogen/tools/experimental/messageplatform/discord/discord.py +288 -0
  337. autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
  338. autogen/tools/experimental/messageplatform/slack/slack.py +391 -0
  339. autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
  340. autogen/tools/experimental/messageplatform/telegram/telegram.py +275 -0
  341. autogen/tools/experimental/perplexity/__init__.py +7 -0
  342. autogen/tools/experimental/perplexity/perplexity_search.py +260 -0
  343. autogen/tools/experimental/tavily/__init__.py +7 -0
  344. autogen/tools/experimental/tavily/tavily_search.py +183 -0
  345. autogen/tools/experimental/web_search_preview/__init__.py +7 -0
  346. autogen/tools/experimental/web_search_preview/web_search_preview.py +114 -0
  347. autogen/tools/experimental/wikipedia/__init__.py +7 -0
  348. autogen/tools/experimental/wikipedia/wikipedia.py +287 -0
  349. autogen/tools/function_utils.py +411 -0
  350. autogen/tools/tool.py +187 -0
  351. autogen/tools/toolkit.py +86 -0
  352. autogen/types.py +29 -0
  353. autogen/version.py +7 -0
  354. ag2-0.9.1.dist-info/RECORD +0 -6
  355. ag2-0.9.1.dist-info/top_level.txt +0 -1
  356. {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/LICENSE +0 -0
  357. {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/NOTICE.md +0 -0
@@ -0,0 +1,1425 @@
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
+ import asyncio
6
+ import copy
7
+ import inspect
8
+ import threading
9
+ import warnings
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+ from functools import partial
13
+ from types import MethodType
14
+ from typing import Annotated, Any, Callable, Literal, Optional, Union
15
+
16
+ from pydantic import BaseModel, field_serializer
17
+
18
+ from ...doc_utils import export_module
19
+ from ...events.agent_events import ErrorEvent, RunCompletionEvent
20
+ from ...io.base import IOStream
21
+ from ...io.run_response import AsyncRunResponse, AsyncRunResponseProtocol, RunResponse, RunResponseProtocol
22
+ from ...io.thread_io_stream import AsyncThreadIOStream, ThreadIOStream
23
+ from ...oai import OpenAIWrapper
24
+ from ...tools import Depends, Tool
25
+ from ...tools.dependency_injection import inject_params, on
26
+ from ..agent import Agent
27
+ from ..chat import ChatResult
28
+ from ..conversable_agent import ConversableAgent
29
+ from ..group.context_expression import ContextExpression
30
+ from ..group.context_str import ContextStr
31
+ from ..group.context_variables import __CONTEXT_VARIABLES_PARAM_NAME__, ContextVariables
32
+ from ..groupchat import SELECT_SPEAKER_PROMPT_TEMPLATE, GroupChat, GroupChatManager
33
+ from ..user_proxy_agent import UserProxyAgent
34
+
35
+ __all__ = [
36
+ "AFTER_WORK",
37
+ "ON_CONDITION",
38
+ "AfterWork",
39
+ "AfterWorkOption",
40
+ "OnCondition",
41
+ "OnContextCondition",
42
+ "SwarmAgent",
43
+ "a_initiate_swarm_chat",
44
+ "create_swarm_transition",
45
+ "initiate_swarm_chat",
46
+ "register_hand_off",
47
+ "run_swarm",
48
+ ]
49
+
50
+
51
+ # Created tool executor's name
52
+ __TOOL_EXECUTOR_NAME__ = "_Swarm_Tool_Executor"
53
+
54
+
55
+ @export_module("autogen")
56
+ class AfterWorkOption(Enum):
57
+ TERMINATE = "TERMINATE"
58
+ REVERT_TO_USER = "REVERT_TO_USER"
59
+ STAY = "STAY"
60
+ SWARM_MANAGER = "SWARM_MANAGER"
61
+
62
+
63
+ @dataclass
64
+ @export_module("autogen")
65
+ class AfterWork: # noqa: N801
66
+ """Handles the next step in the conversation when an agent doesn't suggest a tool call or a handoff.
67
+
68
+ Args:
69
+ agent (Union[AfterWorkOption, ConversableAgent, str, Callable[..., Any]]): The agent to hand off to or the after work option. Can be a ConversableAgent, a string name of a ConversableAgent, an AfterWorkOption, or a Callable.
70
+ The Callable signature is:
71
+ def my_after_work_func(last_speaker: ConversableAgent, messages: list[dict[str, Any]], groupchat: GroupChat) -> Union[AfterWorkOption, ConversableAgent, str]:
72
+ next_agent_selection_msg (Optional[Union[str, Callable[..., Any]]]): Optional message to use for the agent selection (in internal group chat), only valid for when agent is AfterWorkOption.SWARM_MANAGER.
73
+ If a string, it will be used as a template and substitute the context variables.
74
+ If a Callable, it should have the signature:
75
+ def my_selection_message(agent: ConversableAgent, messages: list[dict[str, Any]]) -> str
76
+ """
77
+
78
+ agent: Union[AfterWorkOption, ConversableAgent, str, Callable[..., Any]]
79
+ next_agent_selection_msg: Optional[
80
+ Union[str, ContextStr, Callable[[ConversableAgent, list[dict[str, Any]]], str]]
81
+ ] = None
82
+
83
+ def __post_init__(self) -> None:
84
+ if isinstance(self.agent, str):
85
+ self.agent = AfterWorkOption(self.agent.upper())
86
+
87
+ # next_agent_selection_msg is only valid for when agent is AfterWorkOption.SWARM_MANAGER, but isn't mandatory
88
+ if self.next_agent_selection_msg is not None:
89
+ if not (
90
+ isinstance(self.next_agent_selection_msg, (str, ContextStr)) or callable(self.next_agent_selection_msg)
91
+ ):
92
+ raise ValueError("next_agent_selection_msg must be a string, ContextStr, or a Callable")
93
+
94
+ if self.agent != AfterWorkOption.SWARM_MANAGER:
95
+ warnings.warn(
96
+ "next_agent_selection_msg is only valid for agent=AfterWorkOption.SWARM_MANAGER. Ignoring the value.",
97
+ UserWarning,
98
+ )
99
+ self.next_agent_selection_msg = None
100
+
101
+
102
+ class AFTER_WORK(AfterWork): # noqa: N801
103
+ """Deprecated: Use AfterWork instead. This class will be removed in a future version (TBD)."""
104
+
105
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
106
+ warnings.warn(
107
+ "AFTER_WORK is deprecated and will be removed in a future version (TBD). Use AfterWork instead.",
108
+ DeprecationWarning,
109
+ stacklevel=2,
110
+ )
111
+ super().__init__(*args, **kwargs)
112
+
113
+
114
+ @dataclass
115
+ @export_module("autogen")
116
+ class OnCondition: # noqa: N801
117
+ """Defines a condition for transitioning to another agent or nested chats.
118
+
119
+ This is for LLM-based condition evaluation where these conditions are translated into tools and attached to the agent.
120
+
121
+ These are evaluated after the OnCondition conditions but before the AfterWork conditions.
122
+
123
+ Args:
124
+ target (Optional[Union[ConversableAgent, dict[str, Any]]]): The agent to hand off to or the nested chat configuration. Can be a ConversableAgent or a Dict.
125
+ If a Dict, it should follow the convention of the nested chat configuration, with the exception of a carryover configuration which is unique to Swarms.
126
+ Swarm Nested chat documentation: https://docs.ag2.ai/docs/user-guide/advanced-concepts/swarm-deep-dive#registering-handoffs-to-a-nested-chat
127
+ condition (Optional[Union[str, ContextStr, Callable[[ConversableAgent, list[dict[str, Any]]], str]]]): The condition for transitioning to the target agent, evaluated by the LLM.
128
+ If a string or Callable, no automatic context variable substitution occurs.
129
+ If a ContextStr, context variable substitution occurs.
130
+ The Callable signature is:
131
+ def my_condition_string(agent: ConversableAgent, messages: list[Dict[str, Any]]) -> str
132
+ available (Optional[Union[Callable[[ConversableAgent, list[dict[str, Any]]], bool], str, ContextExpression]]): Optional condition to determine if this OnCondition is included for the LLM to evaluate.
133
+ If a string, it will look up the value of the context variable with that name, which should be a bool, to determine whether it should include this condition.
134
+ If a ContextExpression, it will evaluate the logical expression against the context variables. Can use not, and, or, and comparison operators (>, <, >=, <=, ==, !=).
135
+ Example: ContextExpression("not(${logged_in} and ${is_admin}) or (${guest_checkout})")
136
+ Example with comparison: ContextExpression("${attempts} >= 3 or ${is_premium} == True or ${tier} == 'gold'")
137
+ The Callable signature is:
138
+ def my_available_func(agent: ConversableAgent, messages: list[Dict[str, Any]]) -> bool
139
+ """
140
+
141
+ target: Optional[Union[ConversableAgent, dict[str, Any]]] = None
142
+ condition: Optional[Union[str, ContextStr, Callable[[ConversableAgent, list[dict[str, Any]]], str]]] = None
143
+ available: Optional[Union[Callable[[ConversableAgent, list[dict[str, Any]]], bool], str, ContextExpression]] = None
144
+
145
+ def __post_init__(self) -> None:
146
+ # Ensure valid types
147
+ if (self.target is not None) and (not isinstance(self.target, (ConversableAgent, dict))):
148
+ raise ValueError("'target' must be a ConversableAgent or a dict")
149
+
150
+ # Ensure they have a condition
151
+ if isinstance(self.condition, str):
152
+ if not self.condition.strip():
153
+ raise ValueError("'condition' must be a non-empty string")
154
+ else:
155
+ if not isinstance(self.condition, ContextStr) and not callable(self.condition):
156
+ raise ValueError("'condition' must be a string, ContextStr, or callable")
157
+
158
+ if (self.available is not None) and (
159
+ not (isinstance(self.available, (str, ContextExpression)) or callable(self.available))
160
+ ):
161
+ raise ValueError("'available' must be a callable, a string, or a ContextExpression")
162
+
163
+
164
+ class ON_CONDITION(OnCondition): # noqa: N801
165
+ """Deprecated: Use OnCondition instead. This class will be removed in a future version (TBD)."""
166
+
167
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
168
+ warnings.warn(
169
+ "ON_CONDITION is deprecated and will be removed in a future version (TBD). Use OnCondition instead.",
170
+ DeprecationWarning,
171
+ stacklevel=2,
172
+ )
173
+ super().__init__(*args, **kwargs)
174
+
175
+
176
+ @dataclass
177
+ @export_module("autogen")
178
+ class OnContextCondition: # noqa: N801
179
+ """Defines a condition for transitioning to another agent or nested chats using context variables and the ContextExpression class.
180
+
181
+ This is for context variable-based condition evaluation (does not use the agent's LLM).
182
+
183
+ These are evaluated before the OnCondition and AfterWork conditions.
184
+
185
+ Args:
186
+ target (Optional[Union[ConversableAgent, dict[str, Any]]]): The agent to hand off to or the nested chat configuration. Can be a ConversableAgent or a Dict.
187
+ If a Dict, it should follow the convention of the nested chat configuration, with the exception of a carryover configuration which is unique to Swarms.
188
+ Swarm Nested chat documentation: https://docs.ag2.ai/docs/user-guide/advanced-concepts/swarm-deep-dive#registering-handoffs-to-a-nested-chat
189
+ condition (Optional[Union[str, ContextExpression]]): The condition for transitioning to the target agent, evaluated by the LLM.
190
+ If a string, it needs to represent a context variable key and the value will be evaluated as a boolean
191
+ If a ContextExpression, it will evaluate the logical expression against the context variables. If it is True, the transition will occur.
192
+ Can use not, and, or, and comparison operators (>, <, >=, <=, ==, !=).
193
+ Example: ContextExpression("not(${logged_in} and ${is_admin}) or (${guest_checkout})")
194
+ Example with comparison: ContextExpression("${attempts} >= 3 or ${is_premium} == True or ${tier} == 'gold'")
195
+ available (Optional[Union[Callable[[ConversableAgent, list[dict[str, Any]]], bool], str, ContextExpression]]): Optional condition to determine if this OnContextCondition is included for the LLM to evaluate.
196
+ If a string, it will look up the value of the context variable with that name, which should be a bool, to determine whether it should include this condition.
197
+ If a ContextExpression, it will evaluate the logical expression against the context variables. Can use not, and, or, and comparison operators (>, <, >=, <=, ==, !=).
198
+ The Callable signature is:
199
+ def my_available_func(agent: ConversableAgent, messages: list[Dict[str, Any]]) -> bool
200
+
201
+ """
202
+
203
+ target: Optional[Union[ConversableAgent, dict[str, Any]]] = None
204
+ condition: Optional[Union[str, ContextExpression]] = None
205
+ available: Optional[Union[Callable[[ConversableAgent, list[dict[str, Any]]], bool], str, ContextExpression]] = None
206
+
207
+ def __post_init__(self) -> None:
208
+ # Ensure valid types
209
+ if (self.target is not None) and (not isinstance(self.target, (ConversableAgent, dict))):
210
+ raise ValueError("'target' must be a ConversableAgent or a dict")
211
+
212
+ # Ensure they have a condition
213
+ if isinstance(self.condition, str):
214
+ if not self.condition.strip():
215
+ raise ValueError("'condition' must be a non-empty string")
216
+
217
+ self._context_condition = ContextExpression("${" + self.condition + "}")
218
+ else:
219
+ if not isinstance(self.condition, ContextExpression):
220
+ raise ValueError("'condition' must be a string on ContextExpression")
221
+
222
+ self._context_condition = self.condition
223
+
224
+ if (self.available is not None) and (
225
+ not (isinstance(self.available, (str, ContextExpression)) or callable(self.available))
226
+ ):
227
+ raise ValueError("'available' must be a callable, a string, or a ContextExpression")
228
+
229
+
230
+ def _establish_swarm_agent(agent: ConversableAgent) -> None:
231
+ """Establish the swarm agent with the swarm-related attributes and hooks. Not for the tool executor.
232
+
233
+ Args:
234
+ agent (ConversableAgent): The agent to establish as a swarm agent.
235
+ """
236
+
237
+ def _swarm_agent_str(self: ConversableAgent) -> str:
238
+ """Customise the __str__ method to show the agent name for transition messages."""
239
+ return f"Swarm agent --> {self.name}"
240
+
241
+ agent._swarm_after_work = None # type: ignore[attr-defined]
242
+ agent._swarm_after_work_selection_msg = None # type: ignore[attr-defined]
243
+
244
+ # Store nested chats hand offs as we'll establish these in the initiate_swarm_chat
245
+ # List of Dictionaries containing the nested_chats and condition
246
+ agent._swarm_nested_chat_handoffs = [] # type: ignore[attr-defined]
247
+
248
+ # Store conditional functions (and their OnCondition instances) to add/remove later when transitioning to this agent
249
+ agent._swarm_conditional_functions = {} # type: ignore[attr-defined]
250
+
251
+ # Register the hook to update agent state (except tool executor)
252
+ agent.register_hook("update_agent_state", _update_conditional_functions)
253
+
254
+ # Store the OnContextConditions for evaluation (list[OnContextCondition])
255
+ agent._swarm_oncontextconditions = [] # type: ignore[attr-defined]
256
+
257
+ # Register a reply function to run Python function-based OnConditions before any other reply function
258
+ agent.register_reply(trigger=([Agent, None]), reply_func=_run_oncontextconditions, position=0)
259
+
260
+ agent._get_display_name = MethodType(_swarm_agent_str, agent) # type: ignore[method-assign]
261
+
262
+ # Mark this agent as established as a swarm agent
263
+ agent._swarm_is_established = True # type: ignore[attr-defined]
264
+
265
+
266
+ def _link_agents_to_swarm_manager(agents: list[Agent], group_chat_manager: Agent) -> None:
267
+ """Link all agents to the GroupChatManager so they can access the underlying GroupChat and other agents.
268
+
269
+ This is primarily used so that agents can set the tool executor's _swarm_next_agent attribute to control
270
+ the next agent programmatically.
271
+
272
+ Does not link the Tool Executor agent.
273
+ """
274
+ for agent in agents:
275
+ agent._swarm_manager = group_chat_manager # type: ignore[attr-defined]
276
+
277
+
278
+ def _run_oncontextconditions(
279
+ agent: ConversableAgent,
280
+ messages: Optional[list[dict[str, Any]]] = None,
281
+ sender: Optional[Agent] = None,
282
+ config: Optional[Any] = None,
283
+ ) -> tuple[bool, Optional[Union[str, dict[str, Any]]]]:
284
+ """Run OnContextConditions for an agent before any other reply function."""
285
+ for on_condition in agent._swarm_oncontextconditions: # type: ignore[attr-defined]
286
+ is_available = True
287
+
288
+ if on_condition.available is not None:
289
+ if callable(on_condition.available):
290
+ is_available = on_condition.available(agent, next(iter(agent.chat_messages.values())))
291
+ elif isinstance(on_condition.available, str):
292
+ is_available = agent.context_variables.get(on_condition.available) or False
293
+ elif isinstance(on_condition.available, ContextExpression):
294
+ is_available = on_condition.available.evaluate(agent.context_variables)
295
+
296
+ if is_available and on_condition._context_condition.evaluate(agent.context_variables):
297
+ # Condition has been met, we'll set the Tool Executor's _swarm_next_agent
298
+ # attribute and that will be picked up on the next iteration when
299
+ # _determine_next_agent is called
300
+ for agent in agent._swarm_manager.groupchat.agents: # type: ignore[attr-defined]
301
+ if agent.name == __TOOL_EXECUTOR_NAME__:
302
+ agent._swarm_next_agent = on_condition.target # type: ignore[attr-defined]
303
+ break
304
+
305
+ if isinstance(on_condition.target, ConversableAgent):
306
+ transfer_name = on_condition.target.name
307
+ else:
308
+ transfer_name = "a nested chat"
309
+
310
+ return True, "[Handing off to " + transfer_name + "]"
311
+
312
+ return False, None
313
+
314
+
315
+ def _modify_context_variables_param(f: Callable[..., Any], context_variables: ContextVariables) -> Callable[..., Any]:
316
+ """Modifies the context_variables parameter to use dependency injection and link it to the swarm context variables.
317
+
318
+ This essentially changes:
319
+ def some_function(some_variable: int, context_variables: ContextVariables) -> str:
320
+
321
+ to:
322
+
323
+ def some_function(some_variable: int, context_variables: Annotated[ContextVariables, Depends(on(self.context_variables))]) -> str:
324
+ """
325
+ sig = inspect.signature(f)
326
+
327
+ # Check if context_variables parameter exists and update it if so
328
+ if __CONTEXT_VARIABLES_PARAM_NAME__ in sig.parameters:
329
+ new_params = []
330
+ for name, param in sig.parameters.items():
331
+ if name == __CONTEXT_VARIABLES_PARAM_NAME__:
332
+ # Replace with new annotation using Depends
333
+ new_param = param.replace(annotation=Annotated[ContextVariables, Depends(on(context_variables))])
334
+ new_params.append(new_param)
335
+ else:
336
+ new_params.append(param)
337
+
338
+ # Update signature
339
+ new_sig = sig.replace(parameters=new_params)
340
+ f.__signature__ = new_sig # type: ignore[attr-defined]
341
+
342
+ return f
343
+
344
+
345
+ def _change_tool_context_variables_to_depends(
346
+ agent: ConversableAgent, current_tool: Tool, context_variables: ContextVariables
347
+ ) -> None:
348
+ """Checks for the context_variables parameter in the tool and updates it to use dependency injection."""
349
+
350
+ # If the tool has a context_variables parameter, remove the tool and reregister it without the parameter
351
+ if __CONTEXT_VARIABLES_PARAM_NAME__ in current_tool.tool_schema["function"]["parameters"]["properties"]:
352
+ # We'll replace the tool, so start with getting the underlying function
353
+ tool_func = current_tool._func
354
+
355
+ # Remove the Tool from the agent
356
+ name = current_tool._name
357
+ description = current_tool._description
358
+ agent.remove_tool_for_llm(current_tool)
359
+
360
+ # Recreate the tool without the context_variables parameter
361
+ tool_func = _modify_context_variables_param(current_tool._func, context_variables)
362
+ tool_func = inject_params(tool_func)
363
+ new_tool = ConversableAgent._create_tool_if_needed(func_or_tool=tool_func, name=name, description=description)
364
+
365
+ # Re-register with the agent
366
+ agent.register_for_llm()(new_tool)
367
+
368
+
369
+ def _prepare_swarm_agents(
370
+ initial_agent: ConversableAgent,
371
+ agents: list[ConversableAgent],
372
+ context_variables: ContextVariables,
373
+ exclude_transit_message: bool = True,
374
+ ) -> tuple[ConversableAgent, list[ConversableAgent]]:
375
+ """Validates agents, create the tool executor, configure nested chats.
376
+
377
+ Args:
378
+ initial_agent (ConversableAgent): The first agent in the conversation.
379
+ agents (list[ConversableAgent]): List of all agents in the conversation.
380
+ context_variables (ContextVariables): Context variables to assign to all agents.
381
+ exclude_transit_message (bool): Whether to exclude transit messages from the agents.
382
+
383
+ Returns:
384
+ ConversableAgent: The tool executor agent.
385
+ list[ConversableAgent]: List of nested chat agents.
386
+ """
387
+ if not isinstance(initial_agent, ConversableAgent):
388
+ raise ValueError("initial_agent must be a ConversableAgent")
389
+ if not all(isinstance(agent, ConversableAgent) for agent in agents):
390
+ raise ValueError("Agents must be a list of ConversableAgents")
391
+
392
+ # Initialize all agents as swarm agents
393
+ for agent in agents:
394
+ if not hasattr(agent, "_swarm_is_established"):
395
+ _establish_swarm_agent(agent)
396
+
397
+ # Ensure all agents in hand-off after-works are in the passed in agents list
398
+ for agent in agents:
399
+ if (agent._swarm_after_work is not None and isinstance(agent._swarm_after_work.agent, ConversableAgent)) and ( # type: ignore[attr-defined]
400
+ agent._swarm_after_work.agent not in agents # type: ignore[attr-defined]
401
+ ):
402
+ raise ValueError("Agent in hand-off must be in the agents list")
403
+
404
+ tool_execution = ConversableAgent(
405
+ name=__TOOL_EXECUTOR_NAME__,
406
+ system_message="Tool Execution, do not use this agent directly.",
407
+ )
408
+ _set_to_tool_execution(tool_execution)
409
+
410
+ nested_chat_agents: list[ConversableAgent] = []
411
+ for agent in agents:
412
+ _create_nested_chats(agent, nested_chat_agents)
413
+
414
+ # Update any agent's tools that have context_variables as a parameter
415
+ # To use Dependency Injection
416
+
417
+ # Update tool execution agent with all the functions from all the agents
418
+ for agent in agents + nested_chat_agents:
419
+ tool_execution._function_map.update(agent._function_map)
420
+
421
+ # Add conditional functions to the tool_execution agent
422
+ for func_name, (func, _) in agent._swarm_conditional_functions.items(): # type: ignore[attr-defined]
423
+ tool_execution._function_map[func_name] = func
424
+
425
+ # Update any agent tools that have context_variables parameters to use Dependency Injection
426
+ for tool in agent.tools:
427
+ _change_tool_context_variables_to_depends(agent, tool, context_variables)
428
+
429
+ # Add all tools to the Tool Executor agent
430
+ for tool in agent.tools:
431
+ tool_execution.register_for_execution(serialize=False, silent_override=True)(tool)
432
+
433
+ if exclude_transit_message:
434
+ # get all transit functions names
435
+ to_be_removed = []
436
+ for agent in agents + nested_chat_agents:
437
+ if hasattr(agent, "_swarm_conditional_functions"):
438
+ to_be_removed += list(agent._swarm_conditional_functions.keys())
439
+
440
+ # register hook to remove transit messages for swarm agents
441
+ for agent in agents + nested_chat_agents:
442
+ agent.register_hook("process_all_messages_before_reply", make_remove_function(to_be_removed))
443
+
444
+ return tool_execution, nested_chat_agents
445
+
446
+
447
+ def _create_nested_chats(agent: ConversableAgent, nested_chat_agents: list[ConversableAgent]) -> None:
448
+ """Create nested chat agents and register nested chats.
449
+
450
+ Args:
451
+ agent (ConversableAgent): The agent to create nested chat agents for, including registering the hand offs.
452
+ nested_chat_agents (list[ConversableAgent]): List for all nested chat agents, appends to this.
453
+ """
454
+
455
+ def create_nested_chat_agent(agent: ConversableAgent, nested_chats: dict[str, Any]) -> ConversableAgent:
456
+ """Create a nested chat agent for a nested chat configuration.
457
+
458
+ Args:
459
+ agent (ConversableAgent): The agent to create the nested chat agent for.
460
+ nested_chats (dict[str, Any]): The nested chat configuration.
461
+
462
+ Returns:
463
+ The created nested chat agent.
464
+ """
465
+ # Create a nested chat agent specifically for this nested chat
466
+ nested_chat_agent = ConversableAgent(name=f"nested_chat_{agent.name}_{i + 1}")
467
+
468
+ nested_chat_agent.register_nested_chats(
469
+ nested_chats["chat_queue"],
470
+ reply_func_from_nested_chats=nested_chats.get("reply_func_from_nested_chats")
471
+ or "summary_from_nested_chats",
472
+ config=nested_chats.get("config"),
473
+ trigger=lambda sender: True,
474
+ position=0,
475
+ use_async=nested_chats.get("use_async", False),
476
+ )
477
+
478
+ # After the nested chat is complete, transfer back to the parent agent
479
+ register_hand_off(nested_chat_agent, AfterWork(agent=agent))
480
+
481
+ return nested_chat_agent
482
+
483
+ for i, nested_chat_handoff in enumerate(agent._swarm_nested_chat_handoffs): # type: ignore[attr-defined]
484
+ llm_nested_chats: dict[str, Any] = nested_chat_handoff["nested_chats"]
485
+
486
+ # Create nested chat agent
487
+ nested_chat_agent = create_nested_chat_agent(agent, llm_nested_chats)
488
+ nested_chat_agents.append(nested_chat_agent)
489
+
490
+ # Nested chat is triggered through an agent transfer to this nested chat agent
491
+ condition = nested_chat_handoff["condition"]
492
+ available = nested_chat_handoff["available"]
493
+ register_hand_off(agent, OnCondition(target=nested_chat_agent, condition=condition, available=available))
494
+
495
+ for i, nested_chat_context_handoff in enumerate(agent._swarm_oncontextconditions): # type: ignore[attr-defined]
496
+ if isinstance(nested_chat_context_handoff.target, dict):
497
+ context_nested_chats: dict[str, Any] = nested_chat_context_handoff.target
498
+
499
+ # Create nested chat agent
500
+ nested_chat_agent = create_nested_chat_agent(agent, context_nested_chats)
501
+ nested_chat_agents.append(nested_chat_agent)
502
+
503
+ # Update the OnContextCondition, replacing the nested chat dictionary with the nested chat agent
504
+ nested_chat_context_handoff.target = nested_chat_agent
505
+
506
+
507
+ def _process_initial_messages(
508
+ messages: Union[list[dict[str, Any]], str],
509
+ user_agent: Optional[UserProxyAgent],
510
+ agents: list[ConversableAgent],
511
+ nested_chat_agents: list[ConversableAgent],
512
+ ) -> tuple[list[dict[str, Any]], Optional[Agent], list[str], list[Agent]]:
513
+ """Process initial messages, validating agent names against messages, and determining the last agent to speak.
514
+
515
+ Args:
516
+ messages: Initial messages to process.
517
+ user_agent: Optional user proxy agent passed in to a_/initiate_swarm_chat.
518
+ agents: Agents in swarm.
519
+ nested_chat_agents: List of nested chat agents.
520
+
521
+ Returns:
522
+ list[dict[str, Any]]: Processed message(s).
523
+ Agent: Last agent to speak.
524
+ list[str]: List of agent names.
525
+ list[Agent]: List of temporary user proxy agents to add to GroupChat.
526
+ """
527
+ if isinstance(messages, str):
528
+ messages = [{"role": "user", "content": messages}]
529
+
530
+ swarm_agent_names = [agent.name for agent in agents + nested_chat_agents]
531
+
532
+ # If there's only one message and there's no identified swarm agent
533
+ # Start with a user proxy agent, creating one if they haven't passed one in
534
+ last_agent: Optional[Agent]
535
+ temp_user_proxy: Optional[Agent] = None
536
+ temp_user_list: list[Agent] = []
537
+ if len(messages) == 1 and "name" not in messages[0] and not user_agent:
538
+ temp_user_proxy = UserProxyAgent(name="_User", code_execution_config=False)
539
+ last_agent = temp_user_proxy
540
+ temp_user_list.append(temp_user_proxy)
541
+ else:
542
+ last_message = messages[0]
543
+ if "name" in last_message:
544
+ if last_message["name"] in swarm_agent_names:
545
+ last_agent = next(agent for agent in agents + nested_chat_agents if agent.name == last_message["name"]) # type: ignore[assignment]
546
+ elif user_agent and last_message["name"] == user_agent.name:
547
+ last_agent = user_agent
548
+ else:
549
+ raise ValueError(f"Invalid swarm agent name in last message: {last_message['name']}")
550
+ else:
551
+ last_agent = user_agent if user_agent else temp_user_proxy
552
+
553
+ return messages, last_agent, swarm_agent_names, temp_user_list
554
+
555
+
556
+ def _setup_context_variables(
557
+ tool_execution: ConversableAgent,
558
+ agents: list[ConversableAgent],
559
+ manager: GroupChatManager,
560
+ context_variables: ContextVariables,
561
+ ) -> None:
562
+ """Assign a common context_variables reference to all agents in the swarm, including the tool executor and group chat manager.
563
+
564
+ Args:
565
+ tool_execution: The tool execution agent.
566
+ agents: List of all agents in the conversation.
567
+ manager: GroupChatManager instance.
568
+ context_variables: Context variables to assign to all agents.
569
+ """
570
+ for agent in agents + [tool_execution] + [manager]:
571
+ agent.context_variables = context_variables
572
+
573
+
574
+ def _cleanup_temp_user_messages(chat_result: ChatResult) -> None:
575
+ """Remove temporary user proxy agent name from messages before returning.
576
+
577
+ Args:
578
+ chat_result: ChatResult instance.
579
+ """
580
+ for message in chat_result.chat_history:
581
+ if "name" in message and message["name"] == "_User":
582
+ del message["name"]
583
+
584
+
585
+ def _prepare_groupchat_auto_speaker(
586
+ groupchat: GroupChat,
587
+ last_swarm_agent: ConversableAgent,
588
+ after_work_next_agent_selection_msg: Optional[
589
+ Union[str, ContextStr, Callable[[ConversableAgent, list[dict[str, Any]]], str]]
590
+ ],
591
+ ) -> None:
592
+ """Prepare the group chat for auto speaker selection, includes updating or restore the groupchat speaker selection message.
593
+
594
+ Tool Executor and Nested Chat agents will be removed from the available agents list.
595
+
596
+ Args:
597
+ groupchat (GroupChat): GroupChat instance.
598
+ last_swarm_agent (ConversableAgent): The last swarm agent for which the LLM config is used
599
+ after_work_next_agent_selection_msg (Union[str, ContextStr, Callable[..., Any]]): Optional message to use for the agent selection (in internal group chat).
600
+ if a string, it will be use the string a the prompt template, no context variable substitution however '{agentlist}' will be substituted for a list of agents.
601
+ if a ContextStr, it will substitute the agentlist first and then the context variables
602
+ if a Callable, it will not substitute the agentlist or context variables, signature:
603
+ def my_selection_message(agent: ConversableAgent, messages: list[dict[str, Any]]) -> str
604
+ """
605
+
606
+ def substitute_agentlist(template: str) -> str:
607
+ # Run through group chat's string substitution first for {agentlist}
608
+ # We need to do this so that the next substitution doesn't fail with agentlist
609
+ # and we can remove the tool executor and nested chats from the available agents list
610
+ agent_list = [
611
+ agent
612
+ for agent in groupchat.agents
613
+ if agent.name != __TOOL_EXECUTOR_NAME__ and not agent.name.startswith("nested_chat_")
614
+ ]
615
+
616
+ groupchat.select_speaker_prompt_template = template
617
+ return groupchat.select_speaker_prompt(agent_list)
618
+
619
+ if after_work_next_agent_selection_msg is None:
620
+ # If there's no selection message, restore the default and filter out the tool executor and nested chat agents
621
+ groupchat.select_speaker_prompt_template = substitute_agentlist(SELECT_SPEAKER_PROMPT_TEMPLATE)
622
+ elif isinstance(after_work_next_agent_selection_msg, str):
623
+ # No context variable substitution for string, but agentlist will be substituted
624
+ groupchat.select_speaker_prompt_template = substitute_agentlist(after_work_next_agent_selection_msg)
625
+ elif isinstance(after_work_next_agent_selection_msg, ContextStr):
626
+ # Replace the agentlist in the string first, putting it into a new ContextStr
627
+ agent_list_replaced_string = ContextStr(
628
+ template=substitute_agentlist(after_work_next_agent_selection_msg.template)
629
+ )
630
+
631
+ # Then replace the context variables
632
+ groupchat.select_speaker_prompt_template = agent_list_replaced_string.format( # type: ignore[assignment]
633
+ last_swarm_agent.context_variables
634
+ )
635
+ elif callable(after_work_next_agent_selection_msg):
636
+ groupchat.select_speaker_prompt_template = substitute_agentlist(
637
+ after_work_next_agent_selection_msg(last_swarm_agent, groupchat.messages)
638
+ )
639
+
640
+
641
+ def _determine_next_agent(
642
+ last_speaker: ConversableAgent,
643
+ groupchat: GroupChat,
644
+ initial_agent: ConversableAgent,
645
+ use_initial_agent: bool,
646
+ tool_execution: ConversableAgent,
647
+ swarm_agent_names: list[str],
648
+ user_agent: Optional[UserProxyAgent],
649
+ swarm_after_work: Optional[Union[AfterWorkOption, Callable[..., Any]]],
650
+ ) -> Optional[Union[Agent, Literal["auto"]]]:
651
+ """Determine the next agent in the conversation.
652
+
653
+ Args:
654
+ last_speaker (ConversableAgent): The last agent to speak.
655
+ groupchat (GroupChat): GroupChat instance.
656
+ initial_agent (ConversableAgent): The initial agent in the conversation.
657
+ use_initial_agent (bool): Whether to use the initial agent straight away.
658
+ tool_execution (ConversableAgent): The tool execution agent.
659
+ swarm_agent_names (list[str]): List of agent names.
660
+ user_agent (UserProxyAgent): Optional user proxy agent.
661
+ swarm_after_work (Union[AfterWorkOption, Callable[..., Any]]): Method to handle conversation continuation when an agent doesn't select the next agent.
662
+ """
663
+ if use_initial_agent:
664
+ return initial_agent
665
+
666
+ if "tool_calls" in groupchat.messages[-1]:
667
+ return tool_execution
668
+
669
+ after_work_condition = None
670
+
671
+ if tool_execution._swarm_next_agent is not None: # type: ignore[attr-defined]
672
+ next_agent: Optional[Agent] = tool_execution._swarm_next_agent # type: ignore[attr-defined]
673
+ tool_execution._swarm_next_agent = None # type: ignore[attr-defined]
674
+
675
+ if not isinstance(next_agent, AfterWorkOption):
676
+ # Check for string, access agent from group chat.
677
+
678
+ if isinstance(next_agent, str):
679
+ if next_agent in swarm_agent_names:
680
+ next_agent = groupchat.agent_by_name(name=next_agent)
681
+ else:
682
+ raise ValueError(
683
+ f"No agent found with the name '{next_agent}'. Ensure the agent exists in the swarm."
684
+ )
685
+
686
+ return next_agent
687
+ else:
688
+ after_work_condition = next_agent
689
+
690
+ # get the last swarm agent
691
+ last_swarm_speaker = None
692
+ for message in reversed(groupchat.messages):
693
+ if "name" in message and message["name"] in swarm_agent_names and message["name"] != __TOOL_EXECUTOR_NAME__:
694
+ agent = groupchat.agent_by_name(name=message["name"])
695
+ if isinstance(agent, ConversableAgent):
696
+ last_swarm_speaker = agent
697
+ break
698
+ if last_swarm_speaker is None:
699
+ raise ValueError("No swarm agent found in the message history")
700
+
701
+ # If the user last spoke, return to the agent prior
702
+ if after_work_condition is None and (
703
+ (user_agent and last_speaker == user_agent) or groupchat.messages[-1]["role"] == "tool"
704
+ ):
705
+ return last_swarm_speaker
706
+
707
+ after_work_next_agent_selection_msg = None
708
+
709
+ if after_work_condition is None:
710
+ # Resolve after_work condition if one hasn't been passed in (agent-level overrides global)
711
+ after_work_condition = (
712
+ last_swarm_speaker._swarm_after_work # type: ignore[attr-defined]
713
+ if last_swarm_speaker._swarm_after_work is not None # type: ignore[attr-defined]
714
+ else swarm_after_work
715
+ )
716
+
717
+ if isinstance(after_work_condition, AfterWork):
718
+ after_work_next_agent_selection_msg = after_work_condition.next_agent_selection_msg
719
+ after_work_condition = after_work_condition.agent
720
+
721
+ # Evaluate callable after_work
722
+ if callable(after_work_condition):
723
+ after_work_condition = after_work_condition(last_swarm_speaker, groupchat.messages, groupchat)
724
+
725
+ if isinstance(after_work_condition, str): # Agent name in a string
726
+ if after_work_condition in swarm_agent_names:
727
+ return groupchat.agent_by_name(name=after_work_condition)
728
+ else:
729
+ raise ValueError(f"Invalid agent name in after_work: {after_work_condition}")
730
+ elif isinstance(after_work_condition, ConversableAgent):
731
+ return after_work_condition
732
+ elif isinstance(after_work_condition, AfterWorkOption):
733
+ if after_work_condition == AfterWorkOption.TERMINATE:
734
+ return None
735
+ elif after_work_condition == AfterWorkOption.REVERT_TO_USER:
736
+ return None if user_agent is None else user_agent
737
+ elif after_work_condition == AfterWorkOption.STAY:
738
+ return last_swarm_speaker
739
+ elif after_work_condition == AfterWorkOption.SWARM_MANAGER:
740
+ _prepare_groupchat_auto_speaker(groupchat, last_swarm_speaker, after_work_next_agent_selection_msg)
741
+ return "auto"
742
+ else:
743
+ raise ValueError("Invalid After Work condition or return value from callable")
744
+
745
+
746
+ def create_swarm_transition(
747
+ initial_agent: ConversableAgent,
748
+ tool_execution: ConversableAgent,
749
+ swarm_agent_names: list[str],
750
+ user_agent: Optional[UserProxyAgent],
751
+ swarm_after_work: Optional[Union[AfterWorkOption, Callable[..., Any]]],
752
+ ) -> Callable[[ConversableAgent, GroupChat], Optional[Union[Agent, Literal["auto"]]]]:
753
+ """Creates a transition function for swarm chat with enclosed state for the use_initial_agent.
754
+
755
+ Args:
756
+ initial_agent (ConversableAgent): The first agent to speak
757
+ tool_execution (ConversableAgent): The tool execution agent
758
+ swarm_agent_names (list[str]): List of all agent names
759
+ user_agent (UserProxyAgent): Optional user proxy agent
760
+ swarm_after_work (Union[AfterWorkOption, Callable[..., Any]]): Swarm-level after work
761
+
762
+ Returns:
763
+ Callable transition function (for sync and async swarm chats)
764
+ """
765
+ # Create enclosed state, this will be set once per creation so will only be True on the first execution
766
+ # of swarm_transition
767
+ state = {"use_initial_agent": True}
768
+
769
+ def swarm_transition(
770
+ last_speaker: ConversableAgent, groupchat: GroupChat
771
+ ) -> Optional[Union[Agent, Literal["auto"]]]:
772
+ result = _determine_next_agent(
773
+ last_speaker=last_speaker,
774
+ groupchat=groupchat,
775
+ initial_agent=initial_agent,
776
+ use_initial_agent=state["use_initial_agent"],
777
+ tool_execution=tool_execution,
778
+ swarm_agent_names=swarm_agent_names,
779
+ user_agent=user_agent,
780
+ swarm_after_work=swarm_after_work,
781
+ )
782
+ state["use_initial_agent"] = False
783
+ return result
784
+
785
+ return swarm_transition
786
+
787
+
788
+ def _create_swarm_manager(
789
+ groupchat: GroupChat, swarm_manager_args: Optional[dict[str, Any]], agents: list[ConversableAgent]
790
+ ) -> GroupChatManager:
791
+ """Create a GroupChatManager for the swarm chat utilising any arguments passed in and ensure an LLM Config exists if needed
792
+
793
+ Args:
794
+ groupchat (GroupChat): Swarm groupchat.
795
+ swarm_manager_args (dict[str, Any]): Swarm manager arguments to create the GroupChatManager.
796
+ agents (list[ConversableAgent]): List of agents in the swarm.
797
+
798
+ Returns:
799
+ GroupChatManager: GroupChatManager instance.
800
+ """
801
+ manager_args = (swarm_manager_args or {}).copy()
802
+ if "groupchat" in manager_args:
803
+ raise ValueError("'groupchat' cannot be specified in swarm_manager_args as it is set by initiate_swarm_chat")
804
+ manager = GroupChatManager(groupchat, **manager_args)
805
+
806
+ # Ensure that our manager has an LLM Config if we have any AfterWorkOption.SWARM_MANAGER after works
807
+ if manager.llm_config is False:
808
+ for agent in agents:
809
+ if (
810
+ agent._swarm_after_work # type: ignore[attr-defined]
811
+ and isinstance(agent._swarm_after_work.agent, AfterWorkOption) # type: ignore[attr-defined]
812
+ and agent._swarm_after_work.agent == AfterWorkOption.SWARM_MANAGER # type: ignore[attr-defined]
813
+ ):
814
+ raise ValueError(
815
+ "The swarm manager doesn't have an LLM Config and it is required for AfterWorkOption.SWARM_MANAGER. Use the swarm_manager_args to specify the LLM Config for the swarm manager."
816
+ )
817
+
818
+ return manager
819
+
820
+
821
+ def make_remove_function(tool_msgs_to_remove: list[str]) -> Callable[[list[dict[str, Any]]], list[dict[str, Any]]]:
822
+ """Create a function to remove messages with tool calls from the messages list.
823
+
824
+ The returned function can be registered as a hook to "process_all_messages_before_reply"" to remove messages with tool calls.
825
+ """
826
+
827
+ def remove_messages(messages: list[dict[str, Any]], tool_msgs_to_remove: list[str]) -> list[dict[str, Any]]:
828
+ copied = copy.deepcopy(messages)
829
+ new_messages = []
830
+ removed_tool_ids = []
831
+ for message in copied:
832
+ # remove tool calls
833
+ if message.get("tool_calls") is not None:
834
+ filtered_tool_calls = []
835
+ for tool_call in message["tool_calls"]:
836
+ if tool_call.get("function") is not None and tool_call["function"]["name"] in tool_msgs_to_remove:
837
+ # remove
838
+ removed_tool_ids.append(tool_call["id"])
839
+ else:
840
+ filtered_tool_calls.append(tool_call)
841
+ if len(filtered_tool_calls) > 0:
842
+ message["tool_calls"] = filtered_tool_calls
843
+ else:
844
+ del message["tool_calls"]
845
+ if (
846
+ message.get("content") is None
847
+ or message.get("content") == ""
848
+ or message.get("content") == "None"
849
+ ):
850
+ continue # if no tool call and no content, skip this message
851
+ # else: keep the message with tool_calls removed
852
+ # remove corresponding tool responses
853
+ elif message.get("tool_responses") is not None:
854
+ filtered_tool_responses = []
855
+ for tool_response in message["tool_responses"]:
856
+ if tool_response["tool_call_id"] not in removed_tool_ids:
857
+ filtered_tool_responses.append(tool_response)
858
+
859
+ if len(filtered_tool_responses) > 0:
860
+ message["tool_responses"] = filtered_tool_responses
861
+ else:
862
+ continue
863
+
864
+ new_messages.append(message)
865
+
866
+ return new_messages
867
+
868
+ return partial(remove_messages, tool_msgs_to_remove=tool_msgs_to_remove)
869
+
870
+
871
+ @export_module("autogen")
872
+ def initiate_swarm_chat(
873
+ initial_agent: ConversableAgent,
874
+ messages: Union[list[dict[str, Any]], str],
875
+ agents: list[ConversableAgent],
876
+ user_agent: Optional[UserProxyAgent] = None,
877
+ swarm_manager_args: Optional[dict[str, Any]] = None,
878
+ max_rounds: int = 20,
879
+ context_variables: Optional[ContextVariables] = None,
880
+ after_work: Optional[
881
+ Union[
882
+ AfterWorkOption,
883
+ Callable[
884
+ [ConversableAgent, list[dict[str, Any]], GroupChat], Union[AfterWorkOption, ConversableAgent, str]
885
+ ],
886
+ ]
887
+ ] = AfterWorkOption.TERMINATE,
888
+ exclude_transit_message: bool = True,
889
+ ) -> tuple[ChatResult, ContextVariables, ConversableAgent]:
890
+ """Initialize and run a swarm chat
891
+
892
+ Args:
893
+ initial_agent: The first receiving agent of the conversation.
894
+ messages: Initial message(s).
895
+ agents: list of swarm agents.
896
+ user_agent: Optional user proxy agent for falling back to.
897
+ swarm_manager_args: Optional group chat manager arguments used to establish the swarm's groupchat manager, required when AfterWorkOption.SWARM_MANAGER is used.
898
+ max_rounds: Maximum number of conversation rounds.
899
+ context_variables: Starting context variables.
900
+ after_work: Method to handle conversation continuation when an agent doesn't select the next agent. If no agent is selected and no tool calls are output, we will use this method to determine the next agent.
901
+ Must be a AfterWork instance (which is a dataclass accepting a ConversableAgent, AfterWorkOption, A str (of the AfterWorkOption)) or a callable.
902
+ AfterWorkOption:
903
+ - TERMINATE (Default): Terminate the conversation.
904
+ - REVERT_TO_USER : Revert to the user agent if a user agent is provided. If not provided, terminate the conversation.
905
+ - STAY : Stay with the last speaker.
906
+
907
+ Callable: A custom function that takes the current agent, messages, and groupchat as arguments and returns an AfterWorkOption or a ConversableAgent (by reference or string name).
908
+ ```python
909
+ def custom_afterwork_func(last_speaker: ConversableAgent, messages: list[dict[str, Any]], groupchat: GroupChat) -> Union[AfterWorkOption, ConversableAgent, str]:
910
+ ```
911
+ exclude_transit_message: all registered handoff function call and responses messages will be removed from message list before calling an LLM.
912
+ Note: only with transition functions added with `register_handoff` will be removed. If you pass in a function to manage workflow, it will not be removed. You may register a cumstomized hook to `process_all_messages_before_reply` to remove that.
913
+ Returns:
914
+ ChatResult: Conversations chat history.
915
+ ContextVariables: Updated Context variables.
916
+ ConversableAgent: Last speaker.
917
+ """
918
+ context_variables = context_variables or ContextVariables()
919
+
920
+ tool_execution, nested_chat_agents = _prepare_swarm_agents(
921
+ initial_agent, agents, context_variables, exclude_transit_message
922
+ )
923
+
924
+ processed_messages, last_agent, swarm_agent_names, temp_user_list = _process_initial_messages(
925
+ messages, user_agent, agents, nested_chat_agents
926
+ )
927
+
928
+ # Create transition function (has enclosed state for initial agent)
929
+ swarm_transition = create_swarm_transition(
930
+ initial_agent=initial_agent,
931
+ tool_execution=tool_execution,
932
+ swarm_agent_names=swarm_agent_names,
933
+ user_agent=user_agent,
934
+ swarm_after_work=after_work,
935
+ )
936
+
937
+ groupchat = GroupChat(
938
+ agents=[tool_execution] + agents + nested_chat_agents + ([user_agent] if user_agent else temp_user_list),
939
+ messages=[],
940
+ max_round=max_rounds,
941
+ speaker_selection_method=swarm_transition,
942
+ )
943
+
944
+ manager = _create_swarm_manager(groupchat, swarm_manager_args, agents)
945
+
946
+ # Point all ConversableAgent's context variables to this function's context_variables
947
+ _setup_context_variables(tool_execution, agents, manager, context_variables)
948
+
949
+ # Link all agents with the GroupChatManager to allow access to the group chat
950
+ # and other agents, particularly the tool executor for setting _swarm_next_agent
951
+ _link_agents_to_swarm_manager(groupchat.agents, manager) # Commented out as the function is not defined
952
+
953
+ if len(processed_messages) > 1:
954
+ last_agent, last_message = manager.resume(messages=processed_messages)
955
+ clear_history = False
956
+ else:
957
+ last_message = processed_messages[0]
958
+ clear_history = True
959
+
960
+ if last_agent is None:
961
+ raise ValueError("No agent selected to start the conversation")
962
+
963
+ chat_result = last_agent.initiate_chat( # type: ignore[attr-defined]
964
+ manager,
965
+ message=last_message,
966
+ clear_history=clear_history,
967
+ )
968
+
969
+ _cleanup_temp_user_messages(chat_result)
970
+
971
+ return chat_result, context_variables, manager.last_speaker # type: ignore[return-value]
972
+
973
+
974
+ @export_module("autogen")
975
+ def run_swarm(
976
+ initial_agent: ConversableAgent,
977
+ messages: Union[list[dict[str, Any]], str],
978
+ agents: list[ConversableAgent],
979
+ user_agent: Optional[UserProxyAgent] = None,
980
+ swarm_manager_args: Optional[dict[str, Any]] = None,
981
+ max_rounds: int = 20,
982
+ context_variables: Optional[ContextVariables] = None,
983
+ after_work: Optional[
984
+ Union[
985
+ AfterWorkOption,
986
+ Callable[
987
+ [ConversableAgent, list[dict[str, Any]], GroupChat], Union[AfterWorkOption, ConversableAgent, str]
988
+ ],
989
+ ]
990
+ ] = AfterWorkOption.TERMINATE,
991
+ exclude_transit_message: bool = True,
992
+ ) -> RunResponseProtocol:
993
+ iostream = ThreadIOStream()
994
+ response = RunResponse(iostream, agents) # type: ignore[arg-type]
995
+
996
+ def stream_run(
997
+ iostream: ThreadIOStream = iostream,
998
+ response: RunResponse = response,
999
+ ) -> None:
1000
+ with IOStream.set_default(iostream):
1001
+ try:
1002
+ chat_result, returned_context_variables, last_speaker = initiate_swarm_chat(
1003
+ initial_agent=initial_agent,
1004
+ messages=messages,
1005
+ agents=agents,
1006
+ user_agent=user_agent,
1007
+ swarm_manager_args=swarm_manager_args,
1008
+ max_rounds=max_rounds,
1009
+ context_variables=context_variables,
1010
+ after_work=after_work,
1011
+ exclude_transit_message=exclude_transit_message,
1012
+ )
1013
+
1014
+ IOStream.get_default().send(
1015
+ RunCompletionEvent( # type: ignore[call-arg]
1016
+ history=chat_result.chat_history,
1017
+ summary=chat_result.summary,
1018
+ cost=chat_result.cost,
1019
+ last_speaker=last_speaker.name,
1020
+ context_variables=returned_context_variables,
1021
+ )
1022
+ )
1023
+ except Exception as e:
1024
+ response.iostream.send(ErrorEvent(error=e)) # type: ignore[call-arg]
1025
+
1026
+ threading.Thread(
1027
+ target=stream_run,
1028
+ ).start()
1029
+
1030
+ return response
1031
+
1032
+
1033
+ @export_module("autogen")
1034
+ async def a_initiate_swarm_chat(
1035
+ initial_agent: ConversableAgent,
1036
+ messages: Union[list[dict[str, Any]], str],
1037
+ agents: list[ConversableAgent],
1038
+ user_agent: Optional[UserProxyAgent] = None,
1039
+ swarm_manager_args: Optional[dict[str, Any]] = None,
1040
+ max_rounds: int = 20,
1041
+ context_variables: Optional[ContextVariables] = None,
1042
+ after_work: Optional[
1043
+ Union[
1044
+ AfterWorkOption,
1045
+ Callable[
1046
+ [ConversableAgent, list[dict[str, Any]], GroupChat], Union[AfterWorkOption, ConversableAgent, str]
1047
+ ],
1048
+ ]
1049
+ ] = AfterWorkOption.TERMINATE,
1050
+ exclude_transit_message: bool = True,
1051
+ ) -> tuple[ChatResult, ContextVariables, ConversableAgent]:
1052
+ """Initialize and run a swarm chat asynchronously
1053
+
1054
+ Args:
1055
+ initial_agent: The first receiving agent of the conversation.
1056
+ messages: Initial message(s).
1057
+ agents: List of swarm agents.
1058
+ user_agent: Optional user proxy agent for falling back to.
1059
+ swarm_manager_args: Optional group chat manager arguments used to establish the swarm's groupchat manager, required when AfterWorkOption.SWARM_MANAGER is used.
1060
+ max_rounds: Maximum number of conversation rounds.
1061
+ context_variables: Starting context variables.
1062
+ after_work: Method to handle conversation continuation when an agent doesn't select the next agent. If no agent is selected and no tool calls are output, we will use this method to determine the next agent.
1063
+ Must be a AfterWork instance (which is a dataclass accepting a ConversableAgent, AfterWorkOption, A str (of the AfterWorkOption)) or a callable.
1064
+ AfterWorkOption:
1065
+ - TERMINATE (Default): Terminate the conversation.
1066
+ - REVERT_TO_USER : Revert to the user agent if a user agent is provided. If not provided, terminate the conversation.
1067
+ - STAY : Stay with the last speaker.
1068
+
1069
+ Callable: A custom function that takes the current agent, messages, and groupchat as arguments and returns an AfterWorkOption or a ConversableAgent (by reference or string name).
1070
+ ```python
1071
+ def custom_afterwork_func(last_speaker: ConversableAgent, messages: list[dict[str, Any]], groupchat: GroupChat) -> Union[AfterWorkOption, ConversableAgent, str]:
1072
+ ```
1073
+ exclude_transit_message: all registered handoff function call and responses messages will be removed from message list before calling an LLM.
1074
+ Note: only with transition functions added with `register_handoff` will be removed. If you pass in a function to manage workflow, it will not be removed. You may register a cumstomized hook to `process_all_messages_before_reply` to remove that.
1075
+ Returns:
1076
+ ChatResult: Conversations chat history.
1077
+ ContextVariables: Updated Context variables.
1078
+ ConversableAgent: Last speaker.
1079
+ """
1080
+ context_variables = context_variables or ContextVariables()
1081
+ tool_execution, nested_chat_agents = _prepare_swarm_agents(
1082
+ initial_agent, agents, context_variables, exclude_transit_message
1083
+ )
1084
+
1085
+ processed_messages, last_agent, swarm_agent_names, temp_user_list = _process_initial_messages(
1086
+ messages, user_agent, agents, nested_chat_agents
1087
+ )
1088
+
1089
+ # Create transition function (has enclosed state for initial agent)
1090
+ swarm_transition = create_swarm_transition(
1091
+ initial_agent=initial_agent,
1092
+ tool_execution=tool_execution,
1093
+ swarm_agent_names=swarm_agent_names,
1094
+ user_agent=user_agent,
1095
+ swarm_after_work=after_work,
1096
+ )
1097
+
1098
+ groupchat = GroupChat(
1099
+ agents=[tool_execution] + agents + nested_chat_agents + ([user_agent] if user_agent else temp_user_list),
1100
+ messages=[],
1101
+ max_round=max_rounds,
1102
+ speaker_selection_method=swarm_transition,
1103
+ )
1104
+
1105
+ manager = _create_swarm_manager(groupchat, swarm_manager_args, agents)
1106
+
1107
+ # Point all ConversableAgent's context variables to this function's context_variables
1108
+ _setup_context_variables(tool_execution, agents, manager, context_variables)
1109
+
1110
+ # Link all agents with the GroupChatManager to allow access to the group chat
1111
+ # and other agents, particularly the tool executor for setting _swarm_next_agent
1112
+ _link_agents_to_swarm_manager(groupchat.agents, manager)
1113
+
1114
+ if len(processed_messages) > 1:
1115
+ last_agent, last_message = await manager.a_resume(messages=processed_messages)
1116
+ clear_history = False
1117
+ else:
1118
+ last_message = processed_messages[0]
1119
+ clear_history = True
1120
+
1121
+ if last_agent is None:
1122
+ raise ValueError("No agent selected to start the conversation")
1123
+
1124
+ chat_result = await last_agent.a_initiate_chat( # type: ignore[attr-defined]
1125
+ manager,
1126
+ message=last_message,
1127
+ clear_history=clear_history,
1128
+ )
1129
+
1130
+ _cleanup_temp_user_messages(chat_result)
1131
+
1132
+ return chat_result, context_variables, manager.last_speaker # type: ignore[return-value]
1133
+
1134
+
1135
+ @export_module("autogen")
1136
+ async def a_run_swarm(
1137
+ initial_agent: ConversableAgent,
1138
+ messages: Union[list[dict[str, Any]], str],
1139
+ agents: list[ConversableAgent],
1140
+ user_agent: Optional[UserProxyAgent] = None,
1141
+ swarm_manager_args: Optional[dict[str, Any]] = None,
1142
+ max_rounds: int = 20,
1143
+ context_variables: Optional[ContextVariables] = None,
1144
+ after_work: Optional[
1145
+ Union[
1146
+ AfterWorkOption,
1147
+ Callable[
1148
+ [ConversableAgent, list[dict[str, Any]], GroupChat], Union[AfterWorkOption, ConversableAgent, str]
1149
+ ],
1150
+ ]
1151
+ ] = AfterWorkOption.TERMINATE,
1152
+ exclude_transit_message: bool = True,
1153
+ ) -> AsyncRunResponseProtocol:
1154
+ iostream = AsyncThreadIOStream()
1155
+ response = AsyncRunResponse(iostream, agents) # type: ignore[arg-type]
1156
+
1157
+ async def stream_run(
1158
+ iostream: AsyncThreadIOStream = iostream,
1159
+ response: AsyncRunResponse = response,
1160
+ ) -> None:
1161
+ with IOStream.set_default(iostream):
1162
+ try:
1163
+ chat_result, returned_context_variables, last_speaker = await a_initiate_swarm_chat(
1164
+ initial_agent=initial_agent,
1165
+ messages=messages,
1166
+ agents=agents,
1167
+ user_agent=user_agent,
1168
+ swarm_manager_args=swarm_manager_args,
1169
+ max_rounds=max_rounds,
1170
+ context_variables=context_variables,
1171
+ after_work=after_work,
1172
+ exclude_transit_message=exclude_transit_message,
1173
+ )
1174
+
1175
+ IOStream.get_default().send(
1176
+ RunCompletionEvent( # type: ignore[call-arg]
1177
+ history=chat_result.chat_history,
1178
+ summary=chat_result.summary,
1179
+ cost=chat_result.cost,
1180
+ last_speaker=last_speaker.name,
1181
+ context_variables=returned_context_variables,
1182
+ )
1183
+ )
1184
+ except Exception as e:
1185
+ response.iostream.send(ErrorEvent(error=e)) # type: ignore[call-arg]
1186
+
1187
+ asyncio.create_task(stream_run())
1188
+
1189
+ return response
1190
+
1191
+
1192
+ @export_module("autogen")
1193
+ class SwarmResult(BaseModel):
1194
+ """Encapsulates the possible return values for a swarm agent function."""
1195
+
1196
+ values: str = ""
1197
+ agent: Optional[Union[ConversableAgent, AfterWorkOption, str]] = None
1198
+ context_variables: Optional[ContextVariables] = None
1199
+
1200
+ @field_serializer("agent", when_used="json")
1201
+ def serialize_agent(self, agent: Union[ConversableAgent, str]) -> str:
1202
+ if isinstance(agent, ConversableAgent):
1203
+ return agent.name
1204
+ return agent
1205
+
1206
+ def model_post_init(self, __context: Any) -> None:
1207
+ # Initialise with a new ContextVariables object if not provided
1208
+ if self.context_variables is None:
1209
+ self.context_variables = ContextVariables()
1210
+
1211
+ class Config: # Add this inner class
1212
+ arbitrary_types_allowed = True
1213
+
1214
+ def __str__(self) -> str:
1215
+ return self.values
1216
+
1217
+
1218
+ def _set_to_tool_execution(agent: ConversableAgent) -> None:
1219
+ """Set to a special instance of ConversableAgent that is responsible for executing tool calls from other swarm agents.
1220
+ This agent will be used internally and should not be visible to the user.
1221
+
1222
+ It will execute the tool calls and update the referenced context_variables and next_agent accordingly.
1223
+ """
1224
+ agent._swarm_next_agent = None # type: ignore[attr-defined]
1225
+ agent._reply_func_list.clear()
1226
+ agent.register_reply([Agent, None], _generate_swarm_tool_reply)
1227
+
1228
+
1229
+ @export_module("autogen")
1230
+ def register_hand_off(
1231
+ agent: ConversableAgent,
1232
+ hand_to: Union[list[Union[OnCondition, OnContextCondition, AfterWork]], OnCondition, OnContextCondition, AfterWork],
1233
+ ) -> None:
1234
+ """Register a function to hand off to another agent.
1235
+
1236
+ Args:
1237
+ agent: The agent to register the hand off with.
1238
+ hand_to: A list of OnCondition's and an, optional, AfterWork condition
1239
+
1240
+ Hand off template:
1241
+ def transfer_to_agent_name() -> ConversableAgent:
1242
+ return agent_name
1243
+ 1. register the function with the agent
1244
+ 2. register the schema with the agent, description set to the condition
1245
+ """
1246
+ # If the agent hasn't been established as a swarm agent, do so first
1247
+ if not hasattr(agent, "_swarm_is_established"):
1248
+ _establish_swarm_agent(agent)
1249
+
1250
+ # Ensure that hand_to is a list or OnCondition or AfterWork
1251
+ if not isinstance(hand_to, (list, OnCondition, OnContextCondition, AfterWork)):
1252
+ raise ValueError("hand_to must be a list of OnCondition, OnContextCondition, or AfterWork")
1253
+
1254
+ if isinstance(hand_to, (OnCondition, OnContextCondition, AfterWork)):
1255
+ hand_to = [hand_to]
1256
+
1257
+ for transit in hand_to:
1258
+ if isinstance(transit, AfterWork):
1259
+ if not (isinstance(transit.agent, (AfterWorkOption, ConversableAgent, str)) or callable(transit.agent)):
1260
+ raise ValueError(f"Invalid AfterWork agent: {transit.agent}")
1261
+ agent._swarm_after_work = transit # type: ignore[attr-defined]
1262
+ agent._swarm_after_work_selection_msg = transit.next_agent_selection_msg # type: ignore[attr-defined]
1263
+ elif isinstance(transit, OnCondition):
1264
+ if isinstance(transit.target, ConversableAgent):
1265
+ # Transition to agent
1266
+
1267
+ # Create closure with current loop transit value
1268
+ # to ensure the condition matches the one in the loop
1269
+ def make_transfer_function(current_transit: OnCondition) -> Callable[[], ConversableAgent]:
1270
+ def transfer_to_agent() -> ConversableAgent:
1271
+ return current_transit.target # type: ignore[return-value]
1272
+
1273
+ return transfer_to_agent
1274
+
1275
+ transfer_func = make_transfer_function(transit)
1276
+
1277
+ # Store function to add/remove later based on it being 'available'
1278
+ # Function names are made unique and allow multiple OnCondition's to the same agent
1279
+ base_func_name = f"transfer_{agent.name}_to_{transit.target.name}"
1280
+ func_name = base_func_name
1281
+ count = 2
1282
+ while func_name in agent._swarm_conditional_functions: # type: ignore[attr-defined]
1283
+ func_name = f"{base_func_name}_{count}"
1284
+ count += 1
1285
+
1286
+ # Store function to add/remove later based on it being 'available'
1287
+ agent._swarm_conditional_functions[func_name] = (transfer_func, transit) # type: ignore[attr-defined]
1288
+
1289
+ elif isinstance(transit.target, dict):
1290
+ # Transition to a nested chat
1291
+ # We will store them here and establish them in the initiate_swarm_chat
1292
+ agent._swarm_nested_chat_handoffs.append({ # type: ignore[attr-defined]
1293
+ "nested_chats": transit.target,
1294
+ "condition": transit.condition,
1295
+ "available": transit.available,
1296
+ })
1297
+
1298
+ elif isinstance(transit, OnContextCondition):
1299
+ agent._swarm_oncontextconditions.append(transit) # type: ignore[attr-defined]
1300
+
1301
+ else:
1302
+ raise ValueError("Invalid hand off condition, must be either OnCondition or AfterWork")
1303
+
1304
+
1305
+ def _update_conditional_functions(agent: ConversableAgent, messages: Optional[list[dict[str, Any]]] = None) -> None:
1306
+ """Updates the agent's functions based on the OnCondition's available condition."""
1307
+ for func_name, (func, on_condition) in agent._swarm_conditional_functions.items(): # type: ignore[attr-defined]
1308
+ is_available = True
1309
+
1310
+ if on_condition.available is not None:
1311
+ if callable(on_condition.available):
1312
+ is_available = on_condition.available(agent, next(iter(agent.chat_messages.values())))
1313
+ elif isinstance(on_condition.available, str):
1314
+ is_available = agent.context_variables.get(on_condition.available) or False
1315
+ elif isinstance(on_condition.available, ContextExpression):
1316
+ is_available = on_condition.available.evaluate(agent.context_variables)
1317
+
1318
+ # first remove the function if it exists
1319
+ if func_name in agent._function_map:
1320
+ agent.update_tool_signature(func_name, is_remove=True)
1321
+ del agent._function_map[func_name]
1322
+
1323
+ # then add the function if it is available, so that the function signature is updated
1324
+ if is_available:
1325
+ condition = on_condition.condition
1326
+ if isinstance(condition, ContextStr):
1327
+ condition = condition.format(context_variables=agent.context_variables)
1328
+ elif callable(condition):
1329
+ condition = condition(agent, messages)
1330
+
1331
+ # TODO: Don't add it if it's already there
1332
+ agent._add_single_function(func, func_name, condition)
1333
+
1334
+
1335
+ def _generate_swarm_tool_reply(
1336
+ agent: ConversableAgent,
1337
+ messages: Optional[list[dict[str, Any]]] = None,
1338
+ sender: Optional[Agent] = None,
1339
+ config: Optional[OpenAIWrapper] = None,
1340
+ ) -> tuple[bool, Optional[dict[str, Any]]]:
1341
+ """Pre-processes and generates tool call replies.
1342
+
1343
+ This function:
1344
+ 1. Adds context_variables back to the tool call for the function, if necessary.
1345
+ 2. Generates the tool calls reply.
1346
+ 3. Updates context_variables and next_agent based on the tool call response."""
1347
+
1348
+ if config is None:
1349
+ config = agent # type: ignore[assignment]
1350
+ if messages is None:
1351
+ messages = agent._oai_messages[sender]
1352
+
1353
+ message = messages[-1]
1354
+ if "tool_calls" in message:
1355
+ tool_call_count = len(message["tool_calls"])
1356
+
1357
+ # Loop through tool calls individually (so context can be updated after each function call)
1358
+ next_agent: Optional[Agent] = None
1359
+ tool_responses_inner = []
1360
+ contents = []
1361
+ for index in range(tool_call_count):
1362
+ message_copy = copy.deepcopy(message)
1363
+
1364
+ # 1. add context_variables to the tool call arguments
1365
+ tool_call = message_copy["tool_calls"][index]
1366
+
1367
+ # Ensure we are only executing the one tool at a time
1368
+ message_copy["tool_calls"] = [tool_call]
1369
+
1370
+ # 2. generate tool calls reply
1371
+ _, tool_message = agent.generate_tool_calls_reply([message_copy])
1372
+
1373
+ if tool_message is None:
1374
+ raise ValueError("Tool call did not return a message")
1375
+
1376
+ # 3. update context_variables and next_agent, convert content to string
1377
+ for tool_response in tool_message["tool_responses"]:
1378
+ content = tool_response.get("content")
1379
+
1380
+ if isinstance(content, SwarmResult):
1381
+ if content.context_variables is not None and content.context_variables.to_dict() != {}:
1382
+ agent.context_variables.update(content.context_variables.to_dict())
1383
+ if content.agent is not None:
1384
+ next_agent = content.agent # type: ignore[assignment]
1385
+ elif isinstance(content, Agent):
1386
+ next_agent = content
1387
+
1388
+ # Serialize the content to a string
1389
+ if content is not None:
1390
+ tool_response["content"] = str(content)
1391
+
1392
+ tool_responses_inner.append(tool_response)
1393
+ contents.append(str(tool_response["content"]))
1394
+
1395
+ agent._swarm_next_agent = next_agent # type: ignore[attr-defined]
1396
+
1397
+ # Put the tool responses and content strings back into the response message
1398
+ # Caters for multiple tool calls
1399
+ if tool_message is None:
1400
+ raise ValueError("Tool call did not return a message")
1401
+
1402
+ tool_message["tool_responses"] = tool_responses_inner
1403
+ tool_message["content"] = "\n".join(contents)
1404
+
1405
+ return True, tool_message
1406
+ return False, None
1407
+
1408
+
1409
+ class SwarmAgent(ConversableAgent):
1410
+ """SwarmAgent is deprecated and has been incorporated into ConversableAgent, use ConversableAgent instead. SwarmAgent will be removed in a future version (TBD)"""
1411
+
1412
+ def __init__(self, *args: Any, **kwargs: Any):
1413
+ """Initializes a new instance of the SwarmAgent class.
1414
+
1415
+ Args:
1416
+ *args: Variable length argument list.
1417
+ **kwargs: Arbitrary keyword arguments.
1418
+ """
1419
+ warnings.warn(
1420
+ "SwarmAgent is deprecated and has been incorporated into ConversableAgent, use ConversableAgent instead. SwarmAgent will be removed in a future version (TBD).",
1421
+ DeprecationWarning,
1422
+ stacklevel=2,
1423
+ )
1424
+
1425
+ super().__init__(*args, **kwargs)