Tools & Agents
Tools let an LLM call your own functions mid-generation, so it can fetch data, run code, or take actions instead of just answering. You define each tool with a Zod schema and Gerbil handles the calling convention.
Quick Start
Tools are discovered automatically from .gerbil/tools/ in the REPL and CLI — no imports or registration required. Drop in a file:
01// .gerbil/tools/weather.tool.ts02export default {03 name: "get_weather",04 description: "Get current weather for a city",05 execute: async (params, ctx) => {06 const { city } = params;07 // Your implementation08 return `Weather in ${city}: 72°F, sunny`;09 },10};No imports needed! Tools are automatically discovered from .gerbil/tools/.
Built-in Tools
gerbil_docs
Searches Gerbil documentation for any topic. Built-in tools are referenced by their string id; in Agent mode the model calls them automatically.
{ "tool": "gerbil_docs", "params": { "query": "streaming" } }// Returns documentation about streaming responsesAvailable topics:
- quickstart, generate, stream, json, thinking, embed
- models, load, ai-sdk, next, express, react, hono
- langchain, mcp, skills, define-skill, cli, tools
run_skill
Executes any Gerbil skill by name. The model calls it like any other tool:
{ "tool": "run_skill", "params": { "skill": "summarize", "input": "Long article text..." }}To call a skill directly from your own code, import it from @tryhamster/gerbil/skills instead — see the Skills docs.
Defining Custom Tools
Simple Format (Recommended)
Tools use a simple export format with no imports required:
01// .gerbil/tools/calculator.tool.ts02export default {03 name: "calculator",04 description: "Perform basic math calculations",05 execute: async (params, ctx) => {06 const { expression } = params;07 try {08 const result = eval(expression);09 return `${expression} = ${result}`;10 } catch {11 return `Error: Invalid expression`;12 }13 },14};Tool Context
Tools receive a context object with access to the LLM:
01// .gerbil/tools/generate_joke.tool.ts02export default {03 name: "generate_joke",04 description: "Generate a joke about a topic",05 execute: async (params, ctx) => {06 const { topic } = params;07 08 // Use ctx.generate() to call the LLM!09 if (ctx?.generate) {10 const joke = await ctx.generate(`Tell me a short joke about ${topic}`);11 return joke;12 }13 14 return "Could not generate joke";15 },16};Create Tool Wizard
The REPL includes a 4-step guided wizard to create tools:
- Name — Tool name (snake_case)
- Description — What the tool does
- Parameters — Inputs (optional, press Enter to skip)
- Logic — What to return
The wizard uses AI to generate the execute function body, then saves to .gerbil/tools/.
Tool Registry
Every file in .gerbil/tools/ is registered at startup alongside the built-ins. In the REPL, the Tools view (press 3) lists everything that is registered:
gerbil repl# Press 3 → Tools view lists all registered tools:# gerbil_docs [builtin]# run_skill [builtin]# get_weather [project]# calculator [project]Using Tools
REPL Tools View
Manage tools in the REPL (press 3 or navigate to Tools):
gerbil repl# Press 3 for Tools view- x — Execute tool directly
- c — Create a new tool (guided wizard)
- o — Open tool file in VS Code
- Enter — Expand tool details
Tools are shown with badges: [builtin], [project], or [error].
REPL Agent Mode
Use tools automatically in Chat with Agent mode:
gerbil repl# In Chat view: press ⌘A to toggle Agent mode# Or Tab to cycle modes → select "Agent"In Agent mode, the model can see available tools, call them by outputting JSON, and receive tool results to synthesize a response.
Programmatic Usage
To drive the full generate → tool → feedback loop from code, use engine.generateWithTools (Node / imperative) or the useAgent hook (React/browser). Both handle prompt formatting, parsing the model's tool calls, and executing them for you — see engine.generateWithTools and agentic tool calling below.
Tool Call Format
Gerbil uses a simple JSON format for tool calls:
{ "tool": "tool_name", "params": { "param1": "value1", "param2": "value2" }}The model is prompted to output this format when it wants to use a tool.
Agentic tool calling in the browser
The useAgent hook runs a complete agent loop entirely on-device with WebGPU: the model generates, requests a tool, your tool executes, the result is fed back, and the loop repeats until the model produces a final answer. Nothing leaves the browser. Import it from @tryhamster/gerbil/hooks.
import { useAgent } from "@tryhamster/gerbil/hooks";Define one or more tools, pass them to useAgent, then call run() with a prompt. The returned promise resolves to the final answer string, and the steps array gives you the full trace to render in your UI:
01import { useAgent } from "@tryhamster/gerbil/hooks";02import type { AgentTool } from "@tryhamster/gerbil/hooks";03
04const weatherTool: AgentTool = {05 name: "get_weather",06 description: "Get the current weather for a city",07 parameters: {08 type: "object",09 properties: {10 city: { type: "string", description: "City name" },11 },12 required: ["city"],13 },14 execute: async ({ city }) => {15 const res = await fetch(`https://api.example.com/weather?city=${city}`);16 const data = await res.json();17 return `${data.temp}°F and ${data.conditions} in ${city}`;18 },19};20
21function WeatherAgent() {22 const { run, steps, answer, isRunning, isReady } = useAgent({23 model: "mlx-community/Qwen3.5-0.8B-4bit",24 tools: [weatherTool],25 maxSteps: 5,26 autoLoad: true,27 });28
29 return (30 <div>31 <button32 type="button"33 disabled={!isReady || isRunning}34 onClick={() => run("What's the weather in Paris?")}35 >36 {isRunning ? "Thinking…" : "Ask"}37 </button>38
39 <ol>40 {steps.map((step, i) => {41 if (step.kind === "tool_call") {42 return (43 <li key={i}>44 → calling <strong>{step.tool}</strong>(45 {JSON.stringify(step.args)})46 </li>47 );48 }49 if (step.kind === "tool_result") {50 return (51 <li key={i}>52 ← {step.tool} returned: {step.result}53 </li>54 );55 }56 return <li key={i}>{step.text}</li>;57 })}58 </ol>59
60 {answer && <p>{answer}</p>}61 </div>62 );63}The hook loads the model lazily. With autoLoad: true it begins loading on mount; otherwise call load() yourself and wait for isReady. Use reset() to clear the trace and answer between runs.
useAgent options & return value
useAgent(options: { /** Model id to load (e.g. "mlx-community/Qwen3.5-0.8B-4bit") */ model: string; /** Tools the agent may call */ tools: AgentTool[]; /** Max generate → tool → feedback cycles (default: 5) */ maxSteps?: number; /** Start loading the model on mount (default: false) */ autoLoad?: boolean;}): { /** Run the loop; resolves to the final answer string */ run: (prompt: string) => Promise<string>; /** Ordered trace of tool calls, tool results, and the answer */ steps: AgentStep[]; /** The final answer text */ answer: string; /** True while a run is in flight */ isRunning: boolean; /** True once the model is loaded and ready */ isReady: boolean; /** Manually trigger model loading */ load: () => Promise<void>; /** Clear steps, answer, and error */ reset: () => void; /** Last error message, or null */ error: string | null;}AgentTool & AgentStep
Both types are exported from @tryhamster/gerbil/hooks. An AgentTool is framework-agnostic — its execute function receives the parsed arguments and returns a string (or a promise of one) that is fed back to the model. Tool objects you define in .gerbil/tools/ share this shape, so they can be passed straight to useAgent.
interface AgentTool { /** Tool name the model calls */ name: string; /** Description shown to the model */ description: string; /** JSON-schema-ish parameter shape, shown to the model */ parameters?: unknown; /** Runs the tool; its return value is fed back into the loop */ execute: (args: any) => Promise<string> | string;}
type AgentStep = | { kind: "tool_call"; tool: string; args: unknown } | { kind: "tool_result"; tool: string; result: string } | { kind: "answer"; text: string };Lower-level: engine.generateWithTools
For Node or imperative use, the core WebGPUEngine from @tryhamster/gerbil/gpu exposes generateWithTools, which runs the same loop without React. It generates, parses a <tool_call>{"name","arguments"}</tool_call> block, runs the matching tool's execute, feeds the result back, and repeats until the model answers with no tool call:
01import { WebGPUEngine } from "@tryhamster/gerbil/gpu";02
03const engine = await WebGPUEngine.create({04 repo: "mlx-community/Qwen3.5-0.8B-4bit",05});06
07const { text, steps } = await engine.generateWithTools(08 "What's the weather in Paris?",09 {10 tools: [weatherTool],11 maxSteps: 5,12 onStep: (step) => console.log(step),13 maxTokens: 512,14 }15);16
17console.log(text); // final answer18console.log(steps); // AgentStep[] tracegenerateWithTools accepts the same AgentTool[] and an optional onStep callback, plus standard maxTokens and sampling options. It returns { text, steps }, the final answer and the full trace.
Connecting Skills as Tools
Skills and tools share a similar shape, so you can wrap any skill as an AgentTool and pass it to useAgent or generateWithTools:
01import { summarize } from "@tryhamster/gerbil/skills";02import type { AgentTool } from "@tryhamster/gerbil/hooks";03
04const summarizeTool: AgentTool = {05 name: "summarize",06 description: "Summarize text content",07 parameters: {08 type: "object",09 properties: {10 content: { type: "string" },11 length: { type: "string", enum: ["short", "medium", "long"] },12 },13 required: ["content"],14 },15 execute: async ({ content, length }) =>16 summarize({ content, length }),17};API Reference
Tool file shape
A .gerbil/tools/*.tool.ts file default-exports an object with this shape (no imports required):
interface ToolDefinition { /** Tool name (function name) */ name: string;
/** Description for the LLM */ description: string;
/** Optional JSON-schema parameter shape shown to the model */ parameters?: unknown;
/** The function to execute; returns a string fed back to the model */ execute: (params: any, ctx?: { generate: (p: string) => Promise<string> }) => Promise<string> | string;}Agent API
The public, importable surface for running tools is the agent loop:
// React/browser — @tryhamster/gerbil/hooksuseAgent(options: UseAgentOptions): UseAgentReturn
// Node/imperative — @tryhamster/gerbil/gpuengine.generateWithTools( prompt: string, options: { tools: AgentTool[]; maxSteps?: number; onStep?: (s: AgentStep) => void }): Promise<{ text: string; steps: AgentStep[] }>Model Compatibility
Tool calling works best with:
- ✓Qwen3.5-0.8B — Excellent tool calling and JSON formatting (native default)
- ~LFM2.5-350M — Faster/smaller; usable for simple tool calls, less reliable JSON
Best Practices
- 1.Clear descriptions — The model uses descriptions to decide when to call tools
- 2.Simple parameters — Use basic types when possible
- 3.Helpful errors — Return clear error messages
- 4.Validate inputs — Zod schemas validate before execution
- 5.Keep responses concise — Long tool outputs consume context