aimyapi 0.2.1 → 1.0.3

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/index.mjs CHANGED
@@ -4,6 +4,8 @@ import { OpenAI } from 'openai';
4
4
  import path from 'path';
5
5
  import { getQuickJS } from 'quickjs-emscripten';
6
6
  import * as ts from 'typescript';
7
+ import { z } from 'zod';
8
+ import { zodToJsonSchema } from 'zod-to-json-schema';
7
9
 
8
10
  var require$1 = (
9
11
  true
@@ -196,8 +198,8 @@ function injectTimingFunctions(vm, cbAddedTimer, cbRemovedTimer, maxTimeout = 6e
196
198
  _clearInterval.dispose();
197
199
  }
198
200
 
199
- function tsCompile(source, options = null) {
200
- if (null === options) {
201
+ function tsCompile(source, options) {
202
+ if (!options) {
201
203
  options = {
202
204
  compilerOptions: {
203
205
  target: ts.ScriptTarget.ES2020,
@@ -207,6 +209,34 @@ function tsCompile(source, options = null) {
207
209
  }
208
210
  return ts.transpileModule(source, options).outputText;
209
211
  }
212
+ class Logger {
213
+ constructor(debug = false) {
214
+ this.logs = [];
215
+ this.debug = false;
216
+ this.debug = debug;
217
+ }
218
+ log(args, source) {
219
+ if (Array.isArray(args)) {
220
+ this.logs.push({ type: "log", args, source });
221
+ } else {
222
+ this.logs.push({ type: "log", args: [args], source });
223
+ }
224
+ if (this.debug) {
225
+ console.log(`[${source}]:`, ...args);
226
+ }
227
+ }
228
+ error(args, source) {
229
+ if (Array.isArray(args)) {
230
+ this.logs.push({ type: "error", args, source });
231
+ } else {
232
+ this.logs.push({ type: "error", args: [args], source });
233
+ }
234
+ console.error(`[${source}]:`, ...args);
235
+ }
236
+ clear() {
237
+ this.logs = [];
238
+ }
239
+ }
210
240
  async function createSandbox(QuickJS, requireLookup = {}, globals = {}, debug = false) {
211
241
  const vm = QuickJS.newContext();
212
242
  let errorState = false;
@@ -223,20 +253,21 @@ async function createSandbox(QuickJS, requireLookup = {}, globals = {}, debug =
223
253
  let isAsyncProcessRunning = () => {
224
254
  return asyncProcessesRunning > 0;
225
255
  };
256
+ const logger = new Logger(debug);
226
257
  const logHandle = vm.newFunction("log", (...args) => {
227
258
  const nativeArgs = args.map(vm.dump);
228
- console.log("QuickJS Log:", ...nativeArgs);
259
+ logger.log(nativeArgs, "sandbox");
229
260
  });
230
261
  const consoleHandle = vm.newObject();
231
262
  const exportsHandle = vm.newObject();
232
263
  const errorHandle = vm.newFunction("error", (...args) => {
233
264
  try {
234
265
  const nativeArgs = args.map(vm.dump);
235
- console.log("QuickJS console.error:", ...nativeArgs);
266
+ logger.error(nativeArgs, "sandbox");
236
267
  lastError = JSON.stringify(nativeArgs);
237
268
  errorState = true;
238
269
  } catch (e) {
239
- console.log("Error in error:", e);
270
+ logger.error(["? Error in error:", e], "sandbox");
240
271
  }
241
272
  });
242
273
  const globalNames = Object.getOwnPropertyNames(globals);
@@ -278,170 +309,326 @@ async function createSandbox(QuickJS, requireLookup = {}, globals = {}, debug =
278
309
  return lastError;
279
310
  },
280
311
  isAsyncProcessRunning,
281
- runTask: (task) => {
312
+ // make sure code passes the typescript compilation
313
+ checkCode: (code) => {
314
+ logger.clear();
282
315
  try {
283
- const compiled = tsCompile(task);
316
+ const compiled = tsCompile(code);
284
317
  if (!compiled || compiled.length === 0) {
285
- console.error("Could not compile", task);
286
- return false;
318
+ logger.error(["Could not compile:", code], "compiler");
319
+ return {
320
+ success: false,
321
+ logs: logger.logs
322
+ };
323
+ }
324
+ } catch (e) {
325
+ logger.error(["Error testing code:", e], "compiler");
326
+ return {
327
+ success: false,
328
+ logs: logger.logs
329
+ };
330
+ }
331
+ return {
332
+ success: true,
333
+ logs: logger.logs
334
+ };
335
+ },
336
+ runCode: (code) => {
337
+ try {
338
+ logger.clear();
339
+ const compiled = tsCompile(code);
340
+ if (!compiled || compiled.length === 0) {
341
+ logger.error(["Could not compile:", code], "compiler");
342
+ return {
343
+ success: false,
344
+ logs: logger.logs
345
+ };
287
346
  }
288
347
  errorState = false;
289
348
  const result = vm.evalCode(compiled);
290
349
  if (result.error) {
291
- const lines = compiled.split("\n");
292
- for (let i = 0; i < lines.length; i++) {
293
- console.log(`${i + 1}: ${lines[i]}`);
294
- }
295
- console.log("Execution failed:", vm.dump(result.error));
350
+ const lines = compiled.split("\n").map((line, index) => `${index + 1}: ${line}`);
351
+ logger.error(["Execution failed:", lines.join("\n"), vm.dump(result.error)], "runner");
296
352
  result.error.dispose();
297
- return false;
298
- } else {
353
+ return {
354
+ success: false,
355
+ logs: logger.logs
356
+ };
357
+ } else if ("value" in result) {
299
358
  result.value.dispose();
300
359
  if (errorState) {
301
- const lines = compiled.split("\n");
302
- for (let i = 0; i < lines.length; i++) {
303
- console.log(`${i + 1}: ${lines[i]}`);
304
- }
360
+ const lines = compiled.split("\n").map((line, index) => `${index + 1}: ${line}`);
361
+ logger.error(["An error was reported during execution. Compiled code with line numbers:", lines.join("\n")], "runner");
305
362
  }
306
- return !errorState;
363
+ return {
364
+ success: !errorState,
365
+ logs: logger.logs
366
+ };
367
+ } else {
368
+ logger.error(["Unknown execution result:", result], "runner");
369
+ return {
370
+ success: false,
371
+ logs: logger.logs
372
+ };
307
373
  }
308
374
  } catch (e) {
309
- console.log(e);
375
+ logger.error(["Error running code:", e], "runner");
310
376
  }
311
- return false;
377
+ return {
378
+ success: false,
379
+ logs: logger.logs
380
+ };
312
381
  }
313
382
  };
314
383
  }
315
384
 
316
385
  require$1("dotenv").config();
317
- const openai = new OpenAI({
318
- apiKey: process.env.OPENAI_API_KEY
319
- // This is also the default, can be omitted
320
- });
321
- const CODE_START = `\`\`\`
322
- import * as ApiDefs from 'api.ts'
323
-
324
- (async() {
325
- try {`;
326
- const CODE_END = ` } catch(err) {
386
+ const openai = new OpenAI();
387
+ let QuickJS = null;
388
+ const wrapCode = (apiGlobalName, apiFileDefPath, code) => `
389
+ (async () => {
390
+ try {
391
+ ${code}
392
+ } catch(err) {
327
393
  console.error(err);
328
394
  }
329
395
  })();
330
- \`\`\``;
331
- function indentString(str, numTabs) {
332
- const tabs = " ".repeat(numTabs);
333
- return str.split("\n").map((line) => tabs + line).join("\n");
396
+ `;
397
+ const wrapCodeBlock = (code, codeType = "") => `
398
+ \`\`\`${codeType}
399
+ ${code}
400
+ \`\`\`
401
+ `;
402
+ function zodParseJSON(schema) {
403
+ return (input) => schema.parse(JSON.parse(input));
334
404
  }
335
- function replaceCodeBlocks(text) {
336
- const codeBlockRegex = /```[\s\S]*?```/g;
337
- return text.replace(codeBlockRegex, (match) => {
338
- const code = match.slice(3, -3).trim();
339
- return `${CODE_START}
340
- ${indentString(code, 2)}
341
- ${CODE_END}`;
342
- });
343
- }
344
- function createBasePrompt(apiFilePath, documentationPath) {
405
+ function createBasePrompt(apiFilePath, apiGlobalName, documentationPath) {
345
406
  const apiText = fs.readFileSync(apiFilePath, "utf8");
346
407
  const documentationText = documentationPath ? fs.readFileSync(documentationPath, "utf8") : "";
347
- const createTaskPrompt = fs.readFileSync(path.join(__dirname, "../prompts/create-task-prompt.md"), "utf8").replace("{{DOCUMENTATION}}", replaceCodeBlocks(documentationText)).replace("{{API}}", apiText);
408
+ const createTaskPrompt = fs.readFileSync(path.join(__dirname, "../prompts/create-task-prompt.md"), "utf8").replace("{{DOCUMENTATION}}", documentationText).replace("{{API}}", apiText).replace("{{API_GLOBAL_NAME}}", apiGlobalName || "");
348
409
  return createTaskPrompt;
349
410
  }
350
- const generateCode = async function(queryText, userChatHistory, createTaskPrompt, debug = false, model = "gpt-4o-mini") {
351
- if (!queryText)
352
- return { code: "", loggableCode: "" };
353
- const prompt = createTaskPrompt.replace("{{QUERY_TEXT}}", queryText);
354
- const messages = [
411
+ const runCodeSchema = z.object({
412
+ code: z.string()
413
+ });
414
+ const checkCodeSchema = z.object({
415
+ code: z.string()
416
+ });
417
+ const submitCodeSchema = z.object({
418
+ code: z.string()
419
+ });
420
+ const generateCode = async function(instance, queryText, userChatHistory, createTaskPrompt, debug = false, hideLogsFromAgent = false, model = "gpt-5-mini", additionalModelOptions) {
421
+ let finalSubmittedCode = "";
422
+ if (debug) {
423
+ console.log("Generating code for query:", queryText);
424
+ }
425
+ const codingTools = [
355
426
  {
356
- role: "system",
357
- content: prompt
427
+ type: "function",
428
+ function: {
429
+ name: "runCode",
430
+ // @ts-ignore
431
+ function: async ({ code }) => {
432
+ if (debug) {
433
+ console.log("Running code...", code);
434
+ }
435
+ const res = await instance.runCode(code);
436
+ if (hideLogsFromAgent) {
437
+ res.logs = [];
438
+ }
439
+ return res;
440
+ },
441
+ description: "Run the provided code in a sandboxed environment. The code will have access to the API object. Returns the result of the code execution.",
442
+ parse: zodParseJSON(runCodeSchema),
443
+ parameters: zodToJsonSchema(runCodeSchema)
444
+ }
445
+ },
446
+ {
447
+ type: "function",
448
+ function: {
449
+ name: "checkCode",
450
+ // @ts-ignore
451
+ function: async ({ code }) => {
452
+ if (debug) {
453
+ console.log("Checking code...", code);
454
+ }
455
+ const res = await instance.checkCode(code);
456
+ return res;
457
+ },
458
+ description: "Check the provided code for errors or issues without running it. Returns the result of the code check.",
459
+ parse: zodParseJSON(checkCodeSchema),
460
+ parameters: zodToJsonSchema(checkCodeSchema)
461
+ }
358
462
  },
359
- ...userChatHistory,
360
463
  {
361
- role: "user",
362
- content: `${queryText}. Respond with json`,
363
- name: "user"
464
+ type: "function",
465
+ function: {
466
+ name: "submitCode",
467
+ // @ts-ignore
468
+ function: async ({ code }) => {
469
+ finalSubmittedCode = code;
470
+ return {
471
+ success: true
472
+ };
473
+ },
474
+ description: "Submit the provided code as the final code to run. This will end the code generation process and the code will be run in the sandbox. Use this when you are confident in the code you have generated and want to run it.",
475
+ parse: zodParseJSON(submitCodeSchema),
476
+ parameters: zodToJsonSchema(submitCodeSchema)
477
+ }
364
478
  }
365
479
  ];
366
480
  try {
367
- const results = await openai.chat.completions.create(
368
- {
369
- model,
370
- messages,
371
- temperature: 0,
372
- max_tokens: 2e3,
373
- response_format: {
374
- json_schema: {
375
- name: "codeResponse",
376
- description: "The code generated by the AI",
377
- schema: {
378
- type: "object",
379
- properties: {
380
- yourCode: {
381
- type: "string",
382
- description: "The code generated by the AI"
383
- }
384
- }
385
- }
386
- },
387
- type: "json_schema"
481
+ const runner = openai.chat.completions.runTools({
482
+ model,
483
+ messages: [
484
+ {
485
+ role: "system",
486
+ content: "You will assist with writing code to perform the user's request."
487
+ },
488
+ ...userChatHistory,
489
+ {
490
+ role: "user",
491
+ content: createTaskPrompt.replace("{{USER_QUERY}}", queryText)
388
492
  }
493
+ ],
494
+ tools: codingTools,
495
+ ...additionalModelOptions
496
+ }).on("message", async (message) => {
497
+ if (debug) {
498
+ console.log("Message:", JSON.stringify(message, null, 2));
389
499
  }
390
- );
391
- if (debug)
392
- console.log(results.usage);
393
- const response = results.choices[0].message.content;
394
- if (debug)
395
- console.log(response);
396
- try {
397
- const json = JSON.parse(response);
398
- const code = json.yourCode.replaceAll("```", "");
399
- return { code, loggableCode: code };
400
- } catch (err) {
401
- console.error("Error parsing response", err);
402
- return { code: "", loggableCode: "" };
500
+ }).on("functionToolCall", (functionCall) => {
501
+ if (debug) {
502
+ console.log("Function Call:", JSON.stringify(functionCall, null, 2));
503
+ }
504
+ }).on("functionToolCallResult", (functionCall) => {
505
+ if (debug) {
506
+ console.log("Function Call Result:", JSON.stringify(functionCall, null, 2));
507
+ }
508
+ });
509
+ const finalContent = await runner.finalContent();
510
+ if (debug) {
511
+ console.log("Final Content:", finalContent);
403
512
  }
513
+ return {
514
+ code: finalSubmittedCode,
515
+ comments: finalContent || void 0
516
+ };
404
517
  } catch (err) {
405
518
  console.error(err);
406
- return { code: "", loggableCode: "" };
519
+ return null;
407
520
  }
408
521
  };
409
- let QuickJS = null;
410
522
  async function createWithAPI(options) {
411
- options = {
412
- apiGlobalName: "api",
413
- apiGlobals: {},
414
- apiWhitelist: Object.getOwnPropertyNames(Object.getPrototypeOf(options.apiObject)).filter((f) => f !== "constructor" && !f.startsWith("_")),
415
- debug: false,
416
- model: "gpt-4o-mini",
523
+ const localOptions = {
524
+ ...{
525
+ apiGlobalName: "api",
526
+ apiGlobals: {},
527
+ apiWhitelist: Object.getOwnPropertyNames(Object.getPrototypeOf(options.apiObject)).filter((f) => f !== "constructor" && !f.startsWith("_")),
528
+ debug: false,
529
+ model: process.env.OPENAI_API_MODEL || "gpt-5.4-mini",
530
+ additionalModelOptions: {},
531
+ hideLogsFromAgent: false
532
+ },
417
533
  ...options
418
534
  };
419
- if (!options.apiObject || !options.apiDefFilePath) {
420
- throw new Error("apiObject and apiFilePath are required");
535
+ if (!localOptions.apiObject || !localOptions.apiDefFilePath) {
536
+ throw new Error("apiObject and apiDefFilePath are required");
421
537
  }
422
- const { apiObject, apiWhitelist, apiGlobalName, apiExports, apiDefFilePath, apiDocsPath, debug, model } = options;
423
- const createTaskPrompt = createBasePrompt(apiDefFilePath, apiDocsPath);
424
- return {
425
- options,
538
+ const { apiObject, apiWhitelist, apiGlobalName, apiExports, apiDefFilePath, apiDocsPath, debug, model, additionalModelOptions, hideLogsFromAgent } = localOptions;
539
+ const createTaskPrompt = createBasePrompt(apiDefFilePath, apiGlobalName, apiDocsPath);
540
+ const instance = {
541
+ options: localOptions,
426
542
  generateCode: async function(queryText, userChatHistory, currentContext = void 0) {
427
543
  if (!queryText)
428
- return { code: "", loggableCode: "" };
429
- return await generateCode(queryText, userChatHistory, createTaskPrompt.replace("{{CONTEXT}}", currentContext ? "```" + JSON.stringify(currentContext, null, 2) + "```" : ""), debug, model);
544
+ return null;
545
+ const formattedContext = wrapCodeBlock(JSON.stringify(currentContext, null, 2), "json");
546
+ return await generateCode(
547
+ instance,
548
+ queryText,
549
+ userChatHistory,
550
+ createTaskPrompt.replace("{{CONTEXT}}", formattedContext),
551
+ debug,
552
+ hideLogsFromAgent,
553
+ model,
554
+ additionalModelOptions
555
+ );
430
556
  },
431
- runCode: async function(generatedCode) {
557
+ checkCode: async function(generatedCode) {
432
558
  if (!QuickJS) {
433
559
  if (debug)
434
560
  console.log("Creating quick.js");
435
561
  QuickJS = await getQuickJS();
436
562
  }
437
563
  let apiGlobals = {};
438
- for (const key in options.apiGlobals) {
439
- apiGlobals[key] = {
440
- value: options.apiGlobals[key]
564
+ if (options.apiGlobals) {
565
+ for (const key in options.apiGlobals) {
566
+ apiGlobals[key] = {
567
+ value: options.apiGlobals[key]
568
+ };
569
+ }
570
+ }
571
+ const { vm, checkCode } = await createSandbox(QuickJS, {
572
+ [apiDefFilePath]: apiExports
573
+ }, {
574
+ ...apiGlobals,
575
+ [apiGlobalName]: {
576
+ value: apiObject,
577
+ whitelist: apiWhitelist
578
+ }
579
+ }, options.debug);
580
+ try {
581
+ if (debug) {
582
+ console.log("checking Code:\n", generatedCode);
583
+ }
584
+ const res = await checkCode(wrapCode(apiGlobalName, apiDefFilePath, generatedCode));
585
+ if (!res) {
586
+ console.error("Unknown failure checking code");
587
+ return {
588
+ success: false,
589
+ logs: []
590
+ };
591
+ }
592
+ if (debug) {
593
+ console.log(res.success ? "Code check passed" : "Code check failed");
594
+ }
595
+ if (res.success) {
596
+ return {
597
+ success: true,
598
+ logs: [{ type: "log", args: ["\u2714\uFE0F Code check passed"], source: "compiler" }, ...res.logs]
599
+ };
600
+ }
601
+ return {
602
+ success: false,
603
+ logs: [{ type: "error", args: ["\u274C Code check failed"], source: "compiler" }, ...res.logs]
441
604
  };
605
+ } catch (err) {
606
+ if (debug) {
607
+ console.error("Error checking code:", err);
608
+ }
609
+ }
610
+ vm.dispose();
611
+ return {
612
+ success: false,
613
+ logs: []
614
+ };
615
+ },
616
+ runCode: async function(code) {
617
+ if (!QuickJS) {
618
+ if (debug)
619
+ console.log("Creating quick.js");
620
+ QuickJS = await getQuickJS();
442
621
  }
443
- const { vm, runTask, isAsyncProcessRunning } = await createSandbox(QuickJS, {
444
- [apiDefFilePath.replaceAll("\\", "")]: apiExports
622
+ let apiGlobals = {};
623
+ if (options.apiGlobals) {
624
+ for (const key in options.apiGlobals) {
625
+ apiGlobals[key] = {
626
+ value: options.apiGlobals[key]
627
+ };
628
+ }
629
+ }
630
+ const { vm, runCode: runTask, isAsyncProcessRunning } = await createSandbox(QuickJS, {
631
+ [apiDefFilePath]: apiExports
445
632
  }, {
446
633
  ...apiGlobals,
447
634
  [apiGlobalName]: {
@@ -451,11 +638,15 @@ async function createWithAPI(options) {
451
638
  }, options.debug);
452
639
  try {
453
640
  if (debug) {
454
- console.log("Running Code:\n", generatedCode);
641
+ console.log("Running Code:\n", code);
455
642
  }
456
- const res = await runTask(generatedCode.replace("'api.ts'", `'${options.apiDefFilePath}'`));
643
+ const res = await runTask(wrapCode(apiGlobalName, apiDefFilePath, code));
457
644
  if (!res) {
458
- console.error("Task unsuccessful");
645
+ console.error("Unknown failure running task");
646
+ return {
647
+ success: false,
648
+ logs: []
649
+ };
459
650
  }
460
651
  while (isAsyncProcessRunning()) {
461
652
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -463,6 +654,7 @@ async function createWithAPI(options) {
463
654
  if (debug) {
464
655
  console.log("Completed");
465
656
  }
657
+ return res;
466
658
  } catch (err) {
467
659
  if (debug) {
468
660
  console.error("Error running task:", err);
@@ -471,20 +663,39 @@ async function createWithAPI(options) {
471
663
  await new Promise((resolve) => setTimeout(resolve, 100));
472
664
  rejectOpenPromises(vm);
473
665
  vm.dispose();
666
+ return {
667
+ success: false,
668
+ logs: []
669
+ };
474
670
  },
671
+ // generates code and runs it in one step
475
672
  processRequest: async function(userQuery, currentContext = void 0) {
476
673
  if (debug) {
477
674
  console.log("Query:", userQuery);
478
675
  }
479
676
  console.time("Generate Task");
480
- const generatedCode = await generateCode(userQuery, [], createTaskPrompt.replace("{{CONTEXT}}", currentContext ? "```" + JSON.stringify(currentContext, null, 2) + "```" : ""));
677
+ const generatedCode = await generateCode(
678
+ instance,
679
+ userQuery,
680
+ [],
681
+ createTaskPrompt.replace("{{CONTEXT}}", currentContext ? JSON.stringify(currentContext, null, 2) : ""),
682
+ debug,
683
+ hideLogsFromAgent,
684
+ model,
685
+ additionalModelOptions
686
+ );
481
687
  console.timeEnd("Generate Task");
482
688
  console.time("Run Code");
483
- await this.runCode(generatedCode.code.replace("'api.ts'", `'${options.apiDefFilePath}'`));
689
+ if (generatedCode?.code) {
690
+ await instance.runCode(generatedCode.code);
691
+ } else {
692
+ console.error("No code generated", generatedCode);
693
+ }
484
694
  console.timeEnd("Run Code");
485
695
  return generatedCode;
486
696
  }
487
697
  };
698
+ return instance;
488
699
  }
489
700
  const AIMyAPI = {
490
701
  // Standard way of using the library
@@ -495,4 +706,4 @@ const AIMyAPI = {
495
706
  createSandbox
496
707
  };
497
708
 
498
- export { AIMyAPI, createBasePrompt, generateCode };
709
+ export { AIMyAPI, createBasePrompt, generateCode, zodParseJSON };
@@ -1,6 +1,5 @@
1
1
  import path from "path";
2
2
  import { AIMyAPI } from "../src";
3
- import * as APIExports from "./sales_api";
4
3
  import { API } from "./sales_api_impl";
5
4
 
6
5
  // Simpler example using no support for chat history;
@@ -10,21 +9,23 @@ import { API } from "./sales_api_impl";
10
9
 
11
10
  const aimyapi = await AIMyAPI.createWithAPI({
12
11
  apiObject: api,
13
- apiGlobalName: "api", // Should match whatever you declared as your global in your api.
14
- apiExports: APIExports,
12
+ apiGlobalName: "salesApi", // Should match whatever you declared as your global in your api.
15
13
  apiDefFilePath: path.join(__dirname, "./sales_api.ts"),
16
14
  apiDocsPath: path.join(__dirname, "./sales_api.md"),
17
- debug: true,
15
+ debug: false,
18
16
  })
17
+
18
+ aimyapi.runCode(`console.log("Hello from the sandbox! Here is some sales data:", salesApi.getSalesData())`);
19
19
 
20
- // console.log("Print 'hello' three times.")
21
- // await aimyapi.processRequest("Print 'hello' three times.");
20
+ console.log("Print 'hello' three times.")
21
+ await aimyapi.processRequest("Print 'hello' three times.");
22
22
 
23
- // console.log("What month had the highest sales in 2020 for any business unit?")
24
- // await aimyapi.processRequest("What was our best month in 2020");
23
+ console.log("What month had the highest sales in 2020 for any business unit?")
24
+ await aimyapi.processRequest("What was our best month in 2020");
25
25
 
26
- // console.log("Which business unit had the highest sales in 2020?")
27
- // await aimyapi.processRequest("Which business unit had the highest sales in 2020?");
26
+ console.log("Which business unit had the highest sales in 2020? Print the totals for each")
27
+ await aimyapi.processRequest("Which business unit had the highest sales in 2020? Print the totals for each");
28
+ // Note: this is a bit of a trick question because two of them are tied for first. The agent usually doesn't realize this edge case. Should we instruct it to look for edge cases?
28
29
 
29
30
  console.log("Compute the total sales for each month in 2020 then email the top 3 to test@email.com")
30
31
  await aimyapi.processRequest("Compute the total sales for each month in 2020 then email the top 3 to test@email.com");