Tool Part
Backend tool definition to frontend UI component
Give your AI tools custom UI components that show loading states and rich results.
Overview
Tools let the LLM perform actions (API calls, calculations, etc). This pattern connects a backend tool to a frontend component that renders its input/output with proper loading states.
File convention: lib/ai/tools/{name}.ts → components/part/{name}.tsx
How it works
- Define tool with Vercel AI SDK’s
tool()- schema + execute function - Register in
lib/ai/tools/tools.ts - Route tool type to component in
components/message-parts.tsx - Render with loading/complete states based on
tool.state
The AI SDK streams tool calls as parts with two states:
| State | tool.state |
Available |
|---|---|---|
| Loading | "input-available" |
tool.input |
| Complete | "output-available" |
tool.input + tool.output |
Code
1. Tool Definition
import { tool } from "ai";
import { z } from "zod";
export const getWeather = tool({
description: "Get the current weather at a location",
inputSchema: z.object({
latitude: z.number(),
longitude: z.number(),
}),
execute: async ({
latitude,
longitude,
}: {
latitude: number;
longitude: number;
}) => {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m&hourly=temperature_2m&daily=sunrise,sunset&timezone=auto`
);
const weatherData = await response.json();
return weatherData as WeatherAtLocation;
},
});
export type WeatherAtLocation = {
latitude: number;
longitude: number;
generationtime_ms: number;
utc_offset_seconds: number;
timezone: string;
timezone_abbreviation: string;
elevation: number;
current_units: {
time: string;
interval: string;
temperature_2m: string;
};
current: {
time: string;
interval: number;
temperature_2m: number;
};
hourly_units: {
time: string;
temperature_2m: string;
};
hourly: {
time: string[];
temperature_2m: number[];
};
daily_units: {
time: string;
sunrise: string;
sunset: string;
};
daily: {
time: string[];
sunrise: string[];
sunset: string[];
};
};
2. Register Tool
import { getWeather } from "./get-weather";
export function getTools(
{
/* ...params */
}
) {
return {
getWeather,
// ... other tools
};
}
3. Route to Component
import { Weather } from "./part/weather";
function ToolPart({
part,
messageId,
isReadonly,
}: {
part: ToolUIPart<ChatTools>;
messageId: string;
isReadonly: boolean;
}) {
if (part.type === "tool-getWeather") {
return <Weather tool={part} />;
}
// ... other tools
return null;
}
4. UI Component
"use client";
import type { WeatherAtLocation } from "@/lib/ai/tools/get-weather";
import type { ChatMessage } from "@/lib/ai/types";
export type WeatherTool = Extract<
ChatMessage["parts"][number],
{ type: "tool-getWeather" }
>;
export function Weather({ tool }: { tool: WeatherTool }) {
const isLoading = tool.state === "input-available";
if (isLoading) {
return <div className="skeleton rounded-2xl bg-blue-400 p-4 h-24 w-48" />;
}
const data = tool.output;
return (
<div className="rounded-2xl bg-blue-400 p-4 text-white">
<div className="text-4xl">
{Math.round(data.current.temperature_2m)}
{data.current_units.temperature_2m}
</div>
</div>
);
}