massgen 0.1.2__py3-none-any.whl → 0.1.4__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 (82) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/agent_config.py +33 -7
  3. massgen/api_params_handler/_api_params_handler_base.py +3 -0
  4. massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
  5. massgen/api_params_handler/_claude_api_params_handler.py +4 -0
  6. massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
  7. massgen/api_params_handler/_response_api_params_handler.py +4 -0
  8. massgen/backend/azure_openai.py +9 -1
  9. massgen/backend/base.py +4 -0
  10. massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
  11. massgen/backend/claude_code.py +9 -1
  12. massgen/backend/docs/permissions_and_context_files.md +2 -2
  13. massgen/backend/gemini.py +35 -6
  14. massgen/backend/gemini_utils.py +30 -0
  15. massgen/backend/response.py +2 -0
  16. massgen/chat_agent.py +9 -3
  17. massgen/cli.py +291 -43
  18. massgen/config_builder.py +163 -18
  19. massgen/configs/README.md +69 -14
  20. massgen/configs/debug/restart_test_controlled.yaml +60 -0
  21. massgen/configs/debug/restart_test_controlled_filesystem.yaml +73 -0
  22. massgen/configs/tools/code-execution/docker_with_sudo.yaml +35 -0
  23. massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +56 -0
  24. massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +65 -0
  25. massgen/configs/tools/custom_tools/computer_use_example.yaml +50 -0
  26. massgen/configs/tools/custom_tools/crawl4ai_example.yaml +55 -0
  27. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
  28. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
  29. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
  30. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
  31. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
  32. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
  33. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
  34. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
  35. massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +33 -0
  36. massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +34 -0
  37. massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +33 -0
  38. massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +34 -0
  39. massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +59 -0
  40. massgen/docker/README.md +83 -0
  41. massgen/filesystem_manager/_code_execution_server.py +22 -7
  42. massgen/filesystem_manager/_docker_manager.py +21 -1
  43. massgen/filesystem_manager/_filesystem_manager.py +9 -0
  44. massgen/filesystem_manager/_path_permission_manager.py +148 -0
  45. massgen/filesystem_manager/_workspace_tools_server.py +0 -997
  46. massgen/formatter/_gemini_formatter.py +73 -0
  47. massgen/frontend/coordination_ui.py +175 -257
  48. massgen/frontend/displays/base_display.py +29 -0
  49. massgen/frontend/displays/rich_terminal_display.py +155 -9
  50. massgen/frontend/displays/simple_display.py +21 -0
  51. massgen/frontend/displays/terminal_display.py +22 -2
  52. massgen/logger_config.py +50 -6
  53. massgen/message_templates.py +283 -15
  54. massgen/orchestrator.py +335 -38
  55. massgen/tests/test_binary_file_blocking.py +274 -0
  56. massgen/tests/test_case_studies.md +12 -12
  57. massgen/tests/test_code_execution.py +178 -0
  58. massgen/tests/test_multimodal_size_limits.py +407 -0
  59. massgen/tests/test_orchestration_restart.py +204 -0
  60. massgen/tool/__init__.py +4 -0
  61. massgen/tool/_manager.py +7 -2
  62. massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
  63. massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
  64. massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
  65. massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
  66. massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
  67. massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
  68. massgen/tool/_multimodal_tools/understand_audio.py +211 -0
  69. massgen/tool/_multimodal_tools/understand_file.py +555 -0
  70. massgen/tool/_multimodal_tools/understand_image.py +316 -0
  71. massgen/tool/_multimodal_tools/understand_video.py +340 -0
  72. massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
  73. massgen/tool/docs/multimodal_tools.md +1368 -0
  74. massgen/tool/workflow_toolkits/__init__.py +26 -0
  75. massgen/tool/workflow_toolkits/post_evaluation.py +216 -0
  76. massgen/utils.py +1 -0
  77. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/METADATA +101 -69
  78. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/RECORD +82 -46
  79. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/WHEEL +0 -0
  80. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/entry_points.txt +0 -0
  81. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/licenses/LICENSE +0 -0
  82. {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/top_level.txt +0 -0
massgen/__init__.py CHANGED
@@ -68,7 +68,7 @@ from .chat_agent import (
68
68
  from .message_templates import MessageTemplates, get_templates
69
69
  from .orchestrator import Orchestrator, create_orchestrator
70
70
 
71
- __version__ = "0.1.2"
71
+ __version__ = "0.1.4"
72
72
  __author__ = "MassGen Contributors"
73
73
 
74
74
 
massgen/agent_config.py CHANGED
@@ -35,12 +35,17 @@ class CoordinationConfig:
35
35
  Only the winning agent executes actions during final presentation.
36
36
  If False, agents execute actions during coordination (default behavior).
37
37
  planning_mode_instruction: Custom instruction to add when planning mode is enabled.
38
+ max_orchestration_restarts: Maximum number of times orchestration can be restarted after
39
+ post-evaluation determines the answer is insufficient.
40
+ For example, max_orchestration_restarts=2 allows 3 total attempts
41
+ (initial + 2 restarts). Default is 0 (no restarts).
38
42
  """
39
43
 
40
44
  enable_planning_mode: bool = False
41
45
  planning_mode_instruction: str = (
42
46
  "During coordination, describe what you would do without actually executing actions. Only provide concrete implementation details without calling external APIs or tools."
43
47
  )
48
+ max_orchestration_restarts: int = 0
44
49
 
45
50
 
46
51
  @dataclass
@@ -87,6 +92,9 @@ class AgentConfig:
87
92
  # Debug/test mode - skip coordination rounds and go straight to final presentation
88
93
  skip_coordination_rounds: bool = False
89
94
 
95
+ # Debug mode for restart feature - override final answer on attempt 1 only
96
+ debug_final_answer: Optional[str] = None
97
+
90
98
  @property
91
99
  def custom_system_instruction(self) -> Optional[str]:
92
100
  """
@@ -432,7 +440,8 @@ class AgentConfig:
432
440
  import copy
433
441
 
434
442
  new_config = copy.deepcopy(self)
435
- new_config.custom_system_instruction = instruction
443
+ # Set private attribute directly to avoid deprecation warning
444
+ new_config._custom_system_instruction = instruction
436
445
  return new_config
437
446
 
438
447
  def with_agent_id(self, agent_id: str) -> "AgentConfig":
@@ -538,7 +547,8 @@ class AgentConfig:
538
547
  else:
539
548
  raise ValueError(f"Domain expert configuration not available for backend: {backend}")
540
549
 
541
- config.custom_system_instruction = instruction
550
+ # Set private attribute directly to avoid deprecation warning
551
+ config._custom_system_instruction = instruction
542
552
  return config
543
553
 
544
554
  # =============================================================================
@@ -567,9 +577,10 @@ class AgentConfig:
567
577
  conversation = templates.build_initial_conversation(task=task, agent_summaries=agent_summaries, valid_agent_ids=valid_agent_ids)
568
578
 
569
579
  # Add custom system instruction if provided
570
- if self.custom_system_instruction:
580
+ # Access private attribute to avoid deprecation warning
581
+ if self._custom_system_instruction:
571
582
  base_system = conversation["system_message"]
572
- conversation["system_message"] = f"{self.custom_system_instruction}\n\n{base_system}"
583
+ conversation["system_message"] = f"{self._custom_system_instruction}\n\n{base_system}"
573
584
 
574
585
  # Add backend configuration
575
586
  conversation.update(
@@ -703,7 +714,8 @@ class AgentConfig:
703
714
  result = {
704
715
  "backend_params": self.backend_params,
705
716
  "agent_id": self.agent_id,
706
- "custom_system_instruction": self.custom_system_instruction,
717
+ # Access private attribute to avoid deprecation warning
718
+ "custom_system_instruction": self._custom_system_instruction,
707
719
  "voting_sensitivity": self.voting_sensitivity,
708
720
  "max_new_answers_per_agent": self.max_new_answers_per_agent,
709
721
  "answer_novelty_requirement": self.answer_novelty_requirement,
@@ -716,8 +728,12 @@ class AgentConfig:
716
728
  result["coordination_config"] = {
717
729
  "enable_planning_mode": self.coordination_config.enable_planning_mode,
718
730
  "planning_mode_instruction": self.coordination_config.planning_mode_instruction,
731
+ "max_orchestration_restarts": self.coordination_config.max_orchestration_restarts,
719
732
  }
720
733
 
734
+ # Handle debug fields
735
+ result["debug_final_answer"] = self.debug_final_answer
736
+
721
737
  # Handle message_templates serialization
722
738
  if self.message_templates is not None:
723
739
  try:
@@ -757,6 +773,9 @@ class AgentConfig:
757
773
  if coordination_data:
758
774
  coordination_config = CoordinationConfig(**coordination_data)
759
775
 
776
+ # Handle debug fields
777
+ debug_final_answer = data.get("debug_final_answer")
778
+
760
779
  # Handle message_templates
761
780
  message_templates = None
762
781
  template_data = data.get("message_templates")
@@ -765,17 +784,24 @@ class AgentConfig:
765
784
 
766
785
  message_templates = MessageTemplates(**template_data)
767
786
 
768
- return cls(
787
+ config = cls(
769
788
  backend_params=backend_params,
770
789
  message_templates=message_templates,
771
790
  agent_id=agent_id,
772
- custom_system_instruction=custom_system_instruction,
773
791
  voting_sensitivity=voting_sensitivity,
774
792
  max_new_answers_per_agent=max_new_answers_per_agent,
775
793
  answer_novelty_requirement=answer_novelty_requirement,
776
794
  timeout_config=timeout_config,
777
795
  coordination_config=coordination_config,
778
796
  )
797
+ config.debug_final_answer = debug_final_answer
798
+ return config
799
+
800
+ # Set custom_system_instruction separately to avoid deprecation warning
801
+ if custom_system_instruction is not None:
802
+ config._custom_system_instruction = custom_system_instruction
803
+
804
+ return config
779
805
 
780
806
 
781
807
  # =============================================================================
@@ -56,8 +56,10 @@ class APIParamsHandlerBase(ABC):
56
56
  # Filesystem manager parameters (handled by base class)
57
57
  "cwd",
58
58
  "agent_temporary_workspace",
59
+ "agent_temporary_workspace_parent",
59
60
  "context_paths",
60
61
  "context_write_access_enabled",
62
+ "enforce_read_before_delete",
61
63
  "enable_image_generation",
62
64
  "enable_mcp_command_line",
63
65
  "command_line_allowed_commands",
@@ -67,6 +69,7 @@ class APIParamsHandlerBase(ABC):
67
69
  "command_line_docker_memory_limit",
68
70
  "command_line_docker_cpu_limit",
69
71
  "command_line_docker_network_mode",
72
+ "command_line_docker_enable_sudo",
70
73
  # Backend identification (handled by orchestrator)
71
74
  "enable_audio_generation", # Audio generation parameter
72
75
  "type",
@@ -24,6 +24,10 @@ class ChatCompletionsAPIParamsHandler(APIParamsHandlerBase):
24
24
  "allowed_tools",
25
25
  "exclude_tools",
26
26
  "custom_tools", # Custom tools configuration (processed separately)
27
+ "enable_file_generation", # Internal flag for file generation (used in system messages only)
28
+ "enable_image_generation", # Internal flag for image generation (used in system messages only)
29
+ "enable_audio_generation", # Internal flag for audio generation (used in system messages only)
30
+ "enable_video_generation", # Internal flag for video generation (used in system messages only)
27
31
  },
28
32
  )
29
33
 
@@ -24,6 +24,10 @@ class ClaudeAPIParamsHandler(APIParamsHandlerBase):
24
24
  "exclude_tools",
25
25
  "custom_tools", # Custom tools configuration (processed separately)
26
26
  "_has_files_api_files",
27
+ "enable_file_generation", # Internal flag for file generation (used in system messages only)
28
+ "enable_image_generation", # Internal flag for image generation (used in system messages only)
29
+ "enable_audio_generation", # Internal flag for audio generation (used in system messages only)
30
+ "enable_video_generation", # Internal flag for video generation (used in system messages only)
27
31
  },
28
32
  )
29
33
 
@@ -19,6 +19,10 @@ class GeminiAPIParamsHandler(APIParamsHandlerBase):
19
19
  "allowed_tools",
20
20
  "exclude_tools",
21
21
  "custom_tools",
22
+ "enable_file_generation", # Internal flag for file generation (used in system messages only)
23
+ "enable_image_generation", # Internal flag for image generation (used in system messages only)
24
+ "enable_audio_generation", # Internal flag for audio generation (used in system messages only)
25
+ "enable_video_generation", # Internal flag for video generation (used in system messages only)
22
26
  }
23
27
  return set(base) | extra
24
28
 
@@ -24,6 +24,10 @@ class ResponseAPIParamsHandler(APIParamsHandlerBase):
24
24
  "exclude_tools",
25
25
  "custom_tools", # Custom tools configuration (processed separately)
26
26
  "_has_file_search_files", # Internal flag for file search tracking
27
+ "enable_file_generation", # Internal flag for file generation (used in system messages only)
28
+ "enable_image_generation", # Internal flag for image generation (used in system messages only)
29
+ "enable_audio_generation", # Internal flag for audio generation (used in system messages only)
30
+ "enable_video_generation", # Internal flag for video generation (used in system messages only)
27
31
  },
28
32
  )
29
33
 
@@ -94,7 +94,7 @@ class AzureOpenAIBackend(LLMBackend):
94
94
  raise ValueError("Azure OpenAI requires a deployment name. Pass it as the 'model' parameter.")
95
95
 
96
96
  # Check if workflow tools are present
97
- workflow_tools = [t for t in tools if t.get("function", {}).get("name") in ["new_answer", "vote"]] if tools else []
97
+ workflow_tools = [t for t in tools if t.get("function", {}).get("name") in ["new_answer", "vote", "submit", "restart_orchestration"]] if tools else []
98
98
  has_workflow_tools = len(workflow_tools) > 0
99
99
 
100
100
  # Modify messages to include workflow tool instructions if needed
@@ -270,6 +270,14 @@ class AzureOpenAIBackend(LLMBackend):
270
270
  system_parts.append(f' Usage: {{"tool_name": "vote", ' f'"arguments": {{"agent_id": "agent1", ' f'"reason": "explanation"}}}} // Choose agent_id from: {agent_list}')
271
271
  else:
272
272
  system_parts.append(' Usage: {"tool_name": "vote", ' '"arguments": {"agent_id": "agent1", ' '"reason": "explanation"}}')
273
+ elif name == "submit":
274
+ system_parts.append(
275
+ ' Usage: {"tool_name": "submit", ' '"arguments": {"confirmed": true}}',
276
+ )
277
+ elif name == "restart_orchestration":
278
+ system_parts.append(
279
+ ' Usage: {"tool_name": "restart_orchestration", ' '"arguments": {"reason": "The answer is incomplete because...", ' '"instructions": "In the next attempt, please..."}}',
280
+ )
273
281
 
274
282
  system_parts.append("\n--- MassGen Workflow Instructions ---")
275
283
  system_parts.append("IMPORTANT: You must respond with a structured JSON decision at the end of your response.")
massgen/backend/base.py CHANGED
@@ -112,6 +112,7 @@ class LLMBackend(ABC):
112
112
  "command_line_docker_memory_limit": kwargs.get("command_line_docker_memory_limit"),
113
113
  "command_line_docker_cpu_limit": kwargs.get("command_line_docker_cpu_limit"),
114
114
  "command_line_docker_network_mode": network_mode,
115
+ "command_line_docker_enable_sudo": kwargs.get("command_line_docker_enable_sudo", False),
115
116
  "enable_audio_generation": kwargs.get("enable_audio_generation", False),
116
117
  }
117
118
 
@@ -188,8 +189,10 @@ class LLMBackend(ABC):
188
189
  # Filesystem manager parameters (handled by base class)
189
190
  "cwd",
190
191
  "agent_temporary_workspace",
192
+ "agent_temporary_workspace_parent",
191
193
  "context_paths",
192
194
  "context_write_access_enabled",
195
+ "enforce_read_before_delete",
193
196
  "enable_image_generation",
194
197
  "enable_mcp_command_line",
195
198
  "command_line_allowed_commands",
@@ -199,6 +202,7 @@ class LLMBackend(ABC):
199
202
  "command_line_docker_memory_limit",
200
203
  "command_line_docker_cpu_limit",
201
204
  "command_line_docker_network_mode",
205
+ "command_line_docker_enable_sudo",
202
206
  # Backend identification (handled by orchestrator)
203
207
  "type",
204
208
  "agent_id",
@@ -284,9 +284,19 @@ class CustomToolAndMCPBackend(LLMBackend):
284
284
 
285
285
  # Register each function with its corresponding values
286
286
  for i, func in enumerate(functions):
287
+ # Inject agent_cwd into preset_args if filesystem_manager is available
288
+ final_preset_args = preset_args_list[i].copy() if preset_args_list[i] else {}
289
+ if self.filesystem_manager and self.filesystem_manager.cwd:
290
+ final_preset_args["agent_cwd"] = self.filesystem_manager.cwd
291
+ logger.info(f"Injecting agent_cwd for {func}: {self.filesystem_manager.cwd}")
292
+ elif self.filesystem_manager:
293
+ logger.warning(f"filesystem_manager exists but cwd is None for {func}")
294
+ else:
295
+ logger.warning(f"No filesystem_manager available for {func}")
296
+
287
297
  # Load the function first if custom name is needed
288
298
  if names[i] and names[i] != func:
289
- # Need to load function and apply custom name
299
+ # Load function to apply custom name
290
300
  if path:
291
301
  loaded_func = self.custom_tool_manager._load_function_from_path(path, func)
292
302
  else:
@@ -296,7 +306,6 @@ class CustomToolAndMCPBackend(LLMBackend):
296
306
  logger.error(f"Could not load function '{func}' from path: {path}")
297
307
  continue
298
308
 
299
- # Apply custom name by modifying __name__ attribute
300
309
  loaded_func.__name__ = names[i]
301
310
 
302
311
  # Register with loaded function (no path needed)
@@ -304,7 +313,7 @@ class CustomToolAndMCPBackend(LLMBackend):
304
313
  path=None,
305
314
  func=loaded_func,
306
315
  category=category,
307
- preset_args=preset_args_list[i],
316
+ preset_args=final_preset_args,
308
317
  description=descriptions[i],
309
318
  )
310
319
  else:
@@ -313,7 +322,7 @@ class CustomToolAndMCPBackend(LLMBackend):
313
322
  path=path,
314
323
  func=func,
315
324
  category=category,
316
- preset_args=preset_args_list[i],
325
+ preset_args=final_preset_args,
317
326
  description=descriptions[i],
318
327
  )
319
328
 
@@ -404,9 +413,19 @@ class CustomToolAndMCPBackend(LLMBackend):
404
413
  """
405
414
  import json
406
415
 
416
+ # Parse arguments
417
+ arguments = json.loads(call["arguments"]) if isinstance(call["arguments"], str) else call["arguments"]
418
+
419
+ # Ensure agent_cwd is always injected if filesystem_manager is available
420
+ # This provides a fallback in case preset_args didn't work during registration
421
+ if self.filesystem_manager and self.filesystem_manager.cwd:
422
+ if "agent_cwd" not in arguments or arguments.get("agent_cwd") is None:
423
+ arguments["agent_cwd"] = self.filesystem_manager.cwd
424
+ logger.info(f"Dynamically injected agent_cwd at execution time: {self.filesystem_manager.cwd}")
425
+
407
426
  tool_request = {
408
427
  "name": call["name"],
409
- "input": json.loads(call["arguments"]) if isinstance(call["arguments"], str) else call["arguments"],
428
+ "input": arguments,
410
429
  }
411
430
 
412
431
  result_text = ""
@@ -1120,6 +1139,7 @@ class CustomToolAndMCPBackend(LLMBackend):
1120
1139
  **kwargs,
1121
1140
  ) -> AsyncGenerator[StreamChunk, None]:
1122
1141
  """Simple passthrough streaming without MCP processing."""
1142
+
1123
1143
  agent_id = kwargs.get("agent_id", None)
1124
1144
  all_params = {**self.config, **kwargs}
1125
1145
  processed_messages = await self._process_upload_files(messages, all_params)
@@ -795,7 +795,7 @@ class ClaudeCodeBackend(LLMBackend):
795
795
 
796
796
  # Add workflow tools information if present
797
797
  if tools:
798
- workflow_tools = [t for t in tools if t.get("function", {}).get("name") in ["new_answer", "vote"]]
798
+ workflow_tools = [t for t in tools if t.get("function", {}).get("name") in ["new_answer", "vote", "submit", "restart_orchestration"]]
799
799
  if workflow_tools:
800
800
  system_parts.append("\n--- Coordination Actions ---")
801
801
  for tool in workflow_tools:
@@ -823,6 +823,14 @@ class ClaudeCodeBackend(LLMBackend):
823
823
  system_parts.append(f' Usage: {{"tool_name": "vote", ' f'"arguments": {{"agent_id": "agent1", ' f'"reason": "explanation"}}}} // Choose agent_id from: {agent_list}')
824
824
  else:
825
825
  system_parts.append(' Usage: {"tool_name": "vote", ' '"arguments": {"agent_id": "agent1", ' '"reason": "explanation"}}')
826
+ elif name == "submit":
827
+ system_parts.append(
828
+ ' Usage: {"tool_name": "submit", ' '"arguments": {"confirmed": true}}',
829
+ )
830
+ elif name == "restart_orchestration":
831
+ system_parts.append(
832
+ ' Usage: {"tool_name": "restart_orchestration", ' '"arguments": {"reason": "The answer is incomplete because...", ' '"instructions": "In the next attempt, please..."}}',
833
+ )
826
834
 
827
835
  system_parts.append("\n--- MassGen Coordination Instructions ---")
828
836
  system_parts.append("IMPORTANT: You must respond with a structured JSON decision at the end of your response.")
@@ -1067,8 +1067,8 @@ Files delivered:
1067
1067
  - **Multi-Turn Design**: `docs/dev_notes/multi_turn_filesystem_design.md` - Detailed architecture for session persistence and turn-based workflows
1068
1068
  - **MCP Integration**: `docs/dev_notes/gemini_filesystem_mcp_design.md` - How filesystem access works through Model Context Protocol
1069
1069
  - **Context Sharing**: `docs/dev_notes/v0.0.14-context.md` - Original context sharing design
1070
- - **User Context Paths**: `docs/case_studies/user-context-path-support-with-copy-mcp.md` - Case study on adding user-specified paths
1071
- - **Claude Code Workspace**: `docs/case_studies/claude-code-workspace-management.md` - Native filesystem integration patterns
1070
+ - **User Context Paths**: `docs/source/examples/case_studies/user-context-path-support-with-copy-mcp.md` - Case study on adding user-specified paths
1071
+ - **Claude Code Workspace**: `docs/source/examples/case_studies/claude-code-workspace-management.md` - Native filesystem integration patterns
1072
1072
 
1073
1073
  ## Conclusion
1074
1074
 
massgen/backend/gemini.py CHANGED
@@ -20,6 +20,7 @@ TECHNICAL SOLUTION:
20
20
  """
21
21
 
22
22
  import json
23
+ import logging
23
24
  import os
24
25
  import time
25
26
  from typing import Any, AsyncGenerator, Dict, List, Optional
@@ -39,6 +40,19 @@ from .gemini_mcp_manager import GeminiMCPManager
39
40
  from .gemini_trackers import MCPCallTracker, MCPResponseExtractor, MCPResponseTracker
40
41
  from .gemini_utils import CoordinationResponse
41
42
 
43
+
44
+ # Suppress Gemini SDK logger warning about non-text parts in response
45
+ # Using custom filter per https://github.com/googleapis/python-genai/issues/850
46
+ class NoFunctionCallWarning(logging.Filter):
47
+ def filter(self, record: logging.LogRecord) -> bool:
48
+ message = record.getMessage()
49
+ if "there are non-text parts in the response:" in message:
50
+ return False
51
+ return True
52
+
53
+
54
+ logging.getLogger("google_genai.types").addFilter(NoFunctionCallWarning())
55
+
42
56
  try:
43
57
  from pydantic import BaseModel, Field
44
58
  except ImportError:
@@ -220,6 +234,7 @@ class GeminiBackend(CustomToolAndMCPBackend):
220
234
 
221
235
  # Analyze tool types
222
236
  is_coordination = self.formatter.has_coordination_tools(tools)
237
+ is_post_evaluation = self.formatter.has_post_evaluation_tools(tools)
223
238
 
224
239
  valid_agent_ids = None
225
240
 
@@ -239,6 +254,9 @@ class GeminiBackend(CustomToolAndMCPBackend):
239
254
  # For coordination requests, modify the prompt to use structured output
240
255
  if is_coordination:
241
256
  full_content = self.formatter.build_structured_output_prompt(full_content, valid_agent_ids)
257
+ elif is_post_evaluation:
258
+ # For post-evaluation, modify prompt to use structured output
259
+ full_content = self.formatter.build_post_evaluation_prompt(full_content)
242
260
 
243
261
  # Use google-genai package
244
262
  client = genai.Client(api_key=self.api_key)
@@ -277,6 +295,16 @@ class GeminiBackend(CustomToolAndMCPBackend):
277
295
  else:
278
296
  # Tools or sessions are present; fallback to text parsing
279
297
  pass
298
+ elif is_post_evaluation:
299
+ # For post-evaluation, use JSON response format for structured decisions
300
+ from .gemini_utils import PostEvaluationResponse
301
+
302
+ if (not using_sdk_mcp) and (not using_custom_tools) and (not all_tools):
303
+ config["response_mime_type"] = "application/json"
304
+ config["response_schema"] = PostEvaluationResponse.model_json_schema()
305
+ else:
306
+ # Tools or sessions are present; fallback to text parsing
307
+ pass
280
308
  # Log messages being sent after builtin_tools is defined
281
309
  log_backend_agent_message(
282
310
  agent_id or "default",
@@ -1603,11 +1631,11 @@ class GeminiBackend(CustomToolAndMCPBackend):
1603
1631
 
1604
1632
  content = full_content_text
1605
1633
 
1606
- # Process tool calls - only coordination tool calls (MCP manual mode removed)
1634
+ # Process tool calls - coordination and post-evaluation tool calls (MCP manual mode removed)
1607
1635
  tool_calls_detected: List[Dict[str, Any]] = []
1608
1636
 
1609
- # Then, process coordination tools if present
1610
- if is_coordination and content.strip() and not tool_calls_detected:
1637
+ # Process coordination tools OR post-evaluation tools if present
1638
+ if (is_coordination or is_post_evaluation) and content.strip() and not tool_calls_detected:
1611
1639
  # For structured output mode, the entire content is JSON
1612
1640
  structured_response = None
1613
1641
  # Try multiple parsing strategies
@@ -1626,14 +1654,15 @@ class GeminiBackend(CustomToolAndMCPBackend):
1626
1654
  # Log conversion to tool calls (summary)
1627
1655
  log_stream_chunk("backend.gemini", "tool_calls", tool_calls, agent_id)
1628
1656
 
1629
- # Log each coordination tool call for analytics/debugging
1657
+ # Log each tool call for analytics/debugging
1658
+ tool_type = "post_evaluation" if is_post_evaluation else "coordination"
1630
1659
  try:
1631
1660
  for tool_call in tool_calls:
1632
1661
  log_tool_call(
1633
1662
  agent_id,
1634
- tool_call.get("function", {}).get("name", "unknown_coordination_tool"),
1663
+ tool_call.get("function", {}).get("name", f"unknown_{tool_type}_tool"),
1635
1664
  tool_call.get("function", {}).get("arguments", {}),
1636
- result="coordination_tool_called",
1665
+ result=f"{tool_type}_tool_called",
1637
1666
  backend_name="gemini",
1638
1667
  )
1639
1668
  except Exception:
@@ -20,6 +20,13 @@ class ActionType(enum.Enum):
20
20
  NEW_ANSWER = "new_answer"
21
21
 
22
22
 
23
+ class PostEvaluationActionType(enum.Enum):
24
+ """Action types for post-evaluation structured output."""
25
+
26
+ SUBMIT = "submit"
27
+ RESTART = "restart"
28
+
29
+
23
30
  class VoteAction(BaseModel):
24
31
  """Structured output for voting action."""
25
32
 
@@ -41,3 +48,26 @@ class CoordinationResponse(BaseModel):
41
48
  action_type: ActionType = Field(description="Type of action to take")
42
49
  vote_data: Optional[VoteAction] = Field(default=None, description="Vote data if action is vote")
43
50
  answer_data: Optional[NewAnswerAction] = Field(default=None, description="Answer data if action is new_answer")
51
+
52
+
53
+ class SubmitAction(BaseModel):
54
+ """Structured output for submit action (post-evaluation)."""
55
+
56
+ action: PostEvaluationActionType = Field(default=PostEvaluationActionType.SUBMIT, description="Action type")
57
+ confirmed: bool = Field(default=True, description="Confirmation that answer is satisfactory")
58
+
59
+
60
+ class RestartAction(BaseModel):
61
+ """Structured output for restart action (post-evaluation)."""
62
+
63
+ action: PostEvaluationActionType = Field(default=PostEvaluationActionType.RESTART, description="Action type")
64
+ reason: str = Field(description="Clear explanation of why the answer is insufficient")
65
+ instructions: str = Field(description="Detailed, actionable guidance for agents on the next attempt")
66
+
67
+
68
+ class PostEvaluationResponse(BaseModel):
69
+ """Structured response for post-evaluation actions."""
70
+
71
+ action_type: PostEvaluationActionType = Field(description="Type of post-evaluation action to take")
72
+ submit_data: Optional[SubmitAction] = Field(default=None, description="Submit data if action is submit")
73
+ restart_data: Optional[RestartAction] = Field(default=None, description="Restart data if action is restart")
@@ -57,6 +57,7 @@ class ResponseBackend(CustomToolAndMCPBackend):
57
57
 
58
58
  Wraps parent implementation to ensure File Search cleanup happens after streaming completes.
59
59
  """
60
+
60
61
  try:
61
62
  async for chunk in super().stream_with_tools(messages, tools, **kwargs):
62
63
  yield chunk
@@ -145,6 +146,7 @@ class ResponseBackend(CustomToolAndMCPBackend):
145
146
  **kwargs,
146
147
  ) -> AsyncGenerator[StreamChunk, None]:
147
148
  """Recursively stream MCP responses, executing function calls as needed."""
149
+
148
150
  agent_id = kwargs.get("agent_id")
149
151
 
150
152
  # Build API params for this iteration
massgen/chat_agent.py CHANGED
@@ -365,10 +365,15 @@ class ConfigurableAgent(SingleAgent):
365
365
  backend: LLM backend
366
366
  session_id: Optional session identifier
367
367
  """
368
+ # Extract system message without triggering deprecation warning
369
+ system_message = None
370
+ if hasattr(config, "_custom_system_instruction"):
371
+ system_message = config._custom_system_instruction
372
+
368
373
  super().__init__(
369
374
  backend=backend,
370
375
  agent_id=config.agent_id,
371
- system_message=config.custom_system_instruction,
376
+ system_message=system_message,
372
377
  session_id=session_id,
373
378
  )
374
379
  self.config = config
@@ -411,8 +416,9 @@ class ConfigurableAgent(SingleAgent):
411
416
  return backend_params["append_system_prompt"]
412
417
 
413
418
  # Fall back to custom_system_instruction (deprecated but still supported)
414
- if self.config and self.config.custom_system_instruction:
415
- return self.config.custom_system_instruction
419
+ # Access private attribute directly to avoid deprecation warning
420
+ if self.config and hasattr(self.config, "_custom_system_instruction") and self.config._custom_system_instruction:
421
+ return self.config._custom_system_instruction
416
422
 
417
423
  # Finally fall back to parent class implementation
418
424
  return super().get_configurable_system_message()