字
字节笔记本
2026年2月23日
TanStack AI 工具架构完全指南
TanStack AI 提供了一个强大而灵活的工具系统架构,使 AI 智能体能够与外部系统进行交互。本文将详细介绍该架构的核心组件、调用流程、状态生命周期以及最佳实践。
架构概览
TanStack AI 工具系统包含以下核心组件:
- Server Tools: 在服务器端安全执行,自动处理后端逻辑
- Client Tools: 在浏览器中执行,用于 UI 更新和本地操作
- Agentic Cycle: 支持多步推理和复杂工作流
- Tool States: 提供实时反馈,支持构建健壮的 UI
- Approval Flow: 为敏感操作提供用户控制
该架构支持构建能够执行以下操作的复杂 AI 应用:
- 从 API 和数据库获取数据
- 执行计算和数据转换
- 更新 UI 和管理状态
- 执行多步工作流
- 对敏感操作要求用户批准
调用流程:客户端到 LLM 服务
当用户发送需要工具使用的消息时,流程如下:
步骤分解
-
用户输入: 用户在聊天界面输入消息
-
客户端请求: 浏览器向服务器发送 POST 请求,包含:
- 当前对话历史 (messages)
- 可选的数据载荷 (body)
-
服务器处理: 服务器执行以下操作:
- 接收请求
- 从请求体中提取消息
- 将工具定义转换为 LLM 期望的格式
- 向 LLM 服务(OpenAI、Anthropic 等)发送请求
-
LLM 决策: LLM 服务执行:
- 分析对话和可用工具
- 根据用户请求决定是否调用工具
- 生成带参数的工具调用
-
流式响应: LLM 流式返回数据块:
tool_call块:包含工具名称和参数content块:文本响应done块:完成标记
-
客户端更新: 浏览器接收数据块并实时更新 UI
代码示例
服务器端 (API 路由):
typescript
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();
// 创建带工具的流式聊天
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [getWeather, sendEmail], // 在此传递工具定义
});
return toServerSentEventsResponse(stream);
}客户端 (React 组件):
typescript
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}>{/* 渲染消息 */}</div>
))}
<input onSubmit={(e) => sendMessage(e.target.value)} />
</div>
);
}状态与生命周期
工具在执行过程中会经历不同的状态。理解这些状态有助于构建健壮的 UI 和调试工具执行。
调用状态
| 状态 | 描述 | 客户端操作 |
|---|---|---|
awaiting-input | 已接收工具调用,尚无参数 | 显示加载中 |
input-streaming | 正在接收部分参数 | 显示进度 |
input-complete | 已接收所有参数 | 准备执行 |
approval-requested | 等待用户批准 | 显示批准 UI |
approval-responded | 用户已批准/拒绝 | 执行或取消 |
结果状态
| 状态 | 描述 | 客户端操作 |
|---|---|---|
streaming | 正在流式传输结果(未来功能) | 显示进度 |
complete | 结果已完成 | 显示结果 |
error | 执行过程中发生错误 | 显示错误消息 |
在 React 中监控工具状态
typescript
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">
{/* 显示状态特定的 UI */}
{part.state === "awaiting-input" && (
<div>🔄 正在调用 {part.name}...</div>
)}
{part.state === "input-streaming" && (
<div>📥 正在接收参数...</div>
)}
{part.state === "input-complete" && (
<div>✓ 参数已就绪</div>
)}
{part.state === "approval-requested" && (
<ApprovalUI part={part} />
)}
</div>
);
}
if (part.type === "tool-result") {
return (
<div key={part.toolCallId}>
{part.state === "complete" && (
<div>✓ 工具执行完成</div>
)}
{part.state === "error" && (
<div>❌ 错误: {part.error}</div>
)}
</div>
);
}
})}
</div>
))}
</div>
);
}批准流程
对于敏感操作,工具可以在执行前要求用户批准:
定义需要批准的工具:
typescript
const sendEmailDef = toolDefinition({
name: "send_email",
description: "发送邮件",
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
needsApproval: true, // 需要用户批准
});
const sendEmail = sendEmailDef.server(async ({ to, subject, body }) => {
await emailService.send({ to, subject, body });
return { success: true };
});在客户端处理批准:
typescript
const { messages, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
// 在渲染中:
{part.state === "approval-requested" && (
<div>
<p>批准向 {part.arguments.to} 发送邮件吗?</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: true,
})
}
>
批准
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: false,
})
}
>
拒绝
</button>
</div>
)}混合工具 (服务器 + 客户端)
某些工具需要在两个环境中执行:
typescript
// 服务器端: 从数据库获取数据
const fetchUserPrefsDef = toolDefinition({
name: "fetch_user_preferences",
description: "从服务器获取用户偏好设置",
inputSchema: z.object({
userId: z.string(),
}),
});
const fetchUserPreferences = fetchUserPrefsDef.server(async ({ userId }) => {
const prefs = await db.userPreferences.findUnique({ where: { userId } });
return prefs;
});
// 客户端: 将偏好设置应用到 UI
const applyPrefsDef = toolDefinition({
name: "apply_preferences",
description: "将用户偏好设置应用到 UI",
inputSchema: z.object({
theme: z.string(),
language: z.string(),
}),
});
// 在客户端创建客户端实现
const applyPreferences = applyPrefsDef.client(async ({ theme, language }) => {
// 使用偏好设置更新 UI 状态
document.body.className = theme;
i18n.changeLanguage(language);
return { applied: true };
});
// 用法: LLM 可以将这些串联起来
// 1. 调用 fetchUserPreferences (服务器)
// 2. 使用结果调用 applyPreferences (客户端)并行工具执行
LLM 可以并行调用多个工具以提高效率:
typescript
// 用户: "比较纽约、旧金山和洛杉矶的天气"
// LLM 调用:
// - get_weather({city: "NYC"}) [index: 0]
// - get_weather({city: "SF"}) [index: 1]
// - get_weather({city: "LA"}) [index: 2]
// 所有工具同时执行,然后 LLM 生成比较结果最佳实践
工具设计
- 单一职责: 每个工具应该做好一件事
- 清晰的描述: 帮助 LLM 理解何时使用该工具
- 类型安全: 使用 Zod schema 进行输入/输出验证
- 错误处理: 返回有意义的错误消息,不要抛出异常
- 幂等性: 工具应该可以安全地多次调用
安全性
- 服务器 vs 客户端: 将敏感操作放在服务器端
- 批准流程: 对破坏性操作使用
needsApproval - 输入验证: 始终验证工具输入
- 速率限制: 对昂贵的工具实现速率限制
- 审计日志: 记录工具执行以便调试和安全审计
性能
- 缓存: 在适当时缓存工具结果
- 并行执行: 尽可能启用并行工具调用
- 流式传输: 对长时间运行的操作使用流式传输
- 超时: 为外部 API 调用设置超时
- 懒加载: 仅在需要时加载工具
下一步
- Tools Overview - 基本工具概念和示例
- Server Tools - 服务器端工具深入指南
- Client Tools - 客户端工具深入指南
- Tool Approval Flow - 实现批准工作流
- Stream Chunk Definitions - 理解流式协议
原文链接: https://tanstack.com/ai/latest/docs/guides/tool-architecture
分享: