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
@@ -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 CustomToolAndMCPBackend
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
- yield StreamChunk(type="done")
225
- return
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
- yield StreamChunk(type="done")
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
- # Execute custom tools first
278
- for call in custom_calls:
279
- try:
280
- # Yield custom tool call status
281
- yield StreamChunk(
282
- type="custom_tool_status",
283
- status="custom_tool_called",
284
- content=f"🔧 [Custom Tool] Calling {call['name']}...",
285
- source=f"custom_{call['name']}",
286
- )
287
-
288
- # Yield custom tool arguments
289
- yield StreamChunk(
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
- yield StreamChunk(
340
- type="custom_tool_status",
341
- status="custom_tool_error",
342
- content=f" [Custom Tool Error] {error_msg}",
343
- source=f"custom_{call['name']}",
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
- # Add error result to messages
347
- error_output_msg = {
348
- "role": "tool",
349
- "tool_call_id": call["call_id"],
350
- "content": error_msg,
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 function calls
357
- mcp_functions_executed = False
368
+ # Execute MCP tools using unified method
358
369
  for call in mcp_calls:
359
- function_name = call["name"]
360
- if function_name in self._mcp_functions:
361
- yield StreamChunk(
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
- mcp_functions_executed = True
386
+ functions_executed = True
473
387
 
474
388
  # Trim history after function executions to bound memory usage
475
- if functions_executed or mcp_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