massgen 0.0.3__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 (76) hide show
  1. massgen/__init__.py +94 -0
  2. massgen/agent_config.py +507 -0
  3. massgen/backend/CLAUDE_API_RESEARCH.md +266 -0
  4. massgen/backend/Function calling openai responses.md +1161 -0
  5. massgen/backend/GEMINI_API_DOCUMENTATION.md +410 -0
  6. massgen/backend/OPENAI_RESPONSES_API_FORMAT.md +65 -0
  7. massgen/backend/__init__.py +25 -0
  8. massgen/backend/base.py +180 -0
  9. massgen/backend/chat_completions.py +228 -0
  10. massgen/backend/claude.py +661 -0
  11. massgen/backend/gemini.py +652 -0
  12. massgen/backend/grok.py +187 -0
  13. massgen/backend/response.py +397 -0
  14. massgen/chat_agent.py +440 -0
  15. massgen/cli.py +686 -0
  16. massgen/configs/README.md +293 -0
  17. massgen/configs/creative_team.yaml +53 -0
  18. massgen/configs/gemini_4o_claude.yaml +31 -0
  19. massgen/configs/news_analysis.yaml +51 -0
  20. massgen/configs/research_team.yaml +51 -0
  21. massgen/configs/single_agent.yaml +18 -0
  22. massgen/configs/single_flash2.5.yaml +44 -0
  23. massgen/configs/technical_analysis.yaml +51 -0
  24. massgen/configs/three_agents_default.yaml +31 -0
  25. massgen/configs/travel_planning.yaml +51 -0
  26. massgen/configs/two_agents.yaml +39 -0
  27. massgen/frontend/__init__.py +20 -0
  28. massgen/frontend/coordination_ui.py +945 -0
  29. massgen/frontend/displays/__init__.py +24 -0
  30. massgen/frontend/displays/base_display.py +83 -0
  31. massgen/frontend/displays/rich_terminal_display.py +3497 -0
  32. massgen/frontend/displays/simple_display.py +93 -0
  33. massgen/frontend/displays/terminal_display.py +381 -0
  34. massgen/frontend/logging/__init__.py +9 -0
  35. massgen/frontend/logging/realtime_logger.py +197 -0
  36. massgen/message_templates.py +431 -0
  37. massgen/orchestrator.py +1222 -0
  38. massgen/tests/__init__.py +10 -0
  39. massgen/tests/multi_turn_conversation_design.md +214 -0
  40. massgen/tests/multiturn_llm_input_analysis.md +189 -0
  41. massgen/tests/test_case_studies.md +113 -0
  42. massgen/tests/test_claude_backend.py +310 -0
  43. massgen/tests/test_grok_backend.py +160 -0
  44. massgen/tests/test_message_context_building.py +293 -0
  45. massgen/tests/test_rich_terminal_display.py +378 -0
  46. massgen/tests/test_v3_3agents.py +117 -0
  47. massgen/tests/test_v3_simple.py +216 -0
  48. massgen/tests/test_v3_three_agents.py +272 -0
  49. massgen/tests/test_v3_two_agents.py +176 -0
  50. massgen/utils.py +79 -0
  51. massgen/v1/README.md +330 -0
  52. massgen/v1/__init__.py +91 -0
  53. massgen/v1/agent.py +605 -0
  54. massgen/v1/agents.py +330 -0
  55. massgen/v1/backends/gemini.py +584 -0
  56. massgen/v1/backends/grok.py +410 -0
  57. massgen/v1/backends/oai.py +571 -0
  58. massgen/v1/cli.py +351 -0
  59. massgen/v1/config.py +169 -0
  60. massgen/v1/examples/fast-4o-mini-config.yaml +44 -0
  61. massgen/v1/examples/fast_config.yaml +44 -0
  62. massgen/v1/examples/production.yaml +70 -0
  63. massgen/v1/examples/single_agent.yaml +39 -0
  64. massgen/v1/logging.py +974 -0
  65. massgen/v1/main.py +368 -0
  66. massgen/v1/orchestrator.py +1138 -0
  67. massgen/v1/streaming_display.py +1190 -0
  68. massgen/v1/tools.py +160 -0
  69. massgen/v1/types.py +245 -0
  70. massgen/v1/utils.py +199 -0
  71. massgen-0.0.3.dist-info/METADATA +568 -0
  72. massgen-0.0.3.dist-info/RECORD +76 -0
  73. massgen-0.0.3.dist-info/WHEEL +5 -0
  74. massgen-0.0.3.dist-info/entry_points.txt +2 -0
  75. massgen-0.0.3.dist-info/licenses/LICENSE +204 -0
  76. massgen-0.0.3.dist-info/top_level.txt +1 -0
massgen/v1/agents.py ADDED
@@ -0,0 +1,330 @@
1
+ """
2
+ MassAgent implementations that wrap the existing agent backends.
3
+
4
+ This module provides MassAgent-compatible wrappers for the existing
5
+ OpenAI, Gemini, and Grok agent implementations.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import copy
11
+ import time
12
+ import traceback
13
+ from typing import Callable, Union, Optional, List, Dict, Any
14
+
15
+ from dotenv import load_dotenv
16
+
17
+ load_dotenv()
18
+
19
+ from .agent import MassAgent
20
+ from .types import ModelConfig, TaskInput
21
+ from .tools import register_tool
22
+
23
+
24
+ class OpenAIMassAgent(MassAgent):
25
+ """MassAgent wrapper for OpenAI agent implementation."""
26
+
27
+ def __init__(
28
+ self,
29
+ agent_id: int,
30
+ orchestrator=None,
31
+ model_config: Optional[ModelConfig] = None,
32
+ stream_callback: Optional[Callable] = None,
33
+ **kwargs,
34
+ ):
35
+
36
+ # Pass all configuration to parent, including agent_type
37
+ super().__init__(
38
+ agent_id=agent_id,
39
+ orchestrator=orchestrator,
40
+ model_config=model_config,
41
+ stream_callback=stream_callback,
42
+ **kwargs,
43
+ )
44
+
45
+
46
+ class GrokMassAgent(OpenAIMassAgent):
47
+ """MassAgent wrapper for Grok agent implementation."""
48
+
49
+ def __init__(
50
+ self,
51
+ agent_id: int,
52
+ orchestrator=None,
53
+ model_config: Optional[ModelConfig] = None,
54
+ stream_callback: Optional[Callable] = None,
55
+ **kwargs,
56
+ ):
57
+
58
+ # Pass all configuration to parent, including agent_type
59
+ super().__init__(
60
+ agent_id=agent_id,
61
+ orchestrator=orchestrator,
62
+ model_config=model_config,
63
+ stream_callback=stream_callback,
64
+ **kwargs,
65
+ )
66
+
67
+
68
+ class GeminiMassAgent(OpenAIMassAgent):
69
+ """MassAgent wrapper for Gemini agent implementation."""
70
+
71
+ def __init__(
72
+ self,
73
+ agent_id: int,
74
+ orchestrator=None,
75
+ model_config: Optional[ModelConfig] = None,
76
+ stream_callback: Optional[Callable] = None,
77
+ **kwargs,
78
+ ):
79
+
80
+ # Pass all configuration to parent, including agent_type
81
+ super().__init__(
82
+ agent_id=agent_id,
83
+ orchestrator=orchestrator,
84
+ model_config=model_config,
85
+ stream_callback=stream_callback,
86
+ **kwargs,
87
+ )
88
+
89
+ def _get_curr_messages_and_tools(self, task: TaskInput):
90
+ """Get the current messages and tools for the agent."""
91
+ # Get available tools (system tools + built-in tools + custom tools)
92
+ system_tools = self._get_system_tools()
93
+ built_in_tools = self._get_builtin_tools()
94
+ custom_tools = self._get_registered_tools()
95
+
96
+ # Gemini does not support built-in tools and function call at the same time.
97
+ # If built-in tools are provided, we will switch to them in the next round.
98
+ tool_switch = bool(built_in_tools)
99
+
100
+ # We provide built-in tools in the first round, and then custom tools in the next round.
101
+ if tool_switch:
102
+ function_call_enabled = False
103
+ available_tools = built_in_tools
104
+ else:
105
+ function_call_enabled = True
106
+ available_tools = system_tools + custom_tools
107
+
108
+ # Initialize working messages
109
+ working_status, user_input = self._get_task_input(task)
110
+ working_messages = self._get_task_input_messages(user_input)
111
+
112
+ return (
113
+ working_status,
114
+ working_messages,
115
+ available_tools,
116
+ system_tools,
117
+ custom_tools,
118
+ built_in_tools,
119
+ tool_switch,
120
+ function_call_enabled,
121
+ )
122
+
123
+ def work_on_task(self, task: TaskInput) -> List[Dict[str, str]]:
124
+ """
125
+ Work on the task using the Gemini backend with conversation continuation.
126
+
127
+ NOTE:
128
+ Gemini's does not support built-in tools and function call at the same time.
129
+ Therefore, we provide them interchangedly in different rounds.
130
+ The way the conversation is constructed is also different from OpenAI.
131
+ You can provide consecutive user messages to represent the function call results.
132
+
133
+ Args:
134
+ task: The task to work on
135
+ messages: Current conversation history
136
+ restart_instruction: Optional instruction for restarting work (e.g., updates from other agents)
137
+
138
+ Returns:
139
+ Updated conversation history including agent's work
140
+ """
141
+ curr_round = 0
142
+ (
143
+ working_status,
144
+ working_messages,
145
+ available_tools,
146
+ system_tools,
147
+ custom_tools,
148
+ built_in_tools,
149
+ tool_switch,
150
+ function_call_enabled,
151
+ ) = self._get_curr_messages_and_tools(task)
152
+
153
+ # Start the task solving loop
154
+ while curr_round < self.max_rounds and self.state.status == "working":
155
+ try:
156
+ # If function call is enabled or not, add a notification to the user
157
+ if working_messages[-1].get("role", "") == "user":
158
+ if not function_call_enabled:
159
+ working_messages[-1]["content"] += (
160
+ "\n\n"
161
+ + "Note that the `add_answer` and `vote` tools are not enabled now. Please prioritize using the built-in tools to analyze the task first."
162
+ )
163
+ else:
164
+ working_messages[-1]["content"] += (
165
+ "\n\n"
166
+ + "Note that the `add_answer` and `vote` tools are enabled now."
167
+ )
168
+
169
+ # Call LLM with current conversation
170
+ result = self.process_message(
171
+ messages=working_messages, tools=available_tools
172
+ )
173
+
174
+ # Before Making the new result into effect, check if there is any update from other agents that are unseen by this agent
175
+ agents_with_update = self.check_update()
176
+ has_update = len(agents_with_update) > 0
177
+ # Case 1: if vote() is called and there are new update: make it invalid and renew the conversation
178
+ # Case 2: if add_answer() is called and there are new update: make it valid and renew the conversation
179
+ # Case 3: if no function call is made and there are new update: renew the conversation
180
+
181
+ # Add assistant response
182
+ if result.text:
183
+ working_messages.append(
184
+ {"role": "assistant", "content": result.text}
185
+ )
186
+
187
+ # Execute function calls if any
188
+ if result.function_calls:
189
+ # Deduplicate function calls by their name
190
+ result.function_calls = self.deduplicate_function_calls(
191
+ result.function_calls
192
+ )
193
+ function_outputs, successful_called = self._execute_function_calls(
194
+ result.function_calls, invalid_vote_options=agents_with_update
195
+ )
196
+
197
+ renew_conversation = False
198
+ for function_call, function_output, successful_called in zip(
199
+ result.function_calls, function_outputs, successful_called
200
+ ):
201
+ # If call `add_answer`, we need to rebuild the conversation history with new answers
202
+ if (
203
+ function_call.get("name") == "add_answer"
204
+ and successful_called
205
+ ):
206
+ renew_conversation = True
207
+ break
208
+
209
+ # If call `vote`, we need to break the loop
210
+ if function_call.get("name") == "vote" and successful_called:
211
+ renew_conversation = True
212
+ break
213
+
214
+ if not renew_conversation:
215
+ # Add all function call results to the current conversation
216
+ for function_call, function_output in zip(
217
+ result.function_calls, function_outputs
218
+ ):
219
+ working_messages.extend([function_call, function_output])
220
+ # If we have used custom tools, switch to built-in tools in the next round
221
+ if tool_switch:
222
+ available_tools = built_in_tools
223
+ function_call_enabled = False
224
+ print(
225
+ f"🔄 Agent {self.agent_id} (Gemini) switching to built-in tools in the next round"
226
+ )
227
+ else: # Renew the conversation
228
+ (
229
+ working_status,
230
+ working_messages,
231
+ available_tools,
232
+ system_tools,
233
+ custom_tools,
234
+ built_in_tools,
235
+ tool_switch,
236
+ function_call_enabled,
237
+ ) = self._get_curr_messages_and_tools(task)
238
+ else:
239
+ # No function calls - check if we should continue or stop
240
+ if self.state.status == "voted":
241
+ # Agent has voted, exit the work loop
242
+ break
243
+ else:
244
+ # Check if there is any update from other agents that are unseen by this agent
245
+ if has_update and working_status != "initial":
246
+ # Renew the conversation within the loop
247
+ (
248
+ working_status,
249
+ working_messages,
250
+ available_tools,
251
+ system_tools,
252
+ custom_tools,
253
+ built_in_tools,
254
+ tool_switch,
255
+ function_call_enabled,
256
+ ) = self._get_curr_messages_and_tools(task)
257
+ else: # Continue the current conversation and prompting checkin
258
+ working_messages.append(
259
+ {
260
+ "role": "user",
261
+ "content": "Finish your work above by making a tool call of `vote` or `add_answer`. Make sure you actually call the tool.",
262
+ }
263
+ )
264
+
265
+ # Switch to custom tools in the next round
266
+ if tool_switch:
267
+ available_tools = system_tools + custom_tools
268
+ function_call_enabled = True
269
+ print(
270
+ f"🔄 Agent {self.agent_id} (Gemini) switching to custom tools in the next round"
271
+ )
272
+
273
+ curr_round += 1
274
+ self.state.chat_round += 1
275
+
276
+ # Check if agent voted or failed
277
+ if self.state.status in ["voted", "failed"]:
278
+ break
279
+
280
+ except Exception as e:
281
+ print(
282
+ f"❌ Agent {self.agent_id} error in round {self.state.chat_round}: {e}"
283
+ )
284
+ if self.orchestrator:
285
+ self.orchestrator.mark_agent_failed(self.agent_id, str(e))
286
+
287
+ self.state.chat_round += 1
288
+ curr_round += 1
289
+ break
290
+
291
+ return working_messages
292
+
293
+
294
+ def create_agent(
295
+ agent_type: str,
296
+ agent_id: int,
297
+ orchestrator=None,
298
+ model_config: Optional[ModelConfig] = None,
299
+ **kwargs,
300
+ ) -> MassAgent:
301
+ """
302
+ Factory function to create agents of different types.
303
+
304
+ Args:
305
+ agent_type: Type of agent ("openai", "gemini", "grok")
306
+ agent_id: Unique identifier for the agent
307
+ orchestrator: Reference to the MassOrchestrator
308
+ model_config: Model configuration
309
+ **kwargs: Additional arguments
310
+
311
+ Returns:
312
+ MassAgent instance of the specified type
313
+ """
314
+ agent_classes = {
315
+ "openai": OpenAIMassAgent,
316
+ "gemini": GeminiMassAgent,
317
+ "grok": GrokMassAgent,
318
+ }
319
+
320
+ if agent_type not in agent_classes:
321
+ raise ValueError(
322
+ f"Unknown agent type: {agent_type}. Available types: {list(agent_classes.keys())}"
323
+ )
324
+
325
+ return agent_classes[agent_type](
326
+ agent_id=agent_id,
327
+ orchestrator=orchestrator,
328
+ model_config=model_config,
329
+ **kwargs,
330
+ )