Skip to content
ChatJS
Esc
navigateopen⌘Jpreview
On this page

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}.tscomponents/part/{name}.tsx

How it works

  1. Define tool with Vercel AI SDK’s tool() - schema + execute function
  2. Register in lib/ai/tools/tools.ts
  3. Route tool type to component in components/message-parts.tsx
  4. 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}&current=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>
  );
}

Was this page helpful?