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.
- massgen/__init__.py +1 -1
- massgen/agent_config.py +33 -7
- massgen/api_params_handler/_api_params_handler_base.py +3 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
- massgen/api_params_handler/_claude_api_params_handler.py +4 -0
- massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
- massgen/api_params_handler/_response_api_params_handler.py +4 -0
- massgen/backend/azure_openai.py +9 -1
- massgen/backend/base.py +4 -0
- massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
- massgen/backend/claude_code.py +9 -1
- massgen/backend/docs/permissions_and_context_files.md +2 -2
- massgen/backend/gemini.py +35 -6
- massgen/backend/gemini_utils.py +30 -0
- massgen/backend/response.py +2 -0
- massgen/chat_agent.py +9 -3
- massgen/cli.py +291 -43
- massgen/config_builder.py +163 -18
- massgen/configs/README.md +69 -14
- massgen/configs/debug/restart_test_controlled.yaml +60 -0
- massgen/configs/debug/restart_test_controlled_filesystem.yaml +73 -0
- massgen/configs/tools/code-execution/docker_with_sudo.yaml +35 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +56 -0
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +65 -0
- massgen/configs/tools/custom_tools/computer_use_example.yaml +50 -0
- massgen/configs/tools/custom_tools/crawl4ai_example.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +34 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +34 -0
- massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +59 -0
- massgen/docker/README.md +83 -0
- massgen/filesystem_manager/_code_execution_server.py +22 -7
- massgen/filesystem_manager/_docker_manager.py +21 -1
- massgen/filesystem_manager/_filesystem_manager.py +9 -0
- massgen/filesystem_manager/_path_permission_manager.py +148 -0
- massgen/filesystem_manager/_workspace_tools_server.py +0 -997
- massgen/formatter/_gemini_formatter.py +73 -0
- massgen/frontend/coordination_ui.py +175 -257
- massgen/frontend/displays/base_display.py +29 -0
- massgen/frontend/displays/rich_terminal_display.py +155 -9
- massgen/frontend/displays/simple_display.py +21 -0
- massgen/frontend/displays/terminal_display.py +22 -2
- massgen/logger_config.py +50 -6
- massgen/message_templates.py +283 -15
- massgen/orchestrator.py +335 -38
- massgen/tests/test_binary_file_blocking.py +274 -0
- massgen/tests/test_case_studies.md +12 -12
- massgen/tests/test_code_execution.py +178 -0
- massgen/tests/test_multimodal_size_limits.py +407 -0
- massgen/tests/test_orchestration_restart.py +204 -0
- massgen/tool/__init__.py +4 -0
- massgen/tool/_manager.py +7 -2
- massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
- massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
- massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
- massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
- massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
- massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
- massgen/tool/_multimodal_tools/understand_audio.py +211 -0
- massgen/tool/_multimodal_tools/understand_file.py +555 -0
- massgen/tool/_multimodal_tools/understand_image.py +316 -0
- massgen/tool/_multimodal_tools/understand_video.py +340 -0
- massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
- massgen/tool/docs/multimodal_tools.md +1368 -0
- massgen/tool/workflow_toolkits/__init__.py +26 -0
- massgen/tool/workflow_toolkits/post_evaluation.py +216 -0
- massgen/utils.py +1 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/METADATA +101 -69
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/RECORD +82 -46
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/WHEEL +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -193,6 +193,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
193
193
|
self._key_handler = None
|
|
194
194
|
self._input_thread = None
|
|
195
195
|
self._stop_input_thread = False
|
|
196
|
+
self._user_quit_requested = False # Flag to signal user wants to quit
|
|
196
197
|
self._original_settings = None
|
|
197
198
|
self._agent_selector_active = False # Flag to prevent duplicate agent selector calls
|
|
198
199
|
|
|
@@ -207,6 +208,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
207
208
|
self._final_presentation_agent = None
|
|
208
209
|
self._final_presentation_vote_results = None
|
|
209
210
|
|
|
211
|
+
# Post-evaluation display state
|
|
212
|
+
self._post_evaluation_active = False
|
|
213
|
+
self._post_evaluation_content = ""
|
|
214
|
+
self._post_evaluation_agent = None
|
|
215
|
+
|
|
216
|
+
# Restart context state (for attempt 2+)
|
|
217
|
+
self._restart_context_reason = None
|
|
218
|
+
self._restart_context_instructions = None
|
|
219
|
+
|
|
210
220
|
# Code detection patterns
|
|
211
221
|
self.code_patterns = [
|
|
212
222
|
r"```(\w+)?\n(.*?)\n```", # Markdown code blocks
|
|
@@ -1077,12 +1087,25 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1077
1087
|
Layout(footer, name="footer", size=8),
|
|
1078
1088
|
)
|
|
1079
1089
|
else:
|
|
1080
|
-
#
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1090
|
+
# Build layout components
|
|
1091
|
+
layout_components = []
|
|
1092
|
+
|
|
1093
|
+
# Add header
|
|
1094
|
+
layout_components.append(Layout(header, name="header", size=5))
|
|
1095
|
+
|
|
1096
|
+
# Add agent columns
|
|
1097
|
+
layout_components.append(Layout(agent_columns, name="main"))
|
|
1098
|
+
|
|
1099
|
+
# Add post-evaluation panel if active (below agents)
|
|
1100
|
+
post_eval_panel = self._create_post_evaluation_panel()
|
|
1101
|
+
if post_eval_panel:
|
|
1102
|
+
layout_components.append(Layout(post_eval_panel, name="post_eval", size=6))
|
|
1103
|
+
|
|
1104
|
+
# Add footer
|
|
1105
|
+
layout_components.append(Layout(footer, name="footer", size=8))
|
|
1106
|
+
|
|
1107
|
+
# Arrange layout
|
|
1108
|
+
layout.split_column(*layout_components)
|
|
1086
1109
|
|
|
1087
1110
|
return layout
|
|
1088
1111
|
|
|
@@ -1343,7 +1366,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1343
1366
|
elif key == "q":
|
|
1344
1367
|
# Quit the application - restore terminal and stop
|
|
1345
1368
|
self._stop_input_thread = True
|
|
1369
|
+
self._user_quit_requested = True
|
|
1346
1370
|
self._restore_terminal_settings()
|
|
1371
|
+
# Print quit message
|
|
1372
|
+
self.console.print("\n[yellow]Exiting coordination...[/yellow]")
|
|
1347
1373
|
|
|
1348
1374
|
def _open_agent_in_default_text_editor(self, agent_id: str) -> None:
|
|
1349
1375
|
"""Open agent's txt file in default text editor."""
|
|
@@ -2013,6 +2039,56 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2013
2039
|
expand=True, # Full width
|
|
2014
2040
|
)
|
|
2015
2041
|
|
|
2042
|
+
def _create_post_evaluation_panel(self) -> Optional[Panel]:
|
|
2043
|
+
"""Create a panel for post-evaluation display (below agent columns)."""
|
|
2044
|
+
if not self._post_evaluation_active:
|
|
2045
|
+
return None
|
|
2046
|
+
|
|
2047
|
+
content_text = Text()
|
|
2048
|
+
|
|
2049
|
+
if not self._post_evaluation_content:
|
|
2050
|
+
content_text.append("Evaluating answer...", style=self.colors["text"])
|
|
2051
|
+
else:
|
|
2052
|
+
# Show last few lines of post-eval content
|
|
2053
|
+
lines = self._post_evaluation_content.split("\n")
|
|
2054
|
+
display_lines = lines[-5:] if len(lines) > 5 else lines
|
|
2055
|
+
|
|
2056
|
+
for line in display_lines:
|
|
2057
|
+
if line.strip():
|
|
2058
|
+
formatted_line = self._format_content_line(line)
|
|
2059
|
+
content_text.append(formatted_line)
|
|
2060
|
+
content_text.append("\n")
|
|
2061
|
+
|
|
2062
|
+
title = f"🔍 Post-Evaluation by {self._post_evaluation_agent}"
|
|
2063
|
+
|
|
2064
|
+
return Panel(
|
|
2065
|
+
content_text,
|
|
2066
|
+
title=f"[{self.colors['info']}]{title}[/{self.colors['info']}]",
|
|
2067
|
+
border_style=self.colors["info"],
|
|
2068
|
+
box=ROUNDED,
|
|
2069
|
+
expand=True,
|
|
2070
|
+
height=6, # Fixed height to not take too much space
|
|
2071
|
+
)
|
|
2072
|
+
|
|
2073
|
+
def _create_restart_context_panel(self) -> Optional[Panel]:
|
|
2074
|
+
"""Create restart context panel for attempt 2+ (yellow warning at top)."""
|
|
2075
|
+
if not self._restart_context_reason or not self._restart_context_instructions:
|
|
2076
|
+
return None
|
|
2077
|
+
|
|
2078
|
+
content_text = Text()
|
|
2079
|
+
content_text.append("Reason: ", style="bold bright_yellow")
|
|
2080
|
+
content_text.append(f"{self._restart_context_reason}\n\n", style="bright_yellow")
|
|
2081
|
+
content_text.append("Instructions: ", style="bold bright_yellow")
|
|
2082
|
+
content_text.append(f"{self._restart_context_instructions}", style="bright_yellow")
|
|
2083
|
+
|
|
2084
|
+
return Panel(
|
|
2085
|
+
content_text,
|
|
2086
|
+
title="[bold bright_yellow]⚠️ PREVIOUS ATTEMPT FEEDBACK[/bold bright_yellow]",
|
|
2087
|
+
border_style="bright_yellow",
|
|
2088
|
+
box=ROUNDED,
|
|
2089
|
+
expand=True,
|
|
2090
|
+
)
|
|
2091
|
+
|
|
2016
2092
|
def _format_presentation_content(self, content: str) -> Text:
|
|
2017
2093
|
"""Format presentation content with enhanced styling for orchestrator queries."""
|
|
2018
2094
|
formatted = Text()
|
|
@@ -3174,7 +3250,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3174
3250
|
title="[bold bright_green]🎯 FINAL COORDINATED ANSWER[/bold bright_green]",
|
|
3175
3251
|
border_style=self.colors["success"],
|
|
3176
3252
|
box=DOUBLE,
|
|
3177
|
-
expand=
|
|
3253
|
+
expand=True,
|
|
3178
3254
|
)
|
|
3179
3255
|
|
|
3180
3256
|
self.console.print(final_panel)
|
|
@@ -3244,10 +3320,80 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3244
3320
|
)
|
|
3245
3321
|
self.console.print(error_text)
|
|
3246
3322
|
|
|
3247
|
-
# Show interactive options for viewing agent details (only if not in safe mode)
|
|
3248
|
-
|
|
3323
|
+
# Show interactive options for viewing agent details (only if not in safe mode and not restarting)
|
|
3324
|
+
# Don't show inspection menu if orchestration is restarting
|
|
3325
|
+
is_restarting = hasattr(self, "orchestrator") and hasattr(self.orchestrator, "restart_pending") and self.orchestrator.restart_pending
|
|
3326
|
+
if self._keyboard_interactive_mode and hasattr(self, "_agent_keys") and not self._safe_keyboard_mode and not is_restarting:
|
|
3249
3327
|
self.show_agent_selector()
|
|
3250
3328
|
|
|
3329
|
+
def show_post_evaluation_content(self, content: str, agent_id: str):
|
|
3330
|
+
"""Display post-evaluation streaming content in a panel below agents."""
|
|
3331
|
+
self._post_evaluation_active = True
|
|
3332
|
+
self._post_evaluation_agent = agent_id
|
|
3333
|
+
self._post_evaluation_content += content
|
|
3334
|
+
# Panel will be created/updated in _update_display via layout
|
|
3335
|
+
|
|
3336
|
+
def show_restart_banner(self, reason: str, instructions: str, attempt: int, max_attempts: int):
|
|
3337
|
+
"""Display restart decision banner prominently (like final presentation)."""
|
|
3338
|
+
# Stop live display temporarily for static banner
|
|
3339
|
+
self.live is not None
|
|
3340
|
+
if self.live:
|
|
3341
|
+
self.live.stop()
|
|
3342
|
+
self.live = None
|
|
3343
|
+
|
|
3344
|
+
# Create restart banner content
|
|
3345
|
+
banner_content = Text()
|
|
3346
|
+
banner_content.append("\nREASON:\n", style="bold bright_yellow")
|
|
3347
|
+
banner_content.append(f"{reason}\n\n", style="bright_yellow")
|
|
3348
|
+
banner_content.append("INSTRUCTIONS FOR NEXT ATTEMPT:\n", style="bold bright_yellow")
|
|
3349
|
+
banner_content.append(f"{instructions}\n", style="bright_yellow")
|
|
3350
|
+
|
|
3351
|
+
restart_panel = Panel(
|
|
3352
|
+
banner_content,
|
|
3353
|
+
title=f"[bold bright_yellow]🔄 ORCHESTRATION RESTART (Attempt {attempt}/{max_attempts})[/bold bright_yellow]",
|
|
3354
|
+
border_style="bright_yellow",
|
|
3355
|
+
box=DOUBLE,
|
|
3356
|
+
expand=True,
|
|
3357
|
+
)
|
|
3358
|
+
|
|
3359
|
+
self.console.print(restart_panel)
|
|
3360
|
+
time.sleep(2.0) # Allow user to read restart banner
|
|
3361
|
+
|
|
3362
|
+
# Reset state for fresh attempt - clear all agent content and status
|
|
3363
|
+
for agent_id in self.agent_ids:
|
|
3364
|
+
self.agent_outputs[agent_id] = []
|
|
3365
|
+
self.agent_status[agent_id] = "waiting"
|
|
3366
|
+
# Clear text buffers
|
|
3367
|
+
if hasattr(self, "_text_buffers") and agent_id in self._text_buffers:
|
|
3368
|
+
self._text_buffers[agent_id] = ""
|
|
3369
|
+
|
|
3370
|
+
# Clear cached panels and ALL cached state
|
|
3371
|
+
self._agent_panels_cache.clear()
|
|
3372
|
+
self._footer_cache = None
|
|
3373
|
+
self._header_cache = None
|
|
3374
|
+
|
|
3375
|
+
# Clear orchestrator events (from base class)
|
|
3376
|
+
self.orchestrator_events = []
|
|
3377
|
+
|
|
3378
|
+
# Clear presentation state
|
|
3379
|
+
self._final_presentation_active = False
|
|
3380
|
+
self._final_presentation_content = ""
|
|
3381
|
+
self._post_evaluation_active = False
|
|
3382
|
+
self._post_evaluation_content = ""
|
|
3383
|
+
|
|
3384
|
+
# Clear restart context state (so it doesn't show on next attempt)
|
|
3385
|
+
self._restart_context_reason = None
|
|
3386
|
+
self._restart_context_instructions = None
|
|
3387
|
+
|
|
3388
|
+
# DON'T restart live display here - let the next coordinate() call handle it
|
|
3389
|
+
# The CLI will create a fresh UI instance which will initialize its own display
|
|
3390
|
+
|
|
3391
|
+
def show_restart_context_panel(self, reason: str, instructions: str):
|
|
3392
|
+
"""Display restart context panel at top of UI (for attempt 2+)."""
|
|
3393
|
+
self._restart_context_reason = reason
|
|
3394
|
+
self._restart_context_instructions = instructions
|
|
3395
|
+
# Panel will be displayed in initialize() method before agent columns
|
|
3396
|
+
|
|
3251
3397
|
def _display_answer_with_flush(self, answer: str) -> None:
|
|
3252
3398
|
"""Display answer with flush output effect - streaming character by character."""
|
|
3253
3399
|
import sys
|
|
@@ -90,6 +90,27 @@ class SimpleDisplay(BaseDisplay):
|
|
|
90
90
|
print(f"🗳️ Vote results: {vote_summary}")
|
|
91
91
|
print("=" * 50)
|
|
92
92
|
|
|
93
|
+
def show_post_evaluation_content(self, content: str, agent_id: str):
|
|
94
|
+
"""Display post-evaluation streaming content."""
|
|
95
|
+
print(f"🔍 [{agent_id}] {content}", end="", flush=True)
|
|
96
|
+
|
|
97
|
+
def show_restart_banner(self, reason: str, instructions: str, attempt: int, max_attempts: int):
|
|
98
|
+
"""Display restart decision banner."""
|
|
99
|
+
print("\n" + "🔄" * 40)
|
|
100
|
+
print(f"ORCHESTRATION RESTART - Attempt {attempt}/{max_attempts}")
|
|
101
|
+
print("🔄" * 40)
|
|
102
|
+
print(f"\n{reason}\n")
|
|
103
|
+
print(f"Instructions: {instructions}\n")
|
|
104
|
+
print("🔄" * 40 + "\n")
|
|
105
|
+
|
|
106
|
+
def show_restart_context_panel(self, reason: str, instructions: str):
|
|
107
|
+
"""Display restart context panel at top of UI (for attempt 2+)."""
|
|
108
|
+
print("\n" + "⚠️ " * 30)
|
|
109
|
+
print("PREVIOUS ATTEMPT FEEDBACK")
|
|
110
|
+
print(f"Reason: {reason}")
|
|
111
|
+
print(f"Instructions: {instructions}")
|
|
112
|
+
print("⚠️ " * 30 + "\n")
|
|
113
|
+
|
|
93
114
|
def cleanup(self):
|
|
94
115
|
"""Clean up resources."""
|
|
95
116
|
print(f"\n✅ Coordination completed with {len(self.agent_ids)} agents")
|
|
@@ -220,8 +220,6 @@ class TerminalDisplay(BaseDisplay):
|
|
|
220
220
|
|
|
221
221
|
# Add working indicator if transitioning to working
|
|
222
222
|
if old_status != "working" and status == "working":
|
|
223
|
-
agent_prefix = f"[{agent_id}] " if self.num_agents > 1 else ""
|
|
224
|
-
print(f"\n{agent_prefix}⚡ Working...")
|
|
225
223
|
if not self.agent_outputs[agent_id] or not self.agent_outputs[agent_id][-1].startswith("⚡"):
|
|
226
224
|
self.agent_outputs[agent_id].append("⚡ Working...")
|
|
227
225
|
|
|
@@ -245,6 +243,28 @@ class TerminalDisplay(BaseDisplay):
|
|
|
245
243
|
print(f"🗳️ Vote results: {vote_summary}")
|
|
246
244
|
print("=" * 60)
|
|
247
245
|
|
|
246
|
+
def show_post_evaluation_content(self, content: str, agent_id: str):
|
|
247
|
+
"""Display post-evaluation streaming content."""
|
|
248
|
+
print(f"🔍 Post-Evaluation [{agent_id}]: {content}", end="", flush=True)
|
|
249
|
+
|
|
250
|
+
def show_restart_banner(self, reason: str, instructions: str, attempt: int, max_attempts: int):
|
|
251
|
+
"""Display restart decision banner."""
|
|
252
|
+
print("\n" + "=" * 80)
|
|
253
|
+
print(f"🔄 ORCHESTRATION RESTART (Attempt {attempt}/{max_attempts})")
|
|
254
|
+
print("=" * 80)
|
|
255
|
+
print(f"\nREASON:\n{reason}")
|
|
256
|
+
print(f"\nINSTRUCTIONS FOR NEXT ATTEMPT:\n{instructions}")
|
|
257
|
+
print("\n" + "=" * 80 + "\n")
|
|
258
|
+
|
|
259
|
+
def show_restart_context_panel(self, reason: str, instructions: str):
|
|
260
|
+
"""Display restart context panel at top of UI (for attempt 2+)."""
|
|
261
|
+
print("\n" + "⚠" * 40)
|
|
262
|
+
print("⚠️ PREVIOUS ATTEMPT FEEDBACK")
|
|
263
|
+
print("⚠" * 40)
|
|
264
|
+
print(f"\nReason: {reason}")
|
|
265
|
+
print(f"\nInstructions: {instructions}")
|
|
266
|
+
print("\n" + "⚠" * 40 + "\n")
|
|
267
|
+
|
|
248
268
|
def cleanup(self):
|
|
249
269
|
"""Clean up display resources."""
|
|
250
270
|
# No special cleanup needed for terminal display
|
massgen/logger_config.py
CHANGED
|
@@ -41,6 +41,7 @@ _DEBUG_MODE = False
|
|
|
41
41
|
_LOG_SESSION_DIR = None
|
|
42
42
|
_LOG_BASE_SESSION_DIR = None # Base session dir (without turn subdirectory)
|
|
43
43
|
_CURRENT_TURN = None
|
|
44
|
+
_CURRENT_ATTEMPT = None # Current attempt number for restart tracking
|
|
44
45
|
|
|
45
46
|
# Console logging suppression (for Rich Live display compatibility)
|
|
46
47
|
_CONSOLE_HANDLER_ID = None
|
|
@@ -48,15 +49,15 @@ _CONSOLE_SUPPRESSED = False
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def get_log_session_dir(turn: Optional[int] = None) -> Path:
|
|
51
|
-
"""Get the current log session directory.
|
|
52
|
+
"""Get the current log session directory, including attempt subdirectory if set.
|
|
52
53
|
|
|
53
54
|
Args:
|
|
54
55
|
turn: Optional turn number for multi-turn conversations
|
|
55
56
|
|
|
56
57
|
Returns:
|
|
57
|
-
Path to the log directory
|
|
58
|
+
Path to the log directory (includes attempt subdirectory if _CURRENT_ATTEMPT is set)
|
|
58
59
|
"""
|
|
59
|
-
global _LOG_SESSION_DIR, _LOG_BASE_SESSION_DIR, _CURRENT_TURN
|
|
60
|
+
global _LOG_SESSION_DIR, _LOG_BASE_SESSION_DIR, _CURRENT_TURN, _CURRENT_ATTEMPT
|
|
60
61
|
|
|
61
62
|
# Initialize base session dir once per session
|
|
62
63
|
if _LOG_BASE_SESSION_DIR is None:
|
|
@@ -88,19 +89,62 @@ def get_log_session_dir(turn: Optional[int] = None) -> Path:
|
|
|
88
89
|
_LOG_SESSION_DIR = None # Force recreation
|
|
89
90
|
|
|
90
91
|
if _LOG_SESSION_DIR is None:
|
|
91
|
-
#
|
|
92
|
+
# Build directory structure based on turn and attempt
|
|
92
93
|
if _CURRENT_TURN and _CURRENT_TURN > 0:
|
|
93
94
|
# Multi-turn conversation: organize by turn within session
|
|
94
|
-
|
|
95
|
+
base_dir = _LOG_BASE_SESSION_DIR / f"turn_{_CURRENT_TURN}"
|
|
95
96
|
else:
|
|
96
97
|
# First execution or single execution: use base session dir
|
|
97
|
-
|
|
98
|
+
base_dir = _LOG_BASE_SESSION_DIR
|
|
99
|
+
|
|
100
|
+
# Add attempt subdirectory if attempt is set
|
|
101
|
+
if _CURRENT_ATTEMPT and _CURRENT_ATTEMPT > 0:
|
|
102
|
+
_LOG_SESSION_DIR = base_dir / f"attempt_{_CURRENT_ATTEMPT}"
|
|
103
|
+
else:
|
|
104
|
+
_LOG_SESSION_DIR = base_dir
|
|
98
105
|
|
|
99
106
|
_LOG_SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
|
100
107
|
|
|
101
108
|
return _LOG_SESSION_DIR
|
|
102
109
|
|
|
103
110
|
|
|
111
|
+
def set_log_attempt(attempt: int) -> None:
|
|
112
|
+
"""Set the current attempt number for restart tracking.
|
|
113
|
+
|
|
114
|
+
This forces the log directory to be recreated with the new attempt subdirectory.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
attempt: Attempt number (1-indexed)
|
|
118
|
+
"""
|
|
119
|
+
global _LOG_SESSION_DIR, _CURRENT_ATTEMPT
|
|
120
|
+
_CURRENT_ATTEMPT = attempt
|
|
121
|
+
_LOG_SESSION_DIR = None # Force recreation with new attempt subdirectory
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_log_session_dir_base() -> Path:
|
|
125
|
+
"""Get the base log session directory without attempt subdirectory.
|
|
126
|
+
|
|
127
|
+
This is useful for copying final results to the root level after all attempts complete.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Path to the base log directory (turn level or session root, without attempt)
|
|
131
|
+
"""
|
|
132
|
+
global _LOG_BASE_SESSION_DIR, _CURRENT_TURN
|
|
133
|
+
|
|
134
|
+
# Ensure base session dir is initialized
|
|
135
|
+
if _LOG_BASE_SESSION_DIR is None:
|
|
136
|
+
# Initialize by calling get_log_session_dir
|
|
137
|
+
get_log_session_dir()
|
|
138
|
+
|
|
139
|
+
# Build base directory based on turn (without attempt)
|
|
140
|
+
if _CURRENT_TURN and _CURRENT_TURN > 0:
|
|
141
|
+
# Multi-turn conversation: return turn directory
|
|
142
|
+
return _LOG_BASE_SESSION_DIR / f"turn_{_CURRENT_TURN}"
|
|
143
|
+
else:
|
|
144
|
+
# Single turn: return base session dir
|
|
145
|
+
return _LOG_BASE_SESSION_DIR
|
|
146
|
+
|
|
147
|
+
|
|
104
148
|
def save_execution_metadata(
|
|
105
149
|
query: str,
|
|
106
150
|
config_path: Optional[str] = None,
|