@convex-dev/agent 0.2.0-alpha.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/createTool.d.ts +3 -5
- 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 +8 -1
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +60 -5
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +223 -239
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +368 -382
- 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 +21 -22
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +35 -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/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 +4 -9
- package/src/client/files.ts +88 -10
- package/src/client/index.test.ts +9 -21
- package/src/client/index.ts +664 -650
- package/src/client/messages.ts +16 -2
- package/src/client/search.ts +10 -7
- package/src/client/streaming.ts +53 -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/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 { fetchContextMessages, filterOutOrphanedToolMessages, } from "./search.js";
|
|
25
|
+
export { abortStream, listStreams, syncStreams } from "./streaming.js";
|
|
24
26
|
export { createThread, getThreadMetadata } 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,78 @@ 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
|
+
return Object.assign(result, metadata);
|
|
273
336
|
}
|
|
274
337
|
/**
|
|
275
338
|
* This behaves like {@link generateObject} from the "ai" package except that
|
|
@@ -278,72 +341,30 @@ export class Agent {
|
|
|
278
341
|
* Use {@link continueThread} to get a version of this function already scoped
|
|
279
342
|
* to a thread (and optionally userId).
|
|
280
343
|
*/
|
|
281
|
-
async generateObject(ctx,
|
|
344
|
+
async generateObject(ctx, threadOpts,
|
|
282
345
|
/**
|
|
283
346
|
* The arguments to the generateObject function, similar to the ai.generateObject function.
|
|
284
347
|
*/
|
|
285
|
-
|
|
348
|
+
generateObjectArgs,
|
|
286
349
|
/**
|
|
287
350
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
288
351
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
289
352
|
*/
|
|
290
353
|
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";
|
|
354
|
+
const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
|
|
300
355
|
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;
|
|
356
|
+
const result = (await generateObject(args));
|
|
357
|
+
await save({ object: result });
|
|
358
|
+
const metadata = {
|
|
359
|
+
promptMessageId,
|
|
360
|
+
order,
|
|
361
|
+
savedMessages: getSavedMessages(),
|
|
362
|
+
messageId: promptMessageId,
|
|
363
|
+
};
|
|
364
|
+
return Object.assign(result, metadata);
|
|
339
365
|
}
|
|
340
366
|
catch (error) {
|
|
341
|
-
|
|
342
|
-
await ctx.runMutation(this.component.messages.rollbackMessage, {
|
|
343
|
-
messageId,
|
|
344
|
-
error: error.message,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
367
|
+
await fail(errorToString(error));
|
|
347
368
|
throw error;
|
|
348
369
|
}
|
|
349
370
|
}
|
|
@@ -354,83 +375,50 @@ export class Agent {
|
|
|
354
375
|
* Use {@link continueThread} to get a version of this function already scoped
|
|
355
376
|
* to a thread (and optionally userId).
|
|
356
377
|
*/
|
|
357
|
-
async streamObject(ctx,
|
|
378
|
+
async streamObject(ctx, threadOpts,
|
|
358
379
|
/**
|
|
359
380
|
* The arguments to the streamObject function, similar to the ai `streamObject` function.
|
|
360
381
|
*/
|
|
361
|
-
|
|
382
|
+
streamObjectArgs,
|
|
362
383
|
/**
|
|
363
384
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
364
385
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
365
386
|
*/
|
|
366
387
|
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
|
|
388
|
+
const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
|
|
378
389
|
const stream = streamObject({
|
|
379
390
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
-
...
|
|
391
|
+
...args,
|
|
381
392
|
onError: async (error) => {
|
|
382
|
-
console.error("onError", error);
|
|
393
|
+
console.error(" streamObject onError", error);
|
|
394
|
+
// TODO: content that we have so far
|
|
395
|
+
// content: stream.fullStream.
|
|
396
|
+
await fail(errorToString(error.error));
|
|
383
397
|
return args.onError?.(error);
|
|
384
398
|
},
|
|
385
399
|
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,
|
|
400
|
+
await save({
|
|
401
|
+
object: {
|
|
402
|
+
object: result.object,
|
|
403
|
+
finishReason: result.error ? "error" : "stop",
|
|
413
404
|
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,
|
|
405
|
+
warnings: result.warnings,
|
|
422
406
|
request: await stream.request,
|
|
423
407
|
response: result.response,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
408
|
+
providerMetadata: result.providerMetadata,
|
|
409
|
+
toJsonResponse: stream.toTextStreamResponse,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
427
412
|
return args.onFinish?.(result);
|
|
428
413
|
},
|
|
429
414
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
415
|
+
const metadata = {
|
|
416
|
+
promptMessageId,
|
|
417
|
+
order,
|
|
418
|
+
savedMessages: getSavedMessages(),
|
|
419
|
+
messageId: promptMessageId,
|
|
420
|
+
};
|
|
421
|
+
return Object.assign(stream, metadata);
|
|
434
422
|
}
|
|
435
423
|
/**
|
|
436
424
|
* Save a message to the thread.
|
|
@@ -451,6 +439,7 @@ export class Agent {
|
|
|
451
439
|
: [args.message],
|
|
452
440
|
metadata: args.metadata ? [args.metadata] : undefined,
|
|
453
441
|
skipEmbeddings: args.skipEmbeddings,
|
|
442
|
+
pendingMessageId: args.pendingMessageId,
|
|
454
443
|
});
|
|
455
444
|
const message = messages.at(-1);
|
|
456
445
|
return { messageId: message._id, message };
|
|
@@ -469,7 +458,7 @@ export class Agent {
|
|
|
469
458
|
if (args.embeddings) {
|
|
470
459
|
embeddings = args.embeddings;
|
|
471
460
|
}
|
|
472
|
-
else if (!skipEmbeddings && this.options.
|
|
461
|
+
else if (!skipEmbeddings && this.options.textEmbeddingModel) {
|
|
473
462
|
if (!("runAction" in ctx)) {
|
|
474
463
|
console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
|
|
475
464
|
"Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
|
|
@@ -535,14 +524,14 @@ export class Agent {
|
|
|
535
524
|
contextOptions,
|
|
536
525
|
getEmbedding: async (text) => {
|
|
537
526
|
assert("runAction" in ctx);
|
|
538
|
-
assert(this.options.
|
|
527
|
+
assert(this.options.textEmbeddingModel, "A textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
|
|
539
528
|
return {
|
|
540
529
|
embedding: (await this.doEmbed(ctx, {
|
|
541
530
|
userId: args.userId,
|
|
542
531
|
threadId: args.threadId,
|
|
543
532
|
values: [text],
|
|
544
533
|
})).embeddings[0],
|
|
545
|
-
|
|
534
|
+
textEmbeddingModel: this.options.textEmbeddingModel,
|
|
546
535
|
};
|
|
547
536
|
},
|
|
548
537
|
});
|
|
@@ -573,7 +562,7 @@ export class Agent {
|
|
|
573
562
|
* @returns The embeddings for the messages.
|
|
574
563
|
*/
|
|
575
564
|
async generateEmbeddings(ctx, { userId, threadId, }, messages) {
|
|
576
|
-
if (!this.options.
|
|
565
|
+
if (!this.options.textEmbeddingModel) {
|
|
577
566
|
return undefined;
|
|
578
567
|
}
|
|
579
568
|
let embeddings;
|
|
@@ -585,11 +574,12 @@ export class Agent {
|
|
|
585
574
|
if (textIndexes.length === 0) {
|
|
586
575
|
return undefined;
|
|
587
576
|
}
|
|
577
|
+
const values = messageTexts.filter((t) => !!t);
|
|
588
578
|
// Then embed those messages.
|
|
589
579
|
const textEmbeddings = await this.doEmbed(ctx, {
|
|
590
580
|
userId,
|
|
591
581
|
threadId,
|
|
592
|
-
values
|
|
582
|
+
values,
|
|
593
583
|
});
|
|
594
584
|
// Then assemble the embeddings into a single array with nulls for the messages without text.
|
|
595
585
|
const embeddingsOrNull = Array(messages.length).fill(null);
|
|
@@ -599,7 +589,7 @@ export class Agent {
|
|
|
599
589
|
if (textEmbeddings.embeddings.length > 0) {
|
|
600
590
|
const dimension = textEmbeddings.embeddings[0].length;
|
|
601
591
|
validateVectorDimension(dimension);
|
|
602
|
-
const model = getModelName(this.options.
|
|
592
|
+
const model = getModelName(this.options.textEmbeddingModel);
|
|
603
593
|
embeddings = { vectors: embeddingsOrNull, dimension, model };
|
|
604
594
|
}
|
|
605
595
|
return embeddings;
|
|
@@ -640,8 +630,8 @@ export class Agent {
|
|
|
640
630
|
threadId: messagesMissingEmbeddings[0].threadId,
|
|
641
631
|
}, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
|
|
642
632
|
if (!embeddings) {
|
|
643
|
-
if (!this.options.
|
|
644
|
-
throw new Error("No embeddings were generated for the messages. You must pass a
|
|
633
|
+
if (!this.options.textEmbeddingModel) {
|
|
634
|
+
throw new Error("No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.");
|
|
645
635
|
}
|
|
646
636
|
throw new Error("No embeddings were generated for these messages: " +
|
|
647
637
|
messagesMissingEmbeddings.map((m) => m._id).join(", "));
|
|
@@ -666,9 +656,9 @@ export class Agent {
|
|
|
666
656
|
* @param args The Step generated by the AI SDK.
|
|
667
657
|
*/
|
|
668
658
|
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.
|
|
659
|
+
const { messages } = await serializeNewMessagesInStep(ctx, this.component, args.step, {
|
|
660
|
+
provider: args.provider ?? getProviderName(this.options.languageModel),
|
|
661
|
+
model: args.model ?? getModelName(this.options.languageModel),
|
|
672
662
|
});
|
|
673
663
|
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
674
664
|
return ctx.runMutation(this.component.messages.addMessages, {
|
|
@@ -690,10 +680,14 @@ export class Agent {
|
|
|
690
680
|
*/
|
|
691
681
|
async saveObject(ctx, args) {
|
|
692
682
|
const { messages } = await serializeObjectResult(ctx, this.component, args.result, {
|
|
693
|
-
model: args.model ??
|
|
694
|
-
|
|
683
|
+
model: args.model ??
|
|
684
|
+
args.metadata?.model ??
|
|
685
|
+
getModelName(this.options.languageModel),
|
|
686
|
+
provider: args.provider ??
|
|
687
|
+
args.metadata?.provider ??
|
|
688
|
+
getProviderName(this.options.languageModel),
|
|
695
689
|
});
|
|
696
|
-
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) =>
|
|
690
|
+
const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
|
|
697
691
|
return ctx.runMutation(this.component.messages.addMessages, {
|
|
698
692
|
userId: args.userId,
|
|
699
693
|
threadId: args.threadId,
|
|
@@ -712,19 +706,11 @@ export class Agent {
|
|
|
712
706
|
* @param args What message to save. Generally the parent message sent into
|
|
713
707
|
* the generateText call.
|
|
714
708
|
*/
|
|
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
|
-
}
|
|
709
|
+
async finalizeMessage(ctx, args) {
|
|
710
|
+
await ctx.runMutation(this.component.messages.finalizeMessage, {
|
|
711
|
+
messageId: args.messageId,
|
|
712
|
+
result: args.result,
|
|
713
|
+
});
|
|
728
714
|
}
|
|
729
715
|
/**
|
|
730
716
|
* Update a message by its id.
|
|
@@ -848,7 +834,8 @@ export class Agent {
|
|
|
848
834
|
const userId = argsUserId ??
|
|
849
835
|
(threadId &&
|
|
850
836
|
(await ctx.runQuery(this.component.threads.getThread, { threadId }))
|
|
851
|
-
?.userId)
|
|
837
|
+
?.userId) ??
|
|
838
|
+
undefined;
|
|
852
839
|
// If only a messageId is provided, this will add that message to the end.
|
|
853
840
|
const contextMessages = await this.fetchContextMessages(ctx, {
|
|
854
841
|
userId,
|
|
@@ -865,30 +852,45 @@ export class Agent {
|
|
|
865
852
|
const promptMessage = promptMessageIndex !== -1
|
|
866
853
|
? contextMessages.splice(promptMessageIndex, 1)[0]
|
|
867
854
|
: undefined;
|
|
868
|
-
let
|
|
855
|
+
let promptMessageId = promptMessage?._id;
|
|
869
856
|
let order = promptMessage?.order;
|
|
870
857
|
let stepOrder = promptMessage?.stepOrder;
|
|
871
858
|
let savedMessages = undefined;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
859
|
+
let pendingMessageId = undefined;
|
|
860
|
+
if (threadId && storageOptions?.saveMessages !== "none") {
|
|
861
|
+
let saved;
|
|
862
|
+
if (messages.length + prompt.length &&
|
|
863
|
+
// If it was a promptMessageId, we don't want to save it again.
|
|
864
|
+
(!args.promptMessageId || storageOptions?.saveMessages === "all")) {
|
|
865
|
+
const saveAll = storageOptions?.saveMessages === "all";
|
|
866
|
+
const coreMessages = [...messages, ...prompt];
|
|
867
|
+
const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
|
|
868
|
+
const metadata = Array.from({ length: toSave.length }, () => ({}));
|
|
869
|
+
saved = await this.saveMessages(ctx, {
|
|
870
|
+
threadId,
|
|
871
|
+
userId,
|
|
872
|
+
messages: [...toSave, { role: "assistant", content: [] }],
|
|
873
|
+
metadata: [...metadata, { status: "pending" }],
|
|
874
|
+
failPendingSteps: true,
|
|
875
|
+
pendingMessageId: args.pendingMessageId,
|
|
876
|
+
});
|
|
877
|
+
promptMessageId = saved.messages.at(-2)._id;
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
saved = await this.saveMessages(ctx, {
|
|
881
|
+
threadId,
|
|
882
|
+
userId,
|
|
883
|
+
messages: [{ role: "assistant", content: [] }],
|
|
884
|
+
metadata: [{ status: "pending" }],
|
|
885
|
+
failPendingSteps: true,
|
|
886
|
+
pendingMessageId: args.pendingMessageId,
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
pendingMessageId = saved.messages.at(-1)._id;
|
|
889
890
|
order = saved.messages.at(-1).order;
|
|
890
891
|
stepOrder = saved.messages.at(-1).stepOrder;
|
|
891
|
-
|
|
892
|
+
// Don't return the pending message
|
|
893
|
+
savedMessages = saved.messages.slice(0, -1);
|
|
892
894
|
}
|
|
893
895
|
if (promptMessage?.message) {
|
|
894
896
|
if (!args.prompt) {
|
|
@@ -898,7 +900,7 @@ export class Agent {
|
|
|
898
900
|
// Lazily generate embeddings for the prompt message, if it doesn't have
|
|
899
901
|
// embeddings yet. This can happen if the message was saved in a mutation
|
|
900
902
|
// where the LLM is not available.
|
|
901
|
-
if (!promptMessage.embeddingId && this.options.
|
|
903
|
+
if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
|
|
902
904
|
await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
|
|
903
905
|
}
|
|
904
906
|
}
|
|
@@ -924,27 +926,28 @@ export class Agent {
|
|
|
924
926
|
}
|
|
925
927
|
// Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
|
|
926
928
|
if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
|
|
927
|
-
processedMessages = await
|
|
929
|
+
processedMessages = await inlineMessagesFiles(processedMessages);
|
|
928
930
|
}
|
|
929
931
|
const { prompt: _, model, ...rest } = args;
|
|
930
932
|
return {
|
|
931
933
|
args: {
|
|
932
934
|
...this.options.callSettings,
|
|
933
935
|
...rest,
|
|
934
|
-
model: model ?? this.options.
|
|
936
|
+
model: model ?? this.options.languageModel,
|
|
935
937
|
system: args.system ?? this.options.instructions,
|
|
936
938
|
messages: processedMessages,
|
|
937
939
|
},
|
|
938
940
|
userId,
|
|
939
|
-
|
|
941
|
+
promptMessageId,
|
|
942
|
+
pendingMessageId,
|
|
940
943
|
savedMessages,
|
|
941
944
|
order,
|
|
942
945
|
stepOrder,
|
|
943
946
|
};
|
|
944
947
|
}
|
|
945
948
|
async doEmbed(ctx, options) {
|
|
946
|
-
const embeddingModel = this.options.
|
|
947
|
-
assert(embeddingModel, "a
|
|
949
|
+
const embeddingModel = this.options.textEmbeddingModel;
|
|
950
|
+
assert(embeddingModel, "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
|
|
948
951
|
const result = await embedMany({
|
|
949
952
|
...this.options.callSettings,
|
|
950
953
|
model: embeddingModel,
|
|
@@ -969,64 +972,6 @@ export class Agent {
|
|
|
969
972
|
}
|
|
970
973
|
return { embeddings: result.embeddings };
|
|
971
974
|
}
|
|
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
975
|
/**
|
|
1031
976
|
* WORKFLOW UTILITIES
|
|
1032
977
|
*/
|
|
@@ -1067,15 +1012,15 @@ export class Agent {
|
|
|
1067
1012
|
* @param spec Configuration for the agent acting as an action, including
|
|
1068
1013
|
* {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
|
|
1069
1014
|
*/
|
|
1070
|
-
asTextAction(spec) {
|
|
1071
|
-
const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
|
|
1015
|
+
asTextAction(spec, overrides) {
|
|
1072
1016
|
return internalActionGeneric({
|
|
1073
1017
|
args: vTextArgs,
|
|
1074
1018
|
handler: async (ctx_, args) => {
|
|
1075
1019
|
const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
|
|
1076
1020
|
const targetArgs = { userId: args.userId, threadId: args.threadId };
|
|
1077
1021
|
const llmArgs = {
|
|
1078
|
-
stopWhen,
|
|
1022
|
+
stopWhen: spec?.stopWhen ?? this.options.stopWhen,
|
|
1023
|
+
...overrides,
|
|
1079
1024
|
...omit(args, ["storageOptions", "contextOptions"]),
|
|
1080
1025
|
messages: args.messages?.map(deserializeMessage),
|
|
1081
1026
|
prompt: Array.isArray(args.prompt)
|
|
@@ -1088,7 +1033,7 @@ export class Agent {
|
|
|
1088
1033
|
}
|
|
1089
1034
|
const opts = {
|
|
1090
1035
|
...this.options,
|
|
1091
|
-
...
|
|
1036
|
+
...pick(spec, ["contextOptions", "storageOptions"]),
|
|
1092
1037
|
...pick(args, ["contextOptions", "storageOptions"]),
|
|
1093
1038
|
saveStreamDeltas: stream,
|
|
1094
1039
|
};
|
|
@@ -1101,10 +1046,11 @@ export class Agent {
|
|
|
1101
1046
|
await result.consumeStream();
|
|
1102
1047
|
return {
|
|
1103
1048
|
text: await result.text,
|
|
1104
|
-
|
|
1049
|
+
promptMessageId: result.promptMessageId,
|
|
1105
1050
|
order: result.order,
|
|
1106
1051
|
finishReason: await result.finishReason,
|
|
1107
1052
|
warnings: result.warnings,
|
|
1053
|
+
savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
|
|
1108
1054
|
};
|
|
1109
1055
|
}
|
|
1110
1056
|
else {
|
|
@@ -1112,10 +1058,11 @@ export class Agent {
|
|
|
1112
1058
|
const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
|
|
1113
1059
|
return {
|
|
1114
1060
|
text: res.text,
|
|
1115
|
-
|
|
1061
|
+
promptMessageId: res.promptMessageId,
|
|
1116
1062
|
order: res.order,
|
|
1117
1063
|
finishReason: res.finishReason,
|
|
1118
1064
|
warnings: res.warnings,
|
|
1065
|
+
savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
|
|
1119
1066
|
};
|
|
1120
1067
|
}
|
|
1121
1068
|
},
|
|
@@ -1128,22 +1075,37 @@ export class Agent {
|
|
|
1128
1075
|
* the normal parameters to {@link generateObject}, plus {@link ContextOptions}
|
|
1129
1076
|
* and stopWhen.
|
|
1130
1077
|
*/
|
|
1131
|
-
asObjectAction(
|
|
1078
|
+
asObjectAction(objectArgs, options) {
|
|
1132
1079
|
return internalActionGeneric({
|
|
1133
1080
|
args: vSafeObjectArgs,
|
|
1134
|
-
handler: async (
|
|
1135
|
-
const { userId, threadId, ...rest } = args;
|
|
1081
|
+
handler: async (ctx_, args) => {
|
|
1082
|
+
const { userId, threadId, callSettings, ...rest } = args;
|
|
1136
1083
|
const overrides = pick(rest, ["contextOptions", "storageOptions"]);
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
...
|
|
1140
|
-
|
|
1084
|
+
const targetArgs = { userId, threadId };
|
|
1085
|
+
const llmArgs = {
|
|
1086
|
+
...objectArgs,
|
|
1087
|
+
...callSettings,
|
|
1088
|
+
...omit(rest, ["storageOptions", "contextOptions"]),
|
|
1089
|
+
messages: args.messages?.map(deserializeMessage),
|
|
1090
|
+
prompt: Array.isArray(args.prompt)
|
|
1091
|
+
? args.prompt.map(deserializeMessage)
|
|
1092
|
+
: args.prompt,
|
|
1093
|
+
};
|
|
1094
|
+
const ctx = (options?.customCtx
|
|
1095
|
+
? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
|
|
1096
|
+
: ctx_);
|
|
1097
|
+
const value = await this.generateObject(ctx, targetArgs, llmArgs, {
|
|
1098
|
+
...this.options,
|
|
1099
|
+
...options,
|
|
1100
|
+
...overrides,
|
|
1101
|
+
});
|
|
1141
1102
|
return {
|
|
1142
|
-
object: value.object,
|
|
1143
|
-
|
|
1103
|
+
object: convexToJson(value.object),
|
|
1104
|
+
promptMessageId: value.promptMessageId,
|
|
1144
1105
|
order: value.order,
|
|
1145
1106
|
finishReason: value.finishReason,
|
|
1146
1107
|
warnings: value.warnings,
|
|
1108
|
+
savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
|
|
1147
1109
|
};
|
|
1148
1110
|
},
|
|
1149
1111
|
});
|
|
@@ -1193,4 +1155,28 @@ export class Agent {
|
|
|
1193
1155
|
});
|
|
1194
1156
|
}
|
|
1195
1157
|
}
|
|
1158
|
+
async function willContinue(
|
|
1159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1160
|
+
steps,
|
|
1161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1162
|
+
stopWhen) {
|
|
1163
|
+
const step = steps.at(-1);
|
|
1164
|
+
// we aren't doing another round after a tool result
|
|
1165
|
+
// TODO: whether to handle continuing after too much context used..
|
|
1166
|
+
if (step.finishReason !== "tool-calls")
|
|
1167
|
+
return false;
|
|
1168
|
+
// we don't have a tool result, so we'll wait for more
|
|
1169
|
+
if (step.toolCalls.length > step.toolResults.length)
|
|
1170
|
+
return false;
|
|
1171
|
+
if (Array.isArray(stopWhen)) {
|
|
1172
|
+
return (await Promise.all(stopWhen.map(async (s) => s({ steps })))).every((stop) => !stop);
|
|
1173
|
+
}
|
|
1174
|
+
return !(await stopWhen?.({ steps }));
|
|
1175
|
+
}
|
|
1176
|
+
function errorToString(error) {
|
|
1177
|
+
if (error instanceof Error) {
|
|
1178
|
+
return error.message;
|
|
1179
|
+
}
|
|
1180
|
+
return String(error);
|
|
1181
|
+
}
|
|
1196
1182
|
//# sourceMappingURL=index.js.map
|