@convex-dev/agent 0.1.6 → 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -12
- package/dist/commonjs/client/_generated/_ignore.d.ts +1 -0
- package/dist/commonjs/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/commonjs/client/_generated/_ignore.js +3 -0
- package/dist/commonjs/client/_generated/_ignore.js.map +1 -0
- package/dist/commonjs/client/files.d.ts +57 -0
- package/dist/commonjs/client/files.d.ts.map +1 -0
- package/dist/commonjs/client/files.js +124 -0
- package/dist/commonjs/client/files.js.map +1 -0
- package/dist/commonjs/client/index.d.ts +487 -318
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +652 -550
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/client/types.d.ts +35 -5
- package/dist/commonjs/client/types.d.ts.map +1 -1
- package/dist/commonjs/component/_generated/api.d.ts +47 -711
- package/dist/commonjs/component/files.d.ts +66 -21
- package/dist/commonjs/component/files.d.ts.map +1 -1
- package/dist/commonjs/component/files.js +71 -26
- package/dist/commonjs/component/files.js.map +1 -1
- package/dist/commonjs/component/messages.d.ts +31 -499
- package/dist/commonjs/component/messages.d.ts.map +1 -1
- package/dist/commonjs/component/messages.js +45 -125
- package/dist/commonjs/component/messages.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +949 -3785
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +9 -25
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/streams.d.ts +3 -3
- package/dist/commonjs/mapping.d.ts +28 -19
- package/dist/commonjs/mapping.d.ts.map +1 -1
- package/dist/commonjs/mapping.js +136 -81
- package/dist/commonjs/mapping.js.map +1 -1
- package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
- package/dist/commonjs/react/toUIMessages.js +12 -6
- package/dist/commonjs/react/toUIMessages.js.map +1 -1
- package/dist/commonjs/react/usePaginatedQuery.d.ts.map +1 -1
- package/dist/commonjs/react/usePaginatedQuery.js +0 -19
- package/dist/commonjs/react/usePaginatedQuery.js.map +1 -1
- package/dist/commonjs/validators.d.ts +851 -3388
- package/dist/commonjs/validators.d.ts.map +1 -1
- package/dist/commonjs/validators.js +11 -37
- package/dist/commonjs/validators.js.map +1 -1
- package/dist/commonjs.tsbuildinfo +1 -1
- package/dist/esm/client/_generated/_ignore.d.ts +1 -0
- package/dist/esm/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/esm/client/_generated/_ignore.js +3 -0
- package/dist/esm/client/_generated/_ignore.js.map +1 -0
- package/dist/esm/client/files.d.ts +57 -0
- package/dist/esm/client/files.d.ts.map +1 -0
- package/dist/esm/client/files.js +124 -0
- package/dist/esm/client/files.js.map +1 -0
- package/dist/esm/client/index.d.ts +487 -318
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +652 -550
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +35 -5
- package/dist/esm/client/types.d.ts.map +1 -1
- package/dist/esm/component/_generated/api.d.ts +47 -711
- package/dist/esm/component/files.d.ts +66 -21
- package/dist/esm/component/files.d.ts.map +1 -1
- package/dist/esm/component/files.js +71 -26
- package/dist/esm/component/files.js.map +1 -1
- package/dist/esm/component/messages.d.ts +31 -499
- package/dist/esm/component/messages.d.ts.map +1 -1
- package/dist/esm/component/messages.js +45 -125
- package/dist/esm/component/messages.js.map +1 -1
- package/dist/esm/component/schema.d.ts +949 -3785
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +9 -25
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/streams.d.ts +3 -3
- package/dist/esm/mapping.d.ts +28 -19
- package/dist/esm/mapping.d.ts.map +1 -1
- package/dist/esm/mapping.js +136 -81
- package/dist/esm/mapping.js.map +1 -1
- package/dist/esm/react/toUIMessages.d.ts.map +1 -1
- package/dist/esm/react/toUIMessages.js +12 -6
- package/dist/esm/react/toUIMessages.js.map +1 -1
- package/dist/esm/react/usePaginatedQuery.d.ts.map +1 -1
- package/dist/esm/react/usePaginatedQuery.js +0 -19
- package/dist/esm/react/usePaginatedQuery.js.map +1 -1
- package/dist/esm/validators.d.ts +851 -3388
- package/dist/esm/validators.d.ts.map +1 -1
- package/dist/esm/validators.js +11 -37
- package/dist/esm/validators.js.map +1 -1
- package/dist/esm.tsbuildinfo +1 -1
- package/package.json +11 -9
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/files.ts +167 -0
- package/src/client/index.test.ts +127 -1
- package/src/client/index.ts +970 -812
- package/src/client/setup.test.ts +29 -0
- package/src/client/types.ts +41 -3
- package/src/component/_generated/api.d.ts +47 -711
- package/src/component/files.test.ts +130 -0
- package/src/component/files.ts +83 -27
- package/src/component/messages.test.ts +2 -1
- package/src/component/messages.ts +47 -137
- package/src/component/schema.ts +8 -28
- package/src/mapping.test.ts +163 -0
- package/src/mapping.ts +173 -103
- package/src/react/deltas.test.ts +4 -1
- package/src/react/toUIMessages.ts +12 -6
- package/src/react/usePaginatedQuery.ts +0 -31
- package/src/validators.ts +33 -47
|
@@ -3,11 +3,13 @@ import { assert } from "convex-helpers";
|
|
|
3
3
|
import { internalActionGeneric, internalMutationGeneric, } from "convex/server";
|
|
4
4
|
import { v } from "convex/values";
|
|
5
5
|
import { validateVectorDimension, } from "../component/vector/tables.js";
|
|
6
|
-
import { deserializeMessage, promptOrMessagesToCoreMessages, serializeMessage, serializeNewMessagesInStep, serializeObjectResult,
|
|
6
|
+
import { deserializeMessage, promptOrMessagesToCoreMessages, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
|
|
7
7
|
import { DEFAULT_MESSAGE_RANGE, DEFAULT_RECENT_MESSAGES, extractText, isTool, } from "../shared.js";
|
|
8
8
|
import { vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
|
|
9
9
|
import { createTool, wrapTools } from "./createTool.js";
|
|
10
10
|
import { DeltaStreamer, mergeTransforms, } from "./streaming.js";
|
|
11
|
+
export { storeFile, getFile } from "./files.js";
|
|
12
|
+
export { serializeDataOrUrl } from "../mapping.js";
|
|
11
13
|
export { vMessageDoc, vThreadDoc } from "../component/schema.js";
|
|
12
14
|
export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
|
|
13
15
|
export { createTool, extractText, isTool };
|
|
@@ -65,30 +67,397 @@ export class Agent {
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* @
|
|
70
|
+
* This behaves like {@link generateText} from the "ai" package except that
|
|
71
|
+
* it add context based on the userId and threadId and saves the input and
|
|
72
|
+
* resulting messages to the thread, if specified.
|
|
73
|
+
* Use {@link continueThread} to get a version of this function already scoped
|
|
74
|
+
* to a thread (and optionally userId).
|
|
75
|
+
* @param ctx The context passed from the action function calling this.
|
|
76
|
+
* @param { userId, threadId }: The user and thread to associate the message with
|
|
77
|
+
* @param args The arguments to the generateText function, along with extra controls
|
|
78
|
+
* for the {@link ContextOptions} and {@link StorageOptions}.
|
|
79
|
+
* @returns The result of the generateText function.
|
|
72
80
|
*/
|
|
73
|
-
async
|
|
74
|
-
const
|
|
75
|
-
|
|
81
|
+
async generateText(ctx, { userId: argsUserId, threadId, usageHandler, tools: threadTools, }, args, options) {
|
|
82
|
+
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
83
|
+
userId: argsUserId,
|
|
84
|
+
threadId,
|
|
85
|
+
...options,
|
|
76
86
|
});
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
const { args: aiArgs, messageId, userId } = context;
|
|
88
|
+
const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
|
|
89
|
+
const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
|
|
90
|
+
const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
|
|
91
|
+
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
92
|
+
try {
|
|
93
|
+
const result = (await generateText({
|
|
94
|
+
// Can be overridden
|
|
95
|
+
maxSteps: this.options.maxSteps,
|
|
96
|
+
...aiArgs,
|
|
97
|
+
tools,
|
|
98
|
+
onStepFinish: async (step) => {
|
|
99
|
+
if (threadId && messageId && saveOutputMessages) {
|
|
100
|
+
await this.saveStep(ctx, {
|
|
101
|
+
userId,
|
|
102
|
+
threadId,
|
|
103
|
+
promptMessageId: messageId,
|
|
104
|
+
step,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (this.options.rawRequestResponseHandler) {
|
|
108
|
+
await this.options.rawRequestResponseHandler(ctx, {
|
|
109
|
+
userId,
|
|
110
|
+
threadId,
|
|
111
|
+
agentName: this.options.name,
|
|
112
|
+
request: step.request,
|
|
113
|
+
response: step.response,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (trackUsage && step.usage) {
|
|
117
|
+
await trackUsage(ctx, {
|
|
118
|
+
userId,
|
|
119
|
+
threadId,
|
|
120
|
+
agentName: this.options.name,
|
|
121
|
+
model: aiArgs.model.modelId,
|
|
122
|
+
provider: aiArgs.model.provider,
|
|
123
|
+
usage: step.usage,
|
|
124
|
+
providerMetadata: step.providerMetadata,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return args.onStepFinish?.(step);
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
result.messageId = messageId;
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (threadId && messageId) {
|
|
135
|
+
console.error("RollbackMessage", messageId);
|
|
136
|
+
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
137
|
+
messageId,
|
|
138
|
+
error: error.message,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
79
142
|
}
|
|
80
|
-
return thread;
|
|
81
143
|
}
|
|
82
144
|
/**
|
|
83
|
-
*
|
|
145
|
+
* This behaves like {@link streamText} from the "ai" package except that
|
|
146
|
+
* it add context based on the userId and threadId and saves the input and
|
|
147
|
+
* resulting messages to the thread, if specified.
|
|
148
|
+
* Use {@link continueThread} to get a version of this function already scoped
|
|
149
|
+
* to a thread (and optionally userId).
|
|
150
|
+
*/
|
|
151
|
+
async streamText(ctx, { userId: argsUserId, threadId, usageHandler,
|
|
152
|
+
/**
|
|
153
|
+
* @deprecated Pass `tools` in the next parameter instead.
|
|
154
|
+
* This is only intended to pass through thread-default tools.
|
|
155
|
+
*/
|
|
156
|
+
tools: threadTools, },
|
|
157
|
+
/**
|
|
158
|
+
* The arguments to the streamText function, similar to the ai `streamText` function.
|
|
159
|
+
*/
|
|
160
|
+
args,
|
|
161
|
+
/**
|
|
162
|
+
* The {@link ContextOptions} and {@link StorageOptions}
|
|
163
|
+
* options to use for fetching contextual messages and saving input/output messages.
|
|
164
|
+
*/
|
|
165
|
+
options) {
|
|
166
|
+
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
167
|
+
userId: argsUserId,
|
|
168
|
+
threadId,
|
|
169
|
+
...options,
|
|
170
|
+
});
|
|
171
|
+
const { args: aiArgs, messageId, order, stepOrder, userId } = context;
|
|
172
|
+
const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
|
|
173
|
+
const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
|
|
174
|
+
const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
|
|
175
|
+
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
176
|
+
const streamer = threadId && options?.saveStreamDeltas
|
|
177
|
+
? new DeltaStreamer(this.component, ctx, options.saveStreamDeltas, {
|
|
178
|
+
threadId,
|
|
179
|
+
userId,
|
|
180
|
+
agentName: this.options.name,
|
|
181
|
+
model: aiArgs.model.modelId,
|
|
182
|
+
provider: aiArgs.model.provider,
|
|
183
|
+
providerOptions: aiArgs.providerOptions,
|
|
184
|
+
order,
|
|
185
|
+
stepOrder,
|
|
186
|
+
})
|
|
187
|
+
: undefined;
|
|
188
|
+
const result = streamText({
|
|
189
|
+
// Can be overridden
|
|
190
|
+
maxSteps: this.options.maxSteps,
|
|
191
|
+
...aiArgs,
|
|
192
|
+
tools,
|
|
193
|
+
experimental_transform: mergeTransforms(options?.saveStreamDeltas, args.experimental_transform),
|
|
194
|
+
onChunk: async (event) => {
|
|
195
|
+
await streamer?.addParts([event.chunk]);
|
|
196
|
+
// console.log("onChunk", chunk);
|
|
197
|
+
return args.onChunk?.(event);
|
|
198
|
+
},
|
|
199
|
+
onError: async (error) => {
|
|
200
|
+
console.error("onError", error);
|
|
201
|
+
if (threadId && messageId && saveOutputMessages) {
|
|
202
|
+
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
203
|
+
messageId,
|
|
204
|
+
error: error.error.message,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return args.onError?.(error);
|
|
208
|
+
},
|
|
209
|
+
onStepFinish: async (step) => {
|
|
210
|
+
// console.log("onStepFinish", step);
|
|
211
|
+
// TODO: compare delta to the output. internally drop the deltas when committing
|
|
212
|
+
if (threadId && messageId) {
|
|
213
|
+
const saved = await this.saveStep(ctx, {
|
|
214
|
+
userId,
|
|
215
|
+
threadId,
|
|
216
|
+
promptMessageId: messageId,
|
|
217
|
+
step,
|
|
218
|
+
});
|
|
219
|
+
// TODO: figure out pending/not
|
|
220
|
+
await streamer?.finish(saved.messages);
|
|
221
|
+
}
|
|
222
|
+
if (this.options.rawRequestResponseHandler) {
|
|
223
|
+
await this.options.rawRequestResponseHandler(ctx, {
|
|
224
|
+
userId,
|
|
225
|
+
threadId,
|
|
226
|
+
agentName: this.options.name,
|
|
227
|
+
request: step.request,
|
|
228
|
+
response: step.response,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (trackUsage && step.usage) {
|
|
232
|
+
await trackUsage(ctx, {
|
|
233
|
+
userId,
|
|
234
|
+
threadId,
|
|
235
|
+
agentName: this.options.name,
|
|
236
|
+
model: aiArgs.model.modelId,
|
|
237
|
+
provider: aiArgs.model.provider,
|
|
238
|
+
usage: step.usage,
|
|
239
|
+
providerMetadata: step.providerMetadata,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return args.onStepFinish?.(step);
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
result.messageId = messageId;
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* This behaves like {@link generateObject} from the "ai" package except that
|
|
250
|
+
* it add context based on the userId and threadId and saves the input and
|
|
251
|
+
* resulting messages to the thread, if specified.
|
|
252
|
+
* Use {@link continueThread} to get a version of this function already scoped
|
|
253
|
+
* to a thread (and optionally userId).
|
|
254
|
+
*/
|
|
255
|
+
async generateObject(ctx, { userId: argsUserId, threadId, usageHandler, },
|
|
256
|
+
/**
|
|
257
|
+
* The arguments to the generateObject function, similar to the ai.generateObject function.
|
|
258
|
+
*/
|
|
259
|
+
args,
|
|
260
|
+
/**
|
|
261
|
+
* The {@link ContextOptions} and {@link StorageOptions}
|
|
262
|
+
* options to use for fetching contextual messages and saving input/output messages.
|
|
263
|
+
*/
|
|
264
|
+
options) {
|
|
265
|
+
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
266
|
+
userId: argsUserId,
|
|
267
|
+
threadId,
|
|
268
|
+
...options,
|
|
269
|
+
});
|
|
270
|
+
const { args: aiArgs, messageId, userId } = context;
|
|
271
|
+
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
272
|
+
const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
|
|
273
|
+
try {
|
|
274
|
+
const result = (await generateObject(
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
aiArgs));
|
|
277
|
+
if (threadId && messageId && saveOutputMessages) {
|
|
278
|
+
await this.saveObject(ctx, {
|
|
279
|
+
threadId,
|
|
280
|
+
promptMessageId: messageId,
|
|
281
|
+
result,
|
|
282
|
+
userId,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
result.messageId = messageId;
|
|
286
|
+
if (this.options.rawRequestResponseHandler) {
|
|
287
|
+
await this.options.rawRequestResponseHandler(ctx, {
|
|
288
|
+
userId,
|
|
289
|
+
threadId,
|
|
290
|
+
agentName: this.options.name,
|
|
291
|
+
request: result.request,
|
|
292
|
+
response: result.response,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (trackUsage && result.usage) {
|
|
296
|
+
await trackUsage(ctx, {
|
|
297
|
+
userId,
|
|
298
|
+
threadId,
|
|
299
|
+
agentName: this.options.name,
|
|
300
|
+
model: aiArgs.model.modelId,
|
|
301
|
+
provider: aiArgs.model.provider,
|
|
302
|
+
usage: result.usage,
|
|
303
|
+
providerMetadata: result.providerMetadata,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
if (threadId && messageId) {
|
|
310
|
+
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
311
|
+
messageId,
|
|
312
|
+
error: error.message,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* This behaves like `streamObject` from the "ai" package except that
|
|
320
|
+
* it add context based on the userId and threadId and saves the input and
|
|
321
|
+
* resulting messages to the thread, if specified.
|
|
322
|
+
* Use {@link continueThread} to get a version of this function already scoped
|
|
323
|
+
* to a thread (and optionally userId).
|
|
324
|
+
*/
|
|
325
|
+
async streamObject(ctx, { userId: argsUserId, threadId, usageHandler, },
|
|
326
|
+
/**
|
|
327
|
+
* The arguments to the streamObject function, similar to the ai `streamObject` function.
|
|
328
|
+
*/
|
|
329
|
+
args,
|
|
330
|
+
/**
|
|
331
|
+
* The {@link ContextOptions} and {@link StorageOptions}
|
|
332
|
+
* options to use for fetching contextual messages and saving input/output messages.
|
|
333
|
+
*/
|
|
334
|
+
options) {
|
|
335
|
+
// TODO: unify all this shared code between all the generate* and stream* functions
|
|
336
|
+
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
337
|
+
userId: argsUserId,
|
|
338
|
+
threadId,
|
|
339
|
+
...options,
|
|
340
|
+
});
|
|
341
|
+
const { args: aiArgs, messageId, userId } = context;
|
|
342
|
+
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
343
|
+
const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
|
|
344
|
+
const stream = streamObject({
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
346
|
+
...aiArgs,
|
|
347
|
+
onError: async (error) => {
|
|
348
|
+
console.error("onError", error);
|
|
349
|
+
return args.onError?.(error);
|
|
350
|
+
},
|
|
351
|
+
onFinish: async (result) => {
|
|
352
|
+
if (threadId && messageId && saveOutputMessages) {
|
|
353
|
+
await this.saveObject(ctx, {
|
|
354
|
+
userId,
|
|
355
|
+
threadId,
|
|
356
|
+
promptMessageId: messageId,
|
|
357
|
+
result: {
|
|
358
|
+
object: result.object,
|
|
359
|
+
finishReason: "stop",
|
|
360
|
+
usage: result.usage,
|
|
361
|
+
warnings: result.warnings,
|
|
362
|
+
request: await stream.request,
|
|
363
|
+
response: result.response,
|
|
364
|
+
providerMetadata: result.providerMetadata,
|
|
365
|
+
experimental_providerMetadata: result.experimental_providerMetadata,
|
|
366
|
+
logprobs: undefined,
|
|
367
|
+
toJsonResponse: stream.toTextStreamResponse,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (trackUsage && result.usage) {
|
|
372
|
+
await trackUsage(ctx, {
|
|
373
|
+
userId,
|
|
374
|
+
threadId,
|
|
375
|
+
agentName: this.options.name,
|
|
376
|
+
model: aiArgs.model.modelId,
|
|
377
|
+
provider: aiArgs.model.provider,
|
|
378
|
+
usage: result.usage,
|
|
379
|
+
providerMetadata: result.providerMetadata,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (this.options.rawRequestResponseHandler) {
|
|
383
|
+
await this.options.rawRequestResponseHandler(ctx, {
|
|
384
|
+
userId,
|
|
385
|
+
threadId,
|
|
386
|
+
agentName: this.options.name,
|
|
387
|
+
request: await stream.request,
|
|
388
|
+
response: result.response,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
392
|
+
return args.onFinish?.(result);
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
stream.messageId = messageId;
|
|
396
|
+
return stream;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Save a message to the thread.
|
|
84
400
|
* @param ctx A ctx object from a mutation or action.
|
|
85
|
-
* @param args
|
|
86
|
-
*
|
|
87
|
-
* @returns The
|
|
401
|
+
* @param args The message and what to associate it with (user / thread)
|
|
402
|
+
* You can pass extra metadata alongside the message, e.g. associated fileIds.
|
|
403
|
+
* @returns The messageId of the saved message.
|
|
88
404
|
*/
|
|
89
|
-
async
|
|
90
|
-
const
|
|
91
|
-
|
|
405
|
+
async saveMessage(ctx, args) {
|
|
406
|
+
const { lastMessageId, messages } = await this.saveMessages(ctx, {
|
|
407
|
+
threadId: args.threadId,
|
|
408
|
+
userId: args.userId,
|
|
409
|
+
messages: args.prompt !== undefined
|
|
410
|
+
? [{ role: "user", content: args.prompt }]
|
|
411
|
+
: [args.message],
|
|
412
|
+
metadata: args.metadata ? [args.metadata] : undefined,
|
|
413
|
+
skipEmbeddings: args.skipEmbeddings,
|
|
414
|
+
});
|
|
415
|
+
return { messageId: lastMessageId, message: messages.at(-1) };
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Explicitly save messages associated with the thread (& user if provided)
|
|
419
|
+
* @param ctx The ctx parameter to a mutation or action.
|
|
420
|
+
* @param args The messages and context to save
|
|
421
|
+
* @returns
|
|
422
|
+
*/
|
|
423
|
+
async saveMessages(ctx, args) {
|
|
424
|
+
let embeddings;
|
|
425
|
+
if (args.skipEmbeddings || !("runAction" in ctx)) {
|
|
426
|
+
embeddings = undefined;
|
|
427
|
+
if (!args.skipEmbeddings && this.options.textEmbedding) {
|
|
428
|
+
console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
|
|
429
|
+
"Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
|
|
430
|
+
"They will be generated lazily when you generate or stream text / objects. " +
|
|
431
|
+
"You can explicitly generate them asynchronously by using the scheduler to run an action later that calls `agent.generateAndSaveEmbeddings`.");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
embeddings = await this.generateEmbeddings(ctx, {
|
|
436
|
+
userId: args.userId,
|
|
437
|
+
threadId: args.threadId,
|
|
438
|
+
}, args.messages);
|
|
439
|
+
}
|
|
440
|
+
const result = await ctx.runMutation(this.component.messages.addMessages, {
|
|
441
|
+
threadId: args.threadId,
|
|
442
|
+
userId: args.userId,
|
|
443
|
+
agentName: this.options.name,
|
|
444
|
+
promptMessageId: args.promptMessageId,
|
|
445
|
+
embeddings,
|
|
446
|
+
messages: await Promise.all(args.messages.map(async (m, i) => {
|
|
447
|
+
const { message, fileIds } = await serializeMessage(ctx, this.component, m);
|
|
448
|
+
return {
|
|
449
|
+
...args.metadata?.[i],
|
|
450
|
+
message,
|
|
451
|
+
fileIds,
|
|
452
|
+
};
|
|
453
|
+
})),
|
|
454
|
+
failPendingSteps: args.failPendingSteps ?? false,
|
|
455
|
+
pending: args.pending ?? false,
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
lastMessageId: result.messages.at(-1)._id,
|
|
459
|
+
messages: result.messages,
|
|
460
|
+
};
|
|
92
461
|
}
|
|
93
462
|
/**
|
|
94
463
|
* List messages from a thread.
|
|
@@ -188,398 +557,207 @@ export class Agent {
|
|
|
188
557
|
(args.threadId &&
|
|
189
558
|
(await ctx.runQuery(this.component.threads.getThread, {
|
|
190
559
|
threadId: args.threadId,
|
|
191
|
-
}))?.userId)
|
|
192
|
-
: undefined,
|
|
193
|
-
threadId: args.threadId,
|
|
194
|
-
beforeMessageId: args.upToAndIncludingMessageId,
|
|
195
|
-
...(await this.
|
|
196
|
-
});
|
|
197
|
-
// TODO: track what messages we used for context
|
|
198
|
-
contextMessages.unshift(...searchMessages.filter((m) => !included?.has(m._id)));
|
|
199
|
-
}
|
|
200
|
-
// Ensure we don't include tool messages without a corresponding tool call
|
|
201
|
-
return filterOutOrphanedToolMessages(contextMessages.sort((a, b) =>
|
|
202
|
-
// Sort the raw MessageDocs by order and stepOrder
|
|
203
|
-
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order));
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Get the embeddings for a set of messages.
|
|
207
|
-
* @param messages The messages to get the embeddings for.
|
|
208
|
-
* @returns The embeddings for the messages.
|
|
209
|
-
*/
|
|
210
|
-
async generateEmbeddings(messages) {
|
|
211
|
-
let embeddings;
|
|
212
|
-
if (this.options.textEmbedding) {
|
|
213
|
-
const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
|
|
214
|
-
// Find the indexes of the messages that have text.
|
|
215
|
-
const textIndexes = messageTexts
|
|
216
|
-
.map((t, i) => (t ? i : undefined))
|
|
217
|
-
.filter((i) => i !== undefined);
|
|
218
|
-
if (textIndexes.length === 0) {
|
|
219
|
-
return undefined;
|
|
220
|
-
}
|
|
221
|
-
// Then embed those messages.
|
|
222
|
-
const textEmbeddings = await this.options.textEmbedding.doEmbed({
|
|
223
|
-
values: messageTexts.filter((t) => !!t),
|
|
224
|
-
});
|
|
225
|
-
// TODO: record usage of embeddings
|
|
226
|
-
// Then assemble the embeddings into a single array with nulls for the messages without text.
|
|
227
|
-
const embeddingsOrNull = Array(messages.length).fill(null);
|
|
228
|
-
textIndexes.forEach((i, j) => {
|
|
229
|
-
embeddingsOrNull[i] = textEmbeddings.embeddings[j];
|
|
230
|
-
});
|
|
231
|
-
if (textEmbeddings.embeddings.length > 0) {
|
|
232
|
-
const dimension = textEmbeddings.embeddings[0].length;
|
|
233
|
-
validateVectorDimension(dimension);
|
|
234
|
-
embeddings = {
|
|
235
|
-
vectors: embeddingsOrNull,
|
|
236
|
-
dimension,
|
|
237
|
-
model: this.options.textEmbedding.modelId,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return embeddings;
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Generate embeddings for a set of messages, and save them to the database.
|
|
245
|
-
* It will not generate or save embeddings for messages that already have an
|
|
246
|
-
* embedding.
|
|
247
|
-
* @param ctx The ctx parameter to an action.
|
|
248
|
-
* @param args The messageIds to generate embeddings for.
|
|
249
|
-
*/
|
|
250
|
-
async generateAndSaveEmbeddings(ctx, args) {
|
|
251
|
-
const messages = (await ctx.runQuery(this.component.messages.getMessagesByIds, {
|
|
252
|
-
messageIds: args.messageIds,
|
|
253
|
-
})).filter((m) => m !== null);
|
|
254
|
-
if (messages.length !== args.messageIds.length) {
|
|
255
|
-
throw new Error("Some messages were not found: " +
|
|
256
|
-
args.messageIds
|
|
257
|
-
.filter((id) => !messages.some((m) => m?._id === id))
|
|
258
|
-
.join(", "));
|
|
259
|
-
}
|
|
260
|
-
if (messages.some((m) => !m.message)) {
|
|
261
|
-
throw new Error("Some messages don't have a message: " +
|
|
262
|
-
args.messageIds
|
|
263
|
-
.map((id, i) => (!messages[i].message ? id : undefined))
|
|
264
|
-
.filter((id) => id !== undefined)
|
|
265
|
-
.join(", "));
|
|
266
|
-
}
|
|
267
|
-
const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
|
|
268
|
-
if (messagesMissingEmbeddings.length === 0) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const embeddings = await this.generateEmbeddings(messagesMissingEmbeddings.map((m) => m.message));
|
|
272
|
-
if (!embeddings) {
|
|
273
|
-
if (!this.options.textEmbedding) {
|
|
274
|
-
throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
|
|
275
|
-
}
|
|
276
|
-
throw new Error("No embeddings were generated for these messages: " +
|
|
277
|
-
messagesMissingEmbeddings.map((m) => m._id).join(", "));
|
|
278
|
-
}
|
|
279
|
-
await ctx.runMutation(this.component.vector.index.insertBatch, {
|
|
280
|
-
vectorDimension: embeddings.dimension,
|
|
281
|
-
vectors: messagesMissingEmbeddings
|
|
282
|
-
.map((m, i) => ({
|
|
283
|
-
messageId: m._id,
|
|
284
|
-
model: embeddings.model,
|
|
285
|
-
table: "messages",
|
|
286
|
-
userId: m.userId,
|
|
287
|
-
threadId: m.threadId,
|
|
288
|
-
vector: embeddings.vectors[i],
|
|
289
|
-
}))
|
|
290
|
-
.filter((v) => v.vector !== null),
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
async saveMessage(ctx, args) {
|
|
294
|
-
const { lastMessageId } = await this.saveMessages(ctx, {
|
|
295
|
-
threadId: args.threadId,
|
|
296
|
-
userId: args.userId,
|
|
297
|
-
messages: args.prompt !== undefined
|
|
298
|
-
? [{ role: "user", content: args.prompt }]
|
|
299
|
-
: [args.message],
|
|
300
|
-
metadata: args.metadata ? [args.metadata] : undefined,
|
|
301
|
-
skipEmbeddings: args.skipEmbeddings,
|
|
302
|
-
});
|
|
303
|
-
return { messageId: lastMessageId };
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Explicitly save messages associated with the thread (& user if provided)
|
|
307
|
-
* @param ctx The ctx parameter to a mutation or action.
|
|
308
|
-
* @param args The messages and context to save
|
|
309
|
-
* @returns
|
|
310
|
-
*/
|
|
311
|
-
async saveMessages(ctx, args) {
|
|
312
|
-
let embeddings;
|
|
313
|
-
if (args.skipEmbeddings || !("runAction" in ctx)) {
|
|
314
|
-
embeddings = undefined;
|
|
315
|
-
if (!args.skipEmbeddings && this.options.textEmbedding) {
|
|
316
|
-
console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
|
|
317
|
-
"Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
|
|
318
|
-
"They will be generated lazily when you generate or stream text / objects. " +
|
|
319
|
-
"You can explicitly generate them asynchronously by using the scheduler to run an action later that calls `agent.generateAndSaveEmbeddings`.");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
embeddings = await this.generateEmbeddings(args.messages);
|
|
324
|
-
}
|
|
325
|
-
const result = await ctx.runMutation(this.component.messages.addMessages, {
|
|
326
|
-
threadId: args.threadId,
|
|
327
|
-
userId: args.userId,
|
|
328
|
-
agentName: this.options.name,
|
|
329
|
-
promptMessageId: args.promptMessageId,
|
|
330
|
-
embeddings,
|
|
331
|
-
messages: args.messages.map((m, i) => ({
|
|
332
|
-
...args.metadata?.[i],
|
|
333
|
-
message: serializeMessage(m),
|
|
334
|
-
})),
|
|
335
|
-
failPendingSteps: args.failPendingSteps ?? false,
|
|
336
|
-
pending: args.pending ?? false,
|
|
337
|
-
});
|
|
338
|
-
return {
|
|
339
|
-
lastMessageId: result.messages.at(-1)._id,
|
|
340
|
-
messages: result.messages,
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Save messages to the thread.
|
|
345
|
-
* Useful as a step in Workflows, e.g.
|
|
346
|
-
* ```ts
|
|
347
|
-
* const saveMessages = agent.asSaveMessagesMutation();
|
|
348
|
-
*
|
|
349
|
-
* const myWorkflow = workflow.define()
|
|
350
|
-
* ```
|
|
351
|
-
* @returns A mutation that can be used to save messages to the thread.
|
|
352
|
-
*/
|
|
353
|
-
asSaveMessagesMutation() {
|
|
354
|
-
return internalMutationGeneric({
|
|
355
|
-
args: {
|
|
356
|
-
threadId: v.string(),
|
|
357
|
-
userId: v.optional(v.string()),
|
|
358
|
-
promptMessageId: v.optional(v.string()),
|
|
359
|
-
messages: v.array(vMessageWithMetadata),
|
|
360
|
-
pending: v.optional(v.boolean()),
|
|
361
|
-
failPendingSteps: v.optional(v.boolean()),
|
|
362
|
-
},
|
|
363
|
-
handler: async (ctx, args) => {
|
|
364
|
-
const { lastMessageId, messages } = await this.saveMessages(ctx, {
|
|
365
|
-
...args,
|
|
366
|
-
messages: args.messages.map((m) => m.message),
|
|
367
|
-
metadata: args.messages.map(({ message: _, ...m }) => m),
|
|
368
|
-
});
|
|
369
|
-
return {
|
|
370
|
-
lastMessageId,
|
|
371
|
-
messageIds: messages.map((m) => m._id),
|
|
372
|
-
};
|
|
373
|
-
},
|
|
374
|
-
});
|
|
560
|
+
}))?.userId)
|
|
561
|
+
: undefined,
|
|
562
|
+
threadId: args.threadId,
|
|
563
|
+
beforeMessageId: args.upToAndIncludingMessageId,
|
|
564
|
+
...(await this._searchOptionsWithEmbeddingAndDefaults(ctx, { userId: args.userId, threadId: args.threadId }, opts, messagesToSearch)),
|
|
565
|
+
});
|
|
566
|
+
// TODO: track what messages we used for context
|
|
567
|
+
contextMessages.unshift(...searchMessages.filter((m) => !included?.has(m._id)));
|
|
568
|
+
}
|
|
569
|
+
// Ensure we don't include tool messages without a corresponding tool call
|
|
570
|
+
return filterOutOrphanedToolMessages(contextMessages.sort((a, b) =>
|
|
571
|
+
// Sort the raw MessageDocs by order and stepOrder
|
|
572
|
+
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order));
|
|
375
573
|
}
|
|
376
574
|
/**
|
|
377
|
-
*
|
|
378
|
-
* @param ctx
|
|
379
|
-
* @param args The
|
|
575
|
+
* Get the metadata for a thread.
|
|
576
|
+
* @param ctx A ctx object from a query, mutation, or action.
|
|
577
|
+
* @param args.threadId The thread to get the metadata for.
|
|
578
|
+
* @returns The metadata for the thread.
|
|
380
579
|
*/
|
|
381
|
-
async
|
|
382
|
-
const
|
|
383
|
-
const messages = serializeNewMessagesInStep(args.step, {
|
|
384
|
-
provider: args.provider ?? this.options.chat.provider,
|
|
385
|
-
model: args.model ?? this.options.chat.modelId,
|
|
386
|
-
});
|
|
387
|
-
const embeddings = await this.generateEmbeddings(messages.map((m) => m.message));
|
|
388
|
-
const saved = await ctx.runMutation(this.component.messages.addStep, {
|
|
389
|
-
userId: args.userId,
|
|
580
|
+
async getThreadMetadata(ctx, args) {
|
|
581
|
+
const thread = await ctx.runQuery(this.component.threads.getThread, {
|
|
390
582
|
threadId: args.threadId,
|
|
391
|
-
promptMessageId: args.promptMessageId,
|
|
392
|
-
step: { step, messages, embeddings },
|
|
393
|
-
failPendingSteps: false,
|
|
394
583
|
});
|
|
395
|
-
|
|
584
|
+
if (!thread) {
|
|
585
|
+
throw new Error("Thread not found");
|
|
586
|
+
}
|
|
587
|
+
return thread;
|
|
396
588
|
}
|
|
397
589
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
* @param
|
|
402
|
-
* @
|
|
403
|
-
* the generateText call.
|
|
590
|
+
* Update the metadata for a thread.
|
|
591
|
+
* @param ctx A ctx object from a mutation or action.
|
|
592
|
+
* @param args.threadId The thread to update the metadata for.
|
|
593
|
+
* @param args.patch The patch to apply to the thread.
|
|
594
|
+
* @returns The updated thread metadata.
|
|
404
595
|
*/
|
|
405
|
-
async
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
await ctx.runMutation(this.component.messages.commitMessage, {
|
|
409
|
-
messageId: args.messageId,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
414
|
-
messageId: args.messageId,
|
|
415
|
-
error: result.error,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
596
|
+
async updateThreadMetadata(ctx, args) {
|
|
597
|
+
const thread = await ctx.runMutation(this.component.threads.updateThread, args);
|
|
598
|
+
return thread;
|
|
418
599
|
}
|
|
419
600
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* Use {@link continueThread} to get a version of this function already scoped
|
|
424
|
-
* to a thread (and optionally userId).
|
|
425
|
-
* @param ctx The context passed from the action function calling this.
|
|
426
|
-
* @param { userId, threadId }: The user and thread to associate the message with
|
|
427
|
-
* @param args The arguments to the generateText function, along with extra controls
|
|
428
|
-
* for the {@link ContextOptions} and {@link StorageOptions}.
|
|
429
|
-
* @returns The result of the generateText function.
|
|
601
|
+
* Get the embeddings for a set of messages.
|
|
602
|
+
* @param messages The messages to get the embeddings for.
|
|
603
|
+
* @returns The embeddings for the messages.
|
|
430
604
|
*/
|
|
431
|
-
async
|
|
432
|
-
|
|
433
|
-
|
|
605
|
+
async generateEmbeddings(ctx, { userId, threadId, }, messages) {
|
|
606
|
+
if (!this.options.textEmbedding) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
let embeddings;
|
|
610
|
+
const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
|
|
611
|
+
// Find the indexes of the messages that have text.
|
|
612
|
+
const textIndexes = messageTexts
|
|
613
|
+
.map((t, i) => (t ? i : undefined))
|
|
614
|
+
.filter((i) => i !== undefined);
|
|
615
|
+
if (textIndexes.length === 0) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
// Then embed those messages.
|
|
619
|
+
const textEmbeddings = await this.doEmbed(ctx, {
|
|
620
|
+
userId,
|
|
434
621
|
threadId,
|
|
435
|
-
|
|
622
|
+
values: messageTexts.filter((t) => !!t),
|
|
436
623
|
});
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
await this.saveStep(ctx, {
|
|
452
|
-
userId,
|
|
453
|
-
threadId,
|
|
454
|
-
promptMessageId: messageId,
|
|
455
|
-
step,
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
if (trackUsage && step.usage) {
|
|
459
|
-
await trackUsage(ctx, {
|
|
460
|
-
userId,
|
|
461
|
-
threadId,
|
|
462
|
-
agentName: this.options.name,
|
|
463
|
-
model: aiArgs.model.modelId,
|
|
464
|
-
provider: aiArgs.model.provider,
|
|
465
|
-
usage: step.usage,
|
|
466
|
-
providerMetadata: step.providerMetadata,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
return args.onStepFinish?.(step);
|
|
470
|
-
},
|
|
471
|
-
}));
|
|
472
|
-
result.messageId = messageId;
|
|
473
|
-
return result;
|
|
474
|
-
}
|
|
475
|
-
catch (error) {
|
|
476
|
-
if (threadId && messageId) {
|
|
477
|
-
console.error("RollbackMessage", messageId);
|
|
478
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
479
|
-
messageId,
|
|
480
|
-
error: error.message,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
throw error;
|
|
624
|
+
// TODO: record usage of embeddings
|
|
625
|
+
// Then assemble the embeddings into a single array with nulls for the messages without text.
|
|
626
|
+
const embeddingsOrNull = Array(messages.length).fill(null);
|
|
627
|
+
textIndexes.forEach((i, j) => {
|
|
628
|
+
embeddingsOrNull[i] = textEmbeddings.embeddings[j];
|
|
629
|
+
});
|
|
630
|
+
if (textEmbeddings.embeddings.length > 0) {
|
|
631
|
+
const dimension = textEmbeddings.embeddings[0].length;
|
|
632
|
+
validateVectorDimension(dimension);
|
|
633
|
+
embeddings = {
|
|
634
|
+
vectors: embeddingsOrNull,
|
|
635
|
+
dimension,
|
|
636
|
+
model: this.options.textEmbedding.modelId,
|
|
637
|
+
};
|
|
484
638
|
}
|
|
639
|
+
return embeddings;
|
|
485
640
|
}
|
|
486
641
|
/**
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*/
|
|
493
|
-
async streamText(ctx, { userId: argsUserId, threadId, usageHandler,
|
|
494
|
-
/**
|
|
495
|
-
* @deprecated Pass `tools` in the next parameter instead.
|
|
496
|
-
* This is only intended to pass through thread-default tools.
|
|
497
|
-
*/
|
|
498
|
-
tools: threadTools, },
|
|
499
|
-
/**
|
|
500
|
-
* The arguments to the streamText function, similar to the ai `streamText` function.
|
|
501
|
-
*/
|
|
502
|
-
args,
|
|
503
|
-
/**
|
|
504
|
-
* The {@link ContextOptions} and {@link StorageOptions}
|
|
505
|
-
* options to use for fetching contextual messages and saving input/output messages.
|
|
642
|
+
* Generate embeddings for a set of messages, and save them to the database.
|
|
643
|
+
* It will not generate or save embeddings for messages that already have an
|
|
644
|
+
* embedding.
|
|
645
|
+
* @param ctx The ctx parameter to an action.
|
|
646
|
+
* @param args The messageIds to generate embeddings for.
|
|
506
647
|
*/
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
return args.onError?.(error);
|
|
554
|
-
},
|
|
555
|
-
onStepFinish: async (step) => {
|
|
556
|
-
// console.log("onStepFinish", step);
|
|
557
|
-
// TODO: compare delta to the output. internally drop the deltas when committing
|
|
558
|
-
if (threadId && messageId) {
|
|
559
|
-
const saved = await this.saveStep(ctx, {
|
|
560
|
-
userId,
|
|
561
|
-
threadId,
|
|
562
|
-
promptMessageId: messageId,
|
|
563
|
-
step,
|
|
564
|
-
});
|
|
565
|
-
await streamer?.finish(saved);
|
|
566
|
-
}
|
|
567
|
-
if (trackUsage && step.usage) {
|
|
568
|
-
await trackUsage(ctx, {
|
|
569
|
-
userId,
|
|
570
|
-
threadId,
|
|
571
|
-
agentName: this.options.name,
|
|
572
|
-
model: aiArgs.model.modelId,
|
|
573
|
-
provider: aiArgs.model.provider,
|
|
574
|
-
usage: step.usage,
|
|
575
|
-
providerMetadata: step.providerMetadata,
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
return args.onStepFinish?.(step);
|
|
579
|
-
},
|
|
648
|
+
async generateAndSaveEmbeddings(ctx, args) {
|
|
649
|
+
const messages = (await ctx.runQuery(this.component.messages.getMessagesByIds, {
|
|
650
|
+
messageIds: args.messageIds,
|
|
651
|
+
})).filter((m) => m !== null);
|
|
652
|
+
if (messages.length !== args.messageIds.length) {
|
|
653
|
+
throw new Error("Some messages were not found: " +
|
|
654
|
+
args.messageIds
|
|
655
|
+
.filter((id) => !messages.some((m) => m?._id === id))
|
|
656
|
+
.join(", "));
|
|
657
|
+
}
|
|
658
|
+
if (messages.some((m) => !m.message)) {
|
|
659
|
+
throw new Error("Some messages don't have a message: " +
|
|
660
|
+
args.messageIds
|
|
661
|
+
.map((id, i) => (!messages[i].message ? id : undefined))
|
|
662
|
+
.filter((id) => id !== undefined)
|
|
663
|
+
.join(", "));
|
|
664
|
+
}
|
|
665
|
+
const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
|
|
666
|
+
if (messagesMissingEmbeddings.length === 0) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const embeddings = await this.generateEmbeddings(ctx, {
|
|
670
|
+
userId: messagesMissingEmbeddings[0].userId,
|
|
671
|
+
threadId: messagesMissingEmbeddings[0].threadId,
|
|
672
|
+
}, messagesMissingEmbeddings.map((m) => m.message));
|
|
673
|
+
if (!embeddings) {
|
|
674
|
+
if (!this.options.textEmbedding) {
|
|
675
|
+
throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
|
|
676
|
+
}
|
|
677
|
+
throw new Error("No embeddings were generated for these messages: " +
|
|
678
|
+
messagesMissingEmbeddings.map((m) => m._id).join(", "));
|
|
679
|
+
}
|
|
680
|
+
await ctx.runMutation(this.component.vector.index.insertBatch, {
|
|
681
|
+
vectorDimension: embeddings.dimension,
|
|
682
|
+
vectors: messagesMissingEmbeddings
|
|
683
|
+
.map((m, i) => ({
|
|
684
|
+
messageId: m._id,
|
|
685
|
+
model: embeddings.model,
|
|
686
|
+
table: "messages",
|
|
687
|
+
userId: m.userId,
|
|
688
|
+
threadId: m.threadId,
|
|
689
|
+
vector: embeddings.vectors[i],
|
|
690
|
+
}))
|
|
691
|
+
.filter((v) => v.vector !== null),
|
|
580
692
|
});
|
|
581
|
-
|
|
582
|
-
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Explicitly save a "step" created by the AI SDK.
|
|
696
|
+
* @param ctx The ctx argument to a mutation or action.
|
|
697
|
+
* @param args The Step generated by the AI SDK.
|
|
698
|
+
*/
|
|
699
|
+
async saveStep(ctx, args) {
|
|
700
|
+
const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
|
|
701
|
+
provider: args.provider ?? this.options.chat.provider,
|
|
702
|
+
model: args.model ?? this.options.chat.modelId,
|
|
703
|
+
});
|
|
704
|
+
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
705
|
+
const saved = await ctx.runMutation(this.component.messages.addMessages, {
|
|
706
|
+
userId: args.userId,
|
|
707
|
+
threadId: args.threadId,
|
|
708
|
+
agentName: this.options.name,
|
|
709
|
+
promptMessageId: args.promptMessageId,
|
|
710
|
+
messages,
|
|
711
|
+
embeddings,
|
|
712
|
+
failPendingSteps: false,
|
|
713
|
+
});
|
|
714
|
+
return saved;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Manually save the result of a generateObject call to the thread.
|
|
718
|
+
* This happens automatically when using {@link generateObject} or {@link streamObject}
|
|
719
|
+
* from the `thread` object created by {@link continueThread} or {@link createThread}.
|
|
720
|
+
* @param ctx The context passed from the mutation or action function calling this.
|
|
721
|
+
* @param args The arguments to the saveObject function.
|
|
722
|
+
*/
|
|
723
|
+
async saveObject(ctx, args) {
|
|
724
|
+
const { messages } = serializeObjectResult(args.result, {
|
|
725
|
+
model: this.options.chat.modelId,
|
|
726
|
+
provider: this.options.chat.provider,
|
|
727
|
+
});
|
|
728
|
+
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
729
|
+
await ctx.runMutation(this.component.messages.addMessages, {
|
|
730
|
+
userId: args.userId,
|
|
731
|
+
threadId: args.threadId,
|
|
732
|
+
promptMessageId: args.promptMessageId,
|
|
733
|
+
failPendingSteps: false,
|
|
734
|
+
messages,
|
|
735
|
+
embeddings,
|
|
736
|
+
agentName: this.options.name,
|
|
737
|
+
pending: false,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Commit or rollback a message that was pending.
|
|
742
|
+
* This is done automatically when saving messages by default.
|
|
743
|
+
* If creating pending messages, you can call this when the full "transaction" is done.
|
|
744
|
+
* @param ctx The ctx argument to your mutation or action.
|
|
745
|
+
* @param args What message to save. Generally the parent message sent into
|
|
746
|
+
* the generateText call.
|
|
747
|
+
*/
|
|
748
|
+
async completeMessage(ctx, args) {
|
|
749
|
+
const result = args.result;
|
|
750
|
+
if (result.kind === "success") {
|
|
751
|
+
await ctx.runMutation(this.component.messages.commitMessage, {
|
|
752
|
+
messageId: args.messageId,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
757
|
+
messageId: args.messageId,
|
|
758
|
+
error: result.error,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
583
761
|
}
|
|
584
762
|
async _saveMessagesAndFetchContext(ctx, args, { userId: argsUserId, threadId, contextOptions, storageOptions, }) {
|
|
585
763
|
contextOptions ||= this.options.contextOptions;
|
|
@@ -620,8 +798,9 @@ export class Agent {
|
|
|
620
798
|
: undefined;
|
|
621
799
|
if (threadId &&
|
|
622
800
|
messages.length &&
|
|
801
|
+
storageOptions?.saveMessages !== "none" &&
|
|
623
802
|
storageOptions?.saveAnyInputMessages !== false) {
|
|
624
|
-
const saveAll = storageOptions?.
|
|
803
|
+
const saveAll = storageOptions?.saveMessages === "all";
|
|
625
804
|
const coreMessages = saveAll ? messages : messages.slice(-1);
|
|
626
805
|
const saved = await this.saveMessages(ctx, {
|
|
627
806
|
threadId,
|
|
@@ -653,160 +832,9 @@ export class Agent {
|
|
|
653
832
|
stepOrder,
|
|
654
833
|
};
|
|
655
834
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
* resulting messages to the thread, if specified.
|
|
660
|
-
* Use {@link continueThread} to get a version of this function already scoped
|
|
661
|
-
* to a thread (and optionally userId).
|
|
662
|
-
*/
|
|
663
|
-
async generateObject(ctx, { userId: argsUserId, threadId, usageHandler, },
|
|
664
|
-
/**
|
|
665
|
-
* The arguments to the generateObject function, similar to the ai.generateObject function.
|
|
666
|
-
*/
|
|
667
|
-
args,
|
|
668
|
-
/**
|
|
669
|
-
* The {@link ContextOptions} and {@link StorageOptions}
|
|
670
|
-
* options to use for fetching contextual messages and saving input/output messages.
|
|
671
|
-
*/
|
|
672
|
-
options) {
|
|
673
|
-
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
674
|
-
userId: argsUserId,
|
|
675
|
-
threadId,
|
|
676
|
-
...options,
|
|
677
|
-
});
|
|
678
|
-
const { args: aiArgs, messageId, userId } = context;
|
|
679
|
-
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
680
|
-
const saveOutputMessages = options?.storageOptions?.saveOutputMessages ??
|
|
681
|
-
this.options.storageOptions?.saveOutputMessages;
|
|
682
|
-
try {
|
|
683
|
-
const result = (await generateObject(
|
|
684
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
685
|
-
aiArgs));
|
|
686
|
-
if (threadId && messageId && saveOutputMessages !== false) {
|
|
687
|
-
await this.saveObject(ctx, {
|
|
688
|
-
threadId,
|
|
689
|
-
promptMessageId: messageId,
|
|
690
|
-
result,
|
|
691
|
-
userId,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
result.messageId = messageId;
|
|
695
|
-
if (trackUsage && result.usage) {
|
|
696
|
-
await trackUsage(ctx, {
|
|
697
|
-
userId,
|
|
698
|
-
threadId,
|
|
699
|
-
agentName: this.options.name,
|
|
700
|
-
model: aiArgs.model.modelId,
|
|
701
|
-
provider: aiArgs.model.provider,
|
|
702
|
-
usage: result.usage,
|
|
703
|
-
providerMetadata: result.providerMetadata,
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
return result;
|
|
707
|
-
}
|
|
708
|
-
catch (error) {
|
|
709
|
-
if (threadId && messageId) {
|
|
710
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
711
|
-
messageId,
|
|
712
|
-
error: error.message,
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
throw error;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* This behaves like `streamObject` from the "ai" package except that
|
|
720
|
-
* it add context based on the userId and threadId and saves the input and
|
|
721
|
-
* resulting messages to the thread, if specified.
|
|
722
|
-
* Use {@link continueThread} to get a version of this function already scoped
|
|
723
|
-
* to a thread (and optionally userId).
|
|
724
|
-
*/
|
|
725
|
-
async streamObject(ctx, { userId: argsUserId, threadId, usageHandler, },
|
|
726
|
-
/**
|
|
727
|
-
* The arguments to the streamObject function, similar to the ai `streamObject` function.
|
|
728
|
-
*/
|
|
729
|
-
args,
|
|
730
|
-
/**
|
|
731
|
-
* The {@link ContextOptions} and {@link StorageOptions}
|
|
732
|
-
* options to use for fetching contextual messages and saving input/output messages.
|
|
733
|
-
*/
|
|
734
|
-
options) {
|
|
735
|
-
// TODO: unify all this shared code between all the generate* and stream* functions
|
|
736
|
-
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
737
|
-
userId: argsUserId,
|
|
738
|
-
threadId,
|
|
739
|
-
...options,
|
|
740
|
-
});
|
|
741
|
-
const { args: aiArgs, messageId, userId } = context;
|
|
742
|
-
const trackUsage = usageHandler ?? this.options.usageHandler;
|
|
743
|
-
const saveOutputMessages = options?.storageOptions?.saveOutputMessages ??
|
|
744
|
-
this.options.storageOptions?.saveOutputMessages;
|
|
745
|
-
const stream = streamObject({
|
|
746
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
747
|
-
...aiArgs,
|
|
748
|
-
onError: async (error) => {
|
|
749
|
-
console.error("onError", error);
|
|
750
|
-
return args.onError?.(error);
|
|
751
|
-
},
|
|
752
|
-
onFinish: async (result) => {
|
|
753
|
-
if (threadId && messageId && saveOutputMessages !== false) {
|
|
754
|
-
await this.saveObject(ctx, {
|
|
755
|
-
userId,
|
|
756
|
-
threadId,
|
|
757
|
-
promptMessageId: messageId,
|
|
758
|
-
result: {
|
|
759
|
-
object: result.object,
|
|
760
|
-
finishReason: "stop",
|
|
761
|
-
usage: result.usage,
|
|
762
|
-
warnings: result.warnings,
|
|
763
|
-
request: await stream.request,
|
|
764
|
-
response: result.response,
|
|
765
|
-
providerMetadata: result.providerMetadata,
|
|
766
|
-
experimental_providerMetadata: result.experimental_providerMetadata,
|
|
767
|
-
logprobs: undefined,
|
|
768
|
-
toJsonResponse: stream.toTextStreamResponse,
|
|
769
|
-
},
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
if (trackUsage && result.usage) {
|
|
773
|
-
await trackUsage(ctx, {
|
|
774
|
-
userId,
|
|
775
|
-
threadId,
|
|
776
|
-
agentName: this.options.name,
|
|
777
|
-
model: aiArgs.model.modelId,
|
|
778
|
-
provider: aiArgs.model.provider,
|
|
779
|
-
usage: result.usage,
|
|
780
|
-
providerMetadata: result.providerMetadata,
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
784
|
-
return args.onFinish?.(result);
|
|
785
|
-
},
|
|
786
|
-
});
|
|
787
|
-
stream.messageId = messageId;
|
|
788
|
-
return stream;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Manually save the result of a generateObject call to the thread.
|
|
792
|
-
* This happens automatically when using {@link generateObject} or {@link streamObject}
|
|
793
|
-
* from the `thread` object created by {@link continueThread} or {@link createThread}.
|
|
794
|
-
* @param ctx The context passed from the mutation or action function calling this.
|
|
795
|
-
* @param args The arguments to the saveObject function.
|
|
796
|
-
*/
|
|
797
|
-
async saveObject(ctx, args) {
|
|
798
|
-
const { step, messages } = serializeObjectResult(args.result, {
|
|
799
|
-
model: this.options.chat.modelId,
|
|
800
|
-
provider: this.options.chat.provider,
|
|
801
|
-
});
|
|
802
|
-
const embeddings = await this.generateEmbeddings(messages.map((m) => m.message));
|
|
803
|
-
await ctx.runMutation(this.component.messages.addStep, {
|
|
804
|
-
userId: args.userId,
|
|
805
|
-
threadId: args.threadId,
|
|
806
|
-
promptMessageId: args.promptMessageId,
|
|
807
|
-
failPendingSteps: false,
|
|
808
|
-
step: { step, messages, embeddings },
|
|
809
|
-
});
|
|
835
|
+
_shouldSaveOutputMessages(storageOpts) {
|
|
836
|
+
const opts = storageOpts ?? this.options.storageOptions;
|
|
837
|
+
return opts?.saveOutputMessages !== false && opts?.saveMessages !== "none";
|
|
810
838
|
}
|
|
811
839
|
_mergedContextOptions(opts) {
|
|
812
840
|
const searchOptions = {
|
|
@@ -821,7 +849,7 @@ export class Agent {
|
|
|
821
849
|
: undefined,
|
|
822
850
|
};
|
|
823
851
|
}
|
|
824
|
-
async
|
|
852
|
+
async _searchOptionsWithEmbeddingAndDefaults(ctx, { userId, threadId }, contextOptions, messages) {
|
|
825
853
|
assert(contextOptions.searchOptions?.textSearch ||
|
|
826
854
|
contextOptions.searchOptions?.vectorSearch, "searchOptions is required");
|
|
827
855
|
assert(messages.length > 0, "Core messages cannot be empty");
|
|
@@ -837,14 +865,45 @@ export class Agent {
|
|
|
837
865
|
if (contextOptions.searchOptions?.vectorSearch &&
|
|
838
866
|
text &&
|
|
839
867
|
this.options.textEmbedding) {
|
|
840
|
-
search.vector = (await this.
|
|
868
|
+
search.vector = (await this.doEmbed(ctx, {
|
|
869
|
+
threadId,
|
|
870
|
+
userId,
|
|
841
871
|
values: [text],
|
|
842
872
|
})).embeddings[0];
|
|
843
|
-
// TODO: record usage of embeddings
|
|
844
873
|
search.vectorModel = this.options.textEmbedding.modelId;
|
|
845
874
|
}
|
|
846
875
|
return search;
|
|
847
876
|
}
|
|
877
|
+
async doEmbed(ctx, options) {
|
|
878
|
+
const embedding = this.options.textEmbedding;
|
|
879
|
+
assert(embedding, "textEmbedding is required");
|
|
880
|
+
const result = await embedding.doEmbed({
|
|
881
|
+
values: options.values,
|
|
882
|
+
abortSignal: options.abortSignal,
|
|
883
|
+
headers: options.headers,
|
|
884
|
+
});
|
|
885
|
+
if (this.options.usageHandler && result.usage) {
|
|
886
|
+
await this.options.usageHandler(ctx, {
|
|
887
|
+
userId: options.userId,
|
|
888
|
+
threadId: options.threadId,
|
|
889
|
+
agentName: this.options.name,
|
|
890
|
+
model: embedding.modelId,
|
|
891
|
+
provider: embedding.provider,
|
|
892
|
+
providerMetadata: result.rawResponse
|
|
893
|
+
? { [embedding.provider]: result.rawResponse }
|
|
894
|
+
: undefined,
|
|
895
|
+
usage: {
|
|
896
|
+
promptTokens: result.usage.tokens,
|
|
897
|
+
completionTokens: 0,
|
|
898
|
+
totalTokens: result.usage.tokens,
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return { embeddings: result.embeddings };
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* WORKFLOW UTILITIES
|
|
906
|
+
*/
|
|
848
907
|
/**
|
|
849
908
|
* Create a mutation that creates a thread so you can call it from a Workflow.
|
|
850
909
|
* e.g.
|
|
@@ -945,6 +1004,49 @@ export class Agent {
|
|
|
945
1004
|
},
|
|
946
1005
|
});
|
|
947
1006
|
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Save messages to the thread.
|
|
1009
|
+
* Useful as a step in Workflows, e.g.
|
|
1010
|
+
* ```ts
|
|
1011
|
+
* const saveMessages = agent.asSaveMessagesMutation();
|
|
1012
|
+
*
|
|
1013
|
+
* const myWorkflow = workflow.define({
|
|
1014
|
+
* args: {...},
|
|
1015
|
+
* handler: async (step, args) => {
|
|
1016
|
+
* // do things to create (but not save)messages
|
|
1017
|
+
* const { messageIds } = await step.runMutation(internal.foo.saveMessages, {
|
|
1018
|
+
* threadId: args.threadId,
|
|
1019
|
+
* messages: args.messages,
|
|
1020
|
+
* });
|
|
1021
|
+
* // ...
|
|
1022
|
+
* },
|
|
1023
|
+
* })
|
|
1024
|
+
* ```
|
|
1025
|
+
* @returns A mutation that can be used to save messages to the thread.
|
|
1026
|
+
*/
|
|
1027
|
+
asSaveMessagesMutation() {
|
|
1028
|
+
return internalMutationGeneric({
|
|
1029
|
+
args: {
|
|
1030
|
+
threadId: v.string(),
|
|
1031
|
+
userId: v.optional(v.string()),
|
|
1032
|
+
promptMessageId: v.optional(v.string()),
|
|
1033
|
+
messages: v.array(vMessageWithMetadata),
|
|
1034
|
+
pending: v.optional(v.boolean()),
|
|
1035
|
+
failPendingSteps: v.optional(v.boolean()),
|
|
1036
|
+
},
|
|
1037
|
+
handler: async (ctx, args) => {
|
|
1038
|
+
const { lastMessageId, messages } = await this.saveMessages(ctx, {
|
|
1039
|
+
...args,
|
|
1040
|
+
messages: args.messages.map((m) => m.message),
|
|
1041
|
+
metadata: args.messages.map(({ message: _, ...m }) => m),
|
|
1042
|
+
});
|
|
1043
|
+
return {
|
|
1044
|
+
lastMessageId,
|
|
1045
|
+
messageIds: messages.map((m) => m._id),
|
|
1046
|
+
};
|
|
1047
|
+
},
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
948
1050
|
}
|
|
949
1051
|
export function filterOutOrphanedToolMessages(docs) {
|
|
950
1052
|
const toolCallIds = new Set();
|