Tools
The Agent component supports tool calls, which are a way to allow an LLM to call out to external services or functions. This can be useful for:
- Retrieving data from the database
- Writing or updating data in the database
- Searching the web for more context
- Calling an external API
- Requesting that a user takes an action before proceeding (human-in-the-loop)
Defining tools
You can provide tools at different times:
- Agent constructor: (
new Agent(components.agent, { tools: {...} })
) - Creating a thread:
createThread(ctx, { tools: {...} })
- Continuing a thread:
continueThread(ctx, { tools: {...} })
- On thread functions:
thread.generateText({ tools: {...} })
- Outside of a thread:
supportAgent.generateText(ctx, {}, { tools: {...} })
Specifying tools at each layer will overwrite the defaults. The tools will be
args.tools ?? thread.tools ?? agent.options.tools
. This allows you to create
tools in a context that is convenient.
Using tools
The Agent component will automatically handle passing tool call results back in
and re-generating if you pass stopWhen: stepCountIs(num)
where num > 1
to
generateText
or streamText
.
The tool call and result will be stored as messages in the thread associated with the source message. See Messages for more details.
Creating a tool with a Convex context
There are two ways to create a tool that has access to the Convex context.
- Use the
createTool
function, which is a wrapper around the AI SDK'stool
function.
export const ideaSearch = createTool({
description: "Search for ideas in the database",
args: z.object({ query: z.string().describe("The query to search for") }),
handler: async (ctx, args, options): Promise<Array<Idea>> => {
// ctx has agent, userId, threadId, messageId
// as well as ActionCtx properties like auth, storage, runMutation, and runAction
const ideas = await ctx.runQuery(api.ideas.searchIdeas, {
query: args.query,
});
console.log("found ideas", ideas);
return ideas;
},
});
- Define tools at runtime in a context with the variables you want to use.
async function createTool(ctx: ActionCtx, teamId: Id<"teams">) {
const myTool = tool({
description: "My tool",
parameters: z.object({...}).describe("The arguments for the tool"),
execute: async (args, options) => {
return await ctx.runQuery(internal.foo.bar, args);
},
});
}
In both cases, the args and options match the underlying AI SDK's tool
function.
Note: it's highly recommended to use zod with .describe
to provide details
about each parameter. This will be used to provide a description of the tool to
the LLM.
Adding custom context to tools
It's often useful to have extra metadata in the context of a tool.
By default, the context passed to a tool is a ToolCtx
with:
agent
- the Agent instance calling ituserId
- the user ID associated with the call, if anythreadId
- the thread ID, if anymessageId
- the message ID of the prompt message passed to generate/stream.- Everything in
ActionCtx
, such asauth
,storage
,runQuery
, etc. Note: in scheduled functions, workflows, etc, the auth user will benull
.
To add more fields to the context, you can pass a custom context to the call,
such as agent.generateText({ ...ctx, orgId: "123" })
.
You can enforce the type of the context by passing a type when constructing the Agent.
const myAgent = new Agent<{ orgId: string }>(...);
Then, in your tools, you can use the orgId
field.
type MyCtx = ToolCtx & { orgId: string };
const myTool = createTool({
args: z.object({ ... }),
description: "...",
handler: async (ctx: MyCtx, args) => {
// use ctx.orgId
},
});