massgen 0.0.3__py3-none-any.whl → 0.1.0a1__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 massgen might be problematic. Click here for more details.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1504 -287
- massgen/config_builder.py +2165 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0a1.dist-info/METADATA +1287 -0
- massgen-0.1.0a1.dist-info/RECORD +273 -0
- massgen-0.1.0a1.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0a1.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0a1.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0a1.dist-info}/top_level.txt +0 -0
massgen/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
3
|
MassGen - Multi-Agent System Generator (Foundation Release)
|
|
3
4
|
|
|
@@ -46,33 +47,166 @@ Usage:
|
|
|
46
47
|
print(chunk.content, end="")
|
|
47
48
|
"""
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
from .backend.response import ResponseBackend
|
|
50
|
+
from .agent_config import AgentConfig
|
|
51
51
|
from .backend.claude import ClaudeBackend
|
|
52
52
|
from .backend.gemini import GeminiBackend
|
|
53
53
|
from .backend.grok import GrokBackend
|
|
54
|
+
from .backend.inference import InferenceBackend
|
|
55
|
+
from .backend.lmstudio import LMStudioBackend
|
|
56
|
+
|
|
57
|
+
# Import main classes for convenience
|
|
58
|
+
from .backend.response import ResponseBackend
|
|
54
59
|
from .chat_agent import (
|
|
55
60
|
ChatAgent,
|
|
56
|
-
SingleAgent,
|
|
57
61
|
ConfigurableAgent,
|
|
58
|
-
|
|
62
|
+
SingleAgent,
|
|
63
|
+
create_computational_agent,
|
|
59
64
|
create_expert_agent,
|
|
60
65
|
create_research_agent,
|
|
61
|
-
|
|
66
|
+
create_simple_agent,
|
|
62
67
|
)
|
|
63
|
-
from .orchestrator import Orchestrator, create_orchestrator
|
|
64
68
|
from .message_templates import MessageTemplates, get_templates
|
|
65
|
-
from .
|
|
69
|
+
from .orchestrator import Orchestrator, create_orchestrator
|
|
66
70
|
|
|
67
|
-
__version__ = "0.
|
|
71
|
+
__version__ = "0.1.0a1"
|
|
68
72
|
__author__ = "MassGen Contributors"
|
|
69
73
|
|
|
74
|
+
|
|
75
|
+
# Python API for programmatic usage
|
|
76
|
+
async def run(
|
|
77
|
+
query: str,
|
|
78
|
+
config: str = None,
|
|
79
|
+
model: str = None,
|
|
80
|
+
**kwargs,
|
|
81
|
+
) -> dict:
|
|
82
|
+
"""Run MassGen query programmatically.
|
|
83
|
+
|
|
84
|
+
This is an async wrapper around MassGen's CLI logic, providing a simple
|
|
85
|
+
Python API for programmatic usage.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
query: Question or task for the agent(s)
|
|
89
|
+
config: Config file path or @examples/NAME (optional)
|
|
90
|
+
model: Quick single-agent mode with model name (optional)
|
|
91
|
+
**kwargs: Additional configuration options
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict: Result with 'final_answer' and metadata:
|
|
95
|
+
{
|
|
96
|
+
'final_answer': str, # The generated answer
|
|
97
|
+
'winning_agent_id': str, # ID of winning agent (multi-agent)
|
|
98
|
+
'config_used': str, # Path to config used
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
# Single agent with model
|
|
103
|
+
>>> result = await massgen.run(
|
|
104
|
+
... query="What is machine learning?",
|
|
105
|
+
... model="gpt-5-mini"
|
|
106
|
+
... )
|
|
107
|
+
>>> print(result['final_answer'])
|
|
108
|
+
|
|
109
|
+
# Multi-agent with config
|
|
110
|
+
>>> result = await massgen.run(
|
|
111
|
+
... query="Compare renewable energy sources",
|
|
112
|
+
... config="@examples/basic_multi"
|
|
113
|
+
... )
|
|
114
|
+
|
|
115
|
+
# Use default config (from first-run setup)
|
|
116
|
+
>>> result = await massgen.run("Your question")
|
|
117
|
+
|
|
118
|
+
Note:
|
|
119
|
+
MassGen is async by nature. Use `asyncio.run()` if calling from sync code:
|
|
120
|
+
>>> import asyncio
|
|
121
|
+
>>> result = asyncio.run(massgen.run("Question", model="gpt-5"))
|
|
122
|
+
"""
|
|
123
|
+
from pathlib import Path
|
|
124
|
+
|
|
125
|
+
from .cli import (
|
|
126
|
+
create_agents_from_config,
|
|
127
|
+
create_simple_config,
|
|
128
|
+
load_config_file,
|
|
129
|
+
resolve_config_path,
|
|
130
|
+
run_single_question,
|
|
131
|
+
)
|
|
132
|
+
from .utils import get_backend_type_from_model
|
|
133
|
+
|
|
134
|
+
# Determine config to use
|
|
135
|
+
if config:
|
|
136
|
+
resolved_path = resolve_config_path(config)
|
|
137
|
+
if resolved_path is None:
|
|
138
|
+
raise ValueError("Could not resolve config path. Use --init to create default config.")
|
|
139
|
+
config_dict = load_config_file(str(resolved_path))
|
|
140
|
+
config_path_used = str(resolved_path)
|
|
141
|
+
elif model:
|
|
142
|
+
# Quick single-agent mode
|
|
143
|
+
backend_type = get_backend_type_from_model(model)
|
|
144
|
+
# Create headless UI config for programmatic API usage
|
|
145
|
+
headless_ui_config = {
|
|
146
|
+
"display_type": "simple",
|
|
147
|
+
"logging_enabled": False,
|
|
148
|
+
}
|
|
149
|
+
config_dict = create_simple_config(
|
|
150
|
+
backend_type=backend_type,
|
|
151
|
+
model=model,
|
|
152
|
+
system_message=kwargs.get("system_message"),
|
|
153
|
+
base_url=kwargs.get("base_url"),
|
|
154
|
+
ui_config=headless_ui_config,
|
|
155
|
+
)
|
|
156
|
+
config_path_used = f"single-agent:{model}"
|
|
157
|
+
else:
|
|
158
|
+
# Try default config
|
|
159
|
+
default_config = Path.home() / ".config/massgen/config.yaml"
|
|
160
|
+
if default_config.exists():
|
|
161
|
+
config_dict = load_config_file(str(default_config))
|
|
162
|
+
config_path_used = str(default_config)
|
|
163
|
+
else:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"No config specified and no default config found.\n" "Run `massgen --init` to create a default configuration.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Extract orchestrator config
|
|
169
|
+
orchestrator_cfg = config_dict.get("orchestrator", {})
|
|
170
|
+
|
|
171
|
+
# Create agents
|
|
172
|
+
agents = create_agents_from_config(config_dict, orchestrator_cfg)
|
|
173
|
+
if not agents:
|
|
174
|
+
raise ValueError("No agents configured")
|
|
175
|
+
|
|
176
|
+
# Force headless UI config for programmatic API usage
|
|
177
|
+
# Override any UI settings from the config file to ensure non-interactive operation
|
|
178
|
+
ui_config = {
|
|
179
|
+
"display_type": "simple", # Headless mode for API usage
|
|
180
|
+
"logging_enabled": False, # Quiet for API usage
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Run the query
|
|
184
|
+
answer = await run_single_question(
|
|
185
|
+
query,
|
|
186
|
+
agents,
|
|
187
|
+
ui_config,
|
|
188
|
+
orchestrator=orchestrator_cfg,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Build result dict
|
|
192
|
+
result = {
|
|
193
|
+
"final_answer": answer,
|
|
194
|
+
"config_used": config_path_used,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
|
|
70
200
|
__all__ = [
|
|
201
|
+
# Python API
|
|
202
|
+
"run",
|
|
71
203
|
# Backends
|
|
72
204
|
"ResponseBackend",
|
|
73
205
|
"ClaudeBackend",
|
|
74
206
|
"GeminiBackend",
|
|
75
207
|
"GrokBackend",
|
|
208
|
+
"LMStudioBackend",
|
|
209
|
+
"InferenceBackend",
|
|
76
210
|
# Agents
|
|
77
211
|
"ChatAgent",
|
|
78
212
|
"SingleAgent",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
External agent adapters for MassGen.
|
|
4
|
+
|
|
5
|
+
This package provides adapters for integrating various external agent
|
|
6
|
+
frameworks and systems into MassGen's orchestration system.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Dict, Type
|
|
9
|
+
|
|
10
|
+
from .base import AgentAdapter
|
|
11
|
+
|
|
12
|
+
# Adapter registry maps framework names to adapter classes
|
|
13
|
+
adapter_registry: Dict[str, Type[AgentAdapter]] = {}
|
|
14
|
+
|
|
15
|
+
# Try to import AG2 adapter (optional dependency)
|
|
16
|
+
try:
|
|
17
|
+
from .ag2_adapter import AG2Adapter
|
|
18
|
+
|
|
19
|
+
adapter_registry["ag2"] = AG2Adapter
|
|
20
|
+
adapter_registry["autogen"] = AG2Adapter # Alias for backward compatibility
|
|
21
|
+
except ImportError:
|
|
22
|
+
# AG2 not installed, skip registration
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AgentAdapter",
|
|
28
|
+
"adapter_registry",
|
|
29
|
+
]
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
AG2 (AutoGen) adapter for MassGen.
|
|
4
|
+
|
|
5
|
+
Supports both single agents and GroupChat configurations.
|
|
6
|
+
"""
|
|
7
|
+
# Suppress autogen deprecation warnings
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import Any, AsyncGenerator, Dict, List
|
|
10
|
+
|
|
11
|
+
from massgen.logger_config import log_backend_activity, logger
|
|
12
|
+
|
|
13
|
+
from ..utils import CoordinationStage
|
|
14
|
+
from .base import AgentAdapter, StreamChunk
|
|
15
|
+
from .utils.ag2_utils import (
|
|
16
|
+
create_llm_config,
|
|
17
|
+
get_group_initial_message,
|
|
18
|
+
get_user_agent_default_description,
|
|
19
|
+
get_user_agent_default_system_message,
|
|
20
|
+
get_user_agent_tool_call_message,
|
|
21
|
+
postprocess_group_chat_results,
|
|
22
|
+
register_tools_for_agent,
|
|
23
|
+
setup_agent_from_config,
|
|
24
|
+
setup_api_keys,
|
|
25
|
+
unregister_tools_for_agent,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="autogen")
|
|
29
|
+
warnings.filterwarnings("ignore", message=".*jsonschema.*")
|
|
30
|
+
warnings.filterwarnings("ignore", message=".*Pydantic.*")
|
|
31
|
+
|
|
32
|
+
from autogen import ConversableAgent # noqa: E402
|
|
33
|
+
from autogen.agentchat import a_run_group_chat # noqa: E402
|
|
34
|
+
from autogen.agentchat.group.patterns import AutoPattern # noqa: E402
|
|
35
|
+
|
|
36
|
+
DEFAULT_MAX_ROUNDS = 20
|
|
37
|
+
SUPPORTED_GROUPCHAT_PATTERNS = ["auto"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AG2Adapter(AgentAdapter):
|
|
41
|
+
"""
|
|
42
|
+
Adapter for AG2 (AutoGen) framework.
|
|
43
|
+
|
|
44
|
+
Supports:
|
|
45
|
+
- Single AG2 agents (ConversableAgent, AssistantAgent)
|
|
46
|
+
- Function/tool calling
|
|
47
|
+
- Code execution with multiple executor types:
|
|
48
|
+
* LocalCommandLineCodeExecutor (local shell)
|
|
49
|
+
* DockerCommandLineCodeExecutor (Docker containers)
|
|
50
|
+
* JupyterCodeExecutor (Jupyter kernels)
|
|
51
|
+
* YepCodeCodeExecutor (YepCode serverless)
|
|
52
|
+
- Async execution with a_generate_reply
|
|
53
|
+
- No human-in-the-loop (autonomous operation)
|
|
54
|
+
|
|
55
|
+
Todos:
|
|
56
|
+
- Group chat support with patterns (e.g., AutoPattern, DefaultPattern, etc.)
|
|
57
|
+
- More tool support including MCP
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, **kwargs):
|
|
61
|
+
"""
|
|
62
|
+
Initialize AG2 adapter.
|
|
63
|
+
|
|
64
|
+
The adapter receives the entire backend configuration from MassGen.
|
|
65
|
+
It should contain EITHER 'agent_config' OR 'group_config' (not both).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
**kwargs: Backend configuration containing either:
|
|
69
|
+
- agent_config: Configuration for single AG2 agent
|
|
70
|
+
- group_config: Configuration for AG2 GroupChat
|
|
71
|
+
"""
|
|
72
|
+
super().__init__(**kwargs)
|
|
73
|
+
|
|
74
|
+
# Set up API keys for AG2 compatibility
|
|
75
|
+
setup_api_keys()
|
|
76
|
+
|
|
77
|
+
# Extract agent_config or group_config from kwargs
|
|
78
|
+
self.agent_config = kwargs.get("agent_config")
|
|
79
|
+
self.group_config = kwargs.get("group_config")
|
|
80
|
+
|
|
81
|
+
# Validate that we have exactly one of them
|
|
82
|
+
if self.agent_config and self.group_config:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"Backend configuration should contain EITHER 'agent_config' OR 'group_config', not both.",
|
|
85
|
+
)
|
|
86
|
+
if not self.agent_config and not self.group_config:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"Backend configuration must contain either 'agent_config' for single agent " "or 'group_config' for GroupChat.",
|
|
89
|
+
)
|
|
90
|
+
self.agent_id = None
|
|
91
|
+
# Initialize AG2 components
|
|
92
|
+
self._setup_agents()
|
|
93
|
+
|
|
94
|
+
def _setup_agents(self):
|
|
95
|
+
"""Set up AG2 agents based on configuration."""
|
|
96
|
+
if self.group_config:
|
|
97
|
+
# GroupChat setup
|
|
98
|
+
self._setup_group_chat()
|
|
99
|
+
else:
|
|
100
|
+
# Single agent setup
|
|
101
|
+
self._setup_single_agent()
|
|
102
|
+
|
|
103
|
+
def _setup_single_agent(self):
|
|
104
|
+
"""Set up a single AG2 agent."""
|
|
105
|
+
self.agent = setup_agent_from_config(self.agent_config)
|
|
106
|
+
|
|
107
|
+
self.is_group_chat = False
|
|
108
|
+
|
|
109
|
+
def _setup_group_chat(self):
|
|
110
|
+
"""Set up AG2 GroupChat with multiple agents and pattern."""
|
|
111
|
+
# Validate group_config has required fields
|
|
112
|
+
if "pattern" not in self.group_config:
|
|
113
|
+
raise ValueError("group_config must include 'pattern' configuration")
|
|
114
|
+
|
|
115
|
+
# Get default llm_config from group_config (required)
|
|
116
|
+
self.default_llm_config = self.group_config.get("llm_config")
|
|
117
|
+
if not self.default_llm_config:
|
|
118
|
+
raise ValueError("group_config must include 'llm_config' as default for all agents")
|
|
119
|
+
|
|
120
|
+
# Create sub-agents from configuration
|
|
121
|
+
agents = []
|
|
122
|
+
agent_name_map = {}
|
|
123
|
+
|
|
124
|
+
for agent_cfg in self.group_config.get("agents", []):
|
|
125
|
+
agent = setup_agent_from_config(agent_cfg, default_llm_config=self.default_llm_config)
|
|
126
|
+
agents.append(agent)
|
|
127
|
+
agent_name_map[agent.name] = agent
|
|
128
|
+
|
|
129
|
+
if not agents:
|
|
130
|
+
raise ValueError("No valid agents configured for group chat")
|
|
131
|
+
|
|
132
|
+
# Get pattern configuration
|
|
133
|
+
pattern_config = self.group_config["pattern"]
|
|
134
|
+
pattern_type = pattern_config.get("type")
|
|
135
|
+
|
|
136
|
+
if not pattern_type:
|
|
137
|
+
raise ValueError("pattern configuration must include 'type' field")
|
|
138
|
+
|
|
139
|
+
if pattern_type not in SUPPORTED_GROUPCHAT_PATTERNS:
|
|
140
|
+
raise NotImplementedError(
|
|
141
|
+
f"Pattern type '{pattern_type}' not supported. Supported types: {', '.join(SUPPORTED_GROUPCHAT_PATTERNS)}",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Set up user_agent
|
|
145
|
+
self.user_agent = self._setup_user_agent(
|
|
146
|
+
user_agent_config=self.group_config.get("user_agent"),
|
|
147
|
+
default_llm_config=self.default_llm_config,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Set up group_manager_args
|
|
151
|
+
group_manager_args = self._setup_group_manager_args(pattern_config, self.default_llm_config)
|
|
152
|
+
|
|
153
|
+
# Create pattern based on type
|
|
154
|
+
self.pattern = self._create_pattern(pattern_type, pattern_config, agents, agent_name_map, group_manager_args)
|
|
155
|
+
|
|
156
|
+
# Store agents and pattern info
|
|
157
|
+
self.agents = agents
|
|
158
|
+
self.group_max_rounds = self.group_config.get("max_rounds", DEFAULT_MAX_ROUNDS)
|
|
159
|
+
self.is_group_chat = True
|
|
160
|
+
|
|
161
|
+
logger.info(f"[AG2Adapter] GroupChat setup complete with {len(agents)} agents and {pattern_type} pattern")
|
|
162
|
+
|
|
163
|
+
def _setup_user_agent(self, user_agent_config: Any, default_llm_config: Any) -> ConversableAgent:
|
|
164
|
+
"""
|
|
165
|
+
Set up user_agent for group chat.
|
|
166
|
+
|
|
167
|
+
User agent makes final decisions and calls workflow tools.
|
|
168
|
+
Its name MUST be "User" for termination condition to work.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
user_agent_config: Optional user agent configuration from YAML
|
|
172
|
+
default_llm_config: Default llm_config to use if not specified
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
ConversableAgent with name "User"
|
|
176
|
+
"""
|
|
177
|
+
if user_agent_config:
|
|
178
|
+
# User provided custom user_agent configuration
|
|
179
|
+
user_agent = setup_agent_from_config(user_agent_config, default_llm_config=default_llm_config)
|
|
180
|
+
|
|
181
|
+
# Validate name is "User"
|
|
182
|
+
if user_agent.name != "User":
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"user_agent name must be 'User', got '{user_agent.name}' for termination condition to work",
|
|
185
|
+
)
|
|
186
|
+
return user_agent
|
|
187
|
+
else:
|
|
188
|
+
# Create default user_agent
|
|
189
|
+
return ConversableAgent(
|
|
190
|
+
name="User",
|
|
191
|
+
system_message=get_user_agent_default_system_message(),
|
|
192
|
+
description=get_user_agent_default_description(),
|
|
193
|
+
human_input_mode="NEVER",
|
|
194
|
+
code_execution_config=False,
|
|
195
|
+
llm_config=create_llm_config(default_llm_config),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _setup_group_manager_args(self, pattern_config: Dict[str, Any], default_llm_config: Any) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Set up group_manager_args for pattern.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
pattern_config: Pattern configuration from YAML
|
|
204
|
+
default_llm_config: Default llm_config to use if not specified
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dict with llm_config and termination condition
|
|
208
|
+
"""
|
|
209
|
+
group_manager_args = pattern_config.get("group_manager_args", {})
|
|
210
|
+
|
|
211
|
+
# Ensure group_manager_args has llm_config (use default if not provided)
|
|
212
|
+
if "llm_config" not in group_manager_args:
|
|
213
|
+
group_manager_args["llm_config"] = create_llm_config(default_llm_config)
|
|
214
|
+
else:
|
|
215
|
+
group_manager_args["llm_config"] = create_llm_config(group_manager_args["llm_config"])
|
|
216
|
+
|
|
217
|
+
# Add termination condition: terminate when User says "TERMINATE"
|
|
218
|
+
group_manager_args["is_termination_msg"] = lambda msg: (msg.get("name") == self.user_agent.name and "TERMINATE" in msg.get("content", ""))
|
|
219
|
+
|
|
220
|
+
return group_manager_args
|
|
221
|
+
|
|
222
|
+
def _create_pattern(
|
|
223
|
+
self,
|
|
224
|
+
pattern_type: str,
|
|
225
|
+
pattern_config: Dict[str, Any],
|
|
226
|
+
agents: List[ConversableAgent],
|
|
227
|
+
agent_name_map: Dict[str, ConversableAgent],
|
|
228
|
+
group_manager_args: Dict[str, Any],
|
|
229
|
+
*args,
|
|
230
|
+
) -> Any:
|
|
231
|
+
"""
|
|
232
|
+
Create AG2 pattern based on type.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
pattern_type: Type of pattern (currently only "auto")
|
|
236
|
+
pattern_config: Pattern configuration from YAML
|
|
237
|
+
agents: List of expert agents
|
|
238
|
+
agent_name_map: Mapping from agent names to agent objects
|
|
239
|
+
group_manager_args: Group manager configuration
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Pattern instance (AutoPattern)
|
|
243
|
+
"""
|
|
244
|
+
# Get initial agent
|
|
245
|
+
initial_agent_name = pattern_config.get("initial_agent")
|
|
246
|
+
if not initial_agent_name:
|
|
247
|
+
raise ValueError("initial_agent must be specified in pattern configuration")
|
|
248
|
+
|
|
249
|
+
if initial_agent_name not in agent_name_map:
|
|
250
|
+
raise ValueError(f"initial_agent '{initial_agent_name}' not found in agents list")
|
|
251
|
+
|
|
252
|
+
initial_agent = agent_name_map[initial_agent_name]
|
|
253
|
+
|
|
254
|
+
# Extract extra pattern-specific arguments
|
|
255
|
+
extra_args = {k: v for k, v in pattern_config.items() if k not in ["type", "initial_agent", "group_manager_args"]}
|
|
256
|
+
|
|
257
|
+
# Create pattern
|
|
258
|
+
if pattern_type == "auto":
|
|
259
|
+
return AutoPattern(
|
|
260
|
+
initial_agent=initial_agent,
|
|
261
|
+
agents=agents,
|
|
262
|
+
user_agent=self.user_agent,
|
|
263
|
+
group_manager_args=group_manager_args,
|
|
264
|
+
**extra_args,
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
raise NotImplementedError(f"Pattern type '{pattern_type}' not supported")
|
|
268
|
+
|
|
269
|
+
async def _execute_single_agent(self, messages: List[Dict[str, Any]], agent: ConversableAgent) -> AsyncGenerator[StreamChunk, None]:
|
|
270
|
+
"""
|
|
271
|
+
Execute single AG2 agent.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
messages: Conversation messages
|
|
275
|
+
agent_id: Agent ID for logging
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Tuple of (content, tool_calls)
|
|
279
|
+
"""
|
|
280
|
+
result = await agent.a_generate_reply(messages)
|
|
281
|
+
|
|
282
|
+
# Extract content and tool_calls from AG2 response
|
|
283
|
+
# MassGen and AG2 use same format for tool calls
|
|
284
|
+
content = result.get("content", "") if isinstance(result, dict) else str(result)
|
|
285
|
+
tool_calls = result.get("tool_calls") if isinstance(result, dict) else None
|
|
286
|
+
|
|
287
|
+
# Log extracted data
|
|
288
|
+
log_backend_activity(
|
|
289
|
+
"ag2",
|
|
290
|
+
"Received response data from AG2",
|
|
291
|
+
{
|
|
292
|
+
"has_content": bool(content),
|
|
293
|
+
"content_length": len(content) if content else 0,
|
|
294
|
+
"has_tool_calls": bool(tool_calls),
|
|
295
|
+
"tool_count": len(tool_calls) if tool_calls else 0,
|
|
296
|
+
},
|
|
297
|
+
agent_id=self.agent_id,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Use base class simulate_streaming method
|
|
301
|
+
async for chunk in self.simulate_streaming(content, tool_calls):
|
|
302
|
+
yield chunk
|
|
303
|
+
|
|
304
|
+
async def _execute_group_chat(self, messages: List[Dict[str, Any]]) -> AsyncGenerator[str, None]:
|
|
305
|
+
"""
|
|
306
|
+
Execute AG2 group chat with pattern.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
messages: Conversation messages
|
|
310
|
+
agent_id: Agent ID for logging
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Tuple of (content, tool_calls)
|
|
314
|
+
"""
|
|
315
|
+
# Add name field to all messages so the conversation will start correctly from user agent
|
|
316
|
+
for message in messages:
|
|
317
|
+
message["name"] = "User"
|
|
318
|
+
|
|
319
|
+
# The pattern will coordinate agents until user_agent generates tool_calls
|
|
320
|
+
response = await a_run_group_chat(
|
|
321
|
+
pattern=self.pattern,
|
|
322
|
+
messages=messages,
|
|
323
|
+
max_rounds=self.group_max_rounds,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
last_group_chat_event_msgs = [] # Store messages from the last event
|
|
327
|
+
|
|
328
|
+
def process_and_log_event(*args, **kwargs) -> None:
|
|
329
|
+
"""Process and log AG2 event, returning string representation."""
|
|
330
|
+
line = " ".join(str(arg) for arg in args)
|
|
331
|
+
last_group_chat_event_msgs.append(line)
|
|
332
|
+
|
|
333
|
+
async for event in response.events:
|
|
334
|
+
last_group_chat_event_msgs.clear()
|
|
335
|
+
event.print(f=process_and_log_event)
|
|
336
|
+
formatted_message = "\n".join(last_group_chat_event_msgs)
|
|
337
|
+
|
|
338
|
+
log_backend_activity(
|
|
339
|
+
"ag2",
|
|
340
|
+
"Received response from AG2",
|
|
341
|
+
{"message": formatted_message},
|
|
342
|
+
agent_id=self.agent_id,
|
|
343
|
+
)
|
|
344
|
+
yield formatted_message
|
|
345
|
+
|
|
346
|
+
async def _execute_group_chat_with_user_agent(self, messages: List[Dict[str, Any]]) -> AsyncGenerator[StreamChunk, None]:
|
|
347
|
+
messages_to_execute = []
|
|
348
|
+
if self.coordination_stage == CoordinationStage.INITIAL_ANSWER:
|
|
349
|
+
# Todo: should make ag2 integration stateful and put this in reset_state
|
|
350
|
+
self.user_agent.update_system_message(get_user_agent_default_system_message())
|
|
351
|
+
|
|
352
|
+
messages[0] = get_group_initial_message()
|
|
353
|
+
async for event_msg in self._execute_group_chat(messages):
|
|
354
|
+
yield StreamChunk(type="content", content=event_msg)
|
|
355
|
+
results = list(self.user_agent._oai_messages.values())[0]
|
|
356
|
+
|
|
357
|
+
self.user_agent.update_system_message(get_user_agent_tool_call_message())
|
|
358
|
+
register_tools_for_agent(self.workflow_tools, self.user_agent)
|
|
359
|
+
|
|
360
|
+
messages_to_execute = postprocess_group_chat_results(results)
|
|
361
|
+
|
|
362
|
+
elif self.coordination_stage == CoordinationStage.ENFORCEMENT:
|
|
363
|
+
register_tools_for_agent(self.workflow_tools, self.user_agent)
|
|
364
|
+
messages_to_execute = messages
|
|
365
|
+
|
|
366
|
+
elif self.coordination_stage == CoordinationStage.PRESENTATION:
|
|
367
|
+
self.user_agent.update_system_message(messages[0]["content"])
|
|
368
|
+
messages_to_execute = [messages[1]]
|
|
369
|
+
|
|
370
|
+
async for chunk in self._execute_single_agent(messages=messages_to_execute, agent=self.user_agent):
|
|
371
|
+
yield chunk
|
|
372
|
+
|
|
373
|
+
async def execute_streaming(
|
|
374
|
+
self,
|
|
375
|
+
messages: List[Dict[str, Any]],
|
|
376
|
+
tools: List[Dict[str, Any]],
|
|
377
|
+
**kwargs,
|
|
378
|
+
) -> AsyncGenerator[StreamChunk, None]:
|
|
379
|
+
"""
|
|
380
|
+
Stream response from AG2 agent(s).
|
|
381
|
+
|
|
382
|
+
Since AG2 doesn't support streaming, we simulate it.
|
|
383
|
+
"""
|
|
384
|
+
try:
|
|
385
|
+
self._register_tools(tools)
|
|
386
|
+
agent_id = kwargs.get("agent_id")
|
|
387
|
+
if agent_id:
|
|
388
|
+
self.agent_id = agent_id
|
|
389
|
+
|
|
390
|
+
# Log start
|
|
391
|
+
log_backend_activity(
|
|
392
|
+
"ag2",
|
|
393
|
+
"Starting execute_streaming",
|
|
394
|
+
{"num_messages": len(messages), "num_tools": len(tools) if tools else 0},
|
|
395
|
+
agent_id=agent_id,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Execute agent or group chat
|
|
399
|
+
if self.is_group_chat:
|
|
400
|
+
async for chunk in self._execute_group_chat_with_user_agent(messages):
|
|
401
|
+
yield chunk
|
|
402
|
+
else:
|
|
403
|
+
async for chunk in self._execute_single_agent(messages, self.agent):
|
|
404
|
+
yield chunk
|
|
405
|
+
|
|
406
|
+
# unregister workflow tools after each chat to make sure they're not used in wrong time
|
|
407
|
+
unregister_tools_for_agent(self.workflow_tools, self.user_agent)
|
|
408
|
+
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.error(f"[AG2Adapter] Error in execute_streaming: {e}", exc_info=True)
|
|
411
|
+
|
|
412
|
+
agent_id = kwargs.get("agent_id", "ag2_agent")
|
|
413
|
+
log_backend_activity(
|
|
414
|
+
"ag2",
|
|
415
|
+
"Error during execution",
|
|
416
|
+
{"error": str(e), "error_type": type(e).__name__},
|
|
417
|
+
agent_id=agent_id,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Yield error chunk
|
|
421
|
+
yield StreamChunk(type="error", error=f"AG2 execution error: {str(e)}")
|
|
422
|
+
|
|
423
|
+
# =============================================================================
|
|
424
|
+
# TOOL REGISTRATION METHODS
|
|
425
|
+
# =============================================================================
|
|
426
|
+
|
|
427
|
+
def _register_tools(self, tools: List[Dict[str, Any]]) -> None:
|
|
428
|
+
"""
|
|
429
|
+
Register tools with the agent(s).
|
|
430
|
+
|
|
431
|
+
For single agent: Register all tools to the agent.
|
|
432
|
+
|
|
433
|
+
For group chat:
|
|
434
|
+
- Workflow tools (new_answer, vote) → register ONLY to user_agent
|
|
435
|
+
- Other tools (MCP, etc.) → register to ALL expert agents (not user_agent)
|
|
436
|
+
|
|
437
|
+
MassGen and AG2 both use OpenAI function format for tools.
|
|
438
|
+
"""
|
|
439
|
+
if not tools:
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
if self.is_group_chat:
|
|
443
|
+
self._register_tools_for_group_chat(tools)
|
|
444
|
+
else:
|
|
445
|
+
register_tools_for_agent(tools, self.agent)
|
|
446
|
+
|
|
447
|
+
def _register_tools_for_group_chat(self, tools: List[Dict[str, Any]]) -> None:
|
|
448
|
+
"""Register tools to group chat agents based on type."""
|
|
449
|
+
workflow_tools, other_tools = self._separate_workflow_and_other_tools(tools)
|
|
450
|
+
|
|
451
|
+
# Register other tools to ALL expert agents (not user_agent)
|
|
452
|
+
for agent in self.agents:
|
|
453
|
+
for tool in other_tools:
|
|
454
|
+
register_tools_for_agent([tool], agent)
|
|
455
|
+
if other_tools:
|
|
456
|
+
logger.info(f"[AG2Adapter] Registered {len(other_tools)} non-workflow tools to agent '{agent.name}'")
|
|
457
|
+
|
|
458
|
+
self.workflow_tools = workflow_tools
|
|
459
|
+
|
|
460
|
+
def _separate_workflow_and_other_tools(self, tools: List[Dict[str, Any]]) -> tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
461
|
+
"""
|
|
462
|
+
Separate workflow tools from other tools.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
tools: List of all tools
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
Tuple of (workflow_tools, other_tools)
|
|
469
|
+
"""
|
|
470
|
+
workflow_tools = []
|
|
471
|
+
other_tools = []
|
|
472
|
+
|
|
473
|
+
for tool in tools:
|
|
474
|
+
tool_name = self._get_tool_name(tool)
|
|
475
|
+
if tool_name in ["new_answer", "vote"]:
|
|
476
|
+
workflow_tools.append(tool)
|
|
477
|
+
else:
|
|
478
|
+
other_tools.append(tool)
|
|
479
|
+
|
|
480
|
+
if "new_answer" in workflow_tools and "vote" not in workflow_tools:
|
|
481
|
+
raise ValueError("Both 'new_answer' and 'vote' workflow tools must be provided.")
|
|
482
|
+
|
|
483
|
+
return workflow_tools, other_tools
|