mirror of
https://github.com/Nikhil-Doye/workflow-builder.git
synced 2026-07-22 02:01:56 +02:00
Enhance error handling and reporting in WorkflowAgent and AgentManager
- Improved error handling in WorkflowAgent and AgentManager to include detailed error context, allowing for better debugging and user feedback. - Introduced a new DetailedError class to encapsulate error information, including stage, tool name, input parameters, and suggestions for resolution. - Updated methods to utilize the new error handling structure, ensuring that errors are logged with comprehensive context. - Added utility functions for formatting error messages for both users and developers, enhancing the overall error reporting mechanism. - Enhanced the workflow store to log detailed error context when generating workflows, improving traceability of issues.
This commit is contained in:
@@ -92,12 +92,20 @@ export class AgentManager {
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in AgentManager:", error);
|
||||
|
||||
// Preserve error context if available
|
||||
const errorContext =
|
||||
error instanceof Error && "errorContext" in error
|
||||
? (error as any).errorContext
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
toolsUsed: [],
|
||||
executionTime: 0,
|
||||
confidence: 0.0,
|
||||
errorContext,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
AgentResult,
|
||||
AgentContext,
|
||||
ToolExecutionPlan,
|
||||
ErrorContext,
|
||||
} from "../../types/tools";
|
||||
import { toolRegistry } from "../tools/ToolRegistry";
|
||||
import { ParsedIntent } from "../../types";
|
||||
@@ -16,80 +17,130 @@ export class WorkflowAgent {
|
||||
async processRequest(userInput: string): Promise<AgentResult> {
|
||||
const startTime = Date.now();
|
||||
const toolsUsed: string[] = [];
|
||||
const partialResults: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
// Step 1: Check cache first
|
||||
const cacheTool = toolRegistry.getTool("cache_lookup");
|
||||
if (cacheTool) {
|
||||
const cacheKey = this.generateCacheKey(userInput);
|
||||
const cacheResult = await cacheTool.execute({ key: cacheKey });
|
||||
try {
|
||||
const cacheKey = this.generateCacheKey(userInput);
|
||||
const cacheResult = await cacheTool.execute({ key: cacheKey });
|
||||
|
||||
if (cacheResult.success && cacheResult.data) {
|
||||
return {
|
||||
success: true,
|
||||
data: cacheResult.data,
|
||||
toolsUsed: ["cache_lookup"],
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: 0.9,
|
||||
};
|
||||
if (cacheResult.success && cacheResult.data) {
|
||||
return {
|
||||
success: true,
|
||||
data: cacheResult.data,
|
||||
toolsUsed: ["cache_lookup"],
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: 0.9,
|
||||
};
|
||||
}
|
||||
toolsUsed.push("cache_lookup");
|
||||
} catch (error) {
|
||||
console.warn("Cache lookup failed, continuing without cache:", error);
|
||||
// Continue execution without cache
|
||||
}
|
||||
toolsUsed.push("cache_lookup");
|
||||
}
|
||||
|
||||
// Step 2: Classify intent
|
||||
const intentResult = await this.executeTool("classify_intent", {
|
||||
userInput,
|
||||
});
|
||||
const intentResult = await this.executeToolWithContext(
|
||||
"classify_intent",
|
||||
{ userInput },
|
||||
"intent_classification",
|
||||
partialResults
|
||||
);
|
||||
|
||||
if (!intentResult.success) {
|
||||
throw new Error("Failed to classify intent");
|
||||
throw this.createDetailedError(
|
||||
"Failed to classify intent",
|
||||
"intent_classification",
|
||||
"classify_intent",
|
||||
{ userInput },
|
||||
partialResults,
|
||||
"tool_execution_failure",
|
||||
userInput
|
||||
);
|
||||
}
|
||||
toolsUsed.push("classify_intent");
|
||||
partialResults.intent = intentResult.data;
|
||||
|
||||
// Step 3: Extract entities
|
||||
const entitiesResult = await this.executeTool("extract_entities", {
|
||||
userInput,
|
||||
intent: intentResult.data,
|
||||
});
|
||||
const entitiesResult = await this.executeToolWithContext(
|
||||
"extract_entities",
|
||||
{ userInput, intent: intentResult.data },
|
||||
"entity_extraction",
|
||||
partialResults
|
||||
);
|
||||
|
||||
if (!entitiesResult.success) {
|
||||
throw new Error("Failed to extract entities");
|
||||
throw this.createDetailedError(
|
||||
"Failed to extract entities",
|
||||
"entity_extraction",
|
||||
"extract_entities",
|
||||
{ userInput, intent: intentResult.data },
|
||||
partialResults,
|
||||
"tool_execution_failure",
|
||||
userInput
|
||||
);
|
||||
}
|
||||
toolsUsed.push("extract_entities");
|
||||
partialResults.entities = entitiesResult.data;
|
||||
|
||||
// Step 4: Generate workflow
|
||||
const workflowResult = await this.executeTool("generate_workflow", {
|
||||
userInput,
|
||||
intent: intentResult.data,
|
||||
entities: entitiesResult.data,
|
||||
});
|
||||
const workflowResult = await this.executeToolWithContext(
|
||||
"generate_workflow",
|
||||
{ userInput, intent: intentResult.data, entities: entitiesResult.data },
|
||||
"workflow_generation",
|
||||
partialResults
|
||||
);
|
||||
|
||||
if (!workflowResult.success) {
|
||||
throw new Error("Failed to generate workflow");
|
||||
throw this.createDetailedError(
|
||||
"Failed to generate workflow",
|
||||
"workflow_generation",
|
||||
"generate_workflow",
|
||||
{
|
||||
userInput,
|
||||
intent: intentResult.data,
|
||||
entities: entitiesResult.data,
|
||||
},
|
||||
partialResults,
|
||||
"tool_execution_failure",
|
||||
userInput
|
||||
);
|
||||
}
|
||||
toolsUsed.push("generate_workflow");
|
||||
partialResults.workflow = workflowResult.data;
|
||||
|
||||
// Step 5: Validate workflow
|
||||
const validationResult = await this.executeTool("validate_workflow", {
|
||||
workflow: workflowResult.data,
|
||||
originalInput: userInput,
|
||||
});
|
||||
const validationResult = await this.executeToolWithContext(
|
||||
"validate_workflow",
|
||||
{ workflow: workflowResult.data, originalInput: userInput },
|
||||
"workflow_validation",
|
||||
partialResults
|
||||
);
|
||||
|
||||
if (!validationResult.success) {
|
||||
console.warn(
|
||||
"Workflow validation failed, but continuing with generated workflow"
|
||||
);
|
||||
// Don't throw error for validation failures, just log warning
|
||||
}
|
||||
toolsUsed.push("validate_workflow");
|
||||
partialResults.validation = validationResult.data;
|
||||
|
||||
// Step 6: Generate suggestions
|
||||
const suggestionsResult = await this.executeTool("generate_suggestions", {
|
||||
workflow: workflowResult.data,
|
||||
context: userInput,
|
||||
});
|
||||
const suggestionsResult = await this.executeToolWithContext(
|
||||
"generate_suggestions",
|
||||
{ workflow: workflowResult.data, context: userInput },
|
||||
"suggestion_generation",
|
||||
partialResults
|
||||
);
|
||||
|
||||
if (suggestionsResult.success) {
|
||||
toolsUsed.push("generate_suggestions");
|
||||
partialResults.suggestions = suggestionsResult.data;
|
||||
}
|
||||
|
||||
// Create final result
|
||||
@@ -122,12 +173,28 @@ export class WorkflowAgent {
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error in WorkflowAgent:", error);
|
||||
|
||||
// If it's already a detailed error, use it; otherwise create one
|
||||
const errorContext =
|
||||
error instanceof DetailedError
|
||||
? error.errorContext
|
||||
: this.createErrorContext(
|
||||
"unknown",
|
||||
undefined,
|
||||
{ userInput },
|
||||
partialResults,
|
||||
"unknown",
|
||||
userInput,
|
||||
error instanceof Error ? error.stack : undefined
|
||||
);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
toolsUsed,
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: 0.0,
|
||||
errorContext,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -149,6 +216,170 @@ export class WorkflowAgent {
|
||||
return result;
|
||||
}
|
||||
|
||||
private async executeToolWithContext(
|
||||
toolName: string,
|
||||
params: Record<string, any>,
|
||||
stage: string,
|
||||
partialResults: Record<string, any>
|
||||
): Promise<any> {
|
||||
try {
|
||||
const tool = toolRegistry.getTool(toolName);
|
||||
if (!tool) {
|
||||
throw this.createDetailedError(
|
||||
`Tool not found: ${toolName}`,
|
||||
stage,
|
||||
toolName,
|
||||
params,
|
||||
partialResults,
|
||||
"tool_registry_miss",
|
||||
this.context.userRequest
|
||||
);
|
||||
}
|
||||
|
||||
const result = await tool.execute(params);
|
||||
if (!result.success) {
|
||||
throw this.createDetailedError(
|
||||
`Tool ${toolName} failed: ${result.error}`,
|
||||
stage,
|
||||
toolName,
|
||||
params,
|
||||
partialResults,
|
||||
"tool_execution_failure",
|
||||
this.context.userRequest
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof DetailedError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Convert generic errors to detailed errors
|
||||
throw this.createDetailedError(
|
||||
error instanceof Error ? error.message : "Unknown tool execution error",
|
||||
stage,
|
||||
toolName,
|
||||
params,
|
||||
partialResults,
|
||||
"tool_execution_failure",
|
||||
this.context.userRequest,
|
||||
error instanceof Error ? error.stack : undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private createDetailedError(
|
||||
message: string,
|
||||
stage: string,
|
||||
toolName: string | undefined,
|
||||
inputParameters: Record<string, any>,
|
||||
partialResults: Record<string, any>,
|
||||
errorType: ErrorContext["errorType"],
|
||||
userInput: string,
|
||||
stackTrace?: string
|
||||
): DetailedError {
|
||||
const errorContext = this.createErrorContext(
|
||||
stage,
|
||||
toolName,
|
||||
inputParameters,
|
||||
partialResults,
|
||||
errorType,
|
||||
userInput,
|
||||
stackTrace
|
||||
);
|
||||
|
||||
return new DetailedError(message, errorContext);
|
||||
}
|
||||
|
||||
private createErrorContext(
|
||||
stage: string,
|
||||
toolName: string | undefined,
|
||||
inputParameters: Record<string, any>,
|
||||
partialResults: Record<string, any>,
|
||||
errorType: ErrorContext["errorType"],
|
||||
userInput: string,
|
||||
stackTrace?: string
|
||||
): ErrorContext {
|
||||
return {
|
||||
stage,
|
||||
toolName,
|
||||
inputParameters,
|
||||
partialResults,
|
||||
errorType,
|
||||
timestamp: Date.now(),
|
||||
sessionId: this.context.sessionId,
|
||||
userInput,
|
||||
stackTrace,
|
||||
suggestions: this.generateErrorSuggestions(
|
||||
stage,
|
||||
errorType,
|
||||
partialResults
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private generateErrorSuggestions(
|
||||
stage: string,
|
||||
errorType: ErrorContext["errorType"],
|
||||
partialResults: Record<string, any>
|
||||
): string[] {
|
||||
const suggestions: string[] = [];
|
||||
|
||||
switch (errorType) {
|
||||
case "tool_registry_miss":
|
||||
suggestions.push("Check if the required tool is properly registered");
|
||||
suggestions.push("Verify tool name spelling and availability");
|
||||
break;
|
||||
|
||||
case "tool_execution_failure":
|
||||
suggestions.push("Check tool configuration and parameters");
|
||||
suggestions.push("Verify external service availability (OpenAI, etc.)");
|
||||
if (stage === "intent_classification") {
|
||||
suggestions.push("Try rephrasing your request with clearer intent");
|
||||
} else if (stage === "entity_extraction") {
|
||||
suggestions.push(
|
||||
"Ensure your input contains recognizable entities (URLs, data types, etc.)"
|
||||
);
|
||||
} else if (stage === "workflow_generation") {
|
||||
suggestions.push(
|
||||
"Try breaking down complex requests into simpler steps"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "llm_error":
|
||||
suggestions.push("Check API key configuration and quota limits");
|
||||
suggestions.push("Try reducing request complexity or length");
|
||||
suggestions.push("Verify network connectivity to AI services");
|
||||
break;
|
||||
|
||||
case "validation_error":
|
||||
suggestions.push(
|
||||
"Review generated workflow structure for completeness"
|
||||
);
|
||||
suggestions.push("Check node configurations and connections");
|
||||
break;
|
||||
|
||||
case "cache_error":
|
||||
suggestions.push("Cache error is non-critical, workflow will continue");
|
||||
break;
|
||||
|
||||
default:
|
||||
suggestions.push("Check system logs for additional error details");
|
||||
suggestions.push("Try the request again after a brief delay");
|
||||
}
|
||||
|
||||
// Add stage-specific suggestions
|
||||
if (Object.keys(partialResults).length > 0) {
|
||||
suggestions.push(
|
||||
"Partial results available - some processing completed successfully"
|
||||
);
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private generateCacheKey(userInput: string): string {
|
||||
return userInput.toLowerCase().trim().replace(/\s+/g, "_");
|
||||
}
|
||||
@@ -240,3 +471,16 @@ export class WorkflowAgent {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom error class that includes detailed execution context
|
||||
*/
|
||||
class DetailedError extends Error {
|
||||
public readonly errorContext: ErrorContext;
|
||||
|
||||
constructor(message: string, errorContext: ErrorContext) {
|
||||
super(message);
|
||||
this.name = "DetailedError";
|
||||
this.errorContext = errorContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,6 +835,22 @@ export const useWorkflowStore = create<WorkflowStore>((set, get) => ({
|
||||
saveWorkflowsToStorage(newWorkflows);
|
||||
} catch (error) {
|
||||
console.error("Error generating workflow from description:", error);
|
||||
|
||||
// Enhanced error handling with detailed context
|
||||
if (error instanceof Error && "errorContext" in error) {
|
||||
const errorContext = (error as any).errorContext;
|
||||
console.error("Detailed error context:", {
|
||||
stage: errorContext.stage,
|
||||
toolName: errorContext.toolName,
|
||||
errorType: errorContext.errorType,
|
||||
suggestions: errorContext.suggestions,
|
||||
partialResults: Object.keys(errorContext.partialResults || {}),
|
||||
});
|
||||
|
||||
// You could also dispatch an error event with detailed context here
|
||||
// for better user experience and debugging
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -59,6 +59,26 @@ export interface AgentResult {
|
||||
toolsUsed: string[];
|
||||
executionTime: number;
|
||||
confidence: number;
|
||||
errorContext?: ErrorContext;
|
||||
}
|
||||
|
||||
export interface ErrorContext {
|
||||
stage: string;
|
||||
toolName?: string;
|
||||
inputParameters?: Record<string, any>;
|
||||
partialResults?: Record<string, any>;
|
||||
errorType:
|
||||
| "tool_registry_miss"
|
||||
| "tool_execution_failure"
|
||||
| "llm_error"
|
||||
| "validation_error"
|
||||
| "cache_error"
|
||||
| "unknown";
|
||||
timestamp: number;
|
||||
sessionId: string;
|
||||
userInput: string;
|
||||
stackTrace?: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
export interface AgentContext {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import { ErrorContext } from "../types/tools";
|
||||
|
||||
/**
|
||||
* Utility functions for enhanced error handling and debugging
|
||||
*/
|
||||
export class ErrorReportingUtils {
|
||||
/**
|
||||
* Format error context for user-friendly display
|
||||
*/
|
||||
static formatErrorForUser(errorContext: ErrorContext): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`❌ Error occurred during ${errorContext.stage}`);
|
||||
|
||||
if (errorContext.toolName) {
|
||||
lines.push(`🔧 Tool: ${errorContext.toolName}`);
|
||||
}
|
||||
|
||||
lines.push(`📅 Time: ${new Date(errorContext.timestamp).toLocaleString()}`);
|
||||
lines.push(`🆔 Session: ${errorContext.sessionId}`);
|
||||
|
||||
if (errorContext.suggestions && errorContext.suggestions.length > 0) {
|
||||
lines.push("\n💡 Suggestions:");
|
||||
errorContext.suggestions.forEach((suggestion, index) => {
|
||||
lines.push(` ${index + 1}. ${suggestion}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
errorContext.partialResults &&
|
||||
Object.keys(errorContext.partialResults).length > 0
|
||||
) {
|
||||
lines.push("\n📊 Partial Results Available:");
|
||||
Object.keys(errorContext.partialResults).forEach((key) => {
|
||||
lines.push(` • ${key}: ${typeof errorContext.partialResults![key]}`);
|
||||
});
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error context for developer debugging
|
||||
*/
|
||||
static formatErrorForDeveloper(errorContext: ErrorContext): string {
|
||||
return JSON.stringify(
|
||||
{
|
||||
stage: errorContext.stage,
|
||||
toolName: errorContext.toolName,
|
||||
errorType: errorContext.errorType,
|
||||
inputParameters: errorContext.inputParameters,
|
||||
partialResults: errorContext.partialResults,
|
||||
timestamp: errorContext.timestamp,
|
||||
sessionId: errorContext.sessionId,
|
||||
userInput: errorContext.userInput,
|
||||
stackTrace: errorContext.stackTrace,
|
||||
suggestions: errorContext.suggestions,
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error is recoverable based on context
|
||||
*/
|
||||
static isRecoverableError(errorContext: ErrorContext): boolean {
|
||||
const recoverableTypes = ["cache_error", "validation_error"];
|
||||
return recoverableTypes.includes(errorContext.errorType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get retry recommendation based on error type
|
||||
*/
|
||||
static getRetryRecommendation(errorContext: ErrorContext): {
|
||||
shouldRetry: boolean;
|
||||
delayMs: number;
|
||||
reason: string;
|
||||
} {
|
||||
switch (errorContext.errorType) {
|
||||
case "cache_error":
|
||||
return {
|
||||
shouldRetry: true,
|
||||
delayMs: 0,
|
||||
reason: "Cache error is non-critical",
|
||||
};
|
||||
|
||||
case "llm_error":
|
||||
return {
|
||||
shouldRetry: true,
|
||||
delayMs: 2000,
|
||||
reason: "LLM service may be temporarily unavailable",
|
||||
};
|
||||
|
||||
case "tool_execution_failure":
|
||||
return {
|
||||
shouldRetry: true,
|
||||
delayMs: 1000,
|
||||
reason: "Tool execution may succeed on retry",
|
||||
};
|
||||
|
||||
case "tool_registry_miss":
|
||||
return {
|
||||
shouldRetry: false,
|
||||
delayMs: 0,
|
||||
reason: "Tool registration issue requires system fix",
|
||||
};
|
||||
|
||||
case "validation_error":
|
||||
return {
|
||||
shouldRetry: false,
|
||||
delayMs: 0,
|
||||
reason: "Validation error requires user input correction",
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
shouldRetry: true,
|
||||
delayMs: 5000,
|
||||
reason: "Unknown error - retry with delay",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate error report for support team
|
||||
*/
|
||||
static generateSupportReport(errorContext: ErrorContext): {
|
||||
summary: string;
|
||||
technicalDetails: any;
|
||||
userImpact: string;
|
||||
recommendedActions: string[];
|
||||
} {
|
||||
const summary = `Error in ${errorContext.stage} during ${errorContext.errorType}`;
|
||||
|
||||
const technicalDetails = {
|
||||
stage: errorContext.stage,
|
||||
toolName: errorContext.toolName,
|
||||
errorType: errorContext.errorType,
|
||||
timestamp: errorContext.timestamp,
|
||||
sessionId: errorContext.sessionId,
|
||||
stackTrace: errorContext.stackTrace,
|
||||
};
|
||||
|
||||
let userImpact = "Workflow generation failed";
|
||||
if (
|
||||
errorContext.partialResults &&
|
||||
Object.keys(errorContext.partialResults).length > 0
|
||||
) {
|
||||
userImpact =
|
||||
"Partial workflow generated - some steps completed successfully";
|
||||
}
|
||||
|
||||
const recommendedActions = [
|
||||
...(errorContext.suggestions || []),
|
||||
"Check system logs for additional context",
|
||||
"Verify external service availability",
|
||||
];
|
||||
|
||||
return {
|
||||
summary,
|
||||
technicalDetails,
|
||||
userImpact,
|
||||
recommendedActions,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user