@convex-dev/agent 0.2.0-alpha.2 → 0.2.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/dist/client/createTool.d.ts +4 -6
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +0 -1
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +28 -28
- package/dist/client/files.d.ts +9 -2
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +61 -6
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +232 -242
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +375 -383
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +91 -373
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +7 -1
- package/dist/client/messages.js.map +1 -1
- package/dist/client/search.d.ts +15 -15
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +3 -3
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +30 -22
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +36 -40
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/types.d.ts +148 -61
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +47 -357
- package/dist/component/messages.d.ts +157 -150
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +124 -70
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +1147 -2687
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +5 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +29 -323
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +104 -60
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +4 -4
- package/dist/component/users.d.ts +3 -3
- package/dist/mapping.d.ts +4 -1
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +17 -1
- package/dist/mapping.js.map +1 -1
- package/dist/parts.d.ts +3 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +34 -0
- package/dist/parts.js.map +1 -0
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/deltas.d.ts +3 -2
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +19 -79
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +31 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +2 -8
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +1 -1
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/shared.d.ts +3 -3
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +109 -1590
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +4 -81
- package/dist/validators.js.map +1 -1
- package/package.json +3 -3
- package/src/client/createTool.ts +5 -10
- package/src/client/files.ts +89 -11
- package/src/client/index.test.ts +25 -35
- package/src/client/index.ts +686 -652
- package/src/client/messages.ts +16 -2
- package/src/client/search.ts +10 -7
- package/src/client/streaming.ts +62 -57
- package/src/client/types.ts +227 -97
- package/src/component/_generated/api.d.ts +47 -357
- package/src/component/messages.ts +157 -96
- package/src/component/schema.ts +4 -4
- package/src/component/streams.ts +180 -69
- package/src/mapping.ts +23 -2
- package/src/parts.ts +39 -0
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/deltas.test.ts +38 -42
- package/src/react/deltas.ts +24 -86
- package/src/react/index.ts +35 -13
- package/src/react/optimisticallySendMessage.ts +2 -8
- package/src/react/useSmoothText.ts +14 -15
- package/src/shared.ts +3 -3
- package/src/validators.test.ts +0 -17
- package/src/validators.ts +8 -101
package/dist/client/index.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { embedMany, generateObject, generateText, stepCountIs, streamObject, streamText, } from "ai";
|
|
2
2
|
import { assert, omit, pick } from "convex-helpers";
|
|
3
3
|
import { internalActionGeneric, internalMutationGeneric, } from "convex/server";
|
|
4
|
-
import { v } from "convex/values";
|
|
4
|
+
import { convexToJson, v } from "convex/values";
|
|
5
5
|
import { validateVectorDimension, } from "../component/vector/tables.js";
|
|
6
6
|
import { deserializeMessage, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
|
|
7
7
|
import { extractText, isTool } from "../shared.js";
|
|
8
8
|
import { vMessageEmbeddings, vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
|
|
9
9
|
import { createTool, wrapTools } from "./createTool.js";
|
|
10
10
|
import { listMessages, saveMessages, } from "./messages.js";
|
|
11
|
-
import { createThread, getThreadMetadata } from "./threads.js";
|
|
12
11
|
import { fetchContextMessages, getModelName, getProviderName, } from "./search.js";
|
|
13
12
|
import { DeltaStreamer, mergeTransforms, syncStreams, } from "./streaming.js";
|
|
13
|
+
import { createThread, getThreadMetadata } from "./threads.js";
|
|
14
|
+
import { inlineMessagesFiles } from "./files.js";
|
|
14
15
|
export { stepCountIs } from "ai";
|
|
15
16
|
export { vMessageDoc, vThreadDoc } from "../component/schema.js";
|
|
16
|
-
export {
|
|
17
|
+
export { deserializeMessage, serializeDataOrUrl, serializeMessage, } from "../mapping.js";
|
|
17
18
|
// NOTE: these are also exported via @convex-dev/agent/validators
|
|
18
19
|
// a future version may put them all here or move these over there
|
|
19
20
|
export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
|
|
21
|
+
export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
|
|
20
22
|
export { getFile, storeFile } from "./files.js";
|
|
21
|
-
export { filterOutOrphanedToolMessages, fetchContextMessages, } from "./search.js";
|
|
22
|
-
export { abortStream, listStreams, syncStreams } from "./streaming.js";
|
|
23
23
|
export { listMessages, saveMessage, saveMessages, } from "./messages.js";
|
|
24
|
-
export {
|
|
24
|
+
export { fetchContextMessages, filterOutOrphanedToolMessages, } from "./search.js";
|
|
25
|
+
export { abortStream, listStreams, syncStreams } from "./streaming.js";
|
|
26
|
+
export { createThread, getThreadMetadata, updateThreadMetadata, searchThreadTitles, } from "./threads.js";
|
|
25
27
|
export { createTool, extractText, isTool };
|
|
26
|
-
export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
|
|
27
28
|
export class Agent {
|
|
28
29
|
component;
|
|
29
30
|
options;
|
|
@@ -39,8 +40,6 @@ export class Agent {
|
|
|
39
40
|
const { thread } = await this.continueThread(ctx, {
|
|
40
41
|
threadId,
|
|
41
42
|
userId: args?.userId,
|
|
42
|
-
usageHandler: args?.usageHandler,
|
|
43
|
-
tools: args?.tools,
|
|
44
43
|
});
|
|
45
44
|
return { threadId, thread };
|
|
46
45
|
}
|
|
@@ -70,90 +69,188 @@ export class Agent {
|
|
|
70
69
|
},
|
|
71
70
|
};
|
|
72
71
|
}
|
|
72
|
+
async start(ctx,
|
|
73
73
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* @param ctx The context passed from the action function calling this.
|
|
80
|
-
* @param { userId, threadId }: The user and thread to associate the message with
|
|
81
|
-
* @param args The arguments to the generateText function, along with extra controls
|
|
82
|
-
* for the {@link ContextOptions} and {@link StorageOptions}.
|
|
83
|
-
* @returns The result of the generateText function.
|
|
74
|
+
* These are the arguments you'll pass to the LLM call such as
|
|
75
|
+
* `generateText` or `streamText`. This function will look up the context
|
|
76
|
+
* and provide functions to save the steps, abort the generation, and more.
|
|
77
|
+
* The type of the arguments returned infers from the type of the arguments
|
|
78
|
+
* you pass here.
|
|
84
79
|
*/
|
|
85
|
-
|
|
86
|
-
const opts = { ...this.options, ...options
|
|
80
|
+
args, options) {
|
|
81
|
+
const { threadId, ...opts } = { ...this.options, ...options };
|
|
87
82
|
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
88
|
-
userId:
|
|
89
|
-
threadId,
|
|
83
|
+
userId: options?.userId,
|
|
84
|
+
threadId: options?.threadId,
|
|
90
85
|
...opts,
|
|
91
86
|
});
|
|
92
|
-
|
|
87
|
+
let pendingMessageId = context.pendingMessageId;
|
|
88
|
+
// TODO: extract pending message if one exists
|
|
89
|
+
const { args: aiArgs, promptMessageId, order, stepOrder, userId } = context;
|
|
93
90
|
const messages = context.savedMessages ?? [];
|
|
91
|
+
if (pendingMessageId) {
|
|
92
|
+
if (!aiArgs._internal?.generateId) {
|
|
93
|
+
aiArgs._internal = {
|
|
94
|
+
...aiArgs._internal,
|
|
95
|
+
generateId: () => pendingMessageId ?? crypto.randomUUID(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
94
99
|
const toolCtx = {
|
|
95
100
|
...ctx,
|
|
96
101
|
userId,
|
|
97
102
|
threadId,
|
|
98
|
-
|
|
103
|
+
promptMessageId,
|
|
99
104
|
agent: this,
|
|
100
105
|
};
|
|
101
|
-
const tools = wrapTools(toolCtx, args.tools ??
|
|
106
|
+
const tools = wrapTools(toolCtx, args.tools ?? this.options.tools);
|
|
102
107
|
const saveOutput = opts.storageOptions?.saveMessages !== "none";
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
const fail = async (reason) => {
|
|
109
|
+
if (threadId && promptMessageId) {
|
|
110
|
+
console.error("RollbackMessage", promptMessageId, reason);
|
|
111
|
+
}
|
|
112
|
+
if (pendingMessageId) {
|
|
113
|
+
await ctx.runMutation(this.component.messages.finalizeMessage, {
|
|
114
|
+
messageId: pendingMessageId,
|
|
115
|
+
result: { status: "failed", error: reason },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
let activeModel = aiArgs.model;
|
|
120
|
+
if (aiArgs.abortSignal) {
|
|
121
|
+
const abortSignal = aiArgs.abortSignal;
|
|
122
|
+
aiArgs.abortSignal.addEventListener("abort", async () => {
|
|
123
|
+
await fail(abortSignal.reason ?? "Aborted");
|
|
124
|
+
}, { once: true });
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
args: {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
stopWhen: args.stopWhen ?? this.options.stopWhen,
|
|
107
130
|
...aiArgs,
|
|
108
131
|
tools,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
// abortSignal: abortController.signal,
|
|
133
|
+
},
|
|
134
|
+
order: order ?? 0,
|
|
135
|
+
stepOrder: stepOrder ?? 0,
|
|
136
|
+
userId,
|
|
137
|
+
promptMessageId,
|
|
138
|
+
getSavedMessages: () => messages,
|
|
139
|
+
updateModel: (model) => {
|
|
140
|
+
if (model) {
|
|
141
|
+
activeModel = model;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
fail,
|
|
145
|
+
save: async (toSave, createPendingMessage) => {
|
|
146
|
+
if (threadId && promptMessageId && saveOutput) {
|
|
147
|
+
const metadata = {
|
|
148
|
+
// TODO: get up to date one when user selects mid-generation
|
|
149
|
+
model: getModelName(activeModel),
|
|
150
|
+
provider: getProviderName(activeModel),
|
|
151
|
+
};
|
|
152
|
+
const serialized = "object" in toSave
|
|
153
|
+
? await serializeObjectResult(ctx, this.component, toSave.object, metadata)
|
|
154
|
+
: await serializeNewMessagesInStep(ctx, this.component, toSave.step, metadata);
|
|
155
|
+
const embeddings = await this.generateEmbeddings(ctx, { userId, threadId }, serialized.messages.map((m) => m.message));
|
|
156
|
+
if (createPendingMessage) {
|
|
157
|
+
serialized.messages.push({
|
|
158
|
+
message: { role: "assistant", content: [] },
|
|
159
|
+
status: "pending",
|
|
118
160
|
});
|
|
119
|
-
|
|
161
|
+
embeddings?.vectors.push(null);
|
|
120
162
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
163
|
+
const saved = await ctx.runMutation(this.component.messages.addMessages, {
|
|
164
|
+
userId,
|
|
165
|
+
threadId,
|
|
166
|
+
agentName: this.options.name,
|
|
167
|
+
promptMessageId,
|
|
168
|
+
pendingMessageId,
|
|
169
|
+
messages: serialized.messages,
|
|
170
|
+
embeddings,
|
|
171
|
+
failPendingSteps: false,
|
|
172
|
+
});
|
|
173
|
+
const lastMessage = saved.messages.at(-1);
|
|
174
|
+
if (createPendingMessage) {
|
|
175
|
+
if (lastMessage.status === "failed") {
|
|
176
|
+
pendingMessageId = undefined;
|
|
177
|
+
messages.push(...saved.messages);
|
|
178
|
+
await fail(lastMessage.error ??
|
|
179
|
+
"Aborting - the pending message was marked as failed");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
pendingMessageId = lastMessage._id;
|
|
183
|
+
messages.push(...saved.messages.slice(0, -1));
|
|
184
|
+
}
|
|
129
185
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
threadId,
|
|
134
|
-
agentName: this.options.name,
|
|
135
|
-
model: aiArgs.model.modelId,
|
|
136
|
-
provider: aiArgs.model.provider,
|
|
137
|
-
usage: step.usage,
|
|
138
|
-
providerMetadata: step.providerMetadata,
|
|
139
|
-
});
|
|
186
|
+
else {
|
|
187
|
+
pendingMessageId = undefined;
|
|
188
|
+
messages.push(...saved.messages);
|
|
140
189
|
}
|
|
141
|
-
|
|
190
|
+
}
|
|
191
|
+
const output = "object" in toSave ? toSave.object : toSave.step;
|
|
192
|
+
if (this.options.rawRequestResponseHandler) {
|
|
193
|
+
await this.options.rawRequestResponseHandler(ctx, {
|
|
194
|
+
userId,
|
|
195
|
+
threadId,
|
|
196
|
+
agentName: this.options.name,
|
|
197
|
+
request: output.request,
|
|
198
|
+
response: output.response,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (opts.usageHandler && output.usage) {
|
|
202
|
+
await opts.usageHandler(ctx, {
|
|
203
|
+
userId,
|
|
204
|
+
threadId,
|
|
205
|
+
agentName: this.options.name,
|
|
206
|
+
model: getModelName(activeModel),
|
|
207
|
+
provider: getProviderName(activeModel),
|
|
208
|
+
usage: output.usage,
|
|
209
|
+
providerMetadata: output.providerMetadata,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* This behaves like {@link generateText} from the "ai" package except that
|
|
217
|
+
* it add context based on the userId and threadId and saves the input and
|
|
218
|
+
* resulting messages to the thread, if specified.
|
|
219
|
+
* Use {@link continueThread} to get a version of this function already scoped
|
|
220
|
+
* to a thread (and optionally userId).
|
|
221
|
+
* @param ctx The context passed from the action function calling this.
|
|
222
|
+
* @param { userId, threadId }: The user and thread to associate the message with
|
|
223
|
+
* @param generateTextArgs The arguments to the generateText function, along with extra controls
|
|
224
|
+
* for the {@link ContextOptions} and {@link StorageOptions}.
|
|
225
|
+
* @returns The result of the generateText function.
|
|
226
|
+
*/
|
|
227
|
+
async generateText(ctx, threadOpts, generateTextArgs, options) {
|
|
228
|
+
const { args, promptMessageId, order, ...call } = await this.start(ctx, generateTextArgs, { ...threadOpts, ...options });
|
|
229
|
+
const steps = [];
|
|
230
|
+
try {
|
|
231
|
+
const result = (await generateText({
|
|
232
|
+
...args,
|
|
233
|
+
prepareStep: async (options) => {
|
|
234
|
+
const result = await generateTextArgs.prepareStep?.(options);
|
|
235
|
+
call.updateModel(result?.model ?? options.model);
|
|
236
|
+
return result;
|
|
237
|
+
},
|
|
238
|
+
onStepFinish: async (step) => {
|
|
239
|
+
steps.push(step);
|
|
240
|
+
await call.save({ step }, await willContinue(steps, args.stopWhen));
|
|
241
|
+
return generateTextArgs.onStepFinish?.(step);
|
|
142
242
|
},
|
|
143
243
|
}));
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
244
|
+
const metadata = {
|
|
245
|
+
promptMessageId,
|
|
246
|
+
order,
|
|
247
|
+
savedMessages: call.getSavedMessages(),
|
|
248
|
+
messageId: promptMessageId,
|
|
249
|
+
};
|
|
250
|
+
return Object.assign(result, metadata);
|
|
148
251
|
}
|
|
149
252
|
catch (error) {
|
|
150
|
-
|
|
151
|
-
console.error("RollbackMessage", messageId);
|
|
152
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
153
|
-
messageId,
|
|
154
|
-
error: error.message,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
253
|
+
await call.fail(errorToString(error));
|
|
157
254
|
throw error;
|
|
158
255
|
}
|
|
159
256
|
}
|
|
@@ -164,112 +261,83 @@ export class Agent {
|
|
|
164
261
|
* Use {@link continueThread} to get a version of this function already scoped
|
|
165
262
|
* to a thread (and optionally userId).
|
|
166
263
|
*/
|
|
167
|
-
async streamText(ctx,
|
|
168
|
-
/** Note: to get better type inference, pass tools in the next arg */
|
|
169
|
-
tools: threadTools, ...usageHandler },
|
|
264
|
+
async streamText(ctx, threadOpts,
|
|
170
265
|
/**
|
|
171
266
|
* The arguments to the streamText function, similar to the ai `streamText` function.
|
|
172
267
|
*/
|
|
173
|
-
|
|
268
|
+
streamTextArgs,
|
|
174
269
|
/**
|
|
175
270
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
176
271
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
177
272
|
*/
|
|
178
273
|
options) {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
...opts,
|
|
184
|
-
});
|
|
185
|
-
const { args: aiArgs, messageId, order, stepOrder, userId } = context;
|
|
186
|
-
const messages = context.savedMessages ?? [];
|
|
187
|
-
const toolCtx = {
|
|
188
|
-
...ctx,
|
|
189
|
-
userId,
|
|
190
|
-
threadId,
|
|
191
|
-
messageId,
|
|
192
|
-
agent: this,
|
|
193
|
-
};
|
|
194
|
-
const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
|
|
195
|
-
const saveOutput = opts.storageOptions?.saveMessages !== "none";
|
|
274
|
+
const { threadId } = threadOpts;
|
|
275
|
+
const { args, userId, order, stepOrder, promptMessageId, ...call } = await this.start(ctx, streamTextArgs, { ...threadOpts, ...options });
|
|
276
|
+
const steps = [];
|
|
277
|
+
const opts = { ...this.options, ...options };
|
|
196
278
|
const streamer = threadId && opts.saveStreamDeltas
|
|
197
279
|
? new DeltaStreamer(this.component, ctx, opts.saveStreamDeltas, {
|
|
198
280
|
threadId,
|
|
199
281
|
userId,
|
|
200
282
|
agentName: this.options.name,
|
|
201
|
-
model:
|
|
202
|
-
provider:
|
|
203
|
-
providerOptions:
|
|
283
|
+
model: getModelName(args.model),
|
|
284
|
+
provider: getProviderName(args.model),
|
|
285
|
+
providerOptions: args.providerOptions,
|
|
204
286
|
order,
|
|
205
287
|
stepOrder,
|
|
206
|
-
abortSignal:
|
|
288
|
+
abortSignal: args.abortSignal,
|
|
207
289
|
})
|
|
208
290
|
: undefined;
|
|
209
291
|
const result = streamText({
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
|
|
215
|
-
experimental_transform: mergeTransforms(options?.saveStreamDeltas, args.experimental_transform),
|
|
292
|
+
...args,
|
|
293
|
+
abortSignal: streamer?.abortController.signal ?? args.abortSignal,
|
|
294
|
+
// TODO: this is probably why reasoning isn't streaming
|
|
295
|
+
experimental_transform: mergeTransforms(options?.saveStreamDeltas, streamTextArgs.experimental_transform),
|
|
216
296
|
onChunk: async (event) => {
|
|
217
297
|
await streamer?.addParts([event.chunk]);
|
|
218
298
|
// console.log("onChunk", chunk);
|
|
219
|
-
return
|
|
299
|
+
return streamTextArgs.onChunk?.(event);
|
|
220
300
|
},
|
|
221
301
|
onError: async (error) => {
|
|
222
302
|
console.error("onError", error);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
303
|
+
await call.fail(errorToString(error.error));
|
|
304
|
+
await streamer?.fail(errorToString(error.error));
|
|
305
|
+
return streamTextArgs.onError?.(error);
|
|
306
|
+
},
|
|
307
|
+
// onFinish: async (event) => {
|
|
308
|
+
// return streamTextArgs.onFinish?.(event);
|
|
309
|
+
// },
|
|
310
|
+
prepareStep: async (options) => {
|
|
311
|
+
const result = await streamTextArgs.prepareStep?.(options);
|
|
312
|
+
if (result) {
|
|
313
|
+
const model = result.model ?? options.model;
|
|
314
|
+
call.updateModel(model);
|
|
315
|
+
return result;
|
|
228
316
|
}
|
|
229
|
-
|
|
230
|
-
return args.onError?.(error);
|
|
317
|
+
return undefined;
|
|
231
318
|
},
|
|
232
319
|
onStepFinish: async (step) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
model: aiArgs.model.modelId,
|
|
239
|
-
provider: aiArgs.model.provider,
|
|
240
|
-
promptMessageId: messageId,
|
|
241
|
-
step,
|
|
242
|
-
});
|
|
243
|
-
await streamer?.finish(saved.messages);
|
|
244
|
-
messages.push(...saved.messages);
|
|
245
|
-
}
|
|
246
|
-
if (this.options.rawRequestResponseHandler) {
|
|
247
|
-
await this.options.rawRequestResponseHandler(ctx, {
|
|
248
|
-
userId,
|
|
249
|
-
threadId,
|
|
250
|
-
agentName: this.options.name,
|
|
251
|
-
request: step.request,
|
|
252
|
-
response: step.response,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
if (opts.usageHandler && step.usage) {
|
|
256
|
-
await opts.usageHandler(ctx, {
|
|
257
|
-
userId,
|
|
258
|
-
threadId,
|
|
259
|
-
agentName: this.options.name,
|
|
260
|
-
model: aiArgs.model.modelId,
|
|
261
|
-
provider: aiArgs.model.provider,
|
|
262
|
-
usage: step.usage,
|
|
263
|
-
providerMetadata: step.providerMetadata,
|
|
264
|
-
});
|
|
320
|
+
steps.push(step);
|
|
321
|
+
const createPendingMessage = await willContinue(steps, args.stopWhen);
|
|
322
|
+
await call.save({ step }, createPendingMessage);
|
|
323
|
+
if (!createPendingMessage) {
|
|
324
|
+
await streamer?.finish();
|
|
265
325
|
}
|
|
266
326
|
return args.onStepFinish?.(step);
|
|
267
327
|
},
|
|
268
328
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
329
|
+
const metadata = {
|
|
330
|
+
promptMessageId,
|
|
331
|
+
order,
|
|
332
|
+
savedMessages: call.getSavedMessages(),
|
|
333
|
+
messageId: promptMessageId,
|
|
334
|
+
};
|
|
335
|
+
if ((typeof options?.saveStreamDeltas === "object" &&
|
|
336
|
+
!options.saveStreamDeltas.returnImmediately) ||
|
|
337
|
+
options?.saveStreamDeltas === true) {
|
|
338
|
+
await result.consumeStream();
|
|
339
|
+
}
|
|
340
|
+
return Object.assign(result, metadata);
|
|
273
341
|
}
|
|
274
342
|
/**
|
|
275
343
|
* This behaves like {@link generateObject} from the "ai" package except that
|
|
@@ -278,72 +346,30 @@ export class Agent {
|
|
|
278
346
|
* Use {@link continueThread} to get a version of this function already scoped
|
|
279
347
|
* to a thread (and optionally userId).
|
|
280
348
|
*/
|
|
281
|
-
async generateObject(ctx,
|
|
349
|
+
async generateObject(ctx, threadOpts,
|
|
282
350
|
/**
|
|
283
351
|
* The arguments to the generateObject function, similar to the ai.generateObject function.
|
|
284
352
|
*/
|
|
285
|
-
|
|
353
|
+
generateObjectArgs,
|
|
286
354
|
/**
|
|
287
355
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
288
356
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
289
357
|
*/
|
|
290
358
|
options) {
|
|
291
|
-
const
|
|
292
|
-
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
293
|
-
userId: argsUserId ?? undefined,
|
|
294
|
-
threadId,
|
|
295
|
-
...opts,
|
|
296
|
-
});
|
|
297
|
-
const { args: aiArgs, messageId, order, userId } = context;
|
|
298
|
-
const messages = context.savedMessages ?? [];
|
|
299
|
-
const saveOutput = opts.storageOptions?.saveMessages !== "none";
|
|
359
|
+
const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
|
|
300
360
|
try {
|
|
301
|
-
const result = (await generateObject(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
model: aiArgs.model.modelId,
|
|
311
|
-
provider: aiArgs.model.provider,
|
|
312
|
-
});
|
|
313
|
-
messages.push(...saved.messages);
|
|
314
|
-
}
|
|
315
|
-
result.messageId = messageId;
|
|
316
|
-
result.order = order;
|
|
317
|
-
result.messages = messages;
|
|
318
|
-
if (this.options.rawRequestResponseHandler) {
|
|
319
|
-
await this.options.rawRequestResponseHandler(ctx, {
|
|
320
|
-
userId,
|
|
321
|
-
threadId,
|
|
322
|
-
agentName: this.options.name,
|
|
323
|
-
request: result.request,
|
|
324
|
-
response: result.response,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (opts.usageHandler && result.usage) {
|
|
328
|
-
await opts.usageHandler(ctx, {
|
|
329
|
-
userId,
|
|
330
|
-
threadId,
|
|
331
|
-
agentName: this.options.name,
|
|
332
|
-
model: aiArgs.model.modelId,
|
|
333
|
-
provider: aiArgs.model.provider,
|
|
334
|
-
usage: result.usage,
|
|
335
|
-
providerMetadata: result.providerMetadata,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
return result;
|
|
361
|
+
const result = (await generateObject(args));
|
|
362
|
+
await save({ object: result });
|
|
363
|
+
const metadata = {
|
|
364
|
+
promptMessageId,
|
|
365
|
+
order,
|
|
366
|
+
savedMessages: getSavedMessages(),
|
|
367
|
+
messageId: promptMessageId,
|
|
368
|
+
};
|
|
369
|
+
return Object.assign(result, metadata);
|
|
339
370
|
}
|
|
340
371
|
catch (error) {
|
|
341
|
-
|
|
342
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
343
|
-
messageId,
|
|
344
|
-
error: error.message,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
372
|
+
await fail(errorToString(error));
|
|
347
373
|
throw error;
|
|
348
374
|
}
|
|
349
375
|
}
|
|
@@ -354,83 +380,50 @@ export class Agent {
|
|
|
354
380
|
* Use {@link continueThread} to get a version of this function already scoped
|
|
355
381
|
* to a thread (and optionally userId).
|
|
356
382
|
*/
|
|
357
|
-
async streamObject(ctx,
|
|
383
|
+
async streamObject(ctx, threadOpts,
|
|
358
384
|
/**
|
|
359
385
|
* The arguments to the streamObject function, similar to the ai `streamObject` function.
|
|
360
386
|
*/
|
|
361
|
-
|
|
387
|
+
streamObjectArgs,
|
|
362
388
|
/**
|
|
363
389
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
364
390
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
365
391
|
*/
|
|
366
392
|
options) {
|
|
367
|
-
|
|
368
|
-
const opts = { ...this.options, ...options, ...usageHandler };
|
|
369
|
-
const context = await this._saveMessagesAndFetchContext(ctx, args, {
|
|
370
|
-
userId: argsUserId ?? undefined,
|
|
371
|
-
threadId,
|
|
372
|
-
...opts,
|
|
373
|
-
});
|
|
374
|
-
const { args: aiArgs, messageId, order, userId } = context;
|
|
375
|
-
const messages = context.savedMessages ?? [];
|
|
376
|
-
const saveOutput = opts.storageOptions?.saveMessages !== "none";
|
|
377
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
393
|
+
const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
|
|
378
394
|
const stream = streamObject({
|
|
379
395
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
-
...
|
|
396
|
+
...args,
|
|
381
397
|
onError: async (error) => {
|
|
382
|
-
console.error("onError", error);
|
|
398
|
+
console.error(" streamObject onError", error);
|
|
399
|
+
// TODO: content that we have so far
|
|
400
|
+
// content: stream.fullStream.
|
|
401
|
+
await fail(errorToString(error.error));
|
|
383
402
|
return args.onError?.(error);
|
|
384
403
|
},
|
|
385
404
|
onFinish: async (result) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
promptMessageId: messageId,
|
|
391
|
-
result: {
|
|
392
|
-
object: result.object,
|
|
393
|
-
finishReason: "stop",
|
|
394
|
-
usage: result.usage,
|
|
395
|
-
warnings: result.warnings,
|
|
396
|
-
request: await stream.request,
|
|
397
|
-
response: result.response,
|
|
398
|
-
providerMetadata: result.providerMetadata,
|
|
399
|
-
toJsonResponse: stream.toTextStreamResponse,
|
|
400
|
-
},
|
|
401
|
-
model: aiArgs.model.modelId,
|
|
402
|
-
provider: aiArgs.model.provider,
|
|
403
|
-
});
|
|
404
|
-
messages.push(...saved.messages);
|
|
405
|
-
}
|
|
406
|
-
if (opts.usageHandler && result.usage) {
|
|
407
|
-
await opts.usageHandler(ctx, {
|
|
408
|
-
userId,
|
|
409
|
-
threadId,
|
|
410
|
-
agentName: this.options.name,
|
|
411
|
-
model: aiArgs.model.modelId,
|
|
412
|
-
provider: aiArgs.model.provider,
|
|
405
|
+
await save({
|
|
406
|
+
object: {
|
|
407
|
+
object: result.object,
|
|
408
|
+
finishReason: result.error ? "error" : "stop",
|
|
413
409
|
usage: result.usage,
|
|
414
|
-
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
if (this.options.rawRequestResponseHandler) {
|
|
418
|
-
await this.options.rawRequestResponseHandler(ctx, {
|
|
419
|
-
userId,
|
|
420
|
-
threadId,
|
|
421
|
-
agentName: this.options.name,
|
|
410
|
+
warnings: result.warnings,
|
|
422
411
|
request: await stream.request,
|
|
423
412
|
response: result.response,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
413
|
+
providerMetadata: result.providerMetadata,
|
|
414
|
+
toJsonResponse: stream.toTextStreamResponse,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
427
417
|
return args.onFinish?.(result);
|
|
428
418
|
},
|
|
429
419
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
420
|
+
const metadata = {
|
|
421
|
+
promptMessageId,
|
|
422
|
+
order,
|
|
423
|
+
savedMessages: getSavedMessages(),
|
|
424
|
+
messageId: promptMessageId,
|
|
425
|
+
};
|
|
426
|
+
return Object.assign(stream, metadata);
|
|
434
427
|
}
|
|
435
428
|
/**
|
|
436
429
|
* Save a message to the thread.
|
|
@@ -451,6 +444,7 @@ export class Agent {
|
|
|
451
444
|
: [args.message],
|
|
452
445
|
metadata: args.metadata ? [args.metadata] : undefined,
|
|
453
446
|
skipEmbeddings: args.skipEmbeddings,
|
|
447
|
+
pendingMessageId: args.pendingMessageId,
|
|
454
448
|
});
|
|
455
449
|
const message = messages.at(-1);
|
|
456
450
|
return { messageId: message._id, message };
|
|
@@ -469,7 +463,7 @@ export class Agent {
|
|
|
469
463
|
if (args.embeddings) {
|
|
470
464
|
embeddings = args.embeddings;
|
|
471
465
|
}
|
|
472
|
-
else if (!skipEmbeddings && this.options.
|
|
466
|
+
else if (!skipEmbeddings && this.options.textEmbeddingModel) {
|
|
473
467
|
if (!("runAction" in ctx)) {
|
|
474
468
|
console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
|
|
475
469
|
"Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
|
|
@@ -535,14 +529,14 @@ export class Agent {
|
|
|
535
529
|
contextOptions,
|
|
536
530
|
getEmbedding: async (text) => {
|
|
537
531
|
assert("runAction" in ctx);
|
|
538
|
-
assert(this.options.
|
|
532
|
+
assert(this.options.textEmbeddingModel, "A textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
|
|
539
533
|
return {
|
|
540
534
|
embedding: (await this.doEmbed(ctx, {
|
|
541
535
|
userId: args.userId,
|
|
542
536
|
threadId: args.threadId,
|
|
543
537
|
values: [text],
|
|
544
538
|
})).embeddings[0],
|
|
545
|
-
|
|
539
|
+
textEmbeddingModel: this.options.textEmbeddingModel,
|
|
546
540
|
};
|
|
547
541
|
},
|
|
548
542
|
});
|
|
@@ -573,7 +567,7 @@ export class Agent {
|
|
|
573
567
|
* @returns The embeddings for the messages.
|
|
574
568
|
*/
|
|
575
569
|
async generateEmbeddings(ctx, { userId, threadId, }, messages) {
|
|
576
|
-
if (!this.options.
|
|
570
|
+
if (!this.options.textEmbeddingModel) {
|
|
577
571
|
return undefined;
|
|
578
572
|
}
|
|
579
573
|
let embeddings;
|
|
@@ -585,11 +579,12 @@ export class Agent {
|
|
|
585
579
|
if (textIndexes.length === 0) {
|
|
586
580
|
return undefined;
|
|
587
581
|
}
|
|
582
|
+
const values = messageTexts.filter((t) => !!t);
|
|
588
583
|
// Then embed those messages.
|
|
589
584
|
const textEmbeddings = await this.doEmbed(ctx, {
|
|
590
585
|
userId,
|
|
591
586
|
threadId,
|
|
592
|
-
values
|
|
587
|
+
values,
|
|
593
588
|
});
|
|
594
589
|
// Then assemble the embeddings into a single array with nulls for the messages without text.
|
|
595
590
|
const embeddingsOrNull = Array(messages.length).fill(null);
|
|
@@ -599,7 +594,7 @@ export class Agent {
|
|
|
599
594
|
if (textEmbeddings.embeddings.length > 0) {
|
|
600
595
|
const dimension = textEmbeddings.embeddings[0].length;
|
|
601
596
|
validateVectorDimension(dimension);
|
|
602
|
-
const model = getModelName(this.options.
|
|
597
|
+
const model = getModelName(this.options.textEmbeddingModel);
|
|
603
598
|
embeddings = { vectors: embeddingsOrNull, dimension, model };
|
|
604
599
|
}
|
|
605
600
|
return embeddings;
|
|
@@ -640,8 +635,8 @@ export class Agent {
|
|
|
640
635
|
threadId: messagesMissingEmbeddings[0].threadId,
|
|
641
636
|
}, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
|
|
642
637
|
if (!embeddings) {
|
|
643
|
-
if (!this.options.
|
|
644
|
-
throw new Error("No embeddings were generated for the messages. You must pass a
|
|
638
|
+
if (!this.options.textEmbeddingModel) {
|
|
639
|
+
throw new Error("No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.");
|
|
645
640
|
}
|
|
646
641
|
throw new Error("No embeddings were generated for these messages: " +
|
|
647
642
|
messagesMissingEmbeddings.map((m) => m._id).join(", "));
|
|
@@ -666,9 +661,9 @@ export class Agent {
|
|
|
666
661
|
* @param args The Step generated by the AI SDK.
|
|
667
662
|
*/
|
|
668
663
|
async saveStep(ctx, args) {
|
|
669
|
-
const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
|
|
670
|
-
provider: args.provider ?? getProviderName(this.options.
|
|
671
|
-
model: args.model ?? getModelName(this.options.
|
|
664
|
+
const { messages } = await serializeNewMessagesInStep(ctx, this.component, args.step, {
|
|
665
|
+
provider: args.provider ?? getProviderName(this.options.languageModel),
|
|
666
|
+
model: args.model ?? getModelName(this.options.languageModel),
|
|
672
667
|
});
|
|
673
668
|
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
674
669
|
return ctx.runMutation(this.component.messages.addMessages, {
|
|
@@ -690,10 +685,14 @@ export class Agent {
|
|
|
690
685
|
*/
|
|
691
686
|
async saveObject(ctx, args) {
|
|
692
687
|
const { messages } = await serializeObjectResult(ctx, this.component, args.result, {
|
|
693
|
-
model: args.model ??
|
|
694
|
-
|
|
688
|
+
model: args.model ??
|
|
689
|
+
args.metadata?.model ??
|
|
690
|
+
getModelName(this.options.languageModel),
|
|
691
|
+
provider: args.provider ??
|
|
692
|
+
args.metadata?.provider ??
|
|
693
|
+
getProviderName(this.options.languageModel),
|
|
695
694
|
});
|
|
696
|
-
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) =>
|
|
695
|
+
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
697
696
|
return ctx.runMutation(this.component.messages.addMessages, {
|
|
698
697
|
userId: args.userId,
|
|
699
698
|
threadId: args.threadId,
|
|
@@ -712,19 +711,11 @@ export class Agent {
|
|
|
712
711
|
* @param args What message to save. Generally the parent message sent into
|
|
713
712
|
* the generateText call.
|
|
714
713
|
*/
|
|
715
|
-
async
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
724
|
-
messageId: args.messageId,
|
|
725
|
-
error: result.error,
|
|
726
|
-
});
|
|
727
|
-
}
|
|
714
|
+
async finalizeMessage(ctx, args) {
|
|
715
|
+
await ctx.runMutation(this.component.messages.finalizeMessage, {
|
|
716
|
+
messageId: args.messageId,
|
|
717
|
+
result: args.result,
|
|
718
|
+
});
|
|
728
719
|
}
|
|
729
720
|
/**
|
|
730
721
|
* Update a message by its id.
|
|
@@ -848,7 +839,8 @@ export class Agent {
|
|
|
848
839
|
const userId = argsUserId ??
|
|
849
840
|
(threadId &&
|
|
850
841
|
(await ctx.runQuery(this.component.threads.getThread, { threadId }))
|
|
851
|
-
?.userId)
|
|
842
|
+
?.userId) ??
|
|
843
|
+
undefined;
|
|
852
844
|
// If only a messageId is provided, this will add that message to the end.
|
|
853
845
|
const contextMessages = await this.fetchContextMessages(ctx, {
|
|
854
846
|
userId,
|
|
@@ -865,30 +857,45 @@ export class Agent {
|
|
|
865
857
|
const promptMessage = promptMessageIndex !== -1
|
|
866
858
|
? contextMessages.splice(promptMessageIndex, 1)[0]
|
|
867
859
|
: undefined;
|
|
868
|
-
let
|
|
860
|
+
let promptMessageId = promptMessage?._id;
|
|
869
861
|
let order = promptMessage?.order;
|
|
870
862
|
let stepOrder = promptMessage?.stepOrder;
|
|
871
863
|
let savedMessages = undefined;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
864
|
+
let pendingMessageId = undefined;
|
|
865
|
+
if (threadId && storageOptions?.saveMessages !== "none") {
|
|
866
|
+
let saved;
|
|
867
|
+
if (messages.length + prompt.length &&
|
|
868
|
+
// If it was a promptMessageId, we don't want to save it again.
|
|
869
|
+
(!args.promptMessageId || storageOptions?.saveMessages === "all")) {
|
|
870
|
+
const saveAll = storageOptions?.saveMessages === "all";
|
|
871
|
+
const coreMessages = [...messages, ...prompt];
|
|
872
|
+
const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
|
|
873
|
+
const metadata = Array.from({ length: toSave.length }, () => ({}));
|
|
874
|
+
saved = await this.saveMessages(ctx, {
|
|
875
|
+
threadId,
|
|
876
|
+
userId,
|
|
877
|
+
messages: [...toSave, { role: "assistant", content: [] }],
|
|
878
|
+
metadata: [...metadata, { status: "pending" }],
|
|
879
|
+
failPendingSteps: true,
|
|
880
|
+
pendingMessageId: args.pendingMessageId,
|
|
881
|
+
});
|
|
882
|
+
promptMessageId = saved.messages.at(-2)._id;
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
saved = await this.saveMessages(ctx, {
|
|
886
|
+
threadId,
|
|
887
|
+
userId,
|
|
888
|
+
messages: [{ role: "assistant", content: [] }],
|
|
889
|
+
metadata: [{ status: "pending" }],
|
|
890
|
+
failPendingSteps: true,
|
|
891
|
+
pendingMessageId: args.pendingMessageId,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
pendingMessageId = saved.messages.at(-1)._id;
|
|
889
895
|
order = saved.messages.at(-1).order;
|
|
890
896
|
stepOrder = saved.messages.at(-1).stepOrder;
|
|
891
|
-
|
|
897
|
+
// Don't return the pending message
|
|
898
|
+
savedMessages = saved.messages.slice(0, -1);
|
|
892
899
|
}
|
|
893
900
|
if (promptMessage?.message) {
|
|
894
901
|
if (!args.prompt) {
|
|
@@ -898,7 +905,7 @@ export class Agent {
|
|
|
898
905
|
// Lazily generate embeddings for the prompt message, if it doesn't have
|
|
899
906
|
// embeddings yet. This can happen if the message was saved in a mutation
|
|
900
907
|
// where the LLM is not available.
|
|
901
|
-
if (!promptMessage.embeddingId && this.options.
|
|
908
|
+
if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
|
|
902
909
|
await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
|
|
903
910
|
}
|
|
904
911
|
}
|
|
@@ -924,27 +931,29 @@ export class Agent {
|
|
|
924
931
|
}
|
|
925
932
|
// Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
|
|
926
933
|
if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
|
|
927
|
-
processedMessages = await
|
|
934
|
+
processedMessages = await inlineMessagesFiles(processedMessages);
|
|
928
935
|
}
|
|
929
936
|
const { prompt: _, model, ...rest } = args;
|
|
930
937
|
return {
|
|
931
938
|
args: {
|
|
932
939
|
...this.options.callSettings,
|
|
940
|
+
...this.options.providerOptions,
|
|
933
941
|
...rest,
|
|
934
|
-
model: model ?? this.options.
|
|
942
|
+
model: model ?? this.options.languageModel,
|
|
935
943
|
system: args.system ?? this.options.instructions,
|
|
936
944
|
messages: processedMessages,
|
|
937
945
|
},
|
|
938
946
|
userId,
|
|
939
|
-
|
|
947
|
+
promptMessageId,
|
|
948
|
+
pendingMessageId,
|
|
940
949
|
savedMessages,
|
|
941
950
|
order,
|
|
942
951
|
stepOrder,
|
|
943
952
|
};
|
|
944
953
|
}
|
|
945
954
|
async doEmbed(ctx, options) {
|
|
946
|
-
const embeddingModel = this.options.
|
|
947
|
-
assert(embeddingModel, "a
|
|
955
|
+
const embeddingModel = this.options.textEmbeddingModel;
|
|
956
|
+
assert(embeddingModel, "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
|
|
948
957
|
const result = await embedMany({
|
|
949
958
|
...this.options.callSettings,
|
|
950
959
|
model: embeddingModel,
|
|
@@ -969,64 +978,6 @@ export class Agent {
|
|
|
969
978
|
}
|
|
970
979
|
return { embeddings: result.embeddings };
|
|
971
980
|
}
|
|
972
|
-
/**
|
|
973
|
-
* Process messages to inline file and image URLs that point to localhost
|
|
974
|
-
* by converting them to base64. This solves the problem of LLMs not being
|
|
975
|
-
* able to access localhost URLs.
|
|
976
|
-
*/
|
|
977
|
-
async _inlineMessagesFiles(messages) {
|
|
978
|
-
// Process each message to convert localhost URLs to base64
|
|
979
|
-
return Promise.all(messages.map(async (message) => {
|
|
980
|
-
if ((message.role !== "user" && message.role !== "assistant") ||
|
|
981
|
-
typeof message.content === "string" ||
|
|
982
|
-
!Array.isArray(message.content)) {
|
|
983
|
-
return message;
|
|
984
|
-
}
|
|
985
|
-
const processedContent = await Promise.all(message.content.map(async (part) => {
|
|
986
|
-
if (part.type === "image" && part.image instanceof URL) {
|
|
987
|
-
assert(message.role === "user", "Images can only be in user messages");
|
|
988
|
-
if (this._isLocalhostUrl(part.image)) {
|
|
989
|
-
const imageData = await this._downloadFile(part.image);
|
|
990
|
-
return { ...part, image: imageData };
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
// Handle file parts
|
|
994
|
-
if (part.type === "file" && part.data instanceof URL) {
|
|
995
|
-
if (this._isLocalhostUrl(part.data)) {
|
|
996
|
-
const fileData = await this._downloadFile(part.data);
|
|
997
|
-
return { ...part, data: fileData };
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
return part;
|
|
1001
|
-
}));
|
|
1002
|
-
if (message.role === "user") {
|
|
1003
|
-
return { ...message, content: processedContent };
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
return { ...message, content: processedContent };
|
|
1007
|
-
}
|
|
1008
|
-
}));
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Check if a URL points to localhost
|
|
1012
|
-
*/
|
|
1013
|
-
_isLocalhostUrl(url) {
|
|
1014
|
-
return (url.hostname === "localhost" ||
|
|
1015
|
-
url.hostname === "127.0.0.1" ||
|
|
1016
|
-
url.hostname === "::1" ||
|
|
1017
|
-
url.hostname === "0.0.0.0");
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Download a file from a URL
|
|
1021
|
-
*/
|
|
1022
|
-
async _downloadFile(url) {
|
|
1023
|
-
// Fetch the file
|
|
1024
|
-
const response = await fetch(url);
|
|
1025
|
-
if (!response.ok) {
|
|
1026
|
-
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
1027
|
-
}
|
|
1028
|
-
return await response.arrayBuffer();
|
|
1029
|
-
}
|
|
1030
981
|
/**
|
|
1031
982
|
* WORKFLOW UTILITIES
|
|
1032
983
|
*/
|
|
@@ -1067,15 +1018,15 @@ export class Agent {
|
|
|
1067
1018
|
* @param spec Configuration for the agent acting as an action, including
|
|
1068
1019
|
* {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
|
|
1069
1020
|
*/
|
|
1070
|
-
asTextAction(spec) {
|
|
1071
|
-
const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
|
|
1021
|
+
asTextAction(spec, overrides) {
|
|
1072
1022
|
return internalActionGeneric({
|
|
1073
1023
|
args: vTextArgs,
|
|
1074
1024
|
handler: async (ctx_, args) => {
|
|
1075
1025
|
const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
|
|
1076
1026
|
const targetArgs = { userId: args.userId, threadId: args.threadId };
|
|
1077
1027
|
const llmArgs = {
|
|
1078
|
-
stopWhen,
|
|
1028
|
+
stopWhen: spec?.stopWhen ?? this.options.stopWhen,
|
|
1029
|
+
...overrides,
|
|
1079
1030
|
...omit(args, ["storageOptions", "contextOptions"]),
|
|
1080
1031
|
messages: args.messages?.map(deserializeMessage),
|
|
1081
1032
|
prompt: Array.isArray(args.prompt)
|
|
@@ -1088,7 +1039,7 @@ export class Agent {
|
|
|
1088
1039
|
}
|
|
1089
1040
|
const opts = {
|
|
1090
1041
|
...this.options,
|
|
1091
|
-
...
|
|
1042
|
+
...pick(spec, ["contextOptions", "storageOptions"]),
|
|
1092
1043
|
...pick(args, ["contextOptions", "storageOptions"]),
|
|
1093
1044
|
saveStreamDeltas: stream,
|
|
1094
1045
|
};
|
|
@@ -1101,10 +1052,11 @@ export class Agent {
|
|
|
1101
1052
|
await result.consumeStream();
|
|
1102
1053
|
return {
|
|
1103
1054
|
text: await result.text,
|
|
1104
|
-
|
|
1055
|
+
promptMessageId: result.promptMessageId,
|
|
1105
1056
|
order: result.order,
|
|
1106
1057
|
finishReason: await result.finishReason,
|
|
1107
1058
|
warnings: result.warnings,
|
|
1059
|
+
savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
|
|
1108
1060
|
};
|
|
1109
1061
|
}
|
|
1110
1062
|
else {
|
|
@@ -1112,10 +1064,11 @@ export class Agent {
|
|
|
1112
1064
|
const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
|
|
1113
1065
|
return {
|
|
1114
1066
|
text: res.text,
|
|
1115
|
-
|
|
1067
|
+
promptMessageId: res.promptMessageId,
|
|
1116
1068
|
order: res.order,
|
|
1117
1069
|
finishReason: res.finishReason,
|
|
1118
1070
|
warnings: res.warnings,
|
|
1071
|
+
savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
|
|
1119
1072
|
};
|
|
1120
1073
|
}
|
|
1121
1074
|
},
|
|
@@ -1128,22 +1081,37 @@ export class Agent {
|
|
|
1128
1081
|
* the normal parameters to {@link generateObject}, plus {@link ContextOptions}
|
|
1129
1082
|
* and stopWhen.
|
|
1130
1083
|
*/
|
|
1131
|
-
asObjectAction(
|
|
1084
|
+
asObjectAction(objectArgs, options) {
|
|
1132
1085
|
return internalActionGeneric({
|
|
1133
1086
|
args: vSafeObjectArgs,
|
|
1134
|
-
handler: async (
|
|
1135
|
-
const { userId, threadId, ...rest } = args;
|
|
1087
|
+
handler: async (ctx_, args) => {
|
|
1088
|
+
const { userId, threadId, callSettings, ...rest } = args;
|
|
1136
1089
|
const overrides = pick(rest, ["contextOptions", "storageOptions"]);
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
...
|
|
1140
|
-
|
|
1090
|
+
const targetArgs = { userId, threadId };
|
|
1091
|
+
const llmArgs = {
|
|
1092
|
+
...objectArgs,
|
|
1093
|
+
...callSettings,
|
|
1094
|
+
...omit(rest, ["storageOptions", "contextOptions"]),
|
|
1095
|
+
messages: args.messages?.map(deserializeMessage),
|
|
1096
|
+
prompt: Array.isArray(args.prompt)
|
|
1097
|
+
? args.prompt.map(deserializeMessage)
|
|
1098
|
+
: args.prompt,
|
|
1099
|
+
};
|
|
1100
|
+
const ctx = (options?.customCtx
|
|
1101
|
+
? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
|
|
1102
|
+
: ctx_);
|
|
1103
|
+
const value = await this.generateObject(ctx, targetArgs, llmArgs, {
|
|
1104
|
+
...this.options,
|
|
1105
|
+
...options,
|
|
1106
|
+
...overrides,
|
|
1107
|
+
});
|
|
1141
1108
|
return {
|
|
1142
|
-
object: value.object,
|
|
1143
|
-
|
|
1109
|
+
object: convexToJson(value.object),
|
|
1110
|
+
promptMessageId: value.promptMessageId,
|
|
1144
1111
|
order: value.order,
|
|
1145
1112
|
finishReason: value.finishReason,
|
|
1146
1113
|
warnings: value.warnings,
|
|
1114
|
+
savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
|
|
1147
1115
|
};
|
|
1148
1116
|
},
|
|
1149
1117
|
});
|
|
@@ -1193,4 +1161,28 @@ export class Agent {
|
|
|
1193
1161
|
});
|
|
1194
1162
|
}
|
|
1195
1163
|
}
|
|
1164
|
+
async function willContinue(
|
|
1165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1166
|
+
steps,
|
|
1167
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1168
|
+
stopWhen) {
|
|
1169
|
+
const step = steps.at(-1);
|
|
1170
|
+
// we aren't doing another round after a tool result
|
|
1171
|
+
// TODO: whether to handle continuing after too much context used..
|
|
1172
|
+
if (step.finishReason !== "tool-calls")
|
|
1173
|
+
return false;
|
|
1174
|
+
// we don't have a tool result, so we'll wait for more
|
|
1175
|
+
if (step.toolCalls.length > step.toolResults.length)
|
|
1176
|
+
return false;
|
|
1177
|
+
if (Array.isArray(stopWhen)) {
|
|
1178
|
+
return (await Promise.all(stopWhen.map(async (s) => s({ steps })))).every((stop) => !stop);
|
|
1179
|
+
}
|
|
1180
|
+
return !(await stopWhen?.({ steps }));
|
|
1181
|
+
}
|
|
1182
|
+
function errorToString(error) {
|
|
1183
|
+
if (error instanceof Error) {
|
|
1184
|
+
return error.message;
|
|
1185
|
+
}
|
|
1186
|
+
return String(error);
|
|
1187
|
+
}
|
|
1196
1188
|
//# sourceMappingURL=index.js.map
|