From 73f446078e9a8468c986818f46cabdee393be30d Mon Sep 17 00:00:00 2001 From: ahmad-ajmal Date: Mon, 16 Mar 2026 13:42:49 +0000 Subject: [PATCH 1/3] fixed google embedded creds --- agent_core/core/credentials/embedded_credentials.py | 5 +++++ app/config.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/agent_core/core/credentials/embedded_credentials.py b/agent_core/core/credentials/embedded_credentials.py index a5ae0c09..fd6960a0 100644 --- a/agent_core/core/credentials/embedded_credentials.py +++ b/agent_core/core/credentials/embedded_credentials.py @@ -31,6 +31,7 @@ "NTQwMzU1MDYyMDA1LTM3Y3RmcjBhNHVlazFjMWZzcDRzc25sd", "GhkdGJkbzZ2LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t", ], + "client_secret": ["R09DU1BYLTRpRi12Zmxac0xWYmNabXE2U3ZHTUw4RDllSHo="], }, "zoom": { "client_id": ["YWlsaURjY0JUUGlaZ", "W5Ka29acldHZw=="], @@ -50,6 +51,10 @@ "client_id": ["ODZ4aXVvZHQ", "2cjQ3MnU="], "client_secret": ["V1BMX0FQMS5FSHFHeDRUOGZ", "SM0k1cjM3LnFHNU45QT09"], }, + "telegram": { + "api_id": ["MzQyNDc4MTc="], + "api_hash": ["N2Q5ZjkzN2ZkNzAzYTI0NTkyMDQzNGM2YjU5MDE4OGE="] + } } diff --git a/app/config.py b/app/config.py index 32f2427c..c9e32c80 100644 --- a/app/config.py +++ b/app/config.py @@ -205,7 +205,7 @@ def reload_settings() -> Dict[str, Any]: # Google (PKCE - only client_id required, secret kept for backwards compatibility) GOOGLE_CLIENT_ID: str = get_credential("google", "client_id", "GOOGLE_CLIENT_ID") -GOOGLE_CLIENT_SECRET: str = os.environ.get("GOOGLE_CLIENT_SECRET", "") +GOOGLE_CLIENT_SECRET: str = get_credential("google", "client_secret", "GOOGLE_CLIENT_SECRET") # LinkedIn (requires both client_id and client_secret) LINKEDIN_CLIENT_ID: str = get_credential("linkedin", "client_id", "LINKEDIN_CLIENT_ID") @@ -223,8 +223,8 @@ def reload_settings() -> Dict[str, Any]: TELEGRAM_SHARED_BOT_USERNAME: str = os.environ.get("TELEGRAM_SHARED_BOT_USERNAME", "") # Telegram API credentials for MTProto user login (from https://my.telegram.org) -TELEGRAM_API_ID: str = os.environ.get("TELEGRAM_API_ID", "") -TELEGRAM_API_HASH: str = os.environ.get("TELEGRAM_API_HASH", "") +TELEGRAM_API_ID: str = get_credential("telegram", "api_id", "TELEGRAM_API_ID") +TELEGRAM_API_HASH: str = get_credential("telegram", "api_hash", "TELEGRAM_API_HASH") # Notion (requires both client_id and client_secret - no PKCE support) NOTION_SHARED_CLIENT_ID: str = get_credential("notion", "client_id", "NOTION_SHARED_CLIENT_ID") From 406926a5972f1cc69b0c61e50bff88ee2d4cb3e6 Mon Sep 17 00:00:00 2001 From: zfoong Date: Tue, 17 Mar 2026 13:54:22 +0900 Subject: [PATCH 2/3] bug:fix Anthrophic model output issue --- agent_core/core/impl/llm/errors.py | 156 ++++++++++++++++++ agent_core/core/impl/llm/interface.py | 39 ++++- app/agent_base.py | 6 +- app/config/mcp_config.json | 2 +- app/config/settings.json | 2 +- app/ui_layer/adapters/browser_adapter.py | 22 +++ .../src/pages/Onboarding/OnboardingPage.tsx | 6 +- test_output.py | 28 ---- 8 files changed, 224 insertions(+), 37 deletions(-) create mode 100644 agent_core/core/impl/llm/errors.py delete mode 100644 test_output.py diff --git a/agent_core/core/impl/llm/errors.py b/agent_core/core/impl/llm/errors.py new file mode 100644 index 00000000..a0b26ef2 --- /dev/null +++ b/agent_core/core/impl/llm/errors.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +""" +LLM Error Classification Module. + +Provides user-friendly error messages for LLM-related failures. +Technical details are preserved in logs while users see clear, +actionable messages. +""" + +from __future__ import annotations + + +def classify_llm_error(error: Exception) -> str: + """Classify an LLM error and return a user-friendly message. + + Analyzes the error to determine the root cause and returns + an appropriate message for end users. Technical details + should be logged separately. + + Args: + error: The exception from the LLM call. + + Returns: + A user-friendly error message. + """ + error_str = str(error).lower() + error_type = type(error).__name__.lower() + + # Authentication issues (API key wrong/missing/expired) + if _is_auth_error(error_str, error_type): + return "Unable to connect to AI service. Please check your API key in Settings." + + # Model not supported or not found + if _is_model_error(error_str, error_type): + return "The selected AI model is not available. Please check your model settings." + + # Bad configuration (invalid parameters, unsupported features) + if _is_config_error(error_str, error_type): + return "AI service configuration error. The selected model may not support required features." + + # Rate limiting + if _is_rate_limit_error(error_str, error_type): + return "AI service is rate-limited. Please wait a moment and try again." + + # Service unavailable (server errors, maintenance) + if _is_service_error(error_str, error_type): + return "AI service is temporarily unavailable. Please try again later." + + # Connection/network issues + if _is_connection_error(error_str, error_type): + return "Unable to reach AI service. Please check your internet connection." + + # Generic fallback + return "An error occurred with the AI service. Please check your LLM configuration." + + +def _is_auth_error(error_str: str, error_type: str) -> bool: + """Check if error is authentication-related.""" + auth_patterns = [ + "401", + "403", + "unauthorized", + "authentication", + "invalid_api_key", + "invalid api key", + "api key", + "apikey", + "credential", + "permission denied", + "access denied", + ] + auth_types = ["authenticationerror", "permissionerror"] + return any(p in error_str for p in auth_patterns) or error_type in auth_types + + +def _is_model_error(error_str: str, error_type: str) -> bool: + """Check if error is model-related.""" + model_patterns = [ + "model_not_found", + "model not found", + "does not exist", + "invalid model", + "unknown model", + "no such model", + "model is not available", + ] + # 404 specifically for model endpoints + if "404" in error_str and "model" in error_str: + return True + return any(p in error_str for p in model_patterns) + + +def _is_config_error(error_str: str, error_type: str) -> bool: + """Check if error is configuration-related.""" + config_patterns = [ + "400", + "bad request", + "invalid_request", + "invalid request", + "invalid parameter", + "json_schema", + "output_config", + "not supported", + "unsupported", + ] + config_types = ["badrequesterror", "validationerror"] + return any(p in error_str for p in config_patterns) or error_type in config_types + + +def _is_rate_limit_error(error_str: str, error_type: str) -> bool: + """Check if error is rate-limit-related.""" + rate_patterns = [ + "429", + "rate_limit", + "rate limit", + "too many requests", + "quota exceeded", + "throttl", + ] + rate_types = ["ratelimiterror"] + return any(p in error_str for p in rate_patterns) or error_type in rate_types + + +def _is_service_error(error_str: str, error_type: str) -> bool: + """Check if error is service-availability-related.""" + service_patterns = [ + "500", + "502", + "503", + "504", + "internal server error", + "service unavailable", + "bad gateway", + "gateway timeout", + "overloaded", + "maintenance", + ] + service_types = ["internalservererror", "serviceunavailableerror"] + return any(p in error_str for p in service_patterns) or error_type in service_types + + +def _is_connection_error(error_str: str, error_type: str) -> bool: + """Check if error is connection-related.""" + conn_patterns = [ + "timeout", + "timed out", + "connection refused", + "connection error", + "connection reset", + "network", + "unreachable", + "dns", + "resolve", + ] + conn_types = ["connectionerror", "timeouterror", "connecttimeout"] + return any(p in error_str for p in conn_patterns) or error_type in conn_types diff --git a/agent_core/core/impl/llm/interface.py b/agent_core/core/impl/llm/interface.py index 31a16893..a4f2525f 100644 --- a/agent_core/core/impl/llm/interface.py +++ b/agent_core/core/impl/llm/interface.py @@ -41,6 +41,38 @@ from agent_core.utils.logger import logger +# Models that do NOT support assistant message prefill +# These require output_config.format for structured JSON output +_ANTHROPIC_NO_PREFILL_PATTERNS = ( + "claude-opus-4", # Claude Opus 4.x (4.5, 4.6, etc.) + "claude-sonnet-4", # Claude Sonnet 4.x (4.5, 4.6, etc.) + "claude-3-7", # Claude 3.7 Sonnet + "claude-3.7", # Alternative naming +) + + +def _model_supports_prefill(model: str) -> bool: + """Check if an Anthropic model supports assistant message prefill. + + Newer Claude models (4.x, 3.7) do not support prefilling. + Older models (3.5 Sonnet, 3 Opus) still support it. + + Args: + model: The model identifier string. + + Returns: + True if the model supports prefill, False otherwise. + """ + if not model: + return True # Default to supporting prefill for safety + + model_lower = model.lower() + for pattern in _ANTHROPIC_NO_PREFILL_PATTERNS: + if pattern in model_lower: + return False + return True + + class LLMInterface: """LLM interface with multi-provider support and hook-based customization. @@ -1515,14 +1547,12 @@ def _generate_anthropic( if not self._anthropic_client: raise RuntimeError("Anthropic client was not initialised.") - # Build the message with optional system prompt - # Use JSON prefilling to enforce JSON output + # Build the message - rely on system prompt for JSON formatting message_kwargs: Dict[str, Any] = { "model": self.model, "max_tokens": self.max_tokens, "messages": [ {"role": "user", "content": user_prompt}, - {"role": "assistant", "content": "{"}, # JSON prefilling ], } @@ -1561,8 +1591,7 @@ def _generate_anthropic( if block.type == "text": content += block.text - # Prepend the prefilled '{' to complete JSON - content = "{" + content.strip() + content = content.strip() # Token usage from Anthropic response token_count_input = response.usage.input_tokens diff --git a/app/agent_base.py b/app/agent_base.py index 81b3ab1b..8511989c 100644 --- a/app/agent_base.py +++ b/app/agent_base.py @@ -49,6 +49,7 @@ from app.internal_action_interface import InternalActionInterface from app.llm import LLMInterface, LLMCallType +from agent_core.core.impl.llm.errors import classify_llm_error from app.vlm_interface import VLMInterface from app.database_interface import DatabaseInterface from app.logger import logger @@ -1159,12 +1160,15 @@ async def _handle_react_error( if not session_to_use or not self.event_stream_manager: return + # Get user-friendly error message + user_message = classify_llm_error(error) + try: logger.debug("[REACT ERROR] Logging to event stream") self.event_stream_manager.log( "error", f"[REACT] {type(error).__name__}: {error}\n{tb}", - display_message=None, + display_message=user_message, task_id=session_to_use, ) self.state_manager.bump_event_stream() diff --git a/app/config/mcp_config.json b/app/config/mcp_config.json index f77b823f..d9040e06 100644 --- a/app/config/mcp_config.json +++ b/app/config/mcp_config.json @@ -1262,7 +1262,7 @@ "AMADEUS_API_KEY": "", "AMADEUS_API_SECRET": "" }, - "enabled": false + "enabled": true }, { "name": "booking-mcp", diff --git a/app/config/settings.json b/app/config/settings.json index 403e9084..8ca7040d 100644 --- a/app/config/settings.json +++ b/app/config/settings.json @@ -3,7 +3,7 @@ "agent_name": "CraftBot" }, "proactive": { - "enabled": false + "enabled": true }, "memory": { "enabled": true diff --git a/app/ui_layer/adapters/browser_adapter.py b/app/ui_layer/adapters/browser_adapter.py index c659a4d1..bbf5b915 100644 --- a/app/ui_layer/adapters/browser_adapter.py +++ b/app/ui_layer/adapters/browser_adapter.py @@ -1310,6 +1310,28 @@ async def _handle_onboarding_step_submit(self, value: Any) -> None: }) return + # For API key step, test the connection before proceeding + step = controller.get_current_step() + if step.name == "api_key": + provider = controller.get_collected_data().get("provider", "openai") + # Remote/Ollama provider doesn't require API key validation + if provider != "remote" and value: + test_result = test_connection( + provider=provider, + api_key=value, + ) + if not test_result.get("success"): + error_msg = test_result.get("error") or test_result.get("message") or "Connection test failed" + await self._broadcast({ + "type": "onboarding_submit", + "data": { + "success": False, + "error": f"Invalid API key: {error_msg}", + "index": controller.current_step_index, + }, + }) + return + # Submit the value controller.submit_step_value(value) diff --git a/app/ui_layer/browser/frontend/src/pages/Onboarding/OnboardingPage.tsx b/app/ui_layer/browser/frontend/src/pages/Onboarding/OnboardingPage.tsx index cc69ad81..0ac87419 100644 --- a/app/ui_layer/browser/frontend/src/pages/Onboarding/OnboardingPage.tsx +++ b/app/ui_layer/browser/frontend/src/pages/Onboarding/OnboardingPage.tsx @@ -330,7 +330,11 @@ export function OnboardingPage() { icon={} iconPosition="right" > - {isLastStep ? 'Finish' : 'Next'} + {onboardingLoading && onboardingStep?.name === 'api_key' + ? 'Testing API Key...' + : isLastStep + ? 'Finish' + : 'Next'} diff --git a/test_output.py b/test_output.py deleted file mode 100644 index 9718cac0..00000000 --- a/test_output.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -import time -import sys - -print("="*50) -print(" CraftBot Installation") -print("="*50 + "\n") - -print("="*50) -print(" šŸ“¦ STEP 1: Installing Core Dependencies") -print("="*50) - -# Simulate progress -print("šŸ”§ Installing dependencies from requirements.txt...", end=" ", flush=True) -for i in range(101): - if i % 25 == 0: - sys.stdout.write(f"\ršŸ”§ Installing dependencies from requirements.txt... ({i}%)") - sys.stdout.flush() - time.sleep(0.02) - -print("\ršŸ”§ Installing dependencies from requirements.txt... (100%)") -print("āœ“ Dependencies installed\n") - -print("="*50) -print(" āœ“ Installation Complete!") -print("="*50) - -print("\nLaunching CraftBot...\n") From 9bfbbd0c52f731e65c6cb97b86997f9478952a37 Mon Sep 17 00:00:00 2001 From: zfoong Date: Tue, 17 Mar 2026 14:02:46 +0900 Subject: [PATCH 3/3] Fix band-aid code and reset proactive default setting --- agent_core/core/impl/llm/errors.py | 272 +++++++++++++++-------------- app/config/settings.json | 2 +- 2 files changed, 139 insertions(+), 135 deletions(-) diff --git a/agent_core/core/impl/llm/errors.py b/agent_core/core/impl/llm/errors.py index a0b26ef2..83ea1a4c 100644 --- a/agent_core/core/impl/llm/errors.py +++ b/agent_core/core/impl/llm/errors.py @@ -3,19 +3,44 @@ LLM Error Classification Module. Provides user-friendly error messages for LLM-related failures. -Technical details are preserved in logs while users see clear, -actionable messages. +Uses proper exception types and HTTP status codes - no string pattern matching. """ from __future__ import annotations +from typing import Optional + +# Import provider exception types +try: + import openai +except ImportError: + openai = None + +try: + import anthropic +except ImportError: + anthropic = None + +try: + import requests +except ImportError: + requests = None + + +# User-friendly messages +MSG_AUTH = "Unable to connect to AI service. Please check your API key in Settings." +MSG_MODEL = "The selected AI model is not available. Please check your model settings." +MSG_CONFIG = "AI service configuration error. The selected model may not support required features." +MSG_RATE_LIMIT = "AI service is rate-limited. Please wait a moment and try again." +MSG_SERVICE = "AI service is temporarily unavailable. Please try again later." +MSG_CONNECTION = "Unable to reach AI service. Please check your internet connection." +MSG_GENERIC = "An error occurred with the AI service. Please check your LLM configuration." + def classify_llm_error(error: Exception) -> str: """Classify an LLM error and return a user-friendly message. - Analyzes the error to determine the root cause and returns - an appropriate message for end users. Technical details - should be logged separately. + Uses exception types and HTTP status codes for classification. Args: error: The exception from the LLM call. @@ -23,134 +48,113 @@ def classify_llm_error(error: Exception) -> str: Returns: A user-friendly error message. """ - error_str = str(error).lower() - error_type = type(error).__name__.lower() - - # Authentication issues (API key wrong/missing/expired) - if _is_auth_error(error_str, error_type): - return "Unable to connect to AI service. Please check your API key in Settings." - - # Model not supported or not found - if _is_model_error(error_str, error_type): - return "The selected AI model is not available. Please check your model settings." - - # Bad configuration (invalid parameters, unsupported features) - if _is_config_error(error_str, error_type): - return "AI service configuration error. The selected model may not support required features." - - # Rate limiting - if _is_rate_limit_error(error_str, error_type): - return "AI service is rate-limited. Please wait a moment and try again." - - # Service unavailable (server errors, maintenance) - if _is_service_error(error_str, error_type): - return "AI service is temporarily unavailable. Please try again later." - - # Connection/network issues - if _is_connection_error(error_str, error_type): - return "Unable to reach AI service. Please check your internet connection." + # Check OpenAI exceptions + if openai is not None: + msg = _classify_openai_error(error) + if msg: + return msg + + # Check Anthropic exceptions + if anthropic is not None: + msg = _classify_anthropic_error(error) + if msg: + return msg + + # Check requests exceptions (BytePlus, remote/Ollama) + if requests is not None: + msg = _classify_requests_error(error) + if msg: + return msg + + # Check for status_code attribute on any exception + status_code = _get_status_code(error) + if status_code: + return _message_from_status_code(status_code) # Generic fallback - return "An error occurred with the AI service. Please check your LLM configuration." - - -def _is_auth_error(error_str: str, error_type: str) -> bool: - """Check if error is authentication-related.""" - auth_patterns = [ - "401", - "403", - "unauthorized", - "authentication", - "invalid_api_key", - "invalid api key", - "api key", - "apikey", - "credential", - "permission denied", - "access denied", - ] - auth_types = ["authenticationerror", "permissionerror"] - return any(p in error_str for p in auth_patterns) or error_type in auth_types - - -def _is_model_error(error_str: str, error_type: str) -> bool: - """Check if error is model-related.""" - model_patterns = [ - "model_not_found", - "model not found", - "does not exist", - "invalid model", - "unknown model", - "no such model", - "model is not available", - ] - # 404 specifically for model endpoints - if "404" in error_str and "model" in error_str: - return True - return any(p in error_str for p in model_patterns) - - -def _is_config_error(error_str: str, error_type: str) -> bool: - """Check if error is configuration-related.""" - config_patterns = [ - "400", - "bad request", - "invalid_request", - "invalid request", - "invalid parameter", - "json_schema", - "output_config", - "not supported", - "unsupported", - ] - config_types = ["badrequesterror", "validationerror"] - return any(p in error_str for p in config_patterns) or error_type in config_types - - -def _is_rate_limit_error(error_str: str, error_type: str) -> bool: - """Check if error is rate-limit-related.""" - rate_patterns = [ - "429", - "rate_limit", - "rate limit", - "too many requests", - "quota exceeded", - "throttl", - ] - rate_types = ["ratelimiterror"] - return any(p in error_str for p in rate_patterns) or error_type in rate_types - - -def _is_service_error(error_str: str, error_type: str) -> bool: - """Check if error is service-availability-related.""" - service_patterns = [ - "500", - "502", - "503", - "504", - "internal server error", - "service unavailable", - "bad gateway", - "gateway timeout", - "overloaded", - "maintenance", - ] - service_types = ["internalservererror", "serviceunavailableerror"] - return any(p in error_str for p in service_patterns) or error_type in service_types - - -def _is_connection_error(error_str: str, error_type: str) -> bool: - """Check if error is connection-related.""" - conn_patterns = [ - "timeout", - "timed out", - "connection refused", - "connection error", - "connection reset", - "network", - "unreachable", - "dns", - "resolve", - ] - conn_types = ["connectionerror", "timeouterror", "connecttimeout"] - return any(p in error_str for p in conn_patterns) or error_type in conn_types + return MSG_GENERIC + + +def _classify_openai_error(error: Exception) -> Optional[str]: + """Classify OpenAI SDK exceptions.""" + if isinstance(error, openai.AuthenticationError): + return MSG_AUTH + if isinstance(error, openai.PermissionDeniedError): + return MSG_AUTH + if isinstance(error, openai.NotFoundError): + return MSG_MODEL + if isinstance(error, openai.BadRequestError): + return MSG_CONFIG + if isinstance(error, openai.RateLimitError): + return MSG_RATE_LIMIT + if isinstance(error, openai.InternalServerError): + return MSG_SERVICE + if isinstance(error, openai.APIConnectionError): + return MSG_CONNECTION + if isinstance(error, openai.APITimeoutError): + return MSG_CONNECTION + if isinstance(error, openai.APIStatusError): + return _message_from_status_code(error.status_code) + return None + + +def _classify_anthropic_error(error: Exception) -> Optional[str]: + """Classify Anthropic SDK exceptions.""" + if isinstance(error, anthropic.AuthenticationError): + return MSG_AUTH + if isinstance(error, anthropic.PermissionDeniedError): + return MSG_AUTH + if isinstance(error, anthropic.NotFoundError): + return MSG_MODEL + if isinstance(error, anthropic.BadRequestError): + return MSG_CONFIG + if isinstance(error, anthropic.RateLimitError): + return MSG_RATE_LIMIT + if isinstance(error, anthropic.InternalServerError): + return MSG_SERVICE + if isinstance(error, anthropic.APIConnectionError): + return MSG_CONNECTION + if isinstance(error, anthropic.APITimeoutError): + return MSG_CONNECTION + if isinstance(error, anthropic.APIStatusError): + return _message_from_status_code(error.status_code) + return None + + +def _classify_requests_error(error: Exception) -> Optional[str]: + """Classify requests library exceptions (for BytePlus/Ollama).""" + if isinstance(error, requests.exceptions.HTTPError): + if error.response is not None: + return _message_from_status_code(error.response.status_code) + return MSG_SERVICE + if isinstance(error, requests.exceptions.ConnectionError): + return MSG_CONNECTION + if isinstance(error, requests.exceptions.Timeout): + return MSG_CONNECTION + return None + + +def _get_status_code(error: Exception) -> Optional[int]: + """Extract HTTP status code from exception if available.""" + # Check for status_code attribute + if hasattr(error, "status_code"): + return getattr(error, "status_code", None) + # Check for response.status_code (requests-style) + if hasattr(error, "response") and hasattr(error.response, "status_code"): + return error.response.status_code + return None + + +def _message_from_status_code(status_code: int) -> str: + """Map HTTP status code to user-friendly message.""" + if status_code == 401 or status_code == 403: + return MSG_AUTH + if status_code == 404: + return MSG_MODEL + if status_code == 400: + return MSG_CONFIG + if status_code == 429: + return MSG_RATE_LIMIT + if 500 <= status_code < 600: + return MSG_SERVICE + return MSG_GENERIC diff --git a/app/config/settings.json b/app/config/settings.json index 8ca7040d..403e9084 100644 --- a/app/config/settings.json +++ b/app/config/settings.json @@ -3,7 +3,7 @@ "agent_name": "CraftBot" }, "proactive": { - "enabled": true + "enabled": false }, "memory": { "enabled": true