|
3 | 3 | # Licensed under the MIT License. See License.txt in the project root for license information. |
4 | 4 | # -------------------------------------------------------------------------------------------- |
5 | 5 |
|
6 | | -import logging |
7 | 6 | import os |
8 | 7 | import select |
9 | 8 | import sys |
10 | 9 |
|
11 | | -from azext_aks_agent._consts import ( |
12 | | - CONST_AGENT_CONFIG_PATH_DIR_ENV_KEY, |
13 | | - CONST_AGENT_NAME, |
14 | | - CONST_AGENT_NAME_ENV_KEY, |
15 | | - CONST_DISABLE_PROMETHEUS_TOOLSET_ENV_KEY, |
16 | | - CONST_PRIVACY_NOTICE_BANNER, |
17 | | - CONST_PRIVACY_NOTICE_BANNER_ENV_KEY, |
18 | | -) |
| 10 | +from azext_aks_agent.agent.logging import init_log |
19 | 11 | from azure.cli.core.api import get_config_dir |
| 12 | +from azure.cli.core.azclierror import CLIInternalError |
20 | 13 | from azure.cli.core.commands.client_factory import get_subscription_id |
21 | 14 | from knack.util import CLIError |
22 | 15 |
|
|
25 | 18 | from .telemetry import CLITelemetryClient |
26 | 19 |
|
27 | 20 |
|
28 | | -# NOTE(mainred): environment variables to disable prometheus toolset loading should be set before importing holmes. |
29 | | -def customize_holmesgpt(): |
30 | | - os.environ[CONST_DISABLE_PROMETHEUS_TOOLSET_ENV_KEY] = "true" |
31 | | - os.environ[CONST_AGENT_CONFIG_PATH_DIR_ENV_KEY] = get_config_dir() |
32 | | - os.environ[CONST_AGENT_NAME_ENV_KEY] = CONST_AGENT_NAME |
33 | | - os.environ[CONST_PRIVACY_NOTICE_BANNER_ENV_KEY] = CONST_PRIVACY_NOTICE_BANNER |
34 | | - |
35 | | - |
36 | | -# NOTE(mainred): holmes leverage the log handler RichHandler to provide colorful, readable and well-formatted logs |
37 | | -# making the interactive mode more user-friendly. |
38 | | -# And we removed exising log handlers to avoid duplicate logs. |
39 | | -# Also make the console log consistent, we remove the telemetry and data logger to skip redundant logs. |
40 | | -def init_log(): |
41 | | - # NOTE(mainred): we need to disable INFO logs from LiteLLM before LiteLLM library is loaded, to avoid logging the |
42 | | - # debug logs from heading of LiteLLM. |
43 | | - logging.getLogger("LiteLLM").setLevel(logging.WARNING) |
44 | | - logging.getLogger("telemetry.main").setLevel(logging.WARNING) |
45 | | - logging.getLogger("telemetry.process").setLevel(logging.WARNING) |
46 | | - logging.getLogger("telemetry.save").setLevel(logging.WARNING) |
47 | | - logging.getLogger("telemetry.client").setLevel(logging.WARNING) |
48 | | - logging.getLogger("az_command_data_logger").setLevel(logging.WARNING) |
49 | | - |
50 | | - from holmes.utils.console.logging import init_logging |
51 | | - |
52 | | - # TODO: make log verbose configurable, currently disabled by []. |
53 | | - return init_logging([]) |
54 | | - |
55 | | - |
56 | 21 | def _get_mode_state_file() -> str: |
57 | 22 | """Get the path to the mode state file.""" |
58 | 23 | config_dir = get_config_dir() |
@@ -168,8 +133,6 @@ def aks_agent( |
168 | 133 | raise CLIError( |
169 | 134 | "Please upgrade the python version to 3.10 or above to use aks agent." |
170 | 135 | ) |
171 | | - # customizing holmesgpt should called before importing holmes |
172 | | - customize_holmesgpt() |
173 | 136 |
|
174 | 137 | # Initialize variables |
175 | 138 | interactive = not no_interactive |
@@ -213,85 +176,88 @@ def aks_agent( |
213 | 176 | # MCP Lifecycle Manager |
214 | 177 | mcp_lifecycle = MCPLifecycleManager() |
215 | 178 |
|
216 | | - try: |
217 | | - config = None |
| 179 | + config = None |
218 | 180 |
|
219 | | - if use_aks_mcp: |
220 | | - try: |
221 | | - config_params = { |
222 | | - 'config_file': config_file, |
223 | | - 'model': model, |
224 | | - 'api_key': api_key, |
225 | | - 'max_steps': max_steps, |
226 | | - 'verbose': show_tool_output |
227 | | - } |
228 | | - mcp_info = mcp_lifecycle.setup_mcp_sync(config_params) |
229 | | - config = mcp_info['config'] |
230 | | - |
231 | | - if show_tool_output: |
232 | | - from .user_feedback import ProgressReporter |
233 | | - ProgressReporter.show_status_message("MCP mode active - enhanced capabilities enabled", "info") |
234 | | - |
235 | | - except Exception as e: # pylint: disable=broad-exception-caught |
236 | | - # Fallback to traditional mode on any MCP setup failure |
237 | | - from .error_handler import AgentErrorHandler |
238 | | - mcp_error = AgentErrorHandler.handle_mcp_setup_error(e, "MCP initialization") |
239 | | - if show_tool_output: |
240 | | - console.print(f"[yellow]MCP setup failed, using traditional mode: {mcp_error.message}[/yellow]") |
241 | | - if mcp_error.suggestions: |
242 | | - console.print("[dim]Suggestions for next time:[/dim]") |
243 | | - for suggestion in mcp_error.suggestions[:3]: # Show only first 3 suggestions |
244 | | - console.print(f"[dim] • {suggestion}[/dim]") |
245 | | - use_aks_mcp = False |
246 | | - current_mode = "traditional" |
247 | | - |
248 | | - # Fallback to traditional mode if MCP setup failed or was disabled |
249 | | - if not config: |
250 | | - config = _setup_traditional_mode_sync(config_file, model, api_key, max_steps, show_tool_output) |
251 | | - if show_tool_output: |
252 | | - console.print("[yellow]Traditional mode active (MCP disabled)[/yellow]") |
| 181 | + if use_aks_mcp: |
| 182 | + try: |
| 183 | + config_params = { |
| 184 | + 'config_file': config_file, |
| 185 | + 'model': model, |
| 186 | + 'api_key': api_key, |
| 187 | + 'max_steps': max_steps, |
| 188 | + 'verbose': show_tool_output |
| 189 | + } |
| 190 | + mcp_info = mcp_lifecycle.setup_mcp_sync(config_params) |
| 191 | + config = mcp_info['config'] |
253 | 192 |
|
254 | | - # Save the current mode to state file for next run |
255 | | - _save_current_mode(current_mode) |
| 193 | + if show_tool_output: |
| 194 | + from .user_feedback import ProgressReporter |
| 195 | + ProgressReporter.show_status_message("MCP mode active - enhanced capabilities enabled", "info") |
256 | 196 |
|
257 | | - # Use smart refresh logic |
258 | | - effective_refresh_toolsets = smart_refresh |
| 197 | + except Exception as e: # pylint: disable=broad-exception-caught |
| 198 | + # Fallback to traditional mode on any MCP setup failure |
| 199 | + from .error_handler import AgentErrorHandler |
| 200 | + mcp_error = AgentErrorHandler.handle_mcp_setup_error(e, "MCP initialization") |
| 201 | + if show_tool_output: |
| 202 | + console.print(f"[yellow]MCP setup failed, using traditional mode: {mcp_error.message}[/yellow]") |
| 203 | + if mcp_error.suggestions: |
| 204 | + console.print("[dim]Suggestions for next time:[/dim]") |
| 205 | + for suggestion in mcp_error.suggestions[:3]: # Show only first 3 suggestions |
| 206 | + console.print(f"[dim] • {suggestion}[/dim]") |
| 207 | + use_aks_mcp = False |
| 208 | + current_mode = "traditional" |
| 209 | + |
| 210 | + # Fallback to traditional mode if MCP setup failed or was disabled |
| 211 | + if not config: |
| 212 | + config = _setup_traditional_mode_sync(config_file, model, api_key, max_steps, show_tool_output) |
259 | 213 | if show_tool_output: |
260 | | - from .user_feedback import ProgressReporter |
261 | | - ProgressReporter.show_status_message( |
262 | | - f"Toolset refresh: {effective_refresh_toolsets} (Mode: {current_mode})", "info" |
263 | | - ) |
| 214 | + console.print("[yellow]Traditional mode active (MCP disabled)[/yellow]") |
264 | 215 |
|
265 | | - # Create AI client once with proper refresh settings |
| 216 | + # Save the current mode to state file for next run |
| 217 | + _save_current_mode(current_mode) |
| 218 | + |
| 219 | + # Use smart refresh logic |
| 220 | + effective_refresh_toolsets = smart_refresh |
| 221 | + if show_tool_output: |
| 222 | + from .user_feedback import ProgressReporter |
| 223 | + ProgressReporter.show_status_message( |
| 224 | + f"Toolset refresh: {effective_refresh_toolsets} (Mode: {current_mode})", "info" |
| 225 | + ) |
| 226 | + |
| 227 | + # Validate inputs |
| 228 | + if not prompt and not interactive and not piped_data: |
| 229 | + raise CLIError( |
| 230 | + "Either the 'prompt' argument must be provided (unless using --interactive mode)." |
| 231 | + ) |
| 232 | + try: |
| 233 | + # prepare the toolsets |
266 | 234 | ai = config.create_console_toolcalling_llm( |
267 | 235 | dal=None, |
268 | 236 | refresh_toolsets=effective_refresh_toolsets, |
269 | 237 | ) |
| 238 | + except Exception as e: |
| 239 | + raise CLIError(f"Failed to create AI executor: {str(e)}") |
| 240 | + |
| 241 | + # Handle piped data |
| 242 | + if piped_data: |
| 243 | + if prompt: |
| 244 | + # User provided both piped data and a prompt |
| 245 | + prompt = f"Here's some piped output:\n\n{piped_data}\n\n{prompt}" |
| 246 | + else: |
| 247 | + # Only piped data, no prompt - ask what to do with it |
| 248 | + prompt = f"Here's some piped output:\n\n{piped_data}\n\nWhat can you tell me about this output?" |
270 | 249 |
|
271 | | - # Validate inputs |
272 | | - if not prompt and not interactive and not piped_data: |
273 | | - raise CLIError( |
274 | | - "Either the 'prompt' argument must be provided (unless using --interactive mode)." |
275 | | - ) |
276 | | - |
277 | | - # Handle piped data |
278 | | - if piped_data: |
279 | | - if prompt: |
280 | | - # User provided both piped data and a prompt |
281 | | - prompt = f"Here's some piped output:\n\n{piped_data}\n\n{prompt}" |
282 | | - else: |
283 | | - # Only piped data, no prompt - ask what to do with it |
284 | | - prompt = f"Here's some piped output:\n\n{piped_data}\n\nWhat can you tell me about this output?" |
285 | | - |
286 | | - # Phase 2: Holmes Execution (synchronous - no event loop conflicts) |
287 | | - is_mcp_mode = current_mode == "mcp" |
| 250 | + # Phase 2: Holmes Execution (synchronous - no event loop conflicts) |
| 251 | + is_mcp_mode = current_mode == "mcp" |
| 252 | + try: |
288 | 253 | if interactive: |
289 | 254 | _run_interactive_mode_sync(ai, cmd, resource_group_name, name, |
290 | 255 | prompt, console, show_tool_output, is_mcp_mode, telemetry) |
291 | 256 | else: |
292 | 257 | _run_noninteractive_mode_sync(ai, config, cmd, resource_group_name, name, |
293 | 258 | prompt, console, echo, show_tool_output, is_mcp_mode) |
294 | | - |
| 259 | + except Exception as e: # pylint: disable=broad-exception-caught |
| 260 | + raise CLIInternalError(f"Error occurred during execution: {str(e)}") |
295 | 261 | finally: |
296 | 262 | # Phase 3: MCP Cleanup (isolated async if needed) |
297 | 263 | mcp_lifecycle.cleanup_mcp_sync() |
|
0 commit comments