massgen 0.1.0a3__py3-none-any.whl → 0.1.2__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 +17 -0
- massgen/api_params_handler/_api_params_handler_base.py +1 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +15 -2
- massgen/api_params_handler/_claude_api_params_handler.py +8 -1
- massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
- massgen/api_params_handler/_response_api_params_handler.py +8 -1
- massgen/backend/base.py +83 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +286 -15
- massgen/backend/capabilities.py +6 -6
- massgen/backend/chat_completions.py +200 -103
- massgen/backend/claude.py +115 -18
- massgen/backend/claude_code.py +378 -14
- massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
- massgen/backend/gemini.py +1333 -1629
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/grok.py +39 -6
- massgen/backend/response.py +147 -81
- massgen/cli.py +605 -110
- massgen/config_builder.py +376 -27
- massgen/configs/README.md +123 -80
- massgen/configs/basic/multi/three_agents_default.yaml +3 -3
- massgen/configs/basic/single/single_agent.yaml +1 -1
- massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +7 -29
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +5 -6
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +4 -4
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +4 -4
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +2 -2
- massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
- massgen/formatter/_chat_completions_formatter.py +104 -0
- massgen/formatter/_claude_formatter.py +120 -0
- massgen/formatter/_gemini_formatter.py +448 -0
- massgen/formatter/_response_formatter.py +88 -0
- massgen/frontend/coordination_ui.py +4 -2
- massgen/logger_config.py +35 -3
- massgen/message_templates.py +56 -6
- massgen/orchestrator.py +512 -16
- massgen/stream_chunk/base.py +3 -0
- massgen/tests/custom_tools_example.py +392 -0
- massgen/tests/mcp_test_server.py +17 -7
- massgen/tests/test_config_builder.py +423 -0
- massgen/tests/test_custom_tools.py +401 -0
- massgen/tests/test_intelligent_planning_mode.py +643 -0
- massgen/tests/test_tools.py +127 -0
- massgen/token_manager/token_manager.py +13 -4
- massgen/tool/README.md +935 -0
- massgen/tool/__init__.py +39 -0
- massgen/tool/_async_helpers.py +70 -0
- massgen/tool/_basic/__init__.py +8 -0
- massgen/tool/_basic/_two_num_tool.py +24 -0
- massgen/tool/_code_executors/__init__.py +10 -0
- massgen/tool/_code_executors/_python_executor.py +74 -0
- massgen/tool/_code_executors/_shell_executor.py +61 -0
- massgen/tool/_exceptions.py +39 -0
- massgen/tool/_file_handlers/__init__.py +10 -0
- massgen/tool/_file_handlers/_file_operations.py +218 -0
- massgen/tool/_manager.py +634 -0
- massgen/tool/_registered_tool.py +88 -0
- massgen/tool/_result.py +66 -0
- massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
- massgen/tool/docs/builtin_tools.md +681 -0
- massgen/tool/docs/exceptions.md +794 -0
- massgen/tool/docs/execution_results.md +691 -0
- massgen/tool/docs/manager.md +887 -0
- massgen/tool/docs/workflow_toolkits.md +529 -0
- massgen/tool/workflow_toolkits/__init__.py +57 -0
- massgen/tool/workflow_toolkits/base.py +55 -0
- massgen/tool/workflow_toolkits/new_answer.py +126 -0
- massgen/tool/workflow_toolkits/vote.py +167 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/METADATA +87 -129
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/RECORD +120 -44
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/top_level.txt +0 -0
massgen/cli.py
CHANGED
|
@@ -8,30 +8,34 @@ Supports both interactive mode and single-question mode.
|
|
|
8
8
|
|
|
9
9
|
Usage examples:
|
|
10
10
|
# Use YAML/JSON configuration file
|
|
11
|
-
|
|
11
|
+
massgen --config config.yaml "What is the capital of France?"
|
|
12
12
|
|
|
13
13
|
# Quick setup with backend and model
|
|
14
|
-
|
|
14
|
+
massgen --backend openai --model gpt-4o-mini "What is 2+2?"
|
|
15
15
|
|
|
16
16
|
# Interactive mode
|
|
17
|
-
|
|
17
|
+
massgen --config config.yaml
|
|
18
|
+
massgen # Uses default config if available
|
|
18
19
|
|
|
19
20
|
# Multiple agents from config
|
|
20
|
-
|
|
21
|
+
massgen --config multi_agent.yaml "Compare different approaches to renewable energy"
|
|
21
22
|
"""
|
|
22
23
|
|
|
23
24
|
import argparse
|
|
24
25
|
import asyncio
|
|
26
|
+
import copy
|
|
25
27
|
import json
|
|
26
28
|
import os
|
|
27
29
|
import shutil
|
|
28
30
|
import sys
|
|
29
31
|
from datetime import datetime
|
|
30
32
|
from pathlib import Path
|
|
31
|
-
from typing import Any, Dict, List, Optional
|
|
33
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
32
34
|
|
|
35
|
+
import questionary
|
|
33
36
|
import yaml
|
|
34
37
|
from dotenv import load_dotenv
|
|
38
|
+
from prompt_toolkit.styles import Style
|
|
35
39
|
from rich.console import Console
|
|
36
40
|
from rich.panel import Panel
|
|
37
41
|
from rich.table import Table
|
|
@@ -48,7 +52,7 @@ from .backend.lmstudio import LMStudioBackend
|
|
|
48
52
|
from .backend.response import ResponseBackend
|
|
49
53
|
from .chat_agent import ConfigurableAgent, SingleAgent
|
|
50
54
|
from .frontend.coordination_ui import CoordinationUI
|
|
51
|
-
from .logger_config import _DEBUG_MODE, logger, setup_logging
|
|
55
|
+
from .logger_config import _DEBUG_MODE, logger, save_execution_metadata, setup_logging
|
|
52
56
|
from .orchestrator import Orchestrator
|
|
53
57
|
from .utils import get_backend_type_from_model
|
|
54
58
|
|
|
@@ -86,6 +90,22 @@ BRIGHT_WHITE = "\033[97m"
|
|
|
86
90
|
RESET = "\033[0m"
|
|
87
91
|
BOLD = "\033[1m"
|
|
88
92
|
|
|
93
|
+
# Custom questionary style for polished selection interface
|
|
94
|
+
MASSGEN_QUESTIONARY_STYLE = Style(
|
|
95
|
+
[
|
|
96
|
+
("qmark", "fg:#00d7ff bold"), # Bright cyan question mark
|
|
97
|
+
("question", "fg:#ffffff bold"), # White question text
|
|
98
|
+
("answer", "fg:#00d7ff bold"), # Bright cyan answer
|
|
99
|
+
("pointer", "fg:#00d7ff bold"), # Bright cyan pointer (▸)
|
|
100
|
+
("highlighted", "fg:#00d7ff bold"), # Bright cyan highlighted option
|
|
101
|
+
("selected", "fg:#00ff87"), # Bright green selected
|
|
102
|
+
("separator", "fg:#6c6c6c"), # Gray separators
|
|
103
|
+
("instruction", "fg:#808080"), # Gray instructions
|
|
104
|
+
("text", "fg:#ffffff"), # White text
|
|
105
|
+
("disabled", "fg:#6c6c6c italic"), # Gray disabled
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
|
|
89
109
|
|
|
90
110
|
class ConfigurationError(Exception):
|
|
91
111
|
"""Configuration error for CLI."""
|
|
@@ -802,105 +822,108 @@ async def run_question_with_history(
|
|
|
802
822
|
messages = history.copy()
|
|
803
823
|
messages.append({"role": "user", "content": question})
|
|
804
824
|
|
|
805
|
-
#
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
825
|
+
# In multiturn mode with session persistence, ALWAYS use orchestrator for proper final/ directory creation
|
|
826
|
+
# Single agents in multiturn mode need the orchestrator to create session artifacts (final/, workspace/, etc.)
|
|
827
|
+
# The orchestrator handles single agents efficiently by skipping unnecessary coordination
|
|
828
|
+
|
|
829
|
+
# Create orchestrator config with timeout settings
|
|
830
|
+
timeout_config = kwargs.get("timeout_config")
|
|
831
|
+
orchestrator_config = AgentConfig()
|
|
832
|
+
if timeout_config:
|
|
833
|
+
orchestrator_config.timeout_config = timeout_config
|
|
834
|
+
|
|
835
|
+
# Get orchestrator parameters from config
|
|
836
|
+
orchestrator_cfg = kwargs.get("orchestrator", {})
|
|
837
|
+
|
|
838
|
+
# Apply voting sensitivity if specified
|
|
839
|
+
if "voting_sensitivity" in orchestrator_cfg:
|
|
840
|
+
orchestrator_config.voting_sensitivity = orchestrator_cfg["voting_sensitivity"]
|
|
841
|
+
|
|
842
|
+
# Apply answer count limit if specified
|
|
843
|
+
if "max_new_answers_per_agent" in orchestrator_cfg:
|
|
844
|
+
orchestrator_config.max_new_answers_per_agent = orchestrator_cfg["max_new_answers_per_agent"]
|
|
845
|
+
|
|
846
|
+
# Apply answer novelty requirement if specified
|
|
847
|
+
if "answer_novelty_requirement" in orchestrator_cfg:
|
|
848
|
+
orchestrator_config.answer_novelty_requirement = orchestrator_cfg["answer_novelty_requirement"]
|
|
849
|
+
|
|
850
|
+
# Get context sharing parameters
|
|
851
|
+
snapshot_storage = orchestrator_cfg.get("snapshot_storage")
|
|
852
|
+
agent_temporary_workspace = orchestrator_cfg.get("agent_temporary_workspace")
|
|
853
|
+
session_storage = orchestrator_cfg.get("session_storage", "sessions") # Default to "sessions"
|
|
854
|
+
|
|
855
|
+
# Get debug/test parameters
|
|
856
|
+
if orchestrator_cfg.get("skip_coordination_rounds", False):
|
|
857
|
+
orchestrator_config.skip_coordination_rounds = True
|
|
858
|
+
|
|
859
|
+
# Load previous turns from session storage for multi-turn conversations
|
|
860
|
+
previous_turns = load_previous_turns(session_info, session_storage)
|
|
861
|
+
|
|
862
|
+
orchestrator = Orchestrator(
|
|
863
|
+
agents=agents,
|
|
864
|
+
config=orchestrator_config,
|
|
865
|
+
snapshot_storage=snapshot_storage,
|
|
866
|
+
agent_temporary_workspace=agent_temporary_workspace,
|
|
867
|
+
previous_turns=previous_turns,
|
|
868
|
+
)
|
|
869
|
+
# Create a fresh UI instance for each question to ensure clean state
|
|
870
|
+
ui = CoordinationUI(
|
|
871
|
+
display_type=ui_config.get("display_type", "rich_terminal"),
|
|
872
|
+
logging_enabled=ui_config.get("logging_enabled", True),
|
|
873
|
+
enable_final_presentation=True, # Required for multi-turn: ensures final answer is saved
|
|
874
|
+
)
|
|
835
875
|
|
|
876
|
+
# Determine display mode text
|
|
877
|
+
if len(agents) == 1:
|
|
878
|
+
mode_text = "Single Agent (Orchestrator)"
|
|
836
879
|
else:
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
if
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
# Get debug/test parameters
|
|
853
|
-
if orchestrator_cfg.get("skip_coordination_rounds", False):
|
|
854
|
-
orchestrator_config.skip_coordination_rounds = True
|
|
855
|
-
|
|
856
|
-
# Load previous turns from session storage for multi-turn conversations
|
|
857
|
-
previous_turns = load_previous_turns(session_info, session_storage)
|
|
858
|
-
|
|
859
|
-
orchestrator = Orchestrator(
|
|
860
|
-
agents=agents,
|
|
861
|
-
config=orchestrator_config,
|
|
862
|
-
snapshot_storage=snapshot_storage,
|
|
863
|
-
agent_temporary_workspace=agent_temporary_workspace,
|
|
864
|
-
previous_turns=previous_turns,
|
|
865
|
-
)
|
|
866
|
-
# Create a fresh UI instance for each question to ensure clean state
|
|
867
|
-
ui = CoordinationUI(
|
|
868
|
-
display_type=ui_config.get("display_type", "rich_terminal"),
|
|
869
|
-
logging_enabled=ui_config.get("logging_enabled", True),
|
|
870
|
-
enable_final_presentation=True, # Required for multi-turn: ensures final answer is saved
|
|
871
|
-
)
|
|
880
|
+
mode_text = "Multi-Agent"
|
|
881
|
+
|
|
882
|
+
# Get coordination config from YAML (if present)
|
|
883
|
+
coordination_settings = kwargs.get("orchestrator", {}).get("coordination", {})
|
|
884
|
+
if coordination_settings:
|
|
885
|
+
from .agent_config import CoordinationConfig
|
|
886
|
+
|
|
887
|
+
orchestrator_config.coordination_config = CoordinationConfig(
|
|
888
|
+
enable_planning_mode=coordination_settings.get("enable_planning_mode", False),
|
|
889
|
+
planning_mode_instruction=coordination_settings.get(
|
|
890
|
+
"planning_mode_instruction",
|
|
891
|
+
"""During coordination, describe what you would do. Only provide concrete implementation details and execute read-only actions.
|
|
892
|
+
DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)""",
|
|
893
|
+
),
|
|
894
|
+
)
|
|
872
895
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
896
|
+
print(f"\n🤖 {BRIGHT_CYAN}{mode_text}{RESET}", flush=True)
|
|
897
|
+
print(f"Agents: {', '.join(agents.keys())}", flush=True)
|
|
898
|
+
if history:
|
|
899
|
+
print(f"History: {len(history)//2} previous exchanges", flush=True)
|
|
900
|
+
print(f"Question: {question}", flush=True)
|
|
901
|
+
print("\n" + "=" * 60, flush=True)
|
|
879
902
|
|
|
880
|
-
|
|
881
|
-
|
|
903
|
+
# For multi-agent with history, we need to use a different approach
|
|
904
|
+
# that maintains coordination UI display while supporting conversation context
|
|
882
905
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
906
|
+
if history and len(history) > 0:
|
|
907
|
+
# Use coordination UI with conversation context
|
|
908
|
+
# Extract current question from messages
|
|
909
|
+
current_question = messages[-1].get("content", question) if messages else question
|
|
887
910
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
911
|
+
# Pass the full message context to the UI coordination
|
|
912
|
+
response_content = await ui.coordinate_with_context(orchestrator, current_question, messages)
|
|
913
|
+
else:
|
|
914
|
+
# Standard coordination for new conversations
|
|
915
|
+
response_content = await ui.coordinate(orchestrator, question)
|
|
916
|
+
|
|
917
|
+
# Handle session persistence if applicable
|
|
918
|
+
session_id_to_use, updated_turn, normalized_response = await handle_session_persistence(
|
|
919
|
+
orchestrator,
|
|
920
|
+
question,
|
|
921
|
+
session_info,
|
|
922
|
+
session_storage,
|
|
923
|
+
)
|
|
901
924
|
|
|
902
|
-
|
|
903
|
-
|
|
925
|
+
# Return normalized response so conversation history has correct paths
|
|
926
|
+
return (normalized_response or response_content, session_id_to_use, updated_turn)
|
|
904
927
|
|
|
905
928
|
|
|
906
929
|
async def run_single_question(question: str, agents: Dict[str, SingleAgent], ui_config: Dict[str, Any], **kwargs) -> str:
|
|
@@ -942,9 +965,35 @@ async def run_single_question(question: str, agents: Dict[str, SingleAgent], ui_
|
|
|
942
965
|
if timeout_config:
|
|
943
966
|
orchestrator_config.timeout_config = timeout_config
|
|
944
967
|
|
|
968
|
+
# Get coordination config from YAML (if present)
|
|
969
|
+
coordination_settings = kwargs.get("orchestrator", {}).get("coordination", {})
|
|
970
|
+
if coordination_settings:
|
|
971
|
+
from .agent_config import CoordinationConfig
|
|
972
|
+
|
|
973
|
+
orchestrator_config.coordination_config = CoordinationConfig(
|
|
974
|
+
enable_planning_mode=coordination_settings.get("enable_planning_mode", False),
|
|
975
|
+
planning_mode_instruction=coordination_settings.get(
|
|
976
|
+
"planning_mode_instruction",
|
|
977
|
+
"""During coordination, describe what you would do. Only provide concrete implementation details and execute read-only actions.
|
|
978
|
+
DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)""",
|
|
979
|
+
),
|
|
980
|
+
)
|
|
981
|
+
|
|
945
982
|
# Get orchestrator parameters from config
|
|
946
983
|
orchestrator_cfg = kwargs.get("orchestrator", {})
|
|
947
984
|
|
|
985
|
+
# Apply voting sensitivity if specified
|
|
986
|
+
if "voting_sensitivity" in orchestrator_cfg:
|
|
987
|
+
orchestrator_config.voting_sensitivity = orchestrator_cfg["voting_sensitivity"]
|
|
988
|
+
|
|
989
|
+
# Apply answer count limit if specified
|
|
990
|
+
if "max_new_answers_per_agent" in orchestrator_cfg:
|
|
991
|
+
orchestrator_config.max_new_answers_per_agent = orchestrator_cfg["max_new_answers_per_agent"]
|
|
992
|
+
|
|
993
|
+
# Apply answer novelty requirement if specified
|
|
994
|
+
if "answer_novelty_requirement" in orchestrator_cfg:
|
|
995
|
+
orchestrator_config.answer_novelty_requirement = orchestrator_cfg["answer_novelty_requirement"]
|
|
996
|
+
|
|
948
997
|
# Get context sharing parameters
|
|
949
998
|
snapshot_storage = orchestrator_cfg.get("snapshot_storage")
|
|
950
999
|
agent_temporary_workspace = orchestrator_cfg.get("agent_temporary_workspace")
|
|
@@ -1219,6 +1268,411 @@ def print_example_config(name: str):
|
|
|
1219
1268
|
sys.exit(1)
|
|
1220
1269
|
|
|
1221
1270
|
|
|
1271
|
+
def discover_available_configs() -> Dict[str, List[Tuple[str, Path]]]:
|
|
1272
|
+
"""Discover all available configuration files.
|
|
1273
|
+
|
|
1274
|
+
Returns:
|
|
1275
|
+
Dict with categories as keys and list of (display_name, path) tuples as values
|
|
1276
|
+
"""
|
|
1277
|
+
configs = {
|
|
1278
|
+
"User Configs": [],
|
|
1279
|
+
"Project Configs": [],
|
|
1280
|
+
"Current Directory": [],
|
|
1281
|
+
"Package Examples": [],
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
# 1. User configs (~/.config/massgen/agents/)
|
|
1285
|
+
user_agents_dir = Path.home() / ".config/massgen/agents"
|
|
1286
|
+
if user_agents_dir.exists():
|
|
1287
|
+
for config_file in sorted(user_agents_dir.glob("*.yaml")):
|
|
1288
|
+
display_name = config_file.stem
|
|
1289
|
+
configs["User Configs"].append((display_name, config_file))
|
|
1290
|
+
|
|
1291
|
+
# 2. Project configs (.massgen/)
|
|
1292
|
+
project_config_dir = Path.cwd() / ".massgen"
|
|
1293
|
+
if project_config_dir.exists():
|
|
1294
|
+
for config_file in sorted(project_config_dir.glob("*.yaml")):
|
|
1295
|
+
display_name = f".massgen/{config_file.name}"
|
|
1296
|
+
configs["Project Configs"].append((display_name, config_file))
|
|
1297
|
+
|
|
1298
|
+
# 3. Current directory (*.yaml files, excluding .massgen/ and non-massgen configs)
|
|
1299
|
+
# Filter out common non-massgen YAML files
|
|
1300
|
+
exclude_patterns = {
|
|
1301
|
+
".pre-commit-config.yaml",
|
|
1302
|
+
".readthedocs.yaml",
|
|
1303
|
+
".github",
|
|
1304
|
+
"docker-compose",
|
|
1305
|
+
"ansible",
|
|
1306
|
+
"kubernetes",
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
for config_file in sorted(Path.cwd().glob("*.yaml")):
|
|
1310
|
+
# Skip if inside .massgen/ (already covered)
|
|
1311
|
+
if ".massgen" in str(config_file):
|
|
1312
|
+
continue
|
|
1313
|
+
|
|
1314
|
+
# Skip common non-massgen config files
|
|
1315
|
+
file_name = config_file.name.lower()
|
|
1316
|
+
if any(pattern in file_name for pattern in exclude_patterns):
|
|
1317
|
+
continue
|
|
1318
|
+
|
|
1319
|
+
display_name = config_file.name
|
|
1320
|
+
configs["Current Directory"].append((display_name, config_file))
|
|
1321
|
+
|
|
1322
|
+
# 4. Package examples (massgen/configs/)
|
|
1323
|
+
try:
|
|
1324
|
+
from importlib.resources import files
|
|
1325
|
+
|
|
1326
|
+
configs_root = files("massgen") / "configs"
|
|
1327
|
+
|
|
1328
|
+
# Organize by subdirectory
|
|
1329
|
+
for config_file in sorted(configs_root.rglob("*.yaml")):
|
|
1330
|
+
# Get relative path from configs root
|
|
1331
|
+
rel_path = str(config_file).replace(str(configs_root) + "/", "")
|
|
1332
|
+
# Skip README and docs
|
|
1333
|
+
if "README" in rel_path or "BACKEND_CONFIGURATION" in rel_path:
|
|
1334
|
+
continue
|
|
1335
|
+
# Use relative path as display name
|
|
1336
|
+
display_name = rel_path.replace(".yaml", "")
|
|
1337
|
+
configs["Package Examples"].append((display_name, Path(str(config_file))))
|
|
1338
|
+
|
|
1339
|
+
except Exception as e:
|
|
1340
|
+
logger.warning(f"Could not load package examples: {e}")
|
|
1341
|
+
|
|
1342
|
+
# Remove empty categories
|
|
1343
|
+
configs = {k: v for k, v in configs.items() if v}
|
|
1344
|
+
|
|
1345
|
+
return configs
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def interactive_config_selector() -> Optional[str]:
|
|
1349
|
+
"""Interactively select a configuration file.
|
|
1350
|
+
|
|
1351
|
+
Shows user/project/current directory configs directly in a flat list.
|
|
1352
|
+
Package examples are shown hierarchically (category → config).
|
|
1353
|
+
|
|
1354
|
+
Returns:
|
|
1355
|
+
Path to selected config file, or None if cancelled
|
|
1356
|
+
"""
|
|
1357
|
+
# Create console instance for rich output
|
|
1358
|
+
selector_console = Console()
|
|
1359
|
+
|
|
1360
|
+
# Discover all available configs
|
|
1361
|
+
configs = discover_available_configs()
|
|
1362
|
+
|
|
1363
|
+
if not configs:
|
|
1364
|
+
selector_console.print(
|
|
1365
|
+
"\n[yellow]⚠️ No configurations found![/yellow]",
|
|
1366
|
+
)
|
|
1367
|
+
selector_console.print("[dim]Create one with: massgen --init[/dim]\n")
|
|
1368
|
+
return None
|
|
1369
|
+
|
|
1370
|
+
# Create a summary table showing what's available
|
|
1371
|
+
summary_table = Table(
|
|
1372
|
+
show_header=True,
|
|
1373
|
+
header_style="bold bright_white",
|
|
1374
|
+
border_style="bright_black",
|
|
1375
|
+
box=None,
|
|
1376
|
+
padding=(0, 1),
|
|
1377
|
+
width=88,
|
|
1378
|
+
)
|
|
1379
|
+
summary_table.add_column("Category", style="bright_cyan", no_wrap=True, width=25)
|
|
1380
|
+
summary_table.add_column("Count", justify="center", style="bright_yellow", width=10)
|
|
1381
|
+
summary_table.add_column("Location", style="dim")
|
|
1382
|
+
|
|
1383
|
+
# Build summary and choices
|
|
1384
|
+
choices = []
|
|
1385
|
+
|
|
1386
|
+
# Build summary table (overview only - no duplication)
|
|
1387
|
+
# User configs
|
|
1388
|
+
if "User Configs" in configs and configs["User Configs"]:
|
|
1389
|
+
summary_table.add_row(
|
|
1390
|
+
"👤 Your Configs",
|
|
1391
|
+
str(len(configs["User Configs"])),
|
|
1392
|
+
"~/.config/massgen/agents/",
|
|
1393
|
+
)
|
|
1394
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1395
|
+
for display_name, path in configs["User Configs"]:
|
|
1396
|
+
choices.append(
|
|
1397
|
+
questionary.Choice(
|
|
1398
|
+
title=f" 👤 {display_name}",
|
|
1399
|
+
value=str(path),
|
|
1400
|
+
),
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
# Project configs
|
|
1404
|
+
if "Project Configs" in configs and configs["Project Configs"]:
|
|
1405
|
+
summary_table.add_row(
|
|
1406
|
+
"📁 Project Configs",
|
|
1407
|
+
str(len(configs["Project Configs"])),
|
|
1408
|
+
".massgen/",
|
|
1409
|
+
)
|
|
1410
|
+
if choices:
|
|
1411
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1412
|
+
else:
|
|
1413
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1414
|
+
for display_name, path in configs["Project Configs"]:
|
|
1415
|
+
choices.append(
|
|
1416
|
+
questionary.Choice(
|
|
1417
|
+
title=f" 📁 {display_name}",
|
|
1418
|
+
value=str(path),
|
|
1419
|
+
),
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1422
|
+
# Current directory configs
|
|
1423
|
+
if "Current Directory" in configs and configs["Current Directory"]:
|
|
1424
|
+
summary_table.add_row(
|
|
1425
|
+
"📂 Current Directory",
|
|
1426
|
+
str(len(configs["Current Directory"])),
|
|
1427
|
+
f"*.yaml in {Path.cwd().name}/",
|
|
1428
|
+
)
|
|
1429
|
+
if choices:
|
|
1430
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1431
|
+
else:
|
|
1432
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1433
|
+
for display_name, path in configs["Current Directory"]:
|
|
1434
|
+
choices.append(
|
|
1435
|
+
questionary.Choice(
|
|
1436
|
+
title=f" 📂 {display_name}",
|
|
1437
|
+
value=str(path),
|
|
1438
|
+
),
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
# Package examples
|
|
1442
|
+
if "Package Examples" in configs and configs["Package Examples"]:
|
|
1443
|
+
summary_table.add_row(
|
|
1444
|
+
"📦 Package Examples",
|
|
1445
|
+
str(len(configs["Package Examples"])),
|
|
1446
|
+
"Built-in examples (hierarchical browser)",
|
|
1447
|
+
)
|
|
1448
|
+
if choices:
|
|
1449
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1450
|
+
choices.append(
|
|
1451
|
+
questionary.Choice(
|
|
1452
|
+
title=f" 📦 Browse {len(configs['Package Examples'])} example configs →",
|
|
1453
|
+
value="__browse_examples__",
|
|
1454
|
+
),
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
# Display summary table in a panel
|
|
1458
|
+
selector_console.print()
|
|
1459
|
+
selector_console.print(
|
|
1460
|
+
Panel(
|
|
1461
|
+
summary_table,
|
|
1462
|
+
title="[bold bright_cyan]🚀 Select a Configuration[/bold bright_cyan]",
|
|
1463
|
+
border_style="bright_cyan",
|
|
1464
|
+
padding=(0, 1),
|
|
1465
|
+
width=90,
|
|
1466
|
+
),
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
# Add cancel option
|
|
1470
|
+
choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1471
|
+
choices.append(questionary.Choice(title=" ❌ Cancel", value="__cancel__"))
|
|
1472
|
+
|
|
1473
|
+
# Show the selector
|
|
1474
|
+
selector_console.print()
|
|
1475
|
+
selected = questionary.select(
|
|
1476
|
+
"Select a configuration:",
|
|
1477
|
+
choices=choices,
|
|
1478
|
+
use_shortcuts=True,
|
|
1479
|
+
use_arrow_keys=True,
|
|
1480
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1481
|
+
pointer="▸",
|
|
1482
|
+
).ask()
|
|
1483
|
+
|
|
1484
|
+
if selected is None or selected == "__cancel__":
|
|
1485
|
+
selector_console.print("\n[yellow]⚠️ Selection cancelled[/yellow]\n")
|
|
1486
|
+
return None
|
|
1487
|
+
|
|
1488
|
+
# If user wants to browse package examples, show hierarchical navigation
|
|
1489
|
+
if selected == "__browse_examples__":
|
|
1490
|
+
return _select_package_example(configs["Package Examples"], selector_console)
|
|
1491
|
+
|
|
1492
|
+
# Otherwise, return the selected config path
|
|
1493
|
+
selector_console.print(f"\n[bold green]✓ Selected:[/bold green] [cyan]{selected}[/cyan]\n")
|
|
1494
|
+
return selected
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
def _select_package_example(examples: List[Tuple[str, Path]], console: Console) -> Optional[str]:
|
|
1498
|
+
"""Show hierarchical navigation for package examples.
|
|
1499
|
+
|
|
1500
|
+
Args:
|
|
1501
|
+
examples: List of (display_name, path) tuples
|
|
1502
|
+
console: Rich console for output
|
|
1503
|
+
|
|
1504
|
+
Returns:
|
|
1505
|
+
Path to selected config, or None if cancelled/back
|
|
1506
|
+
"""
|
|
1507
|
+
# Organize examples by category (first directory in path)
|
|
1508
|
+
categories = {}
|
|
1509
|
+
for display_name, path in examples:
|
|
1510
|
+
# Extract category from display name (e.g., "basic/multi/config" -> "basic")
|
|
1511
|
+
parts = display_name.split("/")
|
|
1512
|
+
category = parts[0] if len(parts) > 1 else "other"
|
|
1513
|
+
|
|
1514
|
+
if category not in categories:
|
|
1515
|
+
categories[category] = []
|
|
1516
|
+
categories[category].append((display_name, path))
|
|
1517
|
+
|
|
1518
|
+
# Emoji mapping for categories
|
|
1519
|
+
category_emojis = {
|
|
1520
|
+
"basic": "🎯",
|
|
1521
|
+
"tools": "🛠️",
|
|
1522
|
+
"providers": "🌐",
|
|
1523
|
+
"configs": "⚙️",
|
|
1524
|
+
"other": "📋",
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
# Create category summary table
|
|
1528
|
+
category_table = Table(
|
|
1529
|
+
show_header=True,
|
|
1530
|
+
header_style="bold bright_white",
|
|
1531
|
+
border_style="bright_black",
|
|
1532
|
+
box=None,
|
|
1533
|
+
padding=(0, 1),
|
|
1534
|
+
width=88,
|
|
1535
|
+
)
|
|
1536
|
+
category_table.add_column("Category", style="bright_cyan", no_wrap=True, width=20)
|
|
1537
|
+
category_table.add_column("Count", justify="center", style="bright_yellow", width=10)
|
|
1538
|
+
category_table.add_column("Description", style="dim")
|
|
1539
|
+
|
|
1540
|
+
# Category descriptions
|
|
1541
|
+
category_descriptions = {
|
|
1542
|
+
"basic": "Simple configurations for getting started",
|
|
1543
|
+
"tools": "Configs demonstrating tool integrations",
|
|
1544
|
+
"providers": "Provider-specific example configs",
|
|
1545
|
+
"configs": "Advanced configuration examples",
|
|
1546
|
+
"other": "Miscellaneous configurations",
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
# Build category table and choices
|
|
1550
|
+
category_choices = []
|
|
1551
|
+
for category in sorted(categories.keys()):
|
|
1552
|
+
count = len(categories[category])
|
|
1553
|
+
emoji = category_emojis.get(category, "📁")
|
|
1554
|
+
description = category_descriptions.get(category, "Example configurations")
|
|
1555
|
+
|
|
1556
|
+
category_table.add_row(
|
|
1557
|
+
f"{emoji} {category.title()}",
|
|
1558
|
+
str(count),
|
|
1559
|
+
description,
|
|
1560
|
+
)
|
|
1561
|
+
|
|
1562
|
+
category_choices.append(
|
|
1563
|
+
questionary.Choice(
|
|
1564
|
+
title=f" {emoji} {category.title()} ({count} config{'s' if count != 1 else ''})",
|
|
1565
|
+
value=category,
|
|
1566
|
+
),
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
# Display category summary in a panel
|
|
1570
|
+
console.print()
|
|
1571
|
+
console.print(
|
|
1572
|
+
Panel(
|
|
1573
|
+
category_table,
|
|
1574
|
+
title="[bold bright_yellow]📦 Package Examples - Select Category[/bold bright_yellow]",
|
|
1575
|
+
border_style="bright_yellow",
|
|
1576
|
+
padding=(0, 1),
|
|
1577
|
+
width=90,
|
|
1578
|
+
),
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
# Add back option
|
|
1582
|
+
category_choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1583
|
+
category_choices.append(questionary.Choice(title=" ← Back to main menu", value="__back__"))
|
|
1584
|
+
|
|
1585
|
+
# Step 1: Select category
|
|
1586
|
+
console.print()
|
|
1587
|
+
selected_category = questionary.select(
|
|
1588
|
+
"Select a category:",
|
|
1589
|
+
choices=category_choices,
|
|
1590
|
+
use_shortcuts=True,
|
|
1591
|
+
use_arrow_keys=True,
|
|
1592
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1593
|
+
pointer="▸",
|
|
1594
|
+
).ask()
|
|
1595
|
+
|
|
1596
|
+
if selected_category is None or selected_category == "__cancel__":
|
|
1597
|
+
console.print("\n[yellow]⚠️ Selection cancelled[/yellow]\n")
|
|
1598
|
+
return None
|
|
1599
|
+
|
|
1600
|
+
if selected_category == "__back__":
|
|
1601
|
+
# Go back to main selector
|
|
1602
|
+
return interactive_config_selector()
|
|
1603
|
+
|
|
1604
|
+
# Create configs table
|
|
1605
|
+
emoji = category_emojis.get(selected_category, "📁")
|
|
1606
|
+
configs_table = Table(
|
|
1607
|
+
show_header=True,
|
|
1608
|
+
header_style="bold bright_white",
|
|
1609
|
+
border_style="bright_black",
|
|
1610
|
+
box=None,
|
|
1611
|
+
padding=(0, 1),
|
|
1612
|
+
width=88,
|
|
1613
|
+
)
|
|
1614
|
+
configs_table.add_column("#", style="dim", width=5, justify="right")
|
|
1615
|
+
configs_table.add_column("Configuration", style="bright_cyan")
|
|
1616
|
+
|
|
1617
|
+
# Build config choices and table
|
|
1618
|
+
config_choices = []
|
|
1619
|
+
for idx, (display_name, path) in enumerate(sorted(categories[selected_category]), 1):
|
|
1620
|
+
# Show relative path within category
|
|
1621
|
+
short_name = display_name.replace(f"{selected_category}/", "")
|
|
1622
|
+
configs_table.add_row(str(idx), short_name)
|
|
1623
|
+
config_choices.append(
|
|
1624
|
+
questionary.Choice(
|
|
1625
|
+
title=f" {idx:2d}. {short_name}",
|
|
1626
|
+
value=str(path),
|
|
1627
|
+
),
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
# Display configs in a panel
|
|
1631
|
+
console.print()
|
|
1632
|
+
console.print(
|
|
1633
|
+
Panel(
|
|
1634
|
+
configs_table,
|
|
1635
|
+
title=f"[bold bright_green]{emoji} {selected_category.title()} Configurations[/bold bright_green]",
|
|
1636
|
+
border_style="bright_green",
|
|
1637
|
+
padding=(0, 1),
|
|
1638
|
+
width=90,
|
|
1639
|
+
),
|
|
1640
|
+
)
|
|
1641
|
+
|
|
1642
|
+
# Add back option
|
|
1643
|
+
config_choices.append(questionary.Separator("\n─────────────────────────────────"))
|
|
1644
|
+
config_choices.append(questionary.Choice(title=" ← Back to categories", value="__back__"))
|
|
1645
|
+
|
|
1646
|
+
# Step 2: Select config
|
|
1647
|
+
# For large lists: disable shortcuts (max 36) and enable search filter for better UX
|
|
1648
|
+
# Note: When search filter is enabled, j/k keys must be disabled (they conflict with search)
|
|
1649
|
+
use_shortcuts = len(config_choices) <= 36
|
|
1650
|
+
use_search_filter = len(config_choices) > 36
|
|
1651
|
+
console.print()
|
|
1652
|
+
selected_config = questionary.select(
|
|
1653
|
+
"Select a configuration:",
|
|
1654
|
+
choices=config_choices,
|
|
1655
|
+
use_shortcuts=use_shortcuts,
|
|
1656
|
+
use_arrow_keys=True,
|
|
1657
|
+
use_search_filter=use_search_filter,
|
|
1658
|
+
use_jk_keys=not use_search_filter,
|
|
1659
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1660
|
+
pointer="▸",
|
|
1661
|
+
).ask()
|
|
1662
|
+
|
|
1663
|
+
if selected_config is None or selected_config == "__cancel__":
|
|
1664
|
+
console.print("\n[yellow]⚠️ Selection cancelled[/yellow]\n")
|
|
1665
|
+
return None
|
|
1666
|
+
|
|
1667
|
+
if selected_config == "__back__":
|
|
1668
|
+
# Recursively call to go back to category selection
|
|
1669
|
+
return _select_package_example(examples, console)
|
|
1670
|
+
|
|
1671
|
+
# Return the selected config path
|
|
1672
|
+
console.print(f"\n[bold green]✓ Selected:[/bold green] [cyan]{selected_config}[/cyan]\n")
|
|
1673
|
+
return selected_config
|
|
1674
|
+
|
|
1675
|
+
|
|
1222
1676
|
def should_run_builder() -> bool:
|
|
1223
1677
|
"""Check if config builder should run automatically.
|
|
1224
1678
|
|
|
@@ -1511,6 +1965,14 @@ async def run_interactive_mode(
|
|
|
1511
1965
|
setup_logging(debug=_DEBUG_MODE, turn=next_turn)
|
|
1512
1966
|
logger.info(f"Starting turn {next_turn}")
|
|
1513
1967
|
|
|
1968
|
+
# Save execution metadata for this turn (original_config already has pre-relocation paths)
|
|
1969
|
+
save_execution_metadata(
|
|
1970
|
+
query=question,
|
|
1971
|
+
config_path=config_path,
|
|
1972
|
+
config_content=original_config, # This is the pre-relocation config passed from main()
|
|
1973
|
+
cli_args={"mode": "interactive", "turn": next_turn, "session_id": session_id},
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1514
1976
|
# Pass session state for multi-turn filesystem support
|
|
1515
1977
|
session_info = {
|
|
1516
1978
|
"session_id": session_id,
|
|
@@ -1612,6 +2074,9 @@ async def main(args):
|
|
|
1612
2074
|
logger.debug(f"Created simple config with backend: {backend}, model: {model}")
|
|
1613
2075
|
logger.debug(f"Config content: {json.dumps(config, indent=2)}")
|
|
1614
2076
|
|
|
2077
|
+
# Save original config before relocation (for execution_metadata.yaml)
|
|
2078
|
+
original_config_for_metadata = copy.deepcopy(config)
|
|
2079
|
+
|
|
1615
2080
|
# Validate that all context paths exist before proceeding
|
|
1616
2081
|
validate_context_paths(config)
|
|
1617
2082
|
|
|
@@ -1695,6 +2160,16 @@ async def main(args):
|
|
|
1695
2160
|
if "orchestrator" in config:
|
|
1696
2161
|
kwargs["orchestrator"] = config["orchestrator"]
|
|
1697
2162
|
|
|
2163
|
+
# Save execution metadata for debugging and reconstruction
|
|
2164
|
+
if args.question:
|
|
2165
|
+
# For single question mode, save metadata now (use original config before .massgen/ relocation)
|
|
2166
|
+
save_execution_metadata(
|
|
2167
|
+
query=args.question,
|
|
2168
|
+
config_path=str(resolved_path) if args.config and "resolved_path" in locals() else None,
|
|
2169
|
+
config_content=original_config_for_metadata,
|
|
2170
|
+
cli_args=vars(args),
|
|
2171
|
+
)
|
|
2172
|
+
|
|
1698
2173
|
# Run mode based on whether question was provided
|
|
1699
2174
|
try:
|
|
1700
2175
|
if args.question:
|
|
@@ -1734,23 +2209,27 @@ def cli_main():
|
|
|
1734
2209
|
epilog="""
|
|
1735
2210
|
Examples:
|
|
1736
2211
|
# Use configuration file
|
|
1737
|
-
|
|
2212
|
+
massgen --config config.yaml "What is machine learning?"
|
|
1738
2213
|
|
|
1739
2214
|
# Quick single agent setup
|
|
1740
|
-
|
|
1741
|
-
|
|
2215
|
+
massgen --backend openai --model gpt-4o-mini "Explain quantum computing"
|
|
2216
|
+
massgen --backend claude --model claude-sonnet-4-20250514 "Analyze this data"
|
|
1742
2217
|
|
|
1743
2218
|
# Use ChatCompletion backend with custom base URL
|
|
1744
|
-
|
|
2219
|
+
massgen --backend chatcompletion --model gpt-oss-120b --base-url https://api.cerebras.ai/v1/chat/completions "What is 2+2?"
|
|
1745
2220
|
|
|
1746
2221
|
# Interactive mode
|
|
1747
|
-
|
|
2222
|
+
massgen --config config.yaml
|
|
2223
|
+
massgen # Uses default config if available
|
|
1748
2224
|
|
|
1749
2225
|
# Timeout control examples
|
|
1750
|
-
|
|
2226
|
+
massgen --config config.yaml --orchestrator-timeout 600 "Complex task"
|
|
1751
2227
|
|
|
1752
|
-
#
|
|
1753
|
-
|
|
2228
|
+
# Configuration management
|
|
2229
|
+
massgen --init # Create new configuration interactively
|
|
2230
|
+
massgen --select # Choose from available configurations
|
|
2231
|
+
massgen --setup # Set up API keys
|
|
2232
|
+
massgen --list-examples # View example configurations
|
|
1754
2233
|
|
|
1755
2234
|
Environment Variables:
|
|
1756
2235
|
OPENAI_API_KEY - Required for OpenAI backend
|
|
@@ -1782,6 +2261,11 @@ Environment Variables:
|
|
|
1782
2261
|
# Configuration options
|
|
1783
2262
|
config_group = parser.add_mutually_exclusive_group()
|
|
1784
2263
|
config_group.add_argument("--config", type=str, help="Path to YAML/JSON configuration file or @examples/NAME")
|
|
2264
|
+
config_group.add_argument(
|
|
2265
|
+
"--select",
|
|
2266
|
+
action="store_true",
|
|
2267
|
+
help="Interactively select from available configurations",
|
|
2268
|
+
)
|
|
1785
2269
|
config_group.add_argument(
|
|
1786
2270
|
"--backend",
|
|
1787
2271
|
type=str,
|
|
@@ -1825,7 +2309,7 @@ Environment Variables:
|
|
|
1825
2309
|
help="Launch interactive configuration builder to create config file",
|
|
1826
2310
|
)
|
|
1827
2311
|
parser.add_argument(
|
|
1828
|
-
"--setup
|
|
2312
|
+
"--setup",
|
|
1829
2313
|
action="store_true",
|
|
1830
2314
|
help="Launch interactive API key setup wizard to configure credentials",
|
|
1831
2315
|
)
|
|
@@ -1888,7 +2372,7 @@ Environment Variables:
|
|
|
1888
2372
|
return
|
|
1889
2373
|
|
|
1890
2374
|
# Launch interactive API key setup if requested
|
|
1891
|
-
if args.
|
|
2375
|
+
if args.setup:
|
|
1892
2376
|
from .config_builder import ConfigBuilder
|
|
1893
2377
|
|
|
1894
2378
|
builder = ConfigBuilder()
|
|
@@ -1899,9 +2383,20 @@ Environment Variables:
|
|
|
1899
2383
|
print(f"{BRIGHT_CYAN}💡 You can now use MassGen with these providers{RESET}\n")
|
|
1900
2384
|
else:
|
|
1901
2385
|
print(f"\n{BRIGHT_YELLOW}⚠️ No API keys configured{RESET}")
|
|
1902
|
-
print(f"{BRIGHT_CYAN}💡 You can run 'massgen --setup
|
|
2386
|
+
print(f"{BRIGHT_CYAN}💡 You can run 'massgen --setup' anytime to set them up{RESET}\n")
|
|
1903
2387
|
return
|
|
1904
2388
|
|
|
2389
|
+
# Launch interactive config selector if requested
|
|
2390
|
+
if args.select:
|
|
2391
|
+
selected_config = interactive_config_selector()
|
|
2392
|
+
if selected_config:
|
|
2393
|
+
# Update args to use the selected config
|
|
2394
|
+
args.config = selected_config
|
|
2395
|
+
# Continue to main() with the selected config
|
|
2396
|
+
else:
|
|
2397
|
+
# User cancelled selection
|
|
2398
|
+
return
|
|
2399
|
+
|
|
1905
2400
|
# Launch interactive config builder if requested
|
|
1906
2401
|
if args.init:
|
|
1907
2402
|
from .config_builder import ConfigBuilder
|
|
@@ -1918,7 +2413,7 @@ Environment Variables:
|
|
|
1918
2413
|
elif filepath:
|
|
1919
2414
|
# Config created but user chose not to run
|
|
1920
2415
|
print(f"\n✅ Configuration saved to: {filepath}")
|
|
1921
|
-
print(f'Run with:
|
|
2416
|
+
print(f'Run with: massgen --config {filepath} "Your question"')
|
|
1922
2417
|
return
|
|
1923
2418
|
else:
|
|
1924
2419
|
# User cancelled
|