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.
- {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info}/METADATA +272 -75
- ag2-0.9.2.dist-info/RECORD +406 -0
- {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info}/WHEEL +1 -2
- autogen/__init__.py +89 -0
- autogen/_website/__init__.py +3 -0
- autogen/_website/generate_api_references.py +427 -0
- autogen/_website/generate_mkdocs.py +1174 -0
- autogen/_website/notebook_processor.py +476 -0
- autogen/_website/process_notebooks.py +656 -0
- autogen/_website/utils.py +412 -0
- autogen/agentchat/__init__.py +44 -0
- autogen/agentchat/agent.py +182 -0
- autogen/agentchat/assistant_agent.py +85 -0
- autogen/agentchat/chat.py +309 -0
- autogen/agentchat/contrib/__init__.py +5 -0
- autogen/agentchat/contrib/agent_eval/README.md +7 -0
- autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
- autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
- autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
- autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
- autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
- autogen/agentchat/contrib/agent_eval/task.py +42 -0
- autogen/agentchat/contrib/agent_optimizer.py +429 -0
- autogen/agentchat/contrib/capabilities/__init__.py +5 -0
- autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
- autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
- autogen/agentchat/contrib/capabilities/teachability.py +393 -0
- autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
- autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
- autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
- autogen/agentchat/contrib/capabilities/transforms.py +566 -0
- autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
- autogen/agentchat/contrib/capabilities/vision_capability.py +214 -0
- autogen/agentchat/contrib/captainagent/__init__.py +9 -0
- autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
- autogen/agentchat/contrib/captainagent/captainagent.py +512 -0
- autogen/agentchat/contrib/captainagent/tool_retriever.py +335 -0
- autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
- autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
- autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
- autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
- autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
- autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
- autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
- autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
- autogen/agentchat/contrib/graph_rag/document.py +29 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +170 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
- autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
- autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +268 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
- autogen/agentchat/contrib/img_utils.py +397 -0
- autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
- autogen/agentchat/contrib/llava_agent.py +187 -0
- autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
- autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
- autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +324 -0
- autogen/agentchat/contrib/rag/__init__.py +10 -0
- autogen/agentchat/contrib/rag/chromadb_query_engine.py +272 -0
- autogen/agentchat/contrib/rag/llamaindex_query_engine.py +198 -0
- autogen/agentchat/contrib/rag/mongodb_query_engine.py +329 -0
- autogen/agentchat/contrib/rag/query_engine.py +74 -0
- autogen/agentchat/contrib/retrieve_assistant_agent.py +56 -0
- autogen/agentchat/contrib/retrieve_user_proxy_agent.py +703 -0
- autogen/agentchat/contrib/society_of_mind_agent.py +199 -0
- autogen/agentchat/contrib/swarm_agent.py +1425 -0
- autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
- autogen/agentchat/contrib/vectordb/__init__.py +5 -0
- autogen/agentchat/contrib/vectordb/base.py +232 -0
- autogen/agentchat/contrib/vectordb/chromadb.py +315 -0
- autogen/agentchat/contrib/vectordb/couchbase.py +407 -0
- autogen/agentchat/contrib/vectordb/mongodb.py +550 -0
- autogen/agentchat/contrib/vectordb/pgvectordb.py +928 -0
- autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
- autogen/agentchat/contrib/vectordb/utils.py +126 -0
- autogen/agentchat/contrib/web_surfer.py +303 -0
- autogen/agentchat/conversable_agent.py +4023 -0
- autogen/agentchat/group/__init__.py +64 -0
- autogen/agentchat/group/available_condition.py +91 -0
- autogen/agentchat/group/context_condition.py +77 -0
- autogen/agentchat/group/context_expression.py +238 -0
- autogen/agentchat/group/context_str.py +41 -0
- autogen/agentchat/group/context_variables.py +192 -0
- autogen/agentchat/group/group_tool_executor.py +202 -0
- autogen/agentchat/group/group_utils.py +591 -0
- autogen/agentchat/group/handoffs.py +244 -0
- autogen/agentchat/group/llm_condition.py +93 -0
- autogen/agentchat/group/multi_agent_chat.py +237 -0
- autogen/agentchat/group/on_condition.py +58 -0
- autogen/agentchat/group/on_context_condition.py +54 -0
- autogen/agentchat/group/patterns/__init__.py +18 -0
- autogen/agentchat/group/patterns/auto.py +159 -0
- autogen/agentchat/group/patterns/manual.py +176 -0
- autogen/agentchat/group/patterns/pattern.py +288 -0
- autogen/agentchat/group/patterns/random.py +106 -0
- autogen/agentchat/group/patterns/round_robin.py +117 -0
- autogen/agentchat/group/reply_result.py +26 -0
- autogen/agentchat/group/speaker_selection_result.py +41 -0
- autogen/agentchat/group/targets/__init__.py +4 -0
- autogen/agentchat/group/targets/group_chat_target.py +132 -0
- autogen/agentchat/group/targets/group_manager_target.py +151 -0
- autogen/agentchat/group/targets/transition_target.py +413 -0
- autogen/agentchat/group/targets/transition_utils.py +6 -0
- autogen/agentchat/groupchat.py +1694 -0
- autogen/agentchat/realtime/__init__.py +3 -0
- autogen/agentchat/realtime/experimental/__init__.py +20 -0
- autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
- autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
- autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
- autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
- autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
- autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
- autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
- autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
- autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
- autogen/agentchat/realtime/experimental/clients/realtime_client.py +190 -0
- autogen/agentchat/realtime/experimental/function_observer.py +85 -0
- autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
- autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
- autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
- autogen/agentchat/realtime/experimental/realtime_swarm.py +475 -0
- autogen/agentchat/realtime/experimental/websockets.py +21 -0
- autogen/agentchat/realtime_agent/__init__.py +21 -0
- autogen/agentchat/user_proxy_agent.py +111 -0
- autogen/agentchat/utils.py +206 -0
- autogen/agents/__init__.py +3 -0
- autogen/agents/contrib/__init__.py +10 -0
- autogen/agents/contrib/time/__init__.py +8 -0
- autogen/agents/contrib/time/time_reply_agent.py +73 -0
- autogen/agents/contrib/time/time_tool_agent.py +51 -0
- autogen/agents/experimental/__init__.py +27 -0
- autogen/agents/experimental/deep_research/__init__.py +7 -0
- autogen/agents/experimental/deep_research/deep_research.py +52 -0
- autogen/agents/experimental/discord/__init__.py +7 -0
- autogen/agents/experimental/discord/discord.py +66 -0
- autogen/agents/experimental/document_agent/__init__.py +19 -0
- autogen/agents/experimental/document_agent/chroma_query_engine.py +316 -0
- autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +118 -0
- autogen/agents/experimental/document_agent/document_agent.py +461 -0
- autogen/agents/experimental/document_agent/document_conditions.py +50 -0
- autogen/agents/experimental/document_agent/document_utils.py +380 -0
- autogen/agents/experimental/document_agent/inmemory_query_engine.py +220 -0
- autogen/agents/experimental/document_agent/parser_utils.py +130 -0
- autogen/agents/experimental/document_agent/url_utils.py +426 -0
- autogen/agents/experimental/reasoning/__init__.py +7 -0
- autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
- autogen/agents/experimental/slack/__init__.py +7 -0
- autogen/agents/experimental/slack/slack.py +73 -0
- autogen/agents/experimental/telegram/__init__.py +7 -0
- autogen/agents/experimental/telegram/telegram.py +77 -0
- autogen/agents/experimental/websurfer/__init__.py +7 -0
- autogen/agents/experimental/websurfer/websurfer.py +62 -0
- autogen/agents/experimental/wikipedia/__init__.py +7 -0
- autogen/agents/experimental/wikipedia/wikipedia.py +90 -0
- autogen/browser_utils.py +309 -0
- autogen/cache/__init__.py +10 -0
- autogen/cache/abstract_cache_base.py +75 -0
- autogen/cache/cache.py +203 -0
- autogen/cache/cache_factory.py +88 -0
- autogen/cache/cosmos_db_cache.py +144 -0
- autogen/cache/disk_cache.py +102 -0
- autogen/cache/in_memory_cache.py +58 -0
- autogen/cache/redis_cache.py +123 -0
- autogen/code_utils.py +596 -0
- autogen/coding/__init__.py +22 -0
- autogen/coding/base.py +119 -0
- autogen/coding/docker_commandline_code_executor.py +268 -0
- autogen/coding/factory.py +47 -0
- autogen/coding/func_with_reqs.py +202 -0
- autogen/coding/jupyter/__init__.py +23 -0
- autogen/coding/jupyter/base.py +36 -0
- autogen/coding/jupyter/docker_jupyter_server.py +167 -0
- autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
- autogen/coding/jupyter/import_utils.py +82 -0
- autogen/coding/jupyter/jupyter_client.py +231 -0
- autogen/coding/jupyter/jupyter_code_executor.py +160 -0
- autogen/coding/jupyter/local_jupyter_server.py +172 -0
- autogen/coding/local_commandline_code_executor.py +405 -0
- autogen/coding/markdown_code_extractor.py +45 -0
- autogen/coding/utils.py +56 -0
- autogen/doc_utils.py +34 -0
- autogen/events/__init__.py +7 -0
- autogen/events/agent_events.py +1013 -0
- autogen/events/base_event.py +99 -0
- autogen/events/client_events.py +167 -0
- autogen/events/helpers.py +36 -0
- autogen/events/print_event.py +46 -0
- autogen/exception_utils.py +73 -0
- autogen/extensions/__init__.py +5 -0
- autogen/fast_depends/__init__.py +16 -0
- autogen/fast_depends/_compat.py +80 -0
- autogen/fast_depends/core/__init__.py +14 -0
- autogen/fast_depends/core/build.py +225 -0
- autogen/fast_depends/core/model.py +576 -0
- autogen/fast_depends/dependencies/__init__.py +15 -0
- autogen/fast_depends/dependencies/model.py +29 -0
- autogen/fast_depends/dependencies/provider.py +39 -0
- autogen/fast_depends/library/__init__.py +10 -0
- autogen/fast_depends/library/model.py +46 -0
- autogen/fast_depends/py.typed +6 -0
- autogen/fast_depends/schema.py +66 -0
- autogen/fast_depends/use.py +280 -0
- autogen/fast_depends/utils.py +187 -0
- autogen/formatting_utils.py +83 -0
- autogen/function_utils.py +13 -0
- autogen/graph_utils.py +178 -0
- autogen/import_utils.py +526 -0
- autogen/interop/__init__.py +22 -0
- autogen/interop/crewai/__init__.py +7 -0
- autogen/interop/crewai/crewai.py +88 -0
- autogen/interop/interoperability.py +71 -0
- autogen/interop/interoperable.py +46 -0
- autogen/interop/langchain/__init__.py +8 -0
- autogen/interop/langchain/langchain_chat_model_factory.py +155 -0
- autogen/interop/langchain/langchain_tool.py +82 -0
- autogen/interop/litellm/__init__.py +7 -0
- autogen/interop/litellm/litellm_config_factory.py +179 -0
- autogen/interop/pydantic_ai/__init__.py +7 -0
- autogen/interop/pydantic_ai/pydantic_ai.py +168 -0
- autogen/interop/registry.py +69 -0
- autogen/io/__init__.py +15 -0
- autogen/io/base.py +151 -0
- autogen/io/console.py +56 -0
- autogen/io/processors/__init__.py +12 -0
- autogen/io/processors/base.py +21 -0
- autogen/io/processors/console_event_processor.py +56 -0
- autogen/io/run_response.py +293 -0
- autogen/io/thread_io_stream.py +63 -0
- autogen/io/websockets.py +213 -0
- autogen/json_utils.py +43 -0
- autogen/llm_config.py +382 -0
- autogen/logger/__init__.py +11 -0
- autogen/logger/base_logger.py +128 -0
- autogen/logger/file_logger.py +261 -0
- autogen/logger/logger_factory.py +42 -0
- autogen/logger/logger_utils.py +57 -0
- autogen/logger/sqlite_logger.py +523 -0
- autogen/math_utils.py +339 -0
- autogen/mcp/__init__.py +7 -0
- autogen/mcp/__main__.py +78 -0
- autogen/mcp/mcp_client.py +208 -0
- autogen/mcp/mcp_proxy/__init__.py +19 -0
- autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +63 -0
- autogen/mcp/mcp_proxy/mcp_proxy.py +581 -0
- autogen/mcp/mcp_proxy/operation_grouping.py +158 -0
- autogen/mcp/mcp_proxy/operation_renaming.py +114 -0
- autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py +98 -0
- autogen/mcp/mcp_proxy/security.py +400 -0
- autogen/mcp/mcp_proxy/security_schema_visitor.py +37 -0
- autogen/messages/__init__.py +7 -0
- autogen/messages/agent_messages.py +948 -0
- autogen/messages/base_message.py +107 -0
- autogen/messages/client_messages.py +171 -0
- autogen/messages/print_message.py +49 -0
- autogen/oai/__init__.py +53 -0
- autogen/oai/anthropic.py +714 -0
- autogen/oai/bedrock.py +628 -0
- autogen/oai/cerebras.py +299 -0
- autogen/oai/client.py +1444 -0
- autogen/oai/client_utils.py +169 -0
- autogen/oai/cohere.py +479 -0
- autogen/oai/gemini.py +998 -0
- autogen/oai/gemini_types.py +155 -0
- autogen/oai/groq.py +305 -0
- autogen/oai/mistral.py +303 -0
- autogen/oai/oai_models/__init__.py +11 -0
- autogen/oai/oai_models/_models.py +16 -0
- autogen/oai/oai_models/chat_completion.py +87 -0
- autogen/oai/oai_models/chat_completion_audio.py +32 -0
- autogen/oai/oai_models/chat_completion_message.py +86 -0
- autogen/oai/oai_models/chat_completion_message_tool_call.py +37 -0
- autogen/oai/oai_models/chat_completion_token_logprob.py +63 -0
- autogen/oai/oai_models/completion_usage.py +60 -0
- autogen/oai/ollama.py +643 -0
- autogen/oai/openai_utils.py +881 -0
- autogen/oai/together.py +370 -0
- autogen/retrieve_utils.py +491 -0
- autogen/runtime_logging.py +160 -0
- autogen/token_count_utils.py +267 -0
- autogen/tools/__init__.py +20 -0
- autogen/tools/contrib/__init__.py +9 -0
- autogen/tools/contrib/time/__init__.py +7 -0
- autogen/tools/contrib/time/time.py +41 -0
- autogen/tools/dependency_injection.py +254 -0
- autogen/tools/experimental/__init__.py +48 -0
- autogen/tools/experimental/browser_use/__init__.py +7 -0
- autogen/tools/experimental/browser_use/browser_use.py +161 -0
- autogen/tools/experimental/crawl4ai/__init__.py +7 -0
- autogen/tools/experimental/crawl4ai/crawl4ai.py +153 -0
- autogen/tools/experimental/deep_research/__init__.py +7 -0
- autogen/tools/experimental/deep_research/deep_research.py +328 -0
- autogen/tools/experimental/duckduckgo/__init__.py +7 -0
- autogen/tools/experimental/duckduckgo/duckduckgo_search.py +109 -0
- autogen/tools/experimental/google/__init__.py +14 -0
- autogen/tools/experimental/google/authentication/__init__.py +11 -0
- autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
- autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
- autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
- autogen/tools/experimental/google/drive/__init__.py +9 -0
- autogen/tools/experimental/google/drive/drive_functions.py +124 -0
- autogen/tools/experimental/google/drive/toolkit.py +88 -0
- autogen/tools/experimental/google/model.py +17 -0
- autogen/tools/experimental/google/toolkit_protocol.py +19 -0
- autogen/tools/experimental/google_search/__init__.py +8 -0
- autogen/tools/experimental/google_search/google_search.py +93 -0
- autogen/tools/experimental/google_search/youtube_search.py +181 -0
- autogen/tools/experimental/messageplatform/__init__.py +17 -0
- autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/discord/discord.py +288 -0
- autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/slack/slack.py +391 -0
- autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/telegram/telegram.py +275 -0
- autogen/tools/experimental/perplexity/__init__.py +7 -0
- autogen/tools/experimental/perplexity/perplexity_search.py +260 -0
- autogen/tools/experimental/reliable/__init__.py +10 -0
- autogen/tools/experimental/reliable/reliable.py +1316 -0
- autogen/tools/experimental/tavily/__init__.py +7 -0
- autogen/tools/experimental/tavily/tavily_search.py +183 -0
- autogen/tools/experimental/web_search_preview/__init__.py +7 -0
- autogen/tools/experimental/web_search_preview/web_search_preview.py +114 -0
- autogen/tools/experimental/wikipedia/__init__.py +7 -0
- autogen/tools/experimental/wikipedia/wikipedia.py +287 -0
- autogen/tools/function_utils.py +411 -0
- autogen/tools/tool.py +187 -0
- autogen/tools/toolkit.py +86 -0
- autogen/types.py +29 -0
- autogen/version.py +7 -0
- templates/client_template/main.jinja2 +69 -0
- templates/config_template/config.jinja2 +7 -0
- templates/main.jinja2 +61 -0
- ag2-0.9.1a1.dist-info/RECORD +0 -6
- ag2-0.9.1a1.dist-info/top_level.txt +0 -1
- {ag2-0.9.1a1.dist-info → ag2-0.9.2.dist-info/licenses}/LICENSE +0 -0
- {ag2-0.9.1a1.dist-info → ag2-0.9.2.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)
|