massgen 0.0.3__py3-none-any.whl → 0.1.0__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 +1560 -275
- massgen/config_builder.py +2396 -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.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.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.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for base AgentAdapter class.
|
|
4
|
+
"""
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from massgen.adapters.base import AgentAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MockAdapter(AgentAdapter):
|
|
11
|
+
"""Mock adapter for testing."""
|
|
12
|
+
|
|
13
|
+
async def execute_streaming(self, messages, tools, **kwargs):
|
|
14
|
+
"""Mock implementation."""
|
|
15
|
+
content = "Mock response"
|
|
16
|
+
async for chunk in self.simulate_streaming(content):
|
|
17
|
+
yield chunk
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.asyncio
|
|
21
|
+
async def test_simulate_streaming():
|
|
22
|
+
"""Test streaming simulation."""
|
|
23
|
+
adapter = MockAdapter()
|
|
24
|
+
|
|
25
|
+
content = "Hello world"
|
|
26
|
+
chunks = []
|
|
27
|
+
|
|
28
|
+
async for chunk in adapter.simulate_streaming(content):
|
|
29
|
+
chunks.append(chunk)
|
|
30
|
+
|
|
31
|
+
# Should have content chunks + complete_message + done
|
|
32
|
+
assert len(chunks) > 0
|
|
33
|
+
assert any(c.type == "content" for c in chunks)
|
|
34
|
+
assert any(c.type == "complete_message" for c in chunks)
|
|
35
|
+
assert any(c.type == "done" for c in chunks)
|
|
36
|
+
|
|
37
|
+
# Complete message should have content
|
|
38
|
+
complete_chunks = [c for c in chunks if c.type == "complete_message"]
|
|
39
|
+
assert len(complete_chunks) == 1
|
|
40
|
+
assert complete_chunks[0].complete_message["content"] == content
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_simulate_streaming_with_tool_calls():
|
|
45
|
+
"""Test streaming simulation with tool calls."""
|
|
46
|
+
adapter = MockAdapter()
|
|
47
|
+
|
|
48
|
+
content = "Using tools"
|
|
49
|
+
tool_calls = [
|
|
50
|
+
{
|
|
51
|
+
"id": "call_1",
|
|
52
|
+
"function": {"name": "search", "arguments": '{"query": "test"}'},
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
chunks = []
|
|
57
|
+
async for chunk in adapter.simulate_streaming(content, tool_calls):
|
|
58
|
+
chunks.append(chunk)
|
|
59
|
+
|
|
60
|
+
# Should have tool_calls chunk
|
|
61
|
+
tool_chunks = [c for c in chunks if c.type == "tool_calls"]
|
|
62
|
+
assert len(tool_chunks) == 1
|
|
63
|
+
assert tool_chunks[0].tool_calls == tool_calls
|
|
64
|
+
|
|
65
|
+
# Complete message should include tool calls
|
|
66
|
+
complete_chunks = [c for c in chunks if c.type == "complete_message"]
|
|
67
|
+
assert complete_chunks[0].complete_message["tool_calls"] == tool_calls
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_convert_messages_default():
|
|
71
|
+
"""Test default message conversion (passthrough)."""
|
|
72
|
+
adapter = MockAdapter()
|
|
73
|
+
|
|
74
|
+
messages = [
|
|
75
|
+
{"role": "user", "content": "Hello"},
|
|
76
|
+
{"role": "assistant", "content": "Hi"},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
result = adapter.convert_messages_from_massgen(messages)
|
|
80
|
+
assert result == messages
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_convert_tools_default():
|
|
84
|
+
"""Test default tool conversion (passthrough)."""
|
|
85
|
+
adapter = MockAdapter()
|
|
86
|
+
|
|
87
|
+
tools = [
|
|
88
|
+
{
|
|
89
|
+
"type": "function",
|
|
90
|
+
"function": {"name": "search", "description": "Search tool"},
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
result = adapter.convert_tools_from_massgen(tools)
|
|
95
|
+
assert result == tools
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_is_stateful_default():
|
|
99
|
+
"""Test default stateful behavior."""
|
|
100
|
+
adapter = MockAdapter()
|
|
101
|
+
assert adapter.is_stateful() is False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_clear_history():
|
|
105
|
+
"""Test clearing conversation history."""
|
|
106
|
+
adapter = MockAdapter()
|
|
107
|
+
|
|
108
|
+
# Add some history
|
|
109
|
+
adapter._conversation_history = [{"role": "user", "content": "test"}]
|
|
110
|
+
|
|
111
|
+
# Clear it
|
|
112
|
+
adapter.clear_history()
|
|
113
|
+
|
|
114
|
+
assert len(adapter._conversation_history) == 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_reset_state():
|
|
118
|
+
"""Test resetting adapter state."""
|
|
119
|
+
adapter = MockAdapter()
|
|
120
|
+
|
|
121
|
+
# Add some history
|
|
122
|
+
adapter._conversation_history = [{"role": "user", "content": "test"}]
|
|
123
|
+
|
|
124
|
+
# Reset
|
|
125
|
+
adapter.reset_state()
|
|
126
|
+
|
|
127
|
+
# Should clear history
|
|
128
|
+
assert len(adapter._conversation_history) == 0
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Utility functions for AG2 (AutoGen) adapter.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
# Suppress autogen deprecation warnings
|
|
9
|
+
import warnings
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="autogen")
|
|
13
|
+
warnings.filterwarnings("ignore", message=".*jsonschema.*")
|
|
14
|
+
warnings.filterwarnings("ignore", message=".*Pydantic.*")
|
|
15
|
+
|
|
16
|
+
from autogen import AssistantAgent, ConversableAgent, LLMConfig # noqa: E402
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup_api_keys() -> None:
|
|
20
|
+
"""Set up API keys for AG2 compatibility."""
|
|
21
|
+
# Copy GEMINI_API_KEY to GOOGLE_GEMINI_API_KEY if it exists
|
|
22
|
+
if "GEMINI_API_KEY" in os.environ and "GOOGLE_GEMINI_API_KEY" not in os.environ:
|
|
23
|
+
os.environ["GOOGLE_GEMINI_API_KEY"] = os.environ["GEMINI_API_KEY"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def validate_agent_config(cfg: Dict[str, Any], require_llm_config: bool = True) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Validate required fields in agent configuration.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
cfg: Agent configuration dict
|
|
32
|
+
require_llm_config: If True, llm_config is required. If False, it's optional.
|
|
33
|
+
"""
|
|
34
|
+
if require_llm_config and "llm_config" not in cfg:
|
|
35
|
+
raise ValueError("Each AG2 agent configuration must include 'llm_config'.")
|
|
36
|
+
|
|
37
|
+
if "name" not in cfg:
|
|
38
|
+
raise ValueError("Each AG2 agent configuration must include 'name'.")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_llm_config(llm_config_data: Any) -> LLMConfig:
|
|
42
|
+
"""
|
|
43
|
+
Create LLMConfig from dict or list format.
|
|
44
|
+
|
|
45
|
+
Supports new AG2 syntax:
|
|
46
|
+
- Single dict: LLMConfig({'model': 'gpt-4', 'api_key': '...'})
|
|
47
|
+
- List of dicts: LLMConfig({'model': 'gpt-4', ...}, {'model': 'gpt-3.5', ...})
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(llm_config_data, list):
|
|
50
|
+
# YAML format: llm_config: [{...}, {...}]
|
|
51
|
+
return LLMConfig(*llm_config_data)
|
|
52
|
+
elif isinstance(llm_config_data, dict):
|
|
53
|
+
# YAML format: llm_config: {model: 'gpt-4o', ...}
|
|
54
|
+
return LLMConfig(llm_config_data)
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError(f"llm_config must be a dict or list, got {type(llm_config_data)}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def create_code_executor(executor_config: Dict[str, Any]) -> Any:
|
|
60
|
+
"""Create code executor from configuration."""
|
|
61
|
+
executor_type = executor_config.get("type")
|
|
62
|
+
|
|
63
|
+
if not executor_type:
|
|
64
|
+
raise ValueError("code_execution_config.executor must include 'type' field")
|
|
65
|
+
|
|
66
|
+
# Remove 'type' from config before passing to executor
|
|
67
|
+
executor_params = {k: v for k, v in executor_config.items() if k != "type"}
|
|
68
|
+
|
|
69
|
+
# Create appropriate executor based on type
|
|
70
|
+
if executor_type == "LocalCommandLineCodeExecutor":
|
|
71
|
+
from autogen.coding import LocalCommandLineCodeExecutor
|
|
72
|
+
|
|
73
|
+
return LocalCommandLineCodeExecutor(**executor_params)
|
|
74
|
+
|
|
75
|
+
elif executor_type == "DockerCommandLineCodeExecutor":
|
|
76
|
+
from autogen.coding import DockerCommandLineCodeExecutor
|
|
77
|
+
|
|
78
|
+
return DockerCommandLineCodeExecutor(**executor_params)
|
|
79
|
+
|
|
80
|
+
elif executor_type == "YepCodeCodeExecutor":
|
|
81
|
+
from autogen.coding import YepCodeCodeExecutor
|
|
82
|
+
|
|
83
|
+
return YepCodeCodeExecutor(**executor_params)
|
|
84
|
+
|
|
85
|
+
elif executor_type == "JupyterCodeExecutor":
|
|
86
|
+
from autogen.coding.jupyter import JupyterCodeExecutor
|
|
87
|
+
|
|
88
|
+
return JupyterCodeExecutor(**executor_params)
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Unsupported code executor type: {executor_type}. " f"Supported types: LocalCommandLineCodeExecutor, DockerCommandLineCodeExecutor, " f"YepCodeCodeExecutor, JupyterCodeExecutor",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_agent_kwargs(cfg: Dict[str, Any], llm_config: LLMConfig, code_executor: Any = None) -> Dict[str, Any]:
|
|
97
|
+
"""Build kwargs for agent initialization."""
|
|
98
|
+
agent_kwargs = {
|
|
99
|
+
"name": cfg["name"],
|
|
100
|
+
"system_message": cfg.get("system_message", "You are a helpful AI assistant."),
|
|
101
|
+
"human_input_mode": "NEVER",
|
|
102
|
+
"llm_config": llm_config,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if code_executor is not None:
|
|
106
|
+
agent_kwargs["code_execution_config"] = {"executor": code_executor}
|
|
107
|
+
|
|
108
|
+
return agent_kwargs
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def setup_agent_from_config(config: Dict[str, Any], default_llm_config: Any = None) -> ConversableAgent:
|
|
112
|
+
"""
|
|
113
|
+
Set up a ConversableAgent from configuration.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
config: Agent configuration dict
|
|
117
|
+
default_llm_config: Default llm_config to use if agent doesn't provide one
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
ConversableAgent or AssistantAgent instance
|
|
121
|
+
"""
|
|
122
|
+
cfg = config.copy()
|
|
123
|
+
|
|
124
|
+
# Check if llm_config is provided in agent config
|
|
125
|
+
has_llm_config = "llm_config" in cfg
|
|
126
|
+
|
|
127
|
+
# Validate configuration (llm_config optional if default provided)
|
|
128
|
+
validate_agent_config(cfg, require_llm_config=not default_llm_config)
|
|
129
|
+
|
|
130
|
+
# Extract agent type
|
|
131
|
+
agent_type = cfg.pop("type", "conversable")
|
|
132
|
+
|
|
133
|
+
# Create LLM config
|
|
134
|
+
if has_llm_config:
|
|
135
|
+
llm_config = create_llm_config(cfg.pop("llm_config"))
|
|
136
|
+
elif default_llm_config:
|
|
137
|
+
llm_config = create_llm_config(default_llm_config)
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError("No llm_config provided for agent and no default_llm_config available")
|
|
140
|
+
|
|
141
|
+
# Create code executor if configured
|
|
142
|
+
code_executor = None
|
|
143
|
+
if "code_execution_config" in cfg:
|
|
144
|
+
code_exec_config = cfg.pop("code_execution_config")
|
|
145
|
+
if "executor" in code_exec_config:
|
|
146
|
+
code_executor = create_code_executor(code_exec_config["executor"])
|
|
147
|
+
|
|
148
|
+
# Build agent kwargs
|
|
149
|
+
agent_kwargs = build_agent_kwargs(cfg, llm_config, code_executor)
|
|
150
|
+
|
|
151
|
+
# Create appropriate agent
|
|
152
|
+
if agent_type == "assistant":
|
|
153
|
+
return AssistantAgent(**agent_kwargs)
|
|
154
|
+
elif agent_type == "conversable":
|
|
155
|
+
return ConversableAgent(**agent_kwargs)
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"Unsupported AG2 agent type: {agent_type}. Use 'assistant' or 'conversable' for ag2 agents.",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_group_initial_message() -> Dict[str, Any] | None:
|
|
163
|
+
"""
|
|
164
|
+
Create the initial system message for group chat.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dict with role and content for initial system message
|
|
168
|
+
"""
|
|
169
|
+
initial_message = f"""
|
|
170
|
+
CURRENT ANSWER from multiple agents for final response to a message is given.
|
|
171
|
+
Different agents may have different builtin tools and capabilities.
|
|
172
|
+
Does the best CURRENT ANSWER address the ORIGINAL MESSAGE well?
|
|
173
|
+
|
|
174
|
+
If CURRENT ANSWER is given, digest existing answers, combine their strengths, and do additional work to address their weaknesses.
|
|
175
|
+
if you think CURRENT ANSWER is good enough, you can also use it as your answer.
|
|
176
|
+
|
|
177
|
+
*Note*: The CURRENT TIME is **{time.strftime("%Y-%m-%d %H:%M:%S")}**.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
# Not real system message. Can't send system message to group chat.
|
|
181
|
+
# When a non function/tool message is sent to an agent in group chat, it will be treated as user message.
|
|
182
|
+
return {"role": "system", "content": initial_message}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_user_agent_tool_call_message() -> str:
|
|
186
|
+
system_message = """
|
|
187
|
+
You are the User agent overseeing a team of expert agents.
|
|
188
|
+
They worked together to create an improved answer to the ORIGINAL MESSAGE based on CURRENT ANSWER (if given).
|
|
189
|
+
|
|
190
|
+
Does CURRENT ANSWER address the ORIGINAL MESSAGE well? If YES, use the `vote` tool to record your vote and skip the `new_answer` tool.
|
|
191
|
+
Otherwise, find the final improved answer generated by team and use the `new_answer` tool to provide it as your final answer to the ORIGINAL MESSAGE.
|
|
192
|
+
|
|
193
|
+
When CURRENT ANSWER section is not available and a new answer is provided by the team of experts,
|
|
194
|
+
you should use the `new_answer` tool instead of `vote` tool.
|
|
195
|
+
|
|
196
|
+
You MUST ONLY use one of the two tools (`vote` or `new_answer`) ONCE to respond.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
return system_message
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_user_agent_default_system_message() -> str:
|
|
203
|
+
system_message = """
|
|
204
|
+
"MUST say 'TERMINATE' when the original request is well answered. Do NOT do anything else."
|
|
205
|
+
"""
|
|
206
|
+
return system_message
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_user_agent_default_description() -> str:
|
|
210
|
+
description = """
|
|
211
|
+
ALWAYS check if other agents still needs to be selected before selected this agent.
|
|
212
|
+
MUST ONLY be selected when the original request is well answered and the conversation should terminate.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
return description
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def postprocess_group_chat_results(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
219
|
+
for message in messages:
|
|
220
|
+
if message["content"]:
|
|
221
|
+
message["content"] = f"<SENDER>: {message['name']} </SENDER> \n" + message["content"]
|
|
222
|
+
message["role"] = "assistant"
|
|
223
|
+
|
|
224
|
+
return messages
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def unregister_tools_for_agent(tools: List[Dict[str, Any]], agent: ConversableAgent) -> None:
|
|
228
|
+
"""Unregister all tools from single agent."""
|
|
229
|
+
for tool in tools:
|
|
230
|
+
agent.update_tool_signature(tool_sig=tool, is_remove=True, silent_override=True)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def register_tools_for_agent(tools: List[Dict[str, Any]], agent: ConversableAgent) -> None:
|
|
234
|
+
"""Register all tools to single agent."""
|
|
235
|
+
for tool in tools:
|
|
236
|
+
agent.update_tool_signature(tool_sig=tool, is_remove=False, silent_override=True)
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for AG2 utility functions.
|
|
4
|
+
"""
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
from massgen.adapters.utils.ag2_utils import (
|
|
8
|
+
create_llm_config,
|
|
9
|
+
get_group_initial_message,
|
|
10
|
+
postprocess_group_chat_results,
|
|
11
|
+
register_tools_for_agent,
|
|
12
|
+
unregister_tools_for_agent,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_create_llm_config_from_dict():
|
|
17
|
+
"""Test creating LLMConfig from dictionary."""
|
|
18
|
+
config_dict = {
|
|
19
|
+
"api_type": "openai",
|
|
20
|
+
"model": "gpt-4o",
|
|
21
|
+
"temperature": 0.7,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
llm_config = create_llm_config(config_dict)
|
|
25
|
+
|
|
26
|
+
# Should create LLMConfig instance
|
|
27
|
+
assert llm_config is not None
|
|
28
|
+
# Should contain config_list
|
|
29
|
+
assert hasattr(llm_config, "config_list")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_create_llm_config_from_list():
|
|
33
|
+
"""Test creating LLMConfig from list of configs."""
|
|
34
|
+
config_list = [
|
|
35
|
+
{"api_type": "openai", "model": "gpt-4o"},
|
|
36
|
+
{"api_type": "google", "model": "gemini-pro"},
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
llm_config = create_llm_config(config_list)
|
|
40
|
+
|
|
41
|
+
# Should create LLMConfig instance
|
|
42
|
+
assert llm_config is not None
|
|
43
|
+
assert hasattr(llm_config, "config_list")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_postprocess_group_chat_results():
|
|
47
|
+
"""Test postprocessing of group chat results."""
|
|
48
|
+
messages = [
|
|
49
|
+
{"name": "Agent1", "content": "Hello", "role": "user"},
|
|
50
|
+
{"name": "Agent2", "content": "Hi there", "role": "user"},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
result = postprocess_group_chat_results(messages)
|
|
54
|
+
|
|
55
|
+
# Should add sender tags to content
|
|
56
|
+
assert "<SENDER>: Agent1 </SENDER>" in result[0]["content"]
|
|
57
|
+
assert "<SENDER>: Agent2 </SENDER>" in result[1]["content"]
|
|
58
|
+
|
|
59
|
+
# Should change role to assistant
|
|
60
|
+
assert result[0]["role"] == "assistant"
|
|
61
|
+
assert result[1]["role"] == "assistant"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_postprocess_group_chat_results_empty_content():
|
|
65
|
+
"""Test postprocessing with empty content."""
|
|
66
|
+
messages = [
|
|
67
|
+
{"name": "Agent1", "content": "", "role": "user"},
|
|
68
|
+
{"name": "Agent2", "content": None, "role": "user"},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
result = postprocess_group_chat_results(messages)
|
|
72
|
+
|
|
73
|
+
# Should not add sender tags for empty content
|
|
74
|
+
assert result[0]["content"] == ""
|
|
75
|
+
assert result[1]["content"] is None
|
|
76
|
+
|
|
77
|
+
# Should still change role
|
|
78
|
+
assert result[0]["role"] == "assistant"
|
|
79
|
+
assert result[1]["role"] == "assistant"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_get_group_initial_message():
|
|
83
|
+
"""Test getting initial message for group chat."""
|
|
84
|
+
message = get_group_initial_message()
|
|
85
|
+
|
|
86
|
+
# Should return a dict with role and content
|
|
87
|
+
assert isinstance(message, dict)
|
|
88
|
+
assert "role" in message
|
|
89
|
+
assert "content" in message
|
|
90
|
+
|
|
91
|
+
# Should be system role
|
|
92
|
+
assert message["role"] == "system"
|
|
93
|
+
|
|
94
|
+
# Content should mention key concepts
|
|
95
|
+
assert "CURRENT ANSWER" in message["content"]
|
|
96
|
+
assert "ORIGINAL MESSAGE" in message["content"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_register_tools_for_agent():
|
|
100
|
+
"""Test registering tools with agent."""
|
|
101
|
+
mock_agent = MagicMock()
|
|
102
|
+
tools = [
|
|
103
|
+
{
|
|
104
|
+
"type": "function",
|
|
105
|
+
"function": {"name": "search", "description": "Search tool"},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "function",
|
|
109
|
+
"function": {"name": "calc", "description": "Calculator tool"},
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
register_tools_for_agent(tools, mock_agent)
|
|
114
|
+
|
|
115
|
+
# Should call update_tool_signature for each tool
|
|
116
|
+
assert mock_agent.update_tool_signature.call_count == len(tools)
|
|
117
|
+
|
|
118
|
+
# Should be called with is_remove=False
|
|
119
|
+
for call in mock_agent.update_tool_signature.call_args_list:
|
|
120
|
+
assert call[1]["is_remove"] is False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_unregister_tools_for_agent():
|
|
124
|
+
"""Test unregistering tools from agent."""
|
|
125
|
+
mock_agent = MagicMock()
|
|
126
|
+
tools = [
|
|
127
|
+
{
|
|
128
|
+
"type": "function",
|
|
129
|
+
"function": {"name": "search", "description": "Search tool"},
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
unregister_tools_for_agent(tools, mock_agent)
|
|
134
|
+
|
|
135
|
+
# Should call update_tool_signature with is_remove=True
|
|
136
|
+
mock_agent.update_tool_signature.assert_called_once()
|
|
137
|
+
call_kwargs = mock_agent.update_tool_signature.call_args[1]
|
|
138
|
+
assert call_kwargs["is_remove"] is True
|