massgen 0.1.0a2__py3-none-any.whl → 0.1.1__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 +8 -1
- 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 +31 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +282 -11
- massgen/backend/chat_completions.py +182 -92
- 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 +1275 -1607
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/response.py +129 -70
- massgen/cli.py +643 -132
- massgen/config_builder.py +381 -32
- massgen/configs/README.md +111 -80
- massgen/configs/basic/multi/three_agents_default.yaml +1 -1
- 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/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 +179 -10
- 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_tools.py +127 -0
- 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.0a2.dist-info → massgen-0.1.1.dist-info}/METADATA +89 -131
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/RECORD +111 -36
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/top_level.txt +0 -0
massgen/config_builder.py
CHANGED
|
@@ -25,7 +25,11 @@ from rich.prompt import Confirm, Prompt
|
|
|
25
25
|
from rich.table import Table
|
|
26
26
|
from rich.theme import Theme
|
|
27
27
|
|
|
28
|
-
from massgen.backend.capabilities import
|
|
28
|
+
from massgen.backend.capabilities import (
|
|
29
|
+
BACKEND_CAPABILITIES,
|
|
30
|
+
get_capabilities,
|
|
31
|
+
has_capability,
|
|
32
|
+
)
|
|
29
33
|
|
|
30
34
|
# Load environment variables
|
|
31
35
|
load_dotenv()
|
|
@@ -33,11 +37,11 @@ load_dotenv()
|
|
|
33
37
|
# Custom theme for the CLI - using colors that work on both light and dark backgrounds
|
|
34
38
|
custom_theme = Theme(
|
|
35
39
|
{
|
|
36
|
-
"info": "
|
|
37
|
-
"warning": "
|
|
38
|
-
"error": "
|
|
39
|
-
"success": "
|
|
40
|
-
"prompt": "
|
|
40
|
+
"info": "#4A90E2", # Medium blue - matches system status colors
|
|
41
|
+
"warning": "#CC6600", # Orange-brown - works on both light and dark
|
|
42
|
+
"error": "#CC0000 bold", # Deep red - strong contrast
|
|
43
|
+
"success": "#00AA44 bold", # Deep green - visible on both
|
|
44
|
+
"prompt": "#6633CC bold", # Purple - good on both backgrounds
|
|
41
45
|
},
|
|
42
46
|
)
|
|
43
47
|
|
|
@@ -790,12 +794,13 @@ class ConfigBuilder:
|
|
|
790
794
|
console.print(f"[error]❌ Error configuring custom MCP: {e}[/error]")
|
|
791
795
|
return None
|
|
792
796
|
|
|
793
|
-
def batch_create_agents(self, count: int, provider_id: str) -> List[Dict]:
|
|
797
|
+
def batch_create_agents(self, count: int, provider_id: str, start_index: int = 0) -> List[Dict]:
|
|
794
798
|
"""Create multiple agents with the same provider.
|
|
795
799
|
|
|
796
800
|
Args:
|
|
797
801
|
count: Number of agents to create
|
|
798
802
|
provider_id: Provider ID (e.g., 'openai', 'claude')
|
|
803
|
+
start_index: Starting index for agent naming (default: 0)
|
|
799
804
|
|
|
800
805
|
Returns:
|
|
801
806
|
List of agent configurations with default models
|
|
@@ -806,7 +811,7 @@ class ConfigBuilder:
|
|
|
806
811
|
# Generate agent IDs like agent_a, agent_b, agent_c...
|
|
807
812
|
for i in range(count):
|
|
808
813
|
# Convert index to letter (0->a, 1->b, 2->c, etc.)
|
|
809
|
-
agent_letter = chr(ord("a") + i)
|
|
814
|
+
agent_letter = chr(ord("a") + start_index + i)
|
|
810
815
|
|
|
811
816
|
agent = {
|
|
812
817
|
"id": f"agent_{agent_letter}",
|
|
@@ -816,20 +821,21 @@ class ConfigBuilder:
|
|
|
816
821
|
},
|
|
817
822
|
}
|
|
818
823
|
|
|
819
|
-
# Add workspace for Claude Code (use
|
|
824
|
+
# Add workspace for Claude Code (use global index for unique workspace names)
|
|
820
825
|
if provider_info.get("type") == "claude_code":
|
|
821
|
-
agent["backend"]["cwd"] = f"workspace{i + 1}"
|
|
826
|
+
agent["backend"]["cwd"] = f"workspace{start_index + i + 1}"
|
|
822
827
|
|
|
823
828
|
agents.append(agent)
|
|
824
829
|
|
|
825
830
|
return agents
|
|
826
831
|
|
|
827
|
-
def clone_agent(self, source_agent: Dict, new_id: str) -> Dict:
|
|
828
|
-
"""Clone an agent's configuration with a new ID.
|
|
832
|
+
def clone_agent(self, source_agent: Dict, new_id: str, target_backend_type: str = None) -> Dict:
|
|
833
|
+
"""Clone an agent's configuration with a new ID, optionally preserving target backend.
|
|
829
834
|
|
|
830
835
|
Args:
|
|
831
836
|
source_agent: Agent to clone
|
|
832
837
|
new_id: New agent ID
|
|
838
|
+
target_backend_type: If provided, preserve this backend type instead of copying source's
|
|
833
839
|
|
|
834
840
|
Returns:
|
|
835
841
|
Cloned agent with updated ID and workspace (if applicable)
|
|
@@ -839,9 +845,91 @@ class ConfigBuilder:
|
|
|
839
845
|
cloned = copy.deepcopy(source_agent)
|
|
840
846
|
cloned["id"] = new_id
|
|
841
847
|
|
|
842
|
-
#
|
|
843
|
-
|
|
844
|
-
|
|
848
|
+
# If target backend type is different, preserve it and update model
|
|
849
|
+
if target_backend_type and target_backend_type != source_agent.get("backend", {}).get("type"):
|
|
850
|
+
# Find target provider info to get default model
|
|
851
|
+
target_provider_info = None
|
|
852
|
+
for pid, pinfo in self.PROVIDERS.items():
|
|
853
|
+
if pinfo.get("type") == target_backend_type:
|
|
854
|
+
target_provider_info = pinfo
|
|
855
|
+
break
|
|
856
|
+
|
|
857
|
+
if target_provider_info:
|
|
858
|
+
# Preserve tool enablement flags (provider-agnostic)
|
|
859
|
+
preserved_settings = {}
|
|
860
|
+
skipped_settings = []
|
|
861
|
+
source_backend = source_agent.get("backend", {})
|
|
862
|
+
source_backend_type = source_backend.get("type")
|
|
863
|
+
|
|
864
|
+
# Copy filesystem settings (provider-agnostic)
|
|
865
|
+
if "cwd" in source_backend:
|
|
866
|
+
preserved_settings["cwd"] = source_backend["cwd"]
|
|
867
|
+
|
|
868
|
+
# Copy MCP servers (provider-agnostic, but check if target supports MCP)
|
|
869
|
+
if "mcp_servers" in source_backend:
|
|
870
|
+
# Check if target provider supports MCP
|
|
871
|
+
target_supports_mcp = "mcp" in target_provider_info.get("supports", [])
|
|
872
|
+
if target_supports_mcp:
|
|
873
|
+
preserved_settings["mcp_servers"] = copy.deepcopy(source_backend["mcp_servers"])
|
|
874
|
+
else:
|
|
875
|
+
skipped_settings.append("mcp_servers (not supported by target provider)")
|
|
876
|
+
|
|
877
|
+
# Copy tool flags if they exist and are supported by target
|
|
878
|
+
target_caps = get_capabilities(target_backend_type)
|
|
879
|
+
|
|
880
|
+
for key in [
|
|
881
|
+
"enable_web_search",
|
|
882
|
+
"enable_code_execution",
|
|
883
|
+
"enable_code_interpreter",
|
|
884
|
+
"enable_mcp_command_line",
|
|
885
|
+
"command_line_execution_mode",
|
|
886
|
+
]:
|
|
887
|
+
if key in source_backend:
|
|
888
|
+
# Check if target supports this specific tool
|
|
889
|
+
if key == "enable_web_search":
|
|
890
|
+
if has_capability(target_backend_type, "web_search"):
|
|
891
|
+
preserved_settings[key] = source_backend[key]
|
|
892
|
+
else:
|
|
893
|
+
skipped_settings.append(f"{key} (not supported by {target_backend_type})")
|
|
894
|
+
elif key == "enable_code_interpreter":
|
|
895
|
+
# code_interpreter is OpenAI/Azure-specific
|
|
896
|
+
if target_caps and "code_interpreter" in target_caps.builtin_tools:
|
|
897
|
+
preserved_settings[key] = source_backend[key]
|
|
898
|
+
else:
|
|
899
|
+
skipped_settings.append(f"{key} (not supported by {target_backend_type})")
|
|
900
|
+
elif key == "enable_code_execution":
|
|
901
|
+
# code_execution is Claude/Gemini-specific
|
|
902
|
+
if target_caps and "code_execution" in target_caps.builtin_tools:
|
|
903
|
+
preserved_settings[key] = source_backend[key]
|
|
904
|
+
else:
|
|
905
|
+
skipped_settings.append(f"{key} (not supported by {target_backend_type})")
|
|
906
|
+
else:
|
|
907
|
+
# MCP command line and execution mode are universal
|
|
908
|
+
preserved_settings[key] = source_backend[key]
|
|
909
|
+
|
|
910
|
+
# Copy reasoning/text settings if target is OpenAI
|
|
911
|
+
if target_backend_type == "openai":
|
|
912
|
+
for key in ["text", "reasoning"]:
|
|
913
|
+
if key in source_backend:
|
|
914
|
+
preserved_settings[key] = copy.deepcopy(source_backend[key])
|
|
915
|
+
elif source_backend_type == "openai":
|
|
916
|
+
# Source was OpenAI but target is not - these settings can't be copied
|
|
917
|
+
for key in ["text", "reasoning"]:
|
|
918
|
+
if key in source_backend:
|
|
919
|
+
skipped_settings.append(f"{key} (OpenAI-specific)")
|
|
920
|
+
|
|
921
|
+
# Replace backend with target provider's default model + preserved settings
|
|
922
|
+
cloned["backend"] = {
|
|
923
|
+
"type": target_backend_type,
|
|
924
|
+
"model": target_provider_info.get("models", ["default"])[0],
|
|
925
|
+
**preserved_settings,
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
# Store skipped settings for later warning
|
|
929
|
+
cloned["_skipped_settings"] = skipped_settings
|
|
930
|
+
|
|
931
|
+
# Update workspace for filesystem-enabled agents to avoid conflicts
|
|
932
|
+
if "cwd" in cloned.get("backend", {}):
|
|
845
933
|
# Extract number from new_id (e.g., "agent_b" -> 2)
|
|
846
934
|
if "_" in new_id and len(new_id) > 0:
|
|
847
935
|
agent_letter = new_id.split("_")[-1]
|
|
@@ -1049,12 +1137,13 @@ class ConfigBuilder:
|
|
|
1049
1137
|
console.print(f"[error]❌ Error modifying agent: {e}[/error]")
|
|
1050
1138
|
return agent
|
|
1051
1139
|
|
|
1052
|
-
def apply_preset_to_agent(self, agent: Dict, use_case: str) -> Dict:
|
|
1140
|
+
def apply_preset_to_agent(self, agent: Dict, use_case: str, agent_index: int = 1) -> Dict:
|
|
1053
1141
|
"""Auto-apply preset configuration to an agent.
|
|
1054
1142
|
|
|
1055
1143
|
Args:
|
|
1056
1144
|
agent: Agent configuration dict
|
|
1057
1145
|
use_case: Use case ID for preset configuration
|
|
1146
|
+
agent_index: Agent index for unique workspace naming (1-based)
|
|
1058
1147
|
|
|
1059
1148
|
Returns:
|
|
1060
1149
|
Updated agent configuration with preset applied
|
|
@@ -1080,7 +1169,8 @@ class ConfigBuilder:
|
|
|
1080
1169
|
# Auto-enable filesystem if recommended
|
|
1081
1170
|
if "filesystem" in recommended_tools and "filesystem" in provider_info.get("supports", []):
|
|
1082
1171
|
if not agent["backend"].get("cwd"):
|
|
1083
|
-
|
|
1172
|
+
# Generate unique workspace name for each agent
|
|
1173
|
+
agent["backend"]["cwd"] = f"workspace{agent_index}"
|
|
1084
1174
|
|
|
1085
1175
|
# Auto-enable web search if recommended
|
|
1086
1176
|
if "web_search" in recommended_tools:
|
|
@@ -1673,7 +1763,7 @@ class ConfigBuilder:
|
|
|
1673
1763
|
if not provider_id:
|
|
1674
1764
|
provider_id = available_providers[0]
|
|
1675
1765
|
|
|
1676
|
-
agent_batch = self.batch_create_agents(1, provider_id)
|
|
1766
|
+
agent_batch = self.batch_create_agents(1, provider_id, len(agents))
|
|
1677
1767
|
agents.extend(agent_batch)
|
|
1678
1768
|
|
|
1679
1769
|
provider_name = self.PROVIDERS.get(provider_id, {}).get("name", provider_id)
|
|
@@ -1843,7 +1933,7 @@ class ConfigBuilder:
|
|
|
1843
1933
|
console.print()
|
|
1844
1934
|
console.print(" [cyan]Applying preset configuration to all agents...[/cyan]")
|
|
1845
1935
|
for i, agent in enumerate(agents):
|
|
1846
|
-
agents[i] = self.apply_preset_to_agent(agent, use_case)
|
|
1936
|
+
agents[i] = self.apply_preset_to_agent(agent, use_case, agent_index=i + 1)
|
|
1847
1937
|
|
|
1848
1938
|
console.print(f" [green]✅ {len(agents)} agent(s) configured with preset[/green]")
|
|
1849
1939
|
console.print()
|
|
@@ -1878,17 +1968,107 @@ class ConfigBuilder:
|
|
|
1878
1968
|
).ask()
|
|
1879
1969
|
|
|
1880
1970
|
if clone_choice == "clone":
|
|
1881
|
-
# Clone the previous agent
|
|
1971
|
+
# Clone the previous agent, preserving current agent's backend type
|
|
1882
1972
|
source_agent = agents[i - 2]
|
|
1883
|
-
|
|
1973
|
+
target_backend_type = agent.get("backend", {}).get("type")
|
|
1974
|
+
source_backend_type = source_agent.get("backend", {}).get("type")
|
|
1975
|
+
|
|
1976
|
+
agent = self.clone_agent(source_agent, agent["id"], target_backend_type)
|
|
1977
|
+
|
|
1978
|
+
# If cross-provider cloning, prompt for model selection
|
|
1979
|
+
if target_backend_type != source_backend_type:
|
|
1980
|
+
console.print(f"✅ Cloned settings from agent_{chr(ord('a') + i - 2)} ({source_backend_type})")
|
|
1981
|
+
console.print(f" [dim]Note: Model must be selected for {target_backend_type}[/dim]")
|
|
1982
|
+
|
|
1983
|
+
# Show skipped settings warning if any
|
|
1984
|
+
skipped = agent.get("_skipped_settings", [])
|
|
1985
|
+
if skipped:
|
|
1986
|
+
console.print(" [yellow]⚠️ Skipped incompatible settings:[/yellow]")
|
|
1987
|
+
for setting in skipped:
|
|
1988
|
+
console.print(f" • {setting}")
|
|
1989
|
+
console.print()
|
|
1990
|
+
|
|
1991
|
+
# Prompt for model selection
|
|
1992
|
+
target_provider_info = None
|
|
1993
|
+
for _, pinfo in self.PROVIDERS.items():
|
|
1994
|
+
if pinfo.get("type") == target_backend_type:
|
|
1995
|
+
target_provider_info = pinfo
|
|
1996
|
+
break
|
|
1997
|
+
|
|
1998
|
+
if target_provider_info and target_provider_info.get("models"):
|
|
1999
|
+
model_choice = questionary.select(
|
|
2000
|
+
f"Select {target_backend_type} model:",
|
|
2001
|
+
choices=target_provider_info["models"],
|
|
2002
|
+
style=questionary.Style(
|
|
2003
|
+
[
|
|
2004
|
+
("selected", "fg:cyan bold"),
|
|
2005
|
+
("pointer", "fg:cyan bold"),
|
|
2006
|
+
],
|
|
2007
|
+
),
|
|
2008
|
+
).ask()
|
|
2009
|
+
|
|
2010
|
+
if model_choice:
|
|
2011
|
+
agent["backend"]["model"] = model_choice
|
|
2012
|
+
console.print(f" ✅ Model: {model_choice}")
|
|
2013
|
+
|
|
2014
|
+
# Clean up temporary skipped settings marker
|
|
2015
|
+
agent.pop("_skipped_settings", None)
|
|
2016
|
+
else:
|
|
2017
|
+
console.print(f"✅ Cloned configuration from agent_{chr(ord('a') + i - 2)}")
|
|
2018
|
+
# Clean up temporary skipped settings marker
|
|
2019
|
+
agent.pop("_skipped_settings", None)
|
|
2020
|
+
|
|
1884
2021
|
agents[i - 1] = agent
|
|
1885
|
-
console.print(f"✅ Cloned configuration from agent_{chr(ord('a') + i - 2)}")
|
|
1886
2022
|
console.print()
|
|
1887
2023
|
continue
|
|
1888
2024
|
elif clone_choice == "clone_modify":
|
|
1889
|
-
# Clone and selectively modify
|
|
2025
|
+
# Clone and selectively modify, preserving current agent's backend type
|
|
1890
2026
|
source_agent = agents[i - 2]
|
|
1891
|
-
|
|
2027
|
+
target_backend_type = agent.get("backend", {}).get("type")
|
|
2028
|
+
source_backend_type = source_agent.get("backend", {}).get("type")
|
|
2029
|
+
|
|
2030
|
+
agent = self.clone_agent(source_agent, agent["id"], target_backend_type)
|
|
2031
|
+
|
|
2032
|
+
# If cross-provider cloning, prompt for model selection before modification
|
|
2033
|
+
if target_backend_type != source_backend_type:
|
|
2034
|
+
console.print(f"✅ Cloned settings from agent_{chr(ord('a') + i - 2)} ({source_backend_type})")
|
|
2035
|
+
console.print(f" [dim]Note: Model must be selected for {target_backend_type}[/dim]")
|
|
2036
|
+
|
|
2037
|
+
# Show skipped settings warning if any
|
|
2038
|
+
skipped = agent.get("_skipped_settings", [])
|
|
2039
|
+
if skipped:
|
|
2040
|
+
console.print(" [yellow]⚠️ Skipped incompatible settings:[/yellow]")
|
|
2041
|
+
for setting in skipped:
|
|
2042
|
+
console.print(f" • {setting}")
|
|
2043
|
+
console.print()
|
|
2044
|
+
|
|
2045
|
+
# Prompt for model selection
|
|
2046
|
+
target_provider_info = None
|
|
2047
|
+
for _, pinfo in self.PROVIDERS.items():
|
|
2048
|
+
if pinfo.get("type") == target_backend_type:
|
|
2049
|
+
target_provider_info = pinfo
|
|
2050
|
+
break
|
|
2051
|
+
|
|
2052
|
+
if target_provider_info and target_provider_info.get("models"):
|
|
2053
|
+
model_choice = questionary.select(
|
|
2054
|
+
f"Select {target_backend_type} model:",
|
|
2055
|
+
choices=target_provider_info["models"],
|
|
2056
|
+
style=questionary.Style(
|
|
2057
|
+
[
|
|
2058
|
+
("selected", "fg:cyan bold"),
|
|
2059
|
+
("pointer", "fg:cyan bold"),
|
|
2060
|
+
],
|
|
2061
|
+
),
|
|
2062
|
+
).ask()
|
|
2063
|
+
|
|
2064
|
+
if model_choice:
|
|
2065
|
+
agent["backend"]["model"] = model_choice
|
|
2066
|
+
console.print(f" ✅ Model: {model_choice}")
|
|
2067
|
+
console.print()
|
|
2068
|
+
|
|
2069
|
+
# Clean up temporary skipped settings marker before modification
|
|
2070
|
+
agent.pop("_skipped_settings", None)
|
|
2071
|
+
|
|
1892
2072
|
agent = self.modify_cloned_agent(agent, i)
|
|
1893
2073
|
agents[i - 1] = agent
|
|
1894
2074
|
continue
|
|
@@ -1922,17 +2102,107 @@ class ConfigBuilder:
|
|
|
1922
2102
|
).ask()
|
|
1923
2103
|
|
|
1924
2104
|
if clone_choice == "clone":
|
|
1925
|
-
# Clone the previous agent
|
|
2105
|
+
# Clone the previous agent, preserving current agent's backend type
|
|
1926
2106
|
source_agent = agents[i - 2]
|
|
1927
|
-
|
|
2107
|
+
target_backend_type = agent.get("backend", {}).get("type")
|
|
2108
|
+
source_backend_type = source_agent.get("backend", {}).get("type")
|
|
2109
|
+
|
|
2110
|
+
agent = self.clone_agent(source_agent, agent["id"], target_backend_type)
|
|
2111
|
+
|
|
2112
|
+
# If cross-provider cloning, prompt for model selection
|
|
2113
|
+
if target_backend_type != source_backend_type:
|
|
2114
|
+
console.print(f"✅ Cloned settings from agent_{chr(ord('a') + i - 2)} ({source_backend_type})")
|
|
2115
|
+
console.print(f" [dim]Note: Model must be selected for {target_backend_type}[/dim]")
|
|
2116
|
+
|
|
2117
|
+
# Show skipped settings warning if any
|
|
2118
|
+
skipped = agent.get("_skipped_settings", [])
|
|
2119
|
+
if skipped:
|
|
2120
|
+
console.print(" [yellow]⚠️ Skipped incompatible settings:[/yellow]")
|
|
2121
|
+
for setting in skipped:
|
|
2122
|
+
console.print(f" • {setting}")
|
|
2123
|
+
console.print()
|
|
2124
|
+
|
|
2125
|
+
# Prompt for model selection
|
|
2126
|
+
target_provider_info = None
|
|
2127
|
+
for _, pinfo in self.PROVIDERS.items():
|
|
2128
|
+
if pinfo.get("type") == target_backend_type:
|
|
2129
|
+
target_provider_info = pinfo
|
|
2130
|
+
break
|
|
2131
|
+
|
|
2132
|
+
if target_provider_info and target_provider_info.get("models"):
|
|
2133
|
+
model_choice = questionary.select(
|
|
2134
|
+
f"Select {target_backend_type} model:",
|
|
2135
|
+
choices=target_provider_info["models"],
|
|
2136
|
+
style=questionary.Style(
|
|
2137
|
+
[
|
|
2138
|
+
("selected", "fg:cyan bold"),
|
|
2139
|
+
("pointer", "fg:cyan bold"),
|
|
2140
|
+
],
|
|
2141
|
+
),
|
|
2142
|
+
).ask()
|
|
2143
|
+
|
|
2144
|
+
if model_choice:
|
|
2145
|
+
agent["backend"]["model"] = model_choice
|
|
2146
|
+
console.print(f" ✅ Model: {model_choice}")
|
|
2147
|
+
|
|
2148
|
+
# Clean up temporary skipped settings marker
|
|
2149
|
+
agent.pop("_skipped_settings", None)
|
|
2150
|
+
else:
|
|
2151
|
+
console.print(f"✅ Cloned configuration from agent_{chr(ord('a') + i - 2)}")
|
|
2152
|
+
# Clean up temporary skipped settings marker
|
|
2153
|
+
agent.pop("_skipped_settings", None)
|
|
2154
|
+
|
|
1928
2155
|
agents[i - 1] = agent
|
|
1929
|
-
console.print(f"✅ Cloned configuration from agent_{chr(ord('a') + i - 2)}")
|
|
1930
2156
|
console.print()
|
|
1931
2157
|
continue
|
|
1932
2158
|
elif clone_choice == "clone_modify":
|
|
1933
|
-
# Clone and selectively modify
|
|
2159
|
+
# Clone and selectively modify, preserving current agent's backend type
|
|
1934
2160
|
source_agent = agents[i - 2]
|
|
1935
|
-
|
|
2161
|
+
target_backend_type = agent.get("backend", {}).get("type")
|
|
2162
|
+
source_backend_type = source_agent.get("backend", {}).get("type")
|
|
2163
|
+
|
|
2164
|
+
agent = self.clone_agent(source_agent, agent["id"], target_backend_type)
|
|
2165
|
+
|
|
2166
|
+
# If cross-provider cloning, prompt for model selection before modification
|
|
2167
|
+
if target_backend_type != source_backend_type:
|
|
2168
|
+
console.print(f"✅ Cloned settings from agent_{chr(ord('a') + i - 2)} ({source_backend_type})")
|
|
2169
|
+
console.print(f" [dim]Note: Model must be selected for {target_backend_type}[/dim]")
|
|
2170
|
+
|
|
2171
|
+
# Show skipped settings warning if any
|
|
2172
|
+
skipped = agent.get("_skipped_settings", [])
|
|
2173
|
+
if skipped:
|
|
2174
|
+
console.print(" [yellow]⚠️ Skipped incompatible settings:[/yellow]")
|
|
2175
|
+
for setting in skipped:
|
|
2176
|
+
console.print(f" • {setting}")
|
|
2177
|
+
console.print()
|
|
2178
|
+
|
|
2179
|
+
# Prompt for model selection
|
|
2180
|
+
target_provider_info = None
|
|
2181
|
+
for _, pinfo in self.PROVIDERS.items():
|
|
2182
|
+
if pinfo.get("type") == target_backend_type:
|
|
2183
|
+
target_provider_info = pinfo
|
|
2184
|
+
break
|
|
2185
|
+
|
|
2186
|
+
if target_provider_info and target_provider_info.get("models"):
|
|
2187
|
+
model_choice = questionary.select(
|
|
2188
|
+
f"Select {target_backend_type} model:",
|
|
2189
|
+
choices=target_provider_info["models"],
|
|
2190
|
+
style=questionary.Style(
|
|
2191
|
+
[
|
|
2192
|
+
("selected", "fg:cyan bold"),
|
|
2193
|
+
("pointer", "fg:cyan bold"),
|
|
2194
|
+
],
|
|
2195
|
+
),
|
|
2196
|
+
).ask()
|
|
2197
|
+
|
|
2198
|
+
if model_choice:
|
|
2199
|
+
agent["backend"]["model"] = model_choice
|
|
2200
|
+
console.print(f" ✅ Model: {model_choice}")
|
|
2201
|
+
console.print()
|
|
2202
|
+
|
|
2203
|
+
# Clean up temporary skipped settings marker before modification
|
|
2204
|
+
agent.pop("_skipped_settings", None)
|
|
2205
|
+
|
|
1936
2206
|
agent = self.modify_cloned_agent(agent, i)
|
|
1937
2207
|
agents[i - 1] = agent
|
|
1938
2208
|
continue
|
|
@@ -2051,6 +2321,80 @@ class ConfigBuilder:
|
|
|
2051
2321
|
console.print()
|
|
2052
2322
|
console.print(" ✅ Planning mode enabled - MCP tools will plan without executing during coordination")
|
|
2053
2323
|
|
|
2324
|
+
# Voting Sensitivity - only ask for multi-agent setups
|
|
2325
|
+
if len(agents) > 1:
|
|
2326
|
+
console.print()
|
|
2327
|
+
console.print(" [dim]Voting Sensitivity: Controls how agents reach consensus[/dim]")
|
|
2328
|
+
console.print(" [dim]• L: Lenient - Lower threshold for faster decisions (default)[/dim]")
|
|
2329
|
+
console.print(" [dim]• B: Balanced - Often requires more answers for consensus[/dim]")
|
|
2330
|
+
console.print(" [dim]• S: Strict - High standards, maximum quality (slowest)[/dim]")
|
|
2331
|
+
console.print()
|
|
2332
|
+
|
|
2333
|
+
voting_input = Prompt.ask(
|
|
2334
|
+
" [prompt]Voting sensitivity[/prompt]",
|
|
2335
|
+
choices=["l", "b", "s"],
|
|
2336
|
+
default="l",
|
|
2337
|
+
)
|
|
2338
|
+
|
|
2339
|
+
# Map input to full value
|
|
2340
|
+
voting_map = {"l": "lenient", "b": "balanced", "s": "strict"}
|
|
2341
|
+
voting_choice = voting_map[voting_input]
|
|
2342
|
+
|
|
2343
|
+
orchestrator_config["voting_sensitivity"] = voting_choice
|
|
2344
|
+
console.print()
|
|
2345
|
+
console.print(f" ✅ Voting sensitivity set to: {voting_choice}")
|
|
2346
|
+
|
|
2347
|
+
# Answer Count Limit
|
|
2348
|
+
console.print()
|
|
2349
|
+
console.print(" [dim]Answer Count Limit: Controls maximum new answers per agent[/dim]")
|
|
2350
|
+
console.print(" [dim]• Prevents endless coordination rounds[/dim]")
|
|
2351
|
+
console.print(" [dim]• After limit, agents can only vote (not provide new answers)[/dim]")
|
|
2352
|
+
console.print()
|
|
2353
|
+
|
|
2354
|
+
limit_input = Prompt.ask(
|
|
2355
|
+
" [prompt]Max new answers per agent (leave empty for unlimited)[/prompt]",
|
|
2356
|
+
default="",
|
|
2357
|
+
)
|
|
2358
|
+
|
|
2359
|
+
if limit_input.strip():
|
|
2360
|
+
try:
|
|
2361
|
+
answer_limit = int(limit_input)
|
|
2362
|
+
if answer_limit > 0:
|
|
2363
|
+
orchestrator_config["max_new_answers_per_agent"] = answer_limit
|
|
2364
|
+
console.print()
|
|
2365
|
+
console.print(f" ✅ Answer limit set to: {answer_limit} per agent")
|
|
2366
|
+
else:
|
|
2367
|
+
console.print()
|
|
2368
|
+
console.print(" ⚠️ Invalid limit - using unlimited")
|
|
2369
|
+
except ValueError:
|
|
2370
|
+
console.print()
|
|
2371
|
+
console.print(" ⚠️ Invalid number - using unlimited")
|
|
2372
|
+
else:
|
|
2373
|
+
console.print()
|
|
2374
|
+
console.print(" ✅ Answer limit: unlimited")
|
|
2375
|
+
|
|
2376
|
+
# Answer Novelty Requirement
|
|
2377
|
+
console.print()
|
|
2378
|
+
console.print(" [dim]Answer Novelty: Controls how different new answers must be[/dim]")
|
|
2379
|
+
console.print(" [dim]• L: Lenient - No similarity checks (default, fastest)[/dim]")
|
|
2380
|
+
console.print(" [dim]• B: Balanced - Reject if >70% overlap (prevents rephrasing)[/dim]")
|
|
2381
|
+
console.print(" [dim]• S: Strict - Reject if >50% overlap (requires new approaches)[/dim]")
|
|
2382
|
+
console.print()
|
|
2383
|
+
|
|
2384
|
+
novelty_input = Prompt.ask(
|
|
2385
|
+
" [prompt]Answer novelty requirement[/prompt]",
|
|
2386
|
+
choices=["l", "b", "s"],
|
|
2387
|
+
default="l",
|
|
2388
|
+
)
|
|
2389
|
+
|
|
2390
|
+
# Map input to full value
|
|
2391
|
+
novelty_map = {"l": "lenient", "b": "balanced", "s": "strict"}
|
|
2392
|
+
novelty_choice = novelty_map[novelty_input]
|
|
2393
|
+
|
|
2394
|
+
orchestrator_config["answer_novelty_requirement"] = novelty_choice
|
|
2395
|
+
console.print()
|
|
2396
|
+
console.print(f" ✅ Answer novelty requirement set to: {novelty_choice}")
|
|
2397
|
+
|
|
2054
2398
|
return agents, orchestrator_config
|
|
2055
2399
|
|
|
2056
2400
|
except (KeyboardInterrupt, EOFError):
|
|
@@ -2135,11 +2479,13 @@ class ConfigBuilder:
|
|
|
2135
2479
|
default="1",
|
|
2136
2480
|
)
|
|
2137
2481
|
|
|
2482
|
+
# Determine save directory
|
|
2483
|
+
save_dir = None
|
|
2138
2484
|
if save_location == "2":
|
|
2139
2485
|
# Save to ~/.config/massgen/agents/
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
default_name = str(
|
|
2486
|
+
save_dir = Path.home() / ".config/massgen/agents"
|
|
2487
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
2488
|
+
default_name = str(save_dir / "my_massgen_config.yaml")
|
|
2143
2489
|
|
|
2144
2490
|
while True:
|
|
2145
2491
|
try:
|
|
@@ -2157,7 +2503,10 @@ class ConfigBuilder:
|
|
|
2157
2503
|
if not filename.endswith(".yaml"):
|
|
2158
2504
|
filename += ".yaml"
|
|
2159
2505
|
|
|
2506
|
+
# Create filepath - if save_dir is set and filename is not absolute, join them
|
|
2160
2507
|
filepath = Path(filename)
|
|
2508
|
+
if save_dir and not filepath.is_absolute():
|
|
2509
|
+
filepath = save_dir / filepath
|
|
2161
2510
|
|
|
2162
2511
|
# Check if file exists
|
|
2163
2512
|
if filepath.exists():
|