@convex-dev/agent
Version:
A agent component for Convex.
440 lines (439 loc) • 20.3 kB
TypeScript
import type { FlexibleSchema, InferSchema, ModelMessage, ProviderOptions } from "@ai-sdk/provider-utils";
import type { JSONValue } from "@ai-sdk/provider";
import type { EmbeddingModel, GenerateObjectResult, generateText, GenerateTextResult, LanguageModelRequestMetadata, LanguageModelResponseMetadata, LanguageModelUsage, LanguageModel, streamObject, streamText, StreamTextResult, ToolSet, CallSettings, generateObject } from "ai";
import type { GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, WithoutSystemFields } from "convex/server";
import type { MessageDoc, ProviderMetadata, StreamDelta, StreamMessage, ThreadDoc } from "../validators.js";
import type { StreamingOptions } from "./streaming.js";
import type { ComponentApi } from "../component/_generated/component.js";
export type AgentPrompt = {
/**
* System message to include in the prompt. Overwrites Agent instructions.
*/
system?: string;
/**
* A prompt. It can be either a text prompt or a list of messages.
* If used with `promptMessageId`, it will be used in place of that
* prompt message and no input messages will be saved.
* Otherwise, if used with the storageOptions "promptAndOutput" (default),
* it will be the only message saved.
* If a string is provided, it will be a user message.
*/
prompt?: string | Array<ModelMessage> | undefined;
/**
* A list of messages to use as context before the prompt.
* If used with `prompt`, these will precede the prompt.
* If used with the storageOptions "promptAndOutput" (default),
* none of these messages will be saved.
*/
messages?: Array<ModelMessage> | undefined;
/**
* If provided, it uses this existing message to anchor the prompt:
* - The specified message will be included, unless `prompt` is also
* provided, in which case that will be inserted in place of this
* specified message.
* - Recent and search messages will not include messages after this
* message's order.
* - If there are already responses on the same order,
* for example, tool calls and responses,
* those will be included automatically.
*
* Note: if this is provided, no input messages will be saved by default.
*/
promptMessageId?: string | undefined;
/**
* The model to use for the LLM calls. This will override the languageModel
* specified in the Agent config.
*/
model?: LanguageModel;
};
export type Config = {
/**
* The LLM model to use for generating / streaming text and objects.
* e.g.
* import { openai } from "@ai-sdk/openai"
* const myAgent = new Agent(components.agent, {
* languageModel: openai.chat("gpt-4o-mini"),
*/
languageModel?: LanguageModel;
/**
* The model to use for text embeddings. Optional.
* If specified, it will use this for generating vector embeddings
* of chats, and can opt-in to doing vector search for automatic context
* on generateText, etc.
* e.g.
* import { openai } from "@ai-sdk/openai"
* const myAgent = new Agent(components.agent, {
* ...
* textEmbeddingModel: openai.embedding("text-embedding-3-small")
*/
textEmbeddingModel?: EmbeddingModel<string>;
/**
* Options to determine what messages are included as context in message
* generation. To disable any messages automatically being added, pass:
* { recentMessages: 0 }
*/
contextOptions?: ContextOptions;
/**
* Determines whether messages are automatically stored when passed as
* arguments or generated.
*/
storageOptions?: StorageOptions;
/**
* The usage handler to use for this agent.
*/
usageHandler?: UsageHandler;
/**
* By default, messages are ordered with context in `fetchContextWithPrompt`,
* but you can override this by providing a context handler. Here you can
* filter, modify, or enrich the context messages. If provided, the default
* ordering will not apply. This excludes the system message / instructions.
*/
contextHandler?: ContextHandler;
/**
* Called for each LLM request/response, so you can do things like
* log the raw request body or response headers to a table, or logs.
*/
rawRequestResponseHandler?: RawRequestResponseHandler;
/**
* @deprecated Reach out if you use this. Otherwise will be removed soon.
* Default provider options to pass for the LLM calls.
* This can be overridden at each generate/stream callsite on a per-field
* basis. To clear a default setting, you'll need to pass `undefined`.
*/
providerOptions?: ProviderOptions;
/**
* The default settings to use for the LLM calls.
* This can be overridden at each generate/stream callsite on a per-field
* basis. To clear a default setting, you'll need to pass `undefined`.
*/
callSettings?: CallSettings;
/**
* The maximum number of steps to allow for a single generation.
*
* For example, if an agent wants to call a tool, that call and tool response
* will be one step. Generating a response based on the tool call & response
* will be a second step.
* If it runs out of steps, it will return the last step result, which may
* not be an assistant message.
* This becomes the default value when `stopWhen` is not specified in the
* Agent or generation callsite.
* AI SDK v5 removed the `maxSteps` argument, but this is kept here for
* convenience and backwards compatibility.
* Defaults to 1.
*/
maxSteps?: number;
};
/**
* Options to configure what messages are fetched as context,
* automatically with thread.generateText, or directly via search.
*/
export type ContextOptions = {
/**
* Whether to include tool messages in the context.
* By default, tool calls and results are not included.
*/
excludeToolMessages?: boolean;
/**
* How many recent messages to include. These are added after the search
* messages, and do not count against the search limit.
* Default: 100
*/
recentMessages?: number;
/**
* Options for searching messages.
*/
searchOptions?: {
/**
* The maximum number of messages to fetch. Default is 10.
*/
limit: number;
/**
* Whether to use text search to find messages. Default is false.
*/
textSearch?: boolean;
/**
* Whether to use vector search to find messages. Default is false.
* At least one of textSearch or vectorSearch must be true.
*/
vectorSearch?: boolean;
/**
* The score threshold for vector search. Default is 0.0.
*/
vectorScoreThreshold?: number;
/**
* What messages around the search results to include.
* Default: { before: 2, after: 1 }
* (two before, and one after each message found in the search)
* Note, this is after the limit is applied.
* By default this will quadruple the number of messages fetched.
*/
messageRange?: {
before: number;
after: number;
};
};
/**
* Whether to search across other threads for relevant messages.
* By default, only the current thread is searched.
*/
searchOtherThreads?: boolean;
};
/**
* Options to configure the automatic saving of messages
* when generating text / objects in a thread.
*/
export type StorageOptions = {
/**
* Whether to save messages to the thread history.
* Pass "all" to save all input and output messages.
* Pass "none" to not save any input or output messages.
* Pass "promptAndOutput" to save the prompt and all output messages.
* If you pass {messages} but no {prompt}, it will assume messages.at(-1) is
* the prompt.
* Defaults to "promptAndOutput".
*/
saveMessages?: "all" | "none" | "promptAndOutput";
};
export type GenerationOutputMetadata = {
/**
* The ID of the prompt message for the generation.
*/
promptMessageId?: string;
/**
* The order of the prompt message and responses for the generation.
* Each order starts with a user message, then followed by agent responses.
* If a promptMessageId is provided, that dictates the order.
*/
order?: number;
/**
* The messages saved for the generation - both saved input and output.
* If you passed promptMessageId, it will not include that message.
*/
savedMessages?: MessageDoc[];
/**
* @deprecated Use promptMessageId instead.
* The ID of the prompt message for the generation.
*/
messageId?: string;
};
export type UsageHandler = (ctx: ActionCtx, args: {
userId: string | undefined;
threadId: string | undefined;
agentName: string | undefined;
usage: LanguageModelUsage;
providerMetadata: ProviderMetadata | undefined;
model: string;
provider: string;
}) => void | Promise<void>;
/**
* By default, messages are ordered with context in `fetchContextWithPrompt`,
* but you can override this by providing a context handler. Here you can filter
* out, add in, or reorder messages.
*/
export type ContextHandler = (ctx: ActionCtx, args: {
/**
* All messages in the default order.
*/
allMessages: ModelMessage[];
/**
* The messages fetched from search.
*/
search: ModelMessage[];
/**
* The recent messages already in the thread history,
* excluding any messages that came after promptMessageId.
*/
recent: ModelMessage[];
/**
* The messages passed as the `messages` argument to e.g. generateText.
*/
inputMessages: ModelMessage[];
/**
* The message(s) passed as the `prompt` argument to e.g. generateText.
* Otherwise, if `promptMessageId` was provided, the message at that id.
* `prompt` will override the message at `promptMessageId`.
*/
inputPrompt: ModelMessage[];
/**
* Any messages on the same `order` as the promptMessageId message after the
* prompt message. These are presumably existing responses to the prompt
* message.
*/
existingResponses: ModelMessage[];
/**
* The user associated with the generation, if any.
*/
userId: string | undefined;
/**
* The thread associated with the generation, if any.
*/
threadId: string | undefined;
}) => ModelMessage[] | Promise<ModelMessage[]>;
export type RawRequestResponseHandler = (ctx: ActionCtx, args: {
userId: string | undefined;
threadId: string | undefined;
agentName: string | undefined;
request: LanguageModelRequestMetadata;
response: LanguageModelResponseMetadata;
}) => void | Promise<void>;
export type AgentComponent = ComponentApi;
export type TextArgs<AgentTools extends ToolSet, TOOLS extends ToolSet | undefined = undefined, OUTPUT = never, OUTPUT_PARTIAL = never> = Omit<Parameters<typeof generateText<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT, OUTPUT_PARTIAL>>[0], "model" | "prompt" | "messages"> & {
/**
* The tools to use for the tool calls. This will override tools specified
* in the Agent constructor or createThread / continueThread.
*/
tools?: TOOLS;
} & AgentPrompt;
export type StreamingTextArgs<AgentTools extends ToolSet, TOOLS extends ToolSet | undefined = undefined, OUTPUT = never, OUTPUT_PARTIAL = never> = Omit<Parameters<typeof streamText<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT, OUTPUT_PARTIAL>>[0], "model" | "prompt" | "messages"> & {
/**
* The tools to use for the tool calls. This will override tools specified
* in the Agent constructor or createThread / continueThread.
*/
tools?: TOOLS;
} & AgentPrompt;
export type ObjectMode = "object" | "array" | "enum" | "no-schema";
export type GenerateObjectArgs<SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>, OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string ? "enum" : "object", RESULT = OUTPUT extends "array" ? Array<InferSchema<SCHEMA>> : InferSchema<SCHEMA>> = AgentPrompt & Omit<Parameters<typeof generateObject<SCHEMA, OUTPUT, RESULT>>[0], "model" | "prompt" | "messages"> & {
schema?: SCHEMA;
enum?: Array<RESULT>;
};
export type StreamObjectArgs<SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>, OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string ? "enum" : "object", RESULT = OUTPUT extends "array" ? Array<InferSchema<SCHEMA>> : InferSchema<SCHEMA>> = AgentPrompt & Omit<Parameters<typeof streamObject<SCHEMA, OUTPUT, RESULT>>[0], "model" | "prompt" | "messages"> & {
schema?: SCHEMA;
enum?: Array<RESULT>;
};
export type MaybeCustomCtx<CustomCtx, DataModel extends GenericDataModel, AgentTools extends ToolSet> = CustomCtx extends Record<string, unknown> ? {
/**
* If you have a custom ctx that you use with the Agent
* (e.g. new Agent<{ orgId: string }>(...))
* you need to provide this function to add any extra fields.
* e.g.
* ```ts
* const myAgent = new Agent<{ orgId: string }>(...);
* const myAction = myAgent.asTextAction({
* customCtx: (ctx: ActionCtx, target, llmArgs) => {
* const orgId = await lookupOrgId(ctx, target.threadId);
* return { orgId };
* },
* });
* ```
* Then, in your tools, you can
*/
customCtx: (ctx: GenericActionCtx<DataModel>, target: {
userId?: string | undefined;
threadId?: string | undefined;
}, llmArgs: TextArgs<AgentTools>) => CustomCtx;
} : {
customCtx?: never;
};
type ThreadOutputMetadata = Required<GenerationOutputMetadata>;
/**
* The interface for a thread returned from {@link createThread} or {@link continueThread}.
* This is contextual to a thread and/or user.
*/
export interface Thread<DefaultTools extends ToolSet> {
/**
* The target threadId, from the startThread or continueThread initializers.
*/
threadId: string;
/**
* Get the metadata for the thread.
*/
getMetadata: () => Promise<ThreadDoc>;
/**
* Update the metadata for the thread.
*/
updateMetadata: (patch: Partial<WithoutSystemFields<ThreadDoc>>) => Promise<ThreadDoc>;
/**
* This behaves like {@link generateText} from the "ai" package except that
* it add context based on the userId and threadId and saves the input and
* resulting messages to the thread, if specified.
* Use {@link continueThread} to get a version of this function already scoped
* to a thread (and optionally userId).
* @param args The arguments to the generateText function, along with extra controls
* for the {@link ContextOptions} and {@link StorageOptions}.
* @returns The result of the generateText function.
*/
generateText<TOOLS extends ToolSet | undefined = undefined, OUTPUT = never, OUTPUT_PARTIAL = never>(generateTextArgs: AgentPrompt & TextArgs<TOOLS extends undefined ? DefaultTools : TOOLS, TOOLS, OUTPUT, OUTPUT_PARTIAL>, options?: Options): Promise<GenerateTextResult<TOOLS extends undefined ? DefaultTools : TOOLS, OUTPUT> & ThreadOutputMetadata>;
/**
* This behaves like {@link streamText} from the "ai" package except that
* it add context based on the userId and threadId and saves the input and
* resulting messages to the thread, if specified.
* Use {@link continueThread} to get a version of this function already scoped
* to a thread (and optionally userId).
* @param args The arguments to the streamText function, along with extra controls
* for the {@link ContextOptions} and {@link StorageOptions}.
* @returns The result of the streamText function.
*/
streamText<TOOLS extends ToolSet | undefined = undefined, OUTPUT = never, PARTIAL_OUTPUT = never>(streamTextArgs: AgentPrompt & StreamingTextArgs<TOOLS extends undefined ? DefaultTools : TOOLS, TOOLS, OUTPUT, PARTIAL_OUTPUT>, options?: Options & {
/**
* Whether to save incremental data (deltas) from streaming responses.
* Defaults to false.
* If false, it will not save any deltas to the database.
* If true, it will save deltas with {@link DEFAULT_STREAMING_OPTIONS}.
*
* Regardless of this option, when streaming you are able to use this
* `streamText` function as you would with the "ai" package's version:
* iterating over the text, streaming it over HTTP, etc.
*/
saveStreamDeltas?: boolean | StreamingOptions;
}): Promise<StreamTextResult<TOOLS extends undefined ? DefaultTools : TOOLS, PARTIAL_OUTPUT> & ThreadOutputMetadata>;
/**
* This behaves like {@link generateObject} from the "ai" package except that
* it add context based on the userId and threadId and saves the input and
* resulting messages to the thread, if specified. This overload is for objects, arrays, and enums.
* Use {@link continueThread} to get a version of this function already scoped
* to a thread (and optionally userId).
* @param args The arguments to the generateObject function, along with extra controls
* for the {@link ContextOptions} and {@link StorageOptions}.
* @returns The result of the generateObject function.
*/
generateObject<SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>, OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string ? "enum" : "object", RESULT = OUTPUT extends "array" ? Array<InferSchema<SCHEMA>> : InferSchema<SCHEMA>>(generateObjectArgs: AgentPrompt & GenerateObjectArgs<SCHEMA, OUTPUT, RESULT>, options?: Options): Promise<GenerateObjectResult<RESULT> & ThreadOutputMetadata>;
/**
* This behaves like {@link streamObject} from the "ai" package except that
* it add context based on the userId and threadId and saves the input and
* resulting messages to the thread, if specified.
* Use {@link continueThread} to get a version of this function already scoped
* to a thread (and optionally userId).
* @param args The arguments to the streamObject function, along with extra controls
* for the {@link ContextOptions} and {@link StorageOptions}.
* @returns The result of the streamObject function.
*/
streamObject<SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>, OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string ? "enum" : "object", RESULT = OUTPUT extends "array" ? Array<InferSchema<SCHEMA>> : InferSchema<SCHEMA>>(
/**
* The same arguments you'd pass to "ai" sdk {@link streamObject}.
*/
streamObjectArgs: AgentPrompt & StreamObjectArgs<SCHEMA, OUTPUT, RESULT>, options?: Options): Promise<ReturnType<typeof streamObject<SCHEMA, OUTPUT, RESULT>> & ThreadOutputMetadata>;
}
export type Options = {
/**
* The context options to use for passing in message history to the LLM.
*/
contextOptions?: ContextOptions;
/**
* The storage options to use for saving the input and output messages to the thread.
*/
storageOptions?: StorageOptions;
/**
* The usage handler to use for this thread. Overrides any handler
* set in the agent constructor.
*/
usageHandler?: UsageHandler;
/**
* By default, messages are ordered with context in `fetchContextWithPrompt`,
* but you can override this by providing a context handler. Here you can
* filter, modify, or enrich the context messages. If provided, the default
* ordering will not apply. This excludes the system message / instructions.
*/
contextHandler?: ContextHandler;
};
export type SyncStreamsReturnValue = {
kind: "list";
messages: StreamMessage[];
} | {
kind: "deltas";
deltas: StreamDelta[];
} | undefined;
export type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
export type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runQuery" | "runMutation">;
export type ActionCtx = Pick<GenericActionCtx<GenericDataModel>, "runQuery" | "runMutation" | "runAction" | "storage" | "auth">;
export {};
//# sourceMappingURL=types.d.ts.map