mirror of
https://github.com/coleam00/Archon.git
synced 2026-01-01 04:09:08 -05:00
refactor: Implement provider-agnostic error handling architecture
Transform OpenAI-specific error handling into extensible multi-provider system that supports OpenAI, Google AI, Anthropic, Ollama, and future providers. ## Backend Enhancements - Add ProviderErrorAdapter pattern with provider-specific implementations - Create ProviderErrorFactory for unified error handling across providers - Refactor API key validation to detect and handle any provider - Update error sanitization to use provider-specific patterns - Add provider context to all error responses ## Frontend Enhancements - Rename interfaces from OpenAI-specific to provider-agnostic - Update error detection to work with any provider name - Add provider context to error messages and guidance - Support provider-specific error codes and classifications ## Provider Support Added ✅ OpenAI: sk-* keys, org/proj/req IDs, quota/rate limit patterns ✅ Google AI: AIza* keys, googleapis.com URLs, project patterns ✅ Anthropic: sk-ant-* keys, anthropic.com URLs ✅ Ollama: localhost URLs, connection patterns (no API keys) ## Error Message Examples - OpenAI: 'Invalid or expired OpenAI API key. Please check your API key in settings.' - Google: 'Invalid or expired Google API key. Please check your API key in settings.' - Anthropic: 'Invalid or expired Anthropic API key. Please check your API key in settings.' ## Security Features - Provider-specific sanitization patterns prevent data exposure - Auto-detection of provider from error content - Structured error codes for reliable classification - Enhanced input validation and ReDoS protection This addresses the code review feedback to make error handling truly generic and extensible for all LLM providers, not just OpenAI, while maintaining the same level of user experience and security for each provider.
This commit is contained in:
@@ -275,7 +275,7 @@ export function useCrawlUrl() {
|
||||
}
|
||||
|
||||
// Use enhanced error handling for better user experience
|
||||
const errorMessage = (error as EnhancedError)?.isOpenAIError
|
||||
const errorMessage = (error as EnhancedError)?.isProviderError
|
||||
? getDisplayErrorMessage(error as EnhancedError)
|
||||
: (error instanceof Error ? error.message : "Failed to start crawl");
|
||||
|
||||
@@ -455,7 +455,7 @@ export function useUploadDocument() {
|
||||
}
|
||||
|
||||
// Use enhanced error handling for better user experience
|
||||
const message = (error as EnhancedError)?.isOpenAIError
|
||||
const message = (error as EnhancedError)?.isProviderError
|
||||
? getDisplayErrorMessage(error as EnhancedError)
|
||||
: (error instanceof Error ? error.message : "Failed to upload document");
|
||||
showToast(message, "error");
|
||||
@@ -529,7 +529,7 @@ export function useDeleteKnowledgeItem() {
|
||||
}
|
||||
|
||||
// Use enhanced error handling for better user experience
|
||||
const errorMessage = (error as EnhancedError)?.isOpenAIError
|
||||
const errorMessage = (error as EnhancedError)?.isProviderError
|
||||
? getDisplayErrorMessage(error as EnhancedError)
|
||||
: (error instanceof Error ? error.message : "Failed to delete item");
|
||||
showToast(errorMessage, "error");
|
||||
@@ -579,7 +579,7 @@ export function useUpdateKnowledgeItem() {
|
||||
}
|
||||
|
||||
// Use enhanced error handling for better user experience
|
||||
const errorMessage = (error as EnhancedError)?.isOpenAIError
|
||||
const errorMessage = (error as EnhancedError)?.isProviderError
|
||||
? getDisplayErrorMessage(error as EnhancedError)
|
||||
: (error instanceof Error ? error.message : "Failed to update item");
|
||||
showToast(errorMessage, "error");
|
||||
@@ -618,7 +618,7 @@ export function useRefreshKnowledgeItem() {
|
||||
},
|
||||
onError: (error) => {
|
||||
// Use enhanced error handling for better user experience
|
||||
const errorMessage = (error as EnhancedError)?.isOpenAIError
|
||||
const errorMessage = (error as EnhancedError)?.isProviderError
|
||||
? getDisplayErrorMessage(error as EnhancedError)
|
||||
: (error instanceof Error ? error.message : "Failed to refresh item");
|
||||
showToast(errorMessage, "error");
|
||||
|
||||
@@ -24,54 +24,63 @@ export async function callKnowledgeAPI<T>(
|
||||
// The ETag client extracts the error message but loses the structured details
|
||||
// We need to reconstruct the structured error based on the status code and message
|
||||
|
||||
// Use status code and message patterns to identify OpenAI errors
|
||||
// More reliable than exact string matching
|
||||
if (error.statusCode === 401 && error.message.includes("OpenAI API key")) {
|
||||
// This is our OpenAI authentication error
|
||||
// Detect provider from error message and use appropriate error structure
|
||||
let provider = "LLM";
|
||||
if (error.message.includes("OpenAI")) provider = "OpenAI";
|
||||
else if (error.message.includes("Google")) provider = "Google";
|
||||
else if (error.message.includes("Anthropic")) provider = "Anthropic";
|
||||
else if (error.message.includes("Ollama")) provider = "Ollama";
|
||||
|
||||
if (error.statusCode === 401 && error.message.toLowerCase().includes("api key")) {
|
||||
// Generic authentication error
|
||||
errorData = {
|
||||
status: 401,
|
||||
error: error.message,
|
||||
detail: {
|
||||
error: "Invalid OpenAI API key",
|
||||
message: "Please verify your OpenAI API key in Settings before starting a crawl.",
|
||||
error: `Invalid ${provider} API key`,
|
||||
message: `Please verify your ${provider} API key in Settings before starting a crawl.`,
|
||||
error_type: "authentication_failed",
|
||||
error_code: "OPENAI_AUTH_FAILED"
|
||||
error_code: `${provider.toUpperCase()}_AUTH_FAILED`,
|
||||
provider: provider.toLowerCase()
|
||||
}
|
||||
};
|
||||
} else if (error.statusCode === 429 && error.message.includes("quota")) {
|
||||
// This is our OpenAI quota error
|
||||
} else if (error.statusCode === 429 && error.message.toLowerCase().includes("quota")) {
|
||||
// Generic quota error
|
||||
errorData = {
|
||||
status: 429,
|
||||
error: error.message,
|
||||
detail: {
|
||||
error: "OpenAI quota exhausted",
|
||||
message: "Your OpenAI API key has no remaining credits. Please add credits to your account.",
|
||||
error: `${provider} quota exhausted`,
|
||||
message: `Your ${provider} API quota has been exceeded. Please check your billing settings.`,
|
||||
error_type: "quota_exhausted",
|
||||
error_code: "OPENAI_QUOTA_EXHAUSTED"
|
||||
error_code: `${provider.toUpperCase()}_QUOTA_EXHAUSTED`,
|
||||
provider: provider.toLowerCase()
|
||||
}
|
||||
};
|
||||
} else if (error.statusCode === 429 && error.message.includes("rate limit")) {
|
||||
// This is our rate limit error
|
||||
} else if (error.statusCode === 429 && error.message.toLowerCase().includes("rate limit")) {
|
||||
// Generic rate limit error
|
||||
errorData = {
|
||||
status: 429,
|
||||
error: error.message,
|
||||
detail: {
|
||||
error: "OpenAI API rate limit exceeded",
|
||||
message: "Too many requests to OpenAI API. Please wait a moment and try again.",
|
||||
error: `${provider} API rate limit exceeded`,
|
||||
message: `Too many requests to ${provider} API. Please wait a moment and try again.`,
|
||||
error_type: "rate_limit",
|
||||
error_code: "OPENAI_RATE_LIMIT"
|
||||
error_code: `${provider.toUpperCase()}_RATE_LIMIT`,
|
||||
provider: provider.toLowerCase()
|
||||
}
|
||||
};
|
||||
} else if (error.statusCode === 502 && error.message.includes("OpenAI")) {
|
||||
// This is our generic API error
|
||||
} else if (error.statusCode === 502 && (error.message.toLowerCase().includes("api") || error.message.includes(provider))) {
|
||||
// Generic API error
|
||||
errorData = {
|
||||
status: 502,
|
||||
error: error.message,
|
||||
detail: {
|
||||
error: "OpenAI API error",
|
||||
message: "OpenAI API error. Please check your API key configuration.",
|
||||
error: `${provider} API error`,
|
||||
message: `${provider} API error. Please check your API key configuration.`,
|
||||
error_type: "api_error",
|
||||
error_code: "OPENAI_API_ERROR"
|
||||
error_code: `${provider.toUpperCase()}_API_ERROR`,
|
||||
provider: provider.toLowerCase()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
* by displaying clear error messages when OpenAI API fails.
|
||||
*/
|
||||
|
||||
export interface OpenAIErrorDetails {
|
||||
export interface ProviderErrorDetails {
|
||||
error: string;
|
||||
message: string;
|
||||
error_type: 'quota_exhausted' | 'rate_limit' | 'api_error' | 'authentication_failed' | 'timeout_error' | 'configuration_error';
|
||||
error_code?: string; // Structured error code for reliable detection
|
||||
provider?: string; // LLM provider (openai, google, anthropic, ollama)
|
||||
tokens_used?: number;
|
||||
retry_after?: number;
|
||||
api_key_prefix?: string;
|
||||
@@ -20,8 +21,8 @@ export interface OpenAIErrorDetails {
|
||||
|
||||
export interface EnhancedError extends Error {
|
||||
statusCode?: number;
|
||||
errorDetails?: OpenAIErrorDetails;
|
||||
isOpenAIError?: boolean;
|
||||
errorDetails?: ProviderErrorDetails;
|
||||
isProviderError?: boolean; // Renamed from isOpenAIError for genericity
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,10 +105,10 @@ export function parseKnowledgeBaseError(error: any): EnhancedError {
|
||||
// Prioritize error.detail (where we put structured OpenAI error data)
|
||||
const errorData = error.detail || error.error;
|
||||
|
||||
// Check if it's an OpenAI-specific error
|
||||
// Check if it's a provider-specific error
|
||||
if (typeof errorData === 'object' && errorData?.error_type) {
|
||||
enhancedError.isOpenAIError = true;
|
||||
enhancedError.errorDetails = errorData as OpenAIErrorDetails;
|
||||
enhancedError.isProviderError = true;
|
||||
enhancedError.errorDetails = errorData as ProviderErrorDetails;
|
||||
|
||||
// Override the message with the detailed error message
|
||||
enhancedError.message = errorData.message || errorData.error || enhancedError.message;
|
||||
@@ -122,25 +123,26 @@ export function parseKnowledgeBaseError(error: any): EnhancedError {
|
||||
* Get user-friendly error message for display in UI
|
||||
*/
|
||||
export function getDisplayErrorMessage(error: EnhancedError): string {
|
||||
if (error.isOpenAIError && error.errorDetails) {
|
||||
if (error.isProviderError && error.errorDetails) {
|
||||
const provider = error.errorDetails.provider ? error.errorDetails.provider.charAt(0).toUpperCase() + error.errorDetails.provider.slice(1) : 'LLM';
|
||||
switch (error.errorDetails.error_type) {
|
||||
case 'quota_exhausted':
|
||||
return `OpenAI API quota exhausted. Please add credits to your OpenAI account or check your billing settings.`;
|
||||
return `${provider} API quota exhausted. Please add credits to your ${provider} account or check your billing settings.`;
|
||||
|
||||
case 'rate_limit':
|
||||
return `OpenAI API rate limit exceeded. Please wait a moment and try again.`;
|
||||
return `${provider} API rate limit exceeded. Please wait a moment and try again.`;
|
||||
|
||||
case 'authentication_failed':
|
||||
return `Invalid or expired OpenAI API key. Please check your API key in settings.`;
|
||||
return `Invalid or expired ${provider} API key. Please check your API key in settings.`;
|
||||
|
||||
case 'api_error':
|
||||
return `OpenAI API error: ${error.errorDetails.message}. Please check your API key configuration.`;
|
||||
return `${provider} API error: ${error.errorDetails.message}. Please check your API key configuration.`;
|
||||
|
||||
case 'timeout_error':
|
||||
return `Request timed out. Please try again or check your network connection.`;
|
||||
|
||||
case 'configuration_error':
|
||||
return `OpenAI API configuration error. Please check your API key settings.`;
|
||||
return `${provider} API configuration error. Please check your API key settings.`;
|
||||
|
||||
default:
|
||||
return error.errorDetails.message || error.message;
|
||||
@@ -170,7 +172,7 @@ export function getDisplayErrorMessage(error: EnhancedError): string {
|
||||
* Get error severity level for UI styling
|
||||
*/
|
||||
export function getErrorSeverity(error: EnhancedError): 'error' | 'warning' | 'info' {
|
||||
if (error.isOpenAIError && error.errorDetails) {
|
||||
if (error.isProviderError && error.errorDetails) {
|
||||
switch (error.errorDetails.error_type) {
|
||||
case 'quota_exhausted':
|
||||
return 'error'; // Critical - user action required
|
||||
@@ -200,12 +202,13 @@ export function getErrorSeverity(error: EnhancedError): 'error' | 'warning' | 'i
|
||||
* Get suggested action for the user based on error type
|
||||
*/
|
||||
export function getErrorAction(error: EnhancedError): string | null {
|
||||
if (error.isOpenAIError && error.errorDetails) {
|
||||
if (error.isProviderError && error.errorDetails) {
|
||||
const provider = error.errorDetails.provider ? error.errorDetails.provider.charAt(0).toUpperCase() + error.errorDetails.provider.slice(1) : 'LLM';
|
||||
switch (error.errorDetails.error_type) {
|
||||
case 'quota_exhausted':
|
||||
return 'Check your OpenAI billing dashboard and add credits';
|
||||
return `Check your ${provider} billing dashboard and add credits`;
|
||||
case 'authentication_failed':
|
||||
return 'Verify your OpenAI API key in Settings';
|
||||
return `Verify your ${provider} API key in Settings`;
|
||||
case 'rate_limit':
|
||||
const retryAfter = error.errorDetails.retry_after;
|
||||
if (retryAfter && retryAfter > 0) {
|
||||
@@ -214,11 +217,11 @@ export function getErrorAction(error: EnhancedError): string | null {
|
||||
return 'Wait a moment and try again';
|
||||
}
|
||||
case 'api_error':
|
||||
return 'Verify your OpenAI API key in Settings';
|
||||
return `Verify your ${provider} API key in Settings`;
|
||||
case 'timeout_error':
|
||||
return 'Check your network connection and try again';
|
||||
case 'configuration_error':
|
||||
return 'Check your OpenAI API key in Settings';
|
||||
return `Check your ${provider} API key in Settings`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user