工具(也称为“函数调用”)允许 AI 模型与外部系统、API 或执行计算进行交互。TanStack AI 提供了一个同构工具系统,可以实现类型安全、框架无关的工具定义,这些定义可以在服务器和客户端上工作。
工具使您的 AI 应用程序能够
TanStack AI 与任何 JavaScript 框架一起工作
TanStack AI 与任何 JavaScript 框架一起工作。
TanStack AI 使用一个两步工具定义过程
这种方法提供
TanStack AI 支持两种定义工具模式的方式
Zod 模式提供完整的 TypeScript 类型推断和运行时验证
import { z } from "zod";
const inputSchema = z.object({
location: z.string().describe("City name"),
unit: z.enum(["celsius", "fahrenheit"]).optional(),
});
对于您已经拥有 JSON Schema 定义或更喜欢不使用 Zod 的情况,您可以直接传递原始 JSON Schema 对象
import type { JSONSchema } from "@tanstack/ai";
const inputSchema: JSONSchema = {
type: "object",
properties: {
location: {
type: "string",
description: "City name",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
},
},
required: ["location"],
};
注意:使用 JSON Schema 时,TypeScript 将为输入/输出类型推断 any,因为 JSON Schema 无法提供编译时类型信息。建议使用 Zod 模式以获得完整的类型安全。
工具使用 toolDefinition() 从 @tanstack/ai 定义
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
// Step 1: Define the tool schema
const getWeatherDef = toolDefinition({
name: "get_weather",
description: "Get the current weather for a location",
inputSchema: z.object({
location: z.string().describe("The city and state, e.g. San Francisco, CA"),
unit: z.enum(["celsius", "fahrenheit"]).optional(),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
location: z.string(),
}),
});
// Step 2: Create a server implementation
const getWeatherServer = getWeatherDef.server(async ({ location, unit }) => {
const response = await fetch(
`https://api.weather.com/v1/current?location=${location}&unit=${
unit || "fahrenheit"
}`
);
const data = await response.json();
return {
temperature: data.temperature,
conditions: data.conditions,
location: data.location,
};
});
如果您更喜欢 JSON Schema 或有现有的模式定义
import { toolDefinition } from "@tanstack/ai";
import type { JSONSchema } from "@tanstack/ai";
// Define schemas using JSON Schema
const inputSchema: JSONSchema = {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
},
},
required: ["location"],
};
const outputSchema: JSONSchema = {
type: "object",
properties: {
temperature: { type: "number" },
conditions: { type: "string" },
location: { type: "string" },
},
required: ["temperature", "conditions", "location"],
};
// Create the tool definition
const getWeatherDef = toolDefinition({
name: "get_weather",
description: "Get the current weather for a location",
inputSchema,
outputSchema,
});
// Create server implementation (args is typed as `any` with JSON Schema)
const getWeatherServer = getWeatherDef.server(async (args) => {
const { location, unit } = args;
const response = await fetch(
`https://api.weather.com/v1/current?location=${location}&unit=${unit || "fahrenheit"}`
);
return await response.json();
});
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { getWeatherDef } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
// Create server implementation
const getWeather = getWeatherDef.server(async ({ location, unit }) => {
const response = await fetch(`https://api.weather.com/v1/current?...`);
return await response.json();
});
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [getWeather], // Pass server tools
});
return toServerSentEventsResponse(stream);
}
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tools";
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// Update UI state
setNotification(input.message);
return { success: true };
});
const saveToStorage = saveToStorageDef.client((input) => {
localStorage.setItem("data", JSON.stringify(input));
return { saved: true };
});
// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI, saveToStorage);
const textOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
// Infer message types for full type safety
type ChatMessages = InferChatMessages<typeof textOptions>;
function ChatComponent() {
const { messages, sendMessage } = useChat(textOptions);
// messages is now fully typed with tool names and outputs!
return <Messages messages={messages} />;
}
工具可以针对服务器和客户端实现,从而实现灵活的执行模式
// Define once
const addToCartDef = toolDefinition({
name: "add_to_cart",
description: "Add item to shopping cart",
inputSchema: z.object({
itemId: z.string(),
quantity: z.number(),
}),
outputSchema: z.object({
success: z.boolean(),
cartId: z.string(),
}),
needsApproval: true,
});
// Server implementation - Store in database
const addToCartServer = addToCartDef.server(async (input) => {
const cart = await db.carts.create({
data: { itemId: input.itemId, quantity: input.quantity },
});
return { success: true, cartId: cart.id };
});
// Client implementation - Update local wishlist
const addToCartClient = addToCartDef.client((input) => {
const wishlist = JSON.parse(localStorage.getItem("wishlist") || "[]");
wishlist.push(input.itemId);
localStorage.setItem("wishlist", JSON.stringify(wishlist));
return { success: true, cartId: "local" };
});
在服务器上,传递定义(用于客户端执行)或服务器实现
chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [addToCartDef], // Client will execute, or
tools: [addToCartServer], // Server will execute
});
同构架构提供完整的类型安全
// In your React component
messages.forEach((message) => {
message.parts.forEach((part) => {
if (part.type === 'tool-call' && part.name === 'add_to_cart') {
// ✅ TypeScript knows part.name is literally 'add_to_cart'
// ✅ part.input is typed as { itemId: string, quantity: number }
// ✅ part.output is typed as { success: boolean, cartId: string } | undefined
if (part.output) {
console.log(part.output.cartId); // ✅ Fully typed!
}
}
});
});
工具在执行过程中会经历不同的状态