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.
- massgen/__init__.py +1 -1
- massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
- massgen/backend/capabilities.py +39 -0
- massgen/backend/chat_completions.py +111 -197
- massgen/backend/claude.py +210 -181
- massgen/backend/gemini.py +1015 -1559
- massgen/backend/grok.py +3 -2
- massgen/backend/response.py +160 -220
- massgen/cli.py +73 -6
- massgen/config_builder.py +20 -54
- massgen/config_validator.py +931 -0
- massgen/configs/README.md +51 -8
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
- massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
- massgen/formatter/_gemini_formatter.py +61 -15
- massgen/tests/test_ag2_lesson_planner.py +223 -0
- massgen/tests/test_config_validator.py +1156 -0
- massgen/tests/test_langgraph_lesson_planner.py +223 -0
- massgen/tool/__init__.py +2 -9
- massgen/tool/_decorators.py +52 -0
- massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
- massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
- massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
- massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
- massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
- massgen/tool/_manager.py +102 -16
- massgen/tool/_registered_tool.py +3 -0
- massgen/tool/_result.py +3 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/METADATA +104 -76
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/RECORD +50 -39
- massgen/backend/gemini_mcp_manager.py +0 -545
- massgen/backend/gemini_trackers.py +0 -344
- massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml +0 -98
- massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml +0 -54
- massgen/tools/__init__.py +0 -8
- massgen/tools/_planning_mcp_server.py +0 -520
- massgen/tools/planning_dataclasses.py +0 -434
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -31,7 +31,11 @@ from ..logger_config import log_backend_agent_message, log_stream_chunk, logger
|
|
|
31
31
|
|
|
32
32
|
# Local imports
|
|
33
33
|
from .base import FilesystemSupport, StreamChunk
|
|
34
|
-
from .base_with_custom_tool_and_mcp import
|
|
34
|
+
from .base_with_custom_tool_and_mcp import (
|
|
35
|
+
CustomToolAndMCPBackend,
|
|
36
|
+
CustomToolChunk,
|
|
37
|
+
ToolExecutionConfig,
|
|
38
|
+
)
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
@@ -67,6 +71,71 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
67
71
|
async for chunk in super().stream_with_tools(messages, tools, **kwargs):
|
|
68
72
|
yield chunk
|
|
69
73
|
|
|
74
|
+
def _append_tool_result_message(
|
|
75
|
+
self,
|
|
76
|
+
updated_messages: List[Dict[str, Any]],
|
|
77
|
+
call: Dict[str, Any],
|
|
78
|
+
result: Any,
|
|
79
|
+
tool_type: str,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Append tool result to messages in Chat Completions format.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
updated_messages: Message list to append to
|
|
85
|
+
call: Tool call dictionary with call_id, name, arguments
|
|
86
|
+
result: Tool execution result
|
|
87
|
+
tool_type: "custom" or "mcp"
|
|
88
|
+
"""
|
|
89
|
+
function_output_msg = {
|
|
90
|
+
"role": "tool",
|
|
91
|
+
"tool_call_id": call.get("call_id", ""),
|
|
92
|
+
"content": str(result),
|
|
93
|
+
}
|
|
94
|
+
updated_messages.append(function_output_msg)
|
|
95
|
+
|
|
96
|
+
def _append_tool_error_message(
|
|
97
|
+
self,
|
|
98
|
+
updated_messages: List[Dict[str, Any]],
|
|
99
|
+
call: Dict[str, Any],
|
|
100
|
+
error_msg: str,
|
|
101
|
+
tool_type: str,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Append tool error to messages in Chat Completions format.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
updated_messages: Message list to append to
|
|
107
|
+
call: Tool call dictionary with call_id, name, arguments
|
|
108
|
+
error_msg: Error message string
|
|
109
|
+
tool_type: "custom" or "mcp"
|
|
110
|
+
"""
|
|
111
|
+
error_output_msg = {
|
|
112
|
+
"role": "tool",
|
|
113
|
+
"tool_call_id": call.get("call_id", ""),
|
|
114
|
+
"content": error_msg,
|
|
115
|
+
}
|
|
116
|
+
updated_messages.append(error_output_msg)
|
|
117
|
+
|
|
118
|
+
async def _execute_custom_tool(self, call: Dict[str, Any]) -> AsyncGenerator[CustomToolChunk, None]:
|
|
119
|
+
"""Execute custom tool with streaming support - async generator for base class.
|
|
120
|
+
|
|
121
|
+
This method is called by _execute_tool_with_logging and yields CustomToolChunk
|
|
122
|
+
objects for intermediate streaming output. The base class detects the async
|
|
123
|
+
generator and streams intermediate results to users in real-time.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
call: Tool call dictionary with name and arguments
|
|
127
|
+
|
|
128
|
+
Yields:
|
|
129
|
+
CustomToolChunk objects with streaming data
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
- Intermediate chunks (completed=False) are streamed to users in real-time
|
|
133
|
+
- Final chunk (completed=True) contains the accumulated result for message history
|
|
134
|
+
- The base class automatically handles extracting and displaying intermediate chunks
|
|
135
|
+
"""
|
|
136
|
+
async for chunk in self.stream_custom_tool_execution(call):
|
|
137
|
+
yield chunk
|
|
138
|
+
|
|
70
139
|
async def _stream_with_custom_and_mcp_tools(
|
|
71
140
|
self,
|
|
72
141
|
current_messages: List[Dict[str, Any]],
|
|
@@ -193,18 +262,8 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
193
262
|
|
|
194
263
|
# Execute any captured function calls
|
|
195
264
|
if captured_function_calls and response_completed:
|
|
196
|
-
# Categorize function calls
|
|
197
|
-
mcp_calls =
|
|
198
|
-
custom_calls = []
|
|
199
|
-
provider_calls = []
|
|
200
|
-
|
|
201
|
-
for call in captured_function_calls:
|
|
202
|
-
if call["name"] in self._mcp_functions:
|
|
203
|
-
mcp_calls.append(call)
|
|
204
|
-
elif call["name"] in self._custom_tool_names:
|
|
205
|
-
custom_calls.append(call)
|
|
206
|
-
else:
|
|
207
|
-
provider_calls.append(call)
|
|
265
|
+
# Categorize function calls using base helper
|
|
266
|
+
mcp_calls, custom_calls, provider_calls = self._categorize_tool_calls(captured_function_calls)
|
|
208
267
|
|
|
209
268
|
# If there are provider calls (non-MCP, non-custom), let API handle them
|
|
210
269
|
if provider_calls:
|
|
@@ -221,8 +280,8 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
221
280
|
content="⚠️ [MCP] All servers blocked by circuit breaker",
|
|
222
281
|
source="circuit_breaker",
|
|
223
282
|
)
|
|
224
|
-
|
|
225
|
-
|
|
283
|
+
# Skip MCP tool execution but continue with custom tools
|
|
284
|
+
mcp_calls = []
|
|
226
285
|
|
|
227
286
|
# Initialize for execution
|
|
228
287
|
functions_executed = False
|
|
@@ -243,8 +302,7 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
243
302
|
source="planning_mode",
|
|
244
303
|
)
|
|
245
304
|
# Skip all MCP tool execution but still continue with workflow
|
|
246
|
-
|
|
247
|
-
return
|
|
305
|
+
mcp_calls = []
|
|
248
306
|
else:
|
|
249
307
|
# Selective blocking - log but continue to check each tool individually
|
|
250
308
|
logger.info(f"[ChatCompletions] Planning mode enabled - selective blocking of {len(blocked_tools)} tools")
|
|
@@ -274,188 +332,44 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
274
332
|
}
|
|
275
333
|
updated_messages.append(assistant_message)
|
|
276
334
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
type="custom_tool_status",
|
|
291
|
-
status="function_call",
|
|
292
|
-
content=f"Arguments for Calling {call['name']}: {call['arguments']}",
|
|
293
|
-
source=f"custom_{call['name']}",
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
# Execute custom tool
|
|
297
|
-
result = await self._execute_custom_tool(call)
|
|
298
|
-
|
|
299
|
-
# Add function result to messages
|
|
300
|
-
function_output_msg = {
|
|
301
|
-
"role": "tool",
|
|
302
|
-
"tool_call_id": call["call_id"],
|
|
303
|
-
"content": str(result),
|
|
304
|
-
}
|
|
305
|
-
updated_messages.append(function_output_msg)
|
|
306
|
-
|
|
307
|
-
# Yield custom tool results
|
|
308
|
-
yield StreamChunk(
|
|
309
|
-
type="custom_tool_status",
|
|
310
|
-
status="function_call_output",
|
|
311
|
-
content=f"Results for Calling {call['name']}: {str(result)}",
|
|
312
|
-
source=f"custom_{call['name']}",
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
# Yield custom tool response status
|
|
316
|
-
yield StreamChunk(
|
|
317
|
-
type="custom_tool_status",
|
|
318
|
-
status="custom_tool_response",
|
|
319
|
-
content=f"✅ [Custom Tool] {call['name']} completed",
|
|
320
|
-
source=f"custom_{call['name']}",
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
processed_call_ids.add(call["call_id"])
|
|
324
|
-
functions_executed = True
|
|
325
|
-
logger.info(f"Executed custom tool: {call['name']}")
|
|
326
|
-
|
|
327
|
-
except Exception as e:
|
|
328
|
-
logger.error(f"Error executing custom tool {call['name']}: {e}")
|
|
329
|
-
error_msg = f"Error executing {call['name']}: {str(e)}"
|
|
330
|
-
|
|
331
|
-
# Yield error with arguments shown
|
|
332
|
-
yield StreamChunk(
|
|
333
|
-
type="custom_tool_status",
|
|
334
|
-
status="function_call",
|
|
335
|
-
content=f"Arguments for Calling {call['name']}: {call['arguments']}",
|
|
336
|
-
source=f"custom_{call['name']}",
|
|
337
|
-
)
|
|
335
|
+
# Create tool execution configuration objects
|
|
336
|
+
custom_tool_config = ToolExecutionConfig(
|
|
337
|
+
tool_type="custom",
|
|
338
|
+
chunk_type="custom_tool_status",
|
|
339
|
+
emoji_prefix="🔧 [Custom Tool]",
|
|
340
|
+
success_emoji="✅ [Custom Tool]",
|
|
341
|
+
error_emoji="❌ [Custom Tool Error]",
|
|
342
|
+
source_prefix="custom_",
|
|
343
|
+
status_called="custom_tool_called",
|
|
344
|
+
status_response="custom_tool_response",
|
|
345
|
+
status_error="custom_tool_error",
|
|
346
|
+
execution_callback=self._execute_custom_tool,
|
|
347
|
+
)
|
|
338
348
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
349
|
+
mcp_tool_config = ToolExecutionConfig(
|
|
350
|
+
tool_type="mcp",
|
|
351
|
+
chunk_type="mcp_status",
|
|
352
|
+
emoji_prefix="🔧 [MCP Tool]",
|
|
353
|
+
success_emoji="✅ [MCP Tool]",
|
|
354
|
+
error_emoji="❌ [MCP Tool Error]",
|
|
355
|
+
source_prefix="mcp_",
|
|
356
|
+
status_called="mcp_tool_called",
|
|
357
|
+
status_response="mcp_tool_response",
|
|
358
|
+
status_error="mcp_tool_error",
|
|
359
|
+
execution_callback=self._execute_mcp_function_with_retry,
|
|
360
|
+
)
|
|
345
361
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
updated_messages.append(error_output_msg)
|
|
353
|
-
processed_call_ids.add(call["call_id"])
|
|
354
|
-
functions_executed = True
|
|
362
|
+
# Execute custom tools using unified method
|
|
363
|
+
for call in custom_calls:
|
|
364
|
+
async for chunk in self._execute_tool_with_logging(call, custom_tool_config, updated_messages, processed_call_ids):
|
|
365
|
+
yield chunk
|
|
366
|
+
functions_executed = True
|
|
355
367
|
|
|
356
|
-
# Execute MCP
|
|
357
|
-
mcp_functions_executed = False
|
|
368
|
+
# Execute MCP tools using unified method
|
|
358
369
|
for call in mcp_calls:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
type="mcp_status",
|
|
363
|
-
status="mcp_tool_called",
|
|
364
|
-
content=f"🔧 [MCP Tool] Calling {function_name}...",
|
|
365
|
-
source=f"mcp_{function_name}",
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
# Yield detailed MCP status as StreamChunk
|
|
369
|
-
tools_info = f" ({len(self._mcp_functions)} tools available)" if self._mcp_functions else ""
|
|
370
|
-
yield StreamChunk(
|
|
371
|
-
type="mcp_status",
|
|
372
|
-
status="mcp_tools_initiated",
|
|
373
|
-
content=f"MCP tool call initiated (call #{self._mcp_tool_calls_count}){tools_info}: {function_name}",
|
|
374
|
-
source=f"mcp_{function_name}",
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
try:
|
|
378
|
-
# Execute MCP function with retry and exponential backoff
|
|
379
|
-
result_str, result_obj = await self._execute_mcp_function_with_retry(function_name, call["arguments"])
|
|
380
|
-
|
|
381
|
-
# Check if function failed after all retries
|
|
382
|
-
if isinstance(result_str, str) and result_str.startswith("Error:"):
|
|
383
|
-
# Log failure but still create tool response
|
|
384
|
-
logger.warning(f"MCP function {function_name} failed after retries: {result_str}")
|
|
385
|
-
|
|
386
|
-
# Add error result to messages
|
|
387
|
-
function_output_msg = {
|
|
388
|
-
"role": "tool",
|
|
389
|
-
"tool_call_id": call["call_id"],
|
|
390
|
-
"content": result_str,
|
|
391
|
-
}
|
|
392
|
-
updated_messages.append(function_output_msg)
|
|
393
|
-
|
|
394
|
-
processed_call_ids.add(call["call_id"])
|
|
395
|
-
mcp_functions_executed = True
|
|
396
|
-
continue
|
|
397
|
-
|
|
398
|
-
except Exception as e:
|
|
399
|
-
# Only catch unexpected non-MCP system errors
|
|
400
|
-
logger.error(f"Unexpected error in MCP function execution: {e}")
|
|
401
|
-
error_msg = f"Error executing {function_name}: {str(e)}"
|
|
402
|
-
|
|
403
|
-
# Add error result to messages
|
|
404
|
-
function_output_msg = {
|
|
405
|
-
"role": "tool",
|
|
406
|
-
"tool_call_id": call["call_id"],
|
|
407
|
-
"content": error_msg,
|
|
408
|
-
}
|
|
409
|
-
updated_messages.append(function_output_msg)
|
|
410
|
-
|
|
411
|
-
processed_call_ids.add(call["call_id"])
|
|
412
|
-
mcp_functions_executed = True
|
|
413
|
-
continue
|
|
414
|
-
|
|
415
|
-
# Yield function_call status
|
|
416
|
-
yield StreamChunk(
|
|
417
|
-
type="mcp_status",
|
|
418
|
-
status="function_call",
|
|
419
|
-
content=f"Arguments for Calling {function_name}: {call['arguments']}",
|
|
420
|
-
source=f"mcp_{function_name}",
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
# Add function output to messages and yield status chunk
|
|
424
|
-
function_output_msg = {
|
|
425
|
-
"role": "tool",
|
|
426
|
-
"tool_call_id": call["call_id"],
|
|
427
|
-
"content": str(result_str),
|
|
428
|
-
}
|
|
429
|
-
updated_messages.append(function_output_msg)
|
|
430
|
-
|
|
431
|
-
# Yield function_call_output status with preview
|
|
432
|
-
result_text = str(result_str)
|
|
433
|
-
if hasattr(result_obj, "content") and result_obj.content:
|
|
434
|
-
if isinstance(result_obj.content, list) and len(result_obj.content) > 0:
|
|
435
|
-
first_item = result_obj.content[0]
|
|
436
|
-
if hasattr(first_item, "text"):
|
|
437
|
-
result_text = first_item.text
|
|
438
|
-
|
|
439
|
-
yield StreamChunk(
|
|
440
|
-
type="mcp_status",
|
|
441
|
-
status="function_call_output",
|
|
442
|
-
content=f"Results for Calling {function_name}: {result_text}",
|
|
443
|
-
source=f"mcp_{function_name}",
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
logger.info(f"Executed MCP function {function_name} (stdio/streamable-http)")
|
|
447
|
-
processed_call_ids.add(call["call_id"])
|
|
448
|
-
|
|
449
|
-
# Yield MCP tool response status
|
|
450
|
-
yield StreamChunk(
|
|
451
|
-
type="mcp_status",
|
|
452
|
-
status="mcp_tool_response",
|
|
453
|
-
content=f"✅ [MCP Tool] {function_name} completed",
|
|
454
|
-
source=f"mcp_{function_name}",
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
mcp_functions_executed = True
|
|
458
|
-
functions_executed = True
|
|
370
|
+
async for chunk in self._execute_tool_with_logging(call, mcp_tool_config, updated_messages, processed_call_ids):
|
|
371
|
+
yield chunk
|
|
372
|
+
functions_executed = True
|
|
459
373
|
|
|
460
374
|
# Ensure all captured function calls have results to prevent hanging
|
|
461
375
|
for call in captured_function_calls:
|
|
@@ -469,10 +383,10 @@ class ChatCompletionsBackend(CustomToolAndMCPBackend):
|
|
|
469
383
|
"content": f"Error: Tool call {call['call_id']} for function {call['name']} was not processed. This may indicate a validation or execution error.",
|
|
470
384
|
}
|
|
471
385
|
updated_messages.append(error_output_msg)
|
|
472
|
-
|
|
386
|
+
functions_executed = True
|
|
473
387
|
|
|
474
388
|
# Trim history after function executions to bound memory usage
|
|
475
|
-
if functions_executed
|
|
389
|
+
if functions_executed:
|
|
476
390
|
updated_messages = self._trim_message_history(updated_messages)
|
|
477
391
|
|
|
478
392
|
# Recursive call with updated messages
|