字节笔记本

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 服务

当用户发送需要工具使用的消息时,流程如下:

步骤分解

  1. 用户输入: 用户在聊天界面输入消息

  2. 客户端请求: 浏览器向服务器发送 POST 请求,包含:

    • 当前对话历史 (messages)
    • 可选的数据载荷 (body)
  3. 服务器处理: 服务器执行以下操作:

    • 接收请求
    • 从请求体中提取消息
    • 将工具定义转换为 LLM 期望的格式
    • 向 LLM 服务(OpenAI、Anthropic 等)发送请求
  4. LLM 决策: LLM 服务执行:

    • 分析对话和可用工具
    • 根据用户请求决定是否调用工具
    • 生成带参数的工具调用
  5. 流式响应: LLM 流式返回数据块:

    • tool_call 块:包含工具名称和参数
    • content 块:文本响应
    • done 块:完成标记
  6. 客户端更新: 浏览器接收数据块并实时更新 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 调用设置超时
  • 懒加载: 仅在需要时加载工具

下一步


原文链接: https://tanstack.com/ai/latest/docs/guides/tool-architecture

分享: