massgen 0.0.3__py3-none-any.whl → 0.1.0__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 +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
massgen/logger_config.py
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Centralized logging configuration for MassGen using loguru.
|
|
4
|
+
|
|
5
|
+
This module provides a unified logging system for all MassGen components,
|
|
6
|
+
with special focus on debugging orchestrator and agent backend activities.
|
|
7
|
+
|
|
8
|
+
Color Scheme for Debug Logging:
|
|
9
|
+
- Magenta: Orchestrator activities (🎯)
|
|
10
|
+
- Blue: Messages sent from orchestrator to agents (📤)
|
|
11
|
+
- Green: Messages received from agents (📥)
|
|
12
|
+
- Yellow: Backend activities (⚙️)
|
|
13
|
+
- Cyan: General agent activities (📨)
|
|
14
|
+
- Light-black: Tool calls (🔧)
|
|
15
|
+
- Red: Coordination steps (🔄)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import inspect
|
|
19
|
+
import sys
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Optional
|
|
23
|
+
|
|
24
|
+
from loguru import logger
|
|
25
|
+
|
|
26
|
+
# Remove default logger to have full control
|
|
27
|
+
logger.remove()
|
|
28
|
+
|
|
29
|
+
# Global debug flag
|
|
30
|
+
_DEBUG_MODE = False
|
|
31
|
+
|
|
32
|
+
# Global log session directory and turn tracking
|
|
33
|
+
_LOG_SESSION_DIR = None
|
|
34
|
+
_LOG_BASE_SESSION_DIR = None # Base session dir (without turn subdirectory)
|
|
35
|
+
_CURRENT_TURN = None
|
|
36
|
+
|
|
37
|
+
# Console logging suppression (for Rich Live display compatibility)
|
|
38
|
+
_CONSOLE_HANDLER_ID = None
|
|
39
|
+
_CONSOLE_SUPPRESSED = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_log_session_dir(turn: Optional[int] = None) -> Path:
|
|
43
|
+
"""Get the current log session directory.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
turn: Optional turn number for multi-turn conversations
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Path to the log directory
|
|
50
|
+
"""
|
|
51
|
+
global _LOG_SESSION_DIR, _LOG_BASE_SESSION_DIR, _CURRENT_TURN
|
|
52
|
+
|
|
53
|
+
# Initialize base session dir once per session
|
|
54
|
+
if _LOG_BASE_SESSION_DIR is None:
|
|
55
|
+
# Check if we're running from within the MassGen development directory
|
|
56
|
+
# by looking for pyproject.toml with massgen package
|
|
57
|
+
cwd = Path.cwd()
|
|
58
|
+
|
|
59
|
+
# Check if pyproject.toml exists and contains massgen package definition
|
|
60
|
+
pyproject_file = cwd / "pyproject.toml"
|
|
61
|
+
if pyproject_file.exists():
|
|
62
|
+
try:
|
|
63
|
+
content = pyproject_file.read_text()
|
|
64
|
+
if 'name = "massgen"' in content:
|
|
65
|
+
pass
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
log_base_dir = Path(".massgen") / "massgen_logs"
|
|
70
|
+
log_base_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
# Create timestamped session directory
|
|
73
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
74
|
+
_LOG_BASE_SESSION_DIR = log_base_dir / f"log_{timestamp}"
|
|
75
|
+
_LOG_BASE_SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
|
|
77
|
+
# If turn changed, update the directory
|
|
78
|
+
if turn is not None and turn != _CURRENT_TURN:
|
|
79
|
+
_CURRENT_TURN = turn
|
|
80
|
+
_LOG_SESSION_DIR = None # Force recreation
|
|
81
|
+
|
|
82
|
+
if _LOG_SESSION_DIR is None:
|
|
83
|
+
# Create directory structure based on turn
|
|
84
|
+
if _CURRENT_TURN and _CURRENT_TURN > 0:
|
|
85
|
+
# Multi-turn conversation: organize by turn within session
|
|
86
|
+
_LOG_SESSION_DIR = _LOG_BASE_SESSION_DIR / f"turn_{_CURRENT_TURN}"
|
|
87
|
+
else:
|
|
88
|
+
# First execution or single execution: use base session dir
|
|
89
|
+
_LOG_SESSION_DIR = _LOG_BASE_SESSION_DIR
|
|
90
|
+
|
|
91
|
+
_LOG_SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
|
|
93
|
+
return _LOG_SESSION_DIR
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def save_execution_metadata(query: str, config_path: Optional[str] = None, config_content: Optional[dict] = None):
|
|
97
|
+
"""Save the query and config metadata to the log directory.
|
|
98
|
+
|
|
99
|
+
This allows reconstructing what was executed in this session.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
query: The user's query/prompt
|
|
103
|
+
config_path: Path to the config file that was used (optional)
|
|
104
|
+
config_content: The actual config dictionary (optional)
|
|
105
|
+
"""
|
|
106
|
+
import yaml
|
|
107
|
+
|
|
108
|
+
log_dir = get_log_session_dir()
|
|
109
|
+
|
|
110
|
+
# Create a single metadata file with all execution info
|
|
111
|
+
metadata = {
|
|
112
|
+
"query": query,
|
|
113
|
+
"timestamp": datetime.now().isoformat(),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if config_path:
|
|
117
|
+
metadata["config_path"] = str(config_path)
|
|
118
|
+
|
|
119
|
+
if config_content:
|
|
120
|
+
metadata["config"] = config_content
|
|
121
|
+
|
|
122
|
+
metadata_file = log_dir / "execution_metadata.yaml"
|
|
123
|
+
try:
|
|
124
|
+
with open(metadata_file, "w", encoding="utf-8") as f:
|
|
125
|
+
yaml.dump(metadata, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
126
|
+
logger.info(f"Saved execution metadata to: {metadata_file}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning(f"Failed to save execution metadata: {e}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def setup_logging(debug: bool = False, log_file: Optional[str] = None, turn: Optional[int] = None):
|
|
132
|
+
"""
|
|
133
|
+
Configure MassGen logging system using loguru.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
debug: Enable debug mode with verbose logging
|
|
137
|
+
log_file: Optional path to log file for persistent logging
|
|
138
|
+
turn: Optional turn number for multi-turn conversations
|
|
139
|
+
"""
|
|
140
|
+
global _DEBUG_MODE, _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED
|
|
141
|
+
_DEBUG_MODE = debug
|
|
142
|
+
_CONSOLE_SUPPRESSED = False
|
|
143
|
+
|
|
144
|
+
# Remove all existing handlers
|
|
145
|
+
logger.remove()
|
|
146
|
+
|
|
147
|
+
if debug:
|
|
148
|
+
# Debug mode: verbose console output with full details
|
|
149
|
+
def custom_format(record):
|
|
150
|
+
# Color code the module name based on category
|
|
151
|
+
name = record["extra"].get("name", "")
|
|
152
|
+
if "orchestrator" in name:
|
|
153
|
+
name_color = "magenta"
|
|
154
|
+
elif "backend" in name:
|
|
155
|
+
name_color = "yellow"
|
|
156
|
+
elif "agent" in name:
|
|
157
|
+
name_color = "cyan"
|
|
158
|
+
elif "coordination" in name:
|
|
159
|
+
name_color = "red"
|
|
160
|
+
else:
|
|
161
|
+
name_color = "white"
|
|
162
|
+
|
|
163
|
+
# Format the name to be more readable
|
|
164
|
+
formatted_name = name if name else "{name}"
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
f"<green>{{time:HH:mm:ss.SSS}}</green> | <level>{{level: <8}}</level> | "
|
|
168
|
+
f"<{name_color}>{formatted_name}</{name_color}>:<{name_color}>{{function}}</{name_color}>:"
|
|
169
|
+
f"<{name_color}>{{line}}</{name_color}> - {{message}}\n{{exception}}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
_CONSOLE_HANDLER_ID = logger.add(
|
|
173
|
+
sys.stderr,
|
|
174
|
+
format=custom_format,
|
|
175
|
+
level="DEBUG",
|
|
176
|
+
colorize=True,
|
|
177
|
+
backtrace=True,
|
|
178
|
+
diagnose=True,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Also log to file in debug mode
|
|
182
|
+
if not log_file:
|
|
183
|
+
log_session_dir = get_log_session_dir(turn=turn)
|
|
184
|
+
log_file = log_session_dir / "massgen_debug.log"
|
|
185
|
+
|
|
186
|
+
logger.add(
|
|
187
|
+
str(log_file),
|
|
188
|
+
format=custom_format,
|
|
189
|
+
level="DEBUG",
|
|
190
|
+
rotation="100 MB",
|
|
191
|
+
retention="1 week",
|
|
192
|
+
compression="zip",
|
|
193
|
+
backtrace=True,
|
|
194
|
+
diagnose=True,
|
|
195
|
+
enqueue=True, # Thread-safe logging
|
|
196
|
+
colorize=False, # Keep color codes in file
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
logger.info("Debug logging enabled - logging to console and file: {}", log_file)
|
|
200
|
+
else:
|
|
201
|
+
# Normal mode: only important messages to console, but all INFO+ to file
|
|
202
|
+
console_format = "<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>"
|
|
203
|
+
|
|
204
|
+
_CONSOLE_HANDLER_ID = logger.add(
|
|
205
|
+
sys.stderr,
|
|
206
|
+
format=console_format,
|
|
207
|
+
level="WARNING", # Only show WARNING and above on console in non-debug mode
|
|
208
|
+
colorize=True,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Always create log file in non-debug mode to capture INFO messages
|
|
212
|
+
if not log_file:
|
|
213
|
+
log_session_dir = get_log_session_dir(turn=turn)
|
|
214
|
+
log_file = log_session_dir / "massgen.log"
|
|
215
|
+
|
|
216
|
+
# Use the same format as console with color codes
|
|
217
|
+
logger.add(
|
|
218
|
+
str(log_file),
|
|
219
|
+
format=console_format,
|
|
220
|
+
level="INFO", # Capture INFO and above in file
|
|
221
|
+
rotation="10 MB",
|
|
222
|
+
retention="3 days",
|
|
223
|
+
compression="zip",
|
|
224
|
+
enqueue=True,
|
|
225
|
+
colorize=False, # Keep color codes in file
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
logger.info("Logging enabled - logging INFO+ to file: {}", log_file)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def suppress_console_logging():
|
|
232
|
+
"""
|
|
233
|
+
Temporarily suppress console logging to prevent interference with Rich Live display.
|
|
234
|
+
|
|
235
|
+
This removes the console handler while keeping file logging active.
|
|
236
|
+
Call restore_console_logging() to re-enable console output.
|
|
237
|
+
"""
|
|
238
|
+
global _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED
|
|
239
|
+
|
|
240
|
+
if _CONSOLE_HANDLER_ID is not None and not _CONSOLE_SUPPRESSED:
|
|
241
|
+
try:
|
|
242
|
+
logger.remove(_CONSOLE_HANDLER_ID)
|
|
243
|
+
_CONSOLE_SUPPRESSED = True
|
|
244
|
+
except ValueError:
|
|
245
|
+
# Handler already removed
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def restore_console_logging():
|
|
250
|
+
"""
|
|
251
|
+
Restore console logging after it was suppressed.
|
|
252
|
+
|
|
253
|
+
Re-adds the console handler with the same settings that were used during setup.
|
|
254
|
+
"""
|
|
255
|
+
global _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED, _DEBUG_MODE
|
|
256
|
+
|
|
257
|
+
if not _CONSOLE_SUPPRESSED:
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# Re-add console handler with same settings as setup_logging
|
|
261
|
+
if _DEBUG_MODE:
|
|
262
|
+
|
|
263
|
+
def custom_format(record):
|
|
264
|
+
name = record["extra"].get("name", "")
|
|
265
|
+
if "orchestrator" in name:
|
|
266
|
+
name_color = "magenta"
|
|
267
|
+
elif "backend" in name:
|
|
268
|
+
name_color = "yellow"
|
|
269
|
+
elif "agent" in name:
|
|
270
|
+
name_color = "cyan"
|
|
271
|
+
elif "coordination" in name:
|
|
272
|
+
name_color = "red"
|
|
273
|
+
else:
|
|
274
|
+
name_color = "white"
|
|
275
|
+
formatted_name = name if name else "{name}"
|
|
276
|
+
return (
|
|
277
|
+
f"<green>{{time:HH:mm:ss.SSS}}</green> | <level>{{level: <8}}</level> | "
|
|
278
|
+
f"<{name_color}>{formatted_name}</{name_color}>:<{name_color}>{{function}}</{name_color}>:"
|
|
279
|
+
f"<{name_color}>{{line}}</{name_color}> - {{message}}\n{{exception}}"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
_CONSOLE_HANDLER_ID = logger.add(
|
|
283
|
+
sys.stderr,
|
|
284
|
+
format=custom_format,
|
|
285
|
+
level="DEBUG",
|
|
286
|
+
colorize=True,
|
|
287
|
+
backtrace=True,
|
|
288
|
+
diagnose=True,
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
console_format = "<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>"
|
|
292
|
+
_CONSOLE_HANDLER_ID = logger.add(
|
|
293
|
+
sys.stderr,
|
|
294
|
+
format=console_format,
|
|
295
|
+
level="WARNING",
|
|
296
|
+
colorize=True,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
_CONSOLE_SUPPRESSED = False
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_logger(name: str):
|
|
303
|
+
"""
|
|
304
|
+
Get a logger instance with the given name.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
name: Logger name (typically __name__ of the module)
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Configured logger instance
|
|
311
|
+
"""
|
|
312
|
+
return logger.bind(name=name)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _get_caller_info():
|
|
316
|
+
"""
|
|
317
|
+
Get the caller's line number and function name from the stack frame.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Tuple of (function_name, line_number where the logging function was called)
|
|
321
|
+
"""
|
|
322
|
+
frame = inspect.currentframe()
|
|
323
|
+
# Stack frames:
|
|
324
|
+
# - frame: _get_caller_info (this function)
|
|
325
|
+
# - frame.f_back: log_orchestrator_agent_message or log_backend_agent_message
|
|
326
|
+
# - frame.f_back.f_back: the actual caller (e.g., _stream_agent_execution)
|
|
327
|
+
|
|
328
|
+
if frame and frame.f_back and frame.f_back.f_back:
|
|
329
|
+
caller_frame = frame.f_back.f_back
|
|
330
|
+
function_name = caller_frame.f_code.co_name
|
|
331
|
+
# Get the line number where the logging function was called from within the caller
|
|
332
|
+
line_number = caller_frame.f_lineno
|
|
333
|
+
return function_name, line_number
|
|
334
|
+
return "unknown", 0
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def log_orchestrator_activity(orchestrator_id: str, activity: str, details: dict = None):
|
|
338
|
+
"""
|
|
339
|
+
Log orchestrator activities for debugging.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
orchestrator_id: ID of the orchestrator
|
|
343
|
+
activity: Description of the activity
|
|
344
|
+
details: Additional details as dictionary
|
|
345
|
+
"""
|
|
346
|
+
# Get caller information
|
|
347
|
+
func_name, line_num = _get_caller_info()
|
|
348
|
+
log = logger.bind(name=f"orchestrator.{orchestrator_id}:{func_name}:{line_num}")
|
|
349
|
+
if _DEBUG_MODE:
|
|
350
|
+
# Use magenta color for orchestrator activities
|
|
351
|
+
log.opt(colors=True).debug("<magenta>🎯 {}: {}</magenta>", activity, details or {})
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def log_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
|
|
355
|
+
"""
|
|
356
|
+
Log agent messages (sent/received) for debugging.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
agent_id: ID of the agent
|
|
360
|
+
direction: "SEND" or "RECV"
|
|
361
|
+
message: Message content as dictionary
|
|
362
|
+
backend_name: Optional name of the backend provider
|
|
363
|
+
"""
|
|
364
|
+
# Build a descriptive name with both agent ID and backend
|
|
365
|
+
if backend_name:
|
|
366
|
+
log_name = f"{agent_id}.{backend_name}"
|
|
367
|
+
log = logger.bind(name=log_name)
|
|
368
|
+
else:
|
|
369
|
+
log_name = agent_id
|
|
370
|
+
log = logger.bind(name=log_name)
|
|
371
|
+
|
|
372
|
+
if _DEBUG_MODE:
|
|
373
|
+
if direction == "SEND":
|
|
374
|
+
# Use blue color for sent messages
|
|
375
|
+
log.opt(colors=True).debug(
|
|
376
|
+
"<blue>📤 [{}] Sending message: {}</blue>",
|
|
377
|
+
log_name,
|
|
378
|
+
_format_message(message),
|
|
379
|
+
)
|
|
380
|
+
elif direction == "RECV":
|
|
381
|
+
# Use green color for received messages
|
|
382
|
+
log.opt(colors=True).debug(
|
|
383
|
+
"<green>📥 [{}] Received message: {}</green>",
|
|
384
|
+
log_name,
|
|
385
|
+
_format_message(message),
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
log.opt(colors=True).debug(
|
|
389
|
+
"<cyan>📨 [{}] {}: {}</cyan>",
|
|
390
|
+
log_name,
|
|
391
|
+
direction,
|
|
392
|
+
_format_message(message),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def log_orchestrator_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
|
|
397
|
+
"""
|
|
398
|
+
Log orchestrator-to-agent messages for debugging.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
agent_id: ID of the agent
|
|
402
|
+
direction: "SEND" or "RECV"
|
|
403
|
+
message: Message content as dictionary
|
|
404
|
+
backend_name: Optional name of the backend provider
|
|
405
|
+
"""
|
|
406
|
+
# Get caller information
|
|
407
|
+
func_name, line_num = _get_caller_info()
|
|
408
|
+
|
|
409
|
+
# Build a descriptive name with orchestrator prefix
|
|
410
|
+
if backend_name:
|
|
411
|
+
log_name = f"orchestrator→{agent_id}.{backend_name}:{func_name}:{line_num}"
|
|
412
|
+
log = logger.bind(name=log_name)
|
|
413
|
+
else:
|
|
414
|
+
log_name = f"orchestrator→{agent_id}:{func_name}:{line_num}"
|
|
415
|
+
log = logger.bind(name=log_name)
|
|
416
|
+
|
|
417
|
+
if _DEBUG_MODE:
|
|
418
|
+
if direction == "SEND":
|
|
419
|
+
# Use magenta color for orchestrator sent messages
|
|
420
|
+
log.opt(colors=True).debug(
|
|
421
|
+
"<magenta>🎯📤 [{}] Orchestrator sending to agent: {}</magenta>",
|
|
422
|
+
log_name,
|
|
423
|
+
_format_message(message),
|
|
424
|
+
)
|
|
425
|
+
elif direction == "RECV":
|
|
426
|
+
# Use magenta color for orchestrator received messages
|
|
427
|
+
log.opt(colors=True).debug(
|
|
428
|
+
"<magenta>🎯📥 [{}] Orchestrator received from agent: {}</magenta>",
|
|
429
|
+
log_name,
|
|
430
|
+
_format_message(message),
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
log.opt(colors=True).debug(
|
|
434
|
+
"<magenta>🎯📨 [{}] {}: {}</magenta>",
|
|
435
|
+
log_name,
|
|
436
|
+
direction,
|
|
437
|
+
_format_message(message),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def log_backend_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
|
|
442
|
+
"""
|
|
443
|
+
Log backend-to-LLM messages for debugging.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
agent_id: ID of the agent
|
|
447
|
+
direction: "SEND" or "RECV"
|
|
448
|
+
message: Message content as dictionary
|
|
449
|
+
backend_name: Optional name of the backend provider
|
|
450
|
+
"""
|
|
451
|
+
# Get caller information
|
|
452
|
+
func_name, line_num = _get_caller_info()
|
|
453
|
+
|
|
454
|
+
# Build a descriptive name with backend prefix
|
|
455
|
+
if backend_name:
|
|
456
|
+
log_name = f"backend.{backend_name}→{agent_id}:{func_name}:{line_num}"
|
|
457
|
+
log = logger.bind(name=log_name)
|
|
458
|
+
else:
|
|
459
|
+
log_name = f"backend→{agent_id}:{func_name}:{line_num}"
|
|
460
|
+
log = logger.bind(name=log_name)
|
|
461
|
+
|
|
462
|
+
if _DEBUG_MODE:
|
|
463
|
+
if direction == "SEND":
|
|
464
|
+
# Use yellow color for backend sent messages
|
|
465
|
+
log.opt(colors=True).debug(
|
|
466
|
+
"<yellow>⚙️📤 [{}] Backend sending to LLM: {}</yellow>",
|
|
467
|
+
log_name,
|
|
468
|
+
_format_message(message),
|
|
469
|
+
)
|
|
470
|
+
elif direction == "RECV":
|
|
471
|
+
# Use yellow color for backend received messages
|
|
472
|
+
log.opt(colors=True).debug(
|
|
473
|
+
"<yellow>⚙️📥 [{}] Backend received from LLM: {}</yellow>",
|
|
474
|
+
log_name,
|
|
475
|
+
_format_message(message),
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
log.opt(colors=True).debug(
|
|
479
|
+
"<yellow>⚙️📨 [{}] {}: {}</yellow>",
|
|
480
|
+
log_name,
|
|
481
|
+
direction,
|
|
482
|
+
_format_message(message),
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def log_backend_activity(backend_name: str, activity: str, details: dict = None, agent_id: str = None):
|
|
487
|
+
"""
|
|
488
|
+
Log backend activities for debugging.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
backend_name: Name of the backend (e.g., "openai", "claude")
|
|
492
|
+
activity: Description of the activity
|
|
493
|
+
details: Additional details as dictionary
|
|
494
|
+
agent_id: Optional ID of the agent using this backend
|
|
495
|
+
"""
|
|
496
|
+
# Get caller information
|
|
497
|
+
func_name, line_num = _get_caller_info()
|
|
498
|
+
|
|
499
|
+
# Build a descriptive name with both agent ID and backend
|
|
500
|
+
if agent_id:
|
|
501
|
+
log_name = f"{agent_id}.{backend_name}"
|
|
502
|
+
log = logger.bind(name=f"{log_name}:{func_name}:{line_num}")
|
|
503
|
+
else:
|
|
504
|
+
log_name = backend_name
|
|
505
|
+
log = logger.bind(name=f"backend.{backend_name}:{func_name}:{line_num}")
|
|
506
|
+
|
|
507
|
+
if _DEBUG_MODE:
|
|
508
|
+
# Use yellow color for backend activities
|
|
509
|
+
log.opt(colors=True).debug("<yellow>⚙️ [{}] {}: {}</yellow>", log_name, activity, details or {})
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def log_mcp_activity(backend_name: str, message: str, details: dict = None, agent_id: str = None):
|
|
513
|
+
"""
|
|
514
|
+
Log MCP (Model Context Protocol) activities at INFO level.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
backend_name: Name of the backend (e.g., "claude", "openai")
|
|
518
|
+
message: Description of the MCP activity
|
|
519
|
+
details: Additional details as dictionary
|
|
520
|
+
agent_id: Optional ID of the agent using this backend
|
|
521
|
+
"""
|
|
522
|
+
func_name, line_num = _get_caller_info()
|
|
523
|
+
|
|
524
|
+
if agent_id:
|
|
525
|
+
log_name = f"{agent_id}.{backend_name}"
|
|
526
|
+
log = logger.bind(name=f"{log_name}:{func_name}:{line_num}")
|
|
527
|
+
else:
|
|
528
|
+
log_name = backend_name
|
|
529
|
+
log = logger.bind(name=f"backend.{backend_name}:{func_name}:{line_num}")
|
|
530
|
+
|
|
531
|
+
log.info("MCP: {} - {}", message, details or {})
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def log_tool_call(
|
|
535
|
+
agent_id: str,
|
|
536
|
+
tool_name: str,
|
|
537
|
+
arguments: dict,
|
|
538
|
+
result: Any = None,
|
|
539
|
+
backend_name: str = None,
|
|
540
|
+
):
|
|
541
|
+
"""
|
|
542
|
+
Log tool calls made by agents.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
agent_id: ID of the agent making the tool call
|
|
546
|
+
tool_name: Name of the tool being called
|
|
547
|
+
arguments: Arguments passed to the tool
|
|
548
|
+
result: Result returned by the tool (optional)
|
|
549
|
+
backend_name: Optional name of the backend provider
|
|
550
|
+
"""
|
|
551
|
+
# Build a descriptive name with both agent ID and backend
|
|
552
|
+
if backend_name:
|
|
553
|
+
log_name = f"{agent_id}.{backend_name}"
|
|
554
|
+
log = logger.bind(name=f"{log_name}.tools")
|
|
555
|
+
else:
|
|
556
|
+
log_name = agent_id
|
|
557
|
+
log = logger.bind(name=f"{agent_id}.tools")
|
|
558
|
+
|
|
559
|
+
if _DEBUG_MODE:
|
|
560
|
+
if result is not None:
|
|
561
|
+
# Use light gray color for tool calls
|
|
562
|
+
log.opt(colors=True).debug(
|
|
563
|
+
"<light-black>🔧 [{}] Tool '{}' called with args: {} -> Result: {}</light-black>",
|
|
564
|
+
log_name,
|
|
565
|
+
tool_name,
|
|
566
|
+
arguments,
|
|
567
|
+
result,
|
|
568
|
+
)
|
|
569
|
+
else:
|
|
570
|
+
log.opt(colors=True).debug(
|
|
571
|
+
"<light-black>🔧 [{}] Calling tool '{}' with args: {}</light-black>",
|
|
572
|
+
log_name,
|
|
573
|
+
tool_name,
|
|
574
|
+
arguments,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def log_coordination_step(step: str, details: dict = None):
|
|
579
|
+
"""
|
|
580
|
+
Log coordination workflow steps.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
step: Description of the coordination step
|
|
584
|
+
details: Additional details as dictionary
|
|
585
|
+
"""
|
|
586
|
+
log = logger.bind(name="coordination")
|
|
587
|
+
if _DEBUG_MODE:
|
|
588
|
+
# Use red color for coordination steps (distinctive from orchestrator)
|
|
589
|
+
log.opt(colors=True).debug("<red>🔄 {}: {}</red>", step, details or {})
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def log_stream_chunk(source: str, chunk_type: str, content: Any = None, agent_id: str = None):
|
|
593
|
+
"""
|
|
594
|
+
Log stream chunks at INFO level (always logged to file).
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
source: Source of the stream chunk (e.g., "orchestrator", "backend.claude_code")
|
|
598
|
+
chunk_type: Type of the chunk (e.g., "content", "tool_call", "error")
|
|
599
|
+
content: Content of the chunk
|
|
600
|
+
agent_id: Optional agent ID for context
|
|
601
|
+
"""
|
|
602
|
+
# Get caller information from the actual caller (not this function)
|
|
603
|
+
frame = inspect.currentframe()
|
|
604
|
+
# Stack frames:
|
|
605
|
+
# - frame: log_stream_chunk (this function)
|
|
606
|
+
# - frame.f_back: the actual caller (e.g., _present_final_answer)
|
|
607
|
+
|
|
608
|
+
if frame and frame.f_back:
|
|
609
|
+
caller_frame = frame.f_back
|
|
610
|
+
function_name = caller_frame.f_code.co_name
|
|
611
|
+
line_number = caller_frame.f_lineno
|
|
612
|
+
else:
|
|
613
|
+
function_name = "unknown"
|
|
614
|
+
line_number = 0
|
|
615
|
+
|
|
616
|
+
if agent_id:
|
|
617
|
+
log_name = f"{source}.{agent_id}"
|
|
618
|
+
else:
|
|
619
|
+
log_name = source
|
|
620
|
+
|
|
621
|
+
# Create a custom logger that will show the source name instead of module path
|
|
622
|
+
log = logger.bind(name=f"{log_name}:{function_name}:{line_number}")
|
|
623
|
+
|
|
624
|
+
# Always log stream chunks at INFO level (will go to file)
|
|
625
|
+
# Format content based on type
|
|
626
|
+
if content:
|
|
627
|
+
if isinstance(content, dict):
|
|
628
|
+
log.info("Stream chunk [{}]: {}", chunk_type, content)
|
|
629
|
+
else:
|
|
630
|
+
# No truncation - show full content
|
|
631
|
+
log.info("Stream chunk [{}]: {}", chunk_type, content)
|
|
632
|
+
else:
|
|
633
|
+
log.info("Stream chunk [{}]", chunk_type)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _format_message(message: dict) -> str:
|
|
637
|
+
"""
|
|
638
|
+
Format message for logging without truncation.
|
|
639
|
+
|
|
640
|
+
Args:
|
|
641
|
+
message: Message dictionary
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
Formatted message string
|
|
645
|
+
"""
|
|
646
|
+
if not message:
|
|
647
|
+
return "<empty>"
|
|
648
|
+
|
|
649
|
+
# Format based on message type
|
|
650
|
+
if "role" in message and "content" in message:
|
|
651
|
+
content = message.get("content", "")
|
|
652
|
+
if isinstance(content, str):
|
|
653
|
+
# No truncation - show full content
|
|
654
|
+
return f"[{message['role']}] {content}"
|
|
655
|
+
else:
|
|
656
|
+
return f"[{message['role']}] {str(content)}"
|
|
657
|
+
|
|
658
|
+
# For other message types, just stringify without truncation
|
|
659
|
+
msg_str = str(message)
|
|
660
|
+
return msg_str
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
# Export main components
|
|
664
|
+
__all__ = [
|
|
665
|
+
"logger",
|
|
666
|
+
"setup_logging",
|
|
667
|
+
"suppress_console_logging",
|
|
668
|
+
"restore_console_logging",
|
|
669
|
+
"get_logger",
|
|
670
|
+
"get_log_session_dir",
|
|
671
|
+
"save_execution_metadata",
|
|
672
|
+
"log_orchestrator_activity",
|
|
673
|
+
"log_agent_message",
|
|
674
|
+
"log_orchestrator_agent_message",
|
|
675
|
+
"log_backend_agent_message",
|
|
676
|
+
"log_backend_activity",
|
|
677
|
+
"log_mcp_activity",
|
|
678
|
+
"log_tool_call",
|
|
679
|
+
"log_coordination_step",
|
|
680
|
+
"log_stream_chunk",
|
|
681
|
+
]
|