Tools¶
A tool is a Zig function the model can call. ZigAI derives the JSON Schema from your types, decodes the model's arguments, calls the function, and encodes the result. You write ordinary Zig; the wire format is not your problem.
Typed tools¶
const WeatherArgs = struct {
city: []const u8,
};
const Weather = struct {
temperature_c: i32,
};
fn weather(args: WeatherArgs) !Weather {
_ = args;
return .{ .temperature_c = 31 };
}
const tools = [_]zigai.Tool{
zigai.reflect.tool(
"weather",
"Get the current weather for a city.",
weather,
),
};
const agent = zigai.Agent{
.model = client.model(),
.tools = &tools,
.io = init.io,
};
You can also build tools manually when you need complete control. Invalid
arguments and recoverable failures are returned to the model as error
results, bounded independently for each tool by Tool.max_retries or the
agent's max_tool_retries default. Parallel calls run concurrently through
Agent.io, keep the model's original result order, and count toward
limits.max_tool_calls across the full run.
Reflected tools also expose the return type as
ToolDefinition.return_json_schema. It stays local by default. Set
return_schema_visibility = .model_description to include it in the portable
provider-visible description.
Execution limits¶
Local tools use bounded execution defaults: at most eight calls run at once,
64 may wait, and one result or all follow-up data may use at most 1 MiB. Set
Agent.tool_limits for the run; Tool.limits may tighten it for one tool.
Set Tool.sequential when a call must not overlap any other local tool.
Timeouts require Agent.io.
Overflow is recoverable
Timeout, queue, result-size, and follow-up-size failures become ordinary error tool results, so the model can recover without replaying a successful call.
Follow-up messages¶
To add context for the next model step, return ToolReturn(T):
fn lookup(args: LookupArgs) !zigai.ToolReturn(Weather) {
return .{
.value = .{ .temperature_c = 31 },
.follow_up_messages = &.{.{
.parts = &.{.{ .user_prompt = .{ .text = "The reading came from the roof sensor." } }},
}},
};
}
The typed value becomes the normal tool result. Follow-up requests are copied
after it, in tool-call order, and checked before the next model request. They
can contain only user_prompt parts. Manual tools can return the same shape
through Tool.executeOutputFn.
Toolsets¶
Toolsets group tools that belong together:
const database = zigai.Toolset{
.tools = &.{ search, fetch },
.namespace = "db",
};
const agent = zigai.Agent{
.model = model,
.toolsets = &.{database},
};
The model sees db__search and db__fetch. A prepareFn can enable or
disable tools before each model step using the current messages, usage,
request count, and typed dependencies. Tool, toolset, and prepared-entry
metadata is available to lifecycle hooks, but is not sent to the provider.
Tool policies¶
Use ToolPolicy when behavior belongs to the agent, not to one executor. A
policy receives ordered, typed stages for preparation, arguments, approval,
the call, and its return value.
Preparation policies run after toolsets. They can change or hide a tool for
one model step, with the current messages, usage, request count, and
dependencies available through ToolPolicyRunContext.
Argument and return stages can transform their value or set retry_message.
Approval runs only after valid arguments and may select requires_approval
from the current dependencies and message history. The resolved arguments and
decision are persisted when the agent pauses.
Approval and deferred tools¶
Mark a tool that must stop before execution:
var publish = zigai.reflect.tool(
"publish",
"Publish a message.",
publishMessage,
);
publish.execution = .requires_approval;
Use runUntilPause when the agent includes one of these tools. It returns
either the final result or a pause with the pending calls and versioned JSON
state:
var outcome = try agent.runUntilPause(allocator, "Publish the update.");
defer outcome.deinit();
switch (outcome) {
.complete => |result| useResult(result),
.paused => |paused| persist(paused.state_json),
}
Resume later without repeating the model request:
var resumed = try agent.resumeRun(allocator, state_json, &.{.{
.call_id = call_id,
.action = .approve,
}});
defer resumed.deinit();
Use .deny with a reason to reject a call. Tools marked .external never
execute inside ZigAI; resume them with .result and the externally produced
content. stringifyResumeDecisions and resumeRunJson provide a JSON-only
handoff for queues, databases, and approval services.
Provider-managed tools¶
Provider-managed tools use the same agent API:
const web = [_]zigai.BuiltinTool{
.{ .web_search = .{} },
.{ .web_fetch = .{} },
};
const agent = zigai.Agent{
.model = model,
.builtin_tools = &web,
};
ZigAI maps these to OpenAI web search, Anthropic web search and fetch, and Gemini Google Search and URL Context. The selected model's profile is checked before the request.
Requests fail locally, not silently
OpenAI does not currently expose standalone web fetch, so an agent requesting it fails locally instead of silently changing behavior. This is the general rule: a capability the profile does not support is rejected before any paid request.