Tool Calling
Tool calling lets AI models invoke functions in your application. @aeye handles the full lifecycle: schema compilation, argument parsing, execution, and result injection.
Quick Example
import z from 'zod';
const calculator = ai.tool({
name: 'calculate',
description: 'Perform a math calculation',
schema: z.object({
expression: z.string().describe('Math expression, e.g. "2 + 3 * 4"'),
}),
call: async ({ expression }) => {
return { result: eval(expression) };
},
});
const mathBot = ai.prompt({
name: 'mathBot',
content: 'You are a math tutor. Use the calculator to verify your work.',
tools: [calculator],
});
const result = await mathBot.get('result', {});How It Works
- The prompt compiles tool schemas and sends them to the AI model
- The model decides to call tools and returns
ToolCallobjects - @aeye parses and validates arguments against the Zod schema
- The tool's
call()function is executed - Results are sent back to the model as tool result messages
- The model generates its final response (or calls more tools)
Tool Choice
Control whether and how the model uses tools:
const prompt = ai.prompt({
tools: [tool1, tool2],
config: {
toolChoice: 'auto', // model decides (default)
// toolChoice: 'required', // must call at least one tool
// toolChoice: 'none', // don't call any tools
// toolChoice: { tool: 'tool1' }, // must call this specific tool
},
});Execution Modes
Immediate (Default)
Tools execute as soon as their arguments are parsed:
const prompt = ai.prompt({
tools: [tool1, tool2],
toolExecution: 'immediate',
});Sequential
Tools execute one at a time, in order:
const prompt = ai.prompt({
tools: [tool1, tool2],
toolExecution: 'sequential',
});Parallel
All tool calls in a batch are collected, then executed concurrently:
const prompt = ai.prompt({
tools: [tool1, tool2],
toolExecution: 'parallel',
});Tool Iterations
By default, prompts allow 3 rounds of tool calls. Increase for complex workflows:
const prompt = ai.prompt({
tools: [/* many tools */],
toolIterations: 20, // allow up to 20 rounds
toolRetries: 3, // retry failed tool calls up to 3 times
});Limiting Tool Calls
Cap the total number of successful tool calls:
const prompt = ai.prompt({
tools: [search, read],
toolsMax: 5, // stop after 5 successful tool calls
});Tool-Only Mode
Generate only tool calls, no text output:
const prompt = ai.prompt({
tools: [extractEntity],
toolsOnly: true,
});
const tools = await prompt.get('tools', input);
// tools is an array of { tool, result } objectsStrict Mode
Tool.strict is boolean | number (default: 1). When set, the LLM grammar-constrains tool arguments to your schema — no malformed args, no off-schema fields:
const calc = ai.tool({
name: 'calculate',
schema: z.object({ expression: z.string() }),
// strict omitted → priority 1 (best-effort, default)
call: async ({ expression }) => ({ result: eval(expression) }),
});
const critical = ai.tool({
name: 'transfer',
schema: z.object({ amount: z.number(), to: z.string() }),
strict: true, // hard requirement — selection rejects non-strict-capable models
call: async ({ amount, to }) => { /* ... */ },
});@aeye reconciles each provider's strict-mode dialect (OpenAI / Anthropic / Google) and handles per-request budgets so a request with many strict tools degrades gracefully. See the Strict Mode guide for the full picture, including the curated strictSupport overrides you'll want to wire into your AI config.
Error Handling
Tool errors are caught and reported back to the model, which can retry or adjust:
const riskyTool = ai.tool({
name: 'riskyOp',
schema: z.object({ id: z.string() }),
call: async ({ id }) => {
const item = await db.find(id);
if (!item) throw new Error(`Item ${id} not found`);
return item;
},
});
// The error message is sent to the model as a tool error result
// The model can try again with a different IDRaw Tool Call API
Use tool calling at the Chat API level without prompts:
const response = await ai.chat.get({
messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
tools: [{
name: 'getWeather',
description: 'Get weather for a city',
parameters: z.object({ city: z.string() }),
}],
});
if (response.toolCalls) {
for (const call of response.toolCalls) {
console.log(call.name, JSON.parse(call.arguments));
}
}