@convex-dev/agent 0.2.6-alpha.1 → 0.2.7

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 (101) hide show
  1. package/dist/client/createTool.js +1 -1
  2. package/dist/client/createTool.js.map +1 -1
  3. package/dist/client/definePlaygroundAPI.d.ts +6 -4
  4. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  5. package/dist/client/definePlaygroundAPI.js +15 -6
  6. package/dist/client/definePlaygroundAPI.js.map +1 -1
  7. package/dist/client/index.d.ts +55 -122
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +47 -371
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/messages.d.ts +1 -1
  12. package/dist/client/messages.d.ts.map +1 -1
  13. package/dist/client/mockModel.d.ts +5 -3
  14. package/dist/client/mockModel.d.ts.map +1 -1
  15. package/dist/client/mockModel.js +33 -24
  16. package/dist/client/mockModel.js.map +1 -1
  17. package/dist/client/saveInputMessages.d.ts +20 -0
  18. package/dist/client/saveInputMessages.d.ts.map +1 -0
  19. package/dist/client/saveInputMessages.js +57 -0
  20. package/dist/client/saveInputMessages.js.map +1 -0
  21. package/dist/client/search.d.ts +110 -9
  22. package/dist/client/search.d.ts.map +1 -1
  23. package/dist/client/search.js +271 -39
  24. package/dist/client/search.js.map +1 -1
  25. package/dist/client/start.d.ts +84 -0
  26. package/dist/client/start.d.ts.map +1 -0
  27. package/dist/client/start.js +171 -0
  28. package/dist/client/start.js.map +1 -0
  29. package/dist/client/streaming.d.ts +8 -8
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js +2 -1
  32. package/dist/client/streaming.js.map +1 -1
  33. package/dist/client/textStreamParts.d.ts.map +1 -1
  34. package/dist/client/textStreamParts.js +2 -9
  35. package/dist/client/textStreamParts.js.map +1 -1
  36. package/dist/client/threads.d.ts +1 -1
  37. package/dist/client/threads.d.ts.map +1 -1
  38. package/dist/client/types.d.ts +147 -17
  39. package/dist/client/types.d.ts.map +1 -1
  40. package/dist/component/_generated/api.d.ts +11 -3
  41. package/dist/component/messages.d.ts +13 -4
  42. package/dist/component/messages.d.ts.map +1 -1
  43. package/dist/component/messages.js +67 -25
  44. package/dist/component/messages.js.map +1 -1
  45. package/dist/component/schema.d.ts +2 -1643
  46. package/dist/component/schema.d.ts.map +1 -1
  47. package/dist/component/schema.js +0 -24
  48. package/dist/component/schema.js.map +1 -1
  49. package/dist/mapping.d.ts +5 -8
  50. package/dist/mapping.d.ts.map +1 -1
  51. package/dist/mapping.js +72 -6
  52. package/dist/mapping.js.map +1 -1
  53. package/dist/react/deltas.d.ts.map +1 -1
  54. package/dist/react/deltas.js +1 -0
  55. package/dist/react/deltas.js.map +1 -1
  56. package/dist/react/fromUIMessages.d.ts +13 -0
  57. package/dist/react/fromUIMessages.d.ts.map +1 -0
  58. package/dist/react/fromUIMessages.js +75 -0
  59. package/dist/react/fromUIMessages.js.map +1 -0
  60. package/dist/react/toUIMessages.d.ts +5 -2
  61. package/dist/react/toUIMessages.d.ts.map +1 -1
  62. package/dist/react/toUIMessages.js +3 -0
  63. package/dist/react/toUIMessages.js.map +1 -1
  64. package/dist/shared.d.ts +10 -0
  65. package/dist/shared.d.ts.map +1 -1
  66. package/dist/shared.js +26 -0
  67. package/dist/shared.js.map +1 -1
  68. package/dist/validators.d.ts +1640 -0
  69. package/dist/validators.d.ts.map +1 -1
  70. package/dist/validators.js +41 -0
  71. package/dist/validators.js.map +1 -1
  72. package/package.json +4 -4
  73. package/src/client/createTool.ts +1 -1
  74. package/src/client/definePlaygroundAPI.ts +16 -7
  75. package/src/client/index.test.ts +11 -46
  76. package/src/client/index.ts +128 -573
  77. package/src/client/messages.ts +1 -1
  78. package/src/client/mock.json +68 -0
  79. package/src/client/mockModel.ts +47 -31
  80. package/src/client/saveInputMessages.test.ts +576 -0
  81. package/src/client/saveInputMessages.ts +100 -0
  82. package/src/client/search.test.ts +1017 -0
  83. package/src/client/search.ts +446 -68
  84. package/src/client/start.ts +315 -0
  85. package/src/client/stream.json +48 -0
  86. package/src/client/streaming.ts +3 -3
  87. package/src/client/textStreamParts.ts +2 -11
  88. package/src/client/threads.ts +1 -1
  89. package/src/client/types.ts +153 -22
  90. package/src/component/_generated/api.d.ts +11 -3
  91. package/src/component/messages.ts +73 -27
  92. package/src/component/schema.ts +1 -29
  93. package/src/mapping.ts +83 -6
  94. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  95. package/src/react/deltas.ts +1 -0
  96. package/src/react/fromUIMessages.test.ts +467 -0
  97. package/src/react/fromUIMessages.ts +90 -0
  98. package/src/react/toUIMessages.ts +21 -13
  99. package/src/shared.ts +33 -0
  100. package/src/validators.test.ts +13 -2
  101. 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
  /**
@@ -501,6 +427,7 @@ export class Agent<
501
427
  args: T & {
502
428
  system?: string;
503
429
  model: LanguageModel;
430
+ prompt?: never;
504
431
  messages: ModelMessage[];
505
432
  tools?: TOOLS extends undefined ? AgentTools : TOOLS;
506
433
  } & CallSettings;
@@ -518,182 +445,25 @@ export class Agent<
518
445
  fail: (reason: string) => Promise<void>;
519
446
  getSavedMessages: () => MessageDoc[];
520
447
  }> {
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
448
  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
- }
449
+ return start<T, Tools, CustomCtx>(
450
+ ctx,
451
+ this.component,
452
+ {
453
+ ...args,
454
+ tools: (args.tools ?? this.options.tools) as Tools,
455
+ system: args.system ?? this.options.instructions,
456
+ stopWhen: (args.stopWhen ?? this.options.stopWhen) as
457
+ | StopCondition<Tools>
458
+ | Array<StopCondition<Tools>>,
603
459
  },
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
- }
460
+ {
461
+ ...this.options,
462
+ ...options,
463
+ agentName: this.options.name,
464
+ agentForToolCtx: this,
695
465
  },
696
- };
466
+ );
697
467
  }
698
468
 
699
469
  /**
@@ -715,7 +485,15 @@ export class Agent<
715
485
  >(
716
486
  ctx: ActionCtx & CustomCtx,
717
487
  threadOpts: { userId?: string | null; threadId?: string },
718
- generateTextArgs: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>,
488
+ generateTextArgs: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL> & {
489
+ /**
490
+ * If provided, this message will be used as the "prompt" for the LLM call,
491
+ * instead of the prompt or messages.
492
+ * This is useful if you want to first save a user message, then use it as
493
+ * the prompt for the LLM call in another call.
494
+ */
495
+ promptMessageId?: string;
496
+ },
719
497
  options?: Options,
720
498
  ): Promise<
721
499
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
@@ -778,7 +556,15 @@ export class Agent<
778
556
  TOOLS,
779
557
  OUTPUT,
780
558
  PARTIAL_OUTPUT
781
- >,
559
+ > & {
560
+ /**
561
+ * If provided, this message will be used as the "prompt" for the LLM call,
562
+ * instead of the prompt or messages.
563
+ * This is useful if you want to first save a user message, then use it as
564
+ * the prompt for the LLM call in another call.
565
+ */
566
+ promptMessageId?: string;
567
+ },
782
568
  /**
783
569
  * The {@link ContextOptions} and {@link StorageOptions}
784
570
  * options to use for fetching contextual messages and saving input/output messages.
@@ -853,9 +639,6 @@ export class Agent<
853
639
  await streamer?.fail(errorToString(error.error));
854
640
  return streamTextArgs.onError?.(error);
855
641
  },
856
- // onFinish: async (event) => {
857
- // return streamTextArgs.onFinish?.(event);
858
- // },
859
642
  prepareStep: async (options) => {
860
643
  const result = await streamTextArgs.prepareStep?.(options);
861
644
  if (result) {
@@ -878,12 +661,6 @@ export class Agent<
878
661
  TOOLS extends undefined ? AgentTools : TOOLS,
879
662
  PARTIAL_OUTPUT
880
663
  >;
881
- const metadata: GenerationOutputMetadata = {
882
- promptMessageId,
883
- order,
884
- savedMessages: call.getSavedMessages(),
885
- messageId: promptMessageId,
886
- };
887
664
  if (
888
665
  (typeof options?.saveStreamDeltas === "object" &&
889
666
  !options.saveStreamDeltas.returnImmediately) ||
@@ -891,6 +668,12 @@ export class Agent<
891
668
  ) {
892
669
  await result.consumeStream();
893
670
  }
671
+ const metadata: GenerationOutputMetadata = {
672
+ promptMessageId,
673
+ order,
674
+ savedMessages: call.getSavedMessages(),
675
+ messageId: promptMessageId,
676
+ };
894
677
  return Object.assign(result, metadata);
895
678
  }
896
679
 
@@ -915,7 +698,15 @@ export class Agent<
915
698
  /**
916
699
  * The arguments to the generateObject function, similar to the ai.generateObject function.
917
700
  */
918
- generateObjectArgs: GenerateObjectArgs<SCHEMA, OUTPUT, RESULT>,
701
+ generateObjectArgs: GenerateObjectArgs<SCHEMA, OUTPUT, RESULT> & {
702
+ /**
703
+ * If provided, this message will be used as the "prompt" for the LLM call,
704
+ * instead of the prompt or messages.
705
+ * This is useful if you want to first save a user message, then use it as
706
+ * the prompt for the LLM call in another call.
707
+ */
708
+ promptMessageId?: string;
709
+ },
919
710
  /**
920
711
  * The {@link ContextOptions} and {@link StorageOptions}
921
712
  * options to use for fetching contextual messages and saving input/output messages.
@@ -973,15 +764,6 @@ export class Agent<
973
764
  * the prompt for the LLM call in another call.
974
765
  */
975
766
  promptMessageId?: string;
976
- /**
977
- * The model to use for the LLM calls. This will override the model specified
978
- * in the Agent constructor.
979
- */
980
- model?: LanguageModel;
981
- /**
982
- * The tools to use for the tool calls. This will override tools specified
983
- * in the Agent constructor or createThread / continueThread.
984
- */
985
767
  },
986
768
  /**
987
769
  * The {@link ContextOptions} and {@link StorageOptions}
@@ -1177,11 +959,22 @@ export class Agent<
1177
959
  args: {
1178
960
  userId: string | undefined;
1179
961
  threadId: string | undefined;
1180
- messages: (ModelMessage | Message)[];
1181
962
  /**
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.
963
+ * If targetMessageId is not provided, this text will be used
964
+ * for text and vector search
965
+ */
966
+ searchText?: string;
967
+ /**
968
+ * If provided, it will use this message for text/vector search (if enabled)
969
+ * and will only fetch messages up to (and including) this message's "order"
970
+ */
971
+ targetMessageId?: string;
972
+ /**
973
+ * @deprecated use searchText and targetMessageId instead
974
+ */
975
+ messages?: (ModelMessage | Message)[];
976
+ /**
977
+ * @deprecated use targetMessageId instead
1185
978
  */
1186
979
  upToAndIncludingMessageId?: string;
1187
980
  contextOptions: ContextOptions | undefined;
@@ -1203,7 +996,9 @@ export class Agent<
1203
996
  );
1204
997
  return {
1205
998
  embedding: (
1206
- await this.doEmbed(ctx, {
999
+ await embedMany(ctx, {
1000
+ ...this.options,
1001
+ agentName: this.options.name,
1207
1002
  userId: args.userId,
1208
1003
  threadId: args.threadId,
1209
1004
  values: [text],
@@ -1258,51 +1053,21 @@ export class Agent<
1258
1053
  */
1259
1054
  async generateEmbeddings(
1260
1055
  ctx: RunActionCtx,
1261
- {
1262
- userId,
1263
- threadId,
1264
- }: { userId: string | undefined; threadId: string | undefined },
1056
+ args: { userId: string | undefined; threadId: string | undefined },
1265
1057
  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;
1058
+ ): Promise<
1059
+ | {
1060
+ vectors: (number[] | null)[];
1061
+ dimension: VectorDimension;
1062
+ model: string;
1063
+ }
1064
+ | undefined
1065
+ > {
1066
+ return embedMessages(
1067
+ ctx,
1068
+ { ...args, ...this.options, agentName: this.options.name },
1069
+ messages,
1070
+ );
1306
1071
  }
1307
1072
 
1308
1073
  /**
@@ -1329,10 +1094,6 @@ export class Agent<
1329
1094
  .join(", "),
1330
1095
  );
1331
1096
  }
1332
- await this._generateAndSaveEmbeddings(ctx, messages);
1333
- }
1334
-
1335
- async _generateAndSaveEmbeddings(ctx: RunActionCtx, messages: MessageDoc[]) {
1336
1097
  if (messages.some((m) => !m.message)) {
1337
1098
  throw new Error(
1338
1099
  "Some messages don't have a message: " +
@@ -1342,45 +1103,24 @@ export class Agent<
1342
1103
  .join(", "),
1343
1104
  );
1344
1105
  }
1345
- const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
1346
- if (messagesMissingEmbeddings.length === 0) {
1347
- return;
1106
+ const { textEmbeddingModel } = this.options;
1107
+ if (!textEmbeddingModel) {
1108
+ throw new Error(
1109
+ "No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.",
1110
+ );
1348
1111
  }
1349
- const embeddings = await this.generateEmbeddings(
1112
+ await generateAndSaveEmbeddings(
1350
1113
  ctx,
1114
+ this.component,
1351
1115
  {
1352
- userId: messagesMissingEmbeddings[0]!.userId,
1353
- threadId: messagesMissingEmbeddings[0]!.threadId,
1116
+ ...this.options,
1117
+ agentName: this.options.name,
1118
+ threadId: messages[0].threadId,
1119
+ userId: messages[0].userId,
1120
+ textEmbeddingModel,
1354
1121
  },
1355
- messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1122
+ messages,
1356
1123
  );
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
1124
  }
1385
1125
 
1386
1126
  /**
@@ -1677,189 +1417,6 @@ export class Agent<
1677
1417
  });
1678
1418
  }
1679
1419
 
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
1420
  /**
1864
1421
  * WORKFLOW UTILITIES
1865
1422
  */
@@ -1925,19 +1482,20 @@ export class Agent<
1925
1482
  handler: async (ctx_, args) => {
1926
1483
  const stream =
1927
1484
  args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1928
- const targetArgs = { userId: args.userId, threadId: args.threadId };
1485
+ const { userId, threadId, prompt, messages, maxSteps, ...rest } = args;
1486
+ const targetArgs = { userId, threadId };
1929
1487
  const llmArgs = {
1930
- stopWhen: spec?.stopWhen ?? this.options.stopWhen,
1488
+ stopWhen: spec?.stopWhen,
1931
1489
  ...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,
1490
+ ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
1491
+ messages: messages?.map(deserializeMessage),
1492
+ prompt: Array.isArray(prompt)
1493
+ ? prompt.map(deserializeMessage)
1494
+ : prompt,
1937
1495
  toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1938
1496
  } satisfies StreamingTextArgs<AgentTools>;
1939
- if (args.maxSteps) {
1940
- llmArgs.stopWhen = stepCountIs(args.maxSteps);
1497
+ if (maxSteps) {
1498
+ llmArgs.stopWhen = stepCountIs(maxSteps);
1941
1499
  }
1942
1500
  const opts = {
1943
1501
  ...pick(spec, ["contextOptions", "storageOptions"]),
@@ -1993,11 +1551,8 @@ export class Agent<
1993
1551
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1994
1552
  * and stopWhen.
1995
1553
  */
1996
- asObjectAction<T>(
1997
- objectArgs: Omit<
1998
- Parameters<typeof generateObject<FlexibleSchema<T>>>[0],
1999
- "model"
2000
- >,
1554
+ asObjectAction<T, DataModel extends GenericDataModel>(
1555
+ objectArgs: GenerateObjectArgs<FlexibleSchema<T>>,
2001
1556
  options?: Options & MaybeCustomCtx<CustomCtx, DataModel, AgentTools>,
2002
1557
  ) {
2003
1558
  return internalActionGeneric({
@@ -2014,7 +1569,7 @@ export class Agent<
2014
1569
  prompt: Array.isArray(args.prompt)
2015
1570
  ? args.prompt.map(deserializeMessage)
2016
1571
  : args.prompt,
2017
- } as Omit<Parameters<typeof generateObject>[0], "model">;
1572
+ } as GenerateObjectArgs<FlexibleSchema<T>>;
2018
1573
  const ctx = (
2019
1574
  options?.customCtx
2020
1575
  ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }