@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.
@@ -0,0 +1,480 @@
1
+ /**
2
+ * ACE Feedback Loop Demo - Customer Support Agent
3
+ *
4
+ * This example demonstrates ACE learning from human feedback.
5
+ *
6
+ * ═══════════════════════════════════════════════════════════════════
7
+ * THE CHALLENGE: 3 Strict Policies vs Real-World Edge Cases
8
+ * ═══════════════════════════════════════════════════════════════════
9
+ *
10
+ * The agent knows only 3 simple rules:
11
+ * 1. Returns within 30 days only
12
+ * 2. Refunds to original payment method only
13
+ * 3. Sale items: no returns, no refunds
14
+ *
15
+ * But every scenario presented violates at least one policy!
16
+ * Watch how the agent handles these impossible situations,
17
+ * then teach it when exceptions should apply.
18
+ *
19
+ * ACE learns your feedback and applies it to similar future cases.
20
+ *
21
+ * ═══════════════════════════════════════════════════════════════════
22
+ *
23
+ * Usage: npx tsx examples/ace-customer-support.ts
24
+ */
25
+
26
+ import { AxCrew } from "../dist/index.js";
27
+ import { AxCrewFunctions } from "../dist/functions/index.js";
28
+ import type { AxCrewConfig } from "../dist/types.js";
29
+ import type { Provider } from "../dist/types.js";
30
+ import * as readline from "readline";
31
+ import dotenv from "dotenv";
32
+ dotenv.config();
33
+
34
+ // --- 1. Configuration ---
35
+
36
+ const STANDARD_POLICIES = [
37
+ "Returns: Only accepted within 30 days of delivery",
38
+ "Refunds: Must go to the original payment method used",
39
+ "Sale Items: Final sale - absolutely no returns or refunds"
40
+ ];
41
+
42
+ const crewConfig: AxCrewConfig = {
43
+ crew: [
44
+ {
45
+ name: "SupportAgent",
46
+ description: `You are a customer support agent for TechMart, an e-commerce electronics retailer. Strictly follow the company policies. No exceptions.`,
47
+ signature: "ticket:string, standardPolicies:string[] -> politeSupportResponse:string, decision:string, policyApplied:string",
48
+ provider: "google-gemini" as Provider,
49
+ providerKeyName: "GEMINI_API_KEY",
50
+ ai: {
51
+ model: "gemini-flash-latest",
52
+ temperature: 0.7,
53
+ },
54
+ options: {
55
+ debug: false,
56
+ stream: false
57
+ },
58
+ // Enable ACE for learning from edge case feedback
59
+ ace: {
60
+ teacher: {
61
+ provider: "google-gemini" as Provider,
62
+ providerKeyName: "GEMINI_API_KEY",
63
+ ai: { model: "gemini-flash-latest" }
64
+ },
65
+ options: {
66
+ maxEpochs: 1,
67
+ allowDynamicSections: true
68
+ },
69
+ persistence: {
70
+ playbookPath: "playbooks/customer-support.json",
71
+ autoPersist: true
72
+ },
73
+ metric: { primaryOutputField: "politeSupportResponse" },
74
+ compileOnStart: false,
75
+ }
76
+ }
77
+ ]
78
+ };
79
+
80
+ // --- 2. Sample Support Tickets ---
81
+
82
+ // Each ticket violates at least one of the 3 policies - perfect for teaching exceptions
83
+ const SAMPLE_TICKETS = [
84
+ {
85
+ id: "T-001",
86
+ category: "🚫 Violates: 30-day return policy",
87
+ ticket: `Customer: Sarah Mitchell (8-year Gold Member, $15,000+ purchases)
88
+ Purchase: Laptop, 45 days ago (unopened, still sealed)
89
+ Request: Full refund
90
+ Reason: Was hospitalized for 3 weeks after purchase`
91
+ },
92
+ {
93
+ id: "T-002",
94
+ category: "🚫 Violates: Original payment method",
95
+ ticket: `Customer: James Chen
96
+ Purchase: Headphones, returned within 30 days
97
+ Request: Refund to a DIFFERENT card (not the original)
98
+ Reason: Original card was stolen. Has police report.`
99
+ },
100
+ {
101
+ id: "T-003",
102
+ category: "🚫 Violates: No refunds on sale items",
103
+ ticket: `Customer: Maria Garcia (12-year Platinum Member)
104
+ Purchase: TV during Black Friday sale
105
+ Request: Full refund
106
+ Reason: Dead on arrival - won't power on. Factory defect.`
107
+ },
108
+ {
109
+ id: "T-004",
110
+ category: "🚫 Violates: 30-day return policy",
111
+ ticket: `Customer: David Park
112
+ Purchase: Monitor, 35 days ago
113
+ Request: Replacement
114
+ Reason: Arrived damaged (shipping damage). Photos confirm.
115
+ Couldn't report earlier due to family funeral.`
116
+ },
117
+ {
118
+ id: "T-005",
119
+ category: "🚫 Violates: No refunds on sale items",
120
+ ticket: `Customer: Emily Watson
121
+ Purchase: Earbuds (bought on clearance sale)
122
+ Request: Store credit
123
+ Reason: Allergic reaction to ear tips (medical issue).
124
+ Item unused, original packaging.`
125
+ },
126
+ {
127
+ id: "T-006",
128
+ category: "🚫 Violates: 30-day return policy",
129
+ ticket: `Customer: Robert Taylor (First-time customer)
130
+ Purchase: Gaming keyboard, 60 days ago
131
+ Request: Refund or replacement
132
+ Reason: Keys started failing after 2 weeks of normal use.
133
+ Product defect, documented with video.`
134
+ },
135
+ {
136
+ id: "T-007",
137
+ category: "🚫 Violates: Original payment method",
138
+ ticket: `Customer: Lisa Anderson (5-year Gold Member)
139
+ Purchase: Smart watch, wants refund
140
+ Request: Refund to PayPal (paid with credit card)
141
+ Reason: Bank closed her credit card account.
142
+ Card no longer exists.`
143
+ }
144
+ ];
145
+
146
+ // --- 3. CLI Helper Functions ---
147
+
148
+ const rl = readline.createInterface({
149
+ input: process.stdin,
150
+ output: process.stdout
151
+ });
152
+
153
+ const prompt = (question: string): Promise<string> => {
154
+ return new Promise((resolve) => {
155
+ rl.question(question, (answer) => {
156
+ resolve(answer.trim());
157
+ });
158
+ });
159
+ };
160
+
161
+ const displayPlaybook = (playbook: any, agentName: string) => {
162
+ console.log(`\n📘 ACE Playbook for ${agentName}:`);
163
+ console.log("─".repeat(70));
164
+
165
+ if (!playbook) {
166
+ console.log(" (No learned exceptions yet - standard policies apply)");
167
+ console.log("─".repeat(70));
168
+ return;
169
+ }
170
+
171
+ if (playbook.sections) {
172
+ for (const [sectionName, bullets] of Object.entries(playbook.sections)) {
173
+ console.log(`\n 📂 ${sectionName}:`);
174
+ if (Array.isArray(bullets)) {
175
+ bullets.forEach((bullet: any, i: number) => {
176
+ const content = typeof bullet === 'string' ? bullet : bullet.content || JSON.stringify(bullet);
177
+ // Wrap long lines
178
+ const wrapped = content.length > 60
179
+ ? content.match(/.{1,60}(\s|$)/g)?.join('\n ') || content
180
+ : content;
181
+ console.log(` ${i + 1}. ${wrapped}`);
182
+ });
183
+ }
184
+ }
185
+ } else {
186
+ console.log(" " + JSON.stringify(playbook, null, 2).replace(/\n/g, "\n "));
187
+ }
188
+
189
+ if (playbook.updatedAt) {
190
+ console.log(`\n 🕐 Last updated: ${new Date(playbook.updatedAt).toLocaleString()}`);
191
+ }
192
+
193
+ console.log("─".repeat(70));
194
+ };
195
+
196
+ const displayTicket = (ticket: typeof SAMPLE_TICKETS[0]) => {
197
+ console.log(`\n┌${"─".repeat(68)}┐`);
198
+ console.log(`│ 🎫 Ticket ${ticket.id.padEnd(56)}│`);
199
+ console.log(`│ Category: ${ticket.category.padEnd(55)}│`);
200
+ console.log(`├${"─".repeat(68)}┤`);
201
+
202
+ // Split ticket into lines that fit
203
+ const lines = ticket.ticket.split('\n');
204
+ lines.forEach(line => {
205
+ const trimmed = line.trim();
206
+ if (trimmed.length <= 66) {
207
+ console.log(`│ ${trimmed.padEnd(66)}│`);
208
+ } else {
209
+ // Wrap long lines
210
+ const words = trimmed.split(' ');
211
+ let currentLine = '';
212
+ words.forEach(word => {
213
+ if ((currentLine + ' ' + word).trim().length <= 66) {
214
+ currentLine = (currentLine + ' ' + word).trim();
215
+ } else {
216
+ if (currentLine) console.log(`│ ${currentLine.padEnd(66)}│`);
217
+ currentLine = word;
218
+ }
219
+ });
220
+ if (currentLine) console.log(`│ ${currentLine.padEnd(66)}│`);
221
+ }
222
+ });
223
+
224
+ console.log(`└${"─".repeat(68)}┘`);
225
+ };
226
+
227
+ const displayResponse = (result: any) => {
228
+ console.log(`\n╔${"═".repeat(68)}╗`);
229
+ console.log(`║ 💬 Agent Response${" ".repeat(50)}║`);
230
+ console.log(`╠${"═".repeat(68)}╣`);
231
+
232
+ // Display decision
233
+ console.log(`║ 📋 Decision: ${(result.decision || "N/A").substring(0, 52).padEnd(53)}║`);
234
+ console.log(`║ 📖 Policy: ${(result.policyApplied || "N/A").substring(0, 54).padEnd(55)}║`);
235
+ console.log(`╠${"═".repeat(68)}╣`);
236
+
237
+ // Display response (wrapped)
238
+ const responseLines = (result.politeSupportResponse || "").split('\n');
239
+ responseLines.forEach((line: string) => {
240
+ const trimmed = line.trim();
241
+ if (trimmed.length === 0) {
242
+ console.log(`║ ${" ".repeat(66)}║`);
243
+ } else if (trimmed.length <= 66) {
244
+ console.log(`║ ${trimmed.padEnd(66)}║`);
245
+ } else {
246
+ const words = trimmed.split(' ');
247
+ let currentLine = '';
248
+ words.forEach(word => {
249
+ if ((currentLine + ' ' + word).trim().length <= 66) {
250
+ currentLine = (currentLine + ' ' + word).trim();
251
+ } else {
252
+ if (currentLine) console.log(`║ ${currentLine.padEnd(66)}║`);
253
+ currentLine = word;
254
+ }
255
+ });
256
+ if (currentLine) console.log(`║ ${currentLine.padEnd(66)}║`);
257
+ }
258
+ });
259
+
260
+ console.log(`╚${"═".repeat(68)}╝`);
261
+ };
262
+
263
+ const displayHelp = () => {
264
+ console.log("\n📋 Commands:");
265
+ console.log(" • [1-7] - Select a sample ticket by number");
266
+ console.log(" • custom - Enter a custom support ticket");
267
+ console.log(" • playbook - View learned exceptions");
268
+ console.log(" • policies - Show the 3 strict rules");
269
+ console.log(" • help - Show this help message");
270
+ console.log(" • quit - Exit the demo");
271
+
272
+ console.log("\n📝 Example feedback to teach exceptions:");
273
+ console.log(" • \"Medical emergencies extend the 30-day window to 60 days\"");
274
+ console.log(" • \"Allow alternate payment if original method is closed/stolen\"");
275
+ console.log(" • \"Defective products get refunded regardless of sale status\"");
276
+ console.log(" • \"Loyal customers (5+ years) get extended return windows\"");
277
+ };
278
+
279
+ const displayPolicies = () => {
280
+ console.log("\n📜 The Only 3 Rules the Agent Knows:");
281
+ console.log("═".repeat(70));
282
+ STANDARD_POLICIES.forEach((policy, i) => console.log(` ${i + 1}. ${policy}`));
283
+ console.log("═".repeat(70));
284
+ console.log("\n⚠️ Every sample ticket VIOLATES at least one of these rules!");
285
+ console.log(" Watch how the agent decides, then teach it when to make exceptions.");
286
+ };
287
+
288
+ // --- 4. Main Interactive Loop ---
289
+
290
+ async function main() {
291
+ console.log("\n🎧 ACE Customer Support Demo: 3 Rules vs Reality");
292
+ console.log("═".repeat(70));
293
+ console.log("The agent knows only 3 strict policies. Every ticket violates one.");
294
+ console.log("Watch it struggle, then teach it when exceptions should apply.\n");
295
+
296
+ // Initialize AxCrew
297
+ const crew = new AxCrew(crewConfig, AxCrewFunctions);
298
+
299
+ try {
300
+ console.log("⏳ Initializing Support Agent...");
301
+ await crew.addAgentsToCrew(["SupportAgent"]);
302
+ const agent = crew.agents!.get("SupportAgent")!;
303
+
304
+ console.log("✅ Support Agent ready with ACE enabled\n");
305
+
306
+ // Show initial playbook (if loaded from persistence)
307
+ const initialPlaybook = (agent as any).getPlaybook?.();
308
+ displayPlaybook(initialPlaybook, "SupportAgent");
309
+
310
+ displayHelp();
311
+
312
+ let continueLoop = true;
313
+ let ticketCount = 0;
314
+ let feedbackCount = 0;
315
+
316
+ while (continueLoop) {
317
+ console.log(`\n${"═".repeat(70)}`);
318
+ console.log(`🎧 Support Session #${ticketCount + 1}`);
319
+ console.log("═".repeat(70));
320
+
321
+ // List available tickets
322
+ console.log("\n📋 Sample Tickets:");
323
+ SAMPLE_TICKETS.forEach((t, i) => {
324
+ console.log(` [${i + 1}] ${t.id}: ${t.category}`);
325
+ });
326
+
327
+ const choice = await prompt("\n🔍 Select ticket [1-7], 'custom', 'playbook', 'policies', 'help', or 'quit': ");
328
+
329
+ if (choice.toLowerCase() === 'quit' || choice.toLowerCase() === 'exit' || choice === '') {
330
+ continueLoop = false;
331
+ continue;
332
+ }
333
+
334
+ if (choice.toLowerCase() === 'help') {
335
+ displayHelp();
336
+ continue;
337
+ }
338
+
339
+ if (choice.toLowerCase() === 'playbook') {
340
+ const currentPlaybook = (agent as any).getPlaybook?.();
341
+ displayPlaybook(currentPlaybook, "SupportAgent");
342
+ continue;
343
+ }
344
+
345
+ if (choice.toLowerCase() === 'policies') {
346
+ displayPolicies();
347
+ continue;
348
+ }
349
+
350
+ let ticketText: string;
351
+ let ticketDisplay: typeof SAMPLE_TICKETS[0] | null = null;
352
+
353
+ if (choice.toLowerCase() === 'custom') {
354
+ console.log("\n📝 Enter custom ticket details (press Enter twice when done):");
355
+ let lines: string[] = [];
356
+ let line = await prompt(" ");
357
+ while (line !== '') {
358
+ lines.push(line);
359
+ line = await prompt(" ");
360
+ }
361
+ ticketText = lines.join('\n');
362
+ if (!ticketText.trim()) {
363
+ console.log("❌ Empty ticket, please try again.");
364
+ continue;
365
+ }
366
+ } else {
367
+ const ticketNum = parseInt(choice);
368
+ if (isNaN(ticketNum) || ticketNum < 1 || ticketNum > SAMPLE_TICKETS.length) {
369
+ console.log("❌ Invalid selection. Enter 1-7, 'custom', 'playbook', 'policies', 'help', or 'quit'.");
370
+ continue;
371
+ }
372
+ ticketDisplay = SAMPLE_TICKETS[ticketNum - 1];
373
+ ticketText = ticketDisplay.ticket;
374
+ }
375
+
376
+ ticketCount++;
377
+
378
+ // Display the ticket
379
+ if (ticketDisplay) {
380
+ displayTicket(ticketDisplay);
381
+ } else {
382
+ console.log("\n📝 Custom Ticket:");
383
+ console.log("─".repeat(70));
384
+ console.log(ticketText);
385
+ console.log("─".repeat(70));
386
+ }
387
+
388
+ console.log("\n⏳ Agent processing ticket...\n");
389
+
390
+ try {
391
+ // Execute the query
392
+ const result = await agent.forward({ ticket: ticketText, standardPolicies: STANDARD_POLICIES });
393
+ const taskId = (result as any)._taskId;
394
+
395
+ displayResponse(result);
396
+
397
+ // Get feedback from user
398
+ console.log("\n💬 Supervisor Feedback Loop");
399
+ console.log("─".repeat(70));
400
+ console.log("Was this response correct? If not, provide feedback to teach the agent");
401
+ console.log("how to handle this type of edge case in the future.\n");
402
+
403
+ const feedback = await prompt("📝 Feedback (or Enter to approve): ");
404
+
405
+ if (feedback.toLowerCase() === 'quit' || feedback.toLowerCase() === 'exit') {
406
+ continueLoop = false;
407
+ continue;
408
+ }
409
+
410
+ if (feedback && feedback.length > 0) {
411
+ console.log("\n⏳ Teaching agent new exception handling...");
412
+
413
+ // Apply feedback via ACE
414
+ if (taskId) {
415
+ await crew.applyTaskFeedback({
416
+ taskId,
417
+ feedback,
418
+ strategy: "all"
419
+ });
420
+ } else {
421
+ await (agent as any).applyOnlineUpdate?.({
422
+ example: { ticket: ticketText },
423
+ prediction: result,
424
+ feedback
425
+ });
426
+ }
427
+
428
+ feedbackCount++;
429
+ console.log("✅ Agent learned from feedback!\n");
430
+
431
+ // Display updated playbook
432
+ const updatedPlaybook = (agent as any).getPlaybook?.();
433
+ displayPlaybook(updatedPlaybook, "SupportAgent");
434
+
435
+ console.log("\n💡 The agent will now apply this learning to similar situations!");
436
+ console.log(" Try another ticket to see the agent use this new knowledge.");
437
+ } else {
438
+ console.log("\n✅ Response approved - no changes needed.");
439
+ }
440
+
441
+ } catch (error: any) {
442
+ console.error(`\n❌ Error: ${error.message}`);
443
+ }
444
+ }
445
+
446
+ // Final summary
447
+ console.log("\n" + "═".repeat(70));
448
+ console.log("📊 Session Summary");
449
+ console.log("═".repeat(70));
450
+ console.log(` Tickets handled: ${ticketCount}`);
451
+ console.log(` Feedback provided: ${feedbackCount}`);
452
+
453
+ const finalPlaybook = (agent as any).getPlaybook?.();
454
+ if (finalPlaybook?.sections) {
455
+ const bulletCount = Object.values(finalPlaybook.sections)
456
+ .reduce((acc: number, bullets: any) => acc + (Array.isArray(bullets) ? bullets.length : 0), 0);
457
+ console.log(` Learned exceptions: ${bulletCount}`);
458
+ }
459
+ console.log(` Saved to: playbooks/customer-support.json`);
460
+
461
+ console.log("\n🎧 Thanks for training the Support Agent!");
462
+ console.log(" Learned exceptions are saved for future sessions.\n");
463
+
464
+ } catch (error: any) {
465
+ console.error("\n❌ Error:", error.message);
466
+ console.log("\nTroubleshooting:");
467
+ console.log("• Ensure GEMINI_API_KEY (or OPENAI_API_KEY) is set");
468
+ } finally {
469
+ crew.cleanupOldExecutions(60000);
470
+ crew.destroy();
471
+ rl.close();
472
+ }
473
+ }
474
+
475
+ // --- 5. Run ---
476
+
477
+ main().catch((error) => {
478
+ console.error("Fatal error:", error);
479
+ process.exit(1);
480
+ });