@falai/agent 0.1.0-alpha2 → 0.1.0-alpha3
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 +106 -0
- package/dist/cjs/src/core/BatchExecutor.d.ts +353 -0
- package/dist/cjs/src/core/BatchExecutor.d.ts.map +1 -0
- package/dist/cjs/src/core/BatchExecutor.js +842 -0
- package/dist/cjs/src/core/BatchExecutor.js.map +1 -0
- package/dist/cjs/src/core/BatchPromptBuilder.d.ts +86 -0
- package/dist/cjs/src/core/BatchPromptBuilder.d.ts.map +1 -0
- package/dist/cjs/src/core/BatchPromptBuilder.js +201 -0
- package/dist/cjs/src/core/BatchPromptBuilder.js.map +1 -0
- package/dist/cjs/src/core/ResponseModal.d.ts +34 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponseModal.js +460 -34
- package/dist/cjs/src/core/ResponseModal.js.map +1 -1
- package/dist/cjs/src/index.d.ts +2 -0
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +7 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +9 -1
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/route.d.ts +98 -0
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/src/core/BatchExecutor.d.ts +353 -0
- package/dist/src/core/BatchExecutor.d.ts.map +1 -0
- package/dist/src/core/BatchExecutor.js +837 -0
- package/dist/src/core/BatchExecutor.js.map +1 -0
- package/dist/src/core/BatchPromptBuilder.d.ts +86 -0
- package/dist/src/core/BatchPromptBuilder.d.ts.map +1 -0
- package/dist/src/core/BatchPromptBuilder.js +197 -0
- package/dist/src/core/BatchPromptBuilder.js.map +1 -0
- package/dist/src/core/ResponseModal.d.ts +34 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -1
- package/dist/src/core/ResponseModal.js +460 -34
- package/dist/src/core/ResponseModal.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/agent.d.ts +9 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/route.d.ts +98 -0
- package/dist/src/types/route.d.ts.map +1 -1
- package/docs/api/overview.md +124 -0
- package/docs/architecture/multi-step-execution.md +243 -0
- package/docs/core/ai-integration/prompt-composition.md +135 -0
- package/docs/core/ai-integration/response-processing.md +146 -0
- package/docs/core/conversation-flows/data-collection.md +143 -0
- package/docs/core/conversation-flows/step-transitions.md +132 -0
- package/docs/core/conversation-flows/steps.md +112 -0
- package/docs/core/error-handling.md +193 -0
- package/docs/guides/getting-started/README.md +102 -0
- package/docs/guides/migration/README.md +23 -0
- package/docs/guides/migration/multi-step-execution.md +303 -0
- package/package.json +4 -2
- package/src/core/BatchExecutor.ts +1156 -0
- package/src/core/BatchPromptBuilder.ts +275 -0
- package/src/core/ResponseModal.ts +605 -35
- package/src/index.ts +2 -0
- package/src/types/agent.ts +9 -1
- package/src/types/route.ts +119 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Multi-Step Execution Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
@falai/agent supports **multi-step execution**, enabling multiple consecutive Steps to execute in a single LLM call. This reduces unnecessary multi-turn conversations, minimizes LLM costs, and improves user experience when steps don't require new user input.
|
|
6
|
+
|
|
7
|
+
The core insight is simple: **if a Step's data requirements are already satisfied, there's no reason to pause and wait for user input**. By detecting which Steps can execute together and combining them into a single batch, we reduce LLM calls while improving conversation UX.
|
|
8
|
+
|
|
9
|
+
## Design Priorities
|
|
10
|
+
|
|
11
|
+
1. **Conversation UX** - Reduce unnecessary back-and-forth
|
|
12
|
+
2. **LLM Cost Optimization** - Fewer calls for the same outcome
|
|
13
|
+
3. **Predictability** - Clear, simple rules for when execution pauses
|
|
14
|
+
4. **Framework Understandability** - Keep the Step/Route mental model intact
|
|
15
|
+
|
|
16
|
+
## Batch Execution Flow
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart TD
|
|
20
|
+
A[User Message] --> B[Pre-Extraction]
|
|
21
|
+
B --> C[Batch Determination]
|
|
22
|
+
C --> D{More Steps?}
|
|
23
|
+
D -->|Yes| E[Evaluate Step]
|
|
24
|
+
E --> F{Needs Input?}
|
|
25
|
+
F -->|No| G[Add to Batch]
|
|
26
|
+
G --> D
|
|
27
|
+
F -->|Yes| H[Stop Batch]
|
|
28
|
+
D -->|No| H
|
|
29
|
+
H --> I[Execute Prepare Hooks]
|
|
30
|
+
I --> J[Build Combined Prompt]
|
|
31
|
+
J --> K[Single LLM Call]
|
|
32
|
+
K --> L[Extract Response Data]
|
|
33
|
+
L --> M[Execute Finalize Hooks]
|
|
34
|
+
M --> N[Update Session]
|
|
35
|
+
N --> O[Return Response]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Execution Phases
|
|
39
|
+
|
|
40
|
+
### Phase 1: Pre-Extraction
|
|
41
|
+
|
|
42
|
+
When processing a user message, the engine performs **pre-extraction** before determining the batch. This phase:
|
|
43
|
+
|
|
44
|
+
1. Attempts to extract data for all fields defined in the Route's `requiredFields` and `optionalFields`
|
|
45
|
+
2. Merges pre-extracted data into session data
|
|
46
|
+
3. Maximizes batching by satisfying Step requirements upfront
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// User: "I want to book the Grand Hotel for 2 people next Friday"
|
|
50
|
+
|
|
51
|
+
// Pre-extraction automatically captures:
|
|
52
|
+
{
|
|
53
|
+
hotelName: "Grand Hotel",
|
|
54
|
+
guests: 2,
|
|
55
|
+
date: "next Friday"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Phase 2: Batch Determination
|
|
60
|
+
|
|
61
|
+
The `BatchExecutor` walks through Steps using this algorithm:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
For each Step starting from current position:
|
|
65
|
+
a. Check if it's END_ROUTE → stop with 'end_route'
|
|
66
|
+
b. Evaluate skipIf condition
|
|
67
|
+
c. If skipIf is true → skip Step, continue to next
|
|
68
|
+
d. If skipIf throws error → treat as non-skippable
|
|
69
|
+
e. Evaluate needsInput(step, sessionDataAfterPreExtraction)
|
|
70
|
+
f. If needsInput is false → include Step in batch, continue to next
|
|
71
|
+
g. If needsInput is true → stop with 'needs_input'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Phase 3: Hook Execution (Prepare)
|
|
75
|
+
|
|
76
|
+
Before the LLM call, all `prepare` hooks for batched Steps execute in Step order. If any prepare hook fails, batch execution stops immediately.
|
|
77
|
+
|
|
78
|
+
### Phase 4: LLM Call
|
|
79
|
+
|
|
80
|
+
A single LLM call is made with a combined prompt that includes all Step prompts and collect fields.
|
|
81
|
+
|
|
82
|
+
### Phase 5: Data Collection
|
|
83
|
+
|
|
84
|
+
All `collect` fields from all Steps in the batch are extracted from the LLM response and validated against the agent schema.
|
|
85
|
+
|
|
86
|
+
### Phase 6: Hook Execution (Finalize)
|
|
87
|
+
|
|
88
|
+
After the LLM response, all `finalize` hooks execute in Step order. Finalize hook failures are logged but don't stop execution.
|
|
89
|
+
|
|
90
|
+
## Needs-Input Detection Algorithm
|
|
91
|
+
|
|
92
|
+
The `needsInput` function implements the core execution rule:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
function needsInput<TData>(
|
|
96
|
+
step: Step<unknown, TData>,
|
|
97
|
+
sessionDataAfterPreExtraction: Partial<TData>
|
|
98
|
+
): boolean {
|
|
99
|
+
// Check requires - all must be present (after pre-extraction)
|
|
100
|
+
if (step.requires && step.requires.length > 0) {
|
|
101
|
+
const missingRequired = step.requires.some(
|
|
102
|
+
field => sessionDataAfterPreExtraction[field] === undefined
|
|
103
|
+
);
|
|
104
|
+
if (missingRequired) return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check collect - needs input if collecting and no data exists (after pre-extraction)
|
|
108
|
+
if (step.collect && step.collect.length > 0) {
|
|
109
|
+
const hasAnyCollectData = step.collect.some(
|
|
110
|
+
field => sessionDataAfterPreExtraction[field] !== undefined
|
|
111
|
+
);
|
|
112
|
+
if (!hasAnyCollectData) return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A Step **needs input** when:
|
|
120
|
+
|
|
121
|
+
1. It has `requires` fields and at least one is missing from session data (after pre-extraction)
|
|
122
|
+
2. It has non-empty `collect` fields and none of those fields have data (after pre-extraction)
|
|
123
|
+
|
|
124
|
+
## Pre-Extraction's Role in Batching
|
|
125
|
+
|
|
126
|
+
Pre-extraction is critical for maximizing batch size. Consider this scenario:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Route with 3 steps
|
|
130
|
+
const route = agent.createRoute({
|
|
131
|
+
title: "Booking",
|
|
132
|
+
requiredFields: ["hotel", "date", "guests"],
|
|
133
|
+
steps: [
|
|
134
|
+
{ prompt: "Which hotel?", collect: ["hotel"] },
|
|
135
|
+
{ prompt: "What date?", collect: ["date"] },
|
|
136
|
+
{ prompt: "How many guests?", collect: ["guests"] }
|
|
137
|
+
]
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// User message: "Book Grand Hotel for 2 people on Friday"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Without pre-extraction:**
|
|
144
|
+
- Step 1 needs input (no hotel data) → batch stops
|
|
145
|
+
- 3 separate LLM calls needed
|
|
146
|
+
|
|
147
|
+
**With pre-extraction:**
|
|
148
|
+
- Pre-extraction captures: `{ hotel: "Grand Hotel", date: "Friday", guests: 2 }`
|
|
149
|
+
- All steps have their data satisfied
|
|
150
|
+
- All 3 steps execute in a single batch
|
|
151
|
+
- 1 LLM call needed
|
|
152
|
+
|
|
153
|
+
## Key Components
|
|
154
|
+
|
|
155
|
+
### BatchExecutor
|
|
156
|
+
|
|
157
|
+
The core component responsible for:
|
|
158
|
+
- Determining which Steps can execute together (`determineBatch`)
|
|
159
|
+
- Executing prepare/finalize hooks (`executePrepareHooks`, `executeFinalizeHooks`)
|
|
160
|
+
- Collecting data from LLM responses (`collectBatchData`)
|
|
161
|
+
- Orchestrating complete batch execution (`executeBatch`)
|
|
162
|
+
|
|
163
|
+
### BatchPromptBuilder
|
|
164
|
+
|
|
165
|
+
Combines multiple Step prompts into a single coherent prompt:
|
|
166
|
+
- Preserves each Step's individual prompt intent
|
|
167
|
+
- Includes data collection instructions for all `collect` fields
|
|
168
|
+
- Produces a single LLM call regardless of Step count
|
|
169
|
+
|
|
170
|
+
## Example: Multi-Step Batch
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// User provides all booking info at once
|
|
174
|
+
const response = await agent.respond(
|
|
175
|
+
"I want to book the Grand Hotel for 2 people next Friday"
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Response includes:
|
|
179
|
+
{
|
|
180
|
+
message: "Perfect! I've booked the Grand Hotel for 2 guests on Friday.",
|
|
181
|
+
executedSteps: [
|
|
182
|
+
{ id: "ask-hotel", routeId: "booking" },
|
|
183
|
+
{ id: "ask-date", routeId: "booking" },
|
|
184
|
+
{ id: "ask-guests", routeId: "booking" }
|
|
185
|
+
],
|
|
186
|
+
stoppedReason: "route_complete",
|
|
187
|
+
session: {
|
|
188
|
+
data: {
|
|
189
|
+
hotel: "Grand Hotel",
|
|
190
|
+
date: "Friday",
|
|
191
|
+
guests: 2
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Debugging
|
|
198
|
+
|
|
199
|
+
Enable debug mode to see batch execution decisions:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const agent = new Agent({
|
|
203
|
+
name: "Assistant",
|
|
204
|
+
provider: provider,
|
|
205
|
+
debug: true // Enable detailed logging
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Look for these log messages:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
[BatchExecutor] Starting batch determination from step index 0
|
|
213
|
+
[BatchExecutor] Including step ask-hotel in batch (all requirements satisfied)
|
|
214
|
+
[BatchExecutor] Including step ask-date in batch (all requirements satisfied)
|
|
215
|
+
[BatchExecutor] Step ask-guests needs input, stopping batch
|
|
216
|
+
[BatchExecutor] Batch determination complete. Steps: 2, Stopped reason: needs_input
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Event Emission
|
|
220
|
+
|
|
221
|
+
The BatchExecutor emits events for observability:
|
|
222
|
+
|
|
223
|
+
- `batch_start` - When batch determination begins
|
|
224
|
+
- `step_included` - When a step is included in the batch
|
|
225
|
+
- `step_skipped` - When a step is skipped (due to skipIf)
|
|
226
|
+
- `batch_stop` - When batch determination stops
|
|
227
|
+
- `batch_complete` - When batch execution completes
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const executor = new BatchExecutor();
|
|
231
|
+
|
|
232
|
+
executor.addEventListener((event) => {
|
|
233
|
+
console.log(`[${event.type}]`, event.details);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
**Related Documentation:**
|
|
240
|
+
- [Steps](../core/conversation-flows/steps.md) - Step configuration and batching
|
|
241
|
+
- [Step Transitions](../core/conversation-flows/step-transitions.md) - Stopping conditions
|
|
242
|
+
- [Data Collection](../core/conversation-flows/data-collection.md) - Pre-extraction details
|
|
243
|
+
- [Prompt Composition](../core/ai-integration/prompt-composition.md) - Combined prompts
|
|
@@ -218,3 +218,138 @@ Prompts are optimized for token efficiency:
|
|
|
218
218
|
- Leverage route-specific overrides for specialized behavior
|
|
219
219
|
- Monitor token usage and optimize prompt length
|
|
220
220
|
- Test prompts with different AI providers for consistency
|
|
221
|
+
|
|
222
|
+
## BatchPromptBuilder for Multi-Step Execution
|
|
223
|
+
|
|
224
|
+
When multiple steps execute in a single batch, the `BatchPromptBuilder` combines their prompts into a single coherent prompt.
|
|
225
|
+
|
|
226
|
+
### Combined Prompt Structure
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
[Agent Identity & Personality]
|
|
230
|
+
[Route Context]
|
|
231
|
+
|
|
232
|
+
## Current Conversation Flow
|
|
233
|
+
|
|
234
|
+
You are handling multiple aspects of this conversation in a single response.
|
|
235
|
+
|
|
236
|
+
### Step 1: [Step Description]
|
|
237
|
+
[Step Prompt]
|
|
238
|
+
|
|
239
|
+
### Step 2: [Step Description]
|
|
240
|
+
[Step Prompt]
|
|
241
|
+
|
|
242
|
+
[... additional steps ...]
|
|
243
|
+
|
|
244
|
+
## Data Collection
|
|
245
|
+
|
|
246
|
+
Extract the following information from your response:
|
|
247
|
+
- field1 (type): description
|
|
248
|
+
- field2 (type): description
|
|
249
|
+
|
|
250
|
+
## Response Format
|
|
251
|
+
|
|
252
|
+
Return JSON with:
|
|
253
|
+
- message: Your response to the user
|
|
254
|
+
- [collected fields as top-level properties]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### How Prompts Are Merged
|
|
258
|
+
|
|
259
|
+
The `BatchPromptBuilder` preserves each step's intent while creating a unified prompt:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Individual step prompts
|
|
263
|
+
const step1 = { prompt: "What's your name?", collect: ["name"] };
|
|
264
|
+
const step2 = { prompt: "What's your email?", collect: ["email"] };
|
|
265
|
+
const step3 = { prompt: "How can I help?", collect: ["request"] };
|
|
266
|
+
|
|
267
|
+
// Combined prompt includes all three
|
|
268
|
+
const result = await batchPromptBuilder.buildBatchPrompt({
|
|
269
|
+
steps: [step1, step2, step3],
|
|
270
|
+
route,
|
|
271
|
+
history,
|
|
272
|
+
context,
|
|
273
|
+
session,
|
|
274
|
+
agentOptions,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// result.prompt contains unified prompt
|
|
278
|
+
// result.collectFields = ["name", "email", "request"]
|
|
279
|
+
// result.stepCount = 3
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Collect Fields Aggregation
|
|
283
|
+
|
|
284
|
+
All `collect` fields from all steps are combined and deduplicated:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// Steps with overlapping collect fields
|
|
288
|
+
const steps = [
|
|
289
|
+
{ collect: ["name", "email"] },
|
|
290
|
+
{ collect: ["email", "phone"] }, // email appears twice
|
|
291
|
+
{ collect: ["preferences"] }
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
// Combined collect fields (deduplicated)
|
|
295
|
+
// ["name", "email", "phone", "preferences"]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Schema-Aware Field Descriptions
|
|
299
|
+
|
|
300
|
+
When the agent has a schema, field descriptions are included in the prompt:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Agent schema
|
|
304
|
+
const schema = {
|
|
305
|
+
properties: {
|
|
306
|
+
email: { type: "string", format: "email", description: "User's email address" },
|
|
307
|
+
guests: { type: "number", minimum: 1, description: "Number of guests" }
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Generated data collection section:
|
|
312
|
+
// ## Data Collection
|
|
313
|
+
// Extract the following information from your response:
|
|
314
|
+
// - email (string): User's email address
|
|
315
|
+
// - guests (number): Number of guests
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Single vs Multi-Step Prompts
|
|
319
|
+
|
|
320
|
+
The prompt structure adapts based on batch size:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Single step batch
|
|
324
|
+
// ## Current Step
|
|
325
|
+
// [Step prompt]
|
|
326
|
+
|
|
327
|
+
// Multi-step batch
|
|
328
|
+
// ## Current Conversation Flow
|
|
329
|
+
// You are handling multiple aspects of this conversation in a single response.
|
|
330
|
+
// ### Step 1: ...
|
|
331
|
+
// ### Step 2: ...
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Using BatchPromptBuilder
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { BatchPromptBuilder } from "@falai/agent";
|
|
338
|
+
|
|
339
|
+
const builder = new BatchPromptBuilder<MyContext, MyData>();
|
|
340
|
+
|
|
341
|
+
const result = await builder.buildBatchPrompt({
|
|
342
|
+
steps: batchResult.steps,
|
|
343
|
+
route: currentRoute,
|
|
344
|
+
history: conversationHistory,
|
|
345
|
+
context: agentContext,
|
|
346
|
+
session: currentSession,
|
|
347
|
+
agentOptions: agent.getAgentOptions(),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Use result.prompt for LLM call
|
|
351
|
+
const llmResponse = await provider.generateMessage({
|
|
352
|
+
prompt: result.prompt,
|
|
353
|
+
// ...
|
|
354
|
+
});
|
|
355
|
+
```
|
|
@@ -277,6 +277,150 @@ Built-in monitoring capabilities:
|
|
|
277
277
|
- **Debug logging** - Detailed processing traces
|
|
278
278
|
- **Performance profiling** - Identify bottlenecks
|
|
279
279
|
|
|
280
|
+
## Batch Execution Response Fields
|
|
281
|
+
|
|
282
|
+
When using multi-step execution, the `AgentResponse` includes additional fields for batch execution information.
|
|
283
|
+
|
|
284
|
+
### New AgentResponse Fields
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
interface AgentResponse<TData = unknown> {
|
|
288
|
+
/** The generated message */
|
|
289
|
+
message: string;
|
|
290
|
+
/** Updated session state */
|
|
291
|
+
session?: SessionState<TData>;
|
|
292
|
+
/** Tool calls made during response */
|
|
293
|
+
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
294
|
+
/** Whether the route is complete */
|
|
295
|
+
isRouteComplete?: boolean;
|
|
296
|
+
|
|
297
|
+
// Multi-step execution fields
|
|
298
|
+
/** Steps executed in this response */
|
|
299
|
+
executedSteps?: StepRef[];
|
|
300
|
+
/** Why execution stopped */
|
|
301
|
+
stoppedReason?: StoppedReason;
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### executedSteps Array
|
|
306
|
+
|
|
307
|
+
Lists all steps that were executed in the batch:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const response = await agent.respond("Book Grand Hotel for 2 on Friday");
|
|
311
|
+
|
|
312
|
+
console.log(response.executedSteps);
|
|
313
|
+
// [
|
|
314
|
+
// { id: "ask-hotel", routeId: "booking" },
|
|
315
|
+
// { id: "ask-guests", routeId: "booking" },
|
|
316
|
+
// { id: "ask-date", routeId: "booking" }
|
|
317
|
+
// ]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Each `StepRef` contains:
|
|
321
|
+
- `id` - The step identifier
|
|
322
|
+
- `routeId` - The route this step belongs to
|
|
323
|
+
|
|
324
|
+
### stoppedReason Field
|
|
325
|
+
|
|
326
|
+
Indicates why batch execution stopped:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
type StoppedReason =
|
|
330
|
+
| 'needs_input' // Step requires uncollected data
|
|
331
|
+
| 'end_route' // Reached END_ROUTE
|
|
332
|
+
| 'route_complete' // All Steps processed
|
|
333
|
+
| 'prepare_error' // Error in prepare hook
|
|
334
|
+
| 'llm_error' // Error during LLM call
|
|
335
|
+
| 'validation_error' // Error validating collected data
|
|
336
|
+
| 'finalize_error'; // Error in finalize hook (non-fatal)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### BatchExecutionResult Structure
|
|
340
|
+
|
|
341
|
+
The internal batch execution result provides detailed information:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
interface BatchExecutionResult<TData = unknown> {
|
|
345
|
+
/** The generated message */
|
|
346
|
+
message: string;
|
|
347
|
+
/** Updated session state */
|
|
348
|
+
session: SessionState<TData>;
|
|
349
|
+
/** Steps that were executed */
|
|
350
|
+
executedSteps: StepRef[];
|
|
351
|
+
/** Why execution stopped */
|
|
352
|
+
stoppedReason: StoppedReason;
|
|
353
|
+
/** Collected data from the batch */
|
|
354
|
+
collectedData?: Partial<TData>;
|
|
355
|
+
/** Any errors that occurred */
|
|
356
|
+
error?: BatchExecutionError;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Error Information
|
|
361
|
+
|
|
362
|
+
When errors occur, detailed information is available:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
interface BatchExecutionError {
|
|
366
|
+
/** Type of error that occurred */
|
|
367
|
+
type: 'pre_extraction' | 'skipif_evaluation' | 'prepare_hook' |
|
|
368
|
+
'llm_call' | 'data_validation' | 'finalize_hook';
|
|
369
|
+
/** Error message */
|
|
370
|
+
message: string;
|
|
371
|
+
/** Step where error occurred (if applicable) */
|
|
372
|
+
stepId?: string;
|
|
373
|
+
/** Additional error details */
|
|
374
|
+
details?: unknown;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Using Response Fields
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
const response = await agent.respond("Complete my booking");
|
|
382
|
+
|
|
383
|
+
// Check what was executed
|
|
384
|
+
console.log(`Executed ${response.executedSteps?.length || 0} steps`);
|
|
385
|
+
|
|
386
|
+
// Check why execution stopped
|
|
387
|
+
switch (response.stoppedReason) {
|
|
388
|
+
case 'needs_input':
|
|
389
|
+
console.log("Waiting for more information from user");
|
|
390
|
+
break;
|
|
391
|
+
case 'route_complete':
|
|
392
|
+
case 'end_route':
|
|
393
|
+
console.log("Route finished successfully");
|
|
394
|
+
break;
|
|
395
|
+
case 'validation_error':
|
|
396
|
+
console.log("Data validation issues:", response.error);
|
|
397
|
+
break;
|
|
398
|
+
default:
|
|
399
|
+
console.log("Stopped due to:", response.stoppedReason);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Check route completion
|
|
403
|
+
if (response.isRouteComplete) {
|
|
404
|
+
console.log("All required data collected");
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Session State After Batch
|
|
409
|
+
|
|
410
|
+
The session state reflects the final step position:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
const response = await agent.respond("Book for 2 guests");
|
|
414
|
+
|
|
415
|
+
// Session shows current position
|
|
416
|
+
console.log(response.session?.currentStep);
|
|
417
|
+
// { id: "ask-date", routeId: "booking" }
|
|
418
|
+
|
|
419
|
+
// Session data includes all collected values
|
|
420
|
+
console.log(response.session?.data);
|
|
421
|
+
// { hotel: "Grand Hotel", guests: 2 }
|
|
422
|
+
```
|
|
423
|
+
|
|
280
424
|
## Best Practices
|
|
281
425
|
|
|
282
426
|
- Design schemas for reliable AI extraction
|
|
@@ -285,3 +429,5 @@ Built-in monitoring capabilities:
|
|
|
285
429
|
- Use streaming for better user experience
|
|
286
430
|
- Leverage tool results for context enrichment
|
|
287
431
|
- Validate data at multiple levels (schema + business rules)
|
|
432
|
+
- Check `executedSteps` to understand batch behavior
|
|
433
|
+
- Handle different `stoppedReason` values appropriately
|
|
@@ -13,6 +13,149 @@ The agent-level data collection system provides:
|
|
|
13
13
|
- **Natural Conversations**: AI handles information gathering conversationally
|
|
14
14
|
- **Validation & Enrichment**: Agent-level lifecycle hooks for data processing
|
|
15
15
|
- **Session Persistence**: Data survives across conversation turns and route transitions
|
|
16
|
+
- **Batch Data Collection**: Multiple steps can collect data in a single LLM call
|
|
17
|
+
|
|
18
|
+
## Data Collection Across Batched Steps
|
|
19
|
+
|
|
20
|
+
When multiple steps execute in a single batch, data collection works across all steps simultaneously.
|
|
21
|
+
|
|
22
|
+
### How Batch Data Collection Works
|
|
23
|
+
|
|
24
|
+
1. **Gather collect fields** - All `collect` fields from all steps in the batch are combined
|
|
25
|
+
2. **Single LLM call** - The combined prompt instructs the LLM to extract all fields
|
|
26
|
+
3. **Extract from response** - All specified fields are extracted from the LLM response
|
|
27
|
+
4. **Validate against schema** - Collected data is validated against the agent schema
|
|
28
|
+
5. **Update session** - All collected values are merged into session data
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Batch with 3 steps, each collecting different fields
|
|
32
|
+
const batch = [
|
|
33
|
+
{ collect: ["name"] },
|
|
34
|
+
{ collect: ["email", "phone"] },
|
|
35
|
+
{ collect: ["preferences"] }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Combined collection: ["name", "email", "phone", "preferences"]
|
|
39
|
+
// Single LLM response extracts all fields at once
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Pre-Extraction and Batch Determination
|
|
43
|
+
|
|
44
|
+
Pre-extraction happens **before** batch determination and directly impacts which steps can be batched:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// User message: "I'm John, email john@example.com, I prefer dark mode"
|
|
48
|
+
|
|
49
|
+
// Phase 1: Pre-extraction
|
|
50
|
+
const preExtracted = {
|
|
51
|
+
name: "John",
|
|
52
|
+
email: "john@example.com",
|
|
53
|
+
preferences: { theme: "dark" }
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Phase 2: Batch determination (with pre-extracted data merged)
|
|
57
|
+
// Step 1: collect: ["name"] → name exists → doesn't need input
|
|
58
|
+
// Step 2: collect: ["email"] → email exists → doesn't need input
|
|
59
|
+
// Step 3: collect: ["preferences"] → preferences exists → doesn't need input
|
|
60
|
+
// Result: All 3 steps batched together
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Pre-Extraction Configuration
|
|
64
|
+
|
|
65
|
+
Pre-extraction is automatic when routes define data fields:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Option 1: Route-level required fields
|
|
69
|
+
agent.createRoute({
|
|
70
|
+
title: "Booking",
|
|
71
|
+
requiredFields: ["hotel", "date", "guests"],
|
|
72
|
+
// Pre-extraction enabled automatically
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Option 2: Route-level optional fields
|
|
76
|
+
agent.createRoute({
|
|
77
|
+
title: "Booking",
|
|
78
|
+
optionalFields: ["specialRequests"],
|
|
79
|
+
// Pre-extraction enabled automatically
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Option 3: Steps with collect arrays
|
|
83
|
+
agent.createRoute({
|
|
84
|
+
title: "Booking",
|
|
85
|
+
steps: [
|
|
86
|
+
{ collect: ["hotel"] }, // Pre-extraction enabled automatically
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Batch Collection Example
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const response = await agent.respond(
|
|
95
|
+
"I'm Alice, alice@example.com, and I want to book for 2 guests"
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Response includes all collected data
|
|
99
|
+
console.log(response.session.data);
|
|
100
|
+
// {
|
|
101
|
+
// name: "Alice",
|
|
102
|
+
// email: "alice@example.com",
|
|
103
|
+
// guests: 2
|
|
104
|
+
// }
|
|
105
|
+
|
|
106
|
+
// Shows which steps executed
|
|
107
|
+
console.log(response.executedSteps);
|
|
108
|
+
// [
|
|
109
|
+
// { id: "ask-name", routeId: "booking" },
|
|
110
|
+
// { id: "ask-email", routeId: "booking" },
|
|
111
|
+
// { id: "ask-guests", routeId: "booking" }
|
|
112
|
+
// ]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Validation Across Batch
|
|
116
|
+
|
|
117
|
+
Data validation happens after collection for all fields:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Agent schema defines validation rules
|
|
121
|
+
const agent = new Agent({
|
|
122
|
+
schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
email: { type: "string", format: "email" },
|
|
126
|
+
guests: { type: "number", minimum: 1, maximum: 10 }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// If validation fails for any field:
|
|
132
|
+
const response = await agent.respond("Book for 100 guests");
|
|
133
|
+
|
|
134
|
+
// Response includes validation errors
|
|
135
|
+
if (response.stoppedReason === 'validation_error') {
|
|
136
|
+
console.log(response.error);
|
|
137
|
+
// {
|
|
138
|
+
// type: 'data_validation',
|
|
139
|
+
// message: 'Validation failed for 1 field(s): guests',
|
|
140
|
+
// details: [{ field: 'guests', message: 'Value exceeds maximum of 10' }]
|
|
141
|
+
// }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Partial Data Preservation
|
|
146
|
+
|
|
147
|
+
Even when validation fails, valid partial data is preserved:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// User provides valid name but invalid email
|
|
151
|
+
const response = await agent.respond("I'm John, email: not-an-email");
|
|
152
|
+
|
|
153
|
+
// Valid data is still collected
|
|
154
|
+
console.log(response.session.data.name); // "John"
|
|
155
|
+
|
|
156
|
+
// Invalid data triggers validation error
|
|
157
|
+
console.log(response.stoppedReason); // "validation_error"
|
|
158
|
+
```
|
|
16
159
|
|
|
17
160
|
## Agent-Level Schema Definition
|
|
18
161
|
|