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/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."""
|
|
@@ -291,29 +311,33 @@ def create_backend(backend_type: str, **kwargs) -> Any:
|
|
|
291
311
|
if backend_type == "openai":
|
|
292
312
|
api_key = kwargs.get("api_key") or os.getenv("OPENAI_API_KEY")
|
|
293
313
|
if not api_key:
|
|
294
|
-
|
|
295
|
-
|
|
314
|
+
raise ConfigurationError(
|
|
315
|
+
"OpenAI API key not found. Set OPENAI_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
316
|
+
)
|
|
296
317
|
return ResponseBackend(api_key=api_key, **kwargs)
|
|
297
318
|
|
|
298
319
|
elif backend_type == "grok":
|
|
299
320
|
api_key = kwargs.get("api_key") or os.getenv("XAI_API_KEY")
|
|
300
321
|
if not api_key:
|
|
301
|
-
|
|
302
|
-
|
|
322
|
+
raise ConfigurationError(
|
|
323
|
+
"Grok API key not found. Set XAI_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
324
|
+
)
|
|
303
325
|
return GrokBackend(api_key=api_key, **kwargs)
|
|
304
326
|
|
|
305
327
|
elif backend_type == "claude":
|
|
306
328
|
api_key = kwargs.get("api_key") or os.getenv("ANTHROPIC_API_KEY")
|
|
307
329
|
if not api_key:
|
|
308
|
-
|
|
309
|
-
|
|
330
|
+
raise ConfigurationError(
|
|
331
|
+
"Claude API key not found. Set ANTHROPIC_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
332
|
+
)
|
|
310
333
|
return ClaudeBackend(api_key=api_key, **kwargs)
|
|
311
334
|
|
|
312
335
|
elif backend_type == "gemini":
|
|
313
336
|
api_key = kwargs.get("api_key") or os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
|
|
314
337
|
if not api_key:
|
|
315
|
-
|
|
316
|
-
|
|
338
|
+
raise ConfigurationError(
|
|
339
|
+
"Gemini API key not found. Set GOOGLE_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
340
|
+
)
|
|
317
341
|
return GeminiBackend(api_key=api_key, **kwargs)
|
|
318
342
|
|
|
319
343
|
elif backend_type == "chatcompletion":
|
|
@@ -325,43 +349,81 @@ def create_backend(backend_type: str, **kwargs) -> Any:
|
|
|
325
349
|
if base_url and "cerebras.ai" in base_url:
|
|
326
350
|
api_key = os.getenv("CEREBRAS_API_KEY")
|
|
327
351
|
if not api_key:
|
|
328
|
-
raise ConfigurationError(
|
|
352
|
+
raise ConfigurationError(
|
|
353
|
+
"Cerebras AI API key not found. Set CEREBRAS_API_KEY environment variable.\n"
|
|
354
|
+
"You can add it to a .env file in:\n"
|
|
355
|
+
" - Current directory: .env\n"
|
|
356
|
+
" - Global config: ~/.massgen/.env",
|
|
357
|
+
)
|
|
329
358
|
elif base_url and "together.xyz" in base_url:
|
|
330
359
|
api_key = os.getenv("TOGETHER_API_KEY")
|
|
331
360
|
if not api_key:
|
|
332
|
-
raise ConfigurationError(
|
|
361
|
+
raise ConfigurationError(
|
|
362
|
+
"Together AI API key not found. Set TOGETHER_API_KEY environment variable.\n"
|
|
363
|
+
"You can add it to a .env file in:\n"
|
|
364
|
+
" - Current directory: .env\n"
|
|
365
|
+
" - Global config: ~/.massgen/.env",
|
|
366
|
+
)
|
|
333
367
|
elif base_url and "fireworks.ai" in base_url:
|
|
334
368
|
api_key = os.getenv("FIREWORKS_API_KEY")
|
|
335
369
|
if not api_key:
|
|
336
|
-
raise ConfigurationError(
|
|
370
|
+
raise ConfigurationError(
|
|
371
|
+
"Fireworks AI API key not found. Set FIREWORKS_API_KEY environment variable.\n"
|
|
372
|
+
"You can add it to a .env file in:\n"
|
|
373
|
+
" - Current directory: .env\n"
|
|
374
|
+
" - Global config: ~/.massgen/.env",
|
|
375
|
+
)
|
|
337
376
|
elif base_url and "groq.com" in base_url:
|
|
338
377
|
api_key = os.getenv("GROQ_API_KEY")
|
|
339
378
|
if not api_key:
|
|
340
|
-
raise ConfigurationError(
|
|
379
|
+
raise ConfigurationError(
|
|
380
|
+
"Groq API key not found. Set GROQ_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
381
|
+
)
|
|
341
382
|
elif base_url and "nebius.com" in base_url:
|
|
342
383
|
api_key = os.getenv("NEBIUS_API_KEY")
|
|
343
384
|
if not api_key:
|
|
344
|
-
raise ConfigurationError(
|
|
385
|
+
raise ConfigurationError(
|
|
386
|
+
"Nebius AI Studio API key not found. Set NEBIUS_API_KEY environment variable.\n"
|
|
387
|
+
"You can add it to a .env file in:\n"
|
|
388
|
+
" - Current directory: .env\n"
|
|
389
|
+
" - Global config: ~/.massgen/.env",
|
|
390
|
+
)
|
|
345
391
|
elif base_url and "openrouter.ai" in base_url:
|
|
346
392
|
api_key = os.getenv("OPENROUTER_API_KEY")
|
|
347
393
|
if not api_key:
|
|
348
|
-
raise ConfigurationError(
|
|
394
|
+
raise ConfigurationError(
|
|
395
|
+
"OpenRouter API key not found. Set OPENROUTER_API_KEY environment variable.\n"
|
|
396
|
+
"You can add it to a .env file in:\n"
|
|
397
|
+
" - Current directory: .env\n"
|
|
398
|
+
" - Global config: ~/.massgen/.env",
|
|
399
|
+
)
|
|
349
400
|
elif base_url and ("z.ai" in base_url or "bigmodel.cn" in base_url):
|
|
350
401
|
api_key = os.getenv("ZAI_API_KEY")
|
|
351
402
|
if not api_key:
|
|
352
|
-
raise ConfigurationError(
|
|
403
|
+
raise ConfigurationError(
|
|
404
|
+
"ZAI API key not found. Set ZAI_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
405
|
+
)
|
|
353
406
|
elif base_url and ("moonshot.ai" in base_url or "moonshot.cn" in base_url):
|
|
354
407
|
api_key = os.getenv("MOONSHOT_API_KEY") or os.getenv("KIMI_API_KEY")
|
|
355
408
|
if not api_key:
|
|
356
|
-
raise ConfigurationError(
|
|
409
|
+
raise ConfigurationError(
|
|
410
|
+
"Kimi/Moonshot API key not found. Set MOONSHOT_API_KEY or KIMI_API_KEY environment variable.\n"
|
|
411
|
+
"You can add it to a .env file in:\n"
|
|
412
|
+
" - Current directory: .env\n"
|
|
413
|
+
" - Global config: ~/.massgen/.env",
|
|
414
|
+
)
|
|
357
415
|
elif base_url and "poe.com" in base_url:
|
|
358
416
|
api_key = os.getenv("POE_API_KEY")
|
|
359
417
|
if not api_key:
|
|
360
|
-
raise ConfigurationError(
|
|
418
|
+
raise ConfigurationError(
|
|
419
|
+
"POE API key not found. Set POE_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
420
|
+
)
|
|
361
421
|
elif base_url and "aliyuncs.com" in base_url:
|
|
362
422
|
api_key = os.getenv("QWEN_API_KEY")
|
|
363
423
|
if not api_key:
|
|
364
|
-
raise ConfigurationError(
|
|
424
|
+
raise ConfigurationError(
|
|
425
|
+
"Qwen API key not found. Set QWEN_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
426
|
+
)
|
|
365
427
|
|
|
366
428
|
return ChatCompletionsBackend(api_key=api_key, **kwargs)
|
|
367
429
|
|
|
@@ -370,7 +432,9 @@ def create_backend(backend_type: str, **kwargs) -> Any:
|
|
|
370
432
|
# Supports both global (z.ai) and China (bigmodel.cn) endpoints
|
|
371
433
|
api_key = kwargs.get("api_key") or os.getenv("ZAI_API_KEY")
|
|
372
434
|
if not api_key:
|
|
373
|
-
raise ConfigurationError(
|
|
435
|
+
raise ConfigurationError(
|
|
436
|
+
"ZAI API key not found. Set ZAI_API_KEY environment variable.\n" "You can add it to a .env file in:\n" " - Current directory: .env\n" " - Global config: ~/.massgen/.env",
|
|
437
|
+
)
|
|
374
438
|
return ChatCompletionsBackend(api_key=api_key, **kwargs)
|
|
375
439
|
|
|
376
440
|
elif backend_type == "lmstudio":
|
|
@@ -758,105 +822,94 @@ async def run_question_with_history(
|
|
|
758
822
|
messages = history.copy()
|
|
759
823
|
messages.append({"role": "user", "content": question})
|
|
760
824
|
|
|
761
|
-
#
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
+
)
|
|
791
875
|
|
|
876
|
+
# Determine display mode text
|
|
877
|
+
if len(agents) == 1:
|
|
878
|
+
mode_text = "Single Agent (Orchestrator)"
|
|
792
879
|
else:
|
|
793
|
-
|
|
794
|
-
# Create orchestrator config with timeout settings
|
|
795
|
-
timeout_config = kwargs.get("timeout_config")
|
|
796
|
-
orchestrator_config = AgentConfig()
|
|
797
|
-
if timeout_config:
|
|
798
|
-
orchestrator_config.timeout_config = timeout_config
|
|
880
|
+
mode_text = "Multi-Agent"
|
|
799
881
|
|
|
800
|
-
|
|
801
|
-
|
|
882
|
+
print(f"\nš¤ {BRIGHT_CYAN}{mode_text}{RESET}", flush=True)
|
|
883
|
+
print(f"Agents: {', '.join(agents.keys())}", flush=True)
|
|
884
|
+
if history:
|
|
885
|
+
print(f"History: {len(history)//2} previous exchanges", flush=True)
|
|
886
|
+
print(f"Question: {question}", flush=True)
|
|
887
|
+
print("\n" + "=" * 60, flush=True)
|
|
802
888
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
agent_temporary_workspace = orchestrator_cfg.get("agent_temporary_workspace")
|
|
806
|
-
session_storage = orchestrator_cfg.get("session_storage", "sessions") # Default to "sessions"
|
|
889
|
+
# For multi-agent with history, we need to use a different approach
|
|
890
|
+
# that maintains coordination UI display while supporting conversation context
|
|
807
891
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
892
|
+
if history and len(history) > 0:
|
|
893
|
+
# Use coordination UI with conversation context
|
|
894
|
+
# Extract current question from messages
|
|
895
|
+
current_question = messages[-1].get("content", question) if messages else question
|
|
811
896
|
|
|
812
|
-
#
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
logging_enabled=ui_config.get("logging_enabled", True),
|
|
826
|
-
enable_final_presentation=True, # Required for multi-turn: ensures final answer is saved
|
|
827
|
-
)
|
|
828
|
-
|
|
829
|
-
print(f"\nš¤ {BRIGHT_CYAN}Multi-Agent Mode{RESET}", flush=True)
|
|
830
|
-
print(f"Agents: {', '.join(agents.keys())}", flush=True)
|
|
831
|
-
if history:
|
|
832
|
-
print(f"History: {len(history)//2} previous exchanges", flush=True)
|
|
833
|
-
print(f"Question: {question}", flush=True)
|
|
834
|
-
print("\n" + "=" * 60, flush=True)
|
|
835
|
-
|
|
836
|
-
# For multi-agent with history, we need to use a different approach
|
|
837
|
-
# that maintains coordination UI display while supporting conversation context
|
|
838
|
-
|
|
839
|
-
if history and len(history) > 0:
|
|
840
|
-
# Use coordination UI with conversation context
|
|
841
|
-
# Extract current question from messages
|
|
842
|
-
current_question = messages[-1].get("content", question) if messages else question
|
|
843
|
-
|
|
844
|
-
# Pass the full message context to the UI coordination
|
|
845
|
-
response_content = await ui.coordinate_with_context(orchestrator, current_question, messages)
|
|
846
|
-
else:
|
|
847
|
-
# Standard coordination for new conversations
|
|
848
|
-
response_content = await ui.coordinate(orchestrator, question)
|
|
849
|
-
|
|
850
|
-
# Handle session persistence if applicable
|
|
851
|
-
session_id_to_use, updated_turn, normalized_response = await handle_session_persistence(
|
|
852
|
-
orchestrator,
|
|
853
|
-
question,
|
|
854
|
-
session_info,
|
|
855
|
-
session_storage,
|
|
856
|
-
)
|
|
897
|
+
# Pass the full message context to the UI coordination
|
|
898
|
+
response_content = await ui.coordinate_with_context(orchestrator, current_question, messages)
|
|
899
|
+
else:
|
|
900
|
+
# Standard coordination for new conversations
|
|
901
|
+
response_content = await ui.coordinate(orchestrator, question)
|
|
902
|
+
|
|
903
|
+
# Handle session persistence if applicable
|
|
904
|
+
session_id_to_use, updated_turn, normalized_response = await handle_session_persistence(
|
|
905
|
+
orchestrator,
|
|
906
|
+
question,
|
|
907
|
+
session_info,
|
|
908
|
+
session_storage,
|
|
909
|
+
)
|
|
857
910
|
|
|
858
|
-
|
|
859
|
-
|
|
911
|
+
# Return normalized response so conversation history has correct paths
|
|
912
|
+
return (normalized_response or response_content, session_id_to_use, updated_turn)
|
|
860
913
|
|
|
861
914
|
|
|
862
915
|
async def run_single_question(question: str, agents: Dict[str, SingleAgent], ui_config: Dict[str, Any], **kwargs) -> str:
|
|
@@ -901,6 +954,18 @@ async def run_single_question(question: str, agents: Dict[str, SingleAgent], ui_
|
|
|
901
954
|
# Get orchestrator parameters from config
|
|
902
955
|
orchestrator_cfg = kwargs.get("orchestrator", {})
|
|
903
956
|
|
|
957
|
+
# Apply voting sensitivity if specified
|
|
958
|
+
if "voting_sensitivity" in orchestrator_cfg:
|
|
959
|
+
orchestrator_config.voting_sensitivity = orchestrator_cfg["voting_sensitivity"]
|
|
960
|
+
|
|
961
|
+
# Apply answer count limit if specified
|
|
962
|
+
if "max_new_answers_per_agent" in orchestrator_cfg:
|
|
963
|
+
orchestrator_config.max_new_answers_per_agent = orchestrator_cfg["max_new_answers_per_agent"]
|
|
964
|
+
|
|
965
|
+
# Apply answer novelty requirement if specified
|
|
966
|
+
if "answer_novelty_requirement" in orchestrator_cfg:
|
|
967
|
+
orchestrator_config.answer_novelty_requirement = orchestrator_cfg["answer_novelty_requirement"]
|
|
968
|
+
|
|
904
969
|
# Get context sharing parameters
|
|
905
970
|
snapshot_storage = orchestrator_cfg.get("snapshot_storage")
|
|
906
971
|
agent_temporary_workspace = orchestrator_cfg.get("agent_temporary_workspace")
|
|
@@ -1175,6 +1240,411 @@ def print_example_config(name: str):
|
|
|
1175
1240
|
sys.exit(1)
|
|
1176
1241
|
|
|
1177
1242
|
|
|
1243
|
+
def discover_available_configs() -> Dict[str, List[Tuple[str, Path]]]:
|
|
1244
|
+
"""Discover all available configuration files.
|
|
1245
|
+
|
|
1246
|
+
Returns:
|
|
1247
|
+
Dict with categories as keys and list of (display_name, path) tuples as values
|
|
1248
|
+
"""
|
|
1249
|
+
configs = {
|
|
1250
|
+
"User Configs": [],
|
|
1251
|
+
"Project Configs": [],
|
|
1252
|
+
"Current Directory": [],
|
|
1253
|
+
"Package Examples": [],
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
# 1. User configs (~/.config/massgen/agents/)
|
|
1257
|
+
user_agents_dir = Path.home() / ".config/massgen/agents"
|
|
1258
|
+
if user_agents_dir.exists():
|
|
1259
|
+
for config_file in sorted(user_agents_dir.glob("*.yaml")):
|
|
1260
|
+
display_name = config_file.stem
|
|
1261
|
+
configs["User Configs"].append((display_name, config_file))
|
|
1262
|
+
|
|
1263
|
+
# 2. Project configs (.massgen/)
|
|
1264
|
+
project_config_dir = Path.cwd() / ".massgen"
|
|
1265
|
+
if project_config_dir.exists():
|
|
1266
|
+
for config_file in sorted(project_config_dir.glob("*.yaml")):
|
|
1267
|
+
display_name = f".massgen/{config_file.name}"
|
|
1268
|
+
configs["Project Configs"].append((display_name, config_file))
|
|
1269
|
+
|
|
1270
|
+
# 3. Current directory (*.yaml files, excluding .massgen/ and non-massgen configs)
|
|
1271
|
+
# Filter out common non-massgen YAML files
|
|
1272
|
+
exclude_patterns = {
|
|
1273
|
+
".pre-commit-config.yaml",
|
|
1274
|
+
".readthedocs.yaml",
|
|
1275
|
+
".github",
|
|
1276
|
+
"docker-compose",
|
|
1277
|
+
"ansible",
|
|
1278
|
+
"kubernetes",
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
for config_file in sorted(Path.cwd().glob("*.yaml")):
|
|
1282
|
+
# Skip if inside .massgen/ (already covered)
|
|
1283
|
+
if ".massgen" in str(config_file):
|
|
1284
|
+
continue
|
|
1285
|
+
|
|
1286
|
+
# Skip common non-massgen config files
|
|
1287
|
+
file_name = config_file.name.lower()
|
|
1288
|
+
if any(pattern in file_name for pattern in exclude_patterns):
|
|
1289
|
+
continue
|
|
1290
|
+
|
|
1291
|
+
display_name = config_file.name
|
|
1292
|
+
configs["Current Directory"].append((display_name, config_file))
|
|
1293
|
+
|
|
1294
|
+
# 4. Package examples (massgen/configs/)
|
|
1295
|
+
try:
|
|
1296
|
+
from importlib.resources import files
|
|
1297
|
+
|
|
1298
|
+
configs_root = files("massgen") / "configs"
|
|
1299
|
+
|
|
1300
|
+
# Organize by subdirectory
|
|
1301
|
+
for config_file in sorted(configs_root.rglob("*.yaml")):
|
|
1302
|
+
# Get relative path from configs root
|
|
1303
|
+
rel_path = str(config_file).replace(str(configs_root) + "/", "")
|
|
1304
|
+
# Skip README and docs
|
|
1305
|
+
if "README" in rel_path or "BACKEND_CONFIGURATION" in rel_path:
|
|
1306
|
+
continue
|
|
1307
|
+
# Use relative path as display name
|
|
1308
|
+
display_name = rel_path.replace(".yaml", "")
|
|
1309
|
+
configs["Package Examples"].append((display_name, Path(str(config_file))))
|
|
1310
|
+
|
|
1311
|
+
except Exception as e:
|
|
1312
|
+
logger.warning(f"Could not load package examples: {e}")
|
|
1313
|
+
|
|
1314
|
+
# Remove empty categories
|
|
1315
|
+
configs = {k: v for k, v in configs.items() if v}
|
|
1316
|
+
|
|
1317
|
+
return configs
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def interactive_config_selector() -> Optional[str]:
|
|
1321
|
+
"""Interactively select a configuration file.
|
|
1322
|
+
|
|
1323
|
+
Shows user/project/current directory configs directly in a flat list.
|
|
1324
|
+
Package examples are shown hierarchically (category ā config).
|
|
1325
|
+
|
|
1326
|
+
Returns:
|
|
1327
|
+
Path to selected config file, or None if cancelled
|
|
1328
|
+
"""
|
|
1329
|
+
# Create console instance for rich output
|
|
1330
|
+
selector_console = Console()
|
|
1331
|
+
|
|
1332
|
+
# Discover all available configs
|
|
1333
|
+
configs = discover_available_configs()
|
|
1334
|
+
|
|
1335
|
+
if not configs:
|
|
1336
|
+
selector_console.print(
|
|
1337
|
+
"\n[yellow]ā ļø No configurations found![/yellow]",
|
|
1338
|
+
)
|
|
1339
|
+
selector_console.print("[dim]Create one with: massgen --init[/dim]\n")
|
|
1340
|
+
return None
|
|
1341
|
+
|
|
1342
|
+
# Create a summary table showing what's available
|
|
1343
|
+
summary_table = Table(
|
|
1344
|
+
show_header=True,
|
|
1345
|
+
header_style="bold bright_white",
|
|
1346
|
+
border_style="bright_black",
|
|
1347
|
+
box=None,
|
|
1348
|
+
padding=(0, 1),
|
|
1349
|
+
width=88,
|
|
1350
|
+
)
|
|
1351
|
+
summary_table.add_column("Category", style="bright_cyan", no_wrap=True, width=25)
|
|
1352
|
+
summary_table.add_column("Count", justify="center", style="bright_yellow", width=10)
|
|
1353
|
+
summary_table.add_column("Location", style="dim")
|
|
1354
|
+
|
|
1355
|
+
# Build summary and choices
|
|
1356
|
+
choices = []
|
|
1357
|
+
|
|
1358
|
+
# Build summary table (overview only - no duplication)
|
|
1359
|
+
# User configs
|
|
1360
|
+
if "User Configs" in configs and configs["User Configs"]:
|
|
1361
|
+
summary_table.add_row(
|
|
1362
|
+
"š¤ Your Configs",
|
|
1363
|
+
str(len(configs["User Configs"])),
|
|
1364
|
+
"~/.config/massgen/agents/",
|
|
1365
|
+
)
|
|
1366
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1367
|
+
for display_name, path in configs["User Configs"]:
|
|
1368
|
+
choices.append(
|
|
1369
|
+
questionary.Choice(
|
|
1370
|
+
title=f" š¤ {display_name}",
|
|
1371
|
+
value=str(path),
|
|
1372
|
+
),
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
# Project configs
|
|
1376
|
+
if "Project Configs" in configs and configs["Project Configs"]:
|
|
1377
|
+
summary_table.add_row(
|
|
1378
|
+
"š Project Configs",
|
|
1379
|
+
str(len(configs["Project Configs"])),
|
|
1380
|
+
".massgen/",
|
|
1381
|
+
)
|
|
1382
|
+
if choices:
|
|
1383
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1384
|
+
else:
|
|
1385
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1386
|
+
for display_name, path in configs["Project Configs"]:
|
|
1387
|
+
choices.append(
|
|
1388
|
+
questionary.Choice(
|
|
1389
|
+
title=f" š {display_name}",
|
|
1390
|
+
value=str(path),
|
|
1391
|
+
),
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
# Current directory configs
|
|
1395
|
+
if "Current Directory" in configs and configs["Current Directory"]:
|
|
1396
|
+
summary_table.add_row(
|
|
1397
|
+
"š Current Directory",
|
|
1398
|
+
str(len(configs["Current Directory"])),
|
|
1399
|
+
f"*.yaml in {Path.cwd().name}/",
|
|
1400
|
+
)
|
|
1401
|
+
if choices:
|
|
1402
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1403
|
+
else:
|
|
1404
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1405
|
+
for display_name, path in configs["Current Directory"]:
|
|
1406
|
+
choices.append(
|
|
1407
|
+
questionary.Choice(
|
|
1408
|
+
title=f" š {display_name}",
|
|
1409
|
+
value=str(path),
|
|
1410
|
+
),
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
# Package examples
|
|
1414
|
+
if "Package Examples" in configs and configs["Package Examples"]:
|
|
1415
|
+
summary_table.add_row(
|
|
1416
|
+
"š¦ Package Examples",
|
|
1417
|
+
str(len(configs["Package Examples"])),
|
|
1418
|
+
"Built-in examples (hierarchical browser)",
|
|
1419
|
+
)
|
|
1420
|
+
if choices:
|
|
1421
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1422
|
+
choices.append(
|
|
1423
|
+
questionary.Choice(
|
|
1424
|
+
title=f" š¦ Browse {len(configs['Package Examples'])} example configs ā",
|
|
1425
|
+
value="__browse_examples__",
|
|
1426
|
+
),
|
|
1427
|
+
)
|
|
1428
|
+
|
|
1429
|
+
# Display summary table in a panel
|
|
1430
|
+
selector_console.print()
|
|
1431
|
+
selector_console.print(
|
|
1432
|
+
Panel(
|
|
1433
|
+
summary_table,
|
|
1434
|
+
title="[bold bright_cyan]š Select a Configuration[/bold bright_cyan]",
|
|
1435
|
+
border_style="bright_cyan",
|
|
1436
|
+
padding=(0, 1),
|
|
1437
|
+
width=90,
|
|
1438
|
+
),
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
# Add cancel option
|
|
1442
|
+
choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1443
|
+
choices.append(questionary.Choice(title=" ā Cancel", value="__cancel__"))
|
|
1444
|
+
|
|
1445
|
+
# Show the selector
|
|
1446
|
+
selector_console.print()
|
|
1447
|
+
selected = questionary.select(
|
|
1448
|
+
"Select a configuration:",
|
|
1449
|
+
choices=choices,
|
|
1450
|
+
use_shortcuts=True,
|
|
1451
|
+
use_arrow_keys=True,
|
|
1452
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1453
|
+
pointer="āø",
|
|
1454
|
+
).ask()
|
|
1455
|
+
|
|
1456
|
+
if selected is None or selected == "__cancel__":
|
|
1457
|
+
selector_console.print("\n[yellow]ā ļø Selection cancelled[/yellow]\n")
|
|
1458
|
+
return None
|
|
1459
|
+
|
|
1460
|
+
# If user wants to browse package examples, show hierarchical navigation
|
|
1461
|
+
if selected == "__browse_examples__":
|
|
1462
|
+
return _select_package_example(configs["Package Examples"], selector_console)
|
|
1463
|
+
|
|
1464
|
+
# Otherwise, return the selected config path
|
|
1465
|
+
selector_console.print(f"\n[bold green]ā Selected:[/bold green] [cyan]{selected}[/cyan]\n")
|
|
1466
|
+
return selected
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
def _select_package_example(examples: List[Tuple[str, Path]], console: Console) -> Optional[str]:
|
|
1470
|
+
"""Show hierarchical navigation for package examples.
|
|
1471
|
+
|
|
1472
|
+
Args:
|
|
1473
|
+
examples: List of (display_name, path) tuples
|
|
1474
|
+
console: Rich console for output
|
|
1475
|
+
|
|
1476
|
+
Returns:
|
|
1477
|
+
Path to selected config, or None if cancelled/back
|
|
1478
|
+
"""
|
|
1479
|
+
# Organize examples by category (first directory in path)
|
|
1480
|
+
categories = {}
|
|
1481
|
+
for display_name, path in examples:
|
|
1482
|
+
# Extract category from display name (e.g., "basic/multi/config" -> "basic")
|
|
1483
|
+
parts = display_name.split("/")
|
|
1484
|
+
category = parts[0] if len(parts) > 1 else "other"
|
|
1485
|
+
|
|
1486
|
+
if category not in categories:
|
|
1487
|
+
categories[category] = []
|
|
1488
|
+
categories[category].append((display_name, path))
|
|
1489
|
+
|
|
1490
|
+
# Emoji mapping for categories
|
|
1491
|
+
category_emojis = {
|
|
1492
|
+
"basic": "šÆ",
|
|
1493
|
+
"tools": "š ļø",
|
|
1494
|
+
"providers": "š",
|
|
1495
|
+
"configs": "āļø",
|
|
1496
|
+
"other": "š",
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
# Create category summary table
|
|
1500
|
+
category_table = Table(
|
|
1501
|
+
show_header=True,
|
|
1502
|
+
header_style="bold bright_white",
|
|
1503
|
+
border_style="bright_black",
|
|
1504
|
+
box=None,
|
|
1505
|
+
padding=(0, 1),
|
|
1506
|
+
width=88,
|
|
1507
|
+
)
|
|
1508
|
+
category_table.add_column("Category", style="bright_cyan", no_wrap=True, width=20)
|
|
1509
|
+
category_table.add_column("Count", justify="center", style="bright_yellow", width=10)
|
|
1510
|
+
category_table.add_column("Description", style="dim")
|
|
1511
|
+
|
|
1512
|
+
# Category descriptions
|
|
1513
|
+
category_descriptions = {
|
|
1514
|
+
"basic": "Simple configurations for getting started",
|
|
1515
|
+
"tools": "Configs demonstrating tool integrations",
|
|
1516
|
+
"providers": "Provider-specific example configs",
|
|
1517
|
+
"configs": "Advanced configuration examples",
|
|
1518
|
+
"other": "Miscellaneous configurations",
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
# Build category table and choices
|
|
1522
|
+
category_choices = []
|
|
1523
|
+
for category in sorted(categories.keys()):
|
|
1524
|
+
count = len(categories[category])
|
|
1525
|
+
emoji = category_emojis.get(category, "š")
|
|
1526
|
+
description = category_descriptions.get(category, "Example configurations")
|
|
1527
|
+
|
|
1528
|
+
category_table.add_row(
|
|
1529
|
+
f"{emoji} {category.title()}",
|
|
1530
|
+
str(count),
|
|
1531
|
+
description,
|
|
1532
|
+
)
|
|
1533
|
+
|
|
1534
|
+
category_choices.append(
|
|
1535
|
+
questionary.Choice(
|
|
1536
|
+
title=f" {emoji} {category.title()} ({count} config{'s' if count != 1 else ''})",
|
|
1537
|
+
value=category,
|
|
1538
|
+
),
|
|
1539
|
+
)
|
|
1540
|
+
|
|
1541
|
+
# Display category summary in a panel
|
|
1542
|
+
console.print()
|
|
1543
|
+
console.print(
|
|
1544
|
+
Panel(
|
|
1545
|
+
category_table,
|
|
1546
|
+
title="[bold bright_yellow]š¦ Package Examples - Select Category[/bold bright_yellow]",
|
|
1547
|
+
border_style="bright_yellow",
|
|
1548
|
+
padding=(0, 1),
|
|
1549
|
+
width=90,
|
|
1550
|
+
),
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
# Add back option
|
|
1554
|
+
category_choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1555
|
+
category_choices.append(questionary.Choice(title=" ā Back to main menu", value="__back__"))
|
|
1556
|
+
|
|
1557
|
+
# Step 1: Select category
|
|
1558
|
+
console.print()
|
|
1559
|
+
selected_category = questionary.select(
|
|
1560
|
+
"Select a category:",
|
|
1561
|
+
choices=category_choices,
|
|
1562
|
+
use_shortcuts=True,
|
|
1563
|
+
use_arrow_keys=True,
|
|
1564
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1565
|
+
pointer="āø",
|
|
1566
|
+
).ask()
|
|
1567
|
+
|
|
1568
|
+
if selected_category is None or selected_category == "__cancel__":
|
|
1569
|
+
console.print("\n[yellow]ā ļø Selection cancelled[/yellow]\n")
|
|
1570
|
+
return None
|
|
1571
|
+
|
|
1572
|
+
if selected_category == "__back__":
|
|
1573
|
+
# Go back to main selector
|
|
1574
|
+
return interactive_config_selector()
|
|
1575
|
+
|
|
1576
|
+
# Create configs table
|
|
1577
|
+
emoji = category_emojis.get(selected_category, "š")
|
|
1578
|
+
configs_table = Table(
|
|
1579
|
+
show_header=True,
|
|
1580
|
+
header_style="bold bright_white",
|
|
1581
|
+
border_style="bright_black",
|
|
1582
|
+
box=None,
|
|
1583
|
+
padding=(0, 1),
|
|
1584
|
+
width=88,
|
|
1585
|
+
)
|
|
1586
|
+
configs_table.add_column("#", style="dim", width=5, justify="right")
|
|
1587
|
+
configs_table.add_column("Configuration", style="bright_cyan")
|
|
1588
|
+
|
|
1589
|
+
# Build config choices and table
|
|
1590
|
+
config_choices = []
|
|
1591
|
+
for idx, (display_name, path) in enumerate(sorted(categories[selected_category]), 1):
|
|
1592
|
+
# Show relative path within category
|
|
1593
|
+
short_name = display_name.replace(f"{selected_category}/", "")
|
|
1594
|
+
configs_table.add_row(str(idx), short_name)
|
|
1595
|
+
config_choices.append(
|
|
1596
|
+
questionary.Choice(
|
|
1597
|
+
title=f" {idx:2d}. {short_name}",
|
|
1598
|
+
value=str(path),
|
|
1599
|
+
),
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
# Display configs in a panel
|
|
1603
|
+
console.print()
|
|
1604
|
+
console.print(
|
|
1605
|
+
Panel(
|
|
1606
|
+
configs_table,
|
|
1607
|
+
title=f"[bold bright_green]{emoji} {selected_category.title()} Configurations[/bold bright_green]",
|
|
1608
|
+
border_style="bright_green",
|
|
1609
|
+
padding=(0, 1),
|
|
1610
|
+
width=90,
|
|
1611
|
+
),
|
|
1612
|
+
)
|
|
1613
|
+
|
|
1614
|
+
# Add back option
|
|
1615
|
+
config_choices.append(questionary.Separator("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"))
|
|
1616
|
+
config_choices.append(questionary.Choice(title=" ā Back to categories", value="__back__"))
|
|
1617
|
+
|
|
1618
|
+
# Step 2: Select config
|
|
1619
|
+
# For large lists: disable shortcuts (max 36) and enable search filter for better UX
|
|
1620
|
+
# Note: When search filter is enabled, j/k keys must be disabled (they conflict with search)
|
|
1621
|
+
use_shortcuts = len(config_choices) <= 36
|
|
1622
|
+
use_search_filter = len(config_choices) > 36
|
|
1623
|
+
console.print()
|
|
1624
|
+
selected_config = questionary.select(
|
|
1625
|
+
"Select a configuration:",
|
|
1626
|
+
choices=config_choices,
|
|
1627
|
+
use_shortcuts=use_shortcuts,
|
|
1628
|
+
use_arrow_keys=True,
|
|
1629
|
+
use_search_filter=use_search_filter,
|
|
1630
|
+
use_jk_keys=not use_search_filter,
|
|
1631
|
+
style=MASSGEN_QUESTIONARY_STYLE,
|
|
1632
|
+
pointer="āø",
|
|
1633
|
+
).ask()
|
|
1634
|
+
|
|
1635
|
+
if selected_config is None or selected_config == "__cancel__":
|
|
1636
|
+
console.print("\n[yellow]ā ļø Selection cancelled[/yellow]\n")
|
|
1637
|
+
return None
|
|
1638
|
+
|
|
1639
|
+
if selected_config == "__back__":
|
|
1640
|
+
# Recursively call to go back to category selection
|
|
1641
|
+
return _select_package_example(examples, console)
|
|
1642
|
+
|
|
1643
|
+
# Return the selected config path
|
|
1644
|
+
console.print(f"\n[bold green]ā Selected:[/bold green] [cyan]{selected_config}[/cyan]\n")
|
|
1645
|
+
return selected_config
|
|
1646
|
+
|
|
1647
|
+
|
|
1178
1648
|
def should_run_builder() -> bool:
|
|
1179
1649
|
"""Check if config builder should run automatically.
|
|
1180
1650
|
|
|
@@ -1219,13 +1689,13 @@ async def run_interactive_mode(
|
|
|
1219
1689
|
rich_console.clear()
|
|
1220
1690
|
|
|
1221
1691
|
# ASCII art for interactive multi-agent mode
|
|
1222
|
-
ascii_art = """[bold
|
|
1692
|
+
ascii_art = """[bold #4A90E2]
|
|
1223
1693
|
āāāā āāāā āāāāāā āāāāāāāāāāāāāāāā āāāāāāā āāāāāāāāāāāā āāā
|
|
1224
1694
|
āāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāā āāā
|
|
1225
1695
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāā āāāāāā āāā
|
|
1226
1696
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāā āāāāāāāāāā
|
|
1227
1697
|
āāā āāā āāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāā
|
|
1228
|
-
āāā āāāāāā āāāāāāāāāāāāāāāāāāā āāāāāāā āāāāāāāāāāā āāāāā[/bold
|
|
1698
|
+
āāā āāāāāā āāāāāāāāāāāāāāāāāāā āāāāāāā āāāāāāāāāāā āāāāā[/bold #4A90E2]
|
|
1229
1699
|
|
|
1230
1700
|
[dim] š¤ š¤ š¤ ā š¬ collaborate ā šÆ winner ā š¢ final[/dim]
|
|
1231
1701
|
"""
|
|
@@ -1233,7 +1703,7 @@ async def run_interactive_mode(
|
|
|
1233
1703
|
# Wrap ASCII art in a panel
|
|
1234
1704
|
ascii_panel = Panel(
|
|
1235
1705
|
ascii_art,
|
|
1236
|
-
border_style="bold
|
|
1706
|
+
border_style="bold #4A90E2",
|
|
1237
1707
|
padding=(0, 2),
|
|
1238
1708
|
width=80,
|
|
1239
1709
|
)
|
|
@@ -1467,6 +1937,14 @@ async def run_interactive_mode(
|
|
|
1467
1937
|
setup_logging(debug=_DEBUG_MODE, turn=next_turn)
|
|
1468
1938
|
logger.info(f"Starting turn {next_turn}")
|
|
1469
1939
|
|
|
1940
|
+
# Save execution metadata for this turn (original_config already has pre-relocation paths)
|
|
1941
|
+
save_execution_metadata(
|
|
1942
|
+
query=question,
|
|
1943
|
+
config_path=config_path,
|
|
1944
|
+
config_content=original_config, # This is the pre-relocation config passed from main()
|
|
1945
|
+
cli_args={"mode": "interactive", "turn": next_turn, "session_id": session_id},
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1470
1948
|
# Pass session state for multi-turn filesystem support
|
|
1471
1949
|
session_info = {
|
|
1472
1950
|
"session_id": session_id,
|
|
@@ -1568,6 +2046,9 @@ async def main(args):
|
|
|
1568
2046
|
logger.debug(f"Created simple config with backend: {backend}, model: {model}")
|
|
1569
2047
|
logger.debug(f"Config content: {json.dumps(config, indent=2)}")
|
|
1570
2048
|
|
|
2049
|
+
# Save original config before relocation (for execution_metadata.yaml)
|
|
2050
|
+
original_config_for_metadata = copy.deepcopy(config)
|
|
2051
|
+
|
|
1571
2052
|
# Validate that all context paths exist before proceeding
|
|
1572
2053
|
validate_context_paths(config)
|
|
1573
2054
|
|
|
@@ -1651,6 +2132,16 @@ async def main(args):
|
|
|
1651
2132
|
if "orchestrator" in config:
|
|
1652
2133
|
kwargs["orchestrator"] = config["orchestrator"]
|
|
1653
2134
|
|
|
2135
|
+
# Save execution metadata for debugging and reconstruction
|
|
2136
|
+
if args.question:
|
|
2137
|
+
# For single question mode, save metadata now (use original config before .massgen/ relocation)
|
|
2138
|
+
save_execution_metadata(
|
|
2139
|
+
query=args.question,
|
|
2140
|
+
config_path=str(resolved_path) if args.config and "resolved_path" in locals() else None,
|
|
2141
|
+
config_content=original_config_for_metadata,
|
|
2142
|
+
cli_args=vars(args),
|
|
2143
|
+
)
|
|
2144
|
+
|
|
1654
2145
|
# Run mode based on whether question was provided
|
|
1655
2146
|
try:
|
|
1656
2147
|
if args.question:
|
|
@@ -1690,23 +2181,27 @@ def cli_main():
|
|
|
1690
2181
|
epilog="""
|
|
1691
2182
|
Examples:
|
|
1692
2183
|
# Use configuration file
|
|
1693
|
-
|
|
2184
|
+
massgen --config config.yaml "What is machine learning?"
|
|
1694
2185
|
|
|
1695
2186
|
# Quick single agent setup
|
|
1696
|
-
|
|
1697
|
-
|
|
2187
|
+
massgen --backend openai --model gpt-4o-mini "Explain quantum computing"
|
|
2188
|
+
massgen --backend claude --model claude-sonnet-4-20250514 "Analyze this data"
|
|
1698
2189
|
|
|
1699
2190
|
# Use ChatCompletion backend with custom base URL
|
|
1700
|
-
|
|
2191
|
+
massgen --backend chatcompletion --model gpt-oss-120b --base-url https://api.cerebras.ai/v1/chat/completions "What is 2+2?"
|
|
1701
2192
|
|
|
1702
2193
|
# Interactive mode
|
|
1703
|
-
|
|
2194
|
+
massgen --config config.yaml
|
|
2195
|
+
massgen # Uses default config if available
|
|
1704
2196
|
|
|
1705
2197
|
# Timeout control examples
|
|
1706
|
-
|
|
2198
|
+
massgen --config config.yaml --orchestrator-timeout 600 "Complex task"
|
|
1707
2199
|
|
|
1708
|
-
#
|
|
1709
|
-
|
|
2200
|
+
# Configuration management
|
|
2201
|
+
massgen --init # Create new configuration interactively
|
|
2202
|
+
massgen --select # Choose from available configurations
|
|
2203
|
+
massgen --setup # Set up API keys
|
|
2204
|
+
massgen --list-examples # View example configurations
|
|
1710
2205
|
|
|
1711
2206
|
Environment Variables:
|
|
1712
2207
|
OPENAI_API_KEY - Required for OpenAI backend
|
|
@@ -1738,6 +2233,11 @@ Environment Variables:
|
|
|
1738
2233
|
# Configuration options
|
|
1739
2234
|
config_group = parser.add_mutually_exclusive_group()
|
|
1740
2235
|
config_group.add_argument("--config", type=str, help="Path to YAML/JSON configuration file or @examples/NAME")
|
|
2236
|
+
config_group.add_argument(
|
|
2237
|
+
"--select",
|
|
2238
|
+
action="store_true",
|
|
2239
|
+
help="Interactively select from available configurations",
|
|
2240
|
+
)
|
|
1741
2241
|
config_group.add_argument(
|
|
1742
2242
|
"--backend",
|
|
1743
2243
|
type=str,
|
|
@@ -1781,7 +2281,7 @@ Environment Variables:
|
|
|
1781
2281
|
help="Launch interactive configuration builder to create config file",
|
|
1782
2282
|
)
|
|
1783
2283
|
parser.add_argument(
|
|
1784
|
-
"--setup
|
|
2284
|
+
"--setup",
|
|
1785
2285
|
action="store_true",
|
|
1786
2286
|
help="Launch interactive API key setup wizard to configure credentials",
|
|
1787
2287
|
)
|
|
@@ -1844,7 +2344,7 @@ Environment Variables:
|
|
|
1844
2344
|
return
|
|
1845
2345
|
|
|
1846
2346
|
# Launch interactive API key setup if requested
|
|
1847
|
-
if args.
|
|
2347
|
+
if args.setup:
|
|
1848
2348
|
from .config_builder import ConfigBuilder
|
|
1849
2349
|
|
|
1850
2350
|
builder = ConfigBuilder()
|
|
@@ -1855,9 +2355,20 @@ Environment Variables:
|
|
|
1855
2355
|
print(f"{BRIGHT_CYAN}š” You can now use MassGen with these providers{RESET}\n")
|
|
1856
2356
|
else:
|
|
1857
2357
|
print(f"\n{BRIGHT_YELLOW}ā ļø No API keys configured{RESET}")
|
|
1858
|
-
print(f"{BRIGHT_CYAN}š” You can run 'massgen --setup
|
|
2358
|
+
print(f"{BRIGHT_CYAN}š” You can run 'massgen --setup' anytime to set them up{RESET}\n")
|
|
1859
2359
|
return
|
|
1860
2360
|
|
|
2361
|
+
# Launch interactive config selector if requested
|
|
2362
|
+
if args.select:
|
|
2363
|
+
selected_config = interactive_config_selector()
|
|
2364
|
+
if selected_config:
|
|
2365
|
+
# Update args to use the selected config
|
|
2366
|
+
args.config = selected_config
|
|
2367
|
+
# Continue to main() with the selected config
|
|
2368
|
+
else:
|
|
2369
|
+
# User cancelled selection
|
|
2370
|
+
return
|
|
2371
|
+
|
|
1861
2372
|
# Launch interactive config builder if requested
|
|
1862
2373
|
if args.init:
|
|
1863
2374
|
from .config_builder import ConfigBuilder
|
|
@@ -1874,7 +2385,7 @@ Environment Variables:
|
|
|
1874
2385
|
elif filepath:
|
|
1875
2386
|
# Config created but user chose not to run
|
|
1876
2387
|
print(f"\nā
Configuration saved to: {filepath}")
|
|
1877
|
-
print(f'Run with:
|
|
2388
|
+
print(f'Run with: massgen --config {filepath} "Your question"')
|
|
1878
2389
|
return
|
|
1879
2390
|
else:
|
|
1880
2391
|
# User cancelled
|