massgen 0.1.5__py3-none-any.whl → 0.1.6__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 (57) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
  3. massgen/backend/capabilities.py +39 -0
  4. massgen/backend/chat_completions.py +111 -197
  5. massgen/backend/claude.py +210 -181
  6. massgen/backend/gemini.py +1015 -1559
  7. massgen/backend/grok.py +3 -2
  8. massgen/backend/response.py +160 -220
  9. massgen/cli.py +73 -6
  10. massgen/config_builder.py +20 -54
  11. massgen/config_validator.py +931 -0
  12. massgen/configs/README.md +51 -8
  13. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
  14. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
  15. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
  16. massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
  17. massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
  18. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
  19. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
  20. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
  21. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
  22. massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
  23. massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
  24. massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
  25. massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
  26. massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
  27. massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
  28. massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
  29. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
  30. massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
  31. massgen/formatter/_gemini_formatter.py +61 -15
  32. massgen/tests/test_ag2_lesson_planner.py +223 -0
  33. massgen/tests/test_config_validator.py +1156 -0
  34. massgen/tests/test_langgraph_lesson_planner.py +223 -0
  35. massgen/tool/__init__.py +2 -9
  36. massgen/tool/_decorators.py +52 -0
  37. massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
  38. massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
  39. massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
  40. massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
  41. massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
  42. massgen/tool/_manager.py +102 -16
  43. massgen/tool/_registered_tool.py +3 -0
  44. massgen/tool/_result.py +3 -0
  45. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/METADATA +104 -76
  46. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/RECORD +50 -39
  47. massgen/backend/gemini_mcp_manager.py +0 -545
  48. massgen/backend/gemini_trackers.py +0 -344
  49. massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml +0 -98
  50. massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml +0 -54
  51. massgen/tools/__init__.py +0 -8
  52. massgen/tools/_planning_mcp_server.py +0 -520
  53. massgen/tools/planning_dataclasses.py +0 -434
  54. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
  55. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
  56. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
  57. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
@@ -1,344 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- MCP tracking utilities for the Gemini backend, handling deduplication across streaming chunks and extraction from SDK objects.
4
- """
5
-
6
- import hashlib
7
- import json
8
- import time
9
- from typing import Any, Dict, Optional
10
-
11
-
12
- class MCPResponseTracker:
13
- """
14
- Tracks MCP tool responses across streaming chunks to handle deduplication.
15
-
16
- Similar to MCPCallTracker but for tracking tool responses to avoid duplicate output.
17
- """
18
-
19
- def __init__(self):
20
- """Initialize the tracker with empty storage."""
21
- self.processed_responses = set() # Store hashes of processed responses
22
- self.response_history = [] # Store all unique responses with timestamps
23
-
24
- def get_response_hash(self, tool_name: str, tool_response: Any) -> str:
25
- """
26
- Generate a unique hash for a tool response based on name and response content.
27
-
28
- Args:
29
- tool_name: Name of the tool that responded
30
- tool_response: Response from the tool
31
-
32
- Returns:
33
- MD5 hash string identifying this specific response
34
- """
35
- # Create a deterministic string representation
36
- content = f"{tool_name}:{str(tool_response)}"
37
- return hashlib.md5(content.encode()).hexdigest()
38
-
39
- def is_new_response(self, tool_name: str, tool_response: Any) -> bool:
40
- """
41
- Check if this is a new tool response we haven't seen before.
42
-
43
- Args:
44
- tool_name: Name of the tool that responded
45
- tool_response: Response from the tool
46
-
47
- Returns:
48
- True if this is a new response, False if already processed
49
- """
50
- response_hash = self.get_response_hash(tool_name, tool_response)
51
- return response_hash not in self.processed_responses
52
-
53
- def add_response(self, tool_name: str, tool_response: Any) -> Dict[str, Any]:
54
- """
55
- Add a new response to the tracker.
56
-
57
- Args:
58
- tool_name: Name of the tool that responded
59
- tool_response: Response from the tool
60
-
61
- Returns:
62
- Dictionary containing response details and timestamp
63
- """
64
- response_hash = self.get_response_hash(tool_name, tool_response)
65
- self.processed_responses.add(response_hash)
66
-
67
- record = {
68
- "tool_name": tool_name,
69
- "response": tool_response,
70
- "hash": response_hash,
71
- "timestamp": time.time(),
72
- }
73
- self.response_history.append(record)
74
- return record
75
-
76
-
77
- class MCPCallTracker:
78
- """
79
- Tracks MCP tool calls across streaming chunks to handle deduplication.
80
-
81
- Uses hashing to identify unique tool calls and timestamps to track when they occurred.
82
- This ensures we don't double-count the same tool call appearing in multiple chunks.
83
- """
84
-
85
- def __init__(self):
86
- """Initialize the tracker with empty storage."""
87
- self.processed_calls = set() # Store hashes of processed calls
88
- self.call_history = [] # Store all unique calls with timestamps
89
- self.last_chunk_calls = [] # Track calls from the last chunk for deduplication
90
- self.dedup_window = 0.5 # Time window in seconds for deduplication
91
-
92
- def get_call_hash(self, tool_name: str, tool_args: Dict[str, Any]) -> str:
93
- """
94
- Generate a unique hash for a tool call based on name and arguments.
95
-
96
- Args:
97
- tool_name: Name of the tool being called
98
- tool_args: Arguments passed to the tool
99
-
100
- Returns:
101
- MD5 hash string identifying this specific call
102
- """
103
- # Create a deterministic string representation
104
- content = f"{tool_name}:{json.dumps(tool_args, sort_keys=True)}"
105
- return hashlib.md5(content.encode()).hexdigest()
106
-
107
- def is_new_call(self, tool_name: str, tool_args: Dict[str, Any]) -> bool:
108
- """
109
- Check if this is a new tool call we haven't seen before.
110
-
111
- Uses a time-window based approach: identical calls within the dedup_window
112
- are considered duplicates (likely from streaming chunks), while those outside
113
- the window are considered new calls (likely intentional repeated calls).
114
-
115
- Args:
116
- tool_name: Name of the tool being called
117
- tool_args: Arguments passed to the tool
118
-
119
- Returns:
120
- True if this is a new call, False if we've seen it before
121
- """
122
- call_hash = self.get_call_hash(tool_name, tool_args)
123
- current_time = time.time()
124
-
125
- # Check if this call exists in recent history within the dedup window
126
- for call in self.call_history[-10:]: # Check last 10 calls for efficiency
127
- if call.get("hash") == call_hash:
128
- time_diff = current_time - call.get("timestamp", 0)
129
- if time_diff < self.dedup_window:
130
- # This is likely a duplicate from streaming chunks
131
- return False
132
- # If outside the window, treat as a new intentional call
133
-
134
- # Mark as processed
135
- self.processed_calls.add(call_hash)
136
- return True
137
-
138
- def add_call(self, tool_name: str, tool_args: Dict[str, Any]) -> Dict[str, Any]:
139
- """
140
- Add a new tool call to the history.
141
-
142
- Args:
143
- tool_name: Name of the tool being called
144
- tool_args: Arguments passed to the tool
145
-
146
- Returns:
147
- Dictionary containing the call details with timestamp and hash
148
- """
149
- call_record = {
150
- "name": tool_name,
151
- "arguments": tool_args,
152
- "timestamp": time.time(),
153
- "hash": self.get_call_hash(tool_name, tool_args),
154
- "sequence": len(self.call_history), # Add sequence number for ordering
155
- }
156
- self.call_history.append(call_record)
157
-
158
- # Clean up old history to prevent memory growth
159
- if len(self.call_history) > 100:
160
- self.call_history = self.call_history[-50:]
161
-
162
- return call_record
163
-
164
- def get_summary(self) -> str:
165
- """
166
- Get a summary of all tracked tool calls.
167
-
168
- Returns:
169
- Human-readable summary of tool usage
170
- """
171
- if not self.call_history:
172
- return "No MCP tools called"
173
-
174
- tool_names = [call["name"] for call in self.call_history]
175
- unique_tools = list(dict.fromkeys(tool_names)) # Preserve order
176
- return f"Used {len(self.call_history)} MCP tool calls: {', '.join(unique_tools)}"
177
-
178
-
179
- class MCPResponseExtractor:
180
- """
181
- Extracts MCP tool calls and responses from Gemini SDK stream chunks.
182
-
183
- This class parses the internal SDK chunks to capture:
184
- - function_call parts (tool invocations)
185
- - function_response parts (tool results)
186
- - Paired call-response data for tracking complete tool executions
187
- """
188
-
189
- def __init__(self):
190
- """Initialize the extractor with empty storage."""
191
- self.mcp_calls = [] # All tool calls
192
- self.mcp_responses = [] # All tool responses
193
- self.call_response_pairs = [] # Matched call-response pairs
194
- self._pending_call = None # Track current call awaiting response
195
-
196
- def extract_function_call(self, function_call) -> Optional[Dict[str, Any]]:
197
- """
198
- Extract tool call information from SDK function_call object.
199
-
200
- Tries multiple methods to extract data from different SDK versions:
201
- 1. Direct attributes (name, args)
202
- 2. Dictionary-like interface (get method)
203
- 3. __dict__ attributes
204
- 4. Protobuf _pb attributes
205
- """
206
- tool_name = None
207
- tool_args = None
208
-
209
- # Method 1: Direct attributes
210
- tool_name = getattr(function_call, "name", None)
211
- tool_args = getattr(function_call, "args", None)
212
-
213
- # Method 2: Dictionary-like object
214
- if tool_name is None:
215
- try:
216
- if hasattr(function_call, "get"):
217
- tool_name = function_call.get("name", None)
218
- tool_args = function_call.get("args", None)
219
- except Exception:
220
- pass
221
-
222
- # Method 3: __dict__ inspection
223
- if tool_name is None:
224
- try:
225
- if hasattr(function_call, "__dict__"):
226
- fc_dict = function_call.__dict__
227
- tool_name = fc_dict.get("name", None)
228
- tool_args = fc_dict.get("args", None)
229
- except Exception:
230
- pass
231
-
232
- # Method 4: Protobuf _pb attribute
233
- if tool_name is None:
234
- try:
235
- if hasattr(function_call, "_pb"):
236
- pb = function_call._pb
237
- if hasattr(pb, "name"):
238
- tool_name = pb.name
239
- if hasattr(pb, "args"):
240
- tool_args = pb.args
241
- except Exception:
242
- pass
243
-
244
- if tool_name:
245
- call_data = {
246
- "name": tool_name,
247
- "arguments": tool_args or {},
248
- "timestamp": time.time(),
249
- "raw": str(function_call)[:200], # Truncate for logging
250
- }
251
- self.mcp_calls.append(call_data)
252
- self._pending_call = call_data
253
- return call_data
254
-
255
- return None
256
-
257
- def extract_function_response(self, function_response) -> Optional[Dict[str, Any]]:
258
- """
259
- Extract tool response information from SDK function_response object.
260
-
261
- Uses same extraction methods as function_call for consistency.
262
- """
263
- tool_name = None
264
- tool_response = None
265
-
266
- # Method 1: Direct attributes
267
- tool_name = getattr(function_response, "name", None)
268
- tool_response = getattr(function_response, "response", None)
269
-
270
- # Method 2: Dictionary-like object
271
- if tool_name is None:
272
- try:
273
- if hasattr(function_response, "get"):
274
- tool_name = function_response.get("name", None)
275
- tool_response = function_response.get("response", None)
276
- except Exception:
277
- pass
278
-
279
- # Method 3: __dict__ inspection
280
- if tool_name is None:
281
- try:
282
- if hasattr(function_response, "__dict__"):
283
- fr_dict = function_response.__dict__
284
- tool_name = fr_dict.get("name", None)
285
- tool_response = fr_dict.get("response", None)
286
- except Exception:
287
- pass
288
-
289
- # Method 4: Protobuf _pb attribute
290
- if tool_name is None:
291
- try:
292
- if hasattr(function_response, "_pb"):
293
- pb = function_response._pb
294
- if hasattr(pb, "name"):
295
- tool_name = pb.name
296
- if hasattr(pb, "response"):
297
- tool_response = pb.response
298
- except Exception:
299
- pass
300
-
301
- if tool_name:
302
- response_data = {
303
- "name": tool_name,
304
- "response": tool_response or {},
305
- "timestamp": time.time(),
306
- "raw": str(function_response)[:500], # Truncate for logging
307
- }
308
- self.mcp_responses.append(response_data)
309
-
310
- # Pair with pending call if names match
311
- if self._pending_call and self._pending_call["name"] == tool_name:
312
- self.call_response_pairs.append(
313
- {
314
- "call": self._pending_call,
315
- "response": response_data,
316
- "duration": response_data["timestamp"] - self._pending_call["timestamp"],
317
- "paired_at": time.time(),
318
- },
319
- )
320
- self._pending_call = None
321
-
322
- return response_data
323
-
324
- return None
325
-
326
- def get_summary(self) -> Dict[str, Any]:
327
- """
328
- Get a summary of all extracted MCP tool interactions.
329
- """
330
- return {
331
- "total_calls": len(self.mcp_calls),
332
- "total_responses": len(self.mcp_responses),
333
- "paired_interactions": len(self.call_response_pairs),
334
- "pending_call": self._pending_call is not None,
335
- "tool_names": list(set(call["name"] for call in self.mcp_calls)),
336
- "average_duration": (sum(pair["duration"] for pair in self.call_response_pairs) / len(self.call_response_pairs) if self.call_response_pairs else 0),
337
- }
338
-
339
- def clear(self):
340
- """Clear all stored data."""
341
- self.mcp_calls.clear()
342
- self.mcp_responses.clear()
343
- self.call_response_pairs.clear()
344
- self._pending_call = None
@@ -1,98 +0,0 @@
1
- # massgen --config massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml "Analyze docs.massgen.ai and tell me how to improve its design."
2
-
3
- agents:
4
- - id: agent_a
5
- backend:
6
- type: openai
7
- model: gpt-5-codex
8
- text:
9
- verbosity: medium
10
- reasoning:
11
- effort: medium
12
- summary: auto
13
- cwd: workspace1
14
- enable_mcp_command_line: true
15
- command_line_execution_mode: docker
16
- command_line_docker_network_mode: "bridge" # Enable network access (default: none)
17
- enable_web_search: true
18
- custom_tools:
19
- - name: ["understand_image"]
20
- category: "multimodal"
21
- path: "massgen/tool/_multimodal_tools/understand_image.py"
22
- function: ["understand_image"]
23
- mcp_servers:
24
- playwright:
25
- type: "stdio"
26
- command: "npx"
27
- args: [
28
- "@playwright/mcp@latest",
29
- "--browser=chrome", # Use Chrome browser
30
- "--caps=vision,pdf", # Enable vision and PDF capabilities
31
- "--user-data-dir=${cwd}/playwright-profile", # Persistent browser profile within workspace
32
- "--output-dir=${cwd}", # Save screenshots/PDFs directly to workspace
33
- # "--save-trace" # Save Playwright traces for debugging
34
- ]
35
-
36
- - id: agent_b
37
- backend:
38
- type: claude_code
39
- model: claude-sonnet-4-5-20250929
40
- cwd: workspace2
41
- enable_mcp_command_line: true
42
- command_line_execution_mode: docker
43
- command_line_docker_network_mode: "bridge" # Enable network access (default: none)
44
- custom_tools:
45
- - name: ["understand_image"]
46
- category: "multimodal"
47
- path: "massgen/tool/_multimodal_tools/understand_image.py"
48
- function: ["understand_image"]
49
- mcp_servers:
50
- playwright:
51
- type: "stdio"
52
- command: "npx"
53
- args: [
54
- "@playwright/mcp@latest",
55
- "--browser=chrome", # Use Chrome browser
56
- "--caps=vision,pdf", # Enable vision and PDF capabilities
57
- "--user-data-dir=${cwd}/playwright-profile", # Persistent browser profile within workspace
58
- "--output-dir=${cwd}", # Save screenshots/PDFs directly to workspace
59
- # "--save-trace" # Save Playwright traces for debugging
60
- ]
61
-
62
- - id: agent_c
63
- backend:
64
- type: chatcompletion
65
- base_url: "https://openrouter.ai/api/v1"
66
- model: qwen/qwen3-coder
67
- cwd: workspace3
68
- enable_mcp_command_line: true
69
- command_line_execution_mode: docker
70
- command_line_docker_network_mode: "bridge" # Enable network access (default: none)
71
- custom_tools:
72
- - name: ["understand_image"]
73
- category: "multimodal"
74
- path: "massgen/tool/_multimodal_tools/understand_image.py"
75
- function: ["understand_image"]
76
- mcp_servers:
77
- playwright:
78
- type: "stdio"
79
- command: "npx"
80
- args: [
81
- "@playwright/mcp@latest",
82
- "--browser=chrome", # Use Chrome browser
83
- "--caps=vision,pdf", # Enable vision and PDF capabilities
84
- "--user-data-dir=${cwd}/playwright-profile", # Persistent browser profile within workspace
85
- "--output-dir=${cwd}", # Save screenshots/PDFs directly to workspace
86
- # "--save-trace" # Save Playwright traces for debugging
87
- ]
88
-
89
- ui:
90
- display_type: rich_terminal
91
- logging_enabled: true
92
- orchestrator:
93
- snapshot_storage: snapshots
94
- agent_temporary_workspace: temp_workspaces
95
- session_storage: sessions
96
- # voting_sensitivity: balanced
97
- max_new_answers_per_agent: 5
98
- # answer_novelty_requirement: balanced
@@ -1,54 +0,0 @@
1
- # MassGen Configuration: Understand Video Example
2
- #
3
- # Use Case: Analyze a specific video file using the understand_video tool
4
- #
5
- # This demonstrates direct video analysis without needing to download.
6
- # The video file is provided as a context path for agents to analyze.
7
- #
8
- # Run with:
9
- # uv run massgen --config massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml "What is shown in this video?"
10
-
11
- agents:
12
- - id: "agent_a"
13
- backend:
14
- type: "openai"
15
- model: "gpt-5-mini"
16
- text:
17
- verbosity: "medium"
18
- reasoning:
19
- effort: "medium"
20
- summary: "auto"
21
- custom_tools:
22
- - name: ["understand_video"]
23
- category: "multimodal"
24
- path: "massgen/tool/_multimodal_tools/understand_video.py"
25
- function: ["understand_video"]
26
- cwd: "workspace1"
27
-
28
- - id: "agent_b"
29
- backend:
30
- type: "gemini"
31
- model: "gemini-2.5-pro"
32
- custom_tools:
33
- - name: ["understand_video"]
34
- category: "multimodal"
35
- path: "massgen/tool/_multimodal_tools/understand_video.py"
36
- function: ["understand_video"]
37
- cwd: "workspace2"
38
-
39
- orchestrator:
40
- snapshot_storage: "snapshots"
41
- agent_temporary_workspace: "temp_workspaces"
42
- context_paths:
43
- - path: "massgen/configs/resources/v0.1.3-example/case-study-videos/Dp2oldJJImw.mp4"
44
- permission: "read"
45
-
46
- ui:
47
- display_type: "rich_terminal"
48
- logging_enabled: true
49
-
50
- # What happens:
51
- # 1. Agents have read access to the video file
52
- # 2. They can use understand_video tool to analyze it
53
- # 3. Tool extracts 8 frames and analyzes with GPT-4.1
54
- # 4. Agents collaborate to provide comprehensive insights
massgen/tools/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- MassGen Tools Module
4
-
5
- This module contains agent tools and utilities, including:
6
- - Planning tools for task management and coordination
7
- - MCP servers for agent capabilities
8
- """