diff --git a/gns3server/agent/gns3_copilot/agent_service.py b/gns3server/agent/gns3_copilot/agent_service.py index 1cdc57c9c..aa28a1ac0 100644 --- a/gns3server/agent/gns3_copilot/agent_service.py +++ b/gns3server/agent/gns3_copilot/agent_service.py @@ -57,6 +57,7 @@ from gns3server.agent.gns3_copilot.gns3_client.context_helpers import ( from gns3server.agent.gns3_copilot.gns3_client.context_helpers import ( set_current_llm_config, ) +from gns3server.agent.gns3_copilot.utils.error_handler import format_error_message from gns3server.agent.gns3_copilot.utils.message_converters import ( convert_langchain_to_openai, ) @@ -499,7 +500,11 @@ class AgentService: except Exception as e: log.error("Error in stream_chat: %s", e, exc_info=True) - yield {"type": "error", "error": str(e), "session_id": session_id} + yield { + "type": "error", + "error": format_error_message(e), + "session_id": session_id, + } def _convert_event_to_chunk( self, event: Dict[str, Any], session_id: str diff --git a/gns3server/agent/gns3_copilot/utils/__init__.py b/gns3server/agent/gns3_copilot/utils/__init__.py index 08d1e8b60..ed79a457c 100644 --- a/gns3server/agent/gns3_copilot/utils/__init__.py +++ b/gns3server/agent/gns3_copilot/utils/__init__.py @@ -37,6 +37,7 @@ Author: Yue Guobin (岳国宾) """ # Import main utility functions +from .error_handler import format_error_message from .get_gns3_device_port import get_device_ports_from_topology from .parse_tool_content import format_tool_response from .parse_tool_content import normalize_tool_response @@ -56,6 +57,7 @@ __url__ = "https://github.com/yueguobin/gns3-copilot" # Export main utility functions __all__ = [ + "format_error_message", "get_device_ports_from_topology", "parse_tool_content", "format_tool_response", diff --git a/gns3server/agent/gns3_copilot/utils/error_handler.py b/gns3server/agent/gns3_copilot/utils/error_handler.py new file mode 100644 index 000000000..a919d0a41 --- /dev/null +++ b/gns3server/agent/gns3_copilot/utils/error_handler.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# GNS3-Copilot - AI-powered Network Lab Assistant for GNS3 +# +# This file is part of GNS3-Copilot project. +# +# GNS3-Copilot is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# GNS3-Copilot is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have have received a copy of the GNU General Public License +# along with GNS3-Copilot. If not, see . +# +# Copyright (C) 2025 Yue Guobin (岳国宾) +# Author: Yue Guobin (岳国宾) +# +# Project Home: https://github.com/yueguobin/gns3-copilot +# + +""" +Error Handler for GNS3-Copilot Agent + +This module provides utilities for formatting and handling errors from +the LLM API client layer (LangChain), particularly HTTP errors that +may return HTML or other unhelpful content. +""" + +import logging +import re + +logger = logging.getLogger(__name__) + +# Patterns to detect HTML in error messages +HTML_PATTERNS = [ + r"", + r" str: + """ + Format an error message for user display, cleaning up HTML and + extracting meaningful error information. + + Args: + error: The exception to format + + Returns: + A formatted, user-friendly error message + """ + error_str = str(error) + + # Check if error message contains HTML + if _contains_html(error_str): + logger.warning( + "Detected HTML in error message, likely due to incorrect API " + "configuration or base URL" + ) + return ( + "API request failed. The error response indicates an issue with " + "your API configuration. Please check:\n" + "1. Base URL is correct (e.g., https://api.openai.com/v1 for OpenAI)\n" + "2. API key is valid\n" + "3. Provider is correctly configured" + ) + + # For other errors, return the original message + return error_str + + +def _contains_html(text: str) -> bool: + """Check if text contains HTML tags.""" + text_lower = text.lower() + for pattern in HTML_PATTERNS: + if re.search(pattern, text_lower, re.IGNORECASE): + return True + return False