@falai/agent 0.9.0-alpha-2 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -34
- package/dist/cjs/src/core/Agent.d.ts +48 -44
- package/dist/cjs/src/core/Agent.d.ts.map +1 -1
- package/dist/cjs/src/core/Agent.js +151 -1110
- package/dist/cjs/src/core/Agent.js.map +1 -1
- package/dist/cjs/src/core/ResponseModal.d.ts +211 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponseModal.js +1394 -0
- package/dist/cjs/src/core/ResponseModal.js.map +1 -0
- package/dist/cjs/src/core/ResponsePipeline.d.ts +8 -4
- package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponsePipeline.js +48 -20
- package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/src/core/Route.d.ts +12 -5
- package/dist/cjs/src/core/Route.d.ts.map +1 -1
- package/dist/cjs/src/core/Route.js +26 -5
- package/dist/cjs/src/core/Route.js.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.d.ts +5 -0
- package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.js +37 -25
- package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/src/core/SessionManager.d.ts +9 -1
- package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/src/core/SessionManager.js +27 -5
- package/dist/cjs/src/core/SessionManager.js.map +1 -1
- package/dist/cjs/src/core/Step.d.ts +60 -7
- package/dist/cjs/src/core/Step.d.ts.map +1 -1
- package/dist/cjs/src/core/Step.js +151 -4
- package/dist/cjs/src/core/Step.js.map +1 -1
- package/dist/cjs/src/core/ToolManager.d.ts +234 -0
- package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
- package/dist/cjs/src/core/ToolManager.js +1117 -0
- package/dist/cjs/src/core/ToolManager.js.map +1 -0
- package/dist/cjs/src/index.d.ts +5 -4
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +11 -3
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +2 -1
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/ai.d.ts +1 -1
- package/dist/cjs/src/types/ai.d.ts.map +1 -1
- package/dist/cjs/src/types/index.d.ts +3 -2
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js +3 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/types/route.d.ts +6 -4
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.d.ts +84 -14
- package/dist/cjs/src/types/tool.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.js +13 -0
- package/dist/cjs/src/types/tool.js.map +1 -1
- package/dist/cjs/src/utils/clone.d.ts.map +1 -1
- package/dist/cjs/src/utils/clone.js +0 -4
- package/dist/cjs/src/utils/clone.js.map +1 -1
- package/dist/cjs/src/utils/history.d.ts +30 -1
- package/dist/cjs/src/utils/history.d.ts.map +1 -1
- package/dist/cjs/src/utils/history.js +169 -23
- package/dist/cjs/src/utils/history.js.map +1 -1
- package/dist/cjs/src/utils/index.d.ts +1 -1
- package/dist/cjs/src/utils/index.d.ts.map +1 -1
- package/dist/cjs/src/utils/index.js +5 -1
- package/dist/cjs/src/utils/index.js.map +1 -1
- package/dist/src/core/Agent.d.ts +48 -44
- package/dist/src/core/Agent.d.ts.map +1 -1
- package/dist/src/core/Agent.js +152 -1111
- package/dist/src/core/Agent.js.map +1 -1
- package/dist/src/core/ResponseModal.d.ts +211 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/src/core/ResponseModal.js +1389 -0
- package/dist/src/core/ResponseModal.js.map +1 -0
- package/dist/src/core/ResponsePipeline.d.ts +8 -4
- package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/src/core/ResponsePipeline.js +48 -20
- package/dist/src/core/ResponsePipeline.js.map +1 -1
- package/dist/src/core/Route.d.ts +12 -5
- package/dist/src/core/Route.d.ts.map +1 -1
- package/dist/src/core/Route.js +26 -5
- package/dist/src/core/Route.js.map +1 -1
- package/dist/src/core/RoutingEngine.d.ts +5 -0
- package/dist/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/src/core/RoutingEngine.js +37 -25
- package/dist/src/core/RoutingEngine.js.map +1 -1
- package/dist/src/core/SessionManager.d.ts +9 -1
- package/dist/src/core/SessionManager.d.ts.map +1 -1
- package/dist/src/core/SessionManager.js +27 -5
- package/dist/src/core/SessionManager.js.map +1 -1
- package/dist/src/core/Step.d.ts +60 -7
- package/dist/src/core/Step.d.ts.map +1 -1
- package/dist/src/core/Step.js +151 -4
- package/dist/src/core/Step.js.map +1 -1
- package/dist/src/core/ToolManager.d.ts +234 -0
- package/dist/src/core/ToolManager.d.ts.map +1 -0
- package/dist/src/core/ToolManager.js +1111 -0
- package/dist/src/core/ToolManager.js.map +1 -0
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/agent.d.ts +2 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/ai.d.ts +1 -1
- package/dist/src/types/ai.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +3 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/route.d.ts +6 -4
- package/dist/src/types/route.d.ts.map +1 -1
- package/dist/src/types/tool.d.ts +84 -14
- package/dist/src/types/tool.d.ts.map +1 -1
- package/dist/src/types/tool.js +12 -1
- package/dist/src/types/tool.js.map +1 -1
- package/dist/src/utils/clone.d.ts.map +1 -1
- package/dist/src/utils/clone.js +0 -4
- package/dist/src/utils/clone.js.map +1 -1
- package/dist/src/utils/history.d.ts +30 -1
- package/dist/src/utils/history.d.ts.map +1 -1
- package/dist/src/utils/history.js +165 -23
- package/dist/src/utils/history.js.map +1 -1
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/index.js.map +1 -1
- package/docs/CONTRIBUTING.md +40 -0
- package/docs/README.md +14 -6
- package/docs/api/README.md +235 -45
- package/docs/api/overview.md +140 -33
- package/docs/core/agent/session-management.md +152 -5
- package/docs/core/ai-integration/response-processing.md +115 -4
- package/docs/core/conversation-flows/routes.md +130 -0
- package/docs/core/error-handling.md +638 -0
- package/docs/core/tools/tool-definition.md +684 -60
- package/docs/core/tools/tool-scoping.md +244 -53
- package/docs/guides/error-handling-patterns.md +578 -0
- package/docs/guides/getting-started/README.md +139 -28
- package/docs/guides/migration/README.md +72 -0
- package/docs/guides/migration/response-modal-refactor.md +518 -0
- package/examples/advanced-patterns/knowledge-based-agent.ts +6 -6
- package/examples/advanced-patterns/persistent-onboarding.ts +30 -43
- package/examples/advanced-patterns/streaming-responses.ts +169 -96
- package/examples/ai-providers/anthropic-integration.ts +9 -5
- package/examples/ai-providers/openai-integration.ts +11 -7
- package/examples/core-concepts/basic-agent.ts +106 -67
- package/examples/core-concepts/modern-streaming-api.ts +309 -0
- package/examples/core-concepts/schema-driven-extraction.ts +10 -7
- package/examples/core-concepts/session-management.ts +71 -18
- package/examples/integrations/healthcare-integration.ts +15 -29
- package/examples/integrations/server-session-management.ts +3 -3
- package/examples/persistence/memory-sessions.ts +3 -3
- package/examples/tools/basic-tools.ts +293 -89
- package/examples/tools/data-enrichment-tools.ts +185 -75
- package/package.json +1 -1
- package/src/core/Agent.ts +190 -1529
- package/src/core/ResponseModal.ts +1798 -0
- package/src/core/ResponsePipeline.ts +83 -57
- package/src/core/Route.ts +39 -12
- package/src/core/RoutingEngine.ts +46 -42
- package/src/core/SessionManager.ts +39 -7
- package/src/core/Step.ts +198 -20
- package/src/core/ToolManager.ts +1394 -0
- package/src/index.ts +19 -3
- package/src/types/agent.ts +2 -1
- package/src/types/ai.ts +1 -1
- package/src/types/index.ts +13 -2
- package/src/types/route.ts +6 -4
- package/src/types/tool.ts +116 -25
- package/src/utils/clone.ts +6 -8
- package/src/utils/history.ts +190 -27
- package/src/utils/index.ts +4 -0
- package/dist/cjs/src/core/ToolExecutor.d.ts +0 -45
- package/dist/cjs/src/core/ToolExecutor.d.ts.map +0 -1
- package/dist/cjs/src/core/ToolExecutor.js +0 -84
- package/dist/cjs/src/core/ToolExecutor.js.map +0 -1
- package/dist/src/core/ToolExecutor.d.ts +0 -45
- package/dist/src/core/ToolExecutor.d.ts.map +0 -1
- package/dist/src/core/ToolExecutor.js +0 -80
- package/dist/src/core/ToolExecutor.js.map +0 -1
- package/docs/core/tools/tool-execution.md +0 -815
- package/src/core/ToolExecutor.ts +0 -126
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# Error Handling Patterns
|
|
2
|
+
|
|
3
|
+
This guide provides practical patterns and examples for handling errors in @fali/agent applications, covering streaming operations, route completion, and data synchronization scenarios.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Error Type | Pattern | Recovery Strategy |
|
|
8
|
+
|------------|---------|-------------------|
|
|
9
|
+
| Streaming Errors | Proper async generator error propagation | Catch and re-throw with context |
|
|
10
|
+
| Route Completion | Defensive completion checking | Fallback to incomplete state |
|
|
11
|
+
| Data Sync | Bidirectional rollback | Restore previous consistent state |
|
|
12
|
+
| Tool Execution | Graceful degradation | Fallback responses with error context |
|
|
13
|
+
| Validation | Schema + manual fallback | Progressive fallback strategies |
|
|
14
|
+
|
|
15
|
+
## Streaming Error Patterns
|
|
16
|
+
|
|
17
|
+
### Pattern 1: Provider Error Propagation
|
|
18
|
+
|
|
19
|
+
**Problem:** Streaming errors from AI providers need to be properly propagated to the application layer.
|
|
20
|
+
|
|
21
|
+
**Solution:**
|
|
22
|
+
```typescript
|
|
23
|
+
// ✅ Correct: Proper error propagation in streaming
|
|
24
|
+
async function* handleStreamingResponse(provider: AIProvider, prompt: string) {
|
|
25
|
+
try {
|
|
26
|
+
for await (const chunk of provider.generateMessageStream(prompt)) {
|
|
27
|
+
yield { success: true, data: chunk };
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// Log the error but let it propagate
|
|
31
|
+
console.error("Streaming error:", error.message);
|
|
32
|
+
throw error; // Important: re-throw to stop the stream
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Usage
|
|
37
|
+
try {
|
|
38
|
+
for await (const chunk of handleStreamingResponse(provider, prompt)) {
|
|
39
|
+
if (chunk.success) {
|
|
40
|
+
process.stdout.write(chunk.data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("Stream failed:", error.message);
|
|
45
|
+
// Handle the error appropriately
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Anti-pattern:**
|
|
50
|
+
```typescript
|
|
51
|
+
// ❌ Wrong: Swallowing streaming errors
|
|
52
|
+
async function* badStreamingHandler(provider: AIProvider, prompt: string) {
|
|
53
|
+
try {
|
|
54
|
+
for await (const chunk of provider.generateMessageStream(prompt)) {
|
|
55
|
+
yield chunk;
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Don't do this - error is lost
|
|
59
|
+
yield { error: "Something went wrong" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Pattern 2: MockProvider Testing
|
|
65
|
+
|
|
66
|
+
**Problem:** Testing streaming error scenarios requires proper error configuration.
|
|
67
|
+
|
|
68
|
+
**Solution:**
|
|
69
|
+
```typescript
|
|
70
|
+
// ✅ Correct: MockProvider with proper error testing
|
|
71
|
+
import { MockProvider } from "@falai/agent/testing";
|
|
72
|
+
|
|
73
|
+
describe("Streaming Error Handling", () => {
|
|
74
|
+
it("should propagate streaming errors correctly", async () => {
|
|
75
|
+
const mockProvider = new MockProvider({
|
|
76
|
+
streamingError: "Mock provider streaming error for testing"
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const agent = new Agent({ provider: mockProvider });
|
|
80
|
+
|
|
81
|
+
// Test that the exact error message is propagated
|
|
82
|
+
await expect(async () => {
|
|
83
|
+
for await (const chunk of agent.respondStream({ message: "test" })) {
|
|
84
|
+
// Should throw before yielding any chunks
|
|
85
|
+
}
|
|
86
|
+
}).rejects.toThrow("Mock provider streaming error for testing");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Route Completion Patterns
|
|
92
|
+
|
|
93
|
+
### Pattern 1: Defensive Completion Checking
|
|
94
|
+
|
|
95
|
+
**Problem:** Route completion logic needs to handle various edge cases including conditional steps and explicit termination.
|
|
96
|
+
|
|
97
|
+
**Solution:**
|
|
98
|
+
```typescript
|
|
99
|
+
// ✅ Correct: Comprehensive completion checking
|
|
100
|
+
const checkRouteCompletion = (route: Route, collectedData: any) => {
|
|
101
|
+
try {
|
|
102
|
+
// Check 1: All required fields present
|
|
103
|
+
const missingFields = route.getMissingRequiredFields(collectedData);
|
|
104
|
+
if (missingFields.length === 0) {
|
|
105
|
+
return { complete: true, reason: "all_data_collected" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check 2: All steps processed (including skipped ones)
|
|
109
|
+
const allStepsProcessed = route.steps.every(step => {
|
|
110
|
+
// Skipped steps count as processed
|
|
111
|
+
if (step.skipIf && step.skipIf(collectedData)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return step.isCompleted;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (allStepsProcessed) {
|
|
118
|
+
return { complete: true, reason: "all_steps_processed" };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check 3: Explicit route termination
|
|
122
|
+
if (route.hasExplicitEnd()) {
|
|
123
|
+
return { complete: true, reason: "explicit_end" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { complete: false, missingFields };
|
|
127
|
+
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Route completion check failed:", error);
|
|
130
|
+
// Safe fallback - assume incomplete
|
|
131
|
+
return { complete: false, error: error.message };
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Pattern 2: Conditional Step Handling
|
|
137
|
+
|
|
138
|
+
**Problem:** Routes with `skipIf` conditions can complete even when steps are skipped.
|
|
139
|
+
|
|
140
|
+
**Solution:**
|
|
141
|
+
```typescript
|
|
142
|
+
// ✅ Correct: Handle conditional steps in completion logic
|
|
143
|
+
const route = agent.createRoute({
|
|
144
|
+
title: "Smart Booking",
|
|
145
|
+
requiredFields: ["destination", "dates", "passengers"]
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Steps with smart skipping
|
|
149
|
+
const askDestination = route.initialStep.nextStep({
|
|
150
|
+
prompt: "Where would you like to go?",
|
|
151
|
+
collect: ["destination"],
|
|
152
|
+
skipIf: (data) => !!data.destination, // Skip if already collected
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const askDates = askDestination.nextStep({
|
|
156
|
+
prompt: "When would you like to travel?",
|
|
157
|
+
collect: ["dates"],
|
|
158
|
+
skipIf: (data) => !!data.dates,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const confirmBooking = askDates.nextStep({
|
|
162
|
+
prompt: "Confirm your booking",
|
|
163
|
+
requires: ["destination", "dates", "passengers"],
|
|
164
|
+
onComplete: () => ({ endRoute: true })
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Handle response where all data is collected at once
|
|
168
|
+
const response = await agent.respond({
|
|
169
|
+
message: "Book a flight to Tokyo on March 15th for 2 passengers"
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Route should be complete even though steps were skipped
|
|
173
|
+
console.log("Route complete:", response.isRouteComplete); // Should be true
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Data Synchronization Patterns
|
|
177
|
+
|
|
178
|
+
### Pattern 1: Bidirectional Sync with Rollback
|
|
179
|
+
|
|
180
|
+
**Problem:** Agent and session data must stay synchronized with proper error recovery.
|
|
181
|
+
|
|
182
|
+
**Solution:**
|
|
183
|
+
```typescript
|
|
184
|
+
// ✅ Correct: Bidirectional sync with rollback
|
|
185
|
+
class Agent<TContext, TData> {
|
|
186
|
+
async updateCollectedData(updates: Partial<TData>): Promise<void> {
|
|
187
|
+
const previousAgentData = { ...this.collectedData };
|
|
188
|
+
const previousSessionData = this.session ? { ...this.session.getData() } : null;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// Update agent data first
|
|
192
|
+
this.collectedData = { ...this.collectedData, ...updates };
|
|
193
|
+
|
|
194
|
+
// Sync with session
|
|
195
|
+
if (this.session) {
|
|
196
|
+
await this.session.setData(this.collectedData);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// Rollback both agent and session data
|
|
201
|
+
this.collectedData = previousAgentData;
|
|
202
|
+
|
|
203
|
+
if (this.session && previousSessionData) {
|
|
204
|
+
try {
|
|
205
|
+
await this.session.setData(previousSessionData);
|
|
206
|
+
} catch (rollbackError) {
|
|
207
|
+
console.error("Failed to rollback session data:", rollbackError);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
throw new Error(`Data synchronization failed: ${error.message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Pattern 2: Session History with Persistence
|
|
218
|
+
|
|
219
|
+
**Problem:** Chat history must be reliably persisted with proper error handling.
|
|
220
|
+
|
|
221
|
+
**Solution:**
|
|
222
|
+
```typescript
|
|
223
|
+
// ✅ Correct: Reliable history persistence
|
|
224
|
+
class Agent<TContext, TData> {
|
|
225
|
+
async chat(message: string, sessionId?: string): Promise<AgentResponse<TData>> {
|
|
226
|
+
let userMessageAdded = false;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
// Ensure session is loaded
|
|
230
|
+
await this.ensureSession(sessionId);
|
|
231
|
+
|
|
232
|
+
// Add user message to history BEFORE processing
|
|
233
|
+
await this.session.addMessage("user", message);
|
|
234
|
+
userMessageAdded = true;
|
|
235
|
+
|
|
236
|
+
// Process the message
|
|
237
|
+
const response = await this.respond({
|
|
238
|
+
message,
|
|
239
|
+
sessionId: this.session.id
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Add assistant response to history
|
|
243
|
+
if (response.message) {
|
|
244
|
+
await this.session.addMessage("assistant", response.message);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return response;
|
|
248
|
+
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error("Chat method failed:", error);
|
|
251
|
+
|
|
252
|
+
// Rollback user message if it was added
|
|
253
|
+
if (userMessageAdded) {
|
|
254
|
+
try {
|
|
255
|
+
await this.session.removeLastMessage("user");
|
|
256
|
+
} catch (rollbackError) {
|
|
257
|
+
console.error("Failed to rollback user message:", rollbackError);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
throw new Error(`Chat failed: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Tool Execution Patterns
|
|
268
|
+
|
|
269
|
+
### Pattern 1: Graceful Tool Degradation
|
|
270
|
+
|
|
271
|
+
**Problem:** Tool failures should not break the conversation flow.
|
|
272
|
+
|
|
273
|
+
**Solution:**
|
|
274
|
+
```typescript
|
|
275
|
+
// ✅ Correct: Graceful tool error handling
|
|
276
|
+
const searchFlights: Tool<Context, [], void, FlightData> = {
|
|
277
|
+
id: "search_flights",
|
|
278
|
+
description: "Search for available flights",
|
|
279
|
+
parameters: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
destination: { type: "string" },
|
|
283
|
+
date: { type: "string" }
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
handler: async (toolContext) => {
|
|
287
|
+
try {
|
|
288
|
+
const { data } = toolContext;
|
|
289
|
+
|
|
290
|
+
// Validate inputs
|
|
291
|
+
if (!data.destination || !data.date) {
|
|
292
|
+
return {
|
|
293
|
+
data: "I need both destination and date to search for flights.",
|
|
294
|
+
error: "Missing required parameters"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Perform the search
|
|
299
|
+
const results = await flightAPI.search(data.destination, data.date);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
data: `Found ${results.length} flights to ${data.destination}`,
|
|
303
|
+
dataUpdate: { availableFlights: results }
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Flight search failed:", error);
|
|
308
|
+
|
|
309
|
+
// Provide helpful error message to user
|
|
310
|
+
return {
|
|
311
|
+
data: "I'm having trouble searching for flights right now. Please try again in a moment.",
|
|
312
|
+
error: error.message,
|
|
313
|
+
dataUpdate: { searchError: true }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Pattern 2: Tool Retry with Circuit Breaker
|
|
321
|
+
|
|
322
|
+
**Problem:** External API failures need retry logic with circuit breaking.
|
|
323
|
+
|
|
324
|
+
**Solution:**
|
|
325
|
+
```typescript
|
|
326
|
+
// ✅ Correct: Tool with retry and circuit breaker
|
|
327
|
+
class ToolExecutor {
|
|
328
|
+
private circuitBreaker = new Map<string, CircuitBreaker>();
|
|
329
|
+
|
|
330
|
+
async executeTool<T>(tool: Tool, params: any): Promise<T> {
|
|
331
|
+
const breaker = this.getCircuitBreaker(tool.id);
|
|
332
|
+
|
|
333
|
+
return await breaker.execute(async () => {
|
|
334
|
+
return await this.retryWithBackoff(
|
|
335
|
+
() => tool.handler(params),
|
|
336
|
+
3, // max retries
|
|
337
|
+
1000 // base delay
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private async retryWithBackoff<T>(
|
|
343
|
+
operation: () => Promise<T>,
|
|
344
|
+
maxRetries: number,
|
|
345
|
+
baseDelay: number
|
|
346
|
+
): Promise<T> {
|
|
347
|
+
let lastError: Error;
|
|
348
|
+
|
|
349
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
350
|
+
try {
|
|
351
|
+
return await operation();
|
|
352
|
+
} catch (error) {
|
|
353
|
+
lastError = error as Error;
|
|
354
|
+
|
|
355
|
+
if (attempt < maxRetries - 1) {
|
|
356
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
357
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw lastError;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Validation Patterns
|
|
368
|
+
|
|
369
|
+
### Pattern 1: Progressive Fallback Validation
|
|
370
|
+
|
|
371
|
+
**Problem:** Schema validation failures need graceful fallback strategies.
|
|
372
|
+
|
|
373
|
+
**Solution:**
|
|
374
|
+
```typescript
|
|
375
|
+
// ✅ Correct: Progressive validation fallback
|
|
376
|
+
const extractDataWithFallback = async <T>(
|
|
377
|
+
response: string,
|
|
378
|
+
schema: JSONSchema
|
|
379
|
+
): Promise<{ data: T | null; method: string; error?: string }> => {
|
|
380
|
+
|
|
381
|
+
// Try 1: Schema-based extraction
|
|
382
|
+
try {
|
|
383
|
+
const extracted = await extractWithSchema<T>(response, schema);
|
|
384
|
+
const validation = validateSchema(extracted, schema);
|
|
385
|
+
|
|
386
|
+
if (validation.valid) {
|
|
387
|
+
return { data: extracted, method: "schema" };
|
|
388
|
+
} else {
|
|
389
|
+
console.warn("Schema validation failed:", validation.errors);
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.warn("Schema extraction failed:", error.message);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Try 2: Manual extraction with patterns
|
|
396
|
+
try {
|
|
397
|
+
const manual = await manualExtraction<T>(response);
|
|
398
|
+
return { data: manual, method: "manual" };
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.warn("Manual extraction failed:", error.message);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Try 3: LLM-based extraction as last resort
|
|
404
|
+
try {
|
|
405
|
+
const llmExtracted = await llmBasedExtraction<T>(response, schema);
|
|
406
|
+
return { data: llmExtracted, method: "llm" };
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.error("All extraction methods failed:", error.message);
|
|
409
|
+
return { data: null, method: "none", error: error.message };
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Error Recovery Strategies
|
|
415
|
+
|
|
416
|
+
### Strategy 1: Exponential Backoff
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const withExponentialBackoff = async <T>(
|
|
420
|
+
operation: () => Promise<T>,
|
|
421
|
+
maxRetries: number = 3,
|
|
422
|
+
baseDelay: number = 1000,
|
|
423
|
+
maxDelay: number = 10000
|
|
424
|
+
): Promise<T> => {
|
|
425
|
+
let lastError: Error;
|
|
426
|
+
|
|
427
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
428
|
+
try {
|
|
429
|
+
return await operation();
|
|
430
|
+
} catch (error) {
|
|
431
|
+
lastError = error as Error;
|
|
432
|
+
|
|
433
|
+
if (attempt < maxRetries - 1) {
|
|
434
|
+
const delay = Math.min(
|
|
435
|
+
baseDelay * Math.pow(2, attempt),
|
|
436
|
+
maxDelay
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
console.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms:`, error.message);
|
|
440
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
throw new Error(`Operation failed after ${maxRetries} attempts: ${lastError.message}`);
|
|
446
|
+
};
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Strategy 2: Circuit Breaker
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
class CircuitBreaker {
|
|
453
|
+
private failures = 0;
|
|
454
|
+
private lastFailureTime = 0;
|
|
455
|
+
private state: 'closed' | 'open' | 'half-open' = 'closed';
|
|
456
|
+
|
|
457
|
+
constructor(
|
|
458
|
+
private threshold: number = 5,
|
|
459
|
+
private timeout: number = 60000
|
|
460
|
+
) {}
|
|
461
|
+
|
|
462
|
+
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
463
|
+
if (this.state === 'open') {
|
|
464
|
+
if (Date.now() - this.lastFailureTime > this.timeout) {
|
|
465
|
+
this.state = 'half-open';
|
|
466
|
+
} else {
|
|
467
|
+
throw new Error('Circuit breaker is open - service unavailable');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const result = await operation();
|
|
473
|
+
this.onSuccess();
|
|
474
|
+
return result;
|
|
475
|
+
} catch (error) {
|
|
476
|
+
this.onFailure();
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private onSuccess(): void {
|
|
482
|
+
this.failures = 0;
|
|
483
|
+
this.state = 'closed';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private onFailure(): void {
|
|
487
|
+
this.failures++;
|
|
488
|
+
this.lastFailureTime = Date.now();
|
|
489
|
+
|
|
490
|
+
if (this.failures >= this.threshold) {
|
|
491
|
+
this.state = 'open';
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Testing Error Scenarios
|
|
498
|
+
|
|
499
|
+
### Unit Test Patterns
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
describe("Error Handling", () => {
|
|
503
|
+
describe("Streaming Errors", () => {
|
|
504
|
+
it("should propagate provider streaming errors", async () => {
|
|
505
|
+
const mockProvider = new MockProvider({
|
|
506
|
+
streamingError: "Test streaming error"
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const agent = new Agent({ provider: mockProvider });
|
|
510
|
+
|
|
511
|
+
await expect(async () => {
|
|
512
|
+
for await (const chunk of agent.respondStream({ message: "test" })) {
|
|
513
|
+
// Should throw before yielding
|
|
514
|
+
}
|
|
515
|
+
}).rejects.toThrow("Test streaming error");
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe("Route Completion", () => {
|
|
520
|
+
it("should handle completion check errors gracefully", async () => {
|
|
521
|
+
const route = createMockRoute();
|
|
522
|
+
|
|
523
|
+
// Mock completion check to throw
|
|
524
|
+
jest.spyOn(route, 'isComplete').mockImplementation(() => {
|
|
525
|
+
throw new Error("Completion check failed");
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const result = checkRouteCompletion(route, {});
|
|
529
|
+
|
|
530
|
+
expect(result.complete).toBe(false);
|
|
531
|
+
expect(result.error).toBe("Completion check failed");
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe("Data Synchronization", () => {
|
|
536
|
+
it("should rollback on sync failures", async () => {
|
|
537
|
+
const agent = new Agent({ sessionId: "test" });
|
|
538
|
+
const originalData = { name: "John" };
|
|
539
|
+
|
|
540
|
+
// Set initial data
|
|
541
|
+
await agent.updateCollectedData(originalData);
|
|
542
|
+
|
|
543
|
+
// Mock session setData to fail
|
|
544
|
+
jest.spyOn(agent.session, 'setData').mockRejectedValue(
|
|
545
|
+
new Error("Persistence failed")
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
// Attempt update should fail and rollback
|
|
549
|
+
await expect(
|
|
550
|
+
agent.updateCollectedData({ email: "john@example.com" })
|
|
551
|
+
).rejects.toThrow("Data synchronization failed");
|
|
552
|
+
|
|
553
|
+
// Data should be rolled back
|
|
554
|
+
expect(agent.getCollectedData()).toEqual(originalData);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Best Practices Summary
|
|
561
|
+
|
|
562
|
+
1. **Always propagate streaming errors** - Don't swallow them in generators
|
|
563
|
+
2. **Use defensive completion checking** - Handle all edge cases gracefully
|
|
564
|
+
3. **Implement bidirectional rollback** - Keep agent and session data consistent
|
|
565
|
+
4. **Provide fallback responses** - Never leave users without feedback
|
|
566
|
+
5. **Log errors with context** - Include relevant metadata for debugging
|
|
567
|
+
6. **Test error scenarios** - Cover all failure modes in your tests
|
|
568
|
+
7. **Use circuit breakers** - Protect against cascading failures
|
|
569
|
+
8. **Implement progressive fallbacks** - Multiple recovery strategies
|
|
570
|
+
9. **Monitor error rates** - Track and alert on error patterns
|
|
571
|
+
10. **Document error handling** - Make error behavior clear to users
|
|
572
|
+
|
|
573
|
+
## Further Reading
|
|
574
|
+
|
|
575
|
+
- [Core Error Handling](../core/error-handling.md) - Comprehensive error handling reference
|
|
576
|
+
- [Response Processing](../core/ai-integration/response-processing.md) - AI response error handling
|
|
577
|
+
- [Session Management](../core/agent/session-management.md) - Session error patterns
|
|
578
|
+
- [Route Management](../core/conversation-flows/routes.md) - Route completion error handling
|