# Tools
URL: /docs/guides/tools
Give your assistant actions like API calls, database queries, and more.
***
title: Tools
description: Give your assistant actions like API calls, database queries, and more.
------------------------------------------------------------------------------------
Tools enable LLMs to take actions and interact with external systems. assistant-ui provides a comprehensive toolkit for creating, managing, and visualizing tool interactions in real-time.
## Overview
Tools in assistant-ui are functions that the LLM can call to perform specific tasks. They bridge the gap between the LLM's reasoning capabilities and real-world actions like:
* Fetching data from APIs
* Performing calculations
* Interacting with databases
* Controlling UI elements
* Executing workflows
When tools are executed, you can display custom generative UI components that provide rich, interactive visualizations of the tool's execution and results. Learn more in the [Generative UI guide](/docs/guides/tool-ui).
If you haven't provided a custom UI for a tool, assistant-ui offers a
[`ToolFallback`](/docs/ui/tool-fallback) component that you can add to your
codebase to render a default UI for tool executions. You can customize this by
creating your own Tool UI component for the tool's name.
## Tool Creation Methods
assistant-ui offers multiple ways to create and register tools, each suited for different use cases:
* **`makeAssistantTool`**: Register client-defined tools with the assistant context
* **`useAssistantTool`**: Hook-based dynamic tool registration
* **`makeAssistantToolUI`**: UI-only components for existing tools
* **Direct context registration**: Advanced registration with full model context control
### 1. Using `makeAssistantTool`
Register tools with the assistant context. Returns a React component that registers the tool when rendered:
```tsx
import { makeAssistantTool, tool } from "@assistant-ui/react";
import { z } from "zod";
// Define the tool
const weatherTool = tool({
description: "Get current weather for a location",
parameters: z.object({
location: z.string().describe("City name or zip code"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
execute: async ({ location, unit }) => {
// Tool execution logic
const weather = await fetchWeatherAPI(location, unit);
return weather;
},
});
// Create the component
const WeatherTool = makeAssistantTool({
...weatherTool,
toolName: "getWeather",
});
// Place the tool component inside AssistantRuntimeProvider
function App() {
return (
);
}
```
When using server-side runtimes like Vercel AI SDK, you can pass
client-defined tools to your backend using `frontendTools`. See the
[Client-Defined Tools with
frontendTools](#client-defined-tools-with-frontendtools) section below.
### 2. Using `useAssistantTool` Hook
Register tools dynamically using React hooks. Useful for conditional tools or when tool availability depends on component state:
```tsx
import { useAssistantTool } from "@assistant-ui/react";
import { z } from "zod";
function DynamicTools() {
const [dataSource, setDataSource] = useState<"local" | "cloud">("local");
useAssistantTool({
toolName: "searchData",
description: "Search through the selected data source",
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
if (dataSource === "local") {
return await searchLocalDatabase(query);
} else {
return await searchCloudDatabase(query);
}
},
// Re-register when data source changes
enabled: true,
});
return null;
}
```
### 3. Using `makeAssistantToolUI`
Create generative UI components for tools that are defined elsewhere. This is UI-only - the tool's execution logic must be registered separately (e.g., in your backend, MCP server, or another component):
This creates only the UI component. The actual tool execution happens where
you've defined it (typically in your API route with server-based runtimes like
Vercel AI SDK).
```tsx
import { makeAssistantToolUI, AssistantToolUI } from "@assistant-ui/react";
const SearchResultsUI = makeAssistantToolUI<
{
query: string;
},
{
results: Array<{
id: string;
url: string;
title: string;
snippet: string;
}>;
}
>({
toolName: "webSearch", // Must match the registered tool's name
render: ({ args, result }) => {
return (
);
},
});
// Place the tool component inside AssistantRuntimeProvider
function App() {
return (
);
}
```
### 4. Advanced: Direct Context Registration
Use `api.modelContext().register()` when you need to configure more than just tools:
```tsx
import { tool, useAssistantApi } from "@assistant-ui/react";
import { useEffect, useState } from "react";
import { z } from "zod";
function MyComponent() {
const api = useAssistantApi();
const [isCreativeMode, setIsCreativeMode] = useState(false);
useEffect(() => {
const calculateTool = tool({
description: "Perform mathematical calculations",
parameters: z.object({
expression: z.string(),
}),
execute: async ({ expression }) => {
return eval(expression); // Note: Use proper math parser in production
},
});
// Register tools with model configuration
return api.modelContext().register({
getModelContext: () => ({
tools: { calculate: calculateTool },
callSettings: {
temperature: isCreativeMode ? 0.9 : 0.2,
maxTokens: 1000,
},
priority: 10, // Higher priority overrides other providers
}),
});
}, [api, isCreativeMode]);
return
{/* Your component */}
;
}
```
Use this approach when you need:
* Dynamic model parameters (temperature, maxTokens, etc.)
* Priority-based context merging
* Multiple context types in one registration
## Tool Paradigms
### Frontend Tools
Tools that execute in the browser, accessing client-side resources:
```tsx
const screenshotTool = tool({
description: "Capture a screenshot of the current page",
parameters: z.object({
selector: z.string().optional(),
}),
execute: async ({ selector }) => {
const element = selector ? document.querySelector(selector) : document.body;
const screenshot = await captureElement(element);
return { dataUrl: screenshot };
},
});
const ScreenshotTool = makeAssistantTool({
...screenshotTool,
toolName: "screenshot",
});
```
### Backend Tools
Tools that trigger server-side operations:
```tsx
// Backend route (AI SDK)
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
tools: {
queryDatabase: {
description: "Query the application database",
inputSchema: z.object({
query: z.string(),
table: z.string(),
}),
execute: async ({ query, table }) => {
// Server-side database access
const results = await db.query(query, { table });
return results;
},
},
},
});
return result.toUIMessageStreamResponse();
}
```
### Client-Defined Tools with frontendTools
Currently, the Vercel AI SDK adapter implements automatic serialization of client-defined tools. When using this adapter, tools registered via `makeAssistantTool`, `useAssistantTool`, or `registerModelContextProvider` are automatically included in API requests. The `frontendTools` utility helps you use these tools server-side:
```tsx
// Frontend: Define tool with makeAssistantTool
import { makeAssistantTool, tool } from "@assistant-ui/react";
const calculateTool = tool({
description: "Perform calculations",
parameters: z.object({
expression: z.string(),
}),
execute: async ({ expression }) => {
return eval(expression); // Note: Use proper math parser in production
},
});
const CalculateTool = makeAssistantTool({
...calculateTool,
toolName: "calculate",
});
// Backend: Use frontendTools to receive client tools
import { frontendTools } from "@assistant-ui/react-ai-sdk";
export async function POST(req: Request) {
const { messages, tools } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
tools: {
...frontendTools(tools), // Client-defined tools
// Additional server-side tools
queryDatabase: {
description: "Query the application database",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
return await db.query(query);
},
},
},
});
return result.toUIMessageStreamResponse();
}
```
The `frontendTools` utility is currently only available for the Vercel AI SDK
integration. Other adapters like LangGraph follow a server-side tool
definition model and don't yet implement client tool serialization. Learn more
in the [Vercel AI SDK integration guide](/docs/runtimes/ai-sdk/use-chat-hook).
### Human-in-the-Loop Tools
Tools that require human approval or input:
```tsx
import { makeAssistantTool, tool } from "@assistant-ui/react";
import { z } from "zod";
const refundTool = tool({
description: "Process a customer refund",
parameters: z.object({
orderId: z.string(),
amount: z.number(),
reason: z.string(),
}),
execute: async ({ orderId, amount, reason }) => {
// Wait for human approval
const approved = await requestHumanApproval({
action: "refund",
details: { orderId, amount, reason },
});
if (!approved) {
throw new Error("Refund rejected by administrator");
}
return await processRefund(orderId, amount);
},
});
const RefundTool = makeAssistantTool({
...refundTool,
toolName: "requestRefund",
});
```
### Tool Human Input
Tools can pause their execution to request user input or approval before continuing. This is useful for:
* Requesting user confirmation for sensitive operations
* Collecting additional information mid-execution
* Implementing progressive disclosure workflows
* Building interactive, multi-step tool experiences
```tsx
import { makeAssistantTool, tool } from "@assistant-ui/react";
import { z } from "zod";
const confirmationTool = tool({
description: "Send an email with confirmation",
parameters: z.object({
to: z.string(),
subject: z.string(),
body: z.string(),
}),
execute: async ({ to, subject, body }, { human }) => {
// Request user confirmation before sending
const confirmed = await human({
type: "confirmation",
action: "send-email",
details: { to, subject },
});
if (!confirmed) {
return {
status: "cancelled",
message: "Email sending cancelled by user",
};
}
// Proceed with sending the email
await sendEmail({ to, subject, body });
return { status: "sent", message: `Email sent to ${to}` };
},
});
const EmailTool = makeAssistantTool({
...confirmationTool,
toolName: "sendEmail",
render: ({ args, result, interrupt, resume }) => {
// The interrupt payload is available when the tool is waiting for user input
if (interrupt) {
return (
Confirm Email
Send email to: {interrupt.payload.details.to}
Subject: {interrupt.payload.details.subject}
);
}
// Show the result after completion
if (result) {
return (
{result.message}
);
}
// Show loading state
return
Preparing email...
;
},
});
```
#### Human Input Behavior
* **Payload**: The object passed to `human()` is available in the `render` function as `interrupt.payload`
* **Type**: The `interrupt` object has the structure `{ type: "human", payload: ... }`
* **Resume**: Call `resume(payload)` to continue execution - the payload becomes the resolved value of the `human()` call
* **Multiple Requests**: If `human()` is called multiple times, previous requests are automatically rejected with an error
* **Cancellation**: If the tool execution is aborted (e.g., user cancels the message), all pending requests are rejected
#### Advanced Human Input Patterns
You can use human input for complex multi-step interactions:
```tsx
const wizardTool = tool({
description: "Multi-step data processing wizard",
parameters: z.object({
dataSource: z.string(),
}),
execute: async ({ dataSource }, { human }) => {
// Step 1: Load data
const data = await loadData(dataSource);
// Step 2: Request user to select columns
const selectedColumns = await human({
type: "column-selection",
availableColumns: data.columns,
});
// Step 3: Request processing options
const options = await human({
type: "processing-options",
columns: selectedColumns,
});
// Step 4: Process data with user selections
const result = await processData(data, selectedColumns, options);
return result;
},
});
const WizardTool = makeAssistantTool({
...wizardTool,
toolName: "dataWizard",
render: ({ args, result, interrupt, resume }) => {
if (interrupt?.payload.type === "column-selection") {
return (
resume(cols)}
/>
);
}
if (interrupt?.payload.type === "processing-options") {
return (
resume(opts)}
/>
);
}
if (result) {
return ;
}
return