@botpress/adk 1.15.4 → 1.16.1
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/agent-init/agent-project-generator.d.ts +1 -1
- package/dist/agent-init/agent-project-generator.d.ts.map +1 -1
- package/dist/agent-project/config-writer.d.ts +10 -0
- package/dist/agent-project/config-writer.d.ts.map +1 -1
- package/dist/agent-project/index.d.ts +1 -0
- package/dist/agent-project/index.d.ts.map +1 -1
- package/dist/config/coerce-config-value.d.ts +18 -0
- package/dist/config/coerce-config-value.d.ts.map +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/manager.d.ts +25 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/eval/client.d.ts +25 -0
- package/dist/eval/client.d.ts.map +1 -0
- package/dist/eval/graders/index.d.ts +12 -0
- package/dist/eval/graders/index.d.ts.map +1 -0
- package/dist/eval/graders/llm.d.ts +18 -0
- package/dist/eval/graders/llm.d.ts.map +1 -0
- package/dist/eval/graders/match.d.ts +11 -0
- package/dist/eval/graders/match.d.ts.map +1 -0
- package/dist/eval/graders/outcome.d.ts +16 -0
- package/dist/eval/graders/outcome.d.ts.map +1 -0
- package/dist/eval/graders/response.d.ts +9 -0
- package/dist/eval/graders/response.d.ts.map +1 -0
- package/dist/eval/graders/state.d.ts +16 -0
- package/dist/eval/graders/state.d.ts.map +1 -0
- package/dist/eval/graders/tables.d.ts +8 -0
- package/dist/eval/graders/tables.d.ts.map +1 -0
- package/dist/eval/graders/tools.d.ts +7 -0
- package/dist/eval/graders/tools.d.ts.map +1 -0
- package/dist/eval/graders/workflow.d.ts +7 -0
- package/dist/eval/graders/workflow.d.ts.map +1 -0
- package/dist/eval/index.d.ts +10 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/loader.d.ts +10 -0
- package/dist/eval/loader.d.ts.map +1 -0
- package/dist/eval/runner.d.ts +15 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/store.d.ts +10 -0
- package/dist/eval/store.d.ts.map +1 -0
- package/dist/eval/traces.d.ts +19 -0
- package/dist/eval/traces.d.ts.map +1 -0
- package/dist/eval/types.d.ts +202 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1569 -1616
- package/dist/index.js.map +23 -8
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -718,7 +718,7 @@ var PRETTIER_CONFIG, formatCode = async (code, filepath) => {
|
|
|
718
718
|
`));
|
|
719
719
|
return code;
|
|
720
720
|
}
|
|
721
|
-
}, ADK_VERSION = "1.
|
|
721
|
+
}, ADK_VERSION = "1.16.1", relative2 = (from, to) => {
|
|
722
722
|
const fromDir = path10.dirname(from);
|
|
723
723
|
const relative3 = path10.relative(fromDir, to);
|
|
724
724
|
return relative3.startsWith(".") ? relative3 : `./${relative3}`;
|
|
@@ -861,7 +861,7 @@ var init_integration_action_types = __esm(() => {
|
|
|
861
861
|
var require_package = __commonJS((exports, module) => {
|
|
862
862
|
module.exports = {
|
|
863
863
|
name: "@botpress/adk",
|
|
864
|
-
version: "1.
|
|
864
|
+
version: "1.16.1",
|
|
865
865
|
description: "Core ADK library for building AI agents on Botpress",
|
|
866
866
|
type: "module",
|
|
867
867
|
main: "dist/index.js",
|
|
@@ -905,10 +905,11 @@ var require_package = __commonJS((exports, module) => {
|
|
|
905
905
|
url: "https://github.com/botpress/adk"
|
|
906
906
|
},
|
|
907
907
|
dependencies: {
|
|
908
|
+
"@botpress/chat": "^0.5.5",
|
|
908
909
|
"@botpress/cli": "^5.2.0",
|
|
909
910
|
"@botpress/client": "^1.35.0",
|
|
910
911
|
"@botpress/cognitive": "^0.3.14",
|
|
911
|
-
"@botpress/runtime": "^1.
|
|
912
|
+
"@botpress/runtime": "^1.16.1",
|
|
912
913
|
"@botpress/sdk": "^5.4.3",
|
|
913
914
|
"@bpinternal/jex": "^1.2.4",
|
|
914
915
|
"@bpinternal/yargs-extra": "^0.0.21",
|
|
@@ -3280,6 +3281,62 @@ class ConfigWriter {
|
|
|
3280
3281
|
}
|
|
3281
3282
|
await this.saveConfig(sourceFile);
|
|
3282
3283
|
}
|
|
3284
|
+
async updateConfiguration(updates) {
|
|
3285
|
+
const { sourceFile, configObject } = this.loadConfig();
|
|
3286
|
+
const hasAdds = updates.some((u) => u.action === "add");
|
|
3287
|
+
let configProp = configObject.getProperty("configuration");
|
|
3288
|
+
if (!configProp) {
|
|
3289
|
+
if (!hasAdds)
|
|
3290
|
+
return;
|
|
3291
|
+
configProp = configObject.addPropertyAssignment({
|
|
3292
|
+
name: "configuration",
|
|
3293
|
+
initializer: "{ schema: z.object({}) }"
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
const configInit = configProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
3297
|
+
if (!configInit)
|
|
3298
|
+
return;
|
|
3299
|
+
let schemaProp = configInit.getProperty("schema");
|
|
3300
|
+
if (!schemaProp) {
|
|
3301
|
+
if (!hasAdds)
|
|
3302
|
+
return;
|
|
3303
|
+
schemaProp = configInit.addPropertyAssignment({
|
|
3304
|
+
name: "schema",
|
|
3305
|
+
initializer: "z.object({})"
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
const schemaCall = schemaProp.getInitializerIfKind(SyntaxKind.CallExpression);
|
|
3309
|
+
if (!schemaCall)
|
|
3310
|
+
return;
|
|
3311
|
+
const schemaArg = schemaCall.getArguments()[0];
|
|
3312
|
+
if (!schemaArg || !schemaArg.isKind(SyntaxKind.ObjectLiteralExpression))
|
|
3313
|
+
return;
|
|
3314
|
+
const schemaObject = schemaArg;
|
|
3315
|
+
for (const update of updates) {
|
|
3316
|
+
const existing = schemaObject.getProperty(update.field);
|
|
3317
|
+
switch (update.action) {
|
|
3318
|
+
case "add":
|
|
3319
|
+
if (!existing && update.definition) {
|
|
3320
|
+
schemaObject.addPropertyAssignment({
|
|
3321
|
+
name: update.field,
|
|
3322
|
+
initializer: update.definition
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
break;
|
|
3326
|
+
case "update":
|
|
3327
|
+
if (existing && update.definition) {
|
|
3328
|
+
existing.setInitializer(update.definition);
|
|
3329
|
+
}
|
|
3330
|
+
break;
|
|
3331
|
+
case "remove":
|
|
3332
|
+
if (existing) {
|
|
3333
|
+
existing.remove();
|
|
3334
|
+
}
|
|
3335
|
+
break;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
await this.saveConfig(sourceFile);
|
|
3339
|
+
}
|
|
3283
3340
|
}
|
|
3284
3341
|
|
|
3285
3342
|
// src/integrations/operations.ts
|
|
@@ -5112,6 +5169,45 @@ init_types();
|
|
|
5112
5169
|
// src/config/manager.ts
|
|
5113
5170
|
import { Client as Client12 } from "@botpress/client";
|
|
5114
5171
|
import { sync as jex } from "@bpinternal/jex";
|
|
5172
|
+
|
|
5173
|
+
// src/config/coerce-config-value.ts
|
|
5174
|
+
function coerceConfigValue(value, fieldSchema) {
|
|
5175
|
+
const typeName = getInnerTypeName(fieldSchema);
|
|
5176
|
+
switch (typeName) {
|
|
5177
|
+
case "ZodNumber": {
|
|
5178
|
+
const num = Number(value);
|
|
5179
|
+
if (Number.isNaN(num)) {
|
|
5180
|
+
return value;
|
|
5181
|
+
}
|
|
5182
|
+
return num;
|
|
5183
|
+
}
|
|
5184
|
+
case "ZodBoolean": {
|
|
5185
|
+
const lower = value.toLowerCase();
|
|
5186
|
+
if (lower === "true" || lower === "1" || lower === "yes") {
|
|
5187
|
+
return true;
|
|
5188
|
+
}
|
|
5189
|
+
if (lower === "false" || lower === "0" || lower === "no") {
|
|
5190
|
+
return false;
|
|
5191
|
+
}
|
|
5192
|
+
return value;
|
|
5193
|
+
}
|
|
5194
|
+
default:
|
|
5195
|
+
return value;
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
function getInnerTypeName(schema) {
|
|
5199
|
+
const def = schema?._def;
|
|
5200
|
+
if (!def) {
|
|
5201
|
+
return "unknown";
|
|
5202
|
+
}
|
|
5203
|
+
const typeName = def.typeName ?? "unknown";
|
|
5204
|
+
if ((typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") && def.innerType) {
|
|
5205
|
+
return getInnerTypeName(def.innerType);
|
|
5206
|
+
}
|
|
5207
|
+
return typeName;
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
// src/config/manager.ts
|
|
5115
5211
|
class ConfigManager {
|
|
5116
5212
|
botId;
|
|
5117
5213
|
client;
|
|
@@ -5201,1552 +5297,328 @@ class ConfigManager {
|
|
|
5201
5297
|
const validation = await this.validate(schema);
|
|
5202
5298
|
return validation.valid;
|
|
5203
5299
|
}
|
|
5300
|
+
async describeSchema(schema) {
|
|
5301
|
+
const stored = await this.load();
|
|
5302
|
+
const shape = schema.shape;
|
|
5303
|
+
const fields = [];
|
|
5304
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
5305
|
+
const innerType = getInnerTypeName(fieldSchema);
|
|
5306
|
+
const def = fieldSchema?._def;
|
|
5307
|
+
let type = "unknown";
|
|
5308
|
+
if (innerType === "ZodString")
|
|
5309
|
+
type = "string";
|
|
5310
|
+
else if (innerType === "ZodNumber")
|
|
5311
|
+
type = "number";
|
|
5312
|
+
else if (innerType === "ZodBoolean")
|
|
5313
|
+
type = "boolean";
|
|
5314
|
+
const typeName = def?.typeName ?? "";
|
|
5315
|
+
const isOptional = typeName === "ZodOptional" || typeName === "ZodNullable";
|
|
5316
|
+
const hasDefault = typeName === "ZodDefault";
|
|
5317
|
+
const required = !isOptional && !hasDefault;
|
|
5318
|
+
let defaultValue = undefined;
|
|
5319
|
+
if (hasDefault) {
|
|
5320
|
+
defaultValue = def.defaultValue?.();
|
|
5321
|
+
}
|
|
5322
|
+
const description = fieldSchema.description ?? undefined;
|
|
5323
|
+
fields.push({
|
|
5324
|
+
key,
|
|
5325
|
+
type,
|
|
5326
|
+
required,
|
|
5327
|
+
description,
|
|
5328
|
+
defaultValue,
|
|
5329
|
+
currentValue: stored[key]
|
|
5330
|
+
});
|
|
5331
|
+
}
|
|
5332
|
+
return fields;
|
|
5333
|
+
}
|
|
5334
|
+
async setWithValidation(key, value, schema) {
|
|
5335
|
+
const shape = schema.shape;
|
|
5336
|
+
const fieldSchema = shape[key];
|
|
5337
|
+
if (!fieldSchema) {
|
|
5338
|
+
return { success: false, error: `Key "${key}" not found in configuration schema` };
|
|
5339
|
+
}
|
|
5340
|
+
const coerced = typeof value === "string" ? coerceConfigValue(value, fieldSchema) : value;
|
|
5341
|
+
const result = fieldSchema.safeParse(coerced);
|
|
5342
|
+
if (!result.success) {
|
|
5343
|
+
const messages = result.error.issues.map((i) => i.message);
|
|
5344
|
+
return { success: false, error: messages.join("; ") };
|
|
5345
|
+
}
|
|
5346
|
+
await this.set(key, result.data);
|
|
5347
|
+
return { success: true, data: result.data };
|
|
5348
|
+
}
|
|
5204
5349
|
}
|
|
5205
5350
|
// src/agent-init/agent-project-generator.ts
|
|
5206
5351
|
init_utils();
|
|
5207
5352
|
import * as fs11 from "fs";
|
|
5208
5353
|
import * as path15 from "path";
|
|
5209
5354
|
|
|
5210
|
-
// src/agent-init/
|
|
5211
|
-
var
|
|
5355
|
+
// src/agent-init/ai-assistant-instructions.template.md
|
|
5356
|
+
var ai_assistant_instructions_template_default = `# Botpress ADK Agent
|
|
5212
5357
|
|
|
5213
|
-
This project is built with the **Botpress Agent Development Kit (ADK)**
|
|
5358
|
+
> This project is built with the **Botpress Agent Development Kit (ADK)** — a TypeScript-first framework for building AI agents.
|
|
5214
5359
|
|
|
5215
|
-
##
|
|
5360
|
+
## Key Files
|
|
5216
5361
|
|
|
5217
|
-
-
|
|
5218
|
-
-
|
|
5219
|
-
-
|
|
5220
|
-
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
- [5. Knowledge Bases](#5-knowledge-bases-srcknowledge)
|
|
5226
|
-
- [6. Actions](#6-actions-srcactions)
|
|
5227
|
-
- [7. Zai Library](#7-zai-library)
|
|
5228
|
-
- [Project Structure](#project-structure)
|
|
5229
|
-
- [Development Workflow](#development-workflow)
|
|
5230
|
-
- [Examples](#examples)
|
|
5231
|
-
- [Best Practices](#best-practices)
|
|
5232
|
-
- [Common APIs](#common-apis)
|
|
5233
|
-
- [Advanced Autonomous Execution](#advanced-autonomous-execution)
|
|
5234
|
-
- [State and Metadata Management](#state-and-metadata-management)
|
|
5235
|
-
- [Advanced Table Operations](#advanced-table-operations)
|
|
5236
|
-
- [Knowledge Base Operations](#knowledge-base-operations)
|
|
5237
|
-
- [Advanced Conversation Patterns](#advanced-conversation-patterns)
|
|
5238
|
-
- [Citations System](#citations-system)
|
|
5239
|
-
- [When Making Changes](#when-making-changes)
|
|
5240
|
-
- [Resources](#resources)
|
|
5362
|
+
- \`agent.config.ts\` — Agent configuration, models, state schemas, and dependencies
|
|
5363
|
+
- \`src/conversations/\` — Message handlers (primary user interaction)
|
|
5364
|
+
- \`src/workflows/\` — Long-running background processes
|
|
5365
|
+
- \`src/tools/\` — AI-callable functions
|
|
5366
|
+
- \`src/actions/\` — Reusable business logic
|
|
5367
|
+
- \`src/knowledge/\` — RAG knowledge base sources
|
|
5368
|
+
- \`src/tables/\` — Database table definitions
|
|
5369
|
+
- \`src/triggers/\` — Event-based triggers
|
|
5241
5370
|
|
|
5242
|
-
##
|
|
5371
|
+
## Development
|
|
5243
5372
|
|
|
5244
|
-
|
|
5373
|
+
\`\`\`bash
|
|
5374
|
+
adk dev # Start dev server with hot reload
|
|
5375
|
+
adk build # Build and generate types
|
|
5376
|
+
adk deploy # Deploy to Botpress Cloud
|
|
5377
|
+
adk chat # Chat with your agent in the terminal
|
|
5378
|
+
\`\`\`
|
|
5245
5379
|
|
|
5246
|
-
##
|
|
5380
|
+
## AI Coding Assistant Skills
|
|
5247
5381
|
|
|
5248
|
-
|
|
5382
|
+
This project uses the Botpress ADK. Before making changes, use the relevant skill:
|
|
5249
5383
|
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
-
|
|
5384
|
+
| Skill | Use for |
|
|
5385
|
+
| ------------------ | ------------------------------------------------ |
|
|
5386
|
+
| \`/adk\` | ADK concepts, patterns, and API reference |
|
|
5387
|
+
| \`/adk-integration\` | Finding and using Botpress integrations |
|
|
5388
|
+
| \`/adk-debugger\` | Debugging with traces and test conversations |
|
|
5389
|
+
| \`/adk-frontend\` | Building frontends that connect to ADK bots |
|
|
5254
5390
|
|
|
5255
|
-
|
|
5391
|
+
If these skills are not installed, install them:
|
|
5256
5392
|
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
To generate types without running in dev mode: \`adk build\`
|
|
5393
|
+
\`\`\`
|
|
5394
|
+
npx skills add botpress/skills --skill adk
|
|
5395
|
+
\`\`\`
|
|
5261
5396
|
|
|
5262
|
-
##
|
|
5397
|
+
## Project Overview
|
|
5263
5398
|
|
|
5264
|
-
|
|
5399
|
+
<!-- Describe what your agent does -->
|
|
5265
5400
|
|
|
5266
|
-
|
|
5401
|
+
## Architecture & Conventions
|
|
5267
5402
|
|
|
5268
|
-
-
|
|
5269
|
-
- **Default models** for autonomous and zai operations
|
|
5270
|
-
- **State schemas** (bot-level and user-level state using Zod)
|
|
5271
|
-
- **Configuration variables** (encrypted, secure storage for API keys)
|
|
5272
|
-
- **Integration dependencies** (webchat, chat, etc.)
|
|
5403
|
+
<!-- Add project-specific patterns, decisions, and conventions -->
|
|
5273
5404
|
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5405
|
+
## Notes
|
|
5406
|
+
|
|
5407
|
+
<!-- Add anything else relevant to your project -->
|
|
5408
|
+
`;
|
|
5409
|
+
|
|
5410
|
+
// src/agent-init/agent-project-generator.ts
|
|
5411
|
+
class AgentProjectGenerator {
|
|
5412
|
+
projectPath;
|
|
5413
|
+
projectName;
|
|
5414
|
+
packageManager;
|
|
5415
|
+
template;
|
|
5416
|
+
constructor(projectPath, packageManager = "bun", template = "blank") {
|
|
5417
|
+
this.projectPath = path15.resolve(projectPath);
|
|
5418
|
+
this.projectName = path15.basename(this.projectPath);
|
|
5419
|
+
this.packageManager = packageManager;
|
|
5420
|
+
this.template = template;
|
|
5421
|
+
}
|
|
5422
|
+
async generate() {
|
|
5423
|
+
this.ensureEmptyDirectory();
|
|
5424
|
+
this.createPackageJson();
|
|
5425
|
+
await this.createAgentConfig();
|
|
5426
|
+
this.createTsConfig();
|
|
5427
|
+
this.createAgentJson();
|
|
5428
|
+
this.createGitIgnore();
|
|
5429
|
+
await this.createReadme();
|
|
5430
|
+
this.createAIAssistantInstructions();
|
|
5431
|
+
await this.createSourceStructure();
|
|
5432
|
+
}
|
|
5433
|
+
ensureEmptyDirectory() {
|
|
5434
|
+
if (!fs11.existsSync(this.projectPath)) {
|
|
5435
|
+
fs11.mkdirSync(this.projectPath, { recursive: true });
|
|
5436
|
+
}
|
|
5437
|
+
const files = fs11.readdirSync(this.projectPath);
|
|
5438
|
+
if (files.length > 0) {
|
|
5439
|
+
throw new Error(`Directory ${this.projectPath} is not empty. Please use an empty directory.`);
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
createPackageJson() {
|
|
5443
|
+
const packageJson = {
|
|
5444
|
+
name: this.projectName,
|
|
5445
|
+
version: "1.0.0",
|
|
5446
|
+
description: `A Botpress Agent built with the ADK`,
|
|
5447
|
+
type: "module",
|
|
5448
|
+
packageManager: this.packageManager === "npm" ? undefined : `${this.packageManager}@latest`,
|
|
5449
|
+
scripts: {
|
|
5450
|
+
dev: "adk dev",
|
|
5451
|
+
build: "adk build",
|
|
5452
|
+
deploy: "adk deploy"
|
|
5453
|
+
},
|
|
5454
|
+
dependencies: {
|
|
5455
|
+
"@botpress/runtime": `^${"1.16.1"}`
|
|
5456
|
+
},
|
|
5457
|
+
devDependencies: {
|
|
5458
|
+
typescript: "^5.9.3"
|
|
5459
|
+
}
|
|
5460
|
+
};
|
|
5461
|
+
if (packageJson.packageManager === undefined) {
|
|
5462
|
+
delete packageJson.packageManager;
|
|
5463
|
+
}
|
|
5464
|
+
this.writeJsonFile("package.json", packageJson);
|
|
5465
|
+
}
|
|
5466
|
+
getDefaultDependencies() {
|
|
5467
|
+
const dependencies = {
|
|
5468
|
+
integrations: {}
|
|
5469
|
+
};
|
|
5470
|
+
if (this.template === "hello-world") {
|
|
5471
|
+
dependencies.integrations = {
|
|
5472
|
+
chat: {
|
|
5473
|
+
version: "chat@latest",
|
|
5474
|
+
enabled: true
|
|
5475
|
+
},
|
|
5476
|
+
webchat: {
|
|
5477
|
+
version: "webchat@latest",
|
|
5478
|
+
enabled: true
|
|
5479
|
+
}
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
return dependencies;
|
|
5483
|
+
}
|
|
5484
|
+
async createAgentConfig() {
|
|
5485
|
+
const dependencies = this.getDefaultDependencies();
|
|
5486
|
+
const integrationsJson = JSON.stringify(dependencies.integrations, null, 4).replace(/\n/g, `
|
|
5487
|
+
`);
|
|
5488
|
+
const defaultModels = this.template === "hello-world" ? `
|
|
5277
5489
|
defaultModels: {
|
|
5278
5490
|
autonomous: "cerebras:gpt-oss-120b",
|
|
5279
5491
|
zai: "cerebras:gpt-oss-120b",
|
|
5280
5492
|
},
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
dependencies: {
|
|
5284
|
-
integrations: {
|
|
5285
|
-
webchat: { version: "webchat@0.3.0", enabled: true },
|
|
5286
|
-
},
|
|
5287
|
-
},
|
|
5288
|
-
});
|
|
5289
|
-
\`\`\`
|
|
5290
|
-
|
|
5291
|
-
### 2. Conversations (\`src/conversations/\`)
|
|
5292
|
-
|
|
5293
|
-
**Primary way agents handle user messages**. Each conversation handler:
|
|
5294
|
-
|
|
5295
|
-
- Responds to messages from specific channels
|
|
5296
|
-
- Uses \`execute()\` to run autonomous AI logic
|
|
5297
|
-
- Can access conversation state, send messages, and call tools
|
|
5298
|
-
|
|
5299
|
-
**Key Pattern**: The \`execute()\` function runs the agent's AI loop:
|
|
5493
|
+
` : "";
|
|
5494
|
+
const agentConfig = `import { z, defineConfig } from '@botpress/runtime';
|
|
5300
5495
|
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
tools: [myTool1, myTool2],
|
|
5308
|
-
knowledge: [myKnowledgeBase],
|
|
5309
|
-
});
|
|
5496
|
+
export default defineConfig({
|
|
5497
|
+
name: '${this.projectName}',
|
|
5498
|
+
description: 'An AI agent built with Botpress ADK',
|
|
5499
|
+
${defaultModels}
|
|
5500
|
+
bot: {
|
|
5501
|
+
state: z.object({}),
|
|
5310
5502
|
},
|
|
5311
|
-
});
|
|
5312
|
-
\`\`\`
|
|
5313
|
-
|
|
5314
|
-
### 3. Workflows (\`src/workflows/\`)
|
|
5315
|
-
|
|
5316
|
-
**Long-running processes** for complex, multi-step operations:
|
|
5317
5503
|
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
- Use \`step()\` for durable execution (survives restarts)
|
|
5504
|
+
user: {
|
|
5505
|
+
state: z.object({}),
|
|
5506
|
+
},
|
|
5322
5507
|
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
name: "periodic-indexing",
|
|
5326
|
-
schedule: "0 */6 * * *",
|
|
5327
|
-
handler: async ({ step }) => {
|
|
5328
|
-
await step("task-name", async () => {
|
|
5329
|
-
// Your logic here
|
|
5330
|
-
});
|
|
5508
|
+
dependencies: {
|
|
5509
|
+
integrations: ${integrationsJson},
|
|
5331
5510
|
},
|
|
5332
5511
|
});
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5512
|
+
`;
|
|
5513
|
+
await this.writeFormattedFile("agent.config.ts", agentConfig);
|
|
5514
|
+
}
|
|
5515
|
+
createTsConfig() {
|
|
5516
|
+
const tsConfig = {
|
|
5517
|
+
compilerOptions: {
|
|
5518
|
+
target: "ES2022",
|
|
5519
|
+
module: "ES2022",
|
|
5520
|
+
moduleResolution: "Bundler",
|
|
5521
|
+
lib: ["ES2022", "DOM"],
|
|
5522
|
+
outDir: "./dist",
|
|
5523
|
+
rootDir: ".",
|
|
5524
|
+
strict: true,
|
|
5525
|
+
esModuleInterop: true,
|
|
5526
|
+
allowSyntheticDefaultImports: true,
|
|
5527
|
+
skipLibCheck: true,
|
|
5528
|
+
forceConsistentCasingInFileNames: true,
|
|
5529
|
+
moduleDetection: "force",
|
|
5530
|
+
resolveJsonModule: true,
|
|
5531
|
+
paths: {
|
|
5532
|
+
"@botpress/runtime/_types/*": ["./.adk/*-types"]
|
|
5533
|
+
}
|
|
5534
|
+
},
|
|
5535
|
+
include: ["src/**/*", ".adk/**/*"],
|
|
5536
|
+
exclude: ["node_modules", "dist"]
|
|
5537
|
+
};
|
|
5538
|
+
this.writeJsonFile("tsconfig.json", tsConfig);
|
|
5539
|
+
}
|
|
5540
|
+
createAgentJson() {
|
|
5541
|
+
const agentJson = {};
|
|
5542
|
+
this.writeJsonFile("agent.json", agentJson);
|
|
5543
|
+
}
|
|
5544
|
+
createGitIgnore() {
|
|
5545
|
+
const gitIgnore = `# Dependencies
|
|
5546
|
+
node_modules/
|
|
5547
|
+
.pnpm-store/
|
|
5362
5548
|
|
|
5363
|
-
|
|
5549
|
+
# Build outputs
|
|
5550
|
+
dist/
|
|
5551
|
+
.adk/
|
|
5364
5552
|
|
|
5365
|
-
|
|
5366
|
-
|
|
5553
|
+
# Environment files
|
|
5554
|
+
.env
|
|
5555
|
+
.env.local
|
|
5556
|
+
.env.production
|
|
5367
5557
|
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5558
|
+
# IDE files
|
|
5559
|
+
.vscode/
|
|
5560
|
+
.idea/
|
|
5561
|
+
*.swp
|
|
5562
|
+
*.swo
|
|
5371
5563
|
|
|
5372
|
-
|
|
5564
|
+
# OS files
|
|
5565
|
+
.DS_Store
|
|
5566
|
+
Thumbs.db
|
|
5373
5567
|
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5568
|
+
# Logs
|
|
5569
|
+
*.log
|
|
5570
|
+
logs/
|
|
5377
5571
|
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5572
|
+
# Runtime files
|
|
5573
|
+
*.pid
|
|
5574
|
+
*.seed
|
|
5575
|
+
*.pid.lock
|
|
5576
|
+
`;
|
|
5577
|
+
this.writeFile(".gitignore", gitIgnore);
|
|
5578
|
+
}
|
|
5579
|
+
async createReadme() {
|
|
5580
|
+
const installCommand = this.packageManager === "npm" ? "npm install" : this.packageManager === "yarn" ? "yarn install" : `${this.packageManager} install`;
|
|
5581
|
+
const readme = `# ${this.projectName}
|
|
5382
5582
|
|
|
5383
|
-
|
|
5583
|
+
A Botpress Agent built with the ADK.
|
|
5384
5584
|
|
|
5385
|
-
|
|
5386
|
-
// In workflow
|
|
5387
|
-
const { topic } = await step.request('topic', 'What topic should I research?')
|
|
5585
|
+
## Getting Started
|
|
5388
5586
|
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
\`\`\`
|
|
5587
|
+
1. Install dependencies:
|
|
5588
|
+
\`\`\`bash
|
|
5589
|
+
${installCommand}
|
|
5590
|
+
\`\`\`
|
|
5394
5591
|
|
|
5395
|
-
|
|
5592
|
+
2. Start development server:
|
|
5593
|
+
\`\`\`bash
|
|
5594
|
+
adk dev
|
|
5595
|
+
\`\`\`
|
|
5396
5596
|
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5597
|
+
3. Deploy your agent:
|
|
5598
|
+
\`\`\`bash
|
|
5599
|
+
adk deploy
|
|
5600
|
+
\`\`\`
|
|
5400
5601
|
|
|
5401
|
-
|
|
5602
|
+
## Project Structure
|
|
5402
5603
|
|
|
5403
|
-
|
|
5604
|
+
- \`src/actions/\` - Define callable functions
|
|
5605
|
+
- \`src/workflows/\` - Define long-running processes
|
|
5606
|
+
- \`src/conversations/\` - Define conversation handlers
|
|
5607
|
+
- \`src/tables/\` - Define data storage schemas
|
|
5608
|
+
- \`src/triggers/\` - Define event subscriptions
|
|
5609
|
+
- \`src/knowledge/\` - Add knowledge base files
|
|
5404
5610
|
|
|
5405
|
-
|
|
5406
|
-
- Use Zod schemas for input/output
|
|
5407
|
-
- Can be passed to \`execute()\`
|
|
5408
|
-
|
|
5409
|
-
\`\`\`typescript
|
|
5410
|
-
export default new Autonomous.Tool({
|
|
5411
|
-
name: "searchDatabase",
|
|
5412
|
-
description: "Search the database",
|
|
5413
|
-
input: z.object({ query: z.string() }),
|
|
5414
|
-
output: z.object({ results: z.array(z.any()) }),
|
|
5415
|
-
handler: async ({ query }) => {
|
|
5416
|
-
// Tool logic
|
|
5417
|
-
return { results: [] };
|
|
5418
|
-
},
|
|
5419
|
-
});
|
|
5420
|
-
\`\`\`
|
|
5421
|
-
|
|
5422
|
-
### 5. Knowledge Bases (\`src/knowledge/\`)
|
|
5423
|
-
|
|
5424
|
-
**RAG (Retrieval-Augmented Generation)** for providing context:
|
|
5425
|
-
|
|
5426
|
-
- Website scraping
|
|
5427
|
-
- Document ingestion
|
|
5428
|
-
- Can be passed to \`execute()\` via \`knowledge\` parameter
|
|
5429
|
-
|
|
5430
|
-
### 6. Actions (\`src/actions/\`)
|
|
5431
|
-
|
|
5432
|
-
**Reusable business logic** that can:
|
|
5433
|
-
|
|
5434
|
-
- Be called from anywhere (import \`actions\` from \`@botpress/runtime\`)
|
|
5435
|
-
- Be converted to tools with \`.asTool()\`
|
|
5436
|
-
- Encapsulate logic not tied to conversational flow
|
|
5437
|
-
|
|
5438
|
-
### 7. Zai Library
|
|
5439
|
-
|
|
5440
|
-
**Zai** is an LLM utility library that provides a clean, type-safe API for common AI operations. It's designed to work seamlessly with the ADK and SDK to process LLM inputs and outputs programmatically.
|
|
5441
|
-
|
|
5442
|
-
#### Importing Zai in ADK
|
|
5443
|
-
|
|
5444
|
-
In the ADK, Zai is available from \`@botpress/runtime\`:
|
|
5445
|
-
|
|
5446
|
-
\`\`\`typescript
|
|
5447
|
-
import { adk } from '@botpress/runtime'
|
|
5448
|
-
// then adk.zai.<method_name>
|
|
5449
|
-
\`\`\`
|
|
5450
|
-
|
|
5451
|
-
The default model for Zai operations is configured in \`agent.config.ts\`:
|
|
5452
|
-
|
|
5453
|
-
\`\`\`typescript
|
|
5454
|
-
export default defineConfig({
|
|
5455
|
-
defaultModels: {
|
|
5456
|
-
autonomous: "cerebras:gpt-oss-120b",
|
|
5457
|
-
zai: "cerebras:gpt-oss-120b", // Model used for Zai operations
|
|
5458
|
-
},
|
|
5459
|
-
})
|
|
5460
|
-
\`\`\`
|
|
5461
|
-
|
|
5462
|
-
#### When to Use Zai
|
|
5463
|
-
|
|
5464
|
-
Use Zai when you need to:
|
|
5465
|
-
- Extract structured data from unstructured text
|
|
5466
|
-
- Answer questions from documents with source citations
|
|
5467
|
-
- Verify Boolean conditions in content
|
|
5468
|
-
- Summarize long text into concise summaries
|
|
5469
|
-
- Generate text programmatically based on prompts
|
|
5470
|
-
|
|
5471
|
-
**Use Zai instead of \`execute()\` when**: You need deterministic, structured outputs for specific AI tasks (extraction, validation, summarization) rather than conversational interactions.
|
|
5472
|
-
|
|
5473
|
-
#### Zai Methods
|
|
5474
|
-
|
|
5475
|
-
**1. \`answer()\` - Answer Questions with Citations**
|
|
5476
|
-
|
|
5477
|
-
Answers questions from documents with intelligent source citations.
|
|
5478
|
-
|
|
5479
|
-
\`\`\`typescript
|
|
5480
|
-
const documents = [
|
|
5481
|
-
'Botpress was founded in 2016.',
|
|
5482
|
-
'The company is based in Quebec, Canada.',
|
|
5483
|
-
]
|
|
5484
|
-
|
|
5485
|
-
const result = await zai.answer(documents, 'When was Botpress founded?')
|
|
5486
|
-
|
|
5487
|
-
if (result.type === 'answer') {
|
|
5488
|
-
console.log(result.answer) // "Botpress was founded in 2016."
|
|
5489
|
-
console.log(result.citations) // Array of citations with source references
|
|
5490
|
-
}
|
|
5491
|
-
\`\`\`
|
|
5492
|
-
|
|
5493
|
-
**When to use**: When you need to answer questions from a set of documents with traceable sources (e.g., custom RAG implementations, document Q&A).
|
|
5494
|
-
|
|
5495
|
-
**2. \`extract()\` - Extract Structured Data**
|
|
5496
|
-
|
|
5497
|
-
Extracts structured data from unstructured input using Zod schemas.
|
|
5498
|
-
|
|
5499
|
-
\`\`\`typescript
|
|
5500
|
-
import { z, adk } from '@botpress/runtime'
|
|
5501
|
-
|
|
5502
|
-
const userSchema = z.object({
|
|
5503
|
-
name: z.string(),
|
|
5504
|
-
email: z.string().email(),
|
|
5505
|
-
age: z.number()
|
|
5506
|
-
})
|
|
5507
|
-
|
|
5508
|
-
const input = "My name is John Doe, I'm 30 years old and my email is john@example.com"
|
|
5509
|
-
// zai.extract returns the extracted data DIRECTLY (not wrapped in { output: ... })
|
|
5510
|
-
const result = await adk.zai.extract(input, userSchema)
|
|
5511
|
-
|
|
5512
|
-
console.log(result)
|
|
5513
|
-
// { name: "John Doe", email: "john@example.com", age: 30 }
|
|
5514
|
-
\`\`\`
|
|
5515
|
-
|
|
5516
|
-
**When to use**: When you need to parse unstructured user input into structured data (e.g., form extraction from natural language, parsing contact information).
|
|
5517
|
-
|
|
5518
|
-
**3. \`check()\` - Verify Boolean Conditions**
|
|
5519
|
-
|
|
5520
|
-
Verifies a condition against some input and returns a boolean with explanation.
|
|
5521
|
-
|
|
5522
|
-
\`\`\`typescript
|
|
5523
|
-
const email = "Get rich quick! Click here now!!!"
|
|
5524
|
-
const { output } = await zai.check(email, 'is spam').result()
|
|
5525
|
-
|
|
5526
|
-
console.log(output.value) // true
|
|
5527
|
-
console.log(output.explanation) // "This email contains typical spam indicators..."
|
|
5528
|
-
\`\`\`
|
|
5529
|
-
|
|
5530
|
-
**When to use**: When you need to validate content or make binary decisions (e.g., content moderation, intent verification, condition checking).
|
|
5531
|
-
|
|
5532
|
-
**4. \`summarize()\` - Summarize Text**
|
|
5533
|
-
|
|
5534
|
-
Creates concise summaries of lengthy text to a desired length.
|
|
5535
|
-
|
|
5536
|
-
\`\`\`typescript
|
|
5537
|
-
const longArticle = "..." // Long article content
|
|
5538
|
-
|
|
5539
|
-
const summary = await zai.summarize(longArticle, {
|
|
5540
|
-
length: 100, // tokens
|
|
5541
|
-
prompt: 'key findings and main conclusions'
|
|
5542
|
-
})
|
|
5543
|
-
\`\`\`
|
|
5544
|
-
|
|
5545
|
-
**When to use**: When you need to condense long content (e.g., article summaries, transcript summaries, document overviews).
|
|
5546
|
-
|
|
5547
|
-
**5. \`text()\` - Generate Text**
|
|
5548
|
-
|
|
5549
|
-
Generates text of the desired length according to a prompt.
|
|
5550
|
-
|
|
5551
|
-
\`\`\`typescript
|
|
5552
|
-
const generated = await zai.text('Write a welcome message for new users', {
|
|
5553
|
-
length: 50 // tokens
|
|
5554
|
-
})
|
|
5555
|
-
\`\`\`
|
|
5556
|
-
|
|
5557
|
-
**When to use**: When you need to generate specific text content programmatically (e.g., dynamic content generation, templated responses).
|
|
5558
|
-
|
|
5559
|
-
#### Response Methods
|
|
5560
|
-
|
|
5561
|
-
All Zai operations return a Response object with promise-like behavior and additional functionality:
|
|
5562
|
-
|
|
5563
|
-
\`\`\`typescript
|
|
5564
|
-
// Await the result directly
|
|
5565
|
-
const result = await zai.extract(input, schema)
|
|
5566
|
-
|
|
5567
|
-
// Or use .result() for explicit promise handling
|
|
5568
|
-
const { output } = await zai.check(content, 'is valid').result()
|
|
5569
|
-
\`\`\`
|
|
5570
|
-
|
|
5571
|
-
## Project Structure
|
|
5572
|
-
|
|
5573
|
-
\`\`\`
|
|
5574
|
-
agent.config.ts # Main configuration
|
|
5575
|
-
src/
|
|
5576
|
-
conversations/ # Message handlers (primary user interaction)
|
|
5577
|
-
workflows/ # Long-running processes
|
|
5578
|
-
tools/ # AI-callable functions
|
|
5579
|
-
actions/ # Reusable business logic
|
|
5580
|
-
knowledge/ # Knowledge bases for RAG
|
|
5581
|
-
triggers/ # Event-based triggers
|
|
5582
|
-
tables/ # Database tables
|
|
5583
|
-
.botpress/ # Auto-generated types (DO NOT EDIT)
|
|
5584
|
-
\`\`\`
|
|
5585
|
-
|
|
5586
|
-
## Development Workflow
|
|
5587
|
-
|
|
5588
|
-
1. **Start dev server**: \`adk dev\` (http://localhost:3001 for console)
|
|
5589
|
-
2. **Add integrations**: \`adk add webchat@latest\`
|
|
5590
|
-
3. **Build**: \`adk build\`
|
|
5591
|
-
4. **Deploy**: \`adk deploy\`
|
|
5592
|
-
5. **Chat in CLI**: \`adk chat\`
|
|
5593
|
-
|
|
5594
|
-
## Examples
|
|
5595
|
-
|
|
5596
|
-
Official examples: https://github.com/botpress/adk/tree/main/examples
|
|
5597
|
-
|
|
5598
|
-
### subagents
|
|
5599
|
-
|
|
5600
|
-
**What you'll learn:** How to build a multi-agent system where an orchestrator delegates to specialists.
|
|
5601
|
-
|
|
5602
|
-
Shows the \`SubAgent\` pattern where each specialist (HR, IT, Sales, etc.) runs in its own context with \`mode: "worker"\`, returns structured results via custom exits, and reports progress through \`onTrace\` hooks.
|
|
5603
|
-
|
|
5604
|
-
### webchat-rag
|
|
5605
|
-
|
|
5606
|
-
**What you'll learn:** How to build a RAG assistant with scheduled indexing, guardrails, and admin features.
|
|
5607
|
-
|
|
5608
|
-
Shows \`Autonomous.Object\` for dynamic tool grouping, \`onBeforeTool\` hooks to enforce knowledge search before answering, scheduled workflows for KB refresh, and \`ThinkSignal\` for interrupting execution.
|
|
5609
|
-
|
|
5610
|
-
### deep-research
|
|
5611
|
-
|
|
5612
|
-
**What you'll learn:** How to build complex, long-running workflows with progress tracking.
|
|
5613
|
-
|
|
5614
|
-
Shows \`step()\` and \`step.map()\` for workflow phases, \`Reference.Workflow\` for conversation-workflow linking, Tables for activity tracking, and extensive Zai usage (\`extract\`, \`answer\`, \`filter\`, \`text\`).
|
|
5615
|
-
|
|
5616
|
-
## Best Practices
|
|
5617
|
-
|
|
5618
|
-
1. **Search Botpress docs first** - Use the MCP tool before implementing
|
|
5619
|
-
2. **Keep tools focused** - Single responsibility per tool
|
|
5620
|
-
3. **Use Zod schemas** with \`.describe()\` for clarity
|
|
5621
|
-
4. **State management** - Minimize large variables in main workflow
|
|
5622
|
-
5. **Type safety** - Run \`adk dev\` or \`adk build\` to regenerate types after config changes
|
|
5623
|
-
6. **Conversations vs Workflows**:
|
|
5624
|
-
- Conversations: User interactions, real-time responses
|
|
5625
|
-
- Workflows: Background tasks, scheduled jobs, long-running processes
|
|
5626
|
-
|
|
5627
|
-
## Common APIs
|
|
5628
|
-
|
|
5629
|
-
### Conversation Handler
|
|
5630
|
-
|
|
5631
|
-
\`\`\`typescript
|
|
5632
|
-
handler: async ({
|
|
5633
|
-
execute, // Run autonomous AI loop
|
|
5634
|
-
conversation, // Send messages, manage conversation
|
|
5635
|
-
state, // Conversation state (persisted)
|
|
5636
|
-
message, // Incoming message
|
|
5637
|
-
client, // Botpress API client
|
|
5638
|
-
}) => {};
|
|
5639
|
-
\`\`\`
|
|
5640
|
-
|
|
5641
|
-
### Execute Function
|
|
5642
|
-
|
|
5643
|
-
\`\`\`typescript
|
|
5644
|
-
await execute({
|
|
5645
|
-
instructions: "String or function returning instructions",
|
|
5646
|
-
tools: [tool1, tool2], // Optional tools
|
|
5647
|
-
knowledge: [kb1, kb2], // Optional knowledge bases
|
|
5648
|
-
exits: [customExit], // Optional custom exits
|
|
5649
|
-
hooks: { onTrace, onBeforeTool }, // Optional hooks
|
|
5650
|
-
mode: "worker", // Optional: autonomous until exit
|
|
5651
|
-
iterations: 10, // Max loops (default 10)
|
|
5652
|
-
});
|
|
5653
|
-
\`\`\`
|
|
5654
|
-
|
|
5655
|
-
## Advanced Autonomous Execution
|
|
5656
|
-
|
|
5657
|
-
### Autonomous Namespace
|
|
5658
|
-
|
|
5659
|
-
The \`Autonomous\` namespace provides powerful primitives for controlling LLM behavior:
|
|
5660
|
-
|
|
5661
|
-
#### Autonomous.Exit - Custom Exit Conditions
|
|
5662
|
-
|
|
5663
|
-
Define custom exits for autonomous execution loops:
|
|
5664
|
-
|
|
5665
|
-
\`\`\`typescript
|
|
5666
|
-
import { Autonomous, z } from '@botpress/runtime'
|
|
5667
|
-
|
|
5668
|
-
const AnswerExit = new Autonomous.Exit({
|
|
5669
|
-
name: 'answer',
|
|
5670
|
-
description: 'Return when you have the final answer',
|
|
5671
|
-
schema: z.object({
|
|
5672
|
-
answer: z.string(),
|
|
5673
|
-
confidence: z.number()
|
|
5674
|
-
})
|
|
5675
|
-
})
|
|
5676
|
-
|
|
5677
|
-
const NoAnswerExit = new Autonomous.Exit({
|
|
5678
|
-
name: 'no_answer',
|
|
5679
|
-
description: 'No answer could be found'
|
|
5680
|
-
})
|
|
5681
|
-
|
|
5682
|
-
const result = await execute({
|
|
5683
|
-
instructions: 'Research and answer the question',
|
|
5684
|
-
exits: [AnswerExit, NoAnswerExit],
|
|
5685
|
-
mode: 'worker' // Run until exit triggered
|
|
5686
|
-
})
|
|
5687
|
-
|
|
5688
|
-
// ✅ CORRECT - Use result.is() and result.output
|
|
5689
|
-
if (result.is(AnswerExit)) {
|
|
5690
|
-
console.log(result.output.answer) // Type-safe access
|
|
5691
|
-
console.log(result.output.confidence)
|
|
5692
|
-
} else if (result.is(NoAnswerExit)) {
|
|
5693
|
-
console.log('No answer found')
|
|
5694
|
-
}
|
|
5695
|
-
|
|
5696
|
-
// ❌ WRONG - Don't use result.exit.name or result.exit.value
|
|
5697
|
-
// if (result.exit?.name === 'answer') { ... }
|
|
5698
|
-
\`\`\`
|
|
5699
|
-
|
|
5700
|
-
#### Autonomous.ThinkSignal - Inject Context
|
|
5701
|
-
|
|
5702
|
-
Provide context to the LLM without continuing execution:
|
|
5703
|
-
|
|
5704
|
-
\`\`\`typescript
|
|
5705
|
-
const results = await fetchData()
|
|
5706
|
-
|
|
5707
|
-
if (!results.length) {
|
|
5708
|
-
throw new ThinkSignal('error', 'No results found')
|
|
5709
|
-
}
|
|
5710
|
-
|
|
5711
|
-
// Inject formatted results into LLM context
|
|
5712
|
-
throw new ThinkSignal('results ready', formatResults(results))
|
|
5713
|
-
\`\`\`
|
|
5714
|
-
|
|
5715
|
-
#### Autonomous.Object - Dynamic Tool Grouping
|
|
5716
|
-
|
|
5717
|
-
Group tools dynamically based on state:
|
|
5718
|
-
|
|
5719
|
-
\`\`\`typescript
|
|
5720
|
-
const adminTools = new Autonomous.Object({
|
|
5721
|
-
name: 'admin',
|
|
5722
|
-
description: user.isAdmin ? 'Admin tools available' : 'Login required',
|
|
5723
|
-
tools: user.isAdmin ? [refreshKB, manageBots] : [generateLoginCode]
|
|
5724
|
-
})
|
|
5725
|
-
|
|
5726
|
-
await execute({
|
|
5727
|
-
objects: [adminTools]
|
|
5728
|
-
})
|
|
5729
|
-
\`\`\`
|
|
5730
|
-
|
|
5731
|
-
### Execution Hooks
|
|
5732
|
-
|
|
5733
|
-
Full control over the autonomous execution loop:
|
|
5734
|
-
|
|
5735
|
-
\`\`\`typescript
|
|
5736
|
-
await execute({
|
|
5737
|
-
instructions: '...',
|
|
5738
|
-
hooks: {
|
|
5739
|
-
// Before tool execution - can modify input
|
|
5740
|
-
onBeforeTool: async ({ iteration, tool, input, controller }) => {
|
|
5741
|
-
console.log(\`About to call \${tool.name}\`)
|
|
5742
|
-
return { input: modifiedInput } // Optional: transform input
|
|
5743
|
-
},
|
|
5744
|
-
|
|
5745
|
-
// After tool execution - can modify output
|
|
5746
|
-
onAfterTool: async ({ iteration, tool, input, output, controller }) => {
|
|
5747
|
-
console.log(\`\${tool.name} returned:\`, output)
|
|
5748
|
-
return { output: modifiedOutput } // Optional: transform output
|
|
5749
|
-
},
|
|
5750
|
-
|
|
5751
|
-
// Before code execution in iteration
|
|
5752
|
-
onBeforeExecution: async (iteration, controller) => {
|
|
5753
|
-
return { code: modifiedCode } // Optional: transform generated code
|
|
5754
|
-
},
|
|
5755
|
-
|
|
5756
|
-
// When exit is triggered
|
|
5757
|
-
onExit: async (result) => {
|
|
5758
|
-
console.log('Exited with:', result)
|
|
5759
|
-
},
|
|
5760
|
-
|
|
5761
|
-
// After each iteration completes
|
|
5762
|
-
onIterationEnd: async (iteration, controller) => {
|
|
5763
|
-
if (iteration > 5) {
|
|
5764
|
-
controller.abort() // Stop execution
|
|
5765
|
-
}
|
|
5766
|
-
},
|
|
5767
|
-
|
|
5768
|
-
// On trace events (synchronous, non-blocking)
|
|
5769
|
-
onTrace: ({ trace, iteration }) => {
|
|
5770
|
-
if (trace.type === 'comment') {
|
|
5771
|
-
console.log('LLM thinking:', trace.comment)
|
|
5772
|
-
}
|
|
5773
|
-
if (trace.type === 'tool_call') {
|
|
5774
|
-
console.log('Calling:', trace.tool_name)
|
|
5775
|
-
}
|
|
5776
|
-
}
|
|
5777
|
-
}
|
|
5778
|
-
})
|
|
5779
|
-
\`\`\`
|
|
5780
|
-
|
|
5781
|
-
**Hook use cases:**
|
|
5782
|
-
- Logging and debugging
|
|
5783
|
-
- Input/output validation and transformation
|
|
5784
|
-
- Rate limiting tool calls
|
|
5785
|
-
- Custom abort conditions
|
|
5786
|
-
- Injecting dynamic context
|
|
5787
|
-
|
|
5788
|
-
## State and Metadata Management
|
|
5789
|
-
|
|
5790
|
-
### Tags - Key-Value Metadata
|
|
5791
|
-
|
|
5792
|
-
Track metadata for any entity (bot, user, conversation, workflow):
|
|
5793
|
-
|
|
5794
|
-
\`\`\`typescript
|
|
5795
|
-
import { TrackedTags } from '@botpress/runtime'
|
|
5796
|
-
|
|
5797
|
-
// Create tags instance
|
|
5798
|
-
const tags = TrackedTags.create({
|
|
5799
|
-
type: 'bot', // or 'user' | 'conversation' | 'workflow'
|
|
5800
|
-
id: entityId,
|
|
5801
|
-
client: botClient,
|
|
5802
|
-
initialTags: { status: 'active' }
|
|
5803
|
-
})
|
|
5804
|
-
|
|
5805
|
-
// Load from server
|
|
5806
|
-
await tags.load()
|
|
5807
|
-
|
|
5808
|
-
// Modify tags
|
|
5809
|
-
tags.tags = {
|
|
5810
|
-
...tags.tags,
|
|
5811
|
-
lastSync: new Date().toISOString()
|
|
5812
|
-
}
|
|
5813
|
-
|
|
5814
|
-
// Check if modified
|
|
5815
|
-
if (tags.isDirty()) {
|
|
5816
|
-
await tags.save()
|
|
5817
|
-
}
|
|
5818
|
-
|
|
5819
|
-
// Batch operations
|
|
5820
|
-
await TrackedTags.saveAllDirty()
|
|
5821
|
-
await TrackedTags.loadAll()
|
|
5822
|
-
\`\`\`
|
|
5823
|
-
|
|
5824
|
-
**Access via workflow instance:**
|
|
5825
|
-
|
|
5826
|
-
\`\`\`typescript
|
|
5827
|
-
workflow.tags = { status: 'processing' }
|
|
5828
|
-
await workflow.save()
|
|
5829
|
-
\`\`\`
|
|
5830
|
-
|
|
5831
|
-
### Reference.Workflow - Typed Workflow References
|
|
5832
|
-
|
|
5833
|
-
Serialize workflow references in state that auto-hydrate on access:
|
|
5834
|
-
|
|
5835
|
-
\`\`\`typescript
|
|
5836
|
-
import { Reference, z } from '@botpress/runtime'
|
|
5837
|
-
|
|
5838
|
-
// In conversation state schema
|
|
5839
|
-
state: z.object({
|
|
5840
|
-
research: Reference.Workflow('deep_research').optional()
|
|
5841
|
-
// or untyped: Reference.Workflow().optional()
|
|
5842
|
-
})
|
|
5843
|
-
|
|
5844
|
-
// In handler - always a WorkflowInstance
|
|
5845
|
-
handler: async ({ state }) => {
|
|
5846
|
-
if (state.research) {
|
|
5847
|
-
// state.research is typed WorkflowInstance
|
|
5848
|
-
console.log(state.research.status) // 'running' | 'completed' | etc
|
|
5849
|
-
console.log(state.research.output) // Typed output
|
|
5850
|
-
|
|
5851
|
-
if (state.research.status === 'completed') {
|
|
5852
|
-
// Access completed workflow data
|
|
5853
|
-
}
|
|
5854
|
-
}
|
|
5855
|
-
}
|
|
5856
|
-
\`\`\`
|
|
5857
|
-
|
|
5858
|
-
### Context Object - Runtime Access
|
|
5859
|
-
|
|
5860
|
-
Global context for accessing runtime information:
|
|
5861
|
-
|
|
5862
|
-
\`\`\`typescript
|
|
5863
|
-
import { context } from '@botpress/runtime'
|
|
5864
|
-
|
|
5865
|
-
// Get specific context
|
|
5866
|
-
const client = context.get('client')
|
|
5867
|
-
const citations = context.get('citations')
|
|
5868
|
-
const logger = context.get('logger')
|
|
5869
|
-
|
|
5870
|
-
// Get all context
|
|
5871
|
-
const { client, cognitive, logger, operation } = context.getAll()
|
|
5872
|
-
\`\`\`
|
|
5873
|
-
|
|
5874
|
-
**Available context properties:**
|
|
5875
|
-
- \`client\` - Botpress API client
|
|
5876
|
-
- \`cognitive\` - LLM access
|
|
5877
|
-
- \`logger\` - Logging
|
|
5878
|
-
- \`operation\` - Current operation info
|
|
5879
|
-
- \`citations\` - Citation tracking
|
|
5880
|
-
- \`chat\` - Chat interface
|
|
5881
|
-
- \`bot\` - Bot tags and metadata
|
|
5882
|
-
- \`user\` - User information
|
|
5883
|
-
- \`conversation\` - Current conversation
|
|
5884
|
-
- \`message\` - Incoming message
|
|
5885
|
-
- \`event\` - Current event
|
|
5886
|
-
- \`workflow\` - Current workflow
|
|
5887
|
-
- \`workflowControlContext\` - Workflow control (abort, fail, restart)
|
|
5888
|
-
|
|
5889
|
-
### State Management
|
|
5890
|
-
|
|
5891
|
-
Access and modify tracked state:
|
|
5892
|
-
|
|
5893
|
-
\`\`\`typescript
|
|
5894
|
-
import { bot, user } from '@botpress/runtime'
|
|
5895
|
-
|
|
5896
|
-
// Bot state
|
|
5897
|
-
bot.state.lastIndexed = new Date().toISOString()
|
|
5898
|
-
bot.state.config = { theme: 'dark' }
|
|
5899
|
-
|
|
5900
|
-
// User state
|
|
5901
|
-
user.state.preferences = { notifications: true }
|
|
5902
|
-
user.state.lastActive = Date.now()
|
|
5903
|
-
\`\`\`
|
|
5904
|
-
|
|
5905
|
-
State persists automatically across executions.
|
|
5906
|
-
|
|
5907
|
-
## Advanced Table Operations
|
|
5908
|
-
|
|
5909
|
-
### Table Naming Rules
|
|
5910
|
-
|
|
5911
|
-
**IMPORTANT**: Tables have strict naming requirements:
|
|
5912
|
-
|
|
5913
|
-
\`\`\`typescript
|
|
5914
|
-
// ✅ CORRECT - Name must end with "Table"
|
|
5915
|
-
export const MyDataTable = new Table({
|
|
5916
|
-
name: "mydataTable", // Must end with "Table"
|
|
5917
|
-
columns: { ... }
|
|
5918
|
-
});
|
|
5919
|
-
|
|
5920
|
-
// ❌ WRONG - Missing "Table" suffix
|
|
5921
|
-
name: "mydata"
|
|
5922
|
-
name: "my_data"
|
|
5923
|
-
\`\`\`
|
|
5924
|
-
|
|
5925
|
-
**Reserved column names** - Cannot use these as column names:
|
|
5926
|
-
- \`id\` (auto-generated)
|
|
5927
|
-
- \`createdAt\` (auto-generated)
|
|
5928
|
-
- \`updatedAt\` (auto-generated)
|
|
5929
|
-
- \`computed\`
|
|
5930
|
-
- \`stale\`
|
|
5931
|
-
|
|
5932
|
-
\`\`\`typescript
|
|
5933
|
-
// ❌ WRONG - Using reserved column name
|
|
5934
|
-
columns: {
|
|
5935
|
-
createdAt: z.string() // Reserved!
|
|
5936
|
-
}
|
|
5937
|
-
|
|
5938
|
-
// ✅ CORRECT - Use alternative name
|
|
5939
|
-
columns: {
|
|
5940
|
-
savedAt: z.string()
|
|
5941
|
-
}
|
|
5942
|
-
\`\`\`
|
|
5943
|
-
|
|
5944
|
-
### Auto-Registration
|
|
5945
|
-
|
|
5946
|
-
Files in \`src/tables/\` are **auto-registered** by the ADK. Do NOT re-export from index.ts:
|
|
5947
|
-
|
|
5948
|
-
\`\`\`typescript
|
|
5949
|
-
// src/tables/index.ts
|
|
5950
|
-
// ❌ WRONG - Causes duplicate registration errors
|
|
5951
|
-
export { MyTable } from "./myTable";
|
|
5952
|
-
|
|
5953
|
-
// ✅ CORRECT - Leave empty or add comment
|
|
5954
|
-
// Tables are auto-registered from src/tables/*.ts files
|
|
5955
|
-
\`\`\`
|
|
5956
|
-
|
|
5957
|
-
Same applies to \`src/conversations/\`, \`src/workflows/\`, \`src/triggers/\`, etc.
|
|
5958
|
-
|
|
5959
|
-
Beyond basic CRUD, Tables support powerful query and manipulation features:
|
|
5960
|
-
|
|
5961
|
-
### Complex Filtering
|
|
5962
|
-
|
|
5963
|
-
Use logical operators and conditions:
|
|
5964
|
-
|
|
5965
|
-
\`\`\`typescript
|
|
5966
|
-
await MyTable.findRows({
|
|
5967
|
-
filter: {
|
|
5968
|
-
$and: [
|
|
5969
|
-
{ status: 'open' },
|
|
5970
|
-
{ priority: { $in: ['high', 'urgent'] } }
|
|
5971
|
-
],
|
|
5972
|
-
$or: [
|
|
5973
|
-
{ assignee: userId },
|
|
5974
|
-
{ reporter: userId }
|
|
5975
|
-
],
|
|
5976
|
-
title: { $regex: 'bug|error', $options: 'i' }
|
|
5977
|
-
}
|
|
5978
|
-
})
|
|
5979
|
-
\`\`\`
|
|
5980
|
-
|
|
5981
|
-
**Filter operators:**
|
|
5982
|
-
- \`$eq\`, \`$ne\` - Equal, not equal
|
|
5983
|
-
- \`$gt\`, \`$gte\`, \`$lt\`, \`$lte\` - Comparisons
|
|
5984
|
-
- \`$in\`, \`$nin\` - In array, not in array
|
|
5985
|
-
- \`$exists\` - Field exists
|
|
5986
|
-
- \`$regex\` - Regular expression match
|
|
5987
|
-
- \`$options\` - Regex options (e.g., 'i' for case-insensitive)
|
|
5988
|
-
- \`$and\`, \`$or\` - Logical operators
|
|
5989
|
-
|
|
5990
|
-
### Full-Text Search
|
|
5991
|
-
|
|
5992
|
-
Search across searchable columns:
|
|
5993
|
-
|
|
5994
|
-
\`\`\`typescript
|
|
5995
|
-
await MyTable.findRows({
|
|
5996
|
-
search: 'query string',
|
|
5997
|
-
filter: { status: 'active' }
|
|
5998
|
-
})
|
|
5999
|
-
\`\`\`
|
|
6000
|
-
|
|
6001
|
-
Mark columns as searchable in schema:
|
|
6002
|
-
|
|
6003
|
-
\`\`\`typescript
|
|
6004
|
-
columns: {
|
|
6005
|
-
title: z.string().searchable(),
|
|
6006
|
-
description: z.string().searchable()
|
|
6007
|
-
}
|
|
6008
|
-
\`\`\`
|
|
6009
|
-
|
|
6010
|
-
### Aggregation and Grouping
|
|
6011
|
-
|
|
6012
|
-
Group and aggregate data:
|
|
6013
|
-
|
|
6014
|
-
\`\`\`typescript
|
|
6015
|
-
await MyTable.findRows({
|
|
6016
|
-
group: {
|
|
6017
|
-
status: 'count',
|
|
6018
|
-
priority: ['sum', 'avg'],
|
|
6019
|
-
complexity: ['max', 'min']
|
|
6020
|
-
}
|
|
6021
|
-
})
|
|
6022
|
-
\`\`\`
|
|
6023
|
-
|
|
6024
|
-
**Aggregation operations:** \`key\`, \`count\`, \`sum\`, \`avg\`, \`max\`, \`min\`, \`unique\`
|
|
6025
|
-
|
|
6026
|
-
### Computed Columns
|
|
6027
|
-
|
|
6028
|
-
Columns with values computed from row data:
|
|
6029
|
-
|
|
6030
|
-
\`\`\`typescript
|
|
6031
|
-
columns: {
|
|
6032
|
-
fullName: {
|
|
6033
|
-
computed: true,
|
|
6034
|
-
schema: z.string(),
|
|
6035
|
-
dependencies: ['firstName', 'lastName'],
|
|
6036
|
-
value: async (row) => \`\${row.firstName} \${row.lastName}\`
|
|
6037
|
-
},
|
|
6038
|
-
age: {
|
|
6039
|
-
computed: true,
|
|
6040
|
-
schema: z.number(),
|
|
6041
|
-
dependencies: ['birthDate'],
|
|
6042
|
-
value: async (row) => {
|
|
6043
|
-
const today = new Date()
|
|
6044
|
-
const birth = new Date(row.birthDate)
|
|
6045
|
-
return today.getFullYear() - birth.getFullYear()
|
|
6046
|
-
}
|
|
6047
|
-
}
|
|
6048
|
-
}
|
|
6049
|
-
\`\`\`
|
|
6050
|
-
|
|
6051
|
-
### Upsert Operations
|
|
6052
|
-
|
|
6053
|
-
Insert or update based on key column:
|
|
6054
|
-
|
|
6055
|
-
\`\`\`typescript
|
|
6056
|
-
await MyTable.upsertRows({
|
|
6057
|
-
rows: [
|
|
6058
|
-
{ externalId: '123', name: 'Item 1' },
|
|
6059
|
-
{ externalId: '456', name: 'Item 2' }
|
|
6060
|
-
],
|
|
6061
|
-
keyColumn: 'externalId', // Update if exists, insert if not
|
|
6062
|
-
waitComputed: true // Wait for computed columns to update
|
|
6063
|
-
})
|
|
6064
|
-
\`\`\`
|
|
6065
|
-
|
|
6066
|
-
### Bulk Operations
|
|
6067
|
-
|
|
6068
|
-
Efficient batch operations:
|
|
6069
|
-
|
|
6070
|
-
\`\`\`typescript
|
|
6071
|
-
// Delete by filter
|
|
6072
|
-
await MyTable.deleteRows({
|
|
6073
|
-
filter: { status: 'archived', createdAt: { $lt: '2024-01-01' } }
|
|
6074
|
-
})
|
|
6075
|
-
|
|
6076
|
-
// Delete by IDs
|
|
6077
|
-
await MyTable.deleteRowIds([1, 2, 3])
|
|
6078
|
-
|
|
6079
|
-
// Delete all
|
|
6080
|
-
await MyTable.deleteAllRows()
|
|
6081
|
-
|
|
6082
|
-
// Update multiple
|
|
6083
|
-
await MyTable.updateRows({
|
|
6084
|
-
rows: [
|
|
6085
|
-
{ id: 1, status: 'active' },
|
|
6086
|
-
{ id: 2, status: 'inactive' }
|
|
6087
|
-
],
|
|
6088
|
-
waitComputed: true
|
|
6089
|
-
})
|
|
6090
|
-
\`\`\`
|
|
6091
|
-
|
|
6092
|
-
### Error Handling
|
|
6093
|
-
|
|
6094
|
-
Collect errors and warnings from bulk operations:
|
|
6095
|
-
|
|
6096
|
-
\`\`\`typescript
|
|
6097
|
-
const { errors, warnings } = await MyTable.createRows({
|
|
6098
|
-
rows: data,
|
|
6099
|
-
waitComputed: true
|
|
6100
|
-
})
|
|
6101
|
-
|
|
6102
|
-
if (errors?.length) {
|
|
6103
|
-
console.error('Failed rows:', errors)
|
|
6104
|
-
}
|
|
6105
|
-
if (warnings?.length) {
|
|
6106
|
-
console.warn('Warnings:', warnings)
|
|
6107
|
-
}
|
|
6108
|
-
\`\`\`
|
|
6109
|
-
|
|
6110
|
-
## Knowledge Base Operations
|
|
6111
|
-
|
|
6112
|
-
### Data Sources
|
|
6113
|
-
|
|
6114
|
-
Multiple source types for knowledge bases:
|
|
6115
|
-
|
|
6116
|
-
#### Directory Source
|
|
6117
|
-
|
|
6118
|
-
\`\`\`typescript
|
|
6119
|
-
import { DataSource } from '@botpress/runtime'
|
|
6120
|
-
|
|
6121
|
-
const docs = DataSource.Directory.fromPath('src/knowledge', {
|
|
6122
|
-
id: 'docs',
|
|
6123
|
-
filter: (path) => path.endsWith('.md') || path.endsWith('.txt')
|
|
6124
|
-
})
|
|
6125
|
-
\`\`\`
|
|
6126
|
-
|
|
6127
|
-
#### Website Source
|
|
6128
|
-
|
|
6129
|
-
\`\`\`typescript
|
|
6130
|
-
const siteDocs = DataSource.Website.fromSitemap('https://example.com/sitemap.xml', {
|
|
6131
|
-
id: 'website',
|
|
6132
|
-
maxPages: 500,
|
|
6133
|
-
fetch: 'node:fetch' // or custom fetch implementation
|
|
6134
|
-
})
|
|
6135
|
-
\`\`\`
|
|
6136
|
-
|
|
6137
|
-
### Knowledge Base Definition
|
|
6138
|
-
|
|
6139
|
-
\`\`\`typescript
|
|
6140
|
-
import { Knowledge } from '@botpress/runtime'
|
|
6141
|
-
|
|
6142
|
-
export default new Knowledge({
|
|
6143
|
-
name: 'docs',
|
|
6144
|
-
description: 'Product documentation',
|
|
6145
|
-
sources: [docsDirectory, websiteSource]
|
|
6146
|
-
})
|
|
6147
|
-
\`\`\`
|
|
6148
|
-
|
|
6149
|
-
### Refresh Operations
|
|
6150
|
-
|
|
6151
|
-
Manually refresh knowledge base content:
|
|
6152
|
-
|
|
6153
|
-
\`\`\`typescript
|
|
6154
|
-
// Refresh entire knowledge base
|
|
6155
|
-
await DocsKB.refresh({ force: true })
|
|
6156
|
-
|
|
6157
|
-
// Refresh specific source
|
|
6158
|
-
await DocsKB.refreshSource('website', { force: true })
|
|
6159
|
-
\`\`\`
|
|
6160
|
-
|
|
6161
|
-
**Options:**
|
|
6162
|
-
- \`force: true\` - Force refresh even if recently updated
|
|
6163
|
-
- Automatic refresh via scheduled workflows recommended
|
|
6164
|
-
|
|
6165
|
-
### Using Knowledge in Execute
|
|
6166
|
-
|
|
6167
|
-
\`\`\`typescript
|
|
6168
|
-
await execute({
|
|
6169
|
-
instructions: 'Answer using the documentation',
|
|
6170
|
-
knowledge: [DocsKB, APIKB],
|
|
6171
|
-
tools: [searchTool]
|
|
6172
|
-
})
|
|
6173
|
-
\`\`\`
|
|
6174
|
-
|
|
6175
|
-
Knowledge bases are automatically searchable via the \`search_knowledge\` tool.
|
|
6176
|
-
|
|
6177
|
-
## Advanced Conversation Patterns
|
|
6178
|
-
|
|
6179
|
-
### Multiple Channel Support
|
|
6180
|
-
|
|
6181
|
-
Handle messages from multiple channels in one handler:
|
|
6182
|
-
|
|
6183
|
-
\`\`\`typescript
|
|
6184
|
-
export default new Conversation({
|
|
6185
|
-
channel: ['chat.channel', 'webchat.channel', 'slack.dm'],
|
|
6186
|
-
handler: async ({ channel, execute }) => {
|
|
6187
|
-
console.log(\`Message from: \${channel}\`)
|
|
6188
|
-
await execute({ instructions: '...' })
|
|
6189
|
-
}
|
|
6190
|
-
})
|
|
6191
|
-
\`\`\`
|
|
6192
|
-
|
|
6193
|
-
### Event Handling
|
|
6194
|
-
|
|
6195
|
-
Subscribe to integration events:
|
|
6196
|
-
|
|
6197
|
-
\`\`\`typescript
|
|
6198
|
-
export default new Conversation({
|
|
6199
|
-
channel: 'webchat.channel',
|
|
6200
|
-
events: ['webchat:conversationStarted', 'webchat:conversationEnded'],
|
|
6201
|
-
handler: async ({ type, event, message }) => {
|
|
6202
|
-
if (type === 'event' && event.type === 'webchat:conversationStarted') {
|
|
6203
|
-
// Send welcome message
|
|
6204
|
-
await conversation.send({
|
|
6205
|
-
type: 'text',
|
|
6206
|
-
payload: { text: 'Welcome!' }
|
|
6207
|
-
})
|
|
6208
|
-
}
|
|
6209
|
-
|
|
6210
|
-
if (type === 'message' && message?.type === 'text') {
|
|
6211
|
-
// Handle regular messages
|
|
6212
|
-
await execute({ instructions: '...' })
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
})
|
|
6216
|
-
\`\`\`
|
|
6217
|
-
|
|
6218
|
-
### Workflow Request Handling
|
|
6219
|
-
|
|
6220
|
-
Handle data requests from workflows:
|
|
6221
|
-
|
|
6222
|
-
\`\`\`typescript
|
|
6223
|
-
import { isWorkflowDataRequest } from '@botpress/runtime'
|
|
6224
|
-
|
|
6225
|
-
handler: async ({ type, event, execute }) => {
|
|
6226
|
-
// Check if this is a workflow requesting data
|
|
6227
|
-
if (type === 'workflow_request' && isWorkflowDataRequest(event)) {
|
|
6228
|
-
const userInput = await promptUser(event.payload.message)
|
|
6229
|
-
|
|
6230
|
-
// Provide data back to workflow
|
|
6231
|
-
await workflow.provide(event, { topic: userInput })
|
|
6232
|
-
return
|
|
6233
|
-
}
|
|
6234
|
-
|
|
6235
|
-
// Regular message handling
|
|
6236
|
-
await execute({ instructions: '...' })
|
|
6237
|
-
}
|
|
6238
|
-
\`\`\`
|
|
6239
|
-
|
|
6240
|
-
### Typed Workflow Interactions
|
|
6241
|
-
|
|
6242
|
-
Work with typed workflow instances:
|
|
6243
|
-
|
|
6244
|
-
\`\`\`typescript
|
|
6245
|
-
import { isWorkflow, ResearchWorkflow } from '@botpress/runtime'
|
|
6246
|
-
|
|
6247
|
-
handler: async ({ state }) => {
|
|
6248
|
-
if (state.research && isWorkflow(state.research, 'research')) {
|
|
6249
|
-
// state.research is now typed as ResearchWorkflow
|
|
6250
|
-
console.log(state.research.status)
|
|
6251
|
-
console.log(state.research.output) // Typed output
|
|
6252
|
-
|
|
6253
|
-
if (state.research.status === 'completed') {
|
|
6254
|
-
await conversation.send({
|
|
6255
|
-
type: 'text',
|
|
6256
|
-
payload: { text: state.research.output.result }
|
|
6257
|
-
})
|
|
6258
|
-
}
|
|
6259
|
-
}
|
|
6260
|
-
}
|
|
6261
|
-
\`\`\`
|
|
6262
|
-
|
|
6263
|
-
### Dynamic Tools Based on State
|
|
6264
|
-
|
|
6265
|
-
Provide different tools based on conversation state:
|
|
6266
|
-
|
|
6267
|
-
\`\`\`typescript
|
|
6268
|
-
handler: async ({ state, execute }) => {
|
|
6269
|
-
const tools = () => {
|
|
6270
|
-
if (state.workflowRunning) {
|
|
6271
|
-
return [cancelWorkflowTool, checkStatusTool]
|
|
6272
|
-
} else {
|
|
6273
|
-
return [startWorkflowTool, browseTool, searchTool]
|
|
6274
|
-
}
|
|
6275
|
-
}
|
|
6276
|
-
|
|
6277
|
-
await execute({
|
|
6278
|
-
instructions: '...',
|
|
6279
|
-
tools: tools()
|
|
6280
|
-
})
|
|
6281
|
-
}
|
|
6282
|
-
\`\`\`
|
|
6283
|
-
|
|
6284
|
-
### Message Sending
|
|
6285
|
-
|
|
6286
|
-
Send different message types:
|
|
6287
|
-
|
|
6288
|
-
\`\`\`typescript
|
|
6289
|
-
// Text message
|
|
6290
|
-
await conversation.send({
|
|
6291
|
-
type: 'text',
|
|
6292
|
-
payload: { text: 'Hello!' }
|
|
6293
|
-
})
|
|
6294
|
-
|
|
6295
|
-
// Custom message type (integration-specific)
|
|
6296
|
-
await conversation.send({
|
|
6297
|
-
type: 'custom:messageType',
|
|
6298
|
-
payload: { data: 'custom payload' }
|
|
6299
|
-
})
|
|
6300
|
-
\`\`\`
|
|
6301
|
-
|
|
6302
|
-
## Citations System
|
|
6303
|
-
|
|
6304
|
-
Track and manage source citations for LLM responses:
|
|
6305
|
-
|
|
6306
|
-
### CitationsManager
|
|
6307
|
-
|
|
6308
|
-
Access via context:
|
|
6309
|
-
|
|
6310
|
-
\`\`\`typescript
|
|
6311
|
-
import { context } from '@botpress/runtime'
|
|
6312
|
-
|
|
6313
|
-
const citations = context.get('citations')
|
|
6314
|
-
\`\`\`
|
|
6315
|
-
|
|
6316
|
-
### Registering Sources
|
|
6317
|
-
|
|
6318
|
-
Register sources that can be cited:
|
|
6319
|
-
|
|
6320
|
-
\`\`\`typescript
|
|
6321
|
-
// Register with URL
|
|
6322
|
-
const { tag } = citations.registerSource({
|
|
6323
|
-
url: 'https://example.com/doc',
|
|
6324
|
-
title: 'Documentation Page'
|
|
6325
|
-
})
|
|
6326
|
-
|
|
6327
|
-
// Register with file reference
|
|
6328
|
-
const { tag } = citations.registerSource({
|
|
6329
|
-
file: fileKey,
|
|
6330
|
-
title: 'Internal Document'
|
|
6331
|
-
})
|
|
6332
|
-
\`\`\`
|
|
6333
|
-
|
|
6334
|
-
### Using Citation Tags
|
|
6335
|
-
|
|
6336
|
-
Inject citation tags into LLM content:
|
|
6337
|
-
|
|
6338
|
-
\`\`\`typescript
|
|
6339
|
-
const results = await searchKnowledgeBase(query)
|
|
6340
|
-
|
|
6341
|
-
for (const result of results) {
|
|
6342
|
-
const { tag } = citations.registerSource({
|
|
6343
|
-
file: result.file.key,
|
|
6344
|
-
title: result.file.name
|
|
6345
|
-
})
|
|
6346
|
-
|
|
6347
|
-
content += \`\${result.content} \${tag}\\n\`
|
|
6348
|
-
}
|
|
6349
|
-
|
|
6350
|
-
// Return cited content
|
|
6351
|
-
throw new ThinkSignal('results', content)
|
|
6352
|
-
\`\`\`
|
|
6353
|
-
|
|
6354
|
-
### Citation Format
|
|
6355
|
-
|
|
6356
|
-
Citations are automatically formatted with tags like \`[1]\`, \`[2]\`, etc., and tracked by the system for reference.
|
|
6357
|
-
|
|
6358
|
-
### Example: Tool with Citations
|
|
6359
|
-
|
|
6360
|
-
\`\`\`typescript
|
|
6361
|
-
export default new Autonomous.Tool({
|
|
6362
|
-
name: 'search_docs',
|
|
6363
|
-
description: 'Search documentation',
|
|
6364
|
-
handler: async ({ query }) => {
|
|
6365
|
-
const citations = context.get('citations')
|
|
6366
|
-
const results = await searchDocs(query)
|
|
6367
|
-
|
|
6368
|
-
let response = ''
|
|
6369
|
-
for (const doc of results) {
|
|
6370
|
-
const { tag } = citations.registerSource({
|
|
6371
|
-
url: doc.url,
|
|
6372
|
-
title: doc.title
|
|
6373
|
-
})
|
|
6374
|
-
response += \`\${doc.content} \${tag}\\n\\n\`
|
|
6375
|
-
}
|
|
6376
|
-
|
|
6377
|
-
return response
|
|
6378
|
-
}
|
|
6379
|
-
})
|
|
6380
|
-
\`\`\`
|
|
6381
|
-
|
|
6382
|
-
## Common Mistakes to Avoid
|
|
6383
|
-
|
|
6384
|
-
### 1. Wrong Zai Import
|
|
6385
|
-
\`\`\`typescript
|
|
6386
|
-
// ❌ WRONG
|
|
6387
|
-
import { zai } from '@botpress/runtime'
|
|
6388
|
-
const result = await zai.extract(...)
|
|
6389
|
-
|
|
6390
|
-
// ✅ CORRECT
|
|
6391
|
-
import { adk } from '@botpress/runtime'
|
|
6392
|
-
const result = await adk.zai.extract(...)
|
|
6393
|
-
\`\`\`
|
|
6394
|
-
|
|
6395
|
-
### 2. Expecting \`.output\` from zai.extract
|
|
6396
|
-
\`\`\`typescript
|
|
6397
|
-
// ❌ WRONG - zai.extract returns data directly
|
|
6398
|
-
const result = await adk.zai.extract(input, schema)
|
|
6399
|
-
console.log(result.output) // undefined!
|
|
6400
|
-
|
|
6401
|
-
// ✅ CORRECT
|
|
6402
|
-
const result = await adk.zai.extract(input, schema)
|
|
6403
|
-
console.log(result) // { name: "John", age: 30 }
|
|
6404
|
-
\`\`\`
|
|
6405
|
-
|
|
6406
|
-
### 3. Wrong Exit Result Handling
|
|
6407
|
-
\`\`\`typescript
|
|
6408
|
-
// ❌ WRONG
|
|
6409
|
-
if (result.exit?.name === 'my_exit') {
|
|
6410
|
-
const data = result.exit.value
|
|
6411
|
-
}
|
|
6412
|
-
|
|
6413
|
-
// ✅ CORRECT
|
|
6414
|
-
if (result.is(MyExit)) {
|
|
6415
|
-
const data = result.output // Type-safe!
|
|
6416
|
-
}
|
|
6417
|
-
\`\`\`
|
|
6418
|
-
|
|
6419
|
-
### 4. Reserved Table Column Names
|
|
6420
|
-
\`\`\`typescript
|
|
6421
|
-
// ❌ WRONG - These are reserved
|
|
6422
|
-
columns: {
|
|
6423
|
-
id: z.string(),
|
|
6424
|
-
createdAt: z.string(),
|
|
6425
|
-
updatedAt: z.string()
|
|
6426
|
-
}
|
|
6427
|
-
|
|
6428
|
-
// ✅ CORRECT - Use alternatives
|
|
6429
|
-
columns: {
|
|
6430
|
-
visibleId: z.string(),
|
|
6431
|
-
savedAt: z.string(),
|
|
6432
|
-
modifiedAt: z.string()
|
|
6433
|
-
}
|
|
6434
|
-
\`\`\`
|
|
6435
|
-
|
|
6436
|
-
### 5. Re-exporting Auto-Registered Files
|
|
6437
|
-
\`\`\`typescript
|
|
6438
|
-
// ❌ WRONG - src/tables/index.ts
|
|
6439
|
-
export { MyTable } from "./myTable" // Causes duplicates!
|
|
6440
|
-
|
|
6441
|
-
// ✅ CORRECT - Leave index.ts empty
|
|
6442
|
-
// Files in src/tables/, src/conversations/, etc. are auto-registered
|
|
6443
|
-
\`\`\`
|
|
6444
|
-
|
|
6445
|
-
### 6. Table Name Missing "Table" Suffix
|
|
6446
|
-
\`\`\`typescript
|
|
6447
|
-
// ❌ WRONG
|
|
6448
|
-
name: "users"
|
|
6449
|
-
name: "user_data"
|
|
6450
|
-
|
|
6451
|
-
// ✅ CORRECT
|
|
6452
|
-
name: "usersTable"
|
|
6453
|
-
name: "userdataTable"
|
|
6454
|
-
\`\`\`
|
|
6455
|
-
|
|
6456
|
-
## When Making Changes
|
|
6457
|
-
|
|
6458
|
-
1. **Always search Botpress docs** using \`mcp__botpress-docs__SearchBotpress\`
|
|
6459
|
-
2. **Check examples** for patterns
|
|
6460
|
-
3. **Regenerate types** after changing \`agent.config.ts\` (run \`adk dev\`)
|
|
6461
|
-
4. **Test in dev mode** with hot reloading (\`adk dev\`)
|
|
6462
|
-
5. **Follow TypeScript types** - They're auto-generated from integrations
|
|
6463
|
-
|
|
6464
|
-
## Running Tests
|
|
6465
|
-
|
|
6466
|
-
The ADK provides \`setupTestRuntime()\` to initialize the full ADK runtime within your test process. This sets up all environment variables, generates types, and imports the runtime so your tests can use actions, tools, workflows, etc.
|
|
6467
|
-
|
|
6468
|
-
### Bun Test
|
|
6469
|
-
|
|
6470
|
-
\`\`\`toml
|
|
6471
|
-
# bunfig.toml
|
|
6472
|
-
[test]
|
|
6473
|
-
preload = ["./test-setup.ts"]
|
|
6474
|
-
\`\`\`
|
|
6475
|
-
|
|
6476
|
-
\`\`\`typescript
|
|
6477
|
-
// test-setup.ts
|
|
6478
|
-
import { beforeAll } from "bun:test";
|
|
6479
|
-
import { setupTestRuntime } from "@botpress/adk";
|
|
6480
|
-
|
|
6481
|
-
beforeAll(async () => {
|
|
6482
|
-
const runtime = await setupTestRuntime();
|
|
6483
|
-
await runtime.initialize();
|
|
6484
|
-
});
|
|
6485
|
-
\`\`\`
|
|
6486
|
-
|
|
6487
|
-
### Vitest
|
|
6488
|
-
|
|
6489
|
-
\`\`\`typescript
|
|
6490
|
-
// vitest.setup.ts
|
|
6491
|
-
import { beforeAll } from "vitest";
|
|
6492
|
-
import { setupTestRuntime } from "@botpress/adk";
|
|
6493
|
-
|
|
6494
|
-
beforeAll(async () => {
|
|
6495
|
-
const runtime = await setupTestRuntime();
|
|
6496
|
-
await runtime.initialize();
|
|
6497
|
-
});
|
|
6498
|
-
\`\`\`
|
|
6499
|
-
|
|
6500
|
-
\`\`\`typescript
|
|
6501
|
-
// vitest.config.ts
|
|
6502
|
-
import { defineConfig } from "vitest/config";
|
|
6503
|
-
|
|
6504
|
-
export default defineConfig({
|
|
6505
|
-
test: {
|
|
6506
|
-
setupFiles: ["./vitest.setup.ts"],
|
|
6507
|
-
},
|
|
6508
|
-
});
|
|
6509
|
-
\`\`\`
|
|
6510
|
-
|
|
6511
|
-
### Options
|
|
6512
|
-
|
|
6513
|
-
\`setupTestRuntime()\` auto-detects project path and credentials, but you can override:
|
|
6514
|
-
|
|
6515
|
-
\`\`\`typescript
|
|
6516
|
-
const runtime = await setupTestRuntime({
|
|
6517
|
-
projectPath: "/path/to/agent", // defaults to auto-detect from CWD
|
|
6518
|
-
credentials: { token: "...", apiUrl: "..." }, // defaults to ~/.adk/credentials
|
|
6519
|
-
prod: true, // use production bot instead of dev bot
|
|
6520
|
-
forceRegenerate: true, // force regenerate bot project
|
|
6521
|
-
env: { CUSTOM_VAR: "value" }, // additional env vars
|
|
6522
|
-
});
|
|
6523
|
-
\`\`\`
|
|
6524
|
-
|
|
6525
|
-
### Prerequisites
|
|
6526
|
-
|
|
6527
|
-
- Must have \`@botpress/adk\` installed as a dev dependency (\`bun add -d @botpress/adk\`)
|
|
6528
|
-
- Must have run \`adk dev\` at least once (to create the dev bot)
|
|
6529
|
-
- Must be logged in (\`adk login\`) or provide credentials explicitly
|
|
6530
|
-
|
|
6531
|
-
## Resources
|
|
6532
|
-
|
|
6533
|
-
- [ADK Overview](https://botpress.com/docs/for-developers/adk/overview)
|
|
6534
|
-
- [ADK Getting Started](https://botpress.com/docs/for-developers/adk/getting-started)
|
|
6535
|
-
- [Project Structure](https://botpress.com/docs/for-developers/adk/project-structure)
|
|
6536
|
-
- [Conversations](https://botpress.com/docs/for-developers/adk/concepts/conversations)
|
|
6537
|
-
- [Workflows](https://botpress.com/docs/for-developers/adk/concepts/workflows)
|
|
6538
|
-
`;
|
|
6539
|
-
|
|
6540
|
-
// src/agent-init/agent-project-generator.ts
|
|
6541
|
-
class AgentProjectGenerator {
|
|
6542
|
-
projectPath;
|
|
6543
|
-
projectName;
|
|
6544
|
-
packageManager;
|
|
6545
|
-
template;
|
|
6546
|
-
constructor(projectPath, packageManager = "bun", template = "blank") {
|
|
6547
|
-
this.projectPath = path15.resolve(projectPath);
|
|
6548
|
-
this.projectName = path15.basename(this.projectPath);
|
|
6549
|
-
this.packageManager = packageManager;
|
|
6550
|
-
this.template = template;
|
|
6551
|
-
}
|
|
6552
|
-
async generate() {
|
|
6553
|
-
this.ensureEmptyDirectory();
|
|
6554
|
-
this.createPackageJson();
|
|
6555
|
-
await this.createAgentConfig();
|
|
6556
|
-
this.createTsConfig();
|
|
6557
|
-
this.createAgentJson();
|
|
6558
|
-
this.createGitIgnore();
|
|
6559
|
-
await this.createReadme();
|
|
6560
|
-
this.createClaudeMd();
|
|
6561
|
-
await this.createSourceStructure();
|
|
6562
|
-
}
|
|
6563
|
-
ensureEmptyDirectory() {
|
|
6564
|
-
if (!fs11.existsSync(this.projectPath)) {
|
|
6565
|
-
fs11.mkdirSync(this.projectPath, { recursive: true });
|
|
6566
|
-
}
|
|
6567
|
-
const files = fs11.readdirSync(this.projectPath);
|
|
6568
|
-
if (files.length > 0) {
|
|
6569
|
-
throw new Error(`Directory ${this.projectPath} is not empty. Please use an empty directory.`);
|
|
6570
|
-
}
|
|
6571
|
-
}
|
|
6572
|
-
createPackageJson() {
|
|
6573
|
-
const packageJson = {
|
|
6574
|
-
name: this.projectName,
|
|
6575
|
-
version: "1.0.0",
|
|
6576
|
-
description: `A Botpress Agent built with the ADK`,
|
|
6577
|
-
type: "module",
|
|
6578
|
-
packageManager: this.packageManager === "npm" ? undefined : `${this.packageManager}@latest`,
|
|
6579
|
-
scripts: {
|
|
6580
|
-
dev: "adk dev",
|
|
6581
|
-
build: "adk build",
|
|
6582
|
-
deploy: "adk deploy"
|
|
6583
|
-
},
|
|
6584
|
-
dependencies: {
|
|
6585
|
-
"@botpress/runtime": `^${"1.15.4"}`
|
|
6586
|
-
},
|
|
6587
|
-
devDependencies: {
|
|
6588
|
-
typescript: "^5.9.3"
|
|
6589
|
-
}
|
|
6590
|
-
};
|
|
6591
|
-
if (packageJson.packageManager === undefined) {
|
|
6592
|
-
delete packageJson.packageManager;
|
|
6593
|
-
}
|
|
6594
|
-
this.writeJsonFile("package.json", packageJson);
|
|
6595
|
-
}
|
|
6596
|
-
getDefaultDependencies() {
|
|
6597
|
-
const dependencies = {
|
|
6598
|
-
integrations: {}
|
|
6599
|
-
};
|
|
6600
|
-
if (this.template === "hello-world") {
|
|
6601
|
-
dependencies.integrations = {
|
|
6602
|
-
chat: {
|
|
6603
|
-
version: "chat@latest",
|
|
6604
|
-
enabled: true
|
|
6605
|
-
},
|
|
6606
|
-
webchat: {
|
|
6607
|
-
version: "webchat@latest",
|
|
6608
|
-
enabled: true
|
|
6609
|
-
}
|
|
6610
|
-
};
|
|
6611
|
-
}
|
|
6612
|
-
return dependencies;
|
|
6613
|
-
}
|
|
6614
|
-
async createAgentConfig() {
|
|
6615
|
-
const dependencies = this.getDefaultDependencies();
|
|
6616
|
-
const integrationsJson = JSON.stringify(dependencies.integrations, null, 4).replace(/\n/g, `
|
|
6617
|
-
`);
|
|
6618
|
-
const defaultModels = this.template === "hello-world" ? `
|
|
6619
|
-
defaultModels: {
|
|
6620
|
-
autonomous: "cerebras:gpt-oss-120b",
|
|
6621
|
-
zai: "cerebras:gpt-oss-120b",
|
|
6622
|
-
},
|
|
6623
|
-
` : "";
|
|
6624
|
-
const agentConfig = `import { z, defineConfig } from '@botpress/runtime';
|
|
6625
|
-
|
|
6626
|
-
export default defineConfig({
|
|
6627
|
-
name: '${this.projectName}',
|
|
6628
|
-
description: 'An AI agent built with Botpress ADK',
|
|
6629
|
-
${defaultModels}
|
|
6630
|
-
bot: {
|
|
6631
|
-
state: z.object({}),
|
|
6632
|
-
},
|
|
6633
|
-
|
|
6634
|
-
user: {
|
|
6635
|
-
state: z.object({}),
|
|
6636
|
-
},
|
|
6637
|
-
|
|
6638
|
-
dependencies: {
|
|
6639
|
-
integrations: ${integrationsJson},
|
|
6640
|
-
},
|
|
6641
|
-
});
|
|
6642
|
-
`;
|
|
6643
|
-
await this.writeFormattedFile("agent.config.ts", agentConfig);
|
|
6644
|
-
}
|
|
6645
|
-
createTsConfig() {
|
|
6646
|
-
const tsConfig = {
|
|
6647
|
-
compilerOptions: {
|
|
6648
|
-
target: "ES2022",
|
|
6649
|
-
module: "ES2022",
|
|
6650
|
-
moduleResolution: "Bundler",
|
|
6651
|
-
lib: ["ES2022"],
|
|
6652
|
-
outDir: "./dist",
|
|
6653
|
-
rootDir: ".",
|
|
6654
|
-
strict: true,
|
|
6655
|
-
esModuleInterop: true,
|
|
6656
|
-
allowSyntheticDefaultImports: true,
|
|
6657
|
-
skipLibCheck: true,
|
|
6658
|
-
forceConsistentCasingInFileNames: true,
|
|
6659
|
-
moduleDetection: "force",
|
|
6660
|
-
resolveJsonModule: true,
|
|
6661
|
-
paths: {
|
|
6662
|
-
"@botpress/runtime/_types/*": ["./.adk/*-types"]
|
|
6663
|
-
}
|
|
6664
|
-
},
|
|
6665
|
-
include: ["src/**/*", ".adk/**/*"],
|
|
6666
|
-
exclude: ["node_modules", "dist"]
|
|
6667
|
-
};
|
|
6668
|
-
this.writeJsonFile("tsconfig.json", tsConfig);
|
|
6669
|
-
}
|
|
6670
|
-
createAgentJson() {
|
|
6671
|
-
const agentJson = {};
|
|
6672
|
-
this.writeJsonFile("agent.json", agentJson);
|
|
6673
|
-
}
|
|
6674
|
-
createGitIgnore() {
|
|
6675
|
-
const gitIgnore = `# Dependencies
|
|
6676
|
-
node_modules/
|
|
6677
|
-
.pnpm-store/
|
|
6678
|
-
|
|
6679
|
-
# Build outputs
|
|
6680
|
-
dist/
|
|
6681
|
-
.adk/
|
|
6682
|
-
|
|
6683
|
-
# Environment files
|
|
6684
|
-
.env
|
|
6685
|
-
.env.local
|
|
6686
|
-
.env.production
|
|
6687
|
-
|
|
6688
|
-
# IDE files
|
|
6689
|
-
.vscode/
|
|
6690
|
-
.idea/
|
|
6691
|
-
*.swp
|
|
6692
|
-
*.swo
|
|
6693
|
-
|
|
6694
|
-
# OS files
|
|
6695
|
-
.DS_Store
|
|
6696
|
-
Thumbs.db
|
|
6697
|
-
|
|
6698
|
-
# Logs
|
|
6699
|
-
*.log
|
|
6700
|
-
logs/
|
|
6701
|
-
|
|
6702
|
-
# Runtime files
|
|
6703
|
-
*.pid
|
|
6704
|
-
*.seed
|
|
6705
|
-
*.pid.lock
|
|
6706
|
-
`;
|
|
6707
|
-
this.writeFile(".gitignore", gitIgnore);
|
|
6708
|
-
}
|
|
6709
|
-
async createReadme() {
|
|
6710
|
-
const installCommand = this.packageManager === "npm" ? "npm install" : this.packageManager === "yarn" ? "yarn install" : `${this.packageManager} install`;
|
|
6711
|
-
const readme = `# ${this.projectName}
|
|
6712
|
-
|
|
6713
|
-
A Botpress Agent built with the ADK.
|
|
6714
|
-
|
|
6715
|
-
## Getting Started
|
|
6716
|
-
|
|
6717
|
-
1. Install dependencies:
|
|
6718
|
-
\`\`\`bash
|
|
6719
|
-
${installCommand}
|
|
6720
|
-
\`\`\`
|
|
6721
|
-
|
|
6722
|
-
2. Start development server:
|
|
6723
|
-
\`\`\`bash
|
|
6724
|
-
adk dev
|
|
6725
|
-
\`\`\`
|
|
6726
|
-
|
|
6727
|
-
3. Deploy your agent:
|
|
6728
|
-
\`\`\`bash
|
|
6729
|
-
adk deploy
|
|
6730
|
-
\`\`\`
|
|
6731
|
-
|
|
6732
|
-
## Project Structure
|
|
6733
|
-
|
|
6734
|
-
- \`src/actions/\` - Define callable functions
|
|
6735
|
-
- \`src/workflows/\` - Define long-running processes
|
|
6736
|
-
- \`src/conversations/\` - Define conversation handlers
|
|
6737
|
-
- \`src/tables/\` - Define data storage schemas
|
|
6738
|
-
- \`src/triggers/\` - Define event subscriptions
|
|
6739
|
-
- \`src/knowledge/\` - Add knowledge base files
|
|
6740
|
-
|
|
6741
|
-
## Learn More
|
|
5611
|
+
## Learn More
|
|
6742
5612
|
|
|
6743
5613
|
- [ADK Documentation](https://botpress.com/docs/adk)
|
|
6744
5614
|
- [Botpress Platform](https://botpress.com)
|
|
6745
5615
|
`;
|
|
6746
5616
|
await this.writeFormattedFile("README.md", readme);
|
|
6747
5617
|
}
|
|
6748
|
-
|
|
6749
|
-
|
|
5618
|
+
createAIAssistantInstructions() {
|
|
5619
|
+
const content = ai_assistant_instructions_template_default;
|
|
5620
|
+
this.writeFile("CLAUDE.md", content);
|
|
5621
|
+
this.writeFile("AGENTS.md", content);
|
|
6750
5622
|
}
|
|
6751
5623
|
async createSourceStructure() {
|
|
6752
5624
|
const srcPath = path15.join(this.projectPath, "src");
|
|
@@ -12443,158 +11315,1239 @@ class ScriptRunner {
|
|
|
12443
11315
|
for (const [key, value] of Object.entries(envVars)) {
|
|
12444
11316
|
process.env[key] = value;
|
|
12445
11317
|
}
|
|
12446
|
-
const runtimePath = path41.join(botPath, "src", "index.ts");
|
|
12447
|
-
return {
|
|
12448
|
-
botPath,
|
|
12449
|
-
runtimePath,
|
|
12450
|
-
botId,
|
|
12451
|
-
workspaceId,
|
|
12452
|
-
isProd: this.prod,
|
|
12453
|
-
project,
|
|
12454
|
-
initialize: async () => {
|
|
12455
|
-
const botModule = await import(runtimePath);
|
|
12456
|
-
const runtimeModule = await import("@botpress/runtime/runtime");
|
|
12457
|
-
const { Autonomous } = await import("@botpress/runtime");
|
|
12458
|
-
const { context, agentRegistry } = runtimeModule;
|
|
12459
|
-
const { Client: Client18 } = await import("@botpress/client");
|
|
12460
|
-
const { BotSpecificClient, BotLogger } = await import("@botpress/sdk");
|
|
12461
|
-
const { Cognitive } = await import("@botpress/cognitive");
|
|
12462
|
-
const vanillaClient = new Client18({
|
|
12463
|
-
token: this.credentials.token,
|
|
12464
|
-
apiUrl: this.credentials.apiUrl,
|
|
12465
|
-
botId
|
|
12466
|
-
});
|
|
12467
|
-
const client = new BotSpecificClient(vanillaClient);
|
|
12468
|
-
const cognitive = new Cognitive({
|
|
12469
|
-
client,
|
|
12470
|
-
__experimental_beta: true
|
|
12471
|
-
});
|
|
12472
|
-
const logger = new BotLogger({});
|
|
12473
|
-
context.setDefaultContext({
|
|
12474
|
-
executionId: "test-execution",
|
|
12475
|
-
executionFinished: false,
|
|
12476
|
-
botId,
|
|
12477
|
-
client,
|
|
12478
|
-
cognitive,
|
|
12479
|
-
citations: new Autonomous.CitationsManager,
|
|
12480
|
-
logger,
|
|
12481
|
-
configuration: configuration ?? {},
|
|
12482
|
-
integrations: agentRegistry.integrations,
|
|
12483
|
-
interfaces: agentRegistry.interfaces,
|
|
12484
|
-
states: [],
|
|
12485
|
-
tags: [],
|
|
12486
|
-
scheduledHeavyImports: new Set
|
|
12487
|
-
});
|
|
12488
|
-
return botModule.default;
|
|
11318
|
+
const runtimePath = path41.join(botPath, "src", "index.ts");
|
|
11319
|
+
return {
|
|
11320
|
+
botPath,
|
|
11321
|
+
runtimePath,
|
|
11322
|
+
botId,
|
|
11323
|
+
workspaceId,
|
|
11324
|
+
isProd: this.prod,
|
|
11325
|
+
project,
|
|
11326
|
+
initialize: async () => {
|
|
11327
|
+
const botModule = await import(runtimePath);
|
|
11328
|
+
const runtimeModule = await import("@botpress/runtime/runtime");
|
|
11329
|
+
const { Autonomous } = await import("@botpress/runtime");
|
|
11330
|
+
const { context, agentRegistry } = runtimeModule;
|
|
11331
|
+
const { Client: Client18 } = await import("@botpress/client");
|
|
11332
|
+
const { BotSpecificClient, BotLogger } = await import("@botpress/sdk");
|
|
11333
|
+
const { Cognitive } = await import("@botpress/cognitive");
|
|
11334
|
+
const vanillaClient = new Client18({
|
|
11335
|
+
token: this.credentials.token,
|
|
11336
|
+
apiUrl: this.credentials.apiUrl,
|
|
11337
|
+
botId
|
|
11338
|
+
});
|
|
11339
|
+
const client = new BotSpecificClient(vanillaClient);
|
|
11340
|
+
const cognitive = new Cognitive({
|
|
11341
|
+
client,
|
|
11342
|
+
__experimental_beta: true
|
|
11343
|
+
});
|
|
11344
|
+
const logger = new BotLogger({});
|
|
11345
|
+
context.setDefaultContext({
|
|
11346
|
+
executionId: "test-execution",
|
|
11347
|
+
executionFinished: false,
|
|
11348
|
+
botId,
|
|
11349
|
+
client,
|
|
11350
|
+
cognitive,
|
|
11351
|
+
citations: new Autonomous.CitationsManager,
|
|
11352
|
+
logger,
|
|
11353
|
+
configuration: configuration ?? {},
|
|
11354
|
+
integrations: agentRegistry.integrations,
|
|
11355
|
+
interfaces: agentRegistry.interfaces,
|
|
11356
|
+
states: [],
|
|
11357
|
+
tags: [],
|
|
11358
|
+
scheduledHeavyImports: new Set
|
|
11359
|
+
});
|
|
11360
|
+
return botModule.default;
|
|
11361
|
+
}
|
|
11362
|
+
};
|
|
11363
|
+
}
|
|
11364
|
+
async run(scriptPath, options = {}) {
|
|
11365
|
+
const { botPath, runnerPath, project } = await this.prepare();
|
|
11366
|
+
const absoluteScriptPath = path41.isAbsolute(scriptPath) ? scriptPath : path41.resolve(this.projectPath, scriptPath);
|
|
11367
|
+
if (!existsSync10(absoluteScriptPath)) {
|
|
11368
|
+
throw new Error(`Script not found: ${absoluteScriptPath}`);
|
|
11369
|
+
}
|
|
11370
|
+
const botId = this.prod ? project.agentInfo?.botId : project.agentInfo?.devId || project.agentInfo?.botId;
|
|
11371
|
+
const workspaceId = project.agentInfo?.workspaceId;
|
|
11372
|
+
if (!botId) {
|
|
11373
|
+
const idType = this.prod ? "botId" : "devId";
|
|
11374
|
+
throw new Error(`No ${idType} found in agent.json. ` + (this.prod ? 'Please deploy your agent first with "adk deploy".' : 'Please run "adk dev" first to create a development bot, or use --prod to use the production bot.'));
|
|
11375
|
+
}
|
|
11376
|
+
const args = ["run", runnerPath, absoluteScriptPath, ...options.args || []];
|
|
11377
|
+
let configuration;
|
|
11378
|
+
try {
|
|
11379
|
+
const manager3 = new ConfigManager(botId);
|
|
11380
|
+
configuration = await manager3.getAll();
|
|
11381
|
+
} catch {}
|
|
11382
|
+
const env = {
|
|
11383
|
+
...process.env,
|
|
11384
|
+
ADK_PROJECT_PATH: this.projectPath,
|
|
11385
|
+
ADK_BOT_PATH: botPath,
|
|
11386
|
+
ADK_BOT_ID: botId,
|
|
11387
|
+
ADK_WORKSPACE_ID: workspaceId || "",
|
|
11388
|
+
ADK_IS_PROD: this.prod ? "true" : "false",
|
|
11389
|
+
BP_DISABLE_WORKER_MODE: "true",
|
|
11390
|
+
...options.env,
|
|
11391
|
+
ADK_SCRIPT_MODE: "true",
|
|
11392
|
+
ADK_SCRIPT_PATH: absoluteScriptPath,
|
|
11393
|
+
ADK_TOKEN: this.credentials.token,
|
|
11394
|
+
ADK_API_URL: this.credentials.apiUrl,
|
|
11395
|
+
...configuration && { ADK_CONFIGURATION: JSON.stringify(configuration) }
|
|
11396
|
+
};
|
|
11397
|
+
return new Promise((resolve3, reject) => {
|
|
11398
|
+
const child = spawn("bun", args, {
|
|
11399
|
+
cwd: botPath,
|
|
11400
|
+
env,
|
|
11401
|
+
stdio: options.inheritStdio !== false ? "inherit" : "pipe"
|
|
11402
|
+
});
|
|
11403
|
+
child.on("error", (error) => {
|
|
11404
|
+
reject(error);
|
|
11405
|
+
});
|
|
11406
|
+
child.on("close", (code) => {
|
|
11407
|
+
resolve3(code ?? 0);
|
|
11408
|
+
});
|
|
11409
|
+
});
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
async function runScript(options) {
|
|
11413
|
+
const runner = new ScriptRunner({
|
|
11414
|
+
projectPath: options.projectPath,
|
|
11415
|
+
forceRegenerate: options.forceRegenerate,
|
|
11416
|
+
prod: options.prod,
|
|
11417
|
+
credentials: options.credentials
|
|
11418
|
+
});
|
|
11419
|
+
return runner.run(options.scriptPath, {
|
|
11420
|
+
args: options.args,
|
|
11421
|
+
env: options.env,
|
|
11422
|
+
inheritStdio: options.inheritStdio
|
|
11423
|
+
});
|
|
11424
|
+
}
|
|
11425
|
+
async function setupTestRuntime(options = {}) {
|
|
11426
|
+
let projectPath = options.projectPath;
|
|
11427
|
+
if (!projectPath) {
|
|
11428
|
+
const detected = await findAgentRoot(process.cwd());
|
|
11429
|
+
if (!detected) {
|
|
11430
|
+
throw new Error(`Could not find ADK agent project. No agent.config.ts found in current directory or parents.
|
|
11431
|
+
Either run from within an agent project directory, or provide projectPath explicitly.`);
|
|
11432
|
+
}
|
|
11433
|
+
projectPath = detected;
|
|
11434
|
+
}
|
|
11435
|
+
let credentials = options.credentials;
|
|
11436
|
+
if (!credentials) {
|
|
11437
|
+
const credentialsManager = new CredentialsManager;
|
|
11438
|
+
const loadedCredentials = await credentialsManager.getCredentials();
|
|
11439
|
+
if (!loadedCredentials) {
|
|
11440
|
+
throw new Error('No credentials found. Please run "adk login" first, or provide credentials explicitly.');
|
|
11441
|
+
}
|
|
11442
|
+
credentials = {
|
|
11443
|
+
token: loadedCredentials.token,
|
|
11444
|
+
apiUrl: loadedCredentials.apiUrl
|
|
11445
|
+
};
|
|
11446
|
+
}
|
|
11447
|
+
const runner = new ScriptRunner({
|
|
11448
|
+
projectPath,
|
|
11449
|
+
forceRegenerate: options.forceRegenerate,
|
|
11450
|
+
prod: options.prod,
|
|
11451
|
+
credentials
|
|
11452
|
+
});
|
|
11453
|
+
return runner.setupTestRuntime({ env: options.env });
|
|
11454
|
+
}
|
|
11455
|
+
// src/eval/types.ts
|
|
11456
|
+
function defineEval(def) {
|
|
11457
|
+
return def;
|
|
11458
|
+
}
|
|
11459
|
+
// src/eval/loader.ts
|
|
11460
|
+
import { readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
|
|
11461
|
+
import { resolve as resolve3 } from "path";
|
|
11462
|
+
async function loadEvalFile(filePath) {
|
|
11463
|
+
const absPath = resolve3(filePath);
|
|
11464
|
+
const mod = await import(absPath);
|
|
11465
|
+
const def = mod.default;
|
|
11466
|
+
if (!def || typeof def !== "object" || !def.name || !Array.isArray(def.conversation)) {
|
|
11467
|
+
throw new Error(`Invalid eval file ${filePath}: must export default a defineEval({...}) object with name and conversation`);
|
|
11468
|
+
}
|
|
11469
|
+
return def;
|
|
11470
|
+
}
|
|
11471
|
+
async function loadEvalsFromDir(dirPath) {
|
|
11472
|
+
const absDir = resolve3(dirPath);
|
|
11473
|
+
if (!existsSync11(absDir)) {
|
|
11474
|
+
return [];
|
|
11475
|
+
}
|
|
11476
|
+
const files = readdirSync3(absDir).filter((f) => f.endsWith(".eval.ts"));
|
|
11477
|
+
const evals = [];
|
|
11478
|
+
for (const f of files) {
|
|
11479
|
+
evals.push(await loadEvalFile(`${absDir}/${f}`));
|
|
11480
|
+
}
|
|
11481
|
+
return evals;
|
|
11482
|
+
}
|
|
11483
|
+
async function loadEvalByName(dirPath, name) {
|
|
11484
|
+
const absDir = resolve3(dirPath);
|
|
11485
|
+
if (!existsSync11(absDir))
|
|
11486
|
+
return null;
|
|
11487
|
+
const files = readdirSync3(absDir).filter((f) => f.endsWith(".eval.ts"));
|
|
11488
|
+
for (const f of files) {
|
|
11489
|
+
const def = await loadEvalFile(`${absDir}/${f}`);
|
|
11490
|
+
if (def.name === name)
|
|
11491
|
+
return def;
|
|
11492
|
+
}
|
|
11493
|
+
return null;
|
|
11494
|
+
}
|
|
11495
|
+
function filterEvals(evals, filter) {
|
|
11496
|
+
if (!filter)
|
|
11497
|
+
return evals;
|
|
11498
|
+
return evals.filter((e) => {
|
|
11499
|
+
if (filter.names && filter.names.length > 0) {
|
|
11500
|
+
if (!filter.names.includes(e.name))
|
|
11501
|
+
return false;
|
|
11502
|
+
}
|
|
11503
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
11504
|
+
if (!e.tags || !filter.tags.some((t) => e.tags.includes(t)))
|
|
11505
|
+
return false;
|
|
11506
|
+
}
|
|
11507
|
+
if (filter.type) {
|
|
11508
|
+
if (e.type !== filter.type)
|
|
11509
|
+
return false;
|
|
11510
|
+
}
|
|
11511
|
+
return true;
|
|
11512
|
+
});
|
|
11513
|
+
}
|
|
11514
|
+
// src/eval/runner.ts
|
|
11515
|
+
import { Client as BpClient2 } from "@botpress/client";
|
|
11516
|
+
|
|
11517
|
+
// src/eval/client.ts
|
|
11518
|
+
import { Client as BpClient } from "@botpress/client";
|
|
11519
|
+
import { Client as ChatClient } from "@botpress/chat";
|
|
11520
|
+
|
|
11521
|
+
class ChatSession {
|
|
11522
|
+
webhookId;
|
|
11523
|
+
client = null;
|
|
11524
|
+
conversationId = null;
|
|
11525
|
+
constructor(webhookId) {
|
|
11526
|
+
this.webhookId = webhookId;
|
|
11527
|
+
}
|
|
11528
|
+
async connect() {
|
|
11529
|
+
this.client = await ChatClient.connect({ webhookId: this.webhookId });
|
|
11530
|
+
}
|
|
11531
|
+
get userId() {
|
|
11532
|
+
if (!this.client) {
|
|
11533
|
+
throw new Error("ChatSession not connected. Call connect() first.");
|
|
11534
|
+
}
|
|
11535
|
+
return this.client.user.id;
|
|
11536
|
+
}
|
|
11537
|
+
async sendMessage(message, options = {}) {
|
|
11538
|
+
if (!this.client) {
|
|
11539
|
+
throw new Error("ChatSession not connected. Call connect() first.");
|
|
11540
|
+
}
|
|
11541
|
+
const { timeout = 30000, idleTimeout = 3000 } = options;
|
|
11542
|
+
if (!this.conversationId) {
|
|
11543
|
+
const conv = await this.client.createConversation({});
|
|
11544
|
+
this.conversationId = conv.conversation.id;
|
|
11545
|
+
}
|
|
11546
|
+
const conversationId = this.conversationId;
|
|
11547
|
+
const responses = [];
|
|
11548
|
+
const listener = await this.client.listenConversation({
|
|
11549
|
+
id: conversationId
|
|
11550
|
+
});
|
|
11551
|
+
return new Promise((resolve4, reject) => {
|
|
11552
|
+
let idleTimer = null;
|
|
11553
|
+
let resolved = false;
|
|
11554
|
+
const done = () => {
|
|
11555
|
+
if (resolved)
|
|
11556
|
+
return;
|
|
11557
|
+
resolved = true;
|
|
11558
|
+
if (idleTimer)
|
|
11559
|
+
clearTimeout(idleTimer);
|
|
11560
|
+
clearTimeout(overallTimer);
|
|
11561
|
+
resolve4({ conversationId, responses });
|
|
11562
|
+
};
|
|
11563
|
+
const resetIdle = () => {
|
|
11564
|
+
if (idleTimer)
|
|
11565
|
+
clearTimeout(idleTimer);
|
|
11566
|
+
idleTimer = setTimeout(done, idleTimeout);
|
|
11567
|
+
};
|
|
11568
|
+
const overallTimer = setTimeout(() => {
|
|
11569
|
+
if (!resolved) {
|
|
11570
|
+
if (responses.length > 0) {
|
|
11571
|
+
done();
|
|
11572
|
+
} else {
|
|
11573
|
+
resolved = true;
|
|
11574
|
+
reject(new Error(`Timed out after ${timeout}ms with no bot response.`));
|
|
11575
|
+
}
|
|
11576
|
+
}
|
|
11577
|
+
}, timeout);
|
|
11578
|
+
listener.on("message_created", (event) => {
|
|
11579
|
+
if (resolved)
|
|
11580
|
+
return;
|
|
11581
|
+
if (event.isBot) {
|
|
11582
|
+
const payload = event?.payload;
|
|
11583
|
+
const text = typeof payload === "string" ? payload : payload?.text || JSON.stringify(payload);
|
|
11584
|
+
responses.push({ text, raw: payload });
|
|
11585
|
+
resetIdle();
|
|
11586
|
+
}
|
|
11587
|
+
});
|
|
11588
|
+
listener.on("event_created", (event) => {
|
|
11589
|
+
if (event?.payload?.done) {
|
|
11590
|
+
done();
|
|
11591
|
+
}
|
|
11592
|
+
});
|
|
11593
|
+
this.client.createMessage({
|
|
11594
|
+
conversationId,
|
|
11595
|
+
payload: { type: "text", text: message }
|
|
11596
|
+
}).then(() => {
|
|
11597
|
+
resetIdle();
|
|
11598
|
+
}).catch((err) => {
|
|
11599
|
+
clearTimeout(overallTimer);
|
|
11600
|
+
if (!resolved) {
|
|
11601
|
+
resolved = true;
|
|
11602
|
+
reject(err);
|
|
11603
|
+
}
|
|
11604
|
+
});
|
|
11605
|
+
});
|
|
11606
|
+
}
|
|
11607
|
+
}
|
|
11608
|
+
async function discoverWebhookId(botId, token, apiUrl) {
|
|
11609
|
+
const client = new BpClient({ token, botId, apiUrl });
|
|
11610
|
+
const { bot } = await client.getBot({ id: botId });
|
|
11611
|
+
const integrations = bot.integrations || {};
|
|
11612
|
+
const chat = Object.values(integrations).find((int) => int.name === "chat");
|
|
11613
|
+
const webhookId = chat?.webhookId;
|
|
11614
|
+
if (!webhookId) {
|
|
11615
|
+
throw new Error("No chat integration found on bot. Make sure the bot has the chat integration enabled.");
|
|
11616
|
+
}
|
|
11617
|
+
return webhookId;
|
|
11618
|
+
}
|
|
11619
|
+
|
|
11620
|
+
// src/eval/traces.ts
|
|
11621
|
+
async function fetchTraceSpans(conversationId, devServerUrl) {
|
|
11622
|
+
const url = `${devServerUrl}/api/traces/query?attributeName=conversationId&attributeValue=${encodeURIComponent(conversationId)}&count=1000`;
|
|
11623
|
+
const res = await fetch(url);
|
|
11624
|
+
if (!res.ok) {
|
|
11625
|
+
throw new Error(`Failed to fetch traces: ${res.status} ${res.statusText}`);
|
|
11626
|
+
}
|
|
11627
|
+
const data = await res.json();
|
|
11628
|
+
return Array.isArray(data) ? data : data.spans || [];
|
|
11629
|
+
}
|
|
11630
|
+
function extractToolCalls(spans) {
|
|
11631
|
+
const toolEndSpans = spans.filter((span) => span.name === "autonomous.tool" && span.t === "end" && span.attrs?.["autonomous.tool.name"]);
|
|
11632
|
+
const seen = new Set;
|
|
11633
|
+
const unique = toolEndSpans.filter((span) => {
|
|
11634
|
+
if (seen.has(span.spanId))
|
|
11635
|
+
return false;
|
|
11636
|
+
seen.add(span.spanId);
|
|
11637
|
+
return true;
|
|
11638
|
+
});
|
|
11639
|
+
return unique.sort((a, b) => (a.endNs ?? 0) - (b.endNs ?? 0)).map((span) => {
|
|
11640
|
+
const attrs = span.attrs;
|
|
11641
|
+
let input = {};
|
|
11642
|
+
try {
|
|
11643
|
+
input = JSON.parse(attrs["autonomous.tool.input"]);
|
|
11644
|
+
} catch {}
|
|
11645
|
+
return {
|
|
11646
|
+
name: attrs["autonomous.tool.name"],
|
|
11647
|
+
input,
|
|
11648
|
+
output: attrs["autonomous.tool.output"] || "",
|
|
11649
|
+
status: attrs["autonomous.tool.status"] || "unknown"
|
|
11650
|
+
};
|
|
11651
|
+
});
|
|
11652
|
+
}
|
|
11653
|
+
async function getTraceData(conversationId, devServerUrl, options = {}) {
|
|
11654
|
+
const previousCount = options.previousToolCallCount || 0;
|
|
11655
|
+
const expectNew = options.expectNewCalls ?? false;
|
|
11656
|
+
const maxRetries = expectNew ? 5 : 0;
|
|
11657
|
+
const retryDelay = 500;
|
|
11658
|
+
let allToolCalls = [];
|
|
11659
|
+
let spans = [];
|
|
11660
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
11661
|
+
if (attempt > 0) {
|
|
11662
|
+
await new Promise((resolve4) => setTimeout(resolve4, retryDelay));
|
|
11663
|
+
}
|
|
11664
|
+
spans = await fetchTraceSpans(conversationId, devServerUrl);
|
|
11665
|
+
allToolCalls = extractToolCalls(spans);
|
|
11666
|
+
if (!expectNew || allToolCalls.length > previousCount) {
|
|
11667
|
+
break;
|
|
11668
|
+
}
|
|
11669
|
+
}
|
|
11670
|
+
const toolCalls = allToolCalls.slice(previousCount);
|
|
11671
|
+
return { toolCalls, totalToolCallCount: allToolCalls.length, raw: spans };
|
|
11672
|
+
}
|
|
11673
|
+
|
|
11674
|
+
// src/eval/graders/llm.ts
|
|
11675
|
+
import { Cognitive } from "@botpress/cognitive";
|
|
11676
|
+
import { Client as Client18 } from "@botpress/client";
|
|
11677
|
+
var JUDGE_SYSTEM_PROMPT = `You are an evaluation judge for a chatbot. You will be given:
|
|
11678
|
+
- The user's message
|
|
11679
|
+
- The bot's response
|
|
11680
|
+
- Grading criteria
|
|
11681
|
+
|
|
11682
|
+
Your job is to determine if the bot's response meets the criteria.
|
|
11683
|
+
|
|
11684
|
+
Respond with a JSON object:
|
|
11685
|
+
{
|
|
11686
|
+
"pass": true/false,
|
|
11687
|
+
"score": 1-5,
|
|
11688
|
+
"reason": "brief explanation"
|
|
11689
|
+
}
|
|
11690
|
+
|
|
11691
|
+
Scoring guide:
|
|
11692
|
+
- 5: Fully meets criteria, excellent response
|
|
11693
|
+
- 4: Mostly meets criteria, minor issues
|
|
11694
|
+
- 3: Partially meets criteria, notable gaps
|
|
11695
|
+
- 2: Barely meets criteria, significant issues
|
|
11696
|
+
- 1: Does not meet criteria
|
|
11697
|
+
|
|
11698
|
+
A score of 3 or above is a pass.`;
|
|
11699
|
+
var _cognitive = null;
|
|
11700
|
+
function getCognitive() {
|
|
11701
|
+
if (_cognitive)
|
|
11702
|
+
return _cognitive;
|
|
11703
|
+
const token = process.env.BP_TOKEN || process.env.ADK_TOKEN;
|
|
11704
|
+
const botId = process.env.ADK_BOT_ID;
|
|
11705
|
+
const apiUrl = process.env.ADK_API_URL || "https://api.botpress.cloud";
|
|
11706
|
+
if (!token || !botId)
|
|
11707
|
+
return null;
|
|
11708
|
+
const client = new Client18({ token, apiUrl, botId });
|
|
11709
|
+
_cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
11710
|
+
return _cognitive;
|
|
11711
|
+
}
|
|
11712
|
+
function initLLMJudge(credentials) {
|
|
11713
|
+
const client = new Client18({
|
|
11714
|
+
token: credentials.token,
|
|
11715
|
+
apiUrl: credentials.apiUrl,
|
|
11716
|
+
botId: credentials.botId
|
|
11717
|
+
});
|
|
11718
|
+
_cognitive = new Cognitive({ client, __experimental_beta: true });
|
|
11719
|
+
}
|
|
11720
|
+
async function gradeLLMJudge(botResponse, criteria, context) {
|
|
11721
|
+
try {
|
|
11722
|
+
const cognitive = getCognitive();
|
|
11723
|
+
if (!cognitive) {
|
|
11724
|
+
return {
|
|
11725
|
+
assertion: `llm_judge: "${criteria}"`,
|
|
11726
|
+
pass: true,
|
|
11727
|
+
expected: criteria,
|
|
11728
|
+
actual: "SKIPPED — LLM judge unavailable: no credentials configured"
|
|
11729
|
+
};
|
|
11730
|
+
}
|
|
11731
|
+
const { output } = await cognitive.generateContent({
|
|
11732
|
+
model: "fast",
|
|
11733
|
+
temperature: 0,
|
|
11734
|
+
responseFormat: "json_object",
|
|
11735
|
+
systemPrompt: JUDGE_SYSTEM_PROMPT,
|
|
11736
|
+
messages: [
|
|
11737
|
+
{
|
|
11738
|
+
role: "user",
|
|
11739
|
+
content: `User message: ${context.userMessage}
|
|
11740
|
+
|
|
11741
|
+
Bot response: ${botResponse}
|
|
11742
|
+
|
|
11743
|
+
Criteria: ${criteria}`
|
|
11744
|
+
}
|
|
11745
|
+
]
|
|
11746
|
+
});
|
|
11747
|
+
const rawContent = output?.choices?.[0]?.content;
|
|
11748
|
+
const content = typeof rawContent === "string" ? rawContent : null;
|
|
11749
|
+
if (!content) {
|
|
11750
|
+
return {
|
|
11751
|
+
assertion: `llm_judge: "${criteria}"`,
|
|
11752
|
+
pass: true,
|
|
11753
|
+
expected: criteria,
|
|
11754
|
+
actual: "SKIPPED — LLM judge returned empty response"
|
|
11755
|
+
};
|
|
11756
|
+
}
|
|
11757
|
+
const verdict = JSON.parse(content);
|
|
11758
|
+
return {
|
|
11759
|
+
assertion: `llm_judge: "${criteria}"`,
|
|
11760
|
+
pass: verdict.score >= 3,
|
|
11761
|
+
expected: criteria,
|
|
11762
|
+
actual: `Score ${verdict.score}/5 — ${verdict.reason}`
|
|
11763
|
+
};
|
|
11764
|
+
} catch (err) {
|
|
11765
|
+
return {
|
|
11766
|
+
assertion: `llm_judge: "${criteria}"`,
|
|
11767
|
+
pass: true,
|
|
11768
|
+
expected: criteria,
|
|
11769
|
+
actual: `SKIPPED — LLM judge unavailable: ${err.message}`
|
|
11770
|
+
};
|
|
11771
|
+
}
|
|
11772
|
+
}
|
|
11773
|
+
|
|
11774
|
+
// src/eval/graders/response.ts
|
|
11775
|
+
async function gradeResponse(botResponse, assertions, context) {
|
|
11776
|
+
const results = [];
|
|
11777
|
+
for (const assertion of assertions) {
|
|
11778
|
+
if ("contains" in assertion) {
|
|
11779
|
+
const pass = botResponse.toLowerCase().includes(assertion.contains.toLowerCase());
|
|
11780
|
+
results.push({
|
|
11781
|
+
assertion: `contains "${assertion.contains}"`,
|
|
11782
|
+
pass,
|
|
11783
|
+
expected: `Response contains "${assertion.contains}"`,
|
|
11784
|
+
actual: pass ? `Found in response` : `Not found in response`
|
|
11785
|
+
});
|
|
11786
|
+
continue;
|
|
11787
|
+
}
|
|
11788
|
+
if ("not_contains" in assertion) {
|
|
11789
|
+
const pass = !botResponse.toLowerCase().includes(assertion.not_contains.toLowerCase());
|
|
11790
|
+
results.push({
|
|
11791
|
+
assertion: `not_contains "${assertion.not_contains}"`,
|
|
11792
|
+
pass,
|
|
11793
|
+
expected: `Response does not contain "${assertion.not_contains}"`,
|
|
11794
|
+
actual: pass ? `Not found in response` : `Found in response`
|
|
11795
|
+
});
|
|
11796
|
+
continue;
|
|
11797
|
+
}
|
|
11798
|
+
if ("matches" in assertion) {
|
|
11799
|
+
const regex = new RegExp(assertion.matches, "i");
|
|
11800
|
+
const pass = regex.test(botResponse);
|
|
11801
|
+
results.push({
|
|
11802
|
+
assertion: `matches ${assertion.matches}`,
|
|
11803
|
+
pass,
|
|
11804
|
+
expected: `Response matches /${assertion.matches}/`,
|
|
11805
|
+
actual: pass ? `Matched` : `No match`
|
|
11806
|
+
});
|
|
11807
|
+
continue;
|
|
11808
|
+
}
|
|
11809
|
+
if ("llm_judge" in assertion) {
|
|
11810
|
+
const result = await gradeLLMJudge(botResponse, assertion.llm_judge, context);
|
|
11811
|
+
results.push(result);
|
|
11812
|
+
continue;
|
|
11813
|
+
}
|
|
11814
|
+
if ("similar_to" in assertion) {
|
|
11815
|
+
results.push({
|
|
11816
|
+
assertion: `similar_to: "${assertion.similar_to}"`,
|
|
11817
|
+
pass: true,
|
|
11818
|
+
expected: assertion.similar_to,
|
|
11819
|
+
actual: "SKIPPED — similar_to not yet implemented"
|
|
11820
|
+
});
|
|
11821
|
+
continue;
|
|
11822
|
+
}
|
|
11823
|
+
results.push({
|
|
11824
|
+
assertion: "unknown",
|
|
11825
|
+
pass: false,
|
|
11826
|
+
expected: "known assertion type",
|
|
11827
|
+
actual: `Unknown assertion: ${JSON.stringify(assertion)}`
|
|
11828
|
+
});
|
|
11829
|
+
}
|
|
11830
|
+
return results;
|
|
11831
|
+
}
|
|
11832
|
+
|
|
11833
|
+
// src/eval/graders/match.ts
|
|
11834
|
+
function matchValue(operator, actual) {
|
|
11835
|
+
if (typeof operator === "string") {
|
|
11836
|
+
return String(actual) === operator;
|
|
11837
|
+
}
|
|
11838
|
+
if ("equals" in operator) {
|
|
11839
|
+
return actual === operator.equals;
|
|
11840
|
+
}
|
|
11841
|
+
if ("contains" in operator) {
|
|
11842
|
+
return String(actual).toLowerCase().includes(operator.contains.toLowerCase());
|
|
11843
|
+
}
|
|
11844
|
+
if ("not_contains" in operator) {
|
|
11845
|
+
return !String(actual).toLowerCase().includes(operator.not_contains.toLowerCase());
|
|
11846
|
+
}
|
|
11847
|
+
if ("matches" in operator) {
|
|
11848
|
+
return new RegExp(operator.matches, "i").test(String(actual));
|
|
11849
|
+
}
|
|
11850
|
+
if ("in" in operator) {
|
|
11851
|
+
return operator.in.includes(actual);
|
|
11852
|
+
}
|
|
11853
|
+
if ("exists" in operator) {
|
|
11854
|
+
return operator.exists ? actual !== undefined && actual !== null : actual === undefined || actual === null;
|
|
11855
|
+
}
|
|
11856
|
+
if ("gte" in operator) {
|
|
11857
|
+
return Number(actual) >= operator.gte;
|
|
11858
|
+
}
|
|
11859
|
+
if ("lte" in operator) {
|
|
11860
|
+
return Number(actual) <= operator.lte;
|
|
11861
|
+
}
|
|
11862
|
+
return false;
|
|
11863
|
+
}
|
|
11864
|
+
function operatorToString(operator) {
|
|
11865
|
+
if (typeof operator === "string")
|
|
11866
|
+
return `equals "${operator}"`;
|
|
11867
|
+
if ("equals" in operator)
|
|
11868
|
+
return `equals ${JSON.stringify(operator.equals)}`;
|
|
11869
|
+
if ("contains" in operator)
|
|
11870
|
+
return `contains "${operator.contains}"`;
|
|
11871
|
+
if ("not_contains" in operator)
|
|
11872
|
+
return `not_contains "${operator.not_contains}"`;
|
|
11873
|
+
if ("matches" in operator)
|
|
11874
|
+
return `matches /${operator.matches}/`;
|
|
11875
|
+
if ("in" in operator)
|
|
11876
|
+
return `in [${operator.in.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
11877
|
+
if ("exists" in operator)
|
|
11878
|
+
return operator.exists ? "exists" : "does not exist";
|
|
11879
|
+
if ("gte" in operator)
|
|
11880
|
+
return `>= ${operator.gte}`;
|
|
11881
|
+
if ("lte" in operator)
|
|
11882
|
+
return `<= ${operator.lte}`;
|
|
11883
|
+
return JSON.stringify(operator);
|
|
11884
|
+
}
|
|
11885
|
+
|
|
11886
|
+
// src/eval/graders/tools.ts
|
|
11887
|
+
function gradeTools(toolCalls, assertions) {
|
|
11888
|
+
return assertions.map((assertion) => {
|
|
11889
|
+
if ("called" in assertion && !("not_called" in assertion) && !("call_order" in assertion)) {
|
|
11890
|
+
const matches = toolCalls.filter((tc) => tc.name === assertion.called);
|
|
11891
|
+
const wasCalled = matches.length > 0;
|
|
11892
|
+
if (!wasCalled) {
|
|
11893
|
+
return {
|
|
11894
|
+
assertion: `tool called: ${assertion.called}`,
|
|
11895
|
+
pass: false,
|
|
11896
|
+
expected: `${assertion.called} was called`,
|
|
11897
|
+
actual: `Not called. Tools called: [${toolCalls.map((tc) => tc.name).join(", ") || "none"}]`
|
|
11898
|
+
};
|
|
11899
|
+
}
|
|
11900
|
+
if (assertion.params) {
|
|
11901
|
+
const paramResults = [];
|
|
11902
|
+
for (const [key, operator] of Object.entries(assertion.params)) {
|
|
11903
|
+
const anyMatch = matches.some((tc) => matchValue(operator, tc.input[key]));
|
|
11904
|
+
paramResults.push({
|
|
11905
|
+
key,
|
|
11906
|
+
pass: anyMatch,
|
|
11907
|
+
detail: anyMatch ? `matched` : `expected ${key} ${operatorToString(operator)}, got ${JSON.stringify(matches.map((tc) => tc.input[key]))}`
|
|
11908
|
+
});
|
|
11909
|
+
}
|
|
11910
|
+
const allParamsPass = paramResults.every((p) => p.pass);
|
|
11911
|
+
const failedParams = paramResults.filter((p) => !p.pass);
|
|
11912
|
+
return {
|
|
11913
|
+
assertion: `tool called: ${assertion.called} with params`,
|
|
11914
|
+
pass: allParamsPass,
|
|
11915
|
+
expected: `${assertion.called} called with ${Object.entries(assertion.params).map(([k, v]) => `${k} ${operatorToString(v)}`).join(", ")}`,
|
|
11916
|
+
actual: allParamsPass ? `Matched` : failedParams.map((p) => p.detail).join("; ")
|
|
11917
|
+
};
|
|
11918
|
+
}
|
|
11919
|
+
return {
|
|
11920
|
+
assertion: `tool called: ${assertion.called}`,
|
|
11921
|
+
pass: true,
|
|
11922
|
+
expected: `${assertion.called} was called`,
|
|
11923
|
+
actual: `Called ${matches.length} time(s)`
|
|
11924
|
+
};
|
|
11925
|
+
}
|
|
11926
|
+
if ("not_called" in assertion) {
|
|
11927
|
+
const wasCalled = toolCalls.some((tc) => tc.name === assertion.not_called);
|
|
11928
|
+
return {
|
|
11929
|
+
assertion: `tool not_called: ${assertion.not_called}`,
|
|
11930
|
+
pass: !wasCalled,
|
|
11931
|
+
expected: `${assertion.not_called} was NOT called`,
|
|
11932
|
+
actual: wasCalled ? `Was called` : `Not called`
|
|
11933
|
+
};
|
|
11934
|
+
}
|
|
11935
|
+
if ("call_order" in assertion) {
|
|
11936
|
+
const calledNames = toolCalls.map((tc) => tc.name);
|
|
11937
|
+
const expectedOrder = assertion.call_order;
|
|
11938
|
+
let cursor = 0;
|
|
11939
|
+
for (const name of calledNames) {
|
|
11940
|
+
if (cursor < expectedOrder.length && name === expectedOrder[cursor]) {
|
|
11941
|
+
cursor++;
|
|
11942
|
+
}
|
|
12489
11943
|
}
|
|
11944
|
+
const inOrder = cursor === expectedOrder.length;
|
|
11945
|
+
return {
|
|
11946
|
+
assertion: `call_order: [${expectedOrder.join(" → ")}]`,
|
|
11947
|
+
pass: inOrder,
|
|
11948
|
+
expected: `Tools called in order: [${expectedOrder.join(" → ")}]`,
|
|
11949
|
+
actual: `Actual order: [${calledNames.join(" → ") || "none"}]`
|
|
11950
|
+
};
|
|
11951
|
+
}
|
|
11952
|
+
return {
|
|
11953
|
+
assertion: "unknown tool assertion",
|
|
11954
|
+
pass: false,
|
|
11955
|
+
expected: "known assertion type",
|
|
11956
|
+
actual: `Unknown: ${JSON.stringify(assertion)}`
|
|
12490
11957
|
};
|
|
11958
|
+
});
|
|
11959
|
+
}
|
|
11960
|
+
|
|
11961
|
+
// src/eval/graders/state.ts
|
|
11962
|
+
function parseStatePath(path42) {
|
|
11963
|
+
const dot = path42.indexOf(".");
|
|
11964
|
+
if (dot === -1) {
|
|
11965
|
+
throw new Error(`Invalid state path "${path42}" — expected "type.field" format`);
|
|
12491
11966
|
}
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
11967
|
+
const prefix = path42.slice(0, dot);
|
|
11968
|
+
const field = path42.slice(dot + 1);
|
|
11969
|
+
switch (prefix) {
|
|
11970
|
+
case "bot":
|
|
11971
|
+
return { type: "bot", stateName: "botState", stateId: (ctx) => ctx.botId, field };
|
|
11972
|
+
case "user":
|
|
11973
|
+
return { type: "user", stateName: "userState", stateId: (ctx) => ctx.userId, field };
|
|
11974
|
+
case "conversation":
|
|
11975
|
+
return { type: "conversation", stateName: "conversationState", stateId: (ctx) => ctx.conversationId, field };
|
|
11976
|
+
default:
|
|
11977
|
+
throw new Error(`Unknown state type "${prefix}" in path "${path42}" — expected bot, user, or conversation`);
|
|
11978
|
+
}
|
|
11979
|
+
}
|
|
11980
|
+
async function fetchState(client, type, id, name) {
|
|
11981
|
+
try {
|
|
11982
|
+
const result = await client.getState({ type, id, name });
|
|
11983
|
+
const payload = result.state?.payload;
|
|
11984
|
+
return payload?.value ?? payload ?? null;
|
|
11985
|
+
} catch (err) {
|
|
11986
|
+
if (err?.code === 404 || err?.message?.includes("404") || err?.message?.includes("doesn't exist")) {
|
|
11987
|
+
return null;
|
|
12497
11988
|
}
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
11989
|
+
throw err;
|
|
11990
|
+
}
|
|
11991
|
+
}
|
|
11992
|
+
async function fetchStateWithRetry(client, type, id, name, field, maxRetries = 3, retryDelay = 1000) {
|
|
11993
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
11994
|
+
const payload = await fetchState(client, type, id, name);
|
|
11995
|
+
if (payload && payload[field] !== undefined) {
|
|
11996
|
+
return payload;
|
|
12503
11997
|
}
|
|
12504
|
-
|
|
12505
|
-
|
|
11998
|
+
if (attempt < maxRetries) {
|
|
11999
|
+
await new Promise((resolve4) => setTimeout(resolve4, retryDelay));
|
|
12000
|
+
}
|
|
12001
|
+
}
|
|
12002
|
+
return null;
|
|
12003
|
+
}
|
|
12004
|
+
async function snapshotState(client, assertions, ctx) {
|
|
12005
|
+
const snapshots = new Map;
|
|
12006
|
+
for (const assertion of assertions) {
|
|
12007
|
+
if (assertion.changed === undefined)
|
|
12008
|
+
continue;
|
|
12009
|
+
const parsed = parseStatePath(assertion.path);
|
|
12010
|
+
if (parsed.type === "conversation")
|
|
12011
|
+
continue;
|
|
12012
|
+
const payload = await fetchState(client, parsed.type, parsed.stateId(ctx), parsed.stateName);
|
|
12013
|
+
snapshots.set(assertion.path, payload ? payload[parsed.field] : undefined);
|
|
12014
|
+
}
|
|
12015
|
+
return snapshots;
|
|
12016
|
+
}
|
|
12017
|
+
async function gradeState(client, assertions, ctx, preSnapshots) {
|
|
12018
|
+
const results = [];
|
|
12019
|
+
for (const assertion of assertions) {
|
|
12020
|
+
const parsed = parseStatePath(assertion.path);
|
|
12021
|
+
const stateId = parsed.stateId(ctx);
|
|
12022
|
+
let payload;
|
|
12506
12023
|
try {
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
ADK_BOT_ID: botId,
|
|
12515
|
-
ADK_WORKSPACE_ID: workspaceId || "",
|
|
12516
|
-
ADK_IS_PROD: this.prod ? "true" : "false",
|
|
12517
|
-
BP_DISABLE_WORKER_MODE: "true",
|
|
12518
|
-
...options.env,
|
|
12519
|
-
ADK_SCRIPT_MODE: "true",
|
|
12520
|
-
ADK_SCRIPT_PATH: absoluteScriptPath,
|
|
12521
|
-
ADK_TOKEN: this.credentials.token,
|
|
12522
|
-
ADK_API_URL: this.credentials.apiUrl,
|
|
12523
|
-
...configuration && { ADK_CONFIGURATION: JSON.stringify(configuration) }
|
|
12524
|
-
};
|
|
12525
|
-
return new Promise((resolve3, reject) => {
|
|
12526
|
-
const child = spawn("bun", args, {
|
|
12527
|
-
cwd: botPath,
|
|
12528
|
-
env,
|
|
12529
|
-
stdio: options.inheritStdio !== false ? "inherit" : "pipe"
|
|
12024
|
+
payload = await fetchStateWithRetry(client, parsed.type, stateId, parsed.stateName, parsed.field);
|
|
12025
|
+
} catch (err) {
|
|
12026
|
+
results.push({
|
|
12027
|
+
assertion: `state: ${assertion.path}`,
|
|
12028
|
+
pass: false,
|
|
12029
|
+
expected: `Fetch state for ${assertion.path}`,
|
|
12030
|
+
actual: `Error fetching state: ${err.message}`
|
|
12530
12031
|
});
|
|
12531
|
-
|
|
12532
|
-
|
|
12032
|
+
continue;
|
|
12033
|
+
}
|
|
12034
|
+
const actualValue = payload ? payload[parsed.field] : undefined;
|
|
12035
|
+
if (assertion.equals !== undefined) {
|
|
12036
|
+
const pass = deepEqual(actualValue, assertion.equals);
|
|
12037
|
+
results.push({
|
|
12038
|
+
assertion: `state: ${assertion.path} equals`,
|
|
12039
|
+
pass,
|
|
12040
|
+
expected: JSON.stringify(assertion.equals),
|
|
12041
|
+
actual: JSON.stringify(actualValue)
|
|
12533
12042
|
});
|
|
12534
|
-
|
|
12535
|
-
|
|
12043
|
+
}
|
|
12044
|
+
if (assertion.changed !== undefined) {
|
|
12045
|
+
const preValue = preSnapshots?.get(assertion.path);
|
|
12046
|
+
const didChange = !deepEqual(actualValue, preValue);
|
|
12047
|
+
const pass = assertion.changed ? didChange : !didChange;
|
|
12048
|
+
results.push({
|
|
12049
|
+
assertion: `state: ${assertion.path} ${assertion.changed ? "changed" : "unchanged"}`,
|
|
12050
|
+
pass,
|
|
12051
|
+
expected: assertion.changed ? `Value changed from ${JSON.stringify(preValue)}` : `Value unchanged from ${JSON.stringify(preValue)}`,
|
|
12052
|
+
actual: didChange ? `Changed to ${JSON.stringify(actualValue)}` : `Unchanged: ${JSON.stringify(actualValue)}`
|
|
12536
12053
|
});
|
|
12054
|
+
}
|
|
12055
|
+
}
|
|
12056
|
+
return results;
|
|
12057
|
+
}
|
|
12058
|
+
function deepEqual(a, b) {
|
|
12059
|
+
if (a === b)
|
|
12060
|
+
return true;
|
|
12061
|
+
if (a === null || b === null)
|
|
12062
|
+
return false;
|
|
12063
|
+
if (typeof a !== typeof b)
|
|
12064
|
+
return false;
|
|
12065
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
12066
|
+
if (a.length !== b.length)
|
|
12067
|
+
return false;
|
|
12068
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
12069
|
+
}
|
|
12070
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
12071
|
+
const keysA = Object.keys(a);
|
|
12072
|
+
const keysB = Object.keys(b);
|
|
12073
|
+
if (keysA.length !== keysB.length)
|
|
12074
|
+
return false;
|
|
12075
|
+
return keysA.every((k) => deepEqual(a[k], b[k]));
|
|
12076
|
+
}
|
|
12077
|
+
return false;
|
|
12078
|
+
}
|
|
12079
|
+
|
|
12080
|
+
// src/eval/graders/tables.ts
|
|
12081
|
+
function buildFilter(conditions) {
|
|
12082
|
+
const filter = {};
|
|
12083
|
+
for (const [column, op] of Object.entries(conditions)) {
|
|
12084
|
+
if (typeof op === "string") {
|
|
12085
|
+
filter[column] = op;
|
|
12086
|
+
} else if ("equals" in op) {
|
|
12087
|
+
filter[column] = op.equals;
|
|
12088
|
+
} else if ("contains" in op) {
|
|
12089
|
+
filter[column] = { $regex: op.contains };
|
|
12090
|
+
} else if ("matches" in op) {
|
|
12091
|
+
filter[column] = { $regex: op.matches };
|
|
12092
|
+
} else if ("gte" in op) {
|
|
12093
|
+
filter[column] = { $gte: op.gte };
|
|
12094
|
+
} else if ("lte" in op) {
|
|
12095
|
+
filter[column] = { $lte: op.lte };
|
|
12096
|
+
}
|
|
12097
|
+
}
|
|
12098
|
+
return filter;
|
|
12099
|
+
}
|
|
12100
|
+
async function gradeRowExists(client, assertion) {
|
|
12101
|
+
const conditionDesc = Object.entries(assertion.row_exists).map(([k, v]) => `${k} ${operatorToString(v)}`).join(", ");
|
|
12102
|
+
try {
|
|
12103
|
+
const filter = buildFilter(assertion.row_exists);
|
|
12104
|
+
const result = await client.findTableRows({
|
|
12105
|
+
table: assertion.table,
|
|
12106
|
+
filter,
|
|
12107
|
+
limit: 10
|
|
12537
12108
|
});
|
|
12109
|
+
const rows = result.rows || [];
|
|
12110
|
+
const matchingRows = rows.filter((row) => {
|
|
12111
|
+
for (const [column, op] of Object.entries(assertion.row_exists)) {
|
|
12112
|
+
if (!matchValue(op, row[column]))
|
|
12113
|
+
return false;
|
|
12114
|
+
}
|
|
12115
|
+
return true;
|
|
12116
|
+
});
|
|
12117
|
+
const pass = matchingRows.length > 0;
|
|
12118
|
+
return {
|
|
12119
|
+
assertion: `table: ${assertion.table} row_exists`,
|
|
12120
|
+
pass,
|
|
12121
|
+
expected: `Row exists in ${assertion.table} where ${conditionDesc}`,
|
|
12122
|
+
actual: pass ? `Found ${matchingRows.length} matching row(s)` : `No matching rows found`
|
|
12123
|
+
};
|
|
12124
|
+
} catch (err) {
|
|
12125
|
+
return {
|
|
12126
|
+
assertion: `table: ${assertion.table} row_exists`,
|
|
12127
|
+
pass: false,
|
|
12128
|
+
expected: `Row exists in ${assertion.table} where ${conditionDesc}`,
|
|
12129
|
+
actual: `Error querying table: ${err.message}`
|
|
12130
|
+
};
|
|
12538
12131
|
}
|
|
12539
12132
|
}
|
|
12540
|
-
async function
|
|
12541
|
-
const
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12133
|
+
async function gradeRowCount(client, assertion) {
|
|
12134
|
+
const countDesc = operatorToString(assertion.row_count);
|
|
12135
|
+
const whereDesc = assertion.where ? ` where ${Object.entries(assertion.where).map(([k, v]) => `${k} ${operatorToString(v)}`).join(", ")}` : "";
|
|
12136
|
+
try {
|
|
12137
|
+
const filter = assertion.where ? buildFilter(assertion.where) : {};
|
|
12138
|
+
const result = await client.findTableRows({
|
|
12139
|
+
table: assertion.table,
|
|
12140
|
+
filter,
|
|
12141
|
+
limit: 1000
|
|
12142
|
+
});
|
|
12143
|
+
const rows = result.rows || [];
|
|
12144
|
+
let count;
|
|
12145
|
+
if (assertion.where) {
|
|
12146
|
+
count = rows.filter((row) => {
|
|
12147
|
+
for (const [column, op] of Object.entries(assertion.where)) {
|
|
12148
|
+
if (!matchValue(op, row[column]))
|
|
12149
|
+
return false;
|
|
12150
|
+
}
|
|
12151
|
+
return true;
|
|
12152
|
+
}).length;
|
|
12153
|
+
} else {
|
|
12154
|
+
count = rows.length;
|
|
12155
|
+
}
|
|
12156
|
+
const pass = matchValue(assertion.row_count, count);
|
|
12157
|
+
return {
|
|
12158
|
+
assertion: `table: ${assertion.table} row_count`,
|
|
12159
|
+
pass,
|
|
12160
|
+
expected: `Row count in ${assertion.table}${whereDesc} ${countDesc}`,
|
|
12161
|
+
actual: `Count: ${count}`
|
|
12162
|
+
};
|
|
12163
|
+
} catch (err) {
|
|
12164
|
+
return {
|
|
12165
|
+
assertion: `table: ${assertion.table} row_count`,
|
|
12166
|
+
pass: false,
|
|
12167
|
+
expected: `Row count in ${assertion.table}${whereDesc} ${countDesc}`,
|
|
12168
|
+
actual: `Error querying table: ${err.message}`
|
|
12169
|
+
};
|
|
12170
|
+
}
|
|
12552
12171
|
}
|
|
12553
|
-
async function
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12172
|
+
async function gradeTables(client, assertions) {
|
|
12173
|
+
const results = [];
|
|
12174
|
+
for (const assertion of assertions) {
|
|
12175
|
+
if ("row_exists" in assertion) {
|
|
12176
|
+
results.push(await gradeRowExists(client, assertion));
|
|
12177
|
+
} else if ("row_count" in assertion) {
|
|
12178
|
+
results.push(await gradeRowCount(client, assertion));
|
|
12560
12179
|
}
|
|
12561
|
-
projectPath = detected;
|
|
12562
12180
|
}
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12181
|
+
return results;
|
|
12182
|
+
}
|
|
12183
|
+
|
|
12184
|
+
// src/eval/graders/workflow.ts
|
|
12185
|
+
function gradeWorkflows(spans, assertions) {
|
|
12186
|
+
const results = [];
|
|
12187
|
+
for (const assertion of assertions) {
|
|
12188
|
+
const workflowSpans = spans.filter((span) => {
|
|
12189
|
+
const wfName = span.attrs?.["workflow.name"] || span.attrs?.["workflowName"];
|
|
12190
|
+
return wfName === assertion.name;
|
|
12191
|
+
});
|
|
12192
|
+
if (assertion.entered !== undefined) {
|
|
12193
|
+
const wasEntered = workflowSpans.length > 0;
|
|
12194
|
+
const pass = assertion.entered ? wasEntered : !wasEntered;
|
|
12195
|
+
results.push({
|
|
12196
|
+
assertion: `workflow: ${assertion.name} ${assertion.entered ? "entered" : "not entered"}`,
|
|
12197
|
+
pass,
|
|
12198
|
+
expected: assertion.entered ? `Workflow "${assertion.name}" was entered` : `Workflow "${assertion.name}" was not entered`,
|
|
12199
|
+
actual: wasEntered ? `Found ${workflowSpans.length} workflow span(s)` : `No workflow spans found`
|
|
12200
|
+
});
|
|
12569
12201
|
}
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12202
|
+
if (assertion.completed !== undefined) {
|
|
12203
|
+
const completedSpans = workflowSpans.filter((span) => {
|
|
12204
|
+
const status = span.attrs?.["workflow.status"];
|
|
12205
|
+
return status === "completed" || span.t === "end" && span.name?.includes("workflow");
|
|
12206
|
+
});
|
|
12207
|
+
const didComplete = completedSpans.length > 0;
|
|
12208
|
+
const pass = assertion.completed ? didComplete : !didComplete;
|
|
12209
|
+
results.push({
|
|
12210
|
+
assertion: `workflow: ${assertion.name} ${assertion.completed ? "completed" : "not completed"}`,
|
|
12211
|
+
pass,
|
|
12212
|
+
expected: assertion.completed ? `Workflow "${assertion.name}" completed` : `Workflow "${assertion.name}" did not complete`,
|
|
12213
|
+
actual: didComplete ? `Completed` : `Not completed`
|
|
12214
|
+
});
|
|
12215
|
+
}
|
|
12216
|
+
}
|
|
12217
|
+
return results;
|
|
12218
|
+
}
|
|
12219
|
+
|
|
12220
|
+
// src/eval/graders/outcome.ts
|
|
12221
|
+
async function snapshotOutcomeState(client, evalDef, ctx) {
|
|
12222
|
+
if (!evalDef.outcome?.state) {
|
|
12223
|
+
return new Map;
|
|
12224
|
+
}
|
|
12225
|
+
return snapshotState(client, evalDef.outcome.state, ctx);
|
|
12226
|
+
}
|
|
12227
|
+
async function gradeOutcome(client, evalDef, ctx, traceSpans, preSnapshots) {
|
|
12228
|
+
const outcome = evalDef.outcome;
|
|
12229
|
+
if (!outcome)
|
|
12230
|
+
return [];
|
|
12231
|
+
const results = [];
|
|
12232
|
+
if (outcome.state && outcome.state.length > 0) {
|
|
12233
|
+
const stateResults = await gradeState(client, outcome.state, ctx, preSnapshots);
|
|
12234
|
+
results.push(...stateResults);
|
|
12235
|
+
}
|
|
12236
|
+
if (outcome.tables && outcome.tables.length > 0) {
|
|
12237
|
+
const tableResults = await gradeTables(client, outcome.tables);
|
|
12238
|
+
results.push(...tableResults);
|
|
12239
|
+
}
|
|
12240
|
+
if (outcome.workflow && outcome.workflow.length > 0) {
|
|
12241
|
+
const workflowResults = gradeWorkflows(traceSpans, outcome.workflow);
|
|
12242
|
+
results.push(...workflowResults);
|
|
12243
|
+
}
|
|
12244
|
+
return results;
|
|
12245
|
+
}
|
|
12246
|
+
|
|
12247
|
+
// src/eval/runner.ts
|
|
12248
|
+
import { randomUUID } from "crypto";
|
|
12249
|
+
async function runEval(evalDef, connection, options = {}) {
|
|
12250
|
+
const devServerUrl = options.devServerUrl || "http://localhost:3001";
|
|
12251
|
+
const start = Date.now();
|
|
12252
|
+
const turns = [];
|
|
12253
|
+
let outcomeAssertions = [];
|
|
12254
|
+
try {
|
|
12255
|
+
const session = new ChatSession(connection.webhookId);
|
|
12256
|
+
await session.connect();
|
|
12257
|
+
let bpClient = null;
|
|
12258
|
+
const getBpClient = () => {
|
|
12259
|
+
if (!bpClient) {
|
|
12260
|
+
bpClient = new BpClient2({
|
|
12261
|
+
token: connection.token,
|
|
12262
|
+
botId: connection.botId,
|
|
12263
|
+
apiUrl: connection.apiUrl
|
|
12264
|
+
});
|
|
12265
|
+
}
|
|
12266
|
+
return bpClient;
|
|
12267
|
+
};
|
|
12268
|
+
let preSnapshots = new Map;
|
|
12269
|
+
if (evalDef.outcome?.state) {
|
|
12270
|
+
const ctx = {
|
|
12271
|
+
botId: connection.botId,
|
|
12272
|
+
userId: session.userId,
|
|
12273
|
+
conversationId: ""
|
|
12274
|
+
};
|
|
12275
|
+
preSnapshots = await snapshotOutcomeState(getBpClient(), evalDef, ctx);
|
|
12276
|
+
}
|
|
12277
|
+
let previousToolCallCount = 0;
|
|
12278
|
+
let lastConversationId = "";
|
|
12279
|
+
let allTraceSpans = [];
|
|
12280
|
+
for (let i = 0;i < evalDef.conversation.length; i++) {
|
|
12281
|
+
const turn = evalDef.conversation[i];
|
|
12282
|
+
const turnStart = Date.now();
|
|
12283
|
+
const result = await session.sendMessage(turn.user, {
|
|
12284
|
+
timeout: 30000,
|
|
12285
|
+
idleTimeout: 3000
|
|
12286
|
+
});
|
|
12287
|
+
const botDuration = Date.now() - turnStart;
|
|
12288
|
+
lastConversationId = result.conversationId;
|
|
12289
|
+
const botResponse = result.responses.map((r) => r.text).join(`
|
|
12290
|
+
`);
|
|
12291
|
+
const evalStart = Date.now();
|
|
12292
|
+
let assertions = [];
|
|
12293
|
+
if (turn.assert?.response) {
|
|
12294
|
+
assertions = await gradeResponse(botResponse, turn.assert.response, {
|
|
12295
|
+
userMessage: turn.user
|
|
12296
|
+
});
|
|
12297
|
+
}
|
|
12298
|
+
if (turn.assert?.tools) {
|
|
12299
|
+
try {
|
|
12300
|
+
const expectNewCalls = turn.assert.tools.some((a) => ("called" in a) || ("call_order" in a));
|
|
12301
|
+
const traceData = await getTraceData(result.conversationId, devServerUrl, {
|
|
12302
|
+
previousToolCallCount,
|
|
12303
|
+
expectNewCalls
|
|
12304
|
+
});
|
|
12305
|
+
previousToolCallCount = traceData.totalToolCallCount;
|
|
12306
|
+
allTraceSpans = traceData.raw;
|
|
12307
|
+
const toolResults = gradeTools(traceData.toolCalls, turn.assert.tools);
|
|
12308
|
+
assertions = [...assertions, ...toolResults];
|
|
12309
|
+
} catch (err) {
|
|
12310
|
+
for (const toolAssert of turn.assert.tools) {
|
|
12311
|
+
const name = "called" in toolAssert ? toolAssert.called : ("not_called" in toolAssert) ? toolAssert.not_called : "call_order";
|
|
12312
|
+
assertions.push({
|
|
12313
|
+
assertion: `tool: ${name}`,
|
|
12314
|
+
pass: false,
|
|
12315
|
+
expected: `Tool assertion on ${name}`,
|
|
12316
|
+
actual: `Failed to fetch traces: ${err.message}`
|
|
12317
|
+
});
|
|
12318
|
+
}
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
if (turn.assert?.state) {
|
|
12322
|
+
try {
|
|
12323
|
+
const ctx = {
|
|
12324
|
+
botId: connection.botId,
|
|
12325
|
+
userId: session.userId,
|
|
12326
|
+
conversationId: result.conversationId
|
|
12327
|
+
};
|
|
12328
|
+
const stateResults = await gradeState(getBpClient(), turn.assert.state, ctx, preSnapshots);
|
|
12329
|
+
assertions.push(...stateResults);
|
|
12330
|
+
} catch (err) {
|
|
12331
|
+
assertions.push({
|
|
12332
|
+
assertion: "state",
|
|
12333
|
+
pass: false,
|
|
12334
|
+
expected: "State assertions executed",
|
|
12335
|
+
actual: `Error: ${err.message}`
|
|
12336
|
+
});
|
|
12337
|
+
}
|
|
12338
|
+
}
|
|
12339
|
+
if (turn.assert?.tables) {
|
|
12340
|
+
try {
|
|
12341
|
+
const tableResults = await gradeTables(getBpClient(), turn.assert.tables);
|
|
12342
|
+
assertions.push(...tableResults);
|
|
12343
|
+
} catch (err) {
|
|
12344
|
+
assertions.push({
|
|
12345
|
+
assertion: "tables",
|
|
12346
|
+
pass: false,
|
|
12347
|
+
expected: "Table assertions executed",
|
|
12348
|
+
actual: `Error: ${err.message}`
|
|
12349
|
+
});
|
|
12350
|
+
}
|
|
12351
|
+
}
|
|
12352
|
+
if (turn.assert?.workflow) {
|
|
12353
|
+
if (allTraceSpans.length === 0) {
|
|
12354
|
+
try {
|
|
12355
|
+
const traceData = await getTraceData(result.conversationId, devServerUrl);
|
|
12356
|
+
allTraceSpans = traceData.raw;
|
|
12357
|
+
} catch {}
|
|
12358
|
+
}
|
|
12359
|
+
const workflowResults = gradeWorkflows(allTraceSpans, turn.assert.workflow);
|
|
12360
|
+
assertions.push(...workflowResults);
|
|
12361
|
+
}
|
|
12362
|
+
const turnPass = assertions.every((a) => a.pass);
|
|
12363
|
+
const evalDuration = Date.now() - evalStart;
|
|
12364
|
+
turns.push({
|
|
12365
|
+
turnNumber: i + 1,
|
|
12366
|
+
userMessage: turn.user,
|
|
12367
|
+
botResponse,
|
|
12368
|
+
assertions,
|
|
12369
|
+
pass: turnPass,
|
|
12370
|
+
botDuration,
|
|
12371
|
+
evalDuration
|
|
12372
|
+
});
|
|
12373
|
+
}
|
|
12374
|
+
if (evalDef.outcome) {
|
|
12375
|
+
if (allTraceSpans.length === 0 && lastConversationId && evalDef.outcome.workflow) {
|
|
12376
|
+
try {
|
|
12377
|
+
const traceData = await getTraceData(lastConversationId, devServerUrl);
|
|
12378
|
+
allTraceSpans = traceData.raw;
|
|
12379
|
+
} catch {}
|
|
12380
|
+
}
|
|
12381
|
+
const ctx = {
|
|
12382
|
+
botId: connection.botId,
|
|
12383
|
+
userId: session.userId,
|
|
12384
|
+
conversationId: lastConversationId
|
|
12385
|
+
};
|
|
12386
|
+
try {
|
|
12387
|
+
outcomeAssertions = await gradeOutcome(getBpClient(), evalDef, ctx, allTraceSpans, preSnapshots);
|
|
12388
|
+
} catch (err) {
|
|
12389
|
+
outcomeAssertions = [
|
|
12390
|
+
{
|
|
12391
|
+
assertion: "outcome",
|
|
12392
|
+
pass: false,
|
|
12393
|
+
expected: "Outcome assertions executed",
|
|
12394
|
+
actual: `Error: ${err.message}`
|
|
12395
|
+
}
|
|
12396
|
+
];
|
|
12397
|
+
}
|
|
12398
|
+
}
|
|
12399
|
+
const turnsPass = turns.every((t) => t.pass);
|
|
12400
|
+
const outcomePass = outcomeAssertions.every((a) => a.pass);
|
|
12401
|
+
return {
|
|
12402
|
+
name: evalDef.name,
|
|
12403
|
+
description: evalDef.description,
|
|
12404
|
+
type: evalDef.type,
|
|
12405
|
+
tags: evalDef.tags,
|
|
12406
|
+
turns,
|
|
12407
|
+
outcomeAssertions,
|
|
12408
|
+
pass: turnsPass && outcomePass,
|
|
12409
|
+
duration: Date.now() - start
|
|
12410
|
+
};
|
|
12411
|
+
} catch (err) {
|
|
12412
|
+
return {
|
|
12413
|
+
name: evalDef.name,
|
|
12414
|
+
description: evalDef.description,
|
|
12415
|
+
type: evalDef.type,
|
|
12416
|
+
tags: evalDef.tags,
|
|
12417
|
+
turns,
|
|
12418
|
+
outcomeAssertions,
|
|
12419
|
+
pass: false,
|
|
12420
|
+
duration: Date.now() - start,
|
|
12421
|
+
error: err.message
|
|
12573
12422
|
};
|
|
12574
12423
|
}
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12424
|
+
}
|
|
12425
|
+
async function runEvalSuite(config, filter) {
|
|
12426
|
+
const start = Date.now();
|
|
12427
|
+
const runId = randomUUID().replace(/-/g, "").slice(0, 26);
|
|
12428
|
+
initLLMJudge({
|
|
12429
|
+
token: config.credentials.token,
|
|
12430
|
+
apiUrl: config.credentials.apiUrl,
|
|
12431
|
+
botId: config.credentials.botId
|
|
12580
12432
|
});
|
|
12581
|
-
|
|
12433
|
+
const evalsDir = `${config.agentPath}/evals`;
|
|
12434
|
+
const allEvals = await loadEvalsFromDir(evalsDir);
|
|
12435
|
+
const evals = filterEvals(allEvals, filter);
|
|
12436
|
+
if (evals.length === 0) {
|
|
12437
|
+
return {
|
|
12438
|
+
id: runId,
|
|
12439
|
+
timestamp: new Date().toISOString(),
|
|
12440
|
+
evals: [],
|
|
12441
|
+
passed: 0,
|
|
12442
|
+
failed: 0,
|
|
12443
|
+
total: 0,
|
|
12444
|
+
duration: 0,
|
|
12445
|
+
filter
|
|
12446
|
+
};
|
|
12447
|
+
}
|
|
12448
|
+
let webhookId = config.credentials.webhookId;
|
|
12449
|
+
if (!webhookId) {
|
|
12450
|
+
webhookId = await discoverWebhookId(config.credentials.botId, config.credentials.token, config.credentials.apiUrl);
|
|
12451
|
+
}
|
|
12452
|
+
const connection = {
|
|
12453
|
+
webhookId,
|
|
12454
|
+
botId: config.credentials.botId,
|
|
12455
|
+
token: config.credentials.token,
|
|
12456
|
+
apiUrl: config.credentials.apiUrl
|
|
12457
|
+
};
|
|
12458
|
+
const devServerUrl = config.devServerUrl || "http://localhost:3001";
|
|
12459
|
+
const reports = [];
|
|
12460
|
+
config.onProgress?.({ type: "suite_start", totalEvals: evals.length });
|
|
12461
|
+
for (let i = 0;i < evals.length; i++) {
|
|
12462
|
+
const evalDef = evals[i];
|
|
12463
|
+
config.onProgress?.({ type: "eval_start", evalName: evalDef.name, index: i });
|
|
12464
|
+
const report = await runEval(evalDef, connection, { devServerUrl });
|
|
12465
|
+
reports.push(report);
|
|
12466
|
+
config.onProgress?.({ type: "eval_complete", evalName: evalDef.name, index: i, report });
|
|
12467
|
+
}
|
|
12468
|
+
const runReport = {
|
|
12469
|
+
id: runId,
|
|
12470
|
+
timestamp: new Date().toISOString(),
|
|
12471
|
+
evals: reports,
|
|
12472
|
+
passed: reports.filter((r) => r.pass).length,
|
|
12473
|
+
failed: reports.filter((r) => !r.pass).length,
|
|
12474
|
+
total: reports.length,
|
|
12475
|
+
duration: Date.now() - start,
|
|
12476
|
+
filter
|
|
12477
|
+
};
|
|
12478
|
+
config.onProgress?.({ type: "suite_complete", report: runReport });
|
|
12479
|
+
return runReport;
|
|
12480
|
+
}
|
|
12481
|
+
// src/eval/store.ts
|
|
12482
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
12483
|
+
import { join as join10 } from "path";
|
|
12484
|
+
function getRunsDir(agentPath) {
|
|
12485
|
+
const dir = join10(agentPath, ".adk", "evals", "runs");
|
|
12486
|
+
if (!existsSync12(dir)) {
|
|
12487
|
+
mkdirSync2(dir, { recursive: true });
|
|
12488
|
+
}
|
|
12489
|
+
return dir;
|
|
12490
|
+
}
|
|
12491
|
+
function saveRunResult(agentPath, report) {
|
|
12492
|
+
const dir = getRunsDir(agentPath);
|
|
12493
|
+
const filename = `${report.timestamp.replace(/[:.]/g, "-")}-${report.id}.json`;
|
|
12494
|
+
const filepath = join10(dir, filename);
|
|
12495
|
+
writeFileSync2(filepath, JSON.stringify(report, null, 2));
|
|
12496
|
+
return filepath;
|
|
12497
|
+
}
|
|
12498
|
+
function loadRunResult(agentPath, runId) {
|
|
12499
|
+
const dir = getRunsDir(agentPath);
|
|
12500
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
12501
|
+
for (const file of files) {
|
|
12502
|
+
if (file.includes(runId)) {
|
|
12503
|
+
const filepath = join10(dir, file);
|
|
12504
|
+
return JSON.parse(readFileSync3(filepath, "utf-8"));
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
return null;
|
|
12508
|
+
}
|
|
12509
|
+
function listRunResults(agentPath, limit = 50) {
|
|
12510
|
+
const dir = getRunsDir(agentPath);
|
|
12511
|
+
if (!existsSync12(dir))
|
|
12512
|
+
return [];
|
|
12513
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, limit);
|
|
12514
|
+
return files.map((file) => {
|
|
12515
|
+
const filepath = join10(dir, file);
|
|
12516
|
+
return JSON.parse(readFileSync3(filepath, "utf-8"));
|
|
12517
|
+
});
|
|
12518
|
+
}
|
|
12519
|
+
function getLatestRun(agentPath) {
|
|
12520
|
+
const runs = listRunResults(agentPath, 1);
|
|
12521
|
+
return runs[0] || null;
|
|
12582
12522
|
}
|
|
12583
12523
|
export {
|
|
12584
12524
|
workspaceCache,
|
|
12585
12525
|
stringifyWithOrder,
|
|
12586
12526
|
setupTestRuntime,
|
|
12527
|
+
saveRunResult,
|
|
12587
12528
|
runScript,
|
|
12529
|
+
runEvalSuite,
|
|
12530
|
+
runEval,
|
|
12588
12531
|
orderKeys,
|
|
12532
|
+
loadRunResult,
|
|
12533
|
+
loadEvalsFromDir,
|
|
12534
|
+
loadEvalFile,
|
|
12535
|
+
loadEvalByName,
|
|
12536
|
+
listRunResults,
|
|
12589
12537
|
integrationKeyOrder,
|
|
12590
12538
|
initAssets,
|
|
12591
12539
|
getRelativeTime,
|
|
12540
|
+
getLatestRun,
|
|
12541
|
+
getInnerTypeName,
|
|
12592
12542
|
generateIntegrationTypes,
|
|
12593
12543
|
generateClientWrapper,
|
|
12594
12544
|
generateBotProject,
|
|
12595
12545
|
generateAssetsTypes,
|
|
12596
12546
|
generateAssetsRuntime,
|
|
12547
|
+
filterEvals,
|
|
12597
12548
|
dependenciesKeyOrder,
|
|
12549
|
+
defineEval,
|
|
12550
|
+
coerceConfigValue,
|
|
12598
12551
|
bpCliImporter,
|
|
12599
12552
|
auth,
|
|
12600
12553
|
agentInfoKeyOrder,
|
|
@@ -12642,4 +12595,4 @@ export {
|
|
|
12642
12595
|
AgentProject
|
|
12643
12596
|
};
|
|
12644
12597
|
|
|
12645
|
-
//# debugId=
|
|
12598
|
+
//# debugId=D97F506CD3BBA90E64756E2164756E21
|