@convex-dev/agent
Version:
A agent component for Convex.
102 lines (99 loc) • 3.06 kB
text/typescript
import { type ModelMessage } from "ai";
import type { MessageDoc } from "../validators.js";
import { embedMessages, getPromptArray } from "./search.js";
import type {
ActionCtx,
AgentComponent,
Config,
MutationCtx,
} from "./types.js";
import { saveMessages } from "./messages.js";
import type { Message } from "../validators.js";
import { assert } from "convex-helpers";
import type { VectorDimension } from "../component/vector/tables.js";
export async function saveInputMessages(
ctx: MutationCtx | ActionCtx,
component: AgentComponent,
{
threadId,
userId,
prompt,
messages,
...args
}: {
prompt: string | (ModelMessage | Message)[] | undefined;
messages: (ModelMessage | Message)[] | undefined;
promptMessageId: string | undefined;
userId: string | undefined;
threadId: string;
agentName?: string;
storageOptions?: {
saveMessages?: "all" | "promptAndOutput";
};
} & Pick<Config, "usageHandler" | "textEmbeddingModel" | "callSettings">,
): Promise<{
promptMessageId: string | undefined;
pendingMessage: MessageDoc;
savedMessages: MessageDoc[];
}> {
const shouldSave = args.storageOptions?.saveMessages ?? "promptAndOutput";
// If only a promptMessageId is provided, this will be empty.
const promptArray = getPromptArray(prompt);
const toSave: (ModelMessage | Message)[] = [];
if (args.promptMessageId) {
// We don't save any inputs if a promptMessageId is provided.
// It's unclear where they'd want to save the new messages.
} else if (shouldSave === "all") {
if (messages) toSave.push(...messages);
toSave.push(...promptArray);
} else {
if (promptArray.length) {
// We treat the whole promptArray as the prompt message to save.
toSave.push(...promptArray);
} else if (messages) {
// Otherwise, treat the last message as the prompt message to save.
toSave.push(...messages.slice(-1));
}
}
let embeddings:
| {
vectors: (number[] | null)[];
dimension: VectorDimension;
model: string;
}
| undefined;
if (args.textEmbeddingModel && toSave.length) {
assert(
"runAction" in ctx,
"You must be in an action context to generate embeddings",
);
embeddings = await embedMessages(
ctx,
{ ...args, userId: userId ?? undefined, threadId },
toSave,
);
if (embeddings) {
// for the pending message
embeddings.vectors.push(null);
}
}
const saved = await saveMessages(ctx, component, {
threadId,
userId,
messages: [...toSave, { role: "assistant", content: [] }],
metadata: [
...Array.from({ length: toSave.length }, () => ({})),
{ status: "pending" },
],
failPendingSteps: !!args.promptMessageId,
promptMessageId: args.promptMessageId,
embeddings,
});
return {
promptMessageId: toSave.length
? saved.messages.at(-2)!._id
: args.promptMessageId,
pendingMessage: saved.messages.at(-1)!,
savedMessages: saved.messages.slice(0, -1),
};
}