Tools ​
Tools let an agent read product data, call services, and request side effects through contracts owned by your application.
How a tool call works ​
The model sees a tool name, description, and JSON schema. When it requests that tool, Anvia parses the arguments, validates them, calls the application handler, validates any declared output schema, and sends the result back into the agent run.
Model request → input validation → application handler → output validation → tool resultSchemas protect the shape of the boundary. They do not replace authentication, authorization, business validation, rate limits, or audit logging inside the handler.
1. Define a typed tool ​
Install Zod alongside the v1 runtime if it is not already in the project:
pnpm add @anvia/core zodUse inputSchema for model-supplied arguments and outputSchema when the returned value should also be validated.
import { createTool } from '@anvia/core'
import { z } from 'zod'
const getWeather = createTool({
name: 'get_weather',
description: 'Get the current weather for a city.',
inputSchema: z.object({
city: z.string().min(1).describe('The city to check.'),
}),
outputSchema: z.object({
city: z.string(),
forecast: z.string(),
}),
async execute({ city }) {
return {
city,
forecast: await weather.getCurrent(city),
}
},
})The inferred handler input comes from inputSchema. If outputSchema is present, returning an incompatible value fails before the result is sent back to the model.
2. Add the tool to an agent ​
Register tools in the agent options. Set enough turns for the model to request the tool, receive its result, and produce the final answer.
import { Agent } from '@anvia/core'
const weatherAgent = new Agent({
id: 'weather',
model,
instructions: 'Use the weather tool when a user asks about weather.',
maxTurns: 2,
tools: [getWeather],
})
const response = await weatherAgent.generate({
prompt: 'What is the weather in Jakarta?'
})
if (response.type === 'response') {
console.log(response.output)
}The model chooses whether to call the tool unless the agent configures a stricter tool choice.
3. Observe tool events ​
Streaming exposes the tool lifecycle as normalized events. An interface can show progress without interpreting provider-specific payloads.
for await (const event of weatherAgent.stream({
prompt: 'Check Jakarta weather.'
})) {
if (event.type === 'tool_call') {
console.log('Calling:', event.toolCall.toolName)
}
if (event.type === 'tool_result') {
console.log('Result:', event.result)
}
if (event.type === 'text_delta') {
process.stdout.write(event.delta)
}
}4. Require approval for side effects ​
Read-only lookups can usually execute immediately. Mutations such as refunds, messages, or deployments should be able to pause before execution.
const refundOrder = createTool({
name: 'refund_order',
description: 'Refund an eligible order.',
inputSchema: z.object({ orderId: z.string() }),
requiresApproval: ({ orderId }) => ({
reason: `Approve refund for ${orderId}`,
}),
execute: async ({ orderId }) => billing.refund(orderId),
})When approval is required, generate() returns type: 'interaction' before the handler runs. The application can approve or reject that specific interaction through agent.resume(continuation, response).