-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patherror-handling.ts
More file actions
203 lines (174 loc) · 5.78 KB
/
error-handling.ts
File metadata and controls
203 lines (174 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/**
* Error Handling & Graceful Degradation Example
*
* This example demonstrates robust error handling patterns for autonomous agents:
* 1. Tool-level error recovery with retries
* 2. Circuit breaker pattern for failing tools
* 3. Graceful degradation when tools are unavailable
* 4. Structured error logging for debugging
*/
import { Agent, agentLoop, BashTool, FileReadTool, WebFetchTool } from "@avasis-ai/synthcode";
import { anthropic } from "@avasis-ai/synthcode/llm";
import { InMemoryStore } from "@avasis-ai/synthcode/memory";
import { ToolVerifier } from "@avasis-ai/synthcode/tools";
interface ErrorContext {
toolName: string;
timestamp: number;
error: Error;
retryCount: number;
maxRetries: number;
}
/**
* Simple error logger for autonomous operation
*/
class ErrorLogger {
private errors: ErrorContext[] = [];
private maxLogSize = 100;
log(context: ErrorContext) {
this.errors.push(context);
if (this.errors.length > this.maxLogSize) {
this.errors.shift();
}
console.error(`[${context.toolName}] ${context.error.message} (attempt ${context.retryCount}/${context.maxRetries})`);
}
getErrors(toolName?: string): ErrorContext[] {
if (toolName) {
return this.errors.filter(e => e.toolName === toolName);
}
return [...this.errors];
}
clear() {
this.errors = [];
}
}
/**
* Circuit breaker pattern - temporarily disables tools that fail repeatedly
*/
class CircuitBreaker {
private failures: Map<string, number> = new Map();
private lastFailureTime: Map<string, number> = new Map();
private threshold = 3;
private timeout = 60000; // 1 minute cooldown
shouldAllow(toolName: string): boolean {
const failures = this.failures.get(toolName) || 0;
const lastFailure = this.lastFailureTime.get(toolName) || 0;
const now = Date.now();
// Reset after timeout
if (failures >= this.threshold && now - lastFailure > this.timeout) {
this.failures.delete(toolName);
this.lastFailureTime.delete(toolName);
return true;
}
// Block if threshold exceeded
if (failures >= this.threshold) {
console.warn(`[CircuitBreaker] Tool ${toolName} is temporarily blocked due to repeated failures`);
return false;
}
return true;
}
recordFailure(toolName: string) {
const failures = (this.failures.get(toolName) || 0) + 1;
this.failures.set(toolName, failures);
this.lastFailureTime.set(toolName, Date.now());
}
recordSuccess(toolName: string) {
this.failures.delete(toolName);
this.lastFailureTime.delete(toolName);
}
}
/**
* Wrap a tool with retry logic and circuit breaking
*/
function createResilientTool(
tool: any,
logger: ErrorLogger,
breaker: CircuitBreaker,
maxRetries: number = 3
): any {
const originalExecute = tool.execute.bind(tool);
return {
...tool,
async execute(...args: any[]) {
if (!breaker.shouldAllow(tool.name)) {
throw new Error(`Tool ${tool.name} is temporarily unavailable (circuit breaker tripped)`);
}
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await originalExecute(...args);
breaker.recordSuccess(tool.name);
return result;
} catch (error) {
lastError = error as Error;
logger.log({
toolName: tool.name,
timestamp: Date.now(),
error: lastError,
retryCount: attempt,
maxRetries,
});
// Don't retry on certain errors
if (lastError.message.includes("permission denied") ||
lastError.message.includes("not found")) {
break;
}
// Exponential backoff
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 100;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
breaker.recordFailure(tool.name);
throw lastError;
},
};
}
/**
* Main example - autonomous agent with error resilience
*/
async function main() {
const logger = new ErrorLogger();
const breaker = new CircuitBreaker();
const memory = new InMemoryStore();
// Create tools with resilience wrapper
const bashTool = createResilientTool(new BashTool(), logger, breaker, 3);
const fileTool = createResilientTool(new FileReadTool(), logger, breaker, 2);
const webTool = createResilientTool(new WebFetchTool(), logger, breaker, 2);
const agent = new Agent({
model: anthropic("claude-3-5-sonnet-20241022"),
tools: [bashTool, fileTool, webTool],
systemPrompt: `You are an autonomous agent with robust error handling.
When a tool fails:
1. Try to understand the error message
2. Attempt recovery (e.g., create missing directories, use alternative tools)
3. If a tool is unavailable (circuit breaker), skip that task or use alternatives
4. Log all errors for debugging
Be resilient. Some failures are expected. Focus on what you can accomplish.`,
memory,
});
// Run autonomous loop with graceful error handling
try {
const result = await agentLoop(agent, {
userInput: "Check the current directory structure, create a summary, and save it to workspace/log.txt",
maxTurns: 10,
});
console.log("\n=== Task Complete ===");
console.log(`Final status: ${result.done ? "Success" : "Incomplete"}`);
// Log errors that occurred
const errors = logger.getErrors();
if (errors.length > 0) {
console.log(`\n=== Errors Encountered (${errors.length}) ===`);
errors.forEach(err => {
console.log(`- ${err.toolName}: ${err.error.message}`);
});
}
} catch (error) {
console.error("\n=== Fatal Error ===");
console.error(error);
process.exit(1);
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}