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.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {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,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Utility functions for adapters."""
@@ -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