@amitdeshmukh/ax-crew 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +104 -0
- package/dist/agents/ace.d.ts +134 -0
- package/dist/agents/ace.js +477 -0
- package/dist/agents/agentConfig.d.ts +1 -0
- package/dist/agents/agentConfig.js +1 -0
- package/dist/agents/index.d.ts +83 -1
- package/dist/agents/index.js +359 -4
- package/dist/index.d.ts +3 -3
- package/dist/types.d.ts +39 -1
- package/examples/README.md +46 -8
- package/examples/ace-customer-support.ts +480 -0
- package/examples/ace-flight-finder.ts +329 -0
- package/examples/telemetry-demo.ts +0 -1
- package/package.json +1 -1
- package/plan.md +255 -0
- package/playbooks/customer-support.json +32 -0
- package/playbooks/flight-assistant.json +23 -0
- package/src/agents/ace.ts +594 -0
- package/src/agents/agentConfig.ts +1 -0
- package/src/agents/index.ts +408 -6
- package/src/index.ts +14 -2
- package/src/types.ts +52 -1
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACE (Agentic Context Engineering) integration for AxCrew
|
|
3
|
+
*
|
|
4
|
+
* This module provides helpers to build and manage AxACE optimizers for agents,
|
|
5
|
+
* enabling offline compilation and online learning from feedback.
|
|
6
|
+
*
|
|
7
|
+
* Reference: https://axllm.dev/ace/
|
|
8
|
+
*/
|
|
9
|
+
import { AxACE, ai as buildAI, AxSignature, AxGen } from "@ax-llm/ax";
|
|
10
|
+
/**
|
|
11
|
+
* Create an empty playbook structure
|
|
12
|
+
*/
|
|
13
|
+
export const createEmptyPlaybook = () => {
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
return {
|
|
16
|
+
version: 1,
|
|
17
|
+
sections: {},
|
|
18
|
+
stats: {
|
|
19
|
+
bulletCount: 0,
|
|
20
|
+
helpfulCount: 0,
|
|
21
|
+
harmfulCount: 0,
|
|
22
|
+
tokenEstimate: 0,
|
|
23
|
+
},
|
|
24
|
+
updatedAt: now,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Render a playbook into markdown instruction block for injection into prompts.
|
|
29
|
+
* Mirrors the AxACE renderPlaybook function.
|
|
30
|
+
*/
|
|
31
|
+
export const renderPlaybook = (playbook) => {
|
|
32
|
+
if (!playbook)
|
|
33
|
+
return '';
|
|
34
|
+
const sectionsObj = playbook.sections || {};
|
|
35
|
+
const header = playbook.description
|
|
36
|
+
? `## Context Playbook\n${playbook.description.trim()}\n`
|
|
37
|
+
: '## Context Playbook\n';
|
|
38
|
+
const sectionEntries = Object.entries(sectionsObj);
|
|
39
|
+
if (sectionEntries.length === 0)
|
|
40
|
+
return '';
|
|
41
|
+
const sections = sectionEntries
|
|
42
|
+
.map(([sectionName, bullets]) => {
|
|
43
|
+
const body = bullets
|
|
44
|
+
.map((bullet) => `- [${bullet.id}] ${bullet.content}`)
|
|
45
|
+
.join('\n');
|
|
46
|
+
return body
|
|
47
|
+
? `### ${sectionName}\n${body}`
|
|
48
|
+
: `### ${sectionName}\n_(empty)_`;
|
|
49
|
+
})
|
|
50
|
+
.join('\n\n');
|
|
51
|
+
return `${header}\n${sections}`.trim();
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Check if running in Node.js environment (for file operations)
|
|
55
|
+
*/
|
|
56
|
+
const isNodeLike = () => {
|
|
57
|
+
try {
|
|
58
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Read JSON file (Node.js only)
|
|
66
|
+
*/
|
|
67
|
+
const readFileJSON = async (path) => {
|
|
68
|
+
if (!isNodeLike())
|
|
69
|
+
return undefined;
|
|
70
|
+
try {
|
|
71
|
+
const { readFile } = await import("fs/promises");
|
|
72
|
+
const buf = await readFile(path, "utf-8");
|
|
73
|
+
return JSON.parse(buf);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Write JSON file (Node.js only)
|
|
81
|
+
*/
|
|
82
|
+
const writeFileJSON = async (path, data) => {
|
|
83
|
+
if (!isNodeLike())
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
87
|
+
const { dirname } = await import("path");
|
|
88
|
+
await mkdir(dirname(path), { recursive: true });
|
|
89
|
+
await writeFile(path, JSON.stringify(data ?? {}, null, 2), "utf-8");
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Swallow persistence errors by default
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Resolve environment variable
|
|
97
|
+
*/
|
|
98
|
+
const resolveEnv = (name) => {
|
|
99
|
+
try {
|
|
100
|
+
if (typeof process !== "undefined" && process?.env) {
|
|
101
|
+
return process.env[name];
|
|
102
|
+
}
|
|
103
|
+
return globalThis?.[name];
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Build teacher AI instance from config, falling back to student AI
|
|
111
|
+
*/
|
|
112
|
+
const buildTeacherAI = (teacherCfg, fallback) => {
|
|
113
|
+
if (!teacherCfg)
|
|
114
|
+
return fallback;
|
|
115
|
+
const { provider, providerKeyName, apiURL, ai: aiConfig, providerArgs } = teacherCfg;
|
|
116
|
+
if (!provider || !providerKeyName || !aiConfig)
|
|
117
|
+
return fallback;
|
|
118
|
+
const apiKey = resolveEnv(providerKeyName) || "";
|
|
119
|
+
if (!apiKey)
|
|
120
|
+
return fallback;
|
|
121
|
+
const args = {
|
|
122
|
+
name: provider,
|
|
123
|
+
apiKey,
|
|
124
|
+
config: aiConfig,
|
|
125
|
+
options: {}
|
|
126
|
+
};
|
|
127
|
+
if (apiURL)
|
|
128
|
+
args.apiURL = apiURL;
|
|
129
|
+
if (providerArgs && typeof providerArgs === "object") {
|
|
130
|
+
Object.assign(args, providerArgs);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return buildAI(args);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Build an AxACE optimizer for an agent
|
|
141
|
+
*
|
|
142
|
+
* @param studentAI - The agent's AI instance (used as student)
|
|
143
|
+
* @param cfg - ACE configuration
|
|
144
|
+
* @returns Configured AxACE optimizer
|
|
145
|
+
*/
|
|
146
|
+
export const buildACEOptimizer = (studentAI, cfg) => {
|
|
147
|
+
const teacherAI = buildTeacherAI(cfg.teacher, studentAI);
|
|
148
|
+
// Build optimizer options, only include initialPlaybook if it has the right structure
|
|
149
|
+
const optimizerOptions = {
|
|
150
|
+
maxEpochs: cfg.options?.maxEpochs,
|
|
151
|
+
allowDynamicSections: cfg.options?.allowDynamicSections,
|
|
152
|
+
};
|
|
153
|
+
// Only pass initialPlaybook if it looks like a valid playbook structure
|
|
154
|
+
if (cfg.persistence?.initialPlaybook &&
|
|
155
|
+
typeof cfg.persistence.initialPlaybook === 'object' &&
|
|
156
|
+
'sections' in cfg.persistence.initialPlaybook) {
|
|
157
|
+
optimizerOptions.initialPlaybook = cfg.persistence.initialPlaybook;
|
|
158
|
+
}
|
|
159
|
+
return new AxACE({
|
|
160
|
+
studentAI,
|
|
161
|
+
teacherAI,
|
|
162
|
+
verbose: !!cfg.options?.maxEpochs
|
|
163
|
+
}, optimizerOptions);
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Load initial playbook from file, callback, or inline config
|
|
167
|
+
*
|
|
168
|
+
* @param cfg - Persistence configuration
|
|
169
|
+
* @returns Loaded playbook or undefined
|
|
170
|
+
*/
|
|
171
|
+
export const loadInitialPlaybook = async (cfg) => {
|
|
172
|
+
if (!cfg)
|
|
173
|
+
return undefined;
|
|
174
|
+
// Try callback first
|
|
175
|
+
if (typeof cfg.onLoad === "function") {
|
|
176
|
+
try {
|
|
177
|
+
return await cfg.onLoad();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Fall through to other methods
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Try inline playbook
|
|
184
|
+
if (cfg.initialPlaybook) {
|
|
185
|
+
return cfg.initialPlaybook;
|
|
186
|
+
}
|
|
187
|
+
// Try file path
|
|
188
|
+
if (cfg.playbookPath) {
|
|
189
|
+
return await readFileJSON(cfg.playbookPath);
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Persist playbook to file or via callback
|
|
195
|
+
*
|
|
196
|
+
* @param pb - Playbook to persist
|
|
197
|
+
* @param cfg - Persistence configuration
|
|
198
|
+
*/
|
|
199
|
+
export const persistPlaybook = async (pb, cfg) => {
|
|
200
|
+
if (!cfg || !pb)
|
|
201
|
+
return;
|
|
202
|
+
// Call persist callback if provided
|
|
203
|
+
if (typeof cfg.onPersist === "function") {
|
|
204
|
+
try {
|
|
205
|
+
await cfg.onPersist(pb);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Ignore callback errors
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Write to file if auto-persist enabled
|
|
212
|
+
if (cfg.autoPersist && cfg.playbookPath) {
|
|
213
|
+
await writeFileJSON(cfg.playbookPath, pb);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Resolve metric function from registry or create equality-based metric
|
|
218
|
+
*
|
|
219
|
+
* @param cfg - Metric configuration
|
|
220
|
+
* @param registry - Function registry to search
|
|
221
|
+
* @returns Metric function or undefined
|
|
222
|
+
*/
|
|
223
|
+
export const resolveMetric = (cfg, registry) => {
|
|
224
|
+
if (!cfg)
|
|
225
|
+
return undefined;
|
|
226
|
+
const { metricFnName, primaryOutputField } = cfg;
|
|
227
|
+
// Try to find a function by name in the registry
|
|
228
|
+
if (metricFnName) {
|
|
229
|
+
const candidate = registry[metricFnName];
|
|
230
|
+
if (typeof candidate === "function") {
|
|
231
|
+
return candidate;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Create simple equality-based metric if primary output field specified
|
|
235
|
+
if (primaryOutputField) {
|
|
236
|
+
const field = primaryOutputField;
|
|
237
|
+
return ({ prediction, example }) => {
|
|
238
|
+
try {
|
|
239
|
+
return prediction?.[field] === example?.[field] ? 1 : 0;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Run offline ACE compilation
|
|
250
|
+
*
|
|
251
|
+
* @param args - Compilation arguments
|
|
252
|
+
* @returns Compilation result with optimized program
|
|
253
|
+
*/
|
|
254
|
+
export const runOfflineCompile = async (args) => {
|
|
255
|
+
const { program, optimizer, metric, examples = [], persistence } = args;
|
|
256
|
+
if (!optimizer || !metric || examples.length === 0) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
// Run compilation
|
|
261
|
+
const result = await optimizer.compile(program, examples, metric);
|
|
262
|
+
// Extract and persist playbook
|
|
263
|
+
const playbook = result?.artifact?.playbook;
|
|
264
|
+
if (playbook && persistence) {
|
|
265
|
+
await persistPlaybook(playbook, persistence);
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.warn("ACE offline compile failed:", error);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Apply online update with feedback
|
|
276
|
+
*
|
|
277
|
+
* @param args - Update arguments
|
|
278
|
+
* @returns Curator delta (operations applied)
|
|
279
|
+
*/
|
|
280
|
+
export const runOnlineUpdate = async (args) => {
|
|
281
|
+
const { optimizer, example, prediction, feedback, persistence, debug } = args;
|
|
282
|
+
if (!optimizer)
|
|
283
|
+
return null;
|
|
284
|
+
try {
|
|
285
|
+
// Apply online update (per ACE API: example, prediction, feedback)
|
|
286
|
+
const curatorDelta = await optimizer.applyOnlineUpdate({
|
|
287
|
+
example,
|
|
288
|
+
prediction,
|
|
289
|
+
feedback
|
|
290
|
+
});
|
|
291
|
+
// Access the optimizer's private playbook property
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
|
+
const playbook = optimizer.playbook;
|
|
294
|
+
// Persist updated playbook if we have one and persistence is configured
|
|
295
|
+
if (playbook && persistence?.autoPersist) {
|
|
296
|
+
await persistPlaybook(playbook, persistence);
|
|
297
|
+
}
|
|
298
|
+
return curatorDelta;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
// AxACE's reflector sometimes returns bulletTags in non-array format, causing iteration errors.
|
|
302
|
+
// This is a known issue - we fall back to direct playbook updates via addFeedbackToPlaybook.
|
|
303
|
+
if (debug) {
|
|
304
|
+
console.warn("[ACE Debug] AxACE applyOnlineUpdate failed (falling back to direct update):", error);
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Generate a unique bullet ID (mirrors AxACE's generateBulletId)
|
|
311
|
+
*/
|
|
312
|
+
const generateBulletId = (section) => {
|
|
313
|
+
const normalized = section
|
|
314
|
+
.toLowerCase()
|
|
315
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
316
|
+
.replace(/^-+|-+$/g, '')
|
|
317
|
+
.slice(0, 6);
|
|
318
|
+
const randomHex = Math.random().toString(16).slice(2, 10);
|
|
319
|
+
return `${normalized || 'ctx'}-${randomHex}`;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Recompute playbook stats after modifications
|
|
323
|
+
*/
|
|
324
|
+
const recomputePlaybookStats = (playbook) => {
|
|
325
|
+
let bulletCount = 0;
|
|
326
|
+
let helpfulCount = 0;
|
|
327
|
+
let harmfulCount = 0;
|
|
328
|
+
let tokenEstimate = 0;
|
|
329
|
+
const sections = playbook.sections || {};
|
|
330
|
+
for (const bullets of Object.values(sections)) {
|
|
331
|
+
for (const bullet of bullets) {
|
|
332
|
+
bulletCount += 1;
|
|
333
|
+
helpfulCount += bullet.helpfulCount;
|
|
334
|
+
harmfulCount += bullet.harmfulCount;
|
|
335
|
+
tokenEstimate += Math.ceil(bullet.content.length / 4);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
playbook.stats = { bulletCount, helpfulCount, harmfulCount, tokenEstimate };
|
|
339
|
+
playbook.updatedAt = new Date().toISOString();
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Apply curator operations to playbook (mirrors AxACE's applyCuratorOperations)
|
|
343
|
+
*/
|
|
344
|
+
const applyCuratorOperations = (playbook, operations) => {
|
|
345
|
+
// Ensure playbook has sections initialized
|
|
346
|
+
if (!playbook.sections) {
|
|
347
|
+
playbook.sections = {};
|
|
348
|
+
}
|
|
349
|
+
const now = new Date().toISOString();
|
|
350
|
+
for (const op of operations) {
|
|
351
|
+
if (!op.section)
|
|
352
|
+
continue;
|
|
353
|
+
// Initialize section if needed
|
|
354
|
+
if (!playbook.sections[op.section]) {
|
|
355
|
+
playbook.sections[op.section] = [];
|
|
356
|
+
}
|
|
357
|
+
const section = playbook.sections[op.section];
|
|
358
|
+
switch (op.type) {
|
|
359
|
+
case 'ADD': {
|
|
360
|
+
if (!op.content?.trim())
|
|
361
|
+
continue;
|
|
362
|
+
// Check for duplicates
|
|
363
|
+
const isDuplicate = section.some(b => b.content.toLowerCase() === op.content.toLowerCase());
|
|
364
|
+
if (isDuplicate)
|
|
365
|
+
continue;
|
|
366
|
+
const bullet = {
|
|
367
|
+
id: op.bulletId || generateBulletId(op.section),
|
|
368
|
+
section: op.section,
|
|
369
|
+
content: op.content.trim(),
|
|
370
|
+
helpfulCount: 1,
|
|
371
|
+
harmfulCount: 0,
|
|
372
|
+
createdAt: now,
|
|
373
|
+
updatedAt: now,
|
|
374
|
+
};
|
|
375
|
+
section.push(bullet);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case 'UPDATE': {
|
|
379
|
+
if (!op.bulletId)
|
|
380
|
+
continue;
|
|
381
|
+
const bullet = section.find(b => b.id === op.bulletId);
|
|
382
|
+
if (bullet && op.content) {
|
|
383
|
+
bullet.content = op.content.trim();
|
|
384
|
+
bullet.updatedAt = now;
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'REMOVE': {
|
|
389
|
+
if (!op.bulletId)
|
|
390
|
+
continue;
|
|
391
|
+
const idx = section.findIndex(b => b.id === op.bulletId);
|
|
392
|
+
if (idx >= 0)
|
|
393
|
+
section.splice(idx, 1);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
recomputePlaybookStats(playbook);
|
|
399
|
+
};
|
|
400
|
+
// Cached feedback analyzer program (created lazily)
|
|
401
|
+
let feedbackAnalyzerProgram = null;
|
|
402
|
+
/**
|
|
403
|
+
* Get or create the feedback analyzer program.
|
|
404
|
+
* Uses AxGen with a proper signature, just like AxACE's reflector/curator.
|
|
405
|
+
*
|
|
406
|
+
* Uses `class` type for section to get type-safe enums and better token efficiency.
|
|
407
|
+
* See: https://axllm.dev/signatures/
|
|
408
|
+
*/
|
|
409
|
+
const getOrCreateFeedbackAnalyzer = () => {
|
|
410
|
+
if (!feedbackAnalyzerProgram) {
|
|
411
|
+
const signature = new AxSignature(`feedback:string "User feedback to analyze"
|
|
412
|
+
->
|
|
413
|
+
section:class "Guidelines, Response Strategies, Common Pitfalls, Root Cause Notes" "Playbook section category",
|
|
414
|
+
content:string "The specific instruction to add to the playbook - keep all concrete details"`);
|
|
415
|
+
signature.setDescription(`Convert user feedback into a playbook instruction. Keep ALL specific details from the feedback (times, names, numbers, constraints).`);
|
|
416
|
+
feedbackAnalyzerProgram = new AxGen(signature);
|
|
417
|
+
}
|
|
418
|
+
return feedbackAnalyzerProgram;
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Use LLM to analyze feedback and generate playbook operations.
|
|
422
|
+
*
|
|
423
|
+
* This leverages AxGen with a proper signature (like AxACE's reflector/curator)
|
|
424
|
+
* to properly categorize feedback and extract actionable insights.
|
|
425
|
+
*
|
|
426
|
+
* IMPORTANT: The prompt explicitly tells the LLM to preserve specificity.
|
|
427
|
+
*
|
|
428
|
+
* @param ai - The AI instance to use for analysis
|
|
429
|
+
* @param feedback - User feedback string
|
|
430
|
+
* @param debug - Whether to log debug info
|
|
431
|
+
* @returns Promise of curator operations
|
|
432
|
+
*/
|
|
433
|
+
export const analyzeAndCategorizeFeedback = async (ai, feedback, debug = false) => {
|
|
434
|
+
if (!feedback?.trim())
|
|
435
|
+
return [];
|
|
436
|
+
try {
|
|
437
|
+
const analyzer = getOrCreateFeedbackAnalyzer();
|
|
438
|
+
const result = await analyzer.forward(ai, {
|
|
439
|
+
feedback: feedback.trim(),
|
|
440
|
+
});
|
|
441
|
+
if (debug) {
|
|
442
|
+
console.log('[ACE Debug] Feedback analysis result:', result);
|
|
443
|
+
}
|
|
444
|
+
// Section is guaranteed to be valid by the class type constraint
|
|
445
|
+
const section = result.section || 'Guidelines';
|
|
446
|
+
// Use the LLM's content, but fall back to raw feedback if empty
|
|
447
|
+
const content = result.content?.trim() || feedback.trim();
|
|
448
|
+
return [{ type: 'ADD', section, content }];
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (debug) {
|
|
452
|
+
console.warn('[ACE Debug] Feedback analysis failed, using raw feedback:', error);
|
|
453
|
+
}
|
|
454
|
+
// Fallback: use the raw feedback as-is
|
|
455
|
+
return [{ type: 'ADD', section: 'Guidelines', content: feedback.trim() }];
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
/**
|
|
459
|
+
* Add feedback to playbook using LLM analysis.
|
|
460
|
+
*
|
|
461
|
+
* Uses the AI to properly understand and categorize the feedback,
|
|
462
|
+
* then applies it as a curator operation.
|
|
463
|
+
*
|
|
464
|
+
* @param playbook - The playbook to update (mutated in place)
|
|
465
|
+
* @param feedback - User feedback string to add
|
|
466
|
+
* @param ai - AI instance for smart categorization
|
|
467
|
+
* @param debug - Whether to log debug info
|
|
468
|
+
*/
|
|
469
|
+
export const addFeedbackToPlaybook = async (playbook, feedback, ai, debug = false) => {
|
|
470
|
+
if (!playbook || !feedback?.trim())
|
|
471
|
+
return;
|
|
472
|
+
// Use LLM to categorize feedback while preserving specificity
|
|
473
|
+
const operations = await analyzeAndCategorizeFeedback(ai, feedback, debug);
|
|
474
|
+
if (operations.length > 0) {
|
|
475
|
+
applyCuratorOperations(playbook, operations);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
@@ -179,6 +179,7 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
|
|
|
179
179
|
subAgentNames: agentConfigData.agents || [],
|
|
180
180
|
examples: agentConfigData.examples || [],
|
|
181
181
|
tracker: costTracker,
|
|
182
|
+
debug: agentConfigData.options?.debug ?? agentConfigData.debug ?? false,
|
|
182
183
|
};
|
|
183
184
|
}
|
|
184
185
|
catch (error) {
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { AxAgent, AxAI } from "@ax-llm/ax";
|
|
2
2
|
import type { AxSignature, AxAgentic, AxFunction, AxProgramForwardOptions, AxProgramStreamingForwardOptions, AxGenStreamingOut } from "@ax-llm/ax";
|
|
3
|
-
import type { StateInstance, FunctionRegistryType, UsageCost, AxCrewConfig, AxCrewOptions, MCPTransportConfig } from "../types.js";
|
|
3
|
+
import type { StateInstance, FunctionRegistryType, UsageCost, AxCrewConfig, AxCrewOptions, MCPTransportConfig, ACEConfig } from "../types.js";
|
|
4
4
|
declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
5
5
|
state: StateInstance;
|
|
6
6
|
axai: any;
|
|
7
7
|
private agentName;
|
|
8
8
|
private costTracker?;
|
|
9
9
|
private lastRecordedCostUSD;
|
|
10
|
+
private debugEnabled;
|
|
11
|
+
private aceConfig?;
|
|
12
|
+
private aceOptimizer?;
|
|
13
|
+
private acePlaybook?;
|
|
14
|
+
private aceBaseInstruction?;
|
|
10
15
|
private isAxAIService;
|
|
11
16
|
private isAxAIInstance;
|
|
12
17
|
constructor(ai: AxAI, options: Readonly<{
|
|
@@ -18,6 +23,7 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
18
23
|
functions?: (AxFunction | (() => AxFunction))[] | undefined;
|
|
19
24
|
examples?: Array<Record<string, any>> | undefined;
|
|
20
25
|
mcpServers?: Record<string, MCPTransportConfig> | undefined;
|
|
26
|
+
debug?: boolean;
|
|
21
27
|
}>, state: StateInstance);
|
|
22
28
|
forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
23
29
|
forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
@@ -37,6 +43,51 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
37
43
|
* Call this to start fresh measurement windows for the agent.
|
|
38
44
|
*/
|
|
39
45
|
resetMetrics(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Initialize ACE (Agentic Context Engineering) for this agent.
|
|
48
|
+
* Builds the optimizer and loads any initial playbook from persistence.
|
|
49
|
+
* Sets up the optimizer for online-only mode if compileOnStart is false.
|
|
50
|
+
*/
|
|
51
|
+
initACE(ace?: ACEConfig): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Run offline ACE compilation with examples and metric.
|
|
54
|
+
* Compiles the playbook based on training examples.
|
|
55
|
+
*/
|
|
56
|
+
optimizeOffline(params?: {
|
|
57
|
+
metric?: any;
|
|
58
|
+
examples?: any[];
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Apply online ACE update based on user feedback.
|
|
62
|
+
*
|
|
63
|
+
* For preference-based feedback (e.g., "only show flights between 9am-12pm"),
|
|
64
|
+
* we use our own feedback analyzer that preserves specificity.
|
|
65
|
+
*
|
|
66
|
+
* Note: AxACE's built-in curator is designed for error correction (severity mismatches)
|
|
67
|
+
* and tends to over-abstract preference feedback into generic guidelines.
|
|
68
|
+
* We bypass it and directly use our feedback analyzer for better results.
|
|
69
|
+
*/
|
|
70
|
+
applyOnlineUpdate(params: {
|
|
71
|
+
example: any;
|
|
72
|
+
prediction: any;
|
|
73
|
+
feedback?: string;
|
|
74
|
+
}): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Get the current ACE playbook for this agent.
|
|
77
|
+
*/
|
|
78
|
+
getPlaybook(): any | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Apply an ACE playbook to this agent.
|
|
81
|
+
* Stores the playbook for use in next forward() call.
|
|
82
|
+
* Note: Playbook is composed into instruction BEFORE each forward(), mirroring AxACE.compile behavior.
|
|
83
|
+
*/
|
|
84
|
+
applyPlaybook(pb: any): void;
|
|
85
|
+
/**
|
|
86
|
+
* Compose instruction with current playbook and set on agent.
|
|
87
|
+
* This mirrors what AxACE does internally before each forward() during compile().
|
|
88
|
+
* Should be called BEFORE forward() to ensure playbook is in the prompt.
|
|
89
|
+
*/
|
|
90
|
+
private composeInstructionWithPlaybook;
|
|
40
91
|
}
|
|
41
92
|
/**
|
|
42
93
|
* AxCrew orchestrates a set of Ax agents that share state,
|
|
@@ -64,6 +115,7 @@ declare class AxCrew {
|
|
|
64
115
|
crewId: string;
|
|
65
116
|
agents: Map<string, StatefulAxAgent> | null;
|
|
66
117
|
state: StateInstance;
|
|
118
|
+
private executionHistory;
|
|
67
119
|
/**
|
|
68
120
|
* Creates an instance of AxCrew.
|
|
69
121
|
* @param {AxCrewConfig} crewConfig - JSON object with crew configuration.
|
|
@@ -93,6 +145,36 @@ declare class AxCrew {
|
|
|
93
145
|
*/
|
|
94
146
|
addAgentsToCrew(agentNames: string[]): Promise<Map<string, StatefulAxAgent> | null>;
|
|
95
147
|
addAllAgents(): Promise<Map<string, StatefulAxAgent> | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Track agent execution for ACE feedback routing
|
|
150
|
+
*/
|
|
151
|
+
trackAgentExecution(taskId: string, agentName: string, input: any): void;
|
|
152
|
+
/**
|
|
153
|
+
* Record agent result for ACE feedback routing
|
|
154
|
+
*/
|
|
155
|
+
recordAgentResult(taskId: string, agentName: string, result: any): void;
|
|
156
|
+
/**
|
|
157
|
+
* Get agent involvement for a task (used for ACE feedback routing)
|
|
158
|
+
*/
|
|
159
|
+
getTaskAgentInvolvement(taskId: string): {
|
|
160
|
+
rootAgent: string;
|
|
161
|
+
involvedAgents: string[];
|
|
162
|
+
taskInput: any;
|
|
163
|
+
agentResults: Map<string, any>;
|
|
164
|
+
duration?: number;
|
|
165
|
+
} | null;
|
|
166
|
+
/**
|
|
167
|
+
* Apply feedback to agents involved in a task for ACE online learning
|
|
168
|
+
*/
|
|
169
|
+
applyTaskFeedback(params: {
|
|
170
|
+
taskId: string;
|
|
171
|
+
feedback: string;
|
|
172
|
+
strategy?: 'all' | 'primary' | 'weighted';
|
|
173
|
+
}): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Clean up old execution history (call periodically to prevent memory leaks)
|
|
176
|
+
*/
|
|
177
|
+
cleanupOldExecutions(maxAgeMs?: number): void;
|
|
96
178
|
/**
|
|
97
179
|
* Cleans up the crew by dereferencing agents and resetting the state.
|
|
98
180
|
*/
|