@falai/agent 0.1.4 → 0.2.0
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 +117 -1
- package/dist/cjs/core/Agent.d.ts +11 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +44 -2
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Events.d.ts +2 -2
- package/dist/cjs/core/Events.d.ts.map +1 -1
- package/dist/cjs/core/Events.js +4 -4
- package/dist/cjs/core/Events.js.map +1 -1
- package/dist/cjs/core/Observation.d.ts.map +1 -1
- package/dist/cjs/core/Observation.js +3 -2
- package/dist/cjs/core/Observation.js.map +1 -1
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +3 -4
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/State.d.ts +1 -1
- package/dist/cjs/core/State.d.ts.map +1 -1
- package/dist/cjs/core/State.js +4 -3
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/core/Tool.d.ts +1 -0
- package/dist/cjs/core/Tool.d.ts.map +1 -1
- package/dist/cjs/core/Tool.js +3 -2
- package/dist/cjs/core/Tool.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +26 -2
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/observation.d.ts +2 -0
- package/dist/cjs/types/observation.d.ts.map +1 -1
- package/dist/cjs/types/route.d.ts +2 -0
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +6 -2
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/utils/id.d.ts +25 -0
- package/dist/cjs/utils/id.d.ts.map +1 -0
- package/dist/cjs/utils/id.js +71 -0
- package/dist/cjs/utils/id.js.map +1 -0
- package/dist/core/Agent.d.ts +11 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +44 -2
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Events.d.ts +2 -2
- package/dist/core/Events.d.ts.map +1 -1
- package/dist/core/Events.js +4 -4
- package/dist/core/Events.js.map +1 -1
- package/dist/core/Observation.d.ts.map +1 -1
- package/dist/core/Observation.js +3 -2
- package/dist/core/Observation.js.map +1 -1
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +3 -4
- package/dist/core/Route.js.map +1 -1
- package/dist/core/State.d.ts +1 -1
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +4 -3
- package/dist/core/State.js.map +1 -1
- package/dist/core/Tool.d.ts +1 -0
- package/dist/core/Tool.d.ts.map +1 -1
- package/dist/core/Tool.js +3 -2
- package/dist/core/Tool.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +26 -2
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/observation.d.ts +2 -0
- package/dist/types/observation.d.ts.map +1 -1
- package/dist/types/route.d.ts +2 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/tool.d.ts +6 -2
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/id.d.ts +25 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +65 -0
- package/dist/utils/id.js.map +1 -0
- package/docs/API_REFERENCE.md +122 -6
- package/docs/CONSTRUCTOR_OPTIONS.md +43 -36
- package/docs/CONTEXT_MANAGEMENT.md +447 -0
- package/docs/GETTING_STARTED.md +2 -0
- package/docs/PROVIDERS.md +3 -0
- package/examples/declarative-agent.ts +31 -7
- package/examples/persistent-onboarding.ts +464 -0
- package/package.json +1 -1
- package/src/core/Agent.ts +56 -2
- package/src/core/Events.ts +6 -4
- package/src/core/Observation.ts +3 -3
- package/src/core/Route.ts +3 -5
- package/src/core/State.ts +5 -4
- package/src/core/Tool.ts +4 -3
- package/src/index.ts +10 -0
- package/src/types/agent.ts +36 -2
- package/src/types/observation.ts +2 -0
- package/src/types/route.ts +2 -0
- package/src/types/tool.ts +6 -2
- package/src/utils/id.ts +74 -0
|
@@ -18,16 +18,16 @@ interface AgentOptions<TContext = unknown> {
|
|
|
18
18
|
// Required
|
|
19
19
|
name: string;
|
|
20
20
|
ai: AiProvider;
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Optional metadata
|
|
23
23
|
description?: string;
|
|
24
24
|
goal?: string;
|
|
25
25
|
context?: TContext;
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
// Configuration
|
|
28
28
|
maxEngineIterations?: number;
|
|
29
29
|
compositionMode?: CompositionMode;
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Declarative initialization (NEW!)
|
|
32
32
|
terms?: Term[];
|
|
33
33
|
guidelines?: Guideline[];
|
|
@@ -44,43 +44,47 @@ const agent = new Agent({
|
|
|
44
44
|
name: "SupportBot",
|
|
45
45
|
description: "Helpful customer support",
|
|
46
46
|
goal: "Resolve issues efficiently",
|
|
47
|
-
ai: new GeminiProvider({ apiKey: "..." }),
|
|
47
|
+
ai: new GeminiProvider({ apiKey: "...", model: "..." }),
|
|
48
48
|
context: { userId: "123" },
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
terms: [
|
|
51
|
-
{
|
|
51
|
+
{
|
|
52
|
+
name: "SLA",
|
|
53
|
+
description: "Service Level Agreement",
|
|
54
|
+
synonyms: ["response time"],
|
|
55
|
+
},
|
|
52
56
|
],
|
|
53
|
-
|
|
57
|
+
|
|
54
58
|
guidelines: [
|
|
55
59
|
{
|
|
56
60
|
condition: "User is frustrated",
|
|
57
61
|
action: "Show empathy and offer escalation",
|
|
58
62
|
tags: ["support"],
|
|
59
|
-
enabled: true
|
|
60
|
-
}
|
|
63
|
+
enabled: true,
|
|
64
|
+
},
|
|
61
65
|
],
|
|
62
|
-
|
|
66
|
+
|
|
63
67
|
capabilities: [
|
|
64
|
-
{ title: "Ticket Management", description: "Create and track tickets" }
|
|
68
|
+
{ title: "Ticket Management", description: "Create and track tickets" },
|
|
65
69
|
],
|
|
66
|
-
|
|
70
|
+
|
|
67
71
|
routes: [
|
|
68
72
|
{
|
|
69
73
|
title: "Create Ticket",
|
|
70
74
|
description: "Help user create a support ticket",
|
|
71
75
|
conditions: ["User wants to report an issue"],
|
|
72
76
|
guidelines: [
|
|
73
|
-
{ condition: "Issue is urgent", action: "Prioritize immediately" }
|
|
74
|
-
]
|
|
75
|
-
}
|
|
77
|
+
{ condition: "Issue is urgent", action: "Prioritize immediately" },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
76
80
|
],
|
|
77
|
-
|
|
81
|
+
|
|
78
82
|
observations: [
|
|
79
83
|
{
|
|
80
84
|
description: "User mentions problem but unclear what kind",
|
|
81
|
-
routeRefs: ["Create Ticket", "Check Ticket Status"] // By title!
|
|
82
|
-
}
|
|
83
|
-
]
|
|
85
|
+
routeRefs: ["Create Ticket", "Check Ticket Status"], // By title!
|
|
86
|
+
},
|
|
87
|
+
],
|
|
84
88
|
});
|
|
85
89
|
```
|
|
86
90
|
|
|
@@ -92,7 +96,7 @@ const agent = new Agent({
|
|
|
92
96
|
interface RouteOptions {
|
|
93
97
|
// Required
|
|
94
98
|
title: string;
|
|
95
|
-
|
|
99
|
+
|
|
96
100
|
// Optional
|
|
97
101
|
description?: string;
|
|
98
102
|
conditions?: string[];
|
|
@@ -115,16 +119,16 @@ const agent = new Agent({
|
|
|
115
119
|
{
|
|
116
120
|
condition: "User skips a step",
|
|
117
121
|
action: "Gently remind them it's important",
|
|
118
|
-
tags: ["onboarding"]
|
|
122
|
+
tags: ["onboarding"],
|
|
119
123
|
},
|
|
120
124
|
{
|
|
121
125
|
condition: "User seems confused",
|
|
122
126
|
action: "Offer a quick tutorial video",
|
|
123
|
-
tags: ["help"]
|
|
124
|
-
}
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
]
|
|
127
|
+
tags: ["help"],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
128
132
|
});
|
|
129
133
|
```
|
|
130
134
|
|
|
@@ -187,18 +191,21 @@ obs.disambiguate([route1, route2]);
|
|
|
187
191
|
## 🎨 Best Practices
|
|
188
192
|
|
|
189
193
|
### Use Declarative When:
|
|
194
|
+
|
|
190
195
|
- ✅ Configuration is **static** and known upfront
|
|
191
196
|
- ✅ Loading config from **JSON/YAML files**
|
|
192
197
|
- ✅ Building **reusable agent templates**
|
|
193
198
|
- ✅ You want **clean, readable initialization**
|
|
194
199
|
|
|
195
200
|
### Use Fluent When:
|
|
201
|
+
|
|
196
202
|
- ✅ Logic is **dynamic** or **conditional**
|
|
197
203
|
- ✅ Building routes with **complex state machines**
|
|
198
204
|
- ✅ Adding features **based on runtime conditions**
|
|
199
205
|
- ✅ You prefer **step-by-step construction**
|
|
200
206
|
|
|
201
207
|
### Mix Both!
|
|
208
|
+
|
|
202
209
|
```typescript
|
|
203
210
|
// Start with static config
|
|
204
211
|
const agent = new Agent({
|
|
@@ -212,7 +219,7 @@ const agent = new Agent({
|
|
|
212
219
|
if (user.isPremium) {
|
|
213
220
|
agent.createGuideline({
|
|
214
221
|
condition: "User asks for priority support",
|
|
215
|
-
action: "Escalate immediately to premium team"
|
|
222
|
+
action: "Escalate immediately to premium team",
|
|
216
223
|
});
|
|
217
224
|
}
|
|
218
225
|
```
|
|
@@ -221,15 +228,15 @@ if (user.isPremium) {
|
|
|
221
228
|
|
|
222
229
|
## 📊 Complete Comparison
|
|
223
230
|
|
|
224
|
-
| Feature
|
|
225
|
-
|
|
226
|
-
| **Terms**
|
|
227
|
-
| **Guidelines**
|
|
228
|
-
| **Capabilities**
|
|
229
|
-
| **Routes**
|
|
230
|
-
| **Route Guidelines** | `route.guidelines: Guideline[]`
|
|
231
|
-
| **Observations**
|
|
232
|
-
| **Disambiguation**
|
|
231
|
+
| Feature | Declarative (Constructor) | Fluent (Methods) |
|
|
232
|
+
| -------------------- | ------------------------------------ | ------------------------------ |
|
|
233
|
+
| **Terms** | `terms: Term[]` | `agent.createTerm(...)` |
|
|
234
|
+
| **Guidelines** | `guidelines: Guideline[]` | `agent.createGuideline(...)` |
|
|
235
|
+
| **Capabilities** | `capabilities: Capability[]` | `agent.createCapability(...)` |
|
|
236
|
+
| **Routes** | `routes: RouteOptions[]` | `agent.createRoute(...)` |
|
|
237
|
+
| **Route Guidelines** | `route.guidelines: Guideline[]` | `route.createGuideline(...)` |
|
|
238
|
+
| **Observations** | `observations: ObservationOptions[]` | `agent.createObservation(...)` |
|
|
239
|
+
| **Disambiguation** | `routeRefs: string[]` | `obs.disambiguate([...])` |
|
|
233
240
|
|
|
234
241
|
---
|
|
235
242
|
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Context Management & Persistence
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `@falai/agent` framework provides flexible patterns for managing context in both **single-turn** and **multi-turn persistent** conversations. This guide explains how to handle stateful conversations that persist across requests.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🤔 The Context Lifecycle Problem
|
|
10
|
+
|
|
11
|
+
### Single-Turn Conversations (Simple)
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// ✅ Works great for single-turn conversations
|
|
15
|
+
const agent = new Agent({
|
|
16
|
+
name: "Bot",
|
|
17
|
+
ai: provider,
|
|
18
|
+
context: { userId: "123", preferences: {} },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const response = await agent.respond({ history });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Works because:** Agent is used once and discarded.
|
|
25
|
+
|
|
26
|
+
### Multi-Turn Conversations (Complex)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// ❌ PROBLEM: Context updates don't persist
|
|
30
|
+
const agent = new Agent({
|
|
31
|
+
name: "Bot",
|
|
32
|
+
ai: provider,
|
|
33
|
+
context: { userId: "123", preferences: {} },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Turn 1
|
|
37
|
+
await agent.respond({ history: [message1] });
|
|
38
|
+
// Tool modifies context in memory...
|
|
39
|
+
|
|
40
|
+
// Turn 2 (new request, agent recreated)
|
|
41
|
+
const agent2 = new Agent({ context: { userId: "123", preferences: {} } });
|
|
42
|
+
await agent2.respond({ history: [message2] });
|
|
43
|
+
// ❌ Lost the context changes from Turn 1!
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Problem:** When agents are recreated (e.g., across HTTP requests), context updates are lost.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 💡 Solution: Context Lifecycle Hooks
|
|
51
|
+
|
|
52
|
+
The framework provides **lifecycle hooks** to integrate with your persistence layer (database, cache, etc.).
|
|
53
|
+
|
|
54
|
+
### Pattern 1: `beforeRespond` + `onContextUpdate` (Recommended)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Agent, type ContextLifecycleHooks } from "@falai/agent";
|
|
58
|
+
|
|
59
|
+
// Define hooks
|
|
60
|
+
const hooks: ContextLifecycleHooks<MyContext> = {
|
|
61
|
+
// Load fresh context before each response
|
|
62
|
+
beforeRespond: async (currentContext) => {
|
|
63
|
+
return await database.loadContext(sessionId);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Persist context after updates
|
|
67
|
+
onContextUpdate: async (newContext, previousContext) => {
|
|
68
|
+
await database.saveContext(sessionId, newContext);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Create agent with hooks
|
|
73
|
+
const agent = new Agent({
|
|
74
|
+
name: "Bot",
|
|
75
|
+
ai: provider,
|
|
76
|
+
context: initialContext,
|
|
77
|
+
hooks, // 🔑 Enable persistence
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**How it works:**
|
|
82
|
+
|
|
83
|
+
1. `beforeRespond` fetches fresh context from your database before `respond()` is called
|
|
84
|
+
2. Tools can update context using `toolContext.updateContext()` or returning `{ contextUpdate }`
|
|
85
|
+
3. `onContextUpdate` automatically persists changes to your database
|
|
86
|
+
4. Next request creates a new agent, `beforeRespond` loads the updated context
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 🔧 Tool Context Updates
|
|
91
|
+
|
|
92
|
+
Tools can update context in **two ways**:
|
|
93
|
+
|
|
94
|
+
### Option A: Return `contextUpdate`
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const saveName = defineTool<MyContext, [name: string], boolean>(
|
|
98
|
+
"save_name",
|
|
99
|
+
async (toolContext, name) => {
|
|
100
|
+
return {
|
|
101
|
+
data: true,
|
|
102
|
+
contextUpdate: {
|
|
103
|
+
userName: name,
|
|
104
|
+
updatedAt: new Date(),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Pros:**
|
|
112
|
+
|
|
113
|
+
- Declarative and functional
|
|
114
|
+
- Easy to test
|
|
115
|
+
- Context update is part of the result
|
|
116
|
+
|
|
117
|
+
### Option B: Call `updateContext()` directly
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const saveName = defineTool<MyContext, [name: string], boolean>(
|
|
121
|
+
"save_name",
|
|
122
|
+
async (toolContext, name) => {
|
|
123
|
+
await toolContext.updateContext({
|
|
124
|
+
userName: name,
|
|
125
|
+
updatedAt: new Date(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return { data: true };
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Pros:**
|
|
134
|
+
|
|
135
|
+
- More imperative and direct
|
|
136
|
+
- Can update context at any point in the tool
|
|
137
|
+
- Useful for complex logic
|
|
138
|
+
|
|
139
|
+
**Both approaches trigger the `onContextUpdate` hook automatically.**
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 🌐 Context Provider Pattern
|
|
144
|
+
|
|
145
|
+
For scenarios where context is **always loaded from an external source**, use the `contextProvider` pattern:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const agent = new Agent({
|
|
149
|
+
name: "Bot",
|
|
150
|
+
ai: provider,
|
|
151
|
+
|
|
152
|
+
// Instead of static context:
|
|
153
|
+
contextProvider: async () => {
|
|
154
|
+
// Fetch context fresh on every respond() call
|
|
155
|
+
return await database.loadContext(sessionId);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
hooks: {
|
|
159
|
+
// Still persist updates
|
|
160
|
+
onContextUpdate: async (newContext) => {
|
|
161
|
+
await database.saveContext(sessionId, newContext);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**When to use:**
|
|
168
|
+
|
|
169
|
+
- Context is always loaded from a database/cache
|
|
170
|
+
- You never have a static starting context
|
|
171
|
+
- You want guaranteed fresh data on every request
|
|
172
|
+
|
|
173
|
+
**Difference from `beforeRespond`:**
|
|
174
|
+
|
|
175
|
+
- `contextProvider`: **Replaces** the context entirely
|
|
176
|
+
- `beforeRespond`: **Updates** the existing context (can access previous state)
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 🎯 Complete Example: Multi-Turn Onboarding
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { Agent, defineTool } from "@falai/agent";
|
|
184
|
+
|
|
185
|
+
interface OnboardingContext {
|
|
186
|
+
sessionId: string;
|
|
187
|
+
userId: string;
|
|
188
|
+
businessName?: string;
|
|
189
|
+
industry?: string;
|
|
190
|
+
completedSteps: string[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Database simulation
|
|
194
|
+
const db = {
|
|
195
|
+
async loadSession(sessionId: string) {
|
|
196
|
+
return await database.findSession(sessionId);
|
|
197
|
+
},
|
|
198
|
+
async saveSession(sessionId: string, data: Partial<OnboardingContext>) {
|
|
199
|
+
await database.updateSession(sessionId, data);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Factory function for creating agents
|
|
204
|
+
async function createOnboardingAgent(sessionId: string) {
|
|
205
|
+
const session = await db.loadSession(sessionId);
|
|
206
|
+
|
|
207
|
+
const agent = new Agent<OnboardingContext>({
|
|
208
|
+
name: "OnboardingBot",
|
|
209
|
+
ai: provider,
|
|
210
|
+
context: {
|
|
211
|
+
sessionId,
|
|
212
|
+
userId: session.userId,
|
|
213
|
+
businessName: session.businessName,
|
|
214
|
+
industry: session.industry,
|
|
215
|
+
completedSteps: session.completedSteps || [],
|
|
216
|
+
},
|
|
217
|
+
hooks: {
|
|
218
|
+
// Load fresh context before responding
|
|
219
|
+
beforeRespond: async (current) => {
|
|
220
|
+
const fresh = await db.loadSession(sessionId);
|
|
221
|
+
return { ...current, ...fresh };
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// Persist context after updates
|
|
225
|
+
onContextUpdate: async (newContext) => {
|
|
226
|
+
await db.saveSession(sessionId, {
|
|
227
|
+
businessName: newContext.businessName,
|
|
228
|
+
industry: newContext.industry,
|
|
229
|
+
completedSteps: newContext.completedSteps,
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Define tools with context updates
|
|
236
|
+
const saveBusinessName = defineTool(
|
|
237
|
+
"save_business_name",
|
|
238
|
+
async (ctx, name: string) => {
|
|
239
|
+
return {
|
|
240
|
+
data: true,
|
|
241
|
+
contextUpdate: {
|
|
242
|
+
businessName: name,
|
|
243
|
+
completedSteps: [...ctx.context.completedSteps, "business_name"],
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const saveIndustry = defineTool(
|
|
250
|
+
"save_industry",
|
|
251
|
+
async (ctx, industry: string) => {
|
|
252
|
+
// Alternative: use updateContext directly
|
|
253
|
+
await ctx.updateContext({
|
|
254
|
+
industry,
|
|
255
|
+
completedSteps: [...ctx.context.completedSteps, "industry"],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return { data: true };
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Build conversation routes...
|
|
263
|
+
const route = agent.createRoute({ title: "Onboarding" });
|
|
264
|
+
// ...
|
|
265
|
+
|
|
266
|
+
return agent;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Usage across multiple turns
|
|
270
|
+
async function handleUserMessage(sessionId: string, message: string) {
|
|
271
|
+
// Recreate agent for each turn (context is loaded fresh)
|
|
272
|
+
const agent = await createOnboardingAgent(sessionId);
|
|
273
|
+
|
|
274
|
+
const response = await agent.respond({
|
|
275
|
+
history: [createMessageEvent(EventSource.CUSTOMER, "User", message)],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return response.message;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 📋 Best Practices
|
|
285
|
+
|
|
286
|
+
### ✅ DO
|
|
287
|
+
|
|
288
|
+
- **Recreate agents** for each request in multi-turn conversations
|
|
289
|
+
- **Use lifecycle hooks** to integrate with your database
|
|
290
|
+
- **Store context** in your database/cache (Redis, PostgreSQL, etc.)
|
|
291
|
+
- **Load fresh context** via `beforeRespond` or `contextProvider`
|
|
292
|
+
- **Test persistence** by simulating multiple turns
|
|
293
|
+
- **Handle errors** in hooks gracefully (fallback to current context)
|
|
294
|
+
|
|
295
|
+
### ❌ DON'T
|
|
296
|
+
|
|
297
|
+
- **Cache agent instances** across requests (context gets stale)
|
|
298
|
+
- **Mutate context directly** without using `updateContext()` or `contextUpdate`
|
|
299
|
+
- **Rely on in-memory state** for multi-turn conversations
|
|
300
|
+
- **Forget to handle** `onContextUpdate` failures (could lose data)
|
|
301
|
+
- **Mix** `context` and `contextProvider` (will throw an error)
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 🔍 Debugging Context Issues
|
|
306
|
+
|
|
307
|
+
### Problem: Context updates aren't persisting
|
|
308
|
+
|
|
309
|
+
**Check:**
|
|
310
|
+
|
|
311
|
+
1. Are you using `onContextUpdate` hook?
|
|
312
|
+
2. Is your database save actually working?
|
|
313
|
+
3. Are you recreating the agent with fresh context each turn?
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Debug your hooks
|
|
317
|
+
hooks: {
|
|
318
|
+
beforeRespond: async (current) => {
|
|
319
|
+
console.log("📥 Loading context:", current);
|
|
320
|
+
const fresh = await db.load(sessionId);
|
|
321
|
+
console.log("✅ Loaded fresh:", fresh);
|
|
322
|
+
return fresh;
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
onContextUpdate: async (newContext) => {
|
|
326
|
+
console.log("💾 Saving context:", newContext);
|
|
327
|
+
await db.save(sessionId, newContext);
|
|
328
|
+
console.log("✅ Saved successfully");
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Problem: Context is null/undefined
|
|
334
|
+
|
|
335
|
+
**Check:**
|
|
336
|
+
|
|
337
|
+
1. Did you provide either `context` or `contextProvider`?
|
|
338
|
+
2. Is your `contextProvider` returning valid data?
|
|
339
|
+
3. Is `beforeRespond` returning valid context?
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// Validate your contextProvider
|
|
343
|
+
contextProvider: async () => {
|
|
344
|
+
const ctx = await db.load(sessionId);
|
|
345
|
+
if (!ctx) {
|
|
346
|
+
console.error("❌ Context not found!");
|
|
347
|
+
throw new Error("Session not found");
|
|
348
|
+
}
|
|
349
|
+
return ctx;
|
|
350
|
+
},
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Problem: Tools not updating context
|
|
354
|
+
|
|
355
|
+
**Check:**
|
|
356
|
+
|
|
357
|
+
1. Are you returning `{ contextUpdate }` or calling `toolContext.updateContext()`?
|
|
358
|
+
2. Is `onContextUpdate` being called? (Add console.log)
|
|
359
|
+
3. Are your tools being executed at all?
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 🚀 Advanced Patterns
|
|
364
|
+
|
|
365
|
+
### Pattern: Context Versioning
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
interface VersionedContext extends MyContext {
|
|
369
|
+
version: number;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
hooks: {
|
|
373
|
+
onContextUpdate: async (newContext) => {
|
|
374
|
+
await db.save(sessionId, {
|
|
375
|
+
...newContext,
|
|
376
|
+
version: newContext.version + 1,
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
beforeRespond: async (current) => {
|
|
381
|
+
const fresh = await db.load(sessionId);
|
|
382
|
+
if (fresh.version > current.version) {
|
|
383
|
+
console.log("⚠️ Context changed by another request!");
|
|
384
|
+
}
|
|
385
|
+
return fresh;
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Pattern: Selective Persistence
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Only persist specific fields
|
|
394
|
+
hooks: {
|
|
395
|
+
onContextUpdate: async (newContext) => {
|
|
396
|
+
// Don't persist temporary/computed fields
|
|
397
|
+
const { tempData, ...persistable } = newContext;
|
|
398
|
+
await db.save(sessionId, persistable);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Pattern: Multi-User Context
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
interface MultiUserContext {
|
|
407
|
+
conversationId: string;
|
|
408
|
+
participants: Map<string, UserData>;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
hooks: {
|
|
412
|
+
beforeRespond: async (current) => {
|
|
413
|
+
// Load all participant data
|
|
414
|
+
const conversation = await db.loadConversation(conversationId);
|
|
415
|
+
return {
|
|
416
|
+
conversationId,
|
|
417
|
+
participants: new Map(conversation.participants),
|
|
418
|
+
};
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 📚 Related Resources
|
|
426
|
+
|
|
427
|
+
- [Complete Example: Persistent Onboarding](../examples/persistent-onboarding.ts)
|
|
428
|
+
- [API Reference: AgentOptions](./API_REFERENCE.md#agentoptions)
|
|
429
|
+
- [API Reference: ContextLifecycleHooks](./API_REFERENCE.md#contextlifecyclehooks)
|
|
430
|
+
- [Getting Started](./GETTING_STARTED.md)
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 🆘 Need Help?
|
|
435
|
+
|
|
436
|
+
If you're still having issues:
|
|
437
|
+
|
|
438
|
+
1. Check the [examples](../examples/) for working implementations
|
|
439
|
+
2. Review the [API Reference](./API_REFERENCE.md) for detailed type information
|
|
440
|
+
3. Open an issue on GitHub with your use case
|
|
441
|
+
|
|
442
|
+
**Remember:** The key to persistent conversations is:
|
|
443
|
+
|
|
444
|
+
1. **Recreate agents** each turn
|
|
445
|
+
2. **Load fresh context** via hooks/provider
|
|
446
|
+
3. **Persist updates** via `onContextUpdate`
|
|
447
|
+
4. **Never cache** agent instances across requests
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -58,6 +58,7 @@ interface MyContext {
|
|
|
58
58
|
// Create AI provider
|
|
59
59
|
const ai = new GeminiProvider({
|
|
60
60
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
61
|
+
model: "models/gemini-2.5-pro",
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
// Create your agent
|
|
@@ -307,6 +308,7 @@ Increase timeout in provider config:
|
|
|
307
308
|
```typescript
|
|
308
309
|
new GeminiProvider({
|
|
309
310
|
apiKey: "...",
|
|
311
|
+
model: "models/gemini-2.5-flash",
|
|
310
312
|
retryConfig: {
|
|
311
313
|
timeout: 120000, // 2 minutes
|
|
312
314
|
retries: 5,
|
package/docs/PROVIDERS.md
CHANGED
|
@@ -281,6 +281,7 @@ const geminiAgent = new Agent({
|
|
|
281
281
|
name: "Gemini Assistant",
|
|
282
282
|
ai: new GeminiProvider({
|
|
283
283
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
284
|
+
model: "models/gemini-2.5-flash",
|
|
284
285
|
}),
|
|
285
286
|
});
|
|
286
287
|
|
|
@@ -315,10 +316,12 @@ config();
|
|
|
315
316
|
|
|
316
317
|
const geminiProvider = new GeminiProvider({
|
|
317
318
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
319
|
+
model: "models/gemini-2.5-flash",
|
|
318
320
|
});
|
|
319
321
|
|
|
320
322
|
const openaiProvider = new OpenAIProvider({
|
|
321
323
|
apiKey: process.env.OPENAI_API_KEY!,
|
|
324
|
+
model: "gpt-5",
|
|
322
325
|
});
|
|
323
326
|
```
|
|
324
327
|
|