ag2 0.10.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ag2-0.10.2.dist-info/METADATA +819 -0
- ag2-0.10.2.dist-info/RECORD +423 -0
- ag2-0.10.2.dist-info/WHEEL +4 -0
- ag2-0.10.2.dist-info/licenses/LICENSE +201 -0
- ag2-0.10.2.dist-info/licenses/NOTICE.md +19 -0
- autogen/__init__.py +88 -0
- autogen/_website/__init__.py +3 -0
- autogen/_website/generate_api_references.py +426 -0
- autogen/_website/generate_mkdocs.py +1216 -0
- autogen/_website/notebook_processor.py +475 -0
- autogen/_website/process_notebooks.py +656 -0
- autogen/_website/utils.py +413 -0
- autogen/a2a/__init__.py +36 -0
- autogen/a2a/agent_executor.py +86 -0
- autogen/a2a/client.py +357 -0
- autogen/a2a/errors.py +18 -0
- autogen/a2a/httpx_client_factory.py +79 -0
- autogen/a2a/server.py +221 -0
- autogen/a2a/utils.py +207 -0
- autogen/agentchat/__init__.py +47 -0
- autogen/agentchat/agent.py +180 -0
- autogen/agentchat/assistant_agent.py +86 -0
- autogen/agentchat/chat.py +325 -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 +432 -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 +578 -0
- autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
- autogen/agentchat/contrib/capabilities/vision_capability.py +215 -0
- autogen/agentchat/contrib/captainagent/__init__.py +9 -0
- autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
- autogen/agentchat/contrib/captainagent/captainagent.py +514 -0
- autogen/agentchat/contrib/captainagent/tool_retriever.py +334 -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 +167 -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 +263 -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 +189 -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 +325 -0
- autogen/agentchat/contrib/rag/__init__.py +10 -0
- autogen/agentchat/contrib/rag/chromadb_query_engine.py +268 -0
- autogen/agentchat/contrib/rag/llamaindex_query_engine.py +195 -0
- autogen/agentchat/contrib/rag/mongodb_query_engine.py +319 -0
- autogen/agentchat/contrib/rag/query_engine.py +76 -0
- autogen/agentchat/contrib/retrieve_assistant_agent.py +59 -0
- autogen/agentchat/contrib/retrieve_user_proxy_agent.py +704 -0
- autogen/agentchat/contrib/society_of_mind_agent.py +200 -0
- autogen/agentchat/contrib/swarm_agent.py +1404 -0
- autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
- autogen/agentchat/contrib/vectordb/__init__.py +5 -0
- autogen/agentchat/contrib/vectordb/base.py +224 -0
- autogen/agentchat/contrib/vectordb/chromadb.py +316 -0
- autogen/agentchat/contrib/vectordb/couchbase.py +405 -0
- autogen/agentchat/contrib/vectordb/mongodb.py +551 -0
- autogen/agentchat/contrib/vectordb/pgvectordb.py +927 -0
- autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
- autogen/agentchat/contrib/vectordb/utils.py +126 -0
- autogen/agentchat/contrib/web_surfer.py +304 -0
- autogen/agentchat/conversable_agent.py +4307 -0
- autogen/agentchat/group/__init__.py +67 -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 +39 -0
- autogen/agentchat/group/context_variables.py +182 -0
- autogen/agentchat/group/events/transition_events.py +111 -0
- autogen/agentchat/group/group_tool_executor.py +324 -0
- autogen/agentchat/group/group_utils.py +659 -0
- autogen/agentchat/group/guardrails.py +179 -0
- autogen/agentchat/group/handoffs.py +303 -0
- autogen/agentchat/group/llm_condition.py +93 -0
- autogen/agentchat/group/multi_agent_chat.py +291 -0
- autogen/agentchat/group/on_condition.py +55 -0
- autogen/agentchat/group/on_context_condition.py +51 -0
- autogen/agentchat/group/patterns/__init__.py +18 -0
- autogen/agentchat/group/patterns/auto.py +160 -0
- autogen/agentchat/group/patterns/manual.py +177 -0
- autogen/agentchat/group/patterns/pattern.py +295 -0
- autogen/agentchat/group/patterns/random.py +106 -0
- autogen/agentchat/group/patterns/round_robin.py +117 -0
- autogen/agentchat/group/reply_result.py +24 -0
- autogen/agentchat/group/safeguards/__init__.py +21 -0
- autogen/agentchat/group/safeguards/api.py +241 -0
- autogen/agentchat/group/safeguards/enforcer.py +1158 -0
- autogen/agentchat/group/safeguards/events.py +140 -0
- autogen/agentchat/group/safeguards/validator.py +435 -0
- autogen/agentchat/group/speaker_selection_result.py +41 -0
- autogen/agentchat/group/targets/__init__.py +4 -0
- autogen/agentchat/group/targets/function_target.py +245 -0
- autogen/agentchat/group/targets/group_chat_target.py +133 -0
- autogen/agentchat/group/targets/group_manager_target.py +151 -0
- autogen/agentchat/group/targets/transition_target.py +424 -0
- autogen/agentchat/group/targets/transition_utils.py +6 -0
- autogen/agentchat/groupchat.py +1832 -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 +191 -0
- autogen/agentchat/realtime/experimental/function_observer.py +84 -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 +533 -0
- autogen/agentchat/realtime/experimental/websockets.py +21 -0
- autogen/agentchat/realtime_agent/__init__.py +21 -0
- autogen/agentchat/user_proxy_agent.py +114 -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 +74 -0
- autogen/agents/contrib/time/time_tool_agent.py +52 -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 +301 -0
- autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +113 -0
- autogen/agents/experimental/document_agent/document_agent.py +643 -0
- autogen/agents/experimental/document_agent/document_conditions.py +50 -0
- autogen/agents/experimental/document_agent/document_utils.py +376 -0
- autogen/agents/experimental/document_agent/inmemory_query_engine.py +214 -0
- autogen/agents/experimental/document_agent/parser_utils.py +134 -0
- autogen/agents/experimental/document_agent/url_utils.py +417 -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 +76 -0
- autogen/agents/experimental/websurfer/__init__.py +7 -0
- autogen/agents/experimental/websurfer/websurfer.py +70 -0
- autogen/agents/experimental/wikipedia/__init__.py +7 -0
- autogen/agents/experimental/wikipedia/wikipedia.py +88 -0
- autogen/browser_utils.py +309 -0
- autogen/cache/__init__.py +10 -0
- autogen/cache/abstract_cache_base.py +71 -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 +97 -0
- autogen/cache/in_memory_cache.py +54 -0
- autogen/cache/redis_cache.py +119 -0
- autogen/code_utils.py +598 -0
- autogen/coding/__init__.py +30 -0
- autogen/coding/base.py +120 -0
- autogen/coding/docker_commandline_code_executor.py +283 -0
- autogen/coding/factory.py +56 -0
- autogen/coding/func_with_reqs.py +203 -0
- autogen/coding/jupyter/__init__.py +23 -0
- autogen/coding/jupyter/base.py +36 -0
- autogen/coding/jupyter/docker_jupyter_server.py +160 -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 +224 -0
- autogen/coding/jupyter/jupyter_code_executor.py +154 -0
- autogen/coding/jupyter/local_jupyter_server.py +164 -0
- autogen/coding/local_commandline_code_executor.py +341 -0
- autogen/coding/markdown_code_extractor.py +44 -0
- autogen/coding/utils.py +55 -0
- autogen/coding/yepcode_code_executor.py +197 -0
- autogen/doc_utils.py +35 -0
- autogen/environments/__init__.py +10 -0
- autogen/environments/docker_python_environment.py +365 -0
- autogen/environments/python_environment.py +125 -0
- autogen/environments/system_python_environment.py +85 -0
- autogen/environments/venv_python_environment.py +220 -0
- autogen/environments/working_directory.py +74 -0
- autogen/events/__init__.py +7 -0
- autogen/events/agent_events.py +1016 -0
- autogen/events/base_event.py +100 -0
- autogen/events/client_events.py +168 -0
- autogen/events/helpers.py +44 -0
- autogen/events/print_event.py +45 -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 +75 -0
- autogen/fast_depends/core/__init__.py +14 -0
- autogen/fast_depends/core/build.py +206 -0
- autogen/fast_depends/core/model.py +527 -0
- autogen/fast_depends/dependencies/__init__.py +15 -0
- autogen/fast_depends/dependencies/model.py +30 -0
- autogen/fast_depends/dependencies/provider.py +40 -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 +272 -0
- autogen/fast_depends/utils.py +177 -0
- autogen/formatting_utils.py +83 -0
- autogen/function_utils.py +13 -0
- autogen/graph_utils.py +173 -0
- autogen/import_utils.py +539 -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 +156 -0
- autogen/interop/langchain/langchain_tool.py +78 -0
- autogen/interop/litellm/__init__.py +7 -0
- autogen/interop/litellm/litellm_config_factory.py +178 -0
- autogen/interop/pydantic_ai/__init__.py +7 -0
- autogen/interop/pydantic_ai/pydantic_ai.py +172 -0
- autogen/interop/registry.py +70 -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 +61 -0
- autogen/io/run_response.py +294 -0
- autogen/io/thread_io_stream.py +63 -0
- autogen/io/websockets.py +214 -0
- autogen/json_utils.py +42 -0
- autogen/llm_clients/MIGRATION_TO_V2.md +782 -0
- autogen/llm_clients/__init__.py +77 -0
- autogen/llm_clients/client_v2.py +122 -0
- autogen/llm_clients/models/__init__.py +55 -0
- autogen/llm_clients/models/content_blocks.py +389 -0
- autogen/llm_clients/models/unified_message.py +145 -0
- autogen/llm_clients/models/unified_response.py +83 -0
- autogen/llm_clients/openai_completions_client.py +444 -0
- autogen/llm_config/__init__.py +11 -0
- autogen/llm_config/client.py +59 -0
- autogen/llm_config/config.py +461 -0
- autogen/llm_config/entry.py +169 -0
- autogen/llm_config/types.py +37 -0
- autogen/llm_config/utils.py +223 -0
- autogen/logger/__init__.py +11 -0
- autogen/logger/base_logger.py +129 -0
- autogen/logger/file_logger.py +262 -0
- autogen/logger/logger_factory.py +42 -0
- autogen/logger/logger_utils.py +57 -0
- autogen/logger/sqlite_logger.py +524 -0
- autogen/math_utils.py +338 -0
- autogen/mcp/__init__.py +7 -0
- autogen/mcp/__main__.py +78 -0
- autogen/mcp/helpers.py +45 -0
- autogen/mcp/mcp_client.py +349 -0
- autogen/mcp/mcp_proxy/__init__.py +19 -0
- autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +62 -0
- autogen/mcp/mcp_proxy/mcp_proxy.py +577 -0
- autogen/mcp/mcp_proxy/operation_grouping.py +166 -0
- autogen/mcp/mcp_proxy/operation_renaming.py +110 -0
- autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py +98 -0
- autogen/mcp/mcp_proxy/security.py +399 -0
- autogen/mcp/mcp_proxy/security_schema_visitor.py +37 -0
- autogen/messages/__init__.py +7 -0
- autogen/messages/agent_messages.py +946 -0
- autogen/messages/base_message.py +108 -0
- autogen/messages/client_messages.py +172 -0
- autogen/messages/print_message.py +48 -0
- autogen/oai/__init__.py +61 -0
- autogen/oai/anthropic.py +1516 -0
- autogen/oai/bedrock.py +800 -0
- autogen/oai/cerebras.py +302 -0
- autogen/oai/client.py +1658 -0
- autogen/oai/client_utils.py +196 -0
- autogen/oai/cohere.py +494 -0
- autogen/oai/gemini.py +1045 -0
- autogen/oai/gemini_types.py +156 -0
- autogen/oai/groq.py +319 -0
- autogen/oai/mistral.py +311 -0
- autogen/oai/oai_models/__init__.py +23 -0
- autogen/oai/oai_models/_models.py +16 -0
- autogen/oai/oai_models/chat_completion.py +86 -0
- autogen/oai/oai_models/chat_completion_audio.py +32 -0
- autogen/oai/oai_models/chat_completion_message.py +97 -0
- autogen/oai/oai_models/chat_completion_message_tool_call.py +60 -0
- autogen/oai/oai_models/chat_completion_token_logprob.py +62 -0
- autogen/oai/oai_models/completion_usage.py +59 -0
- autogen/oai/ollama.py +657 -0
- autogen/oai/openai_responses.py +451 -0
- autogen/oai/openai_utils.py +897 -0
- autogen/oai/together.py +387 -0
- autogen/remote/__init__.py +18 -0
- autogen/remote/agent.py +199 -0
- autogen/remote/agent_service.py +197 -0
- autogen/remote/errors.py +17 -0
- autogen/remote/httpx_client_factory.py +131 -0
- autogen/remote/protocol.py +37 -0
- autogen/remote/retry.py +102 -0
- autogen/remote/runtime.py +96 -0
- autogen/retrieve_utils.py +490 -0
- autogen/runtime_logging.py +161 -0
- autogen/testing/__init__.py +12 -0
- autogen/testing/messages.py +45 -0
- autogen/testing/test_agent.py +111 -0
- autogen/token_count_utils.py +280 -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 +40 -0
- autogen/tools/dependency_injection.py +249 -0
- autogen/tools/experimental/__init__.py +54 -0
- autogen/tools/experimental/browser_use/__init__.py +7 -0
- autogen/tools/experimental/browser_use/browser_use.py +154 -0
- autogen/tools/experimental/code_execution/__init__.py +7 -0
- autogen/tools/experimental/code_execution/python_code_execution.py +86 -0
- autogen/tools/experimental/crawl4ai/__init__.py +7 -0
- autogen/tools/experimental/crawl4ai/crawl4ai.py +150 -0
- autogen/tools/experimental/deep_research/__init__.py +7 -0
- autogen/tools/experimental/deep_research/deep_research.py +329 -0
- autogen/tools/experimental/duckduckgo/__init__.py +7 -0
- autogen/tools/experimental/duckduckgo/duckduckgo_search.py +103 -0
- autogen/tools/experimental/firecrawl/__init__.py +7 -0
- autogen/tools/experimental/firecrawl/firecrawl_tool.py +836 -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 +284 -0
- autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/slack/slack.py +385 -0
- autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/telegram/telegram.py +271 -0
- autogen/tools/experimental/perplexity/__init__.py +7 -0
- autogen/tools/experimental/perplexity/perplexity_search.py +249 -0
- autogen/tools/experimental/reliable/__init__.py +10 -0
- autogen/tools/experimental/reliable/reliable.py +1311 -0
- autogen/tools/experimental/searxng/__init__.py +7 -0
- autogen/tools/experimental/searxng/searxng_search.py +142 -0
- autogen/tools/experimental/tavily/__init__.py +7 -0
- autogen/tools/experimental/tavily/tavily_search.py +176 -0
- autogen/tools/experimental/web_search_preview/__init__.py +7 -0
- autogen/tools/experimental/web_search_preview/web_search_preview.py +120 -0
- autogen/tools/experimental/wikipedia/__init__.py +7 -0
- autogen/tools/experimental/wikipedia/wikipedia.py +284 -0
- autogen/tools/function_utils.py +412 -0
- autogen/tools/tool.py +188 -0
- autogen/tools/toolkit.py +86 -0
- autogen/types.py +29 -0
- autogen/version.py +7 -0
- templates/client_template/main.jinja2 +72 -0
- templates/config_template/config.jinja2 +7 -0
- templates/main.jinja2 +61 -0
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import copy
|
|
5
|
+
import math
|
|
6
|
+
import random
|
|
7
|
+
import re
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import Any, Literal, Optional
|
|
10
|
+
|
|
11
|
+
from .... import Agent, AssistantAgent, UserProxyAgent
|
|
12
|
+
from ....doc_utils import export_module
|
|
13
|
+
from ....import_utils import optional_import_block
|
|
14
|
+
from ....llm_config import LLMConfig
|
|
15
|
+
|
|
16
|
+
__all__ = ["ReasoningAgent", "ThinkNode"]
|
|
17
|
+
|
|
18
|
+
EPSILON = 1e-6
|
|
19
|
+
|
|
20
|
+
REASONING_AGENT_MESSAGE = """You are a Reasoning AI Assistant. You generate high-quality responses to user questions by taking advantage of a tree-of-thought reasoning process."""
|
|
21
|
+
|
|
22
|
+
TREEOFTHOUGHT_MESSAGE = """Role: Deep Thinking AI Assistant
|
|
23
|
+
|
|
24
|
+
End Goal: Generate an efficient thinking trajectory of steps to follow in order to provide a high-quality response to the user. Think deep in complex questions and shallow in simple ones.
|
|
25
|
+
|
|
26
|
+
Current Task: Given the question and a list of previous thinking steps (the plan trajectory), you have two options:
|
|
27
|
+
1) Terminate: if you believe the user's question has been explored, terminate the task.
|
|
28
|
+
2) Continue Thinking: generate at least four innovative options for the next step in the thinking process to add in the trajectory. The user will not answer you anything.
|
|
29
|
+
|
|
30
|
+
### 1) Terminate
|
|
31
|
+
#### Instructions:
|
|
32
|
+
- Always terminate the task when you believe the user's question has been explored.
|
|
33
|
+
- The user wants the response the quicker possible, so when a high quality response can be crafted by the exploration performed, terminate the process.
|
|
34
|
+
- Don't terminate from the first step.
|
|
35
|
+
- Never provide additional options when terminating the task.
|
|
36
|
+
|
|
37
|
+
#### Format of Output:
|
|
38
|
+
REFLECTION:
|
|
39
|
+
*Give a few sentence reflections on the previous steps in the thinking trajectory, explaining why the task has been explored thoroughly.*
|
|
40
|
+
|
|
41
|
+
** Possible Options:**
|
|
42
|
+
Option 1: TERMINATE
|
|
43
|
+
<Short description>
|
|
44
|
+
|
|
45
|
+
### 2) Continue thinking
|
|
46
|
+
#### Instructions:
|
|
47
|
+
- Continue thinking when you believe that more exploration is needed to provide a high-quality response.
|
|
48
|
+
- Review the user's question and the previous steps taken. If only the question is provided and no previous steps, then make your suggestions to initiate the thinking process.
|
|
49
|
+
- The options you will provide must be alternatives for the next step in the thinking trajectory. Not steps that consider another option as given. So, make them focused and not too many.
|
|
50
|
+
- Identify any mistakes or errors in the previous thinking. If you find any mistakes, include options to correct them in your proposed options.
|
|
51
|
+
- If the question is a multi-choice question, you should carefully eliminate obviously wrong choices, look for contextual clues in the question, and use logical reasoning to select the most plausible answer.
|
|
52
|
+
- If you need to validate, simulate, or illustrate a reasoning concept (like mathematical expressions, code execution, algorithms, etc.) with Python, place the code in a fenced block like ```python ... ``` and always print the results that you want to see.
|
|
53
|
+
|
|
54
|
+
#### Options Restrictions:
|
|
55
|
+
- Never suggest options that access/consult/cross-check the internet, external sources, literature, datasets, books, or experts.
|
|
56
|
+
- Never suggest options in the physical world like conducting experiments or surveys, your approach in practical problems should still be theoretical.
|
|
57
|
+
- Never suggest options that require data you do not have, or suggest research to collect them.
|
|
58
|
+
- Never use Python when there is no need to.
|
|
59
|
+
- Never include a code option without the script to execute (e.g. not: Use python to make the calculations, but: Use this script to make the calculations: ```python... ```).
|
|
60
|
+
|
|
61
|
+
#### Format of Output:
|
|
62
|
+
REFLECTION:
|
|
63
|
+
*Give a few sentence reflections on the previous steps in the thinking trajectory, what is wrong and what is good.*
|
|
64
|
+
|
|
65
|
+
**Possible Options:**
|
|
66
|
+
Option 1: <Thinking 1>
|
|
67
|
+
<Short description, optional code snippet to execute>
|
|
68
|
+
|
|
69
|
+
Option 2: <Thinking 2>
|
|
70
|
+
<Short description, optional code snippet to execute>
|
|
71
|
+
|
|
72
|
+
Option 3: <Thinking 3>
|
|
73
|
+
<Short description, optional code snippet to execute>
|
|
74
|
+
|
|
75
|
+
Option 4: <Thinking 4>
|
|
76
|
+
<Short description, optional code snippet to execute>
|
|
77
|
+
|
|
78
|
+
...
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
EXECUTOR_MESSAGE = "Please provide an answer for the last step in the thinking trajectory, to advance the thinking process. Keep your answers as consise as possible. Never suggest the next step."
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@export_module("autogen.agents.experimental")
|
|
85
|
+
class ThinkNode:
|
|
86
|
+
def __init__(self, content: str, parent: Optional["ThinkNode"] = None) -> None:
|
|
87
|
+
"""A node in a tree structure representing a step in the reasoning process.
|
|
88
|
+
|
|
89
|
+
This class implements a tree node that stores content (text describing a reasoning step),
|
|
90
|
+
maintains parent-child relationships, tracks node statistics, and provides utilities
|
|
91
|
+
for traversing/visualizing the reasoning path.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
content (str): The text content/description for this reasoning step.
|
|
95
|
+
parent (Optional[ThinkNode]): The parent node in the tree, if any.
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
content (str): The text content/description for this reasoning step.
|
|
99
|
+
value (float): A numeric score/value assigned to this node.
|
|
100
|
+
parent (Optional[ThinkNode]): Reference to the parent node.
|
|
101
|
+
reflection (str): A string containing reflections on the reasoning process.
|
|
102
|
+
rating_details (str): A string providing details about the rating of this node.
|
|
103
|
+
output (Optional[str]): The output generated at this node through the `execute_node` method.
|
|
104
|
+
depth (int): The depth of this node in the tree (root = 0).
|
|
105
|
+
children (list[ThinkNode]): list of child nodes.
|
|
106
|
+
visits (int): Number of times this node has been visited during search.
|
|
107
|
+
|
|
108
|
+
The node automatically maintains the tree structure by:
|
|
109
|
+
- Setting its depth based on the parent's depth + 1.
|
|
110
|
+
- Adding itself to the parent's children list if the parent exists.
|
|
111
|
+
- Providing trajectory utilities to get the full path from root to this node.
|
|
112
|
+
"""
|
|
113
|
+
self.content: str = content
|
|
114
|
+
self.value: float = 0.0
|
|
115
|
+
self.parent: ThinkNode | None = parent
|
|
116
|
+
self.reflection: str = ""
|
|
117
|
+
self.rating_details: str = ""
|
|
118
|
+
self.output: str | None = None
|
|
119
|
+
self.depth: int = parent.depth + 1 if parent is not None else 0
|
|
120
|
+
self.children: list[ThinkNode] = []
|
|
121
|
+
self.visits: int = 0
|
|
122
|
+
if self.parent:
|
|
123
|
+
self.parent.children.append(self)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def _trajectory_arr(self) -> list[str]:
|
|
127
|
+
"""Gets the full path from root to this node as a list of strings.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
list[str]: list containing the content of each node from root to current node
|
|
131
|
+
"""
|
|
132
|
+
step = f"Content: {self.content}"
|
|
133
|
+
if self.output is not None:
|
|
134
|
+
step += f"\nOutput: {self.output}"
|
|
135
|
+
if self.parent:
|
|
136
|
+
return self.parent._trajectory_arr + [step]
|
|
137
|
+
return ["# Question:\n" + step + "\n---\n"]
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def trajectory(self) -> str:
|
|
141
|
+
"""Get a formatted string representation of the path from root to this node.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
str: A formatted string showing the question and each step in the reasoning process
|
|
145
|
+
"""
|
|
146
|
+
traj = self._trajectory_arr
|
|
147
|
+
ans = traj[0]
|
|
148
|
+
ans += "# Trajectory:\n"
|
|
149
|
+
for i, step in enumerate(traj[1:]):
|
|
150
|
+
ans += f"\nStep {i + 1}:\n{step}"
|
|
151
|
+
return ans
|
|
152
|
+
|
|
153
|
+
def backpropagate(self, reward: float) -> None:
|
|
154
|
+
"""Update the score of this node and its parents using moving average.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
reward (float): The reward to backpropagate up the tree.
|
|
158
|
+
"""
|
|
159
|
+
node: ThinkNode | None = self
|
|
160
|
+
while node is not None:
|
|
161
|
+
node.visits += 1
|
|
162
|
+
node.value = (node.value * (node.visits - 1) + reward) / node.visits
|
|
163
|
+
node = node.parent
|
|
164
|
+
|
|
165
|
+
def __str__(self) -> str:
|
|
166
|
+
return f"{self.content} -> Depth: {self.depth} Value: {self.value} Visits: {self.visits}"
|
|
167
|
+
|
|
168
|
+
def __repr__(self) -> str:
|
|
169
|
+
return self.__str__()
|
|
170
|
+
|
|
171
|
+
def to_dict(self) -> dict[str, Any]:
|
|
172
|
+
"""Convert ThinkNode to dictionary representation.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
dict[str, Any]: dictionary containing all node attributes and recursive children
|
|
176
|
+
"""
|
|
177
|
+
return {
|
|
178
|
+
"content": self.content,
|
|
179
|
+
"value": self.value,
|
|
180
|
+
"depth": self.depth,
|
|
181
|
+
"reflection": self.reflection,
|
|
182
|
+
"rating_details": self.rating_details,
|
|
183
|
+
"output": self.output,
|
|
184
|
+
"visits": self.visits,
|
|
185
|
+
"children": [child.to_dict() for child in self.children],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def from_dict(cls, data: dict[str, Any], parent: Optional["ThinkNode"] = None) -> "ThinkNode":
|
|
190
|
+
"""Create ThinkNode from dictionary representation.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
data (dict[str, Any]): dictionary containing node data
|
|
194
|
+
parent (Optional[ThinkNode]): Parent node to attach to
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ThinkNode: Reconstructed node with all children
|
|
198
|
+
"""
|
|
199
|
+
node = cls(content=data["content"], parent=parent)
|
|
200
|
+
node.value = data["value"]
|
|
201
|
+
node.depth = data["depth"]
|
|
202
|
+
node.visits = data["visits"]
|
|
203
|
+
node.reflection = data.get("reflection", "")
|
|
204
|
+
node.rating_details = data.get("rating_details", "")
|
|
205
|
+
node.output = data.get("output")
|
|
206
|
+
|
|
207
|
+
# Recursively create children
|
|
208
|
+
for child_data in data["children"]:
|
|
209
|
+
cls.from_dict(child_data, parent=node)
|
|
210
|
+
|
|
211
|
+
return node
|
|
212
|
+
|
|
213
|
+
def visualize_tree(self) -> None:
|
|
214
|
+
"""Visualize the tree of thoughts using graphviz."""
|
|
215
|
+
with optional_import_block() as result:
|
|
216
|
+
from graphviz import Digraph
|
|
217
|
+
|
|
218
|
+
if not result.is_successful:
|
|
219
|
+
print("Please install graphviz: pip install graphviz")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
dot = Digraph(comment="Tree of Thoughts")
|
|
223
|
+
dot.attr(rankdir="TB") # Top to Bottom direction
|
|
224
|
+
|
|
225
|
+
def add_nodes(node: ThinkNode, node_id: str = "0") -> None:
|
|
226
|
+
# Truncate long content for better visualization
|
|
227
|
+
display_content = (node.content[:50] + "...") if len(node.content) > 50 else node.content
|
|
228
|
+
|
|
229
|
+
# Add node with stats
|
|
230
|
+
label = f"{display_content}\n visits: {node.visits}\n value: {node.value}"
|
|
231
|
+
dot.node(node_id, label)
|
|
232
|
+
|
|
233
|
+
# Recursively add children
|
|
234
|
+
for i, child in enumerate(node.children):
|
|
235
|
+
child_id = f"{node_id}_{i}"
|
|
236
|
+
add_nodes(child, child_id)
|
|
237
|
+
dot.edge(node_id, child_id)
|
|
238
|
+
|
|
239
|
+
add_nodes(self)
|
|
240
|
+
|
|
241
|
+
# Render the graph
|
|
242
|
+
try:
|
|
243
|
+
dot.render("tree_of_thoughts", view=False, format="png", cleanup=True)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
print(f"Error rendering graph: {e}")
|
|
246
|
+
print("Make sure graphviz is installed on your system: https://graphviz.org/download/")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def extract_sft_dataset(root: ThinkNode) -> list[dict[str, Any]]:
|
|
250
|
+
"""Extract the best trajectory or multiple equally good trajectories for SFT training.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
root (ThinkNonde): The root node of the tree.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
list[dict]: list of best trajectories, each one is a pair of instruction and response.
|
|
257
|
+
"""
|
|
258
|
+
instruction = root.content
|
|
259
|
+
idx = len("# Question: ") + len(root.content) + 1
|
|
260
|
+
|
|
261
|
+
def _find_leaf_nodes(node: ThinkNode) -> list[ThinkNode]:
|
|
262
|
+
"""Recursively find all leaf nodes."""
|
|
263
|
+
if not node.children:
|
|
264
|
+
return [node]
|
|
265
|
+
leafs = []
|
|
266
|
+
for child in node.children:
|
|
267
|
+
leafs.extend(_find_leaf_nodes(child))
|
|
268
|
+
return leafs
|
|
269
|
+
|
|
270
|
+
# Step 1: Find all leaf nodes
|
|
271
|
+
leaf_nodes = _find_leaf_nodes(root)
|
|
272
|
+
|
|
273
|
+
# Step 2: Determine the highest score among leaf nodes
|
|
274
|
+
max_value = max(leaf_nodes, key=lambda x: x.value).value
|
|
275
|
+
|
|
276
|
+
# Step 3: Collect all leaf nodes with the highest score
|
|
277
|
+
best_leafs = [leaf for leaf in leaf_nodes if leaf.value == max_value]
|
|
278
|
+
|
|
279
|
+
# Step 4: Collect trajectories for all the best leaf nodes
|
|
280
|
+
best_trajectories = [{"instruction": instruction, "response": leaf.trajectory[idx:]} for leaf in best_leafs]
|
|
281
|
+
|
|
282
|
+
return best_trajectories
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def extract_rlhf_preference_dataset(root: ThinkNode, contrastive_threshold: float = 0.2) -> list[dict[str, Any]]:
|
|
286
|
+
"""Extract and generate preference pairs for RLHF training by comparing sibling nodes.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
root (ThinkNode): The root node of the tree.
|
|
290
|
+
contrastive_threshold (float): between (0, 1), a distance measure that we are confident to call
|
|
291
|
+
one is positive and another is negative.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
list[dict]: list of preference pairs, where each pair contains two responses and
|
|
295
|
+
indicates which one is preferred.
|
|
296
|
+
"""
|
|
297
|
+
preference_pairs = []
|
|
298
|
+
|
|
299
|
+
assert contrastive_threshold > 0
|
|
300
|
+
assert contrastive_threshold < 1
|
|
301
|
+
|
|
302
|
+
def traverse_tree(node: ThinkNode) -> None:
|
|
303
|
+
"""Traverse the tree to compare sibling nodes and collect preferences."""
|
|
304
|
+
if not node.children:
|
|
305
|
+
return # Leaf node, no comparisons needed
|
|
306
|
+
|
|
307
|
+
# Step 1: Compare all sibling nodes
|
|
308
|
+
for i in range(len(node.children)):
|
|
309
|
+
for j in range(len(node.children)):
|
|
310
|
+
if i == j:
|
|
311
|
+
continue
|
|
312
|
+
child_a, child_b = node.children[i], node.children[j]
|
|
313
|
+
|
|
314
|
+
is_a_better = False
|
|
315
|
+
if child_a.visits > 0 and child_b.visits > 0:
|
|
316
|
+
# for MCTS
|
|
317
|
+
is_a_better = (
|
|
318
|
+
child_a.value / child_a.visits - child_b.value / child_b.visits > contrastive_threshold
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
# for Beam Search
|
|
322
|
+
is_a_better = child_a.value - child_b.value > contrastive_threshold
|
|
323
|
+
if is_a_better:
|
|
324
|
+
preference_pairs.append({
|
|
325
|
+
"instruction": node.trajectory,
|
|
326
|
+
"reflection": node.reflection,
|
|
327
|
+
"preferred_response": f"Step {child_a.depth}: {child_a.content}",
|
|
328
|
+
"dispreferred_response": f"Step {child_b.depth}: {child_b.content}",
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
# Step 2: Recurse into child nodes
|
|
332
|
+
for child in node.children:
|
|
333
|
+
traverse_tree(child)
|
|
334
|
+
|
|
335
|
+
# Start traversal from the root
|
|
336
|
+
traverse_tree(root)
|
|
337
|
+
|
|
338
|
+
return preference_pairs
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@export_module("autogen.agents.experimental")
|
|
342
|
+
class ReasoningAgent(AssistantAgent):
|
|
343
|
+
def __init__(
|
|
344
|
+
self,
|
|
345
|
+
name: str,
|
|
346
|
+
llm_config: LLMConfig | dict[str, Any] | None = None,
|
|
347
|
+
grader_llm_config: LLMConfig | dict[str, Any] | None = None,
|
|
348
|
+
max_depth: int = 4,
|
|
349
|
+
beam_size: int = 3,
|
|
350
|
+
answer_approach: Literal["pool", "best"] = "pool",
|
|
351
|
+
reason_config: dict[str, Any] | None = None,
|
|
352
|
+
code_execution_config: dict[str, Any] | Literal[False] = False,
|
|
353
|
+
scope: str | None = None,
|
|
354
|
+
**kwargs: Any,
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Initialize a ReasoningAgent that uses tree-of-thought reasoning.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
name (str): Name of the agent
|
|
360
|
+
llm_config (Optional[Union[LLMConfig, dict[str, Any]]]): Configuration for the language model
|
|
361
|
+
grader_llm_config (Optional[Union[LLMConfig, dict[str, Any]]]): Optional separate configuration for the grader model. If not provided, uses llm_config
|
|
362
|
+
max_depth (int): Maximum depth of the reasoning tree
|
|
363
|
+
beam_size (int): DEPRECATED. Number of parallel reasoning paths to maintain
|
|
364
|
+
answer_approach (str): DEPRECATED. Either "pool" or "best" - how to generate final answer
|
|
365
|
+
reason_config (Optional[dict[str, Any]]): Configuration for the reasoning method.
|
|
366
|
+
method (str): The search strategy to use. Options:
|
|
367
|
+
- "beam_search" (default): Uses beam search with parallel paths
|
|
368
|
+
- "mcts": Uses Monte Carlo Tree Search for exploration
|
|
369
|
+
- "lats": Uses Language Agent Tree Search with per-step rewards
|
|
370
|
+
- "dfs": Uses depth-first search (equivalent to beam_search with beam_size=1)
|
|
371
|
+
|
|
372
|
+
Common parameters:
|
|
373
|
+
max_depth (int): Maximum depth of reasoning tree (default: 3)
|
|
374
|
+
forest_size (int): Number of independent trees to maintain (default: 1)
|
|
375
|
+
rating_scale (int): Scale for grading responses, e.g. 1-10 (default: 10)
|
|
376
|
+
interim_execution (bool): Whether to execute the suggested options between the steps.
|
|
377
|
+
|
|
378
|
+
Beam Search specific:
|
|
379
|
+
beam_size (int): Number of parallel paths to maintain (default: 3)
|
|
380
|
+
answer_approach (str): How to select final answer, "pool" or "best" (default: "pool")
|
|
381
|
+
batch_grading (bool): Whether to grade all options on each beam at once or one by one (default: False).
|
|
382
|
+
|
|
383
|
+
MCTS/LATS specific:
|
|
384
|
+
nsim (int): Number of simulations to run (default: 3)
|
|
385
|
+
exploration_constant (float): UCT exploration parameter (default: 1.41)
|
|
386
|
+
|
|
387
|
+
Example configs:
|
|
388
|
+
`{"method": "beam_search", "beam_size": 5, "max_depth": 4}`
|
|
389
|
+
`{"method": "mcts", "nsim": 10, "exploration_constant": 2.0}`
|
|
390
|
+
`{"method": "lats", "nsim": 5, "forest_size": 3}`
|
|
391
|
+
code_execution_config (dict or False): config for the code execution.
|
|
392
|
+
To disable code execution, set to False. Otherwise, set to a dictionary with the following keys:
|
|
393
|
+
- work_dir (Optional, str): The working directory for the code execution.
|
|
394
|
+
If None, a default working directory will be used.
|
|
395
|
+
The default working directory is the "extensions" directory under
|
|
396
|
+
"path_to_autogen".
|
|
397
|
+
- use_docker (Optional, list, str or bool): The docker image to use for code execution.
|
|
398
|
+
Default is True, which means the code will be executed in a docker container. A default list of images will be used.
|
|
399
|
+
If a list or a str of image name(s) is provided, the code will be executed in a docker container
|
|
400
|
+
with the first image successfully pulled.
|
|
401
|
+
If False, the code will be executed in the current environment.
|
|
402
|
+
We strongly recommend using docker for code execution.
|
|
403
|
+
- timeout (Optional, int): The maximum execution time in seconds.
|
|
404
|
+
- last_n_messages (Experimental, int or str): The number of messages to look back for code execution.
|
|
405
|
+
If set to 'auto', it will scan backwards through all messages arriving since the agent last spoke, which is typically the last time execution was attempted. (Default: auto)
|
|
406
|
+
scope (Optional[str]): The scope of the agent, includes information on how the agent should operate. It is appended to all system prompts of the internal agents. If None, no scope is added to the prompts.
|
|
407
|
+
**kwargs (Any): Additional keyword arguments passed to parent class
|
|
408
|
+
"""
|
|
409
|
+
reason_config = reason_config or {}
|
|
410
|
+
if "verbose" in kwargs:
|
|
411
|
+
warnings.warn(
|
|
412
|
+
"The parameter `verbose` in ReasoningAgent has been deprecated. "
|
|
413
|
+
"Please use the `silent` parameter as other AG2 agents.",
|
|
414
|
+
DeprecationWarning,
|
|
415
|
+
)
|
|
416
|
+
kwargs["silent"] = not kwargs.pop("verbose")
|
|
417
|
+
|
|
418
|
+
llm_config = LLMConfig.get_current_llm_config(llm_config) # type: ignore[arg-type]
|
|
419
|
+
self._scope = scope
|
|
420
|
+
|
|
421
|
+
system_msg = kwargs.pop("system_message", REASONING_AGENT_MESSAGE)
|
|
422
|
+
system_msg = self._add_scope(system_msg)
|
|
423
|
+
|
|
424
|
+
super().__init__(
|
|
425
|
+
name=name,
|
|
426
|
+
system_message=system_msg,
|
|
427
|
+
llm_config=llm_config,
|
|
428
|
+
code_execution_config=code_execution_config,
|
|
429
|
+
**kwargs,
|
|
430
|
+
)
|
|
431
|
+
self._llm_config: LLMConfig | dict[str, Any] | None = llm_config
|
|
432
|
+
self._grader_llm_config: LLMConfig | dict[str, Any] | None = (
|
|
433
|
+
grader_llm_config if grader_llm_config else llm_config
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
if max_depth != 4 or beam_size != 3 or answer_approach != "pool":
|
|
437
|
+
warnings.warn(
|
|
438
|
+
"The parameters max_depth, beam_size, and answer_approach have been deprecated. "
|
|
439
|
+
"Please use the reason_config dictionary to configure these settings instead.",
|
|
440
|
+
DeprecationWarning,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
self._reason_config: dict[str, Any] = reason_config or {}
|
|
444
|
+
self._method: Literal["beam_search", "mcts", "lats", "dfs"] = reason_config.get("method", "beam_search")
|
|
445
|
+
if self._method not in ["beam_search", "mcts", "lats", "dfs"]:
|
|
446
|
+
raise ValueError(
|
|
447
|
+
f"Invalid reasoning method specified: '{self._method}'. Should be one of 'beam_search', 'mcts', 'lats', or 'dfs'."
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
self._beam_size: int = 1
|
|
451
|
+
if self._method in ["beam_search", "dfs"]:
|
|
452
|
+
if self._method != "dfs":
|
|
453
|
+
self._beam_size = reason_config.get("beam_size", beam_size)
|
|
454
|
+
self._answer_approach: Literal["pool", "best"] = reason_config.get("answer_approach", answer_approach)
|
|
455
|
+
if self._answer_approach not in ["pool", "best"]:
|
|
456
|
+
raise ValueError(
|
|
457
|
+
f"Invalid answer_approach specified: '{self._answer_approach}'. Should be one of 'pool' or 'best'."
|
|
458
|
+
)
|
|
459
|
+
self._batch_grading: bool = reason_config.get("batch_grading", False)
|
|
460
|
+
elif self._method in ["mcts", "lats"]:
|
|
461
|
+
self._nsim: int = reason_config.get("nsim", 3)
|
|
462
|
+
self._exploration_constant: float = reason_config.get("exploration_constant", 1.41)
|
|
463
|
+
|
|
464
|
+
self._max_depth: int = reason_config.get("max_depth", max_depth)
|
|
465
|
+
self._forest_size: int = reason_config.get("forest_size", 1)
|
|
466
|
+
self._rating_scale: int = reason_config.get("rating_scale", 10)
|
|
467
|
+
self._interim_execution: bool = reason_config.get("interim_execution", False)
|
|
468
|
+
|
|
469
|
+
self._root: ThinkNode | None = None
|
|
470
|
+
self._lats_context: str = ""
|
|
471
|
+
self.register_reply([Agent, None], ReasoningAgent.generate_forest_response)
|
|
472
|
+
|
|
473
|
+
# Initialize llm agent for interim step execution
|
|
474
|
+
self._executor: AssistantAgent | None = None
|
|
475
|
+
# Add scope if provided
|
|
476
|
+
executor_msg = self._add_scope(EXECUTOR_MESSAGE)
|
|
477
|
+
if self._interim_execution:
|
|
478
|
+
self._executor = AssistantAgent(
|
|
479
|
+
name="tot_executor", system_message=executor_msg, llm_config=self._llm_config
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
tot_msg = self._add_scope(TREEOFTHOUGHT_MESSAGE)
|
|
483
|
+
|
|
484
|
+
# Initialize user proxy agent for code execution
|
|
485
|
+
self._user_proxy: UserProxyAgent | None = None
|
|
486
|
+
if self._code_execution_config:
|
|
487
|
+
# to execute code interim_execution should be True
|
|
488
|
+
if not self._interim_execution:
|
|
489
|
+
raise ValueError(
|
|
490
|
+
"Code execution is enabled in the system, but interim_execution is set to False. "
|
|
491
|
+
"Please set interim_execution to True to allow code execution between reasoning steps."
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
self._user_proxy = UserProxyAgent(
|
|
495
|
+
name="reasoner_user_proxy",
|
|
496
|
+
human_input_mode="NEVER",
|
|
497
|
+
code_execution_config=self._code_execution_config,
|
|
498
|
+
max_consecutive_auto_reply=1,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
self._code_execution_config = False # code should only be executed by the user proxy
|
|
502
|
+
else:
|
|
503
|
+
# remove python instructions from the tot message
|
|
504
|
+
tot_msg = "\n".join([
|
|
505
|
+
line for line in tot_msg.split("\n") if not re.compile(r".*(python|```).*").search(line)
|
|
506
|
+
])
|
|
507
|
+
|
|
508
|
+
# Initialize required agents
|
|
509
|
+
self._thinker = AssistantAgent(name="tot_thinker", system_message=tot_msg, llm_config=self._llm_config)
|
|
510
|
+
self._grader = AssistantAgent(name="tot_grader", llm_config=self._grader_llm_config)
|
|
511
|
+
self._prompt_rewriter = AssistantAgent(name="prompt_rewriter", llm_config=self._llm_config)
|
|
512
|
+
|
|
513
|
+
def _add_scope(self, system_prompt: str) -> str:
|
|
514
|
+
"""Add scope information to the system prompt.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
system_prompt (str): The original system prompt.
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
str: The modified system prompt with scope information.
|
|
521
|
+
"""
|
|
522
|
+
if self._scope:
|
|
523
|
+
return f"Task Scope: {self._scope}\n\n{system_prompt}"
|
|
524
|
+
return system_prompt
|
|
525
|
+
|
|
526
|
+
def generate_forest_response(
|
|
527
|
+
self,
|
|
528
|
+
messages: list[dict[str, Any]] | None = None,
|
|
529
|
+
sender: Agent | None = None,
|
|
530
|
+
config: dict[str, Any] | None = None,
|
|
531
|
+
) -> tuple[bool, str]:
|
|
532
|
+
"""Generate a response using tree-of-thought reasoning.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
messages (Optional[list[dict[str, Any]]]): Input messages to respond to
|
|
536
|
+
sender (Optional[Agent]): Agent sending the messages
|
|
537
|
+
config (Optional[dict[str, Any]]): Optional configuration
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
Tuple[bool, str]: Success flag and generated response
|
|
541
|
+
"""
|
|
542
|
+
if sender == self:
|
|
543
|
+
return False, "" # Defer the LLM call to next reply functions.
|
|
544
|
+
prompt, ground_truth = self._process_prompt(messages, sender)
|
|
545
|
+
if not prompt:
|
|
546
|
+
return True, "TERMINATE"
|
|
547
|
+
|
|
548
|
+
forest_answers: list[str] = []
|
|
549
|
+
for _ in range(self._forest_size):
|
|
550
|
+
if self._method in ["beam_search", "dfs"]:
|
|
551
|
+
response = self._beam_reply(prompt, ground_truth)
|
|
552
|
+
elif self._method in ["mcts", "lats"]:
|
|
553
|
+
response = self._mcts_reply(prompt, ground_truth)
|
|
554
|
+
else:
|
|
555
|
+
raise ValueError("Invalid reasoning method specified.")
|
|
556
|
+
|
|
557
|
+
forest_answers.append(response)
|
|
558
|
+
|
|
559
|
+
if len(forest_answers) == 1:
|
|
560
|
+
return True, forest_answers[0]
|
|
561
|
+
else:
|
|
562
|
+
forest_answers_str = "-" + "\n-".join(forest_answers)
|
|
563
|
+
self.send(
|
|
564
|
+
message=f"""Given a list of different answers provide a complete response to a user's question.
|
|
565
|
+
Question:
|
|
566
|
+
{prompt}
|
|
567
|
+
|
|
568
|
+
Answers:
|
|
569
|
+
{forest_answers_str}
|
|
570
|
+
|
|
571
|
+
Final Answer:
|
|
572
|
+
""",
|
|
573
|
+
recipient=self,
|
|
574
|
+
request_reply=True,
|
|
575
|
+
silent=self.silent,
|
|
576
|
+
)
|
|
577
|
+
last_msg: dict[str, Any] | None = self.last_message(self)
|
|
578
|
+
if last_msg is None:
|
|
579
|
+
return True, ""
|
|
580
|
+
return True, last_msg["content"].strip()
|
|
581
|
+
|
|
582
|
+
def rate_node(self, node: ThinkNode, ground_truth: str | None = None, is_outcome: bool = False) -> float:
|
|
583
|
+
"""Rate the quality of a reasoning path or the final answer using the grader agent.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
node (ThinkNode): Node containing the reasoning trajectory to evaluate
|
|
587
|
+
ground_truth (Optional[str]): Optional ground truth to provide to the grader
|
|
588
|
+
is_outcome (bool): indicates whether the rating is for an outcome (final answer) or a process (thinking trajectory).
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
float: Normalized score between 0 and 1 indicating trajectory quality
|
|
592
|
+
"""
|
|
593
|
+
if node.value > 0 and node.rating_details:
|
|
594
|
+
# we already calculated the rating for the node
|
|
595
|
+
return node.value
|
|
596
|
+
|
|
597
|
+
# Update Grader's system message
|
|
598
|
+
if is_outcome:
|
|
599
|
+
# Outcome Rating
|
|
600
|
+
message = f"""Please rate the answer on a scale of 1 to {self._rating_scale}, where 1 is the worst and {self._rating_scale} is the best.
|
|
601
|
+
|
|
602
|
+
A great answer must:
|
|
603
|
+
- Directly address the original question
|
|
604
|
+
- Be factually accurate and complete
|
|
605
|
+
- Show clear logical reasoning
|
|
606
|
+
|
|
607
|
+
Additionally, a good answer should:
|
|
608
|
+
- Be concise and well-structured
|
|
609
|
+
- Use appropriate language and tone
|
|
610
|
+
- Provide relevant examples or evidence when needed
|
|
611
|
+
- Be free of contradictions or inconsistencies
|
|
612
|
+
|
|
613
|
+
If the answer fails to meet any of the core requirements above, it should be considered a poor response.
|
|
614
|
+
|
|
615
|
+
Also, rate poory (with 1) trajectories that:
|
|
616
|
+
- Require access to internet, experts opinions or external sources.
|
|
617
|
+
- Require research, hypotheses or data that are not provided.
|
|
618
|
+
- Include solutions in the physical world, like conducting experiments or surveys (code execution is fine).
|
|
619
|
+
|
|
620
|
+
Please provide your rating along with a brief explanation of your assessment.
|
|
621
|
+
"""
|
|
622
|
+
else:
|
|
623
|
+
# Process Rating
|
|
624
|
+
message = f"""Please rate the thinking trajectory on a scale of 1 to {self._rating_scale}, where 1 is the worst and {self._rating_scale} is the best.
|
|
625
|
+
|
|
626
|
+
A great thinking trajectory must:
|
|
627
|
+
- Advance the process of solving the problem.
|
|
628
|
+
|
|
629
|
+
Additionally, a good trajectory should:
|
|
630
|
+
- Be appropriate in conversation.
|
|
631
|
+
- Contain no inaccuracies.
|
|
632
|
+
- Be free of any odd or irrelevant content.
|
|
633
|
+
|
|
634
|
+
If the trajectory does not meet one of the above requirements, it is considered a bad response.
|
|
635
|
+
|
|
636
|
+
Also, rate poory (with 1) trajectories that:
|
|
637
|
+
- Require access to internet, experts opinions or external sources.
|
|
638
|
+
- Require research, hypotheses or data that are not provided.
|
|
639
|
+
- Include solutions in the physical world, like conducting experiments or surveys (code execution is fine).
|
|
640
|
+
|
|
641
|
+
Please provide your rating along with a brief explanation of your assessment.
|
|
642
|
+
"""
|
|
643
|
+
# Add ground truth to the message.
|
|
644
|
+
if ground_truth:
|
|
645
|
+
# override the system message
|
|
646
|
+
message += f"--- Note that the Ground Truth is ---\n{ground_truth}\n---\n"
|
|
647
|
+
|
|
648
|
+
# add scope if provided
|
|
649
|
+
message = self._add_scope(message)
|
|
650
|
+
|
|
651
|
+
self._grader.update_system_message(message)
|
|
652
|
+
|
|
653
|
+
if self._method == "lats":
|
|
654
|
+
prompt = self._lats_context + "\n\n---\n\n" + f"Rate:\n{node.trajectory}"
|
|
655
|
+
else:
|
|
656
|
+
prompt = f"Rate:\n{node.trajectory}"
|
|
657
|
+
|
|
658
|
+
self._grader.clear_history()
|
|
659
|
+
self.send(
|
|
660
|
+
message=prompt,
|
|
661
|
+
recipient=self._grader,
|
|
662
|
+
request_reply=True,
|
|
663
|
+
silent=self.silent,
|
|
664
|
+
)
|
|
665
|
+
rating: str = ""
|
|
666
|
+
last_message: dict[str, Any] | None = self._grader.last_message()
|
|
667
|
+
if last_message is not None:
|
|
668
|
+
rating = last_message["content"].strip()
|
|
669
|
+
node.rating_details = rating
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
# Scale rating to [0, 1]
|
|
673
|
+
reward = (float(re.findall(r"[\d.]+", rating)[0]) - 1.0) / (self._rating_scale - 1.0)
|
|
674
|
+
except (IndexError, ValueError):
|
|
675
|
+
reward = 0.0 # Default reward if parsing fails
|
|
676
|
+
return reward
|
|
677
|
+
|
|
678
|
+
def rate_batch_nodes(self, nodes: list[ThinkNode], ground_truth: str | None = None) -> list[float]:
|
|
679
|
+
"""Rate a batch of nodes using a single call of the grader agent. All the nodes must have the same parent.
|
|
680
|
+
|
|
681
|
+
This method evaluates all given nodes while considering the other available options.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
nodes (list[ThinkNode]): List of nodes to rate, all nodes must have the same parent
|
|
685
|
+
ground_truth (Optional[str]): Optional ground truth to provide to the grader
|
|
686
|
+
"""
|
|
687
|
+
# Assert that all nodes have the same parent and it is not None
|
|
688
|
+
assert all(node.parent == nodes[0].parent for node in nodes), "All nodes must have the same parent."
|
|
689
|
+
assert nodes[0].parent is not None, "Parent node must not be None."
|
|
690
|
+
|
|
691
|
+
# Update Grader's system message
|
|
692
|
+
message = f"""You will be provided a thinking trajectory and a list of options for the next step.
|
|
693
|
+
Please rate the thinking trajectory created by each option on a scale of 1 to {self._rating_scale}, where 1 is the worst and {self._rating_scale} is the best.
|
|
694
|
+
|
|
695
|
+
A great thinking trajectory must:
|
|
696
|
+
- Advance the process of solving the problem.
|
|
697
|
+
|
|
698
|
+
Additionally, a good trajectory should:
|
|
699
|
+
- Be appropriate in conversation.
|
|
700
|
+
- Contain no inaccuracies.
|
|
701
|
+
- Be free of any odd or irrelevant content.
|
|
702
|
+
|
|
703
|
+
If the trajectory does not meet one of the above requirements, it is considered a bad response.
|
|
704
|
+
|
|
705
|
+
Also, rate poorly (with 1) trajectories that:
|
|
706
|
+
- Require access to internet, experts opinions or external sources.
|
|
707
|
+
- Require research, hypotheses or data that are not provided.
|
|
708
|
+
- Include solutions in the physical world, like conducting experiments or surveys (code execution is fine).
|
|
709
|
+
|
|
710
|
+
Please provide your rating along with a brief explanation of your assessment.
|
|
711
|
+
|
|
712
|
+
**Output Format:**
|
|
713
|
+
Option 1: <your explanation here for the trajectory>
|
|
714
|
+
Rating: <rating>
|
|
715
|
+
|
|
716
|
+
Option 2: <your explanation here for the trajectory>
|
|
717
|
+
Rating: <rating>
|
|
718
|
+
...
|
|
719
|
+
"""
|
|
720
|
+
# Add ground truth to the message.
|
|
721
|
+
if ground_truth:
|
|
722
|
+
# override the system message
|
|
723
|
+
message += f"--- Note that the Ground Truth is ---\n{ground_truth}\n---\n"
|
|
724
|
+
|
|
725
|
+
# add scope if provided
|
|
726
|
+
message = self._add_scope(message)
|
|
727
|
+
|
|
728
|
+
self._grader.update_system_message(message)
|
|
729
|
+
|
|
730
|
+
# add lats context if necessary
|
|
731
|
+
prompt = f"{self._lats_context}\n\n---\n\n" if self._method == "lats" else ""
|
|
732
|
+
|
|
733
|
+
# add current trajectory
|
|
734
|
+
prompt += f"Trajectory:\n{nodes[0].parent.trajectory}\n\n---\n\nOptions:\n"
|
|
735
|
+
|
|
736
|
+
# add options
|
|
737
|
+
for i, node in enumerate(nodes):
|
|
738
|
+
prompt += f"\nOption {i + 1}:\n{node.content}"
|
|
739
|
+
|
|
740
|
+
self._grader.clear_history()
|
|
741
|
+
self.send(
|
|
742
|
+
message=prompt,
|
|
743
|
+
recipient=self._grader,
|
|
744
|
+
request_reply=True,
|
|
745
|
+
silent=self.silent,
|
|
746
|
+
)
|
|
747
|
+
rating: str = ""
|
|
748
|
+
last_message: dict[str, Any] | None = self._grader.last_message()
|
|
749
|
+
if last_message is not None:
|
|
750
|
+
rating = last_message["content"].strip()
|
|
751
|
+
|
|
752
|
+
# Extract ratings and details for each option
|
|
753
|
+
options_with_ratings = re.findall(r"(Option \d+:.*?Rating:\s*[\d.]+)", rating, re.DOTALL)
|
|
754
|
+
ratings = []
|
|
755
|
+
for option in options_with_ratings:
|
|
756
|
+
match = re.search(r"Rating:\s*([\d.]+)", option)
|
|
757
|
+
if match:
|
|
758
|
+
ratings.append(match.group(1))
|
|
759
|
+
|
|
760
|
+
# if the response wasn't of the expected format, return default rewards
|
|
761
|
+
if len(ratings) != len(nodes):
|
|
762
|
+
return [0.0] * len(nodes)
|
|
763
|
+
|
|
764
|
+
rewards = []
|
|
765
|
+
# Get rewards and assign rating details to corresponding nodes
|
|
766
|
+
for node, rating, details in zip(nodes, ratings, options_with_ratings):
|
|
767
|
+
if node.value > 0 and node.rating_details:
|
|
768
|
+
# we already calculated the rating for the node
|
|
769
|
+
rewards.append(node.value)
|
|
770
|
+
continue
|
|
771
|
+
node.rating_details = details
|
|
772
|
+
rewards.append((float(rating) - 1.0) / (self._rating_scale - 1.0))
|
|
773
|
+
return rewards
|
|
774
|
+
|
|
775
|
+
def execute_node(self, node: ThinkNode) -> str | None:
|
|
776
|
+
"""Execute the node's content to get the response.
|
|
777
|
+
|
|
778
|
+
This method runs the node's content to get the response.
|
|
779
|
+
If the content contains a Python code snippet, it sends the code to the user proxy agent for execution.
|
|
780
|
+
Else, it sends the content to the LLM for generating the response.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
node (ThinkNode): The node to run.
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
Optional[str]: The response generated by the node, or None if the node is TERMINATE.
|
|
787
|
+
"""
|
|
788
|
+
assert isinstance(self._executor, AssistantAgent)
|
|
789
|
+
|
|
790
|
+
if node.output is not None:
|
|
791
|
+
return node.output
|
|
792
|
+
|
|
793
|
+
if "TERMINATE" in node.content: # don't use _is_terminal, as the terminal node for beam search can be executed
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
# check for python snippet
|
|
797
|
+
if "```python" in node.content:
|
|
798
|
+
# if code execution is disabled, ask to follow a different approach
|
|
799
|
+
if not self._user_proxy:
|
|
800
|
+
return "Python code execution is disabled. Follow a different approach."
|
|
801
|
+
self._user_proxy.clear_history()
|
|
802
|
+
self.send(
|
|
803
|
+
message=node.content,
|
|
804
|
+
recipient=self._user_proxy,
|
|
805
|
+
request_reply=True,
|
|
806
|
+
silent=self.silent,
|
|
807
|
+
)
|
|
808
|
+
user_proxy_last_msg: dict[str, Any] | None = self._user_proxy.last_message(self)
|
|
809
|
+
user_proxy_last_msg_content: str = user_proxy_last_msg["content"] if user_proxy_last_msg is not None else ""
|
|
810
|
+
return user_proxy_last_msg_content
|
|
811
|
+
|
|
812
|
+
# run with the LLM
|
|
813
|
+
prompt = f"{self._lats_context}\n\n---\n\n" if self._method == "lats" else ""
|
|
814
|
+
prompt += f"Trajectory:\n{node.trajectory}\nOutput:"
|
|
815
|
+
|
|
816
|
+
self._executor.clear_history()
|
|
817
|
+
self.send(
|
|
818
|
+
message=prompt,
|
|
819
|
+
recipient=self._executor,
|
|
820
|
+
request_reply=True,
|
|
821
|
+
silent=self.silent,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
output = ""
|
|
825
|
+
last_message: dict[str, Any] | None = self._executor.last_message()
|
|
826
|
+
|
|
827
|
+
# this agent is not supposed to write Python code, so if there is a need for that ask the thinker to do so
|
|
828
|
+
if last_message is not None:
|
|
829
|
+
if "```python" in last_message["content"]:
|
|
830
|
+
output = (
|
|
831
|
+
"To execute Python code please provide the exact snippet in a fenced block like ```python ... ```."
|
|
832
|
+
)
|
|
833
|
+
else:
|
|
834
|
+
output = last_message["content"].strip()
|
|
835
|
+
|
|
836
|
+
return output
|
|
837
|
+
|
|
838
|
+
def _process_prompt(
|
|
839
|
+
self, messages: list[dict[str, Any]] | None, sender: Agent | None
|
|
840
|
+
) -> tuple[str | None, str | None]:
|
|
841
|
+
"""Process the incoming messages to extract the prompt and ground truth.
|
|
842
|
+
|
|
843
|
+
This method checks if the provided messages are None and identifies the prompt.
|
|
844
|
+
If there is only one message, it uses that as the prompt. Otherwise, it asks the question in the messages including also the important information from the previous messages.
|
|
845
|
+
It also looks for a specific keyword "GROUND_TRUTH" in any of the messages to separate the ground truth for evaluation purposes.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
messages (Optional[list[dict[str, Any]]]): A list of message dictionaries containing the content to process.
|
|
849
|
+
sender (Optional[Agent]): The agent sending the messages.
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
Tuple[Optional[str], Optional[str]]: A tuple containing the processed prompt and the ground truth.
|
|
853
|
+
If the prompt is empty, returns (None, None).
|
|
854
|
+
"""
|
|
855
|
+
messages = self._oai_messages[sender] if messages is None else messages
|
|
856
|
+
messages_copy = copy.deepcopy(messages)
|
|
857
|
+
|
|
858
|
+
# Extract the ground truth for more accurate evaluation.
|
|
859
|
+
# TODO: in the future, allow user to pass a callable (func) to calculate reward.
|
|
860
|
+
ground_truth = None
|
|
861
|
+
for i, message in enumerate(messages_copy):
|
|
862
|
+
if "GROUND_TRUTH" in message["content"]:
|
|
863
|
+
idx = message["content"].find("GROUND_TRUTH")
|
|
864
|
+
messages_copy[i]["content"], ground_truth = message["content"][:idx].rstrip(), message["content"][idx:]
|
|
865
|
+
break
|
|
866
|
+
|
|
867
|
+
if len(messages) == 1:
|
|
868
|
+
# First message, no previous context
|
|
869
|
+
prompt = messages_copy[0]["content"]
|
|
870
|
+
else:
|
|
871
|
+
rewriter_message = f"""
|
|
872
|
+
Task: Given a list of messages including a previous discussion, write a prompt that summarizes the discussion, including all the useful information, and asks a question.
|
|
873
|
+
|
|
874
|
+
**Messages:**
|
|
875
|
+
{messages_copy}
|
|
876
|
+
|
|
877
|
+
**Format of Output:**
|
|
878
|
+
QUESTION: *Write the initial question asked by the user here.*
|
|
879
|
+
SUMMARY: *summarize the existing discussions.*
|
|
880
|
+
|
|
881
|
+
ACTIVITY LOG:
|
|
882
|
+
- *Action 1 performed*
|
|
883
|
+
- *Action 2 performed*
|
|
884
|
+
- ...
|
|
885
|
+
|
|
886
|
+
CURRENT_QUESTION: *Write the current/last question to be addressed here. In case the task has been completed, write: "The task has now been completed, write the final response and terminate the task."*
|
|
887
|
+
"""
|
|
888
|
+
self._prompt_rewriter.clear_history()
|
|
889
|
+
self.send(
|
|
890
|
+
message=rewriter_message,
|
|
891
|
+
recipient=self._prompt_rewriter,
|
|
892
|
+
request_reply=True,
|
|
893
|
+
silent=self.silent,
|
|
894
|
+
)
|
|
895
|
+
last_msg: dict[str, Any] | None = self._prompt_rewriter.last_message()
|
|
896
|
+
prompt = last_msg["content"].strip() if last_msg is not None else ""
|
|
897
|
+
|
|
898
|
+
if not prompt:
|
|
899
|
+
return None, None
|
|
900
|
+
|
|
901
|
+
return prompt, ground_truth
|
|
902
|
+
|
|
903
|
+
def _beam_reply(self, prompt: str, ground_truth: str | None = None) -> str:
|
|
904
|
+
"""Generate a response using tree-of-thought reasoning.
|
|
905
|
+
|
|
906
|
+
Implements beam search through a tree of reasoning steps, using the thinker
|
|
907
|
+
agent to generate possible next steps and the grader agent to evaluate paths.
|
|
908
|
+
|
|
909
|
+
Args:
|
|
910
|
+
prompt (str): The question or prompt to generate a response for.
|
|
911
|
+
ground_truth (Optional[str]): The ground truth or correct answer for evaluation.
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
str: The generated response based on the reasoning process.
|
|
915
|
+
"""
|
|
916
|
+
root = ThinkNode(content=prompt, parent=None)
|
|
917
|
+
self._root = root # save the root node for later visualization
|
|
918
|
+
prev_leafs: list[ThinkNode] = [root]
|
|
919
|
+
final_answers: set[ThinkNode] = set() # store the final answers
|
|
920
|
+
|
|
921
|
+
while prev_leafs and len(final_answers) < self._beam_size:
|
|
922
|
+
new_leafs: list[ThinkNode] = []
|
|
923
|
+
new_leafs_per_beam: list[list[ThinkNode]] = [] # used for batch grading
|
|
924
|
+
for node in prev_leafs:
|
|
925
|
+
if self._is_terminal(node):
|
|
926
|
+
# Reached max depth; collect possible answers
|
|
927
|
+
if node.value is None:
|
|
928
|
+
node.value = self.rate_node(node, ground_truth)
|
|
929
|
+
final_answers.add(node)
|
|
930
|
+
continue
|
|
931
|
+
|
|
932
|
+
expansion_leafs = self._expand(node)
|
|
933
|
+
new_leafs += expansion_leafs
|
|
934
|
+
new_leafs_per_beam.append(expansion_leafs)
|
|
935
|
+
|
|
936
|
+
prev_leafs = new_leafs
|
|
937
|
+
|
|
938
|
+
if len(prev_leafs) + len(final_answers) > self._beam_size:
|
|
939
|
+
if len(final_answers) >= self._beam_size:
|
|
940
|
+
prev_leafs = [] # stop searching, max beam size reached
|
|
941
|
+
break
|
|
942
|
+
|
|
943
|
+
# Rate
|
|
944
|
+
if self._batch_grading:
|
|
945
|
+
for beam_nodes in new_leafs_per_beam:
|
|
946
|
+
rewards = self.rate_batch_nodes(beam_nodes, ground_truth)
|
|
947
|
+
for node, reward in zip(beam_nodes, rewards):
|
|
948
|
+
node.value = reward
|
|
949
|
+
else:
|
|
950
|
+
for node in prev_leafs:
|
|
951
|
+
node.value = self.rate_node(node, ground_truth)
|
|
952
|
+
# Beam search: keep top beam_size leaf nodes
|
|
953
|
+
prev_leafs = sorted(prev_leafs, key=lambda x: x.value if x.value else 0, reverse=True)[
|
|
954
|
+
: self._beam_size - len(final_answers)
|
|
955
|
+
]
|
|
956
|
+
|
|
957
|
+
# Execute
|
|
958
|
+
if self._interim_execution:
|
|
959
|
+
for node in prev_leafs:
|
|
960
|
+
node.output = self.execute_node(node)
|
|
961
|
+
|
|
962
|
+
assert final_answers, "No final answers found."
|
|
963
|
+
final_answers_list = list(final_answers)
|
|
964
|
+
|
|
965
|
+
if self._answer_approach == "best":
|
|
966
|
+
# Best the final answers
|
|
967
|
+
best_leaf = max(final_answers_list, key=lambda x: x.value)
|
|
968
|
+
message = f"""Given a thinking process, you have to provide a complete response to a user's question.
|
|
969
|
+
Question:
|
|
970
|
+
{prompt}
|
|
971
|
+
|
|
972
|
+
Thinking process:
|
|
973
|
+
{best_leaf.trajectory}
|
|
974
|
+
|
|
975
|
+
Final Answer:
|
|
976
|
+
"""
|
|
977
|
+
elif self._answer_approach == "pool":
|
|
978
|
+
all_thoughts = "\n\n".join([
|
|
979
|
+
f"--- Possibility {i + 1} ---\n{node.trajectory}\n" for i, node in enumerate(final_answers_list)
|
|
980
|
+
])
|
|
981
|
+
message = f"""Given a list of thinking processes, you have to provide a complete response to a user's question.
|
|
982
|
+
Question:
|
|
983
|
+
{prompt}
|
|
984
|
+
|
|
985
|
+
Thinking processes:
|
|
986
|
+
{all_thoughts}
|
|
987
|
+
|
|
988
|
+
Final Answer:
|
|
989
|
+
"""
|
|
990
|
+
self.send(
|
|
991
|
+
message=message,
|
|
992
|
+
recipient=self,
|
|
993
|
+
request_reply=True,
|
|
994
|
+
silent=self.silent,
|
|
995
|
+
)
|
|
996
|
+
last_msg: dict[str, Any] | None = self.last_message(self)
|
|
997
|
+
final_answer: str = last_msg["content"].strip() if last_msg is not None else ""
|
|
998
|
+
return final_answer
|
|
999
|
+
|
|
1000
|
+
def _mcts_reply(self, prompt: str, ground_truth: str | None = None) -> str:
|
|
1001
|
+
"""Generate a response using Monte Carlo Tree Search (MCTS) reasoning.
|
|
1002
|
+
|
|
1003
|
+
Args:
|
|
1004
|
+
prompt (str): The question or prompt to generate a response for.
|
|
1005
|
+
ground_truth (Optional[str]): The ground truth or correct answer for evaluation.
|
|
1006
|
+
|
|
1007
|
+
Returns:
|
|
1008
|
+
str: The generated response based on the reasoning process.
|
|
1009
|
+
"""
|
|
1010
|
+
root = ThinkNode(content=prompt, parent=None)
|
|
1011
|
+
self._root = root
|
|
1012
|
+
answer_nodes: list[ThinkNode] = []
|
|
1013
|
+
|
|
1014
|
+
self._lats_context = "## Here are some previous trajectories and reflections\n\n" # Store LATS's reflections
|
|
1015
|
+
|
|
1016
|
+
# TODO: future, parallelism with Swarm agent or AsyncOpenAI client.
|
|
1017
|
+
for _ in range(self._nsim):
|
|
1018
|
+
node = root
|
|
1019
|
+
|
|
1020
|
+
# Selection
|
|
1021
|
+
while not self._is_terminal(node) and len(node.children) > 0:
|
|
1022
|
+
choices_weights = [
|
|
1023
|
+
(child.value / (child.visits + EPSILON))
|
|
1024
|
+
+ self._exploration_constant
|
|
1025
|
+
* math.sqrt(2 * math.log(node.visits + EPSILON) / (child.visits + EPSILON))
|
|
1026
|
+
for child in node.children
|
|
1027
|
+
]
|
|
1028
|
+
node = node.children[choices_weights.index(max(choices_weights))]
|
|
1029
|
+
|
|
1030
|
+
# Execution
|
|
1031
|
+
if self._interim_execution:
|
|
1032
|
+
node.output = self.execute_node(node)
|
|
1033
|
+
|
|
1034
|
+
# Expansion and Simulation
|
|
1035
|
+
while not self._is_terminal(node):
|
|
1036
|
+
if len(node.children) == 0:
|
|
1037
|
+
self._expand(node)
|
|
1038
|
+
if len(node.children) == 0:
|
|
1039
|
+
node.content += "\nTERMINATE"
|
|
1040
|
+
break
|
|
1041
|
+
node = random.choice(node.children)
|
|
1042
|
+
|
|
1043
|
+
# Execution
|
|
1044
|
+
if self._interim_execution:
|
|
1045
|
+
node.output = self.execute_node(node)
|
|
1046
|
+
|
|
1047
|
+
# Add answer (leaf) node and evaluate answer
|
|
1048
|
+
self.send(
|
|
1049
|
+
message=f"""Given a thinking process, you have to provide a complete response to a user's question.
|
|
1050
|
+
Question:
|
|
1051
|
+
{prompt}
|
|
1052
|
+
|
|
1053
|
+
Thinking process:
|
|
1054
|
+
{node.trajectory}
|
|
1055
|
+
|
|
1056
|
+
Final Answer:
|
|
1057
|
+
""",
|
|
1058
|
+
recipient=self,
|
|
1059
|
+
request_reply=True,
|
|
1060
|
+
silent=self.silent,
|
|
1061
|
+
)
|
|
1062
|
+
last_msg: dict[str, Any] | None = self.last_message(self)
|
|
1063
|
+
_answer: str = last_msg["content"].strip() if last_msg is not None else ""
|
|
1064
|
+
_ans_node = ThinkNode(content=_answer, parent=node)
|
|
1065
|
+
reward = self.rate_node(_ans_node, ground_truth, is_outcome=True)
|
|
1066
|
+
_ans_node.value = reward
|
|
1067
|
+
answer_nodes.append(_ans_node)
|
|
1068
|
+
self._lats_context += f"### Previous Tries:\n{node.trajectory}\n\nRating:{_ans_node.rating_details}\n\n"
|
|
1069
|
+
node.backpropagate(reward)
|
|
1070
|
+
|
|
1071
|
+
best_ans_node = max(answer_nodes, key=lambda node: node.value)
|
|
1072
|
+
return best_ans_node.content
|
|
1073
|
+
|
|
1074
|
+
def _expand(self, node: ThinkNode) -> list[ThinkNode]:
|
|
1075
|
+
"""Expand the node by generating possible next steps based on the current trajectory.
|
|
1076
|
+
|
|
1077
|
+
This method sends a message to the thinker agent, asking for possible next steps
|
|
1078
|
+
that can be taken from the current node's trajectory. It processes the response to
|
|
1079
|
+
extract the options provided by the thinker and creates new ThinkNode instances
|
|
1080
|
+
for each option.
|
|
1081
|
+
|
|
1082
|
+
Args:
|
|
1083
|
+
node (ThinkNode): The node to expand, representing the current state in the reasoning process.
|
|
1084
|
+
|
|
1085
|
+
Returns:
|
|
1086
|
+
list[ThinkNode]: A list of new ThinkNode instances created from the options provided by the thinker.
|
|
1087
|
+
"""
|
|
1088
|
+
self._thinker.clear_history()
|
|
1089
|
+
|
|
1090
|
+
if self._method == "lats":
|
|
1091
|
+
prompt = (
|
|
1092
|
+
self._lats_context
|
|
1093
|
+
+ "\n\n---\n\n"
|
|
1094
|
+
+ f"{node.trajectory}\n---\nHow should the thinking process continue?"
|
|
1095
|
+
)
|
|
1096
|
+
else:
|
|
1097
|
+
prompt = f"{node.trajectory}\n---\nHow should the thinking process continue?"
|
|
1098
|
+
|
|
1099
|
+
self.send(
|
|
1100
|
+
message=prompt,
|
|
1101
|
+
recipient=self._thinker,
|
|
1102
|
+
request_reply=True,
|
|
1103
|
+
silent=self.silent,
|
|
1104
|
+
)
|
|
1105
|
+
last_msg: dict[str, Any] | None = self._thinker.last_message()
|
|
1106
|
+
reply: str = last_msg["content"].strip() if last_msg is not None else ""
|
|
1107
|
+
reflection = re.findall(r"REFLECTION:\s*(.+?)(?=\*\*Possible Options:\*\*|Option \d+:|$)", reply, re.DOTALL)
|
|
1108
|
+
if reflection:
|
|
1109
|
+
node.reflection += str(reflection[0].strip())
|
|
1110
|
+
options = re.findall(r"Option \d+:(.+?)(?=Option \d+:|$)", reply, re.DOTALL)
|
|
1111
|
+
|
|
1112
|
+
option_nodes = [ThinkNode(content=option.strip().rstrip(), parent=node) for option in options]
|
|
1113
|
+
|
|
1114
|
+
return option_nodes
|
|
1115
|
+
|
|
1116
|
+
def _is_terminal(self, node: ThinkNode) -> bool:
|
|
1117
|
+
"""Check if the node is a terminal state in the reasoning process.
|
|
1118
|
+
|
|
1119
|
+
Args:
|
|
1120
|
+
node (ThinkNode): The node to check for terminal state.
|
|
1121
|
+
|
|
1122
|
+
Returns:
|
|
1123
|
+
bool: True if the node is terminal, False otherwise.
|
|
1124
|
+
"""
|
|
1125
|
+
return node.depth >= self._max_depth or "TERMINATE" in node.content
|
|
1126
|
+
|
|
1127
|
+
@property
|
|
1128
|
+
def method(self) -> str:
|
|
1129
|
+
"""Get the reasoning method being used.
|
|
1130
|
+
|
|
1131
|
+
Returns:
|
|
1132
|
+
str: The name of the reasoning method
|
|
1133
|
+
"""
|
|
1134
|
+
return self._method
|
|
1135
|
+
|
|
1136
|
+
def visualize_tree(self) -> None:
|
|
1137
|
+
"""Visualize the tree of thoughts using graphviz.
|
|
1138
|
+
|
|
1139
|
+
Raises:
|
|
1140
|
+
RuntimeError: If the tree has not been generated yet.
|
|
1141
|
+
"""
|
|
1142
|
+
if self._root:
|
|
1143
|
+
self._root.visualize_tree()
|
|
1144
|
+
else:
|
|
1145
|
+
raise RuntimeError("No tree to visualize. Run the reasoning process first.")
|
|
1146
|
+
|
|
1147
|
+
def extract_sft_dataset(self) -> list[dict[str, Any]]:
|
|
1148
|
+
"""Extract the best trajectory or multiple equally good trajectories for SFT training.
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
list[dict]: list of best trajectories, each one is a pair of instruction and response.
|
|
1152
|
+
|
|
1153
|
+
Raises:
|
|
1154
|
+
RuntimeError: If the tree has not been generated yet.
|
|
1155
|
+
"""
|
|
1156
|
+
if self._root:
|
|
1157
|
+
return extract_sft_dataset(self._root)
|
|
1158
|
+
else:
|
|
1159
|
+
raise RuntimeError("No tree to extract dataset from. Run the reasoning process first.")
|
|
1160
|
+
|
|
1161
|
+
def extract_rlhf_preference_dataset(self, contrastive_threshold: float = 0.2) -> list[dict[str, Any]]:
|
|
1162
|
+
"""Extract and generate preference pairs for RLHF training by comparing sibling nodes.
|
|
1163
|
+
|
|
1164
|
+
Args:
|
|
1165
|
+
contrastive_threshold (float): between (0, 1), a distance measure that we are confident to call
|
|
1166
|
+
one is positive and another is negative.
|
|
1167
|
+
|
|
1168
|
+
Returns:
|
|
1169
|
+
list[dict]: list of preference pairs, where each pair contains two responses and
|
|
1170
|
+
indicates which one is preferred.
|
|
1171
|
+
|
|
1172
|
+
Raises:
|
|
1173
|
+
RuntimeError: If the tree has not been generated yet.
|
|
1174
|
+
"""
|
|
1175
|
+
if self._root:
|
|
1176
|
+
return extract_rlhf_preference_dataset(self._root, contrastive_threshold)
|
|
1177
|
+
else:
|
|
1178
|
+
raise RuntimeError("No tree to extract dataset from. Run the reasoning process first.")
|