@convex-dev/agent 0.2.6-alpha.0 → 0.2.6

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 (98) hide show
  1. package/dist/client/definePlaygroundAPI.d.ts +6 -4
  2. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  3. package/dist/client/definePlaygroundAPI.js +15 -6
  4. package/dist/client/definePlaygroundAPI.js.map +1 -1
  5. package/dist/client/index.d.ts +26 -120
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +48 -373
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/messages.d.ts +1 -1
  10. package/dist/client/messages.d.ts.map +1 -1
  11. package/dist/client/mockModel.d.ts +3 -3
  12. package/dist/client/mockModel.d.ts.map +1 -1
  13. package/dist/client/mockModel.js +22 -17
  14. package/dist/client/mockModel.js.map +1 -1
  15. package/dist/client/saveInputMessages.d.ts +20 -0
  16. package/dist/client/saveInputMessages.d.ts.map +1 -0
  17. package/dist/client/saveInputMessages.js +57 -0
  18. package/dist/client/saveInputMessages.js.map +1 -0
  19. package/dist/client/search.d.ts +110 -9
  20. package/dist/client/search.d.ts.map +1 -1
  21. package/dist/client/search.js +271 -39
  22. package/dist/client/search.js.map +1 -1
  23. package/dist/client/start.d.ts +83 -0
  24. package/dist/client/start.d.ts.map +1 -0
  25. package/dist/client/start.js +171 -0
  26. package/dist/client/start.js.map +1 -0
  27. package/dist/client/streaming.d.ts +8 -8
  28. package/dist/client/streaming.d.ts.map +1 -1
  29. package/dist/client/streaming.js +2 -1
  30. package/dist/client/streaming.js.map +1 -1
  31. package/dist/client/textStreamParts.d.ts.map +1 -1
  32. package/dist/client/textStreamParts.js +2 -9
  33. package/dist/client/textStreamParts.js.map +1 -1
  34. package/dist/client/threads.d.ts +1 -1
  35. package/dist/client/threads.d.ts.map +1 -1
  36. package/dist/client/types.d.ts +137 -5
  37. package/dist/client/types.d.ts.map +1 -1
  38. package/dist/component/_generated/api.d.ts +11 -3
  39. package/dist/component/messages.d.ts +13 -4
  40. package/dist/component/messages.d.ts.map +1 -1
  41. package/dist/component/messages.js +67 -25
  42. package/dist/component/messages.js.map +1 -1
  43. package/dist/component/schema.d.ts +2 -1643
  44. package/dist/component/schema.d.ts.map +1 -1
  45. package/dist/component/schema.js +0 -24
  46. package/dist/component/schema.js.map +1 -1
  47. package/dist/mapping.d.ts +7 -9
  48. package/dist/mapping.d.ts.map +1 -1
  49. package/dist/mapping.js +73 -7
  50. package/dist/mapping.js.map +1 -1
  51. package/dist/react/deltas.d.ts.map +1 -1
  52. package/dist/react/deltas.js +15 -5
  53. package/dist/react/deltas.js.map +1 -1
  54. package/dist/react/fromUIMessages.d.ts +13 -0
  55. package/dist/react/fromUIMessages.d.ts.map +1 -0
  56. package/dist/react/fromUIMessages.js +70 -0
  57. package/dist/react/fromUIMessages.js.map +1 -0
  58. package/dist/react/toUIMessages.d.ts +5 -2
  59. package/dist/react/toUIMessages.d.ts.map +1 -1
  60. package/dist/react/toUIMessages.js +3 -0
  61. package/dist/react/toUIMessages.js.map +1 -1
  62. package/dist/shared.d.ts +10 -0
  63. package/dist/shared.d.ts.map +1 -1
  64. package/dist/shared.js +26 -0
  65. package/dist/shared.js.map +1 -1
  66. package/dist/validators.d.ts +1640 -0
  67. package/dist/validators.d.ts.map +1 -1
  68. package/dist/validators.js +41 -0
  69. package/dist/validators.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/client/definePlaygroundAPI.ts +16 -7
  72. package/src/client/index.test.ts +11 -46
  73. package/src/client/index.ts +99 -558
  74. package/src/client/messages.ts +1 -1
  75. package/src/client/mock.json +68 -0
  76. package/src/client/mockModel.ts +34 -23
  77. package/src/client/saveInputMessages.test.ts +576 -0
  78. package/src/client/saveInputMessages.ts +100 -0
  79. package/src/client/search.test.ts +1017 -0
  80. package/src/client/search.ts +446 -68
  81. package/src/client/start.ts +313 -0
  82. package/src/client/stream.json +48 -0
  83. package/src/client/streaming.ts +3 -3
  84. package/src/client/textStreamParts.ts +2 -11
  85. package/src/client/threads.ts +1 -1
  86. package/src/client/types.ts +143 -3
  87. package/src/component/_generated/api.d.ts +11 -3
  88. package/src/component/messages.ts +73 -27
  89. package/src/component/schema.ts +1 -29
  90. package/src/mapping.ts +84 -7
  91. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  92. package/src/react/deltas.ts +18 -5
  93. package/src/react/fromUIMessages.test.ts +427 -0
  94. package/src/react/fromUIMessages.ts +85 -0
  95. package/src/react/toUIMessages.ts +21 -13
  96. package/src/shared.ts +33 -0
  97. package/src/validators.test.ts +13 -2
  98. package/src/validators.ts +48 -0
@@ -2,11 +2,9 @@ import type {
2
2
  FlexibleSchema,
3
3
  IdGenerator,
4
4
  InferSchema,
5
- ProviderOptions,
6
5
  } from "@ai-sdk/provider-utils";
7
6
  import type {
8
7
  CallSettings,
9
- EmbeddingModel,
10
8
  GenerateObjectResult,
11
9
  GenerateTextResult,
12
10
  LanguageModel,
@@ -18,7 +16,6 @@ import type {
18
16
  ToolSet,
19
17
  } from "ai";
20
18
  import {
21
- embedMany,
22
19
  generateObject,
23
20
  generateText,
24
21
  stepCountIs,
@@ -35,31 +32,28 @@ import {
35
32
  type WithoutSystemFields,
36
33
  } from "convex/server";
37
34
  import { convexToJson, v, type Value } from "convex/values";
38
- import type { MessageDoc, ThreadDoc } from "../component/schema.js";
39
35
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
40
- import {
41
- validateVectorDimension,
42
- type VectorDimension,
43
- } from "../component/vector/tables.js";
36
+ import { type VectorDimension } from "../component/vector/tables.js";
44
37
  import {
45
38
  deserializeMessage,
46
39
  serializeMessage,
47
40
  serializeNewMessagesInStep,
48
41
  serializeObjectResult,
49
42
  } from "../mapping.js";
50
- import { extractText, isTool } from "../shared.js";
43
+ import { getModelName, getProviderName } from "../shared.js";
51
44
  import {
52
45
  vMessageEmbeddings,
53
46
  vMessageWithMetadata,
54
47
  vSafeObjectArgs,
55
48
  vTextArgs,
56
49
  type Message,
50
+ type MessageDoc,
57
51
  type MessageStatus,
58
52
  type MessageWithMetadata,
59
53
  type ProviderMetadata,
60
54
  type StreamArgs,
55
+ type ThreadDoc,
61
56
  } from "../validators.js";
62
- import { wrapTools, type ToolCtx } from "./createTool.js";
63
57
  import {
64
58
  listMessages,
65
59
  saveMessages,
@@ -67,10 +61,12 @@ import {
67
61
  type SaveMessagesArgs,
68
62
  } from "./messages.js";
69
63
  import {
64
+ embedMany,
65
+ embedMessages,
70
66
  fetchContextMessages,
71
- getModelName,
72
- getProviderName,
67
+ generateAndSaveEmbeddings,
73
68
  } from "./search.js";
69
+ import { start } from "./start.js";
74
70
  import {
75
71
  DeltaStreamer,
76
72
  syncStreams,
@@ -104,12 +100,10 @@ import type {
104
100
  Thread,
105
101
  UsageHandler,
106
102
  UserActionCtx,
103
+ Config,
107
104
  } from "./types.js";
108
- import { inlineMessagesFiles } from "./files.js";
109
- import type { DataModel } from "../component/_generated/dataModel.js";
110
105
 
111
106
  export { stepCountIs } from "ai";
112
- export { vMessageDoc, vThreadDoc } from "../component/schema.js";
113
107
  export {
114
108
  deserializeMessage,
115
109
  serializeDataOrUrl,
@@ -123,18 +117,22 @@ export {
123
117
  vAssistantMessage,
124
118
  vContextOptions,
125
119
  vMessage,
120
+ vMessageDoc,
126
121
  vPaginationResult,
127
122
  vProviderMetadata,
128
123
  vStorageOptions,
129
124
  vStreamArgs,
130
125
  vSystemMessage,
126
+ vThreadDoc,
131
127
  vToolMessage,
132
128
  vUsage,
133
129
  vUserMessage,
134
130
  vSource,
135
131
  vContent,
136
- type SourcePart,
137
132
  type Message,
133
+ type MessageDoc,
134
+ type SourcePart,
135
+ type ThreadDoc,
138
136
  type Usage,
139
137
  } from "../validators.js";
140
138
  export type { ToolCtx } from "./createTool.js";
@@ -154,6 +152,10 @@ export {
154
152
  export {
155
153
  fetchContextMessages,
156
154
  filterOutOrphanedToolMessages,
155
+ fetchContextWithPrompt,
156
+ generateAndSaveEmbeddings,
157
+ embedMessages,
158
+ embedMany,
157
159
  } from "./search.js";
158
160
  export {
159
161
  abortStream,
@@ -171,94 +173,18 @@ export { extractText, isTool, sorted } from "../shared.js";
171
173
  export { createTool } from "./createTool.js";
172
174
  export type {
173
175
  AgentComponent,
176
+ Config,
174
177
  ContextOptions,
175
- MessageDoc,
176
178
  ProviderMetadata,
177
179
  RawRequestResponseHandler,
178
180
  StorageOptions,
179
181
  StreamArgs,
180
182
  SyncStreamsReturnValue,
181
183
  Thread,
182
- ThreadDoc,
183
184
  UsageHandler,
184
185
  };
185
186
  export { mockModel } from "./mockModel.js";
186
187
 
187
- // 10k characters should be more than enough for most cases, and stays under
188
- // the 8k token limit for some models.
189
- const MAX_EMBEDDING_TEXT_LENGTH = 10_000;
190
-
191
- export type Config = {
192
- /**
193
- * The LLM model to use for generating / streaming text and objects.
194
- * e.g.
195
- * import { openai } from "@ai-sdk/openai"
196
- * const myAgent = new Agent(components.agent, {
197
- * languageModel: openai.chat("gpt-4o-mini"),
198
- */
199
- languageModel?: LanguageModel;
200
- /**
201
- * The model to use for text embeddings. Optional.
202
- * If specified, it will use this for generating vector embeddings
203
- * of chats, and can opt-in to doing vector search for automatic context
204
- * on generateText, etc.
205
- * e.g.
206
- * import { openai } from "@ai-sdk/openai"
207
- * const myAgent = new Agent(components.agent, {
208
- * ...
209
- * textEmbeddingModel: openai.embedding("text-embedding-3-small")
210
- */
211
- textEmbeddingModel?: EmbeddingModel<string>;
212
- /**
213
- * Options to determine what messages are included as context in message
214
- * generation. To disable any messages automatically being added, pass:
215
- * { recentMessages: 0 }
216
- */
217
- contextOptions?: ContextOptions;
218
- /**
219
- * Determines whether messages are automatically stored when passed as
220
- * arguments or generated.
221
- */
222
- storageOptions?: StorageOptions;
223
- /**
224
- * The usage handler to use for this agent.
225
- */
226
- usageHandler?: UsageHandler;
227
- /**
228
- * Called for each LLM request/response, so you can do things like
229
- * log the raw request body or response headers to a table, or logs.
230
- */
231
- rawRequestResponseHandler?: RawRequestResponseHandler;
232
- /**
233
- * Default provider options to pass for the LLM calls.
234
- * This can be overridden at each generate/stream callsite on a per-field
235
- * basis. To clear a default setting, you'll need to pass `undefined`.
236
- */
237
- providerOptions?: ProviderOptions;
238
- /**
239
- * The default settings to use for the LLM calls.
240
- * This can be overridden at each generate/stream callsite on a per-field
241
- * basis. To clear a default setting, you'll need to pass `undefined`.
242
- */
243
- callSettings?: CallSettings;
244
- /**
245
- * The maximum number of steps to allow for a single generation.
246
- *
247
- * For example, if an agent wants to call a tool, that call and tool response
248
- * will be one step. Generating a response based on the tool call & response
249
- * will be a second step.
250
- * If it runs out of steps, it will return the last step result, which may
251
- * not be an assistant message.
252
-
253
- * This becomes the default value when `stopWhen` is not specified in the
254
- * Agent or generation callsite.
255
- * AI SDK v5 removed the `maxSteps` argument, but this is kept here for
256
- * convenience and backwards compatibility.
257
- * Defaults to 1.
258
- */
259
- maxSteps?: number;
260
- };
261
-
262
188
  export class Agent<
263
189
  /**
264
190
  * You can require that all `ctx` args to generateText & streamText
@@ -313,7 +239,7 @@ export class Agent<
313
239
  tools?: AgentTools;
314
240
  /**
315
241
  * When generating or streaming text with tools available, this
316
- * determines when to stop. Defaults to stepCountIs(1).
242
+ * determines when to stop. Defaults to the AI SDK default.
317
243
  */
318
244
  stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
319
245
  /**
@@ -518,182 +444,25 @@ export class Agent<
518
444
  fail: (reason: string) => Promise<void>;
519
445
  getSavedMessages: () => MessageDoc[];
520
446
  }> {
521
- const { threadId, ...opts } = { ...this.options, ...options };
522
- const context = await this._saveMessagesAndFetchContext(ctx, {
523
- userId: options?.userId,
524
- threadId: options?.threadId,
525
- messages: args.messages,
526
- prompt: args.prompt,
527
- promptMessageId: args.promptMessageId,
528
- ...opts,
529
- });
530
- let pendingMessageId = context.pendingMessageId;
531
- const { messages, promptMessageId, order, stepOrder, userId } = context;
532
- const savedMessages = context.savedMessages ?? [];
533
- const toolCtx = {
534
- ...(ctx as UserActionCtx & CustomCtx),
535
- userId,
536
- threadId,
537
- promptMessageId,
538
- agent: this,
539
- } satisfies ToolCtx;
540
447
  type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
541
- const tools = wrapTools(toolCtx, args.tools ?? this.options.tools) as Tools;
542
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
543
- const fail = async (reason: string) => {
544
- if (threadId && promptMessageId) {
545
- console.error(
546
- `Message failed in thread ${threadId} with promptMessageId ${promptMessageId}: ${reason}`,
547
- );
548
- }
549
- if (pendingMessageId) {
550
- await ctx.runMutation(this.component.messages.finalizeMessage, {
551
- messageId: pendingMessageId,
552
- result: { status: "failed", error: reason },
553
- });
554
- }
555
- };
556
- if (args.abortSignal) {
557
- const abortSignal = args.abortSignal;
558
- abortSignal.addEventListener(
559
- "abort",
560
- async () => {
561
- await fail(abortSignal.reason ?? "Aborted");
562
- },
563
- { once: true },
564
- );
565
- }
566
- const aiArgs = {
567
- ...this.options.callSettings,
568
- providerOptions: this.options.providerOptions,
569
- ...omit(args, ["messages", "prompt", "promptMessageId"]),
570
- model: args.model ?? this.options.languageModel,
571
- system: args.system ?? this.options.instructions,
572
- messages,
573
- stopWhen:
574
- args.stopWhen ??
575
- this.options.stopWhen ??
576
- stepCountIs(this.options.maxSteps ?? 1),
577
- tools,
578
- } as T & {
579
- model: LanguageModel;
580
- messages: ModelMessage[];
581
- tools?: TOOLS extends undefined ? AgentTools : TOOLS;
582
- } & CallSettings;
583
- if (pendingMessageId) {
584
- if (!aiArgs._internal?.generateId) {
585
- aiArgs._internal = {
586
- ...aiArgs._internal,
587
- generateId: () => pendingMessageId ?? crypto.randomUUID(),
588
- };
589
- }
590
- }
591
- let activeModel = aiArgs.model;
592
- return {
593
- args: aiArgs,
594
- order: order ?? 0,
595
- stepOrder: stepOrder ?? 0,
596
- userId,
597
- promptMessageId,
598
- getSavedMessages: () => savedMessages,
599
- updateModel: (model: LanguageModel | undefined) => {
600
- if (model) {
601
- activeModel = model;
602
- }
448
+ return start<T, Tools, CustomCtx>(
449
+ ctx,
450
+ this.component,
451
+ {
452
+ ...args,
453
+ tools: (args.tools ?? this.options.tools) as Tools,
454
+ system: args.system ?? this.options.instructions,
455
+ stopWhen: (args.stopWhen ?? this.options.stopWhen) as
456
+ | StopCondition<Tools>
457
+ | Array<StopCondition<Tools>>,
603
458
  },
604
- fail,
605
- save: async <TOOLS extends ToolSet>(
606
- toSave:
607
- | { step: StepResult<TOOLS> }
608
- | { object: GenerateObjectResult<unknown> },
609
- createPendingMessage?: boolean,
610
- ) => {
611
- if (threadId && promptMessageId && saveOutput) {
612
- const metadata = {
613
- // TODO: get up to date one when user selects mid-generation
614
- model: getModelName(activeModel),
615
- provider: getProviderName(activeModel),
616
- };
617
- const serialized =
618
- "object" in toSave
619
- ? await serializeObjectResult(
620
- ctx,
621
- this.component,
622
- toSave.object,
623
- metadata,
624
- )
625
- : await serializeNewMessagesInStep(
626
- ctx,
627
- this.component,
628
- toSave.step,
629
- metadata,
630
- );
631
- const embeddings = await this.generateEmbeddings(
632
- ctx,
633
- { userId, threadId },
634
- serialized.messages.map((m) => m.message),
635
- );
636
- if (createPendingMessage) {
637
- serialized.messages.push({
638
- message: { role: "assistant", content: [] },
639
- status: "pending",
640
- });
641
- embeddings?.vectors.push(null);
642
- }
643
- const saved = await ctx.runMutation(
644
- this.component.messages.addMessages,
645
- {
646
- userId,
647
- threadId,
648
- agentName: this.options.name,
649
- promptMessageId,
650
- pendingMessageId,
651
- messages: serialized.messages,
652
- embeddings,
653
- failPendingSteps: false,
654
- },
655
- );
656
- const lastMessage = saved.messages.at(-1)!;
657
- if (createPendingMessage) {
658
- if (lastMessage.status === "failed") {
659
- pendingMessageId = undefined;
660
- savedMessages.push(...saved.messages);
661
- await fail(
662
- lastMessage.error ??
663
- "Aborting - the pending message was marked as failed",
664
- );
665
- } else {
666
- pendingMessageId = lastMessage._id;
667
- savedMessages.push(...saved.messages.slice(0, -1));
668
- }
669
- } else {
670
- pendingMessageId = undefined;
671
- savedMessages.push(...saved.messages);
672
- }
673
- }
674
- const output = "object" in toSave ? toSave.object : toSave.step;
675
- if (this.options.rawRequestResponseHandler) {
676
- await this.options.rawRequestResponseHandler(ctx, {
677
- userId,
678
- threadId,
679
- agentName: this.options.name,
680
- request: output.request,
681
- response: output.response,
682
- });
683
- }
684
- if (opts.usageHandler && output.usage) {
685
- await opts.usageHandler(ctx, {
686
- userId,
687
- threadId,
688
- agentName: this.options.name,
689
- model: getModelName(activeModel),
690
- provider: getProviderName(activeModel),
691
- usage: output.usage,
692
- providerMetadata: output.providerMetadata,
693
- });
694
- }
459
+ {
460
+ ...this.options,
461
+ ...options,
462
+ agentName: this.options.name,
463
+ agentForToolCtx: this,
695
464
  },
696
- };
465
+ );
697
466
  }
698
467
 
699
468
  /**
@@ -853,9 +622,6 @@ export class Agent<
853
622
  await streamer?.fail(errorToString(error.error));
854
623
  return streamTextArgs.onError?.(error);
855
624
  },
856
- // onFinish: async (event) => {
857
- // return streamTextArgs.onFinish?.(event);
858
- // },
859
625
  prepareStep: async (options) => {
860
626
  const result = await streamTextArgs.prepareStep?.(options);
861
627
  if (result) {
@@ -878,12 +644,6 @@ export class Agent<
878
644
  TOOLS extends undefined ? AgentTools : TOOLS,
879
645
  PARTIAL_OUTPUT
880
646
  >;
881
- const metadata: GenerationOutputMetadata = {
882
- promptMessageId,
883
- order,
884
- savedMessages: call.getSavedMessages(),
885
- messageId: promptMessageId,
886
- };
887
647
  if (
888
648
  (typeof options?.saveStreamDeltas === "object" &&
889
649
  !options.saveStreamDeltas.returnImmediately) ||
@@ -891,6 +651,12 @@ export class Agent<
891
651
  ) {
892
652
  await result.consumeStream();
893
653
  }
654
+ const metadata: GenerationOutputMetadata = {
655
+ promptMessageId,
656
+ order,
657
+ savedMessages: call.getSavedMessages(),
658
+ messageId: promptMessageId,
659
+ };
894
660
  return Object.assign(result, metadata);
895
661
  }
896
662
 
@@ -1177,11 +943,22 @@ export class Agent<
1177
943
  args: {
1178
944
  userId: string | undefined;
1179
945
  threadId: string | undefined;
1180
- messages: (ModelMessage | Message)[];
1181
946
  /**
1182
- * If provided, it will search for messages up to and including this message.
1183
- * Note: if this is far in the past, text and vector search results may be more
1184
- * limited, as it's post-filtering the results.
947
+ * If targetMessageId is not provided, this text will be used
948
+ * for text and vector search
949
+ */
950
+ searchText?: string;
951
+ /**
952
+ * If provided, it will use this message for text/vector search (if enabled)
953
+ * and will only fetch messages up to (and including) this message's "order"
954
+ */
955
+ targetMessageId?: string;
956
+ /**
957
+ * @deprecated use searchText and targetMessageId instead
958
+ */
959
+ messages?: (ModelMessage | Message)[];
960
+ /**
961
+ * @deprecated use targetMessageId instead
1185
962
  */
1186
963
  upToAndIncludingMessageId?: string;
1187
964
  contextOptions: ContextOptions | undefined;
@@ -1203,7 +980,9 @@ export class Agent<
1203
980
  );
1204
981
  return {
1205
982
  embedding: (
1206
- await this.doEmbed(ctx, {
983
+ await embedMany(ctx, {
984
+ ...this.options,
985
+ agentName: this.options.name,
1207
986
  userId: args.userId,
1208
987
  threadId: args.threadId,
1209
988
  values: [text],
@@ -1258,51 +1037,21 @@ export class Agent<
1258
1037
  */
1259
1038
  async generateEmbeddings(
1260
1039
  ctx: RunActionCtx,
1261
- {
1262
- userId,
1263
- threadId,
1264
- }: { userId: string | undefined; threadId: string | undefined },
1040
+ args: { userId: string | undefined; threadId: string | undefined },
1265
1041
  messages: (ModelMessage | Message)[],
1266
- ) {
1267
- if (!this.options.textEmbeddingModel) {
1268
- return undefined;
1269
- }
1270
- let embeddings:
1271
- | {
1272
- vectors: (number[] | null)[];
1273
- dimension: VectorDimension;
1274
- model: string;
1275
- }
1276
- | undefined;
1277
- const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
1278
- // Find the indexes of the messages that have text.
1279
- const textIndexes = messageTexts
1280
- .map((t, i) => (t ? i : undefined))
1281
- .filter((i) => i !== undefined);
1282
- if (textIndexes.length === 0) {
1283
- return undefined;
1284
- }
1285
- const values = messageTexts
1286
- .map((t) => t && t.trim().slice(0, MAX_EMBEDDING_TEXT_LENGTH))
1287
- .filter((t): t is string => !!t);
1288
- // Then embed those messages.
1289
- const textEmbeddings = await this.doEmbed(ctx, {
1290
- userId,
1291
- threadId,
1292
- values,
1293
- });
1294
- // Then assemble the embeddings into a single array with nulls for the messages without text.
1295
- const embeddingsOrNull = Array(messages.length).fill(null);
1296
- textIndexes.forEach((i, j) => {
1297
- embeddingsOrNull[i] = textEmbeddings.embeddings[j];
1298
- });
1299
- if (textEmbeddings.embeddings.length > 0) {
1300
- const dimension = textEmbeddings.embeddings[0].length;
1301
- validateVectorDimension(dimension);
1302
- const model = getModelName(this.options.textEmbeddingModel);
1303
- embeddings = { vectors: embeddingsOrNull, dimension, model };
1304
- }
1305
- return embeddings;
1042
+ ): Promise<
1043
+ | {
1044
+ vectors: (number[] | null)[];
1045
+ dimension: VectorDimension;
1046
+ model: string;
1047
+ }
1048
+ | undefined
1049
+ > {
1050
+ return embedMessages(
1051
+ ctx,
1052
+ { ...args, ...this.options, agentName: this.options.name },
1053
+ messages,
1054
+ );
1306
1055
  }
1307
1056
 
1308
1057
  /**
@@ -1329,10 +1078,6 @@ export class Agent<
1329
1078
  .join(", "),
1330
1079
  );
1331
1080
  }
1332
- await this._generateAndSaveEmbeddings(ctx, messages);
1333
- }
1334
-
1335
- async _generateAndSaveEmbeddings(ctx: RunActionCtx, messages: MessageDoc[]) {
1336
1081
  if (messages.some((m) => !m.message)) {
1337
1082
  throw new Error(
1338
1083
  "Some messages don't have a message: " +
@@ -1342,45 +1087,24 @@ export class Agent<
1342
1087
  .join(", "),
1343
1088
  );
1344
1089
  }
1345
- const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
1346
- if (messagesMissingEmbeddings.length === 0) {
1347
- return;
1090
+ const { textEmbeddingModel } = this.options;
1091
+ if (!textEmbeddingModel) {
1092
+ throw new Error(
1093
+ "No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.",
1094
+ );
1348
1095
  }
1349
- const embeddings = await this.generateEmbeddings(
1096
+ await generateAndSaveEmbeddings(
1350
1097
  ctx,
1098
+ this.component,
1351
1099
  {
1352
- userId: messagesMissingEmbeddings[0]!.userId,
1353
- threadId: messagesMissingEmbeddings[0]!.threadId,
1100
+ ...this.options,
1101
+ agentName: this.options.name,
1102
+ threadId: messages[0].threadId,
1103
+ userId: messages[0].userId,
1104
+ textEmbeddingModel,
1354
1105
  },
1355
- messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1106
+ messages,
1356
1107
  );
1357
- if (!embeddings) {
1358
- if (!this.options.textEmbeddingModel) {
1359
- throw new Error(
1360
- "No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.",
1361
- );
1362
- }
1363
- throw new Error(
1364
- "No embeddings were generated for these messages: " +
1365
- messagesMissingEmbeddings.map((m) => m!._id).join(", "),
1366
- );
1367
- }
1368
- await ctx.runMutation(this.component.vector.index.insertBatch, {
1369
- vectorDimension: embeddings.dimension,
1370
- vectors: messagesMissingEmbeddings
1371
- .map((m, i) => ({
1372
- messageId: m!._id,
1373
- model: embeddings.model,
1374
- table: "messages",
1375
- userId: m.userId,
1376
- threadId: m.threadId,
1377
- vector: embeddings.vectors[i],
1378
- }))
1379
- .filter(
1380
- (v): v is Extract<typeof v, { vector: number[] }> =>
1381
- v.vector !== null,
1382
- ),
1383
- });
1384
1108
  }
1385
1109
 
1386
1110
  /**
@@ -1677,189 +1401,6 @@ export class Agent<
1677
1401
  });
1678
1402
  }
1679
1403
 
1680
- async _saveMessagesAndFetchContext(
1681
- ctx: RunActionCtx,
1682
- {
1683
- userId: argsUserId,
1684
- threadId,
1685
- contextOptions,
1686
- storageOptions,
1687
- ...args
1688
- }: {
1689
- prompt: string | (ModelMessage | Message)[] | undefined;
1690
- messages: (ModelMessage | Message)[] | undefined;
1691
- promptMessageId: string | undefined;
1692
- userId: string | null | undefined;
1693
- threadId: string | undefined;
1694
- } & Options,
1695
- ): Promise<{
1696
- messages: ModelMessage[];
1697
- userId: string | undefined;
1698
- promptMessageId: string | undefined;
1699
- pendingMessageId: string | undefined;
1700
- order: number | undefined;
1701
- stepOrder: number | undefined;
1702
- savedMessages: MessageDoc[] | undefined;
1703
- }> {
1704
- // If only a promptMessageId is provided, this will be empty.
1705
- const messages: (ModelMessage | Message)[] = args.messages ?? [];
1706
- const promptArray: ModelMessage[] = !args.prompt
1707
- ? []
1708
- : Array.isArray(args.prompt)
1709
- ? args.prompt.map((p) => deserializeMessage(p))
1710
- : [{ role: "user", content: args.prompt }];
1711
- const userId =
1712
- argsUserId ??
1713
- (threadId &&
1714
- (await ctx.runQuery(this.component.threads.getThread, { threadId }))
1715
- ?.userId) ??
1716
- undefined;
1717
- // If only a messageId is provided, this will add that message to the end.
1718
- const contextMessages: MessageDoc[] = await this.fetchContextMessages(ctx, {
1719
- userId,
1720
- threadId,
1721
- upToAndIncludingMessageId: args.promptMessageId,
1722
- messages,
1723
- contextOptions,
1724
- });
1725
- // If it was a promptMessageId, pop it off context messages
1726
- // and add to the end of messages.
1727
- const promptMessageIndex = args.promptMessageId
1728
- ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
1729
- : -1;
1730
- const promptMessage: MessageDoc | undefined =
1731
- promptMessageIndex !== -1
1732
- ? contextMessages.splice(promptMessageIndex, 1)[0]
1733
- : undefined;
1734
-
1735
- let promptMessageId = promptMessage?._id;
1736
- let order = promptMessage?.order;
1737
- let stepOrder = promptMessage?.stepOrder;
1738
- let savedMessages = undefined;
1739
- let pendingMessageId = undefined;
1740
- if (threadId && storageOptions?.saveMessages !== "none") {
1741
- let saved: { messages: MessageDoc[] };
1742
- if (
1743
- messages.length + promptArray.length &&
1744
- // If it was a promptMessageId, we don't want to save it again.
1745
- (!args.promptMessageId || storageOptions?.saveMessages === "all")
1746
- ) {
1747
- const saveAll = storageOptions?.saveMessages === "all";
1748
- const coreMessages: (ModelMessage | Message)[] = [
1749
- ...messages,
1750
- ...promptArray,
1751
- ];
1752
- const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
1753
- const metadata = Array.from({ length: toSave.length }, () => ({}));
1754
- saved = await this.saveMessages(ctx, {
1755
- threadId,
1756
- userId,
1757
- messages: [...toSave, { role: "assistant", content: [] }],
1758
- metadata: [...metadata, { status: "pending" }],
1759
- // TODO: sanity check
1760
- failPendingSteps: !!args.promptMessageId,
1761
- });
1762
- promptMessageId = saved.messages.at(-2)!._id;
1763
- } else {
1764
- saved = await this.saveMessages(ctx, {
1765
- threadId,
1766
- userId,
1767
- messages: [{ role: "assistant", content: [] }],
1768
- metadata: [{ status: "pending" }],
1769
- failPendingSteps: !!args.promptMessageId,
1770
- });
1771
- }
1772
- pendingMessageId = saved.messages.at(-1)!._id;
1773
- order = saved.messages.at(-1)!.order;
1774
- stepOrder = saved.messages.at(-1)!.stepOrder;
1775
- // Don't return the pending message
1776
- savedMessages = saved.messages.slice(0, -1);
1777
- }
1778
-
1779
- if (promptMessage?.message) {
1780
- if (!args.prompt) {
1781
- // If they override the prompt, we skip the existing prompt message.
1782
- messages.push(promptMessage.message);
1783
- }
1784
- // Lazily generate embeddings for the prompt message, if it doesn't have
1785
- // embeddings yet. This can happen if the message was saved in a mutation
1786
- // where the LLM is not available.
1787
- if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
1788
- await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
1789
- }
1790
- }
1791
-
1792
- const prePrompt = contextMessages.map((m) => m.message).filter((m) => !!m);
1793
- let existingResponses: (ModelMessage | Message)[] = [];
1794
- if (promptMessageIndex !== -1) {
1795
- // pull any messages that already responded to the prompt off
1796
- // and add them after the prompt
1797
- existingResponses = prePrompt.splice(promptMessageIndex);
1798
- }
1799
-
1800
- let processedMessages: ModelMessage[] = [
1801
- ...prePrompt,
1802
- ...messages,
1803
- ...promptArray,
1804
- ...existingResponses,
1805
- ].map((m) => deserializeMessage(m));
1806
-
1807
- // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
1808
- if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
1809
- processedMessages = await inlineMessagesFiles(processedMessages);
1810
- }
1811
-
1812
- return {
1813
- messages: processedMessages,
1814
- userId,
1815
- promptMessageId,
1816
- pendingMessageId,
1817
- savedMessages,
1818
- order,
1819
- stepOrder,
1820
- };
1821
- }
1822
-
1823
- async doEmbed(
1824
- ctx: RunActionCtx,
1825
- options: {
1826
- userId: string | undefined;
1827
- threadId: string | undefined;
1828
- values: string[];
1829
- abortSignal?: AbortSignal;
1830
- headers?: Record<string, string>;
1831
- },
1832
- ): Promise<{ embeddings: number[][] }> {
1833
- const embeddingModel = this.options.textEmbeddingModel;
1834
- assert(
1835
- embeddingModel,
1836
- "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with",
1837
- );
1838
- const result = await embedMany({
1839
- ...this.options.callSettings,
1840
- model: embeddingModel,
1841
- values: options.values,
1842
- abortSignal: options.abortSignal,
1843
- headers: options.headers,
1844
- });
1845
- if (this.options.usageHandler && result.usage) {
1846
- await this.options.usageHandler(ctx, {
1847
- userId: options.userId,
1848
- threadId: options.threadId,
1849
- agentName: this.options.name,
1850
- model: getModelName(embeddingModel),
1851
- provider: getProviderName(embeddingModel),
1852
- providerMetadata: undefined,
1853
- usage: {
1854
- inputTokens: result.usage.tokens,
1855
- outputTokens: 0,
1856
- totalTokens: result.usage.tokens,
1857
- },
1858
- });
1859
- }
1860
- return { embeddings: result.embeddings };
1861
- }
1862
-
1863
1404
  /**
1864
1405
  * WORKFLOW UTILITIES
1865
1406
  */
@@ -1925,22 +1466,22 @@ export class Agent<
1925
1466
  handler: async (ctx_, args) => {
1926
1467
  const stream =
1927
1468
  args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1928
- const targetArgs = { userId: args.userId, threadId: args.threadId };
1469
+ const { userId, threadId, prompt, messages, maxSteps, ...rest } = args;
1470
+ const targetArgs = { userId, threadId };
1929
1471
  const llmArgs = {
1930
- stopWhen: spec?.stopWhen ?? this.options.stopWhen,
1472
+ stopWhen: spec?.stopWhen,
1931
1473
  ...overrides,
1932
- ...omit(args, ["storageOptions", "contextOptions"]),
1933
- messages: args.messages?.map(deserializeMessage),
1934
- prompt: Array.isArray(args.prompt)
1935
- ? args.prompt.map(deserializeMessage)
1936
- : args.prompt,
1474
+ ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
1475
+ messages: messages?.map(deserializeMessage),
1476
+ prompt: Array.isArray(prompt)
1477
+ ? prompt.map(deserializeMessage)
1478
+ : prompt,
1937
1479
  toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1938
1480
  } satisfies StreamingTextArgs<AgentTools>;
1939
- if (args.maxSteps) {
1940
- llmArgs.stopWhen = stepCountIs(args.maxSteps);
1481
+ if (maxSteps) {
1482
+ llmArgs.stopWhen = stepCountIs(maxSteps);
1941
1483
  }
1942
1484
  const opts = {
1943
- ...this.options,
1944
1485
  ...pick(spec, ["contextOptions", "storageOptions"]),
1945
1486
  ...pick(args, ["contextOptions", "storageOptions"]),
1946
1487
  saveStreamDeltas: stream,
@@ -1964,7 +1505,7 @@ export class Agent<
1964
1505
  promptMessageId: result.promptMessageId,
1965
1506
  order: result.order,
1966
1507
  finishReason: await result.finishReason,
1967
- warnings: result.warnings,
1508
+ warnings: await result.warnings,
1968
1509
  savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1969
1510
  };
1970
1511
  } else {
@@ -1994,7 +1535,7 @@ export class Agent<
1994
1535
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1995
1536
  * and stopWhen.
1996
1537
  */
1997
- asObjectAction<T>(
1538
+ asObjectAction<T, DataModel extends GenericDataModel>(
1998
1539
  objectArgs: Omit<
1999
1540
  Parameters<typeof generateObject<FlexibleSchema<T>>>[0],
2000
1541
  "model"