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.
- massgen/__init__.py +94 -0
- massgen/agent_config.py +507 -0
- massgen/backend/CLAUDE_API_RESEARCH.md +266 -0
- massgen/backend/Function calling openai responses.md +1161 -0
- massgen/backend/GEMINI_API_DOCUMENTATION.md +410 -0
- massgen/backend/OPENAI_RESPONSES_API_FORMAT.md +65 -0
- massgen/backend/__init__.py +25 -0
- massgen/backend/base.py +180 -0
- massgen/backend/chat_completions.py +228 -0
- massgen/backend/claude.py +661 -0
- massgen/backend/gemini.py +652 -0
- massgen/backend/grok.py +187 -0
- massgen/backend/response.py +397 -0
- massgen/chat_agent.py +440 -0
- massgen/cli.py +686 -0
- massgen/configs/README.md +293 -0
- massgen/configs/creative_team.yaml +53 -0
- massgen/configs/gemini_4o_claude.yaml +31 -0
- massgen/configs/news_analysis.yaml +51 -0
- massgen/configs/research_team.yaml +51 -0
- massgen/configs/single_agent.yaml +18 -0
- massgen/configs/single_flash2.5.yaml +44 -0
- massgen/configs/technical_analysis.yaml +51 -0
- massgen/configs/three_agents_default.yaml +31 -0
- massgen/configs/travel_planning.yaml +51 -0
- massgen/configs/two_agents.yaml +39 -0
- massgen/frontend/__init__.py +20 -0
- massgen/frontend/coordination_ui.py +945 -0
- massgen/frontend/displays/__init__.py +24 -0
- massgen/frontend/displays/base_display.py +83 -0
- massgen/frontend/displays/rich_terminal_display.py +3497 -0
- massgen/frontend/displays/simple_display.py +93 -0
- massgen/frontend/displays/terminal_display.py +381 -0
- massgen/frontend/logging/__init__.py +9 -0
- massgen/frontend/logging/realtime_logger.py +197 -0
- massgen/message_templates.py +431 -0
- massgen/orchestrator.py +1222 -0
- massgen/tests/__init__.py +10 -0
- massgen/tests/multi_turn_conversation_design.md +214 -0
- massgen/tests/multiturn_llm_input_analysis.md +189 -0
- massgen/tests/test_case_studies.md +113 -0
- massgen/tests/test_claude_backend.py +310 -0
- massgen/tests/test_grok_backend.py +160 -0
- massgen/tests/test_message_context_building.py +293 -0
- massgen/tests/test_rich_terminal_display.py +378 -0
- massgen/tests/test_v3_3agents.py +117 -0
- massgen/tests/test_v3_simple.py +216 -0
- massgen/tests/test_v3_three_agents.py +272 -0
- massgen/tests/test_v3_two_agents.py +176 -0
- massgen/utils.py +79 -0
- massgen/v1/README.md +330 -0
- massgen/v1/__init__.py +91 -0
- massgen/v1/agent.py +605 -0
- massgen/v1/agents.py +330 -0
- massgen/v1/backends/gemini.py +584 -0
- massgen/v1/backends/grok.py +410 -0
- massgen/v1/backends/oai.py +571 -0
- massgen/v1/cli.py +351 -0
- massgen/v1/config.py +169 -0
- massgen/v1/examples/fast-4o-mini-config.yaml +44 -0
- massgen/v1/examples/fast_config.yaml +44 -0
- massgen/v1/examples/production.yaml +70 -0
- massgen/v1/examples/single_agent.yaml +39 -0
- massgen/v1/logging.py +974 -0
- massgen/v1/main.py +368 -0
- massgen/v1/orchestrator.py +1138 -0
- massgen/v1/streaming_display.py +1190 -0
- massgen/v1/tools.py +160 -0
- massgen/v1/types.py +245 -0
- massgen/v1/utils.py +199 -0
- massgen-0.0.3.dist-info/METADATA +568 -0
- massgen-0.0.3.dist-info/RECORD +76 -0
- massgen-0.0.3.dist-info/WHEEL +5 -0
- massgen-0.0.3.dist-info/entry_points.txt +2 -0
- massgen-0.0.3.dist-info/licenses/LICENSE +204 -0
- 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
|
+
)
|