> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools & Function Calling

> Define tools with defineTool(), Zod schemas, and execute function calling in Agentium agents.

# Tools & Function Calling

Agentium agents can call external functions (tools) during a run. Tools are defined with `defineTool()`, validated with Zod schemas, and executed automatically when the LLM decides to use them.

***

## defineTool()

Use `defineTool()` to create a type-safe tool definition:

```typescript theme={null}
import { defineTool } from "@agentium/core";
import { z } from "zod";

const myTool = defineTool({
  name: "toolName",
  description: "What the tool does (used by the LLM to decide when to call it)",
  parameters: z.object({
    param1: z.string().describe("Description for the LLM"),
    param2: z.number().optional(),
  }),
  execute: async (args, ctx) => {
    // args is typed from the Zod schema
    return "Result string or ToolResult";
  },
});
```

***

## Tool Definition Anatomy

<Accordion title="name">
  Unique identifier for the tool. The LLM uses this to invoke the tool. Use camelCase (e.g. `getWeather`, `searchDatabase`).
</Accordion>

<Accordion title="description">
  Natural language description of what the tool does. The LLM uses this to decide when to call the tool. Be clear and specific.
</Accordion>

<Accordion title="parameters">
  A Zod object schema defining the tool's input. Use `.describe()` on fields to help the LLM understand each parameter. The schema is converted to JSON Schema for the provider.
</Accordion>

<Accordion title="execute">
  Async function that runs when the tool is called. Receives parsed `args` (typed from the schema) and `RunContext`. Returns a `string` or `ToolResult` (with optional artifacts).
</Accordion>

***

## Examples

### Weather Tool

```typescript theme={null}
import { defineTool } from "@agentium/core";
import { z } from "zod";

const weatherTool = defineTool({
  name: "getWeather",
  description: "Get the current weather for a city",
  parameters: z.object({
    city: z.string().describe("City name"),
  }),
  execute: async ({ city }) => {
    const conditions = ["sunny", "cloudy", "rainy", "snowy"];
    const temp = Math.floor(Math.random() * 30) + 5;
    const condition = conditions[Math.floor(Math.random() * conditions.length)];
    return `${city}: ${temp}°C, ${condition}`;
  },
});
```

### Calculator Tool

```typescript theme={null}
const calculator = defineTool({
  name: "calculator",
  description: "Evaluate a math expression",
  parameters: z.object({
    expression: z.string().describe("Math expression to evaluate (e.g. 2 + 3 * 4)"),
  }),
  execute: async ({ expression }) => {
    const result = new Function(`return (${expression})`)();
    return String(result);
  },
});
```

***

## Multiple Tools on an Agent

Pass an array of tools to the agent:

```typescript theme={null}
const agent = new Agent({
  name: "ToolBot",
  model: openai("gpt-4o"),
  tools: [weatherTool, calculator],
  instructions: "You help with weather queries and math. Use tools when needed.",
});
```

The LLM receives all tool definitions and can call any of them in a single turn when appropriate.

***

## ToolResult Type

Instead of returning a plain string, return a `ToolResult` to include optional artifacts:

```typescript theme={null}
interface ToolResult {
  content: string;   // The text sent back to the LLM
  artifacts?: Artifact[];  // Optional structured data for downstream use
}

interface Artifact {
  type: string;
  data: unknown;
  mimeType?: string;
}
```

<CodeGroup>
  ```typescript String result theme={null}
  execute: async ({ query }) => {
    return `Found 3 results for "${query}"`;
  }
  ```

  ```typescript ToolResult with artifacts theme={null}
  execute: async ({ query }, ctx) => {
    const results = await searchAPI(query);
    return {
      content: `Found ${results.length} results.`,
      artifacts: [
        { type: "search_results", data: results, mimeType: "application/json" },
      ],
    };
  }
  ```
</CodeGroup>

***

## Tool Artifacts

Artifacts are structured data attached to a tool result. The LLM only sees the `content` string, but your application receives the full artifacts in `ToolCallResult`. Use artifacts to pass structured data (JSON, images, charts) through the agent pipeline without polluting the LLM context.

```typescript theme={null}
const chartTool = defineTool({
  name: "generateChart",
  description: "Generate a sales chart for a given time period",
  parameters: z.object({
    period: z.enum(["week", "month", "quarter"]).describe("Time period"),
  }),
  execute: async ({ period }) => {
    const data = await getSalesData(period);
    const chartUrl = await renderChart(data);

    return {
      content: `Generated ${period}ly sales chart. Total revenue: $${data.total.toLocaleString()}.`,
      artifacts: [
        {
          type: "chart",
          data: { url: chartUrl, period, dataPoints: data.points },
          mimeType: "image/png",
        },
        {
          type: "raw_data",
          data: data.points,
          mimeType: "application/json",
        },
      ],
    };
  },
});

const result = await agent.run("Show me this month's sales chart");
console.log(result.text); // LLM's summary

// Access artifacts from tool calls
for (const tc of result.toolCalls) {
  const artifacts = tc.result?.artifacts ?? [];
  for (const artifact of artifacts) {
    if (artifact.type === "chart") {
      displayChart(artifact.data.url);
    }
  }
}
```

***

## Parallel Tool Execution

When the LLM returns multiple tool calls in a single turn, Agentium executes them in parallel (batches of up to 5 by default). Each tool call is validated against its schema; invalid arguments produce an error result for that call only.

```typescript theme={null}
// Agent can call getWeather("Tokyo") and calculator("42 * 17") in the same turn
const result = await agent.run("What's the weather in Tokyo? Also, what's 42 * 17?");
// Both tools run in parallel; results are combined and sent back to the LLM.
```

***

## Tool Caching

Add a `cache` property to any tool to cache results and avoid redundant executions:

```typescript theme={null}
const weatherTool = defineTool({
  name: "getWeather",
  description: "Get current weather for a city",
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => { /* ... */ },
  cache: { ttl: 60_000 }, // Cache for 1 minute
});
```

See [Tool Caching](/agents/tool-caching) for full documentation.

***

## Strict Mode (OpenAI Structured Outputs)

Enable `strict` on a tool to activate OpenAI's Structured Outputs for tool calls. When enabled, the model is guaranteed to return valid JSON matching the schema — no malformed arguments.

```typescript theme={null}
const weatherTool = defineTool({
  name: "getWeather",
  description: "Get weather for a city",
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => `Sunny in ${city}`,
  strict: true,
});
```

When `strict` is `true`, Agentium automatically:

1. Strips verbose JSON Schema metadata (`$schema`, `additionalProperties` on nested objects)
2. Sets `additionalProperties: false` at the top level (required by OpenAI)
3. Passes `strict: true` to the OpenAI function definition

<Note>
  Strict mode is only supported by OpenAI models. For other providers, the `strict` option is ignored.
</Note>

***

## Sandbox & Approval

Tools support two additional safety features:

* **`sandbox`** — Run the tool in an isolated subprocess with timeout and memory limits. See [Sandbox Execution](/agents/sandbox).
* **`requiresApproval`** — Require human approval before executing the tool. See [Human-in-the-Loop](/agents/approval).

```typescript theme={null}
const riskyTool = defineTool({
  name: "deleteUser",
  description: "Delete a user account",
  parameters: z.object({ userId: z.string() }),
  execute: async ({ userId }) => `Deleted ${userId}`,
  sandbox: { timeout: 5_000 },
  requiresApproval: true,
});
```

***

## Tool Error Handling

When a tool's `execute` function throws an error, Agentium catches it and returns the error message as the tool result. The LLM sees the error and can decide how to respond — often retrying with different arguments or explaining the failure to the user.

```typescript theme={null}
const flightTool = defineTool({
  name: "searchFlights",
  description: "Search for available flights",
  parameters: z.object({
    origin: z.string().describe("Airport code (e.g. LAX)"),
    destination: z.string().describe("Airport code (e.g. JFK)"),
    date: z.string().describe("Date in YYYY-MM-DD format"),
  }),
  execute: async ({ origin, destination, date }) => {
    const flights = await flightAPI.search(origin, destination, date);
    if (flights.length === 0) {
      throw new Error(`No flights found from ${origin} to ${destination} on ${date}`);
    }
    return JSON.stringify(flights.slice(0, 5));
  },
});
```

When the tool throws, the LLM receives:

```
Error: No flights found from LAX to JFK on 2026-03-15
```

The LLM can then inform the user or try alternative dates. Tool errors do **not** abort the run — they are treated as informational results.

For schema validation errors (wrong argument types), Agentium returns a descriptive validation error so the model can self-correct.

***

## Tool Router (Automatic Tool Selection)

When an agent has many tools (e.g., 50+ from an MCP server), sending all tool schemas in every prompt wastes tokens and can confuse the model. The **Tool Router** automatically selects only the relevant tools for each query using a cheap/fast model.

### Basic usage

Pass a `toolRouter` config to the agent. The router runs before every LLM call and filters the tool list:

```typescript theme={null}
import { Agent, openai, anthropic } from "@agentium/core";

const agent = new Agent({
  name: "assistant",
  model: anthropic("claude-sonnet-4-6"),
  tools: allMyTools, // 50+ tools
  toolRouter: {
    model: anthropic("claude-haiku-4-5-20251001"), // cheap model for selection
    maxTools: 8,
  },
});
```

The main model only sees the 8 most relevant tools instead of all 50+.

### Configuration

| Option        | Type            | Default    | Description                                                                         |
| ------------- | --------------- | ---------- | ----------------------------------------------------------------------------------- |
| `model`       | `ModelProvider` | *required* | Cheap/fast model used to select relevant tools                                      |
| `maxTools`    | `number`        | `8`        | Maximum number of tools to select per query                                         |
| `minTools`    | `number`        | `0`        | Minimum tools to return; if selection returns fewer, all tools are sent as fallback |
| `temperature` | `number`        | `0`        | Temperature for the selection model                                                 |

### How it works

1. Before each `run()` or `stream()` call, the router sends the user query and a compact tool index (name + description) to the selection model.
2. The selection model returns a JSON array of tool names.
3. The agent rebuilds its tool set with only the selected tools for that turn.
4. If selection fails or returns too few tools, all tools are sent as a safe fallback.

### When to use it

* **Many tools** — MCP servers or large toolkit collections with 10+ tools
* **Cost-sensitive** — Reducing prompt tokens directly lowers cost
* **Mixed-domain agents** — Agent has tools across shipping, billing, CRM, etc. and most queries only need a few

### Choosing a router model

Use the cheapest model that can reliably read tool names and match them to a query. Good choices:

* `anthropic("claude-haiku-4-5-20251001")`
* `openai("gpt-4o-mini")`
* `google("gemini-2.0-flash")`

The router model does **not** need to be the same provider as the main model.

<Note>
  The `toolRouter` config is entirely optional. If omitted, the agent sends all tools on every call (the default behavior).
</Note>

***

## Tool Result Limits

When tools return large payloads (e.g. an MCP server returning 200KB of database records), the entire result is sent back to the LLM on the next roundtrip — causing massive prompt token usage and high costs.

**`toolResultLimit`** intercepts oversized tool results and either smart-truncates them or summarizes them via a cheap model before they reach the main LLM.

### Basic usage

```typescript theme={null}
const agent = new Agent({
  name: "assistant",
  model: anthropic("claude-sonnet-4-6"),
  tools: allMyTools,
  toolResultLimit: {
    maxChars: 20000,        // Trigger at 20K chars (~5K tokens)
    strategy: "truncate",   // Smart JSON truncation (default)
  },
});
```

### With summarization

Use a cheap model to summarize large results instead of truncating. This preserves data fidelity while cutting tokens:

```typescript theme={null}
const agent = new Agent({
  name: "assistant",
  model: anthropic("claude-sonnet-4-6"),
  tools: allMyTools,
  toolResultLimit: {
    maxChars: 20000,
    strategy: "summarize",
    model: anthropic("claude-haiku-4-5-20251001"),
  },
});
```

### Configuration

| Option     | Type                        | Default      | Description                                                       |
| ---------- | --------------------------- | ------------ | ----------------------------------------------------------------- |
| `maxChars` | `number`                    | `20000`      | Character threshold before the strategy kicks in                  |
| `strategy` | `"truncate" \| "summarize"` | `"truncate"` | How to handle oversized results                                   |
| `model`    | `ModelProvider`             | —            | Model for summarization (required when strategy is `"summarize"`) |

### Strategies

**`"truncate"` (default)** — Smart JSON-aware truncation:

* JSON arrays: keeps the first N items that fit, appends `"[Showing 15 of 892 items — 877 more omitted]"`
* JSON objects with array values: truncates each array proportionally
* Plain text: hard-cut with a note about remaining characters

**`"summarize"`** — Sends the full result to a cheap/fast model with instructions to preserve all key data points, totals, IDs, and dates. The summary replaces the original result before the main model sees it. Falls back to truncation if summarization fails.

### When to use it

* **MCP tools returning bulk data** — API endpoints that return full record sets without pagination
* **Cost-sensitive agents** — A 200KB tool result can cost \$0.25+ in prompt tokens on a single roundtrip
* **Any agent with `maxToolRoundtrips` > 0** — Tool results are sent back to the LLM on each roundtrip; large results compound quickly

<Note>
  `toolResultLimit` only affects text results sent back to the LLM. It does not modify the `ToolCallResult` stored in `RunOutput.toolCalls` — your application still has access to the original full result.
</Note>

***

## Using RunContext in Tools

The `execute` function receives two arguments: `args` (the parsed parameters) and `ctx` (the `RunContext`). Use `ctx` to access session info, metadata, state, and dependencies.

```typescript theme={null}
const orderTool = defineTool({
  name: "lookup_order",
  description: "Look up an order by ID",
  parameters: z.object({ orderId: z.string() }),
  execute: async (args, ctx) => {
    // Session and user info
    const userId = ctx.userId;
    const sessionId = ctx.sessionId;
    const tenantId = ctx.tenantId;

    // Metadata passed via RunOpts
    const region = ctx.metadata.region as string;

    // Read/write session state (persists across turns)
    const lastOrder = ctx.getState<string>("lastOrderId");
    ctx.setState("lastOrderId", args.orderId);

    // Injected dependencies
    const apiUrl = ctx.dependencies.ORDER_API_URL;

    // Check cancellation
    if (ctx.signal?.aborted) return "Request cancelled";

    return `Order ${args.orderId} for user ${userId} in ${region}`;
  },
});
```

### RunContext properties available in tools

| Property           | Type                      | Description                       |
| ------------------ | ------------------------- | --------------------------------- |
| `ctx.runId`        | `string`                  | Current run's unique ID           |
| `ctx.sessionId`    | `string`                  | Session ID                        |
| `ctx.userId`       | `string?`                 | User ID                           |
| `ctx.tenantId`     | `string?`                 | Tenant ID                         |
| `ctx.metadata`     | `Record<string, unknown>` | Arbitrary metadata from `RunOpts` |
| `ctx.sessionState` | `Record<string, unknown>` | Mutable key-value state bag       |
| `ctx.dependencies` | `Record<string, string>`  | Resolved dependency variables     |
| `ctx.signal`       | `AbortSignal?`            | Cancellation signal               |
| `ctx.eventBus`     | `EventBus`                | Event bus for emitting events     |

See [Types Reference](/agents/types-reference) for the full RunContext specification.

***

## Sandbox Configuration

Run tools in isolated subprocesses with resource limits. Set at the agent level (applies to all tools) or per-tool.

| Property       | Type                                                       | Default                       | Description                                       |
| -------------- | ---------------------------------------------------------- | ----------------------------- | ------------------------------------------------- |
| `enabled`      | `boolean`                                                  | `true` (when config provided) | Explicit on/off                                   |
| `timeout`      | `number`                                                   | `30000` (30s)                 | Execution timeout in ms                           |
| `maxMemoryMB`  | `number`                                                   | `256`                         | Max heap memory in MB                             |
| `allowNetwork` | `boolean`                                                  | `false`                       | Allow outbound network                            |
| `allowFS`      | `boolean \| { readOnly?: string[]; readWrite?: string[] }` | `false`                       | Filesystem access. Pass object for granular paths |
| `env`          | `Record<string, string>`                                   | `undefined`                   | Environment variables forwarded to the sandbox    |

```typescript theme={null}
// Agent-level sandbox (applies to all tools)
const agent = new Agent({
  model: openai("gpt-4o"),
  sandbox: {
    timeout: 10_000,
    maxMemoryMB: 128,
    allowFS: { readOnly: ["/data"] },
  },
  tools: [safeTool, riskyTool],
});

// Per-tool sandbox (overrides agent-level)
const riskyTool = defineTool({
  name: "run_code",
  description: "Execute arbitrary code",
  parameters: z.object({ code: z.string() }),
  sandbox: {
    timeout: 5000,
    maxMemoryMB: 64,
    allowNetwork: false,
    allowFS: false,
  },
  execute: async (args) => eval(args.code),
});
```

***

## Conditional Approval

`requiresApproval` can be a function that decides approval based on the arguments:

```typescript theme={null}
const deleteTool = defineTool({
  name: "delete_record",
  description: "Delete a database record",
  parameters: z.object({
    table: z.string(),
    id: z.string(),
  }),
  requiresApproval: (args) => {
    // Only require approval for production tables
    return args.table === "users" || args.table === "orders";
  },
  execute: async (args) => {
    await db.delete(args.table, args.id);
    return `Deleted ${args.id} from ${args.table}`;
  },
});
```

***

## Runtime Tool Mutation

Add, remove, and replace tools on a running agent:

```typescript theme={null}
const agent = new Agent({
  name: "flexible-agent",
  model: openai("gpt-4o"),
  tools: [searchTool],
});

// Add a tool at runtime
agent.addTool(calculatorTool);

// Remove a tool
agent.removeTool("search");

// Replace all tools
agent.setTools([newTool1, newTool2]);

// List current tools
const tools = agent.listTools();
// ["newTool1", "newTool2"]
```

### Dynamic Tool Resolver

For context-dependent tools that change per run, use `toolResolver`:

```typescript theme={null}
const agent = new Agent({
  model: openai("gpt-4o"),
  toolResolver: async (ctx) => {
    const role = ctx.metadata.role as string;
    if (role === "admin") {
      return [readTool, writeTool, deleteTool];
    }
    return [readTool]; // Regular users only get read access
  },
});
```
