@botpress/adk-cli 1.19.0-beta.4 → 1.19.0-beta.5
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/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
- package/dist/SKILL-75ym6tcj.md +257 -0
- package/dist/SKILL-chnwzmt1.md +548 -0
- package/dist/SKILL-d4bs53y3.md +244 -0
- package/dist/SKILL-h4b2f1n9.md +114 -0
- package/dist/SKILL-hbw5nt95.md +111 -0
- package/dist/actions-h7hxxvxr.md +823 -0
- package/dist/adk-build-emhwd0tg.md +39 -0
- package/dist/adk-debug-yrrdtpmb.md +23 -0
- package/dist/adk-dev-console-7z9rtjpy.md +17 -0
- package/dist/adk-document-ras6zzzh.md +68 -0
- package/dist/adk-eval-vx76dwq0.md +17 -0
- package/dist/adk-explain-fcsaw852.md +22 -0
- package/dist/adk-init-9kmxmbbe.md +30 -0
- package/dist/adk-ship-dybzn884.md +29 -0
- package/dist/adk-test-xq67savs.md +27 -0
- package/dist/adk-validate-1p4skv4m.md +31 -0
- package/dist/advanced-patterns-z86m9h1a.md +669 -0
- package/dist/agent-config-1aqvkrgf.md +450 -0
- package/dist/agent-steps-zj4c1n9t.md +115 -0
- package/dist/assets-q7jt8r2q.md +237 -0
- package/dist/autonomous-execution-fymc12e5.md +747 -0
- package/dist/cli-c31e06yr.md +1334 -0
- package/dist/cli.js +970 -566
- package/dist/commands.json +24 -3
- package/dist/common-failures-ht29w2db.md +231 -0
- package/dist/component-registry-bbpw0see.md +43 -0
- package/dist/context-api-760cvh0h.md +461 -0
- package/dist/conversation-analysis-8ka4nrm4.md +420 -0
- package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
- package/dist/conversations-gwfvxaee.md +1111 -0
- package/dist/custom-components-y5wdgpey.md +167 -0
- package/dist/debug-workflow-vz84q80v.md +239 -0
- package/dist/desk-k54ktvsd.md +852 -0
- package/dist/doc-standards-ck6ax2jd.md +124 -0
- package/dist/eval-format-7n9brja4.md +354 -0
- package/dist/evals-y1zcr1mc.md +11 -0
- package/dist/explain-config-1yaq3zzd.md +424 -0
- package/dist/files-dzt0wxn3.md +918 -0
- package/dist/integration-actions-xang7tma.md +659 -0
- package/dist/integrations-k5ss07f4.md +266 -0
- package/dist/interfaces-6t0bpxs4.md +97 -0
- package/dist/knowledge-bases-20rg8p5c.md +456 -0
- package/dist/llm-debugging-76ng8rzm.md +209 -0
- package/dist/mcp-server-n1asyzhv.md +112 -0
- package/dist/messages-gkkg0b54.md +769 -0
- package/dist/model-configuration-f273kgd0.md +201 -0
- package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
- package/dist/pages-xn18xn73.md +273 -0
- package/dist/patterns-mistakes-5vb99dhf.md +469 -0
- package/dist/plugins-ttnx8f49.md +280 -0
- package/dist/tables-x45t36dd.md +1136 -0
- package/dist/tags-46td3qrg.md +516 -0
- package/dist/test-patterns-hj47w4nc.md +355 -0
- package/dist/testing-workflow-2vkp932f.md +225 -0
- package/dist/tools-wk7bq24z.md +581 -0
- package/dist/trace-summarization-hdyx1c2e.md +313 -0
- package/dist/traces-and-logs-f2kjfh19.md +203 -0
- package/dist/triggers-4j2cc86x.md +719 -0
- package/dist/ui-server-entry.js +952 -137
- package/dist/workflow-steps-mrdrf1c5.md +851 -0
- package/dist/workflows-gadx3s69.md +1408 -0
- package/dist/zai-agent-reference-e6644aad.md +454 -0
- package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
- package/package.json +4 -4
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
# Advanced Patterns
|
|
2
|
+
|
|
3
|
+
Production-ready patterns for building sophisticated ADK agents with guardrails, authentication, observability, and modular architecture.
|
|
4
|
+
|
|
5
|
+
## Pattern 1: Guardrails
|
|
6
|
+
|
|
7
|
+
Enforce behavioral constraints on autonomous agents to prevent unwanted actions and ensure data validation.
|
|
8
|
+
|
|
9
|
+
### Use Cases
|
|
10
|
+
|
|
11
|
+
- Ensure knowledge search before answering questions
|
|
12
|
+
- Validate preconditions before executing actions (e.g., credit checks before purchases)
|
|
13
|
+
- Prevent hallucinations by enforcing data lookup
|
|
14
|
+
|
|
15
|
+
### Implementation
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// src/conversations/extensions/guardrails.ts
|
|
19
|
+
import { adk, Autonomous } from "@botpress/runtime";
|
|
20
|
+
|
|
21
|
+
export const makeGuardrails = (message: any) => {
|
|
22
|
+
// Use zai to determine if knowledge search is needed
|
|
23
|
+
const isKnowledgeSearchAsync = adk.zai.check(
|
|
24
|
+
message,
|
|
25
|
+
`Is this a question that requires knowledge search?`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let hasSearched = false;
|
|
29
|
+
|
|
30
|
+
const onBeforeToolGuard: Autonomous.Hooks["onBeforeTool"] = async ({
|
|
31
|
+
iteration,
|
|
32
|
+
tool,
|
|
33
|
+
input,
|
|
34
|
+
controller,
|
|
35
|
+
}) => {
|
|
36
|
+
if (tool.name === "search_knowledge") {
|
|
37
|
+
hasSearched = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isKnowledgeSearch = await isKnowledgeSearchAsync;
|
|
41
|
+
|
|
42
|
+
if (tool.name === "Message" && isKnowledgeSearch && !hasSearched) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Knowledge search is required for this question. Please use "search_knowledge" first.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return { onBeforeToolGuard };
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> **Note:** `adk.zai.check()` is the recommended approach for AI-powered validation. An advanced pattern `adk.zai.learn().check()` exists for cached/reusable checks, but `adk.zai.check()` is simpler and covers most cases. See [zai-agent-reference.md](./zai-agent-reference.md) for all Zai operations and edge cases.
|
|
54
|
+
|
|
55
|
+
### Usage in Conversation
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// src/conversations/webchat.ts
|
|
59
|
+
import { Conversation } from "@botpress/runtime";
|
|
60
|
+
import { makeGuardrails } from "./extensions/guardrails";
|
|
61
|
+
|
|
62
|
+
export default new Conversation({
|
|
63
|
+
channel: "*",
|
|
64
|
+
handler: async ({ execute, message }) => {
|
|
65
|
+
const guardrail = makeGuardrails(message);
|
|
66
|
+
|
|
67
|
+
await execute({
|
|
68
|
+
instructions: `You are a helpful assistant...`,
|
|
69
|
+
hooks: {
|
|
70
|
+
onBeforeTool: async (props) => guardrail.onBeforeToolGuard(props),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Variations
|
|
78
|
+
|
|
79
|
+
**Credit Check Guardrail:**
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
export const makeCreditCheckGuardrail = () => {
|
|
83
|
+
let creditChecked = false;
|
|
84
|
+
|
|
85
|
+
const onBeforeToolGuard: Autonomous.Hooks["onBeforeTool"] = async ({
|
|
86
|
+
iteration,
|
|
87
|
+
tool,
|
|
88
|
+
input,
|
|
89
|
+
controller,
|
|
90
|
+
}) => {
|
|
91
|
+
if (tool.name === "check_credit") {
|
|
92
|
+
creditChecked = true;
|
|
93
|
+
}
|
|
94
|
+
if (tool.name === "process_payment" && !creditChecked) {
|
|
95
|
+
throw new Error("Credit check must be performed before processing payment");
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return { onBeforeToolGuard };
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Data Validation Guardrail:**
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export const makeDataValidationGuardrail = () => {
|
|
107
|
+
const onBeforeToolGuard: Autonomous.Hooks["onBeforeTool"] = async ({
|
|
108
|
+
iteration,
|
|
109
|
+
tool,
|
|
110
|
+
input,
|
|
111
|
+
controller,
|
|
112
|
+
}) => {
|
|
113
|
+
if (tool.name === "send_email") {
|
|
114
|
+
const hasValidEmail = await adk.zai.check(
|
|
115
|
+
input,
|
|
116
|
+
"Does this contain a valid email address?"
|
|
117
|
+
);
|
|
118
|
+
if (!hasValidEmail) {
|
|
119
|
+
throw new Error("Invalid email address provided");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return { onBeforeToolGuard };
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Key Techniques
|
|
129
|
+
|
|
130
|
+
1. **Async Validation**: Use `adk.zai.check()` for AI-powered validation
|
|
131
|
+
2. **State Tracking**: Track actions with closure variables (`hasSearched`)
|
|
132
|
+
3. **Hook Interception**: Use `onBeforeTool` to intercept and validate tool calls
|
|
133
|
+
4. **Error Throwing**: Throw errors to prevent unwanted tool execution
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Pattern 2: Admin Mode
|
|
138
|
+
|
|
139
|
+
Implement privileged access control with temporary authentication for administrative functions.
|
|
140
|
+
|
|
141
|
+
### Use Cases
|
|
142
|
+
|
|
143
|
+
- Knowledge base re-indexing
|
|
144
|
+
- Agent configuration changes
|
|
145
|
+
- Debugging tools for support teams
|
|
146
|
+
- System maintenance operations
|
|
147
|
+
|
|
148
|
+
### Implementation
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// src/conversations/extensions/admin-mode.ts
|
|
152
|
+
import { adk, Autonomous, context, user, z } from "@botpress/runtime";
|
|
153
|
+
|
|
154
|
+
// Define user state schema for admin mode
|
|
155
|
+
export const AdminModeUserSchema: z.ZodRawShape = {
|
|
156
|
+
admin: z
|
|
157
|
+
.object({
|
|
158
|
+
adminUtil: z.string().nullable().describe("Expiration date of admin status (ISO)."),
|
|
159
|
+
code: z.string().nullable().describe("The admin access code."),
|
|
160
|
+
codeValidUntil: z.string().nullable().describe("Expiration date of admin code (ISO)."),
|
|
161
|
+
})
|
|
162
|
+
.default({ adminUtil: null, code: null, codeValidUntil: null }),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Admin-only tool: refresh knowledge bases
|
|
166
|
+
const getIndexKnowledgeBasesTool = () => {
|
|
167
|
+
const ctx = context.getAll();
|
|
168
|
+
return new Autonomous.Tool({
|
|
169
|
+
name: "refreshKnowledgeBases",
|
|
170
|
+
description: "Refresh and re-index all knowledge bases.",
|
|
171
|
+
output: z.string().describe("Confirmation message after refreshing."),
|
|
172
|
+
handler: async () => {
|
|
173
|
+
context.enterWith(ctx); // Restore context for async operations
|
|
174
|
+
await Promise.all(adk.project.knowledge.map((kb) => kb.refresh()));
|
|
175
|
+
return `Started re-indexing: ${adk.project.knowledge.map((kb) => kb.name).join(", ")}`;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Generate one-time login code
|
|
181
|
+
const getLoginTool = () => {
|
|
182
|
+
const ctx = context.getAll();
|
|
183
|
+
user.state ??= {};
|
|
184
|
+
|
|
185
|
+
const CODE_VALIDITY_DURATION_MS = 5 * 60 * 1000; // 5 minutes
|
|
186
|
+
|
|
187
|
+
const expectedCode = user.state.admin?.code?.toLowerCase().trim();
|
|
188
|
+
const codeGenerated =
|
|
189
|
+
expectedCode &&
|
|
190
|
+
user.state.admin?.codeValidUntil &&
|
|
191
|
+
new Date(user.state.admin?.codeValidUntil) > new Date();
|
|
192
|
+
|
|
193
|
+
if (codeGenerated) {
|
|
194
|
+
return new Autonomous.Tool({
|
|
195
|
+
name: "loginWithCode",
|
|
196
|
+
description: "Log in as admin using an access code.",
|
|
197
|
+
input: z.string().describe('The admin access code, e.g. "ABC346"'),
|
|
198
|
+
output: z.boolean().describe("Returns true if login is successful."),
|
|
199
|
+
handler: async (code: string) => {
|
|
200
|
+
context.enterWith(ctx);
|
|
201
|
+
const providedCode = code.toLowerCase().trim();
|
|
202
|
+
|
|
203
|
+
if (expectedCode && providedCode === expectedCode) {
|
|
204
|
+
user.state.admin = {
|
|
205
|
+
adminUtil: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
206
|
+
code: null,
|
|
207
|
+
codeValidUntil: null,
|
|
208
|
+
};
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
throw new Autonomous.ThinkSignal("Invalid or expired admin access code");
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return new Autonomous.Tool({
|
|
218
|
+
name: "generateLoginCode",
|
|
219
|
+
description: "Generate a one-time access code for admin login.",
|
|
220
|
+
handler: async () => {
|
|
221
|
+
context.enterWith(ctx);
|
|
222
|
+
const generatedCode = Math.random().toString(36).substring(2, 8).toUpperCase();
|
|
223
|
+
|
|
224
|
+
user.state.admin = {
|
|
225
|
+
adminUtil: null,
|
|
226
|
+
code: generatedCode,
|
|
227
|
+
codeValidUntil: new Date(Date.now() + CODE_VALIDITY_DURATION_MS).toISOString(),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
console.log(`Generated admin login code: ${generatedCode}`);
|
|
231
|
+
|
|
232
|
+
throw new Autonomous.ThinkSignal(
|
|
233
|
+
`An admin login code has been generated and logged in the developer console. Retrieve it from the Botpress dashboard.`
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function isUserAdmin() {
|
|
240
|
+
return (
|
|
241
|
+
user.state.admin?.adminUtil &&
|
|
242
|
+
new Date(user.state.admin.adminUtil) > new Date()
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const getAdminModeObject = () =>
|
|
247
|
+
new Autonomous.Object({
|
|
248
|
+
name: "admin",
|
|
249
|
+
description: getAdminStatus(), // Dynamic description based on state
|
|
250
|
+
tools: isUserAdmin() ? [getIndexKnowledgeBasesTool()] : [getLoginTool()],
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Configuration in agent.config.ts
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { z, defineConfig } from "@botpress/runtime";
|
|
258
|
+
import { AdminModeUserSchema } from "./src/conversations/extensions/admin-mode";
|
|
259
|
+
|
|
260
|
+
export default defineConfig({
|
|
261
|
+
name: "my-agent",
|
|
262
|
+
user: {
|
|
263
|
+
state: z.object({}).extend(AdminModeUserSchema),
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Usage in Conversation
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { Conversation } from "@botpress/runtime";
|
|
272
|
+
import { getAdminModeObject } from "./extensions/admin-mode";
|
|
273
|
+
|
|
274
|
+
export default new Conversation({
|
|
275
|
+
channel: "*",
|
|
276
|
+
handler: async ({ execute }) => {
|
|
277
|
+
await execute({
|
|
278
|
+
instructions: `You are a helpful assistant...`,
|
|
279
|
+
objects: [getAdminModeObject()],
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Key Techniques
|
|
286
|
+
|
|
287
|
+
1. **Dynamic Tools**: Tools change based on authentication state
|
|
288
|
+
2. **Time-Based Auth**: Expiration timestamps for codes and sessions (codes: 5 min, sessions: 1 hour)
|
|
289
|
+
3. **Context Restoration**: `context.enterWith()` for async operations
|
|
290
|
+
4. **Think Signals**: Use `Autonomous.ThinkSignal` to control agent reasoning flow
|
|
291
|
+
5. **Console Logging**: Log codes for secure out-of-band delivery (not shown in chat)
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Pattern 3: Logging & Observability
|
|
296
|
+
|
|
297
|
+
Comprehensive logging and error tracking for production agents.
|
|
298
|
+
|
|
299
|
+
### Basic Trace Logging
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// src/conversations/extensions/logging.ts
|
|
303
|
+
import { Autonomous } from "@botpress/runtime";
|
|
304
|
+
|
|
305
|
+
export const onTraceLogging: Autonomous.Hooks["onTrace"] = ({ trace, iteration }) => {
|
|
306
|
+
if (trace.type === "code_execution_exception") {
|
|
307
|
+
console.error(`Code Execution Error: ${trace.message}`, trace.stackTrace);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (trace.type === "tool_call" && !trace.success) {
|
|
311
|
+
console.error(
|
|
312
|
+
`Error during tool call to "${trace.tool_name}" with input "${JSON.stringify(trace.input)}":`,
|
|
313
|
+
trace.error
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Usage in Conversation
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { Conversation } from "@botpress/runtime";
|
|
323
|
+
import { onTraceLogging } from "./extensions/logging";
|
|
324
|
+
|
|
325
|
+
export default new Conversation({
|
|
326
|
+
channel: "*",
|
|
327
|
+
handler: async ({ execute }) => {
|
|
328
|
+
await execute({
|
|
329
|
+
instructions: `You are a helpful assistant...`,
|
|
330
|
+
hooks: {
|
|
331
|
+
onTrace: (props) => onTraceLogging!(props),
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Advanced: Comprehensive Trace Logging
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
export const onTraceLogging: Autonomous.Hooks["onTrace"] = ({ trace, iteration }) => {
|
|
342
|
+
switch (trace.type) {
|
|
343
|
+
case "code_execution_exception":
|
|
344
|
+
console.error(`[CODE ERROR] ${trace.message}`, trace.stackTrace);
|
|
345
|
+
break;
|
|
346
|
+
case "tool_call":
|
|
347
|
+
if (trace.success) {
|
|
348
|
+
console.log(`[TOOL SUCCESS] ${trace.tool_name}`, trace.output);
|
|
349
|
+
} else {
|
|
350
|
+
console.error(`[TOOL ERROR] ${trace.tool_name}`, trace.error);
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
case "think":
|
|
354
|
+
console.debug(`[THINK] ${trace.content}`);
|
|
355
|
+
break;
|
|
356
|
+
default:
|
|
357
|
+
console.log(`[TRACE] ${trace.type}`, trace);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Advanced: Performance Monitoring
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
export const makePerformanceMonitor = () => {
|
|
366
|
+
const startTime = Date.now();
|
|
367
|
+
const toolMetrics = new Map<string, number[]>();
|
|
368
|
+
|
|
369
|
+
const onBeforeTool: Autonomous.Hooks["onBeforeTool"] = async ({
|
|
370
|
+
iteration,
|
|
371
|
+
tool,
|
|
372
|
+
input,
|
|
373
|
+
controller,
|
|
374
|
+
}) => {
|
|
375
|
+
(tool as any)._startTime = Date.now();
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const onAfterTool: Autonomous.Hooks["onAfterTool"] = async ({ tool, output }) => {
|
|
379
|
+
const duration = Date.now() - ((tool as any)._startTime || 0);
|
|
380
|
+
const metrics = toolMetrics.get(tool.name) || [];
|
|
381
|
+
metrics.push(duration);
|
|
382
|
+
toolMetrics.set(tool.name, metrics);
|
|
383
|
+
console.log(`[PERF] ${tool.name}: ${duration}ms`);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const onTrace: Autonomous.Hooks["onTrace"] = ({ trace, iteration }) => {
|
|
387
|
+
if (trace.type === "end") {
|
|
388
|
+
console.log(`[PERF] Total conversation: ${Date.now() - startTime}ms`);
|
|
389
|
+
console.log(`[PERF] Tool metrics:`, Object.fromEntries(toolMetrics));
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return { onBeforeTool, onAfterTool, onTrace };
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Hook Signatures Reference
|
|
398
|
+
|
|
399
|
+
| Hook | Parameters | Description |
|
|
400
|
+
|------|-----------|-------------|
|
|
401
|
+
| `onBeforeTool` | `{ iteration, tool, input, controller }` | Fires before each tool call |
|
|
402
|
+
| `onAfterTool` | `{ tool, output }` | Fires after each tool call |
|
|
403
|
+
| `onTrace` | `{ trace, iteration }` | Fires on every trace event |
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Pattern 4: Extension Composition
|
|
408
|
+
|
|
409
|
+
Combine multiple extensions into a cohesive agent architecture.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// src/conversations/webchat.ts
|
|
413
|
+
import { Conversation } from "@botpress/runtime";
|
|
414
|
+
import { WebsiteKB } from "../knowledge/website-docs";
|
|
415
|
+
import { getAdminModeObject } from "./extensions/admin-mode";
|
|
416
|
+
import { makeGuardrails } from "./extensions/guardrails";
|
|
417
|
+
import { onTraceLogging } from "./extensions/logging";
|
|
418
|
+
|
|
419
|
+
export default new Conversation({
|
|
420
|
+
channel: "*",
|
|
421
|
+
handler: async ({ execute, message }) => {
|
|
422
|
+
const guardrail = makeGuardrails(message);
|
|
423
|
+
|
|
424
|
+
await execute({
|
|
425
|
+
instructions: `You are a helpful assistant that provides accurate information.`,
|
|
426
|
+
knowledge: [WebsiteKB],
|
|
427
|
+
objects: [getAdminModeObject()],
|
|
428
|
+
hooks: {
|
|
429
|
+
onBeforeTool: async (props) => guardrail.onBeforeToolGuard(props),
|
|
430
|
+
onTrace: (props) => onTraceLogging!(props),
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Architecture
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
Conversation Handler
|
|
441
|
+
├── Instructions (system prompt)
|
|
442
|
+
├── Knowledge (RAG knowledge bases)
|
|
443
|
+
├── Objects (admin mode, custom tool groups)
|
|
444
|
+
└── Hooks
|
|
445
|
+
├── onBeforeTool (guardrails, validation)
|
|
446
|
+
├── onAfterTool (logging, cleanup)
|
|
447
|
+
└── onTrace (monitoring, debugging)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Multi-Extension Composition
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { makeGuardrails } from "./extensions/guardrails";
|
|
454
|
+
import { onTraceLogging } from "./extensions/logging";
|
|
455
|
+
import { makePerformanceMonitor } from "./extensions/performance";
|
|
456
|
+
|
|
457
|
+
export default new Conversation({
|
|
458
|
+
channel: "*",
|
|
459
|
+
handler: async ({ execute, message }) => {
|
|
460
|
+
const guardrail = makeGuardrails(message);
|
|
461
|
+
const perfMonitor = makePerformanceMonitor();
|
|
462
|
+
|
|
463
|
+
await execute({
|
|
464
|
+
instructions: `You are a documentation assistant. Always search before answering.`,
|
|
465
|
+
knowledge: [DocsKB],
|
|
466
|
+
objects: [getAdminModeObject()],
|
|
467
|
+
hooks: {
|
|
468
|
+
onBeforeTool: async (props) => {
|
|
469
|
+
await guardrail.onBeforeToolGuard(props);
|
|
470
|
+
await perfMonitor.onBeforeTool(props);
|
|
471
|
+
},
|
|
472
|
+
onAfterTool: async (props) => {
|
|
473
|
+
await perfMonitor.onAfterTool(props);
|
|
474
|
+
},
|
|
475
|
+
onTrace: (props) => {
|
|
476
|
+
onTraceLogging!(props);
|
|
477
|
+
perfMonitor.onTrace(props);
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Best Practices
|
|
486
|
+
|
|
487
|
+
1. **Modular Extensions**: Separate files per concern (`guardrails.ts`, `logging.ts`, `admin-mode.ts`)
|
|
488
|
+
2. **Factory Functions**: Export factory functions for flexibility
|
|
489
|
+
3. **Hook Composition**: Combine multiple hooks in `execute()`
|
|
490
|
+
4. **Type-Safe Hooks**: Always type hooks via `Autonomous.Hooks["hookName"]`
|
|
491
|
+
|
|
492
|
+
```
|
|
493
|
+
src/conversations/
|
|
494
|
+
├── webchat.ts # Main conversation handler
|
|
495
|
+
└── extensions/
|
|
496
|
+
├── admin-mode.ts # Admin authentication
|
|
497
|
+
├── guardrails.ts # Behavioral constraints
|
|
498
|
+
├── logging.ts # Observability
|
|
499
|
+
└── performance.ts # Performance monitoring
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Pattern 5: Context Management
|
|
505
|
+
|
|
506
|
+
Properly manage async context in tool handlers and workflows.
|
|
507
|
+
|
|
508
|
+
### Problem
|
|
509
|
+
|
|
510
|
+
Tool handlers run in async contexts where the ADK runtime context (`user`, `conversation`, `client`) is not automatically available.
|
|
511
|
+
|
|
512
|
+
### Solution
|
|
513
|
+
|
|
514
|
+
Use `context.enterWith()` to restore context in async handlers:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { context, Autonomous } from "@botpress/runtime";
|
|
518
|
+
|
|
519
|
+
const myTool = () => {
|
|
520
|
+
const ctx = context.getAll(); // Capture context at tool creation
|
|
521
|
+
|
|
522
|
+
return new Autonomous.Tool({
|
|
523
|
+
name: "myTool",
|
|
524
|
+
handler: async () => {
|
|
525
|
+
context.enterWith(ctx); // Restore context in async handler
|
|
526
|
+
const userId = user.id;
|
|
527
|
+
await client.createMessage({ ... });
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### When to Use
|
|
534
|
+
|
|
535
|
+
- Tool handlers that access `user`, `conversation`, `client`
|
|
536
|
+
- Async operations within tools
|
|
537
|
+
- Long-running operations
|
|
538
|
+
- Workflows that spawn async tasks
|
|
539
|
+
|
|
540
|
+
> **Note:** Context reassignment should always use `context.enterWith()`. Do not use `context.set("key", value)` to reassign the full context — `enterWith()` atomically restores the entire context snapshot.
|
|
541
|
+
|
|
542
|
+
### Common Pitfalls
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
// ❌ BAD - Context not available
|
|
546
|
+
handler: async () => {
|
|
547
|
+
await client.createMessage({ ... }); // Error: client not available
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// ✅ GOOD - Restore context first
|
|
551
|
+
const ctx = context.getAll();
|
|
552
|
+
handler: async () => {
|
|
553
|
+
context.enterWith(ctx);
|
|
554
|
+
await client.createMessage({ ... });
|
|
555
|
+
};
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Pattern 6: Shared Schema
|
|
561
|
+
|
|
562
|
+
Centralize Zod schemas to ensure consistency across Actions, Tables, Workflows, and validation logic.
|
|
563
|
+
|
|
564
|
+
### Central Schema Definition
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// src/schemas.ts
|
|
568
|
+
import { z } from "@botpress/runtime";
|
|
569
|
+
|
|
570
|
+
export type User = z.infer<typeof UserSchema>;
|
|
571
|
+
export const UserSchema = z.object({
|
|
572
|
+
id: z.string(),
|
|
573
|
+
email: z.string().email(),
|
|
574
|
+
name: z.string().optional(),
|
|
575
|
+
role: z.enum(["admin", "user", "guest"]).default("user"),
|
|
576
|
+
createdAt: z.string().describe("ISO 8601 timestamp"),
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Reuse Across Components
|
|
581
|
+
|
|
582
|
+
**In Actions:**
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { Action } from "@botpress/runtime";
|
|
586
|
+
import { UserSchema } from "../schemas";
|
|
587
|
+
|
|
588
|
+
export const GetUser = new Action({
|
|
589
|
+
name: "getUser",
|
|
590
|
+
input: z.object({ userId: z.string() }),
|
|
591
|
+
output: UserSchema,
|
|
592
|
+
handler: async ({ input }) => {
|
|
593
|
+
return await fetchUserFromAPI(input.userId);
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**In Tables:**
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { Table } from "@botpress/runtime";
|
|
602
|
+
import { UserSchema } from "../schemas";
|
|
603
|
+
|
|
604
|
+
export const UsersTable = new Table({
|
|
605
|
+
name: "UsersTable",
|
|
606
|
+
description: "Store user information",
|
|
607
|
+
columns: {
|
|
608
|
+
email: { schema: UserSchema.shape.email, searchable: true },
|
|
609
|
+
name: { schema: UserSchema.shape.name, searchable: true },
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**In Workflows:**
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
import { Workflow } from "@botpress/runtime";
|
|
618
|
+
import { UserSchema, ConversationSchema } from "../schemas";
|
|
619
|
+
|
|
620
|
+
export const ProcessUser = new Workflow({
|
|
621
|
+
name: "processUser",
|
|
622
|
+
input: z.object({ user: UserSchema, conversationId: z.string() }),
|
|
623
|
+
state: z.object({ conversation: ConversationSchema.optional() }),
|
|
624
|
+
handler: async ({ input, state, step }) => {
|
|
625
|
+
console.log(`Processing user: ${input.user.email}`);
|
|
626
|
+
return { userId: input.user.id };
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Schema Composition
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
// Base schema
|
|
635
|
+
export const BaseEntitySchema = z.object({
|
|
636
|
+
id: z.string(),
|
|
637
|
+
createdAt: z.string(),
|
|
638
|
+
updatedAt: z.string(),
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Extend
|
|
642
|
+
export const ConversationSchema = BaseEntitySchema.extend({
|
|
643
|
+
userId: z.string(),
|
|
644
|
+
status: z.enum(["open", "closed", "snoozed"]),
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Pick / Omit
|
|
648
|
+
export const UserSummarySchema = UserSchema.pick({ id: true, email: true, name: true });
|
|
649
|
+
export const PublicUserSchema = UserSchema.omit({ metadata: true });
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Best Practices
|
|
653
|
+
|
|
654
|
+
- Organize schemas by domain (`src/schemas/users.ts`, `src/schemas/conversations.ts`)
|
|
655
|
+
- Export both types and schemas (`export type User = z.infer<typeof UserSchema>`)
|
|
656
|
+
- Add `.describe()` annotations for documentation
|
|
657
|
+
- Use `.extend()`, `.pick()`, `.omit()` to build variations without duplication
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## See Also
|
|
662
|
+
|
|
663
|
+
- [./actions.md](./actions.md) — Actions reference
|
|
664
|
+
- [./conversations.md](./conversations.md) — Conversation handlers
|
|
665
|
+
- [./workflows.md](./workflows.md) — Workflow patterns
|
|
666
|
+
- [./tools.md](./tools.md) — Tools reference
|
|
667
|
+
- [./context-api.md](./context-api.md) — Context API details
|
|
668
|
+
- [./agent-config.md](./agent-config.md) — Configuration reference
|
|
669
|
+
- [./zai-complete-guide.md](./zai-complete-guide.md) — Zai validation guide
|