TanStack AI 工具系统提供了一个强大、灵活的架构,使 AI 代理能够与外部系统交互
服务器工具 在后端安全地执行,并自动处理
客户端工具 在浏览器中执行,用于 UI 更新和本地操作
代理循环 能够实现多步骤推理和复杂的工作流程
工具状态 提供实时反馈并实现强大的 UI
审批流程 赋予用户对敏感操作的控制权。这种架构能够构建复杂的 AI 应用程序,可以
从 API 和数据库中获取数据
执行计算和转换
更新 UI 并管理状态
执行多步骤工作流程
需要用户批准敏感操作
当用户发送需要使用工具的消息时,将发生以下流程
服务器(API 路由)
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { getWeather, sendEmail } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
// Create streaming chat with tools
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [getWeather, sendEmail], // Tool definitions passed here
});
return toServerSentEventsResponse(stream);
}
客户端(React 组件)
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
function ChatComponent() {
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
<div>
{messages.map((message) => (
<div key={message.id}>{/* Render message */}</div>
))}
<input onSubmit={(e) => sendMessage(e.target.value)} />
</div>
);
}
工具在其生命周期内会经历不同的状态。了解这些状态有助于构建强大的 UI 并调试工具执行。
| 状态 | 描述 | 客户端操作 |
|---|---|---|
| awaiting-input | 已接收工具调用,但尚未接收到参数 | 显示加载 |
| input-streaming | 正在接收部分参数 | 显示进度 |
| input-complete | 已接收所有参数 | 准备执行 |
| approval-requested | 正在等待用户批准 | 显示审批 UI |
| approval-responded | 用户已批准/拒绝 | 执行或取消 |
| 状态 | 描述 | 客户端操作 |
|---|---|---|
| streaming | 正在流式传输结果(未来功能) | 显示进度 |
| complete | 结果已完成 | 显示结果 |
| error | 执行过程中发生错误 | 显示错误消息 |
function ChatComponent() {
const { messages } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
<div>
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part) => {
if (part.type === "tool-call") {
return (
<div key={part.id} className="tool-status">
{/* Show state-specific UI */}
{part.state === "awaiting-input" && (
<div>🔄 Calling {part.name}...</div>
)}
{part.state === "input-streaming" && (
<div>📥 Receiving arguments...</div>
)}
{part.state === "input-complete" && (
<div>✓ Arguments ready</div>
)}
{part.state === "approval-requested" && (
<ApprovalUI part={part} />
)}
</div>
);
}
if (part.type === "tool-result") {
return (
<div key={part.toolCallId}>
{part.state === "complete" && (
<div>✓ Tool completed</div>
)}
{part.state === "error" && (
<div>❌ Error: {part.error}</div>
)}
</div>
);
}
})}
</div>
))}
</div>
);
}
对于敏感操作,工具在执行前可能需要用户批准
使用审批定义工具
const sendEmailDef = toolDefinition({
name: "send_email",
description: "Send an email",
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
needsApproval: true, // Requires user approval
});
const sendEmail = sendEmailDef.server(async ({ to, subject, body }) => {
await emailService.send({ to, subject, body });
return { success: true };
});
在客户端中处理审批
const { messages, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
// In your render:
{part.state === "approval-requested" && (
<div>
<p>Approve sending email to {part.arguments.to}?</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: false,
})
}
>
Deny
</button>
</div>
)}
有些工具需要在两个环境中执行
// Server: Fetch data from database
const fetchUserPrefsDef = toolDefinition({
name: "fetch_user_preferences",
description: "Get user preferences from server",
inputSchema: z.object({
userId: z.string(),
}),
});
const fetchUserPreferences = fetchUserPrefsDef.server(async ({ userId }) => {
const prefs = await db.userPreferences.findUnique({ where: { userId } });
return prefs;
});
// Client: Apply preferences to UI
const applyPrefsDef = toolDefinition({
name: "apply_preferences",
description: "Apply user preferences to the UI",
inputSchema: z.object({
theme: z.string(),
language: z.string(),
}),
});
// On client, create client implementation
const applyPreferences = applyPrefsDef.client(async ({ theme, language }) => {
// Update UI state with preferences
document.body.className = theme;
i18n.changeLanguage(language);
return { applied: true };
});
// Usage: LLM can chain these together
// 1. Call fetchUserPreferences (server)
// 2. Call applyPreferences with the result (client)
为了提高效率,LLM 可以并行调用多个工具
示例
User: "Compare the weather in NYC, SF, and LA"
LLM calls:
- get_weather({city: "NYC"}) [index: 0]
- get_weather({city: "SF"}) [index: 1]
- get_weather({city: "LA"}) [index: 2]
All execute simultaneously, then LLM generates comparison.