Files

1119 lines
38 KiB
Python
Raw Permalink Normal View History

"""
Centralized error handling framework for the journal organizer application.
Provides consistent error types and handling patterns across all components.
"""
import logging
import traceback
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, Any, Optional, Union, List
class JournalOrganizerError(Exception):
"""Base exception for journal organizer errors"""
def __init__(
self,
message: str,
context: Optional[Dict[str, Any]] = None,
cause: Optional[Exception] = None,
) -> None:
"""
Initialize base error
Args:
message: Error message
context: Additional context information
cause: Original exception that caused this error
"""
super().__init__(message)
self.message = message
self.context = context or {}
self.cause = cause
self.timestamp = datetime.now().isoformat()
def to_dict(self) -> Dict[str, Any]:
"""Convert error to dictionary for logging/serialization"""
return {
"error_type": self.__class__.__name__,
"message": self.message,
"context": self.context,
"timestamp": self.timestamp,
"cause": str(self.cause) if self.cause else None,
}
class ConfigurationError(JournalOrganizerError):
"""Raised when configuration is invalid or missing"""
def __init__(
self,
message: str,
config_key: Optional[str] = None,
config_value: Optional[Any] = None,
cause: Optional[Exception] = None,
) -> None:
context = {}
if config_key:
context["config_key"] = config_key
if config_value is not None:
# Sanitize sensitive values
if (
"key" in str(config_key).lower()
or "password" in str(config_key).lower()
):
context["config_value"] = "[REDACTED]"
else:
context["config_value"] = str(config_value)
super().__init__(message, context, cause)
class ClaudeConfigurationError(ConfigurationError):
"""Specific error for Claude API configuration issues"""
def __init__(
self,
message: str,
config_key: Optional[str] = None,
config_value: Optional[Any] = None,
suggestions: Optional[list] = None,
corrective_action: Optional[str] = None,
cause: Optional[Exception] = None,
) -> None:
# Add Claude-specific context
context = {}
if config_key:
context["config_key"] = config_key
if config_value is not None:
# Sanitize sensitive values for Claude config
if any(sensitive in str(config_key).lower() for sensitive in ["key", "token", "secret"]):
context["config_value"] = "[REDACTED]"
else:
context["config_value"] = str(config_value)
if suggestions:
context["suggestions"] = suggestions
if corrective_action:
context["corrective_action"] = corrective_action
super().__init__(message, config_key, config_value, cause)
self.suggestions = suggestions or []
self.corrective_action = corrective_action
def get_user_friendly_message(self) -> str:
"""Get a user-friendly error message with suggestions"""
message_parts = [self.message]
if self.suggestions:
message_parts.append("\nSuggestions:")
for suggestion in self.suggestions:
message_parts.append(f" • {suggestion}")
if self.corrective_action:
message_parts.append(f"\nTo fix this: {self.corrective_action}")
return "\n".join(message_parts)
class ClaudeAPIURLError(ClaudeConfigurationError):
"""Error for invalid Claude API URL configuration"""
def __init__(
self,
message: str,
invalid_url: Optional[str] = None,
cause: Optional[Exception] = None,
) -> None:
suggestions = [
"Use a valid HTTP or HTTPS URL (e.g., https://api.anthropic.com)",
"Ensure the URL doesn't have trailing slashes",
"For custom endpoints, verify the server is running and accessible",
"For localhost endpoints, use http://localhost:PORT or https://localhost:PORT"
]
corrective_action = (
"Update your configuration with a valid API URL. "
"The default Anthropic API URL is 'https://api.anthropic.com'"
)
super().__init__(
message=message,
config_key="api_url",
config_value=invalid_url,
suggestions=suggestions,
corrective_action=corrective_action,
cause=cause
)
class ClaudeModelValidationError(ClaudeConfigurationError):
"""Error for invalid Claude model names with suggestions"""
def __init__(
self,
message: str,
invalid_model: Optional[str] = None,
valid_models: Optional[list] = None,
cause: Optional[Exception] = None,
) -> None:
# Default valid models if not provided
if not valid_models:
valid_models = [
"claude-3-5-sonnet-20241022",
"claude-3-5-haiku-20241022",
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307"
]
suggestions = [
f"Use one of these supported models: {', '.join(valid_models[:3])}",
"Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest",
"Check the Anthropic documentation for the latest available models",
"Ensure the model name is spelled correctly and includes the date suffix"
]
corrective_action = (
f"Update your model configuration to use a valid model name. "
f"Recommended: '{valid_models[0]}'"
)
super().__init__(
message=message,
config_key="model",
config_value=invalid_model,
suggestions=suggestions,
corrective_action=corrective_action,
cause=cause
)
class ClaudeAPIKeyError(ClaudeConfigurationError):
"""Error for invalid Claude API key configuration"""
def __init__(
self,
message: str,
cause: Optional[Exception] = None,
) -> None:
suggestions = [
"Obtain a valid API key from https://console.anthropic.com/",
"Ensure the API key starts with 'sk-ant-'",
"Set the API key as an environment variable: export ANTHROPIC_API_KEY='sk-ant-...'",
"Verify the API key is not truncated or contains extra whitespace",
"Check that your API key has the necessary permissions"
]
corrective_action = (
"Get a valid Claude API key from the Anthropic console and set it in your configuration "
"or as the ANTHROPIC_API_KEY environment variable"
)
super().__init__(
message=message,
config_key="api_key",
config_value="[REDACTED]",
suggestions=suggestions,
corrective_action=corrective_action,
cause=cause
)
class ClaudeConnectionError(ClaudeConfigurationError):
"""Error for Claude API connection issues"""
def __init__(
self,
message: str,
api_url: Optional[str] = None,
status_code: Optional[int] = None,
cause: Optional[Exception] = None,
) -> None:
suggestions = []
corrective_action = ""
# Provide specific suggestions based on the error context
if status_code == 401:
suggestions = [
"Verify your API key is correct and active",
"Check that the API key has not expired",
"Ensure you're using the correct API key for the endpoint"
]
corrective_action = "Update your API key with a valid, active key from the Anthropic console"
elif status_code == 404:
suggestions = [
"Verify the API URL is correct",
"Check if the API endpoint is available",
"For custom endpoints, ensure the service is running"
]
corrective_action = "Verify and correct your API URL configuration"
elif status_code == 429:
suggestions = [
"You've exceeded the API rate limit",
"Wait before making more requests",
"Consider implementing request throttling"
]
corrective_action = "Wait for the rate limit to reset before retrying"
elif "connection" in message.lower() or "timeout" in message.lower():
suggestions = [
"Check your internet connection",
"Verify the API URL is accessible",
"For custom endpoints, ensure the server is running",
"Check firewall settings that might block the connection"
]
corrective_action = "Verify network connectivity and API endpoint availability"
elif "ssl" in message.lower() or "certificate" in message.lower():
suggestions = [
"For localhost endpoints, SSL errors are expected",
"For custom HTTPS endpoints, ensure valid SSL certificates",
"Consider using HTTP for local development endpoints"
]
corrective_action = "Check SSL certificate configuration or use HTTP for local endpoints"
else:
suggestions = [
"Check the API endpoint status",
"Verify your configuration is correct",
"Try again after a short delay"
]
corrective_action = "Review your Claude API configuration and try again"
context = {}
if api_url:
context["api_url"] = api_url
if status_code:
context["status_code"] = status_code
super().__init__(
message=message,
config_key="connection",
suggestions=suggestions,
corrective_action=corrective_action,
cause=cause
)
# Add additional context
self.context.update(context)
class APIError(JournalOrganizerError):
"""Raised when external API calls fail"""
def __init__(
self,
message: str,
api_name: Optional[str] = None,
status_code: Optional[int] = None,
response_data: Optional[str] = None,
cause: Optional[Exception] = None,
) -> None:
context = {}
if api_name:
context["api_name"] = api_name
if status_code:
context["status_code"] = status_code
if response_data:
# Limit response data length to prevent log spam
context["response_data"] = (
f"{response_data[:500]}..."
if len(response_data) > 500
else response_data
)
super().__init__(message, context, cause)
class ValidationError(JournalOrganizerError):
"""Raised when input validation fails"""
def __init__(
self,
message: str,
field_name: Optional[str] = None,
field_value: Optional[Any] = None,
validation_rule: Optional[str] = None,
cause: Optional[Exception] = None,
) -> None:
context = {}
if field_name:
context["field_name"] = field_name
if field_value is not None:
# Sanitize potentially sensitive field values
if any(
sensitive in str(field_name).lower()
for sensitive in ["password", "key", "token", "secret"]
):
context["field_value"] = "[REDACTED]"
else:
context["field_value"] = str(field_value)[:100] # Limit length
if validation_rule:
context["validation_rule"] = validation_rule
super().__init__(message, context, cause)
class FileSystemError(JournalOrganizerError):
"""Raised when file system operations fail"""
def __init__(
self,
message: str,
file_path: Optional[str] = None,
operation: Optional[str] = None,
cause: Optional[Exception] = None,
) -> None:
context = {}
if file_path:
context["file_path"] = file_path
if operation:
context["operation"] = operation
super().__init__(message, context, cause)
class SecurityError(JournalOrganizerError):
"""Raised when security violations are detected"""
def __init__(
self,
message: str,
security_issue: Optional[str] = None,
attempted_path: Optional[str] = None,
risk_level: str = "high",
cause: Optional[Exception] = None,
) -> None:
context = {}
if security_issue:
context["security_issue"] = security_issue
if attempted_path:
# Sanitize the attempted path to prevent log injection
context["attempted_path"] = str(attempted_path)[:500] # Limit length
context["risk_level"] = risk_level
super().__init__(message, context, cause)
@dataclass
class ErrorContext:
"""Context information for error handling"""
component: str
operation: str
user_message: str = ""
technical_details: Dict[str, Any] = field(default_factory=dict)
severity: str = "error" # error, warning, info
def __post_init__(self) -> None:
"""Validate error context after initialization"""
if not self.component or not self.component.strip():
raise ValueError("component cannot be empty")
if not self.operation or not self.operation.strip():
raise ValueError("operation cannot be empty")
valid_severities = ["error", "warning", "info"]
if self.severity not in valid_severities:
raise ValueError(f"severity must be one of {valid_severities}")
if not isinstance(self.technical_details, dict):
raise ValueError("technical_details must be a dictionary")
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary"""
return {
"component": self.component,
"operation": self.operation,
"user_message": self.user_message,
"technical_details": self.technical_details,
"severity": self.severity,
}
class ErrorHandler:
"""Centralized error handling and logging"""
def __init__(self, logger: Optional[logging.Logger] = None) -> None:
"""
Initialize error handler
Args:
logger: Logger instance to use (creates default if None)
"""
self.logger = logger or logging.getLogger(__name__)
def handle_error(self, error: Exception, context: ErrorContext) -> Dict[str, Any]:
"""
Handle any error with consistent logging and formatting
Args:
error: The exception that occurred
context: Error context information
Returns:
Standardized error response dictionary
"""
# Log the error with appropriate level
log_level = getattr(logging, context.severity.upper(), logging.ERROR)
if isinstance(error, JournalOrganizerError):
# Use our custom error information
error_dict = error.to_dict()
# Sanitize the error dictionary
sanitized_error_dict = self._sanitize_context_data(error_dict)
self.logger.log(
log_level,
f"{context.component}.{context.operation}: {error.message}",
extra={"error_context": sanitized_error_dict},
)
else:
# Handle unexpected errors
sanitized_context = self._sanitize_context_data(context.to_dict())
self.logger.log(
log_level,
f"{context.component}.{context.operation}: {str(error)}",
extra={
"error_context": sanitized_context,
"traceback": traceback.format_exc(),
},
)
# Return standardized error response with sanitized messages
return {
"success": False,
"error": self._sanitize_error_message(str(error)),
"message": context.user_message or f"Error in {context.operation}",
"component": context.component,
"operation": context.operation,
"timestamp": datetime.now().isoformat(),
"error_type": error.__class__.__name__,
}
def handle_api_error(
self, error: Exception, api_name: str, operation: str
) -> Dict[str, Any]:
"""
Handle API-related errors consistently
Args:
error: The exception that occurred
api_name: Name of the API (e.g., 'obsidian', 'claude')
operation: Operation being performed
Returns:
Standardized error response
"""
if isinstance(error, APIError):
api_error = error
else:
# Wrap generic exceptions in APIError
api_error = APIError(
message=f"{api_name} API error: {str(error)}",
api_name=api_name,
cause=error,
)
context = ErrorContext(
component=f"{api_name}_api",
operation=operation,
user_message=f"Failed to communicate with {api_name}. Please check your configuration and try again.",
severity="error",
)
return self.handle_error(api_error, context)
def handle_validation_error(
self, error: Exception, field_name: str, operation: str
) -> Dict[str, Any]:
"""
Handle validation errors with user-friendly messages
Args:
error: The validation exception
field_name: Name of the field that failed validation
operation: Operation being performed
Returns:
Standardized error response
"""
if isinstance(error, ValidationError):
validation_error = error
else:
validation_error = ValidationError(
message=f"Invalid {field_name}: {str(error)}",
field_name=field_name,
cause=error,
)
context = ErrorContext(
component="validation",
operation=operation,
user_message=f"Please check your {field_name} and try again.",
severity="warning",
)
return self.handle_error(validation_error, context)
def handle_configuration_error(
self, error: Exception, config_key: str
) -> Dict[str, Any]:
"""
Handle configuration errors with helpful guidance
Args:
error: The configuration exception
config_key: Configuration key that caused the error
Returns:
Standardized error response
"""
if isinstance(error, ConfigurationError):
config_error = error
else:
config_error = ConfigurationError(
message=f"Configuration error for {config_key}: {str(error)}",
config_key=config_key,
cause=error,
)
# Provide helpful guidance based on config key
guidance_messages = {
"api_key": "Please check your API key configuration. Ensure the key is valid and properly set.",
"vault_path": "Please verify that the Obsidian vault path exists and is accessible.",
"url": "Please check the API URL configuration. Ensure the service is running and accessible.",
"model": "Please verify the AI model name is correct and available.",
}
user_message = guidance_messages.get(
config_key, f"Please check your {config_key} configuration."
)
context = ErrorContext(
component="configuration",
operation="load_config",
user_message=user_message,
severity="error",
)
return self.handle_error(config_error, context)
def handle_claude_configuration_error(
self, error: Exception, config_key: str = None, operation: str = "validate_config"
) -> Dict[str, Any]:
"""
Handle Claude-specific configuration errors with detailed guidance
Args:
error: The configuration exception
config_key: Specific Claude configuration key that caused the error
operation: Operation being performed when error occurred
Returns:
Standardized error response with Claude-specific guidance
"""
# Convert to Claude-specific error if not already
if isinstance(error, ClaudeConfigurationError):
claude_error = error
else:
# Create appropriate Claude error based on config key
if config_key == "api_url":
claude_error = ClaudeAPIURLError(
message=str(error),
cause=error
)
elif config_key == "model":
claude_error = ClaudeModelValidationError(
message=str(error),
cause=error
)
elif config_key == "api_key":
claude_error = ClaudeAPIKeyError(
message=str(error),
cause=error
)
else:
claude_error = ClaudeConfigurationError(
message=f"Claude configuration error for {config_key}: {str(error)}",
config_key=config_key,
cause=error
)
# Get user-friendly message with suggestions
user_message = claude_error.get_user_friendly_message()
context = ErrorContext(
component="claude_configuration",
operation=operation,
user_message=user_message,
severity="error",
)
return self.handle_error(claude_error, context)
def handle_claude_connection_error(
self, error: Exception, api_url: str = None, operation: str = "test_connection"
) -> Dict[str, Any]:
"""
Handle Claude API connection errors with specific guidance
Args:
error: The connection exception
api_url: API URL that failed to connect
operation: Operation being performed when error occurred
Returns:
Standardized error response with connection-specific guidance
"""
# Extract status code if available
status_code = None
error_str = str(error)
# Try to extract HTTP status codes from common error messages
import re
status_match = re.search(r'\b(4\d{2}|5\d{2})\b', error_str)
if status_match:
status_code = int(status_match.group(1))
# Create Claude connection error
if isinstance(error, ClaudeConnectionError):
connection_error = error
else:
connection_error = ClaudeConnectionError(
message=str(error),
api_url=api_url,
status_code=status_code,
cause=error
)
# Get user-friendly message with suggestions
user_message = connection_error.get_user_friendly_message()
context = ErrorContext(
component="claude_api",
operation=operation,
user_message=user_message,
severity="error",
)
return self.handle_error(connection_error, context)
def _sanitize_error_message(self, message: str) -> str:
"""
Sanitize error messages to prevent sensitive information exposure
Args:
message: Original error message
Returns:
Sanitized error message
"""
# List of patterns that might contain sensitive information
sensitive_patterns = [
"api_key",
"password",
"token",
"secret",
"auth",
"bearer",
"key",
"pwd",
"pass",
"credential",
"authorization",
]
sanitized = message
# Replace potential sensitive values with placeholder
import re
# Pattern 1: key=value, key:value patterns
for pattern in sensitive_patterns:
patterns_to_replace = [
# Match key=value patterns (with optional quotes)
rf'{pattern}["\']?\s*[:=]\s*["\']?[^\s"\'&,;]+["\']?',
# Match Bearer token patterns
rf"Bearer\s+[^\s]+",
# Match quoted key-value pairs
rf'["\']?{pattern}["\']?\s*:\s*["\'][^"\']+["\']',
# Match URL parameters
rf"{pattern}=[^&\s]+",
# Match JSON-like patterns
rf'["\']?{pattern}["\']?\s*:\s*["\']?[^"\'&,;\s]+["\']?',
]
for regex_pattern in patterns_to_replace:
sanitized = re.sub(
regex_pattern,
f"{pattern}=[REDACTED]",
sanitized,
flags=re.IGNORECASE,
)
# Additional patterns for common sensitive data
additional_patterns = [
# Email addresses
(
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
"[EMAIL_REDACTED]",
),
# IP addresses (be conservative, only redact private ranges)
(
r"\b(?:10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)\d{1,3}\.\d{1,3}\b",
"[IP_REDACTED]",
),
# File paths that might contain usernames
(r"/Users/[^/\s]+", "/Users/[USER_REDACTED]"),
(r"C:\\\\Users\\\\[^\\\\s]+", "C:\\\\Users\\\\[USER_REDACTED]"),
# UUIDs (might be sensitive identifiers)
(
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b",
"[UUID_REDACTED]",
),
# Long hex strings that might be tokens/keys
(r"\b[0-9a-fA-F]{32,}\b", "[HEX_TOKEN_REDACTED]"),
]
for pattern, replacement in additional_patterns:
sanitized = re.sub(pattern, replacement, sanitized)
return sanitized
def _sanitize_context_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Sanitize context data to prevent sensitive information exposure
Args:
data: Original context data
Returns:
Sanitized context data
"""
if not isinstance(data, dict):
return data
sanitized = {}
sensitive_keys = {
"api_key",
"password",
"token",
"secret",
"auth",
"bearer",
"key",
"pwd",
"pass",
"credential",
"authorization",
"private_key",
"client_secret",
"access_token",
"refresh_token",
}
for key, value in data.items():
key_lower = str(key).lower()
# Check if key contains sensitive information
if any(sensitive in key_lower for sensitive in sensitive_keys):
sanitized[key] = "[REDACTED]"
elif isinstance(value, dict):
# Recursively sanitize nested dictionaries
sanitized[key] = self._sanitize_context_data(value)
elif isinstance(value, str):
# Sanitize string values
sanitized[key] = self._sanitize_error_message(value)
else:
sanitized[key] = value
return sanitized
# Global error handler instance
_global_error_handler: Optional[ErrorHandler] = None
def get_error_handler() -> ErrorHandler:
"""Get the global error handler instance"""
global _global_error_handler
if _global_error_handler is None:
_global_error_handler = ErrorHandler()
return _global_error_handler
def set_error_handler(handler: ErrorHandler) -> None:
"""Set the global error handler instance"""
global _global_error_handler
_global_error_handler = handler
def audit_error_message_security(message: str) -> Dict[str, Any]:
"""
Audit an error message for potential security issues
Args:
message: Error message to audit
Returns:
Dictionary containing audit results
"""
issues = []
# Check for potential sensitive patterns
sensitive_patterns = [
(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "email_address"),
(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "ip_address"),
(r"/Users/[^/\s]+", "user_path"),
(r"C:\\Users\\[^\\s]+", "windows_user_path"),
(r"\b[0-9a-fA-F]{32,}\b", "potential_token"),
(
r"(?i)(api_key|password|token|secret|auth|bearer)\s*[:=]\s*[^\s]+",
"credential_pattern",
),
(r"Bearer\s+[^\s]+", "bearer_token"),
]
for pattern, issue_type in sensitive_patterns:
import re
if re.search(pattern, message):
issues.append(
{
"type": issue_type,
"pattern": pattern,
"severity": "high"
if "token" in issue_type or "credential" in issue_type
else "medium",
}
)
return {
"message": message,
"has_issues": len(issues) > 0,
"issues": issues,
"risk_level": "high"
if any(issue["severity"] == "high" for issue in issues)
else "medium"
if issues
else "low",
}
def transform_pydantic_validation_error(validation_error, config_section: str = "configuration") -> ClaudeConfigurationError:
"""
Transform Pydantic validation errors into user-friendly Claude configuration errors
Args:
validation_error: Pydantic ValidationError instance
config_section: Configuration section being validated
Returns:
ClaudeConfigurationError with user-friendly message and suggestions
"""
try:
# Import pydantic ValidationError if available
from pydantic import ValidationError as PydanticValidationError
if not isinstance(validation_error, PydanticValidationError):
# Not a pydantic error, create generic error
return ClaudeConfigurationError(
message=f"Configuration validation failed: {str(validation_error)}",
cause=validation_error
)
errors = validation_error.errors()
if not errors:
return ClaudeConfigurationError(
message="Unknown validation error occurred",
cause=validation_error
)
# Process the first error (most relevant)
first_error = errors[0]
field_path = " -> ".join(str(loc) for loc in first_error.get('loc', []))
error_msg = first_error.get('msg', 'Unknown validation error')
error_type = first_error.get('type', 'unknown')
# Determine the specific field that failed
field_name = first_error.get('loc', [])[-1] if first_error.get('loc') else 'unknown'
# Create specific error based on field
if field_name == 'api_url':
return ClaudeAPIURLError(
message=f"Invalid API URL: {error_msg}",
invalid_url=first_error.get('input'),
cause=validation_error
)
elif field_name == 'model':
return ClaudeModelValidationError(
message=f"Invalid model name: {error_msg}",
invalid_model=first_error.get('input'),
cause=validation_error
)
elif field_name == 'api_key':
return ClaudeAPIKeyError(
message=f"Invalid API key: {error_msg}",
cause=validation_error
)
else:
# Generic Claude configuration error
suggestions = []
corrective_action = ""
# Add specific suggestions based on error type
if error_type == 'value_error':
suggestions.append("Check that the value meets the required format")
corrective_action = f"Update the {field_name} field with a valid value"
elif error_type == 'missing':
suggestions.append(f"The {field_name} field is required")
corrective_action = f"Add the missing {field_name} field to your configuration"
elif error_type == 'type_error':
suggestions.append(f"The {field_name} field has the wrong data type")
corrective_action = f"Ensure {field_name} is the correct data type"
return ClaudeConfigurationError(
message=f"Configuration error in {field_path}: {error_msg}",
config_key=field_name,
suggestions=suggestions,
corrective_action=corrective_action,
cause=validation_error
)
except ImportError:
# Pydantic not available, create generic error
return ClaudeConfigurationError(
message=f"Configuration validation failed: {str(validation_error)}",
cause=validation_error
)
def create_claude_configuration_help_message() -> str:
"""
Create a comprehensive help message for Claude configuration
Returns:
Formatted help message with configuration guidance
"""
return """
Claude API Configuration Help:
1. API Key (api_key):
- Required: Yes
- Format: Must start with 'sk-ant-'
- Source: Get from https://console.anthropic.com/
- Environment Variable: ANTHROPIC_API_KEY
- Example: sk-ant-api03-abc123...
2. API URL (api_url):
- Required: No (defaults to https://api.anthropic.com)
- Format: Valid HTTP/HTTPS URL
- Custom Endpoints: Supported for proxy servers or regional endpoints
- Example: https://api.anthropic.com
3. Model (model):
- Required: No (defaults to claude-3-5-sonnet-20241022)
- Format: claude-X-Y-YYYYMMDD or claude-X-Y-latest
- Supported Models:
• claude-3-5-sonnet-20241022 (recommended)
• claude-3-5-haiku-20241022
• claude-3-opus-20240229
• claude-3-sonnet-20240229
• claude-3-haiku-20240307
4. Additional Parameters:
- max_tokens: Maximum response length (1-200000, default: 4096)
- temperature: Response randomness (0.0-1.0, default: 0.7)
5. Environment Variables:
- Use ${VARIABLE_NAME} syntax in configuration
- Set required variables: export ANTHROPIC_API_KEY="your-key"
- Default values: ${VARIABLE_NAME:-default_value}
6. Common Issues:
- API Key: Ensure it's complete and starts with 'sk-ant-'
- URL: Use valid HTTP/HTTPS format without trailing slashes
- Model: Check spelling and include date suffix
- Connection: Verify network access and endpoint availability
7. Testing Configuration:
- The system will validate your configuration on startup
- Connection tests are performed automatically
- Check logs for detailed error information
For more help, visit: https://docs.anthropic.com/claude/reference/
"""
def get_claude_error_recovery_suggestions(error_type: str, context: Dict[str, Any] = None) -> List[str]:
"""
Get specific recovery suggestions based on error type and context
Args:
error_type: Type of error (e.g., 'connection', 'authentication', 'model')
context: Additional context information
Returns:
List of recovery suggestions
"""
context = context or {}
suggestions = []
if error_type == 'connection':
suggestions = [
"Check your internet connection",
"Verify the API URL is correct and accessible",
"For custom endpoints, ensure the service is running",
"Check firewall settings that might block API access",
"Try again after a short delay"
]
if context.get('api_url') and 'localhost' in context['api_url']:
suggestions.insert(2, "For localhost endpoints, ensure the local service is running")
elif error_type == 'authentication':
suggestions = [
"Verify your API key is correct and complete",
"Check that the API key starts with 'sk-ant-'",
"Ensure the API key hasn't expired",
"Verify the API key has necessary permissions",
"Get a new API key from https://console.anthropic.com/"
]
elif error_type == 'model':
suggestions = [
"Check the model name spelling and format",
"Ensure the model name includes the date suffix (YYYYMMDD)",
"Use a supported model like 'claude-3-5-sonnet-20241022'",
"Verify the model is available in your region",
"Check the Anthropic documentation for available models"
]
if context.get('invalid_model'):
suggestions.insert(0, f"Replace '{context['invalid_model']}' with a valid model name")
elif error_type == 'rate_limit':
suggestions = [
"Wait before making more API requests",
"Implement request throttling in your application",
"Consider upgrading your API plan for higher limits",
"Reduce the frequency of API calls",
"Use exponential backoff for retries"
]
elif error_type == 'validation':
suggestions = [
"Check your configuration file syntax",
"Ensure all required fields are present",
"Verify data types match the expected format",
"Remove any extra or invalid configuration fields",
"Use the configuration example as a reference"
]
else:
# Generic suggestions
suggestions = [
"Check your Claude API configuration",
"Verify all required fields are present and correct",
"Review the error message for specific details",
"Consult the configuration documentation",
"Try restarting the application after fixing the configuration"
]
return suggestions