@convex-dev/agent 0.0.12 → 0.0.14-alpha.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.
Files changed (48) hide show
  1. package/README.md +5 -1
  2. package/dist/commonjs/client/index.d.ts +62 -31
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +133 -37
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/messages.d.ts +398 -64
  7. package/dist/commonjs/component/messages.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.js +10 -18
  9. package/dist/commonjs/component/messages.js.map +1 -1
  10. package/dist/commonjs/component/schema.d.ts +442 -176
  11. package/dist/commonjs/component/schema.d.ts.map +1 -1
  12. package/dist/commonjs/component/schema.js +8 -1
  13. package/dist/commonjs/component/schema.js.map +1 -1
  14. package/dist/commonjs/mapping.d.ts +9 -3
  15. package/dist/commonjs/mapping.d.ts.map +1 -1
  16. package/dist/commonjs/mapping.js +49 -20
  17. package/dist/commonjs/mapping.js.map +1 -1
  18. package/dist/commonjs/validators.d.ts +1575 -1243
  19. package/dist/commonjs/validators.d.ts.map +1 -1
  20. package/dist/commonjs/validators.js +34 -11
  21. package/dist/commonjs/validators.js.map +1 -1
  22. package/dist/esm/client/index.d.ts +62 -31
  23. package/dist/esm/client/index.d.ts.map +1 -1
  24. package/dist/esm/client/index.js +133 -37
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/component/messages.d.ts +398 -64
  27. package/dist/esm/component/messages.d.ts.map +1 -1
  28. package/dist/esm/component/messages.js +10 -18
  29. package/dist/esm/component/messages.js.map +1 -1
  30. package/dist/esm/component/schema.d.ts +442 -176
  31. package/dist/esm/component/schema.d.ts.map +1 -1
  32. package/dist/esm/component/schema.js +8 -1
  33. package/dist/esm/component/schema.js.map +1 -1
  34. package/dist/esm/mapping.d.ts +9 -3
  35. package/dist/esm/mapping.d.ts.map +1 -1
  36. package/dist/esm/mapping.js +49 -20
  37. package/dist/esm/mapping.js.map +1 -1
  38. package/dist/esm/validators.d.ts +1575 -1243
  39. package/dist/esm/validators.d.ts.map +1 -1
  40. package/dist/esm/validators.js +34 -11
  41. package/dist/esm/validators.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/client/index.ts +177 -47
  44. package/src/component/_generated/api.d.ts +225 -11
  45. package/src/component/messages.ts +11 -29
  46. package/src/component/schema.ts +11 -0
  47. package/src/mapping.ts +56 -26
  48. package/src/validators.ts +43 -16
@@ -19,13 +19,12 @@ import type {
19
19
  import {
20
20
  generateObject,
21
21
  generateText,
22
- jsonSchema,
23
22
  streamObject,
24
23
  streamText,
25
24
  tool,
26
25
  } from "ai";
27
26
  import { assert } from "convex-helpers";
28
- import { internalActionGeneric } from "convex/server";
27
+ import { internalActionGeneric, internalMutationGeneric } from "convex/server";
29
28
  import { Infer, v } from "convex/values";
30
29
  import { z } from "zod";
31
30
  import { Mounts } from "../component/_generated/api.js";
@@ -37,7 +36,7 @@ import {
37
36
  type AIMessageWithoutId,
38
37
  deserializeMessage,
39
38
  promptOrMessagesToCoreMessages,
40
- serializeMessageWithId,
39
+ serializeMessage,
41
40
  serializeNewMessagesInStep,
42
41
  serializeObjectResult,
43
42
  serializeStep,
@@ -46,12 +45,15 @@ import {
46
45
  DEFAULT_MESSAGE_RANGE,
47
46
  DEFAULT_RECENT_MESSAGES,
48
47
  extractText,
48
+ isTool,
49
49
  } from "../shared.js";
50
50
  import {
51
51
  type CallSettings,
52
+ MessageWithMetadata,
52
53
  type ProviderMetadata,
53
54
  type ProviderOptions,
54
55
  type SearchOptions,
56
+ Usage,
55
57
  vSafeObjectArgs,
56
58
  vTextArgs,
57
59
  } from "../validators.js";
@@ -145,6 +147,19 @@ export type GenerationOutputMetadata = { messageId?: string };
145
147
 
146
148
  type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
147
149
 
150
+ export type UsageHandler = (
151
+ ctx: RunActionCtx,
152
+ args: {
153
+ userId?: string;
154
+ threadId?: string;
155
+ usage: Usage;
156
+ // Often has more information, like cached token usage in the case of openai.
157
+ providerMetadata?: ProviderMetadata;
158
+ model: string;
159
+ provider: string;
160
+ }
161
+ ) => Promise<void>;
162
+
148
163
  export class Agent<AgentTools extends ToolSet> {
149
164
  constructor(
150
165
  public component: UseApi<Mounts>,
@@ -206,6 +221,10 @@ export class Agent<AgentTools extends ToolSet> {
206
221
  * This can be overridden at each generate/stream callsite.
207
222
  */
208
223
  maxRetries?: number;
224
+ /**
225
+ * The usage handler to use for this agent.
226
+ */
227
+ usageHandler?: UsageHandler;
209
228
  }
210
229
  ) {}
211
230
 
@@ -433,7 +452,7 @@ export class Agent<AgentTools extends ToolSet> {
433
452
  }
434
453
  | undefined;
435
454
  if (this.options.textEmbedding) {
436
- const messageTexts = messages.map((m) => extractText(m));
455
+ const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
437
456
  // Find the indexes of the messages that have text.
438
457
  const textIndexes = messageTexts
439
458
  .map((t, i) => (t ? i : undefined))
@@ -445,6 +464,7 @@ export class Agent<AgentTools extends ToolSet> {
445
464
  const textEmbeddings = await this.options.textEmbedding.doEmbed({
446
465
  values: messageTexts.filter((t): t is string => !!t),
447
466
  });
467
+ // TODO: record usage of embeddings
448
468
  // Then assemble the embeddings into a single array with nulls for the messages without text.
449
469
  const embeddingsOrNull = Array(messages.length).fill(null);
450
470
  textIndexes.forEach((i, j) => {
@@ -463,12 +483,6 @@ export class Agent<AgentTools extends ToolSet> {
463
483
  return embeddings;
464
484
  }
465
485
 
466
- /**
467
- * Explicitly save messages associated with the thread (& user if provided)
468
- * @param ctx The ctx parameter to a mutation or action.
469
- * @param args The messages and context to save
470
- * @returns
471
- */
472
486
  /**
473
487
  * Explicitly save messages associated with the thread (& user if provided)
474
488
  * @param ctx The ctx parameter to a mutation or action.
@@ -482,17 +496,16 @@ export class Agent<AgentTools extends ToolSet> {
482
496
  userId?: string;
483
497
  messages: CoreMessageMaybeWithId[];
484
498
  /**
485
- * If false, it will "commit" the messages immediately.
486
- * If true, it will mark them as pending until the final step has finished.
499
+ * Metadata to save with the messages. Each element corresponds to the
500
+ * message at the same index.
487
501
  */
502
+ metadata?: Omit<MessageWithMetadata, "message">[];
488
503
  /**
489
504
  * If false, it will "commit" the messages immediately.
490
505
  * If true, it will mark them as pending until the final step has finished.
506
+ * Defaults to false.
491
507
  */
492
508
  pending?: boolean;
493
- /**
494
- * The message that this is responding to.
495
- */
496
509
  /**
497
510
  * The message that this is responding to.
498
511
  */
@@ -500,10 +513,7 @@ export class Agent<AgentTools extends ToolSet> {
500
513
  /**
501
514
  * Whether to mark all pending messages in the thread as failed.
502
515
  * This is used to recover from a failure via a retry that wipes the slate clean.
503
- */
504
- /**
505
- * Whether to mark all pending messages in the thread as failed.
506
- * This is used to recover from a failure via a retry that wipes the slate clean.
516
+ * Defaults to true.
507
517
  */
508
518
  failPendingSteps?: boolean;
509
519
  }
@@ -511,13 +521,23 @@ export class Agent<AgentTools extends ToolSet> {
511
521
  lastMessageId: string;
512
522
  messageIds: string[];
513
523
  }> {
524
+ const embeddings = await this.getEmbeddings(args.messages);
514
525
  const result = await ctx.runMutation(this.component.messages.addMessages, {
515
526
  threadId: args.threadId,
516
527
  userId: args.userId,
517
528
  agentName: this.options.name,
518
- model: this.options.chat.modelId,
519
- messages: args.messages.map(serializeMessageWithId),
520
- embeddings: await this.getEmbeddings(args.messages),
529
+ messages: args.messages.map(
530
+ (m, i) =>
531
+ ({
532
+ embedding: embeddings?.vectors[i] && {
533
+ model: embeddings.model,
534
+ dimension: embeddings.dimension,
535
+ vector: embeddings.vectors[i],
536
+ },
537
+ ...args.metadata?.[i],
538
+ message: serializeMessage(m),
539
+ }) as MessageWithMetadata
540
+ ),
521
541
  failPendingSteps: args.failPendingSteps ?? true,
522
542
  pending: args.pending ?? false,
523
543
  parentMessageId: args.parentMessageId,
@@ -528,11 +548,6 @@ export class Agent<AgentTools extends ToolSet> {
528
548
  };
529
549
  }
530
550
 
531
- /**
532
- * Explicitly save a "step" created by the AI SDK.
533
- * @param ctx The ctx argument to a mutation or action.
534
- * @param args What to save
535
- */
536
551
  /**
537
552
  * Explicitly save a "step" created by the AI SDK.
538
553
  * @param ctx The ctx argument to a mutation or action.
@@ -550,27 +565,41 @@ export class Agent<AgentTools extends ToolSet> {
550
565
  * The step to save, possibly including multiple tool calls.
551
566
  */
552
567
  step: StepResult<TOOLS>;
568
+ /**
569
+ * The model used to generate the step.
570
+ * Defaults to the chat model for the Agent.
571
+ */
572
+ model?: string;
573
+ /**
574
+ * The provider of the model used to generate the step.
575
+ * Defaults to the chat provider for the Agent.
576
+ */
577
+ provider?: string;
553
578
  }
554
579
  ): Promise<void> {
555
580
  const step = serializeStep(args.step as StepResult<ToolSet>);
556
- const messages = serializeNewMessagesInStep(args.step);
581
+ const messages = serializeNewMessagesInStep(args.step, {
582
+ provider: args.provider ?? this.options.chat.provider,
583
+ model: args.model ?? this.options.chat.modelId,
584
+ });
585
+ const embeddings = await this.getEmbeddings(messages.map((m) => m.message));
586
+ if (embeddings) {
587
+ const { model, dimension, vectors } = embeddings;
588
+ for (let i = 0; i < messages.length; i++) {
589
+ const vector = vectors[i];
590
+ if (vector) {
591
+ messages[i].embedding = { model, dimension, vector };
592
+ }
593
+ }
594
+ }
557
595
  await ctx.runMutation(this.component.messages.addStep, {
558
596
  threadId: args.threadId,
559
597
  messageId: args.messageId,
560
598
  step: { step, messages },
561
599
  failPendingSteps: false,
562
- embeddings: await this.getEmbeddings(messages.map((m) => m.message)),
563
600
  });
564
601
  }
565
602
 
566
- /**
567
- * Commit or rollback a message that was pending.
568
- * This is done automatically when saving messages by default.
569
- * If creating pending messages, you can call this when the full "transaction" is done.
570
- * @param ctx The ctx argument to your mutation or action.
571
- * @param args What message to save. Generally the parent message sent into
572
- * the generateText call.
573
- */
574
603
  /**
575
604
  * Commit or rollback a message that was pending.
576
605
  * This is done automatically when saving messages by default.
@@ -642,13 +671,14 @@ export class Agent<AgentTools extends ToolSet> {
642
671
  const saveOutputMessages =
643
672
  args.saveOutputMessages ??
644
673
  this.options.storageOptions?.saveOutputMessages;
674
+ const model = aiArgs.model ?? this.options.chat;
645
675
  try {
646
676
  const result = (await generateText({
647
677
  // Can be overridden
648
- model: this.options.chat,
649
678
  maxSteps: this.options.maxSteps,
650
679
  maxRetries: this.options.maxRetries,
651
680
  ...aiArgs,
681
+ model,
652
682
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
653
683
  toolChoice: args.toolChoice as any,
654
684
  tools,
@@ -660,6 +690,16 @@ export class Agent<AgentTools extends ToolSet> {
660
690
  step,
661
691
  });
662
692
  }
693
+ if (this.options.usageHandler && step.usage) {
694
+ await this.options.usageHandler(ctx, {
695
+ userId,
696
+ threadId,
697
+ model: model.modelId,
698
+ provider: model.provider,
699
+ usage: step.usage,
700
+ providerMetadata: step.providerMetadata,
701
+ });
702
+ }
663
703
  return args.onStepFinish?.(step);
664
704
  },
665
705
  })) as GenerateTextResult<TOOLS, OUTPUT> & GenerationOutputMetadata;
@@ -713,12 +753,13 @@ export class Agent<AgentTools extends ToolSet> {
713
753
  const saveOutputMessages =
714
754
  args.saveOutputMessages ??
715
755
  this.options.storageOptions?.saveOutputMessages;
756
+ const model = aiArgs.model ?? this.options.chat;
716
757
  const result = streamText({
717
758
  // Can be overridden
718
- model: this.options.chat,
719
759
  maxSteps: this.options.maxSteps,
720
760
  maxRetries: this.options.maxRetries,
721
761
  ...aiArgs,
762
+ model,
722
763
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
723
764
  toolChoice: args.toolChoice as any,
724
765
  tools,
@@ -746,6 +787,16 @@ export class Agent<AgentTools extends ToolSet> {
746
787
  step,
747
788
  });
748
789
  }
790
+ if (this.options.usageHandler && step.usage) {
791
+ await this.options.usageHandler(ctx, {
792
+ userId,
793
+ threadId,
794
+ model: model.modelId,
795
+ provider: model.provider,
796
+ usage: step.usage,
797
+ providerMetadata: step.providerMetadata,
798
+ });
799
+ }
749
800
  return args.onStepFinish?.(step);
750
801
  },
751
802
  }) as StreamTextResult<TOOLS, PARTIAL_OUTPUT> & GenerationOutputMetadata;
@@ -755,6 +806,7 @@ export class Agent<AgentTools extends ToolSet> {
755
806
 
756
807
  async saveMessagesAndFetchContext<
757
808
  T extends {
809
+ id?: string;
758
810
  prompt?: string;
759
811
  messages?: CoreMessage[] | AIMessageWithoutId[];
760
812
  system?: string;
@@ -762,6 +814,7 @@ export class Agent<AgentTools extends ToolSet> {
762
814
  >(
763
815
  ctx: RunActionCtx | RunMutationCtx,
764
816
  {
817
+ id,
765
818
  userId,
766
819
  threadId,
767
820
  parentMessageId,
@@ -795,10 +848,12 @@ export class Agent<AgentTools extends ToolSet> {
795
848
  });
796
849
  let messageId: string | undefined;
797
850
  if (threadId && saveAny !== false) {
851
+ const coreMessages = saveAll ? messages : messages.slice(-1);
798
852
  const saved = await this.saveMessages(ctx, {
799
853
  threadId,
800
854
  userId,
801
- messages: saveAll ? messages : messages.slice(-1),
855
+ messages: coreMessages,
856
+ metadata: coreMessages.length === 1 ? [{ id }] : undefined,
802
857
  pending: true,
803
858
  // We should just fail if you pass in an ID for the message, fail those children
804
859
  // failPendingSteps: true,
@@ -838,16 +893,16 @@ export class Agent<AgentTools extends ToolSet> {
838
893
  ctx,
839
894
  { ...args, userId, threadId }
840
895
  );
841
-
896
+ const model = aiArgs.model ?? this.options.chat;
842
897
  const saveOutputMessages =
843
898
  args.saveOutputMessages ??
844
899
  this.options.storageOptions?.saveOutputMessages;
845
900
  try {
846
901
  const result = (await generateObject({
847
902
  // Can be overridden
848
- model: this.options.chat,
849
903
  maxRetries: this.options.maxRetries,
850
904
  ...aiArgs,
905
+ model,
851
906
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
852
907
  } as any)) as GenerateObjectResult<T> & GenerationOutputMetadata;
853
908
 
@@ -855,6 +910,16 @@ export class Agent<AgentTools extends ToolSet> {
855
910
  await this.saveObject(ctx, { threadId, messageId, result });
856
911
  }
857
912
  result.messageId = messageId;
913
+ if (this.options.usageHandler && result.usage) {
914
+ await this.options.usageHandler(ctx, {
915
+ userId,
916
+ threadId,
917
+ model: model.modelId,
918
+ provider: model.provider,
919
+ usage: result.usage,
920
+ providerMetadata: result.providerMetadata,
921
+ });
922
+ }
858
923
  return result;
859
924
  } catch (error) {
860
925
  if (threadId && messageId) {
@@ -880,7 +945,7 @@ export class Agent<AgentTools extends ToolSet> {
880
945
  * @returns The result of the streamObject function.
881
946
  */
882
947
  async streamObject<T>(
883
- ctx: RunMutationCtx,
948
+ ctx: RunActionCtx,
884
949
  { userId, threadId }: { userId?: string; threadId?: string },
885
950
  args: OurStreamObjectArgs<T>
886
951
  ): Promise<
@@ -891,15 +956,16 @@ export class Agent<AgentTools extends ToolSet> {
891
956
  ctx,
892
957
  { ...args, userId, threadId }
893
958
  );
959
+ const model = aiArgs.model ?? this.options.chat;
894
960
  const saveOutputMessages =
895
961
  args.saveOutputMessages ??
896
962
  this.options.storageOptions?.saveOutputMessages;
897
963
  const stream = streamObject<T>({
898
964
  // Can be overridden
899
- model: this.options.chat,
900
965
  maxRetries: this.options.maxRetries,
901
966
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
902
967
  ...(aiArgs as any),
968
+ model,
903
969
  onError: async (error) => {
904
970
  console.error("onError", error);
905
971
  return args.onError?.(error);
@@ -924,6 +990,16 @@ export class Agent<AgentTools extends ToolSet> {
924
990
  },
925
991
  });
926
992
  }
993
+ if (this.options.usageHandler && result.usage) {
994
+ await this.options.usageHandler(ctx, {
995
+ userId,
996
+ threadId,
997
+ model: model.modelId,
998
+ provider: model.provider,
999
+ usage: result.usage,
1000
+ providerMetadata: result.providerMetadata,
1001
+ });
1002
+ }
927
1003
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
928
1004
  return args.onFinish?.(result as any);
929
1005
  },
@@ -946,15 +1022,35 @@ export class Agent<AgentTools extends ToolSet> {
946
1022
  threadId: string;
947
1023
  messageId: string;
948
1024
  result: GenerateObjectResult<unknown>;
1025
+ metadata?: Omit<MessageWithMetadata, "message">;
949
1026
  }
950
1027
  ): Promise<void> {
951
- const step = serializeObjectResult(args.result);
1028
+ const { step, messages: withoutEmbed } = serializeObjectResult(
1029
+ args.result,
1030
+ {
1031
+ model: this.options.chat.modelId,
1032
+ provider: this.options.chat.provider,
1033
+ }
1034
+ );
1035
+ const embeddings = await this.getEmbeddings([withoutEmbed[0].message]);
1036
+ const messages = embeddings?.vectors[0]
1037
+ ? [
1038
+ {
1039
+ ...withoutEmbed[0],
1040
+ embedding: {
1041
+ dimension: embeddings.dimension,
1042
+ model: embeddings.model,
1043
+ vector: embeddings.vectors[0],
1044
+ },
1045
+ },
1046
+ ]
1047
+ : withoutEmbed;
1048
+
952
1049
  await ctx.runMutation(this.component.messages.addStep, {
953
1050
  threadId: args.threadId,
954
1051
  messageId: args.messageId,
955
1052
  failPendingSteps: false,
956
- embeddings: await this.getEmbeddings([step.messages[0].message]),
957
- step,
1053
+ step: { step, messages },
958
1054
  });
959
1055
  }
960
1056
 
@@ -1001,11 +1097,45 @@ export class Agent<AgentTools extends ToolSet> {
1001
1097
  values: [text],
1002
1098
  })
1003
1099
  ).embeddings[0];
1100
+ // TODO: record usage of embeddings
1004
1101
  search.vectorModel = this.options.textEmbedding.modelId;
1005
1102
  }
1006
1103
  return search;
1007
1104
  }
1008
1105
 
1106
+ /**
1107
+ * Create a mutation that creates a thread so you can call it from a Workflow.
1108
+ * e.g.
1109
+ * ```ts
1110
+ * // in convex/foo.ts
1111
+ * export const createThread = weatherAgent.createThreadMutation();
1112
+ *
1113
+ * const workflow = new WorkflowManager(components.workflow);
1114
+ * export const myWorkflow = workflow.define({
1115
+ * args: {},
1116
+ * handler: async (step) => {
1117
+ * const { threadId } = await step.runMutation(internal.foo.createThread);
1118
+ * // use the threadId to generate text, object, etc.
1119
+ * },
1120
+ * });
1121
+ * ```
1122
+ * @returns A mutation that creates a thread.
1123
+ */
1124
+ createThreadMutation() {
1125
+ return internalMutationGeneric({
1126
+ args: {
1127
+ userId: v.optional(v.string()),
1128
+ parentThreadIds: v.optional(v.array(v.string())),
1129
+ title: v.optional(v.string()),
1130
+ summary: v.optional(v.string()),
1131
+ },
1132
+ handler: async (ctx, args) => {
1133
+ const { threadId } = await this.createThread(ctx, args);
1134
+ return { threadId };
1135
+ },
1136
+ });
1137
+ }
1138
+
1009
1139
  /**
1010
1140
  * Create an action out of this agent so you can call it from workflows or other actions
1011
1141
  * without a wrapping function.