文档
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
类引用
函数引用
接口引用
类型别名引用
变量引用
指南

工具审批流程

工具审批流程允许您在执行敏感工具之前需要用户批准,让用户控制诸如发送电子邮件、进行购买或删除数据等操作。工具在审批过程中会经历以下状态

  1. approval-requested - 正在等待用户批准
  2. executing - 已批准,正在执行
  3. output-available - 执行完成
  4. output-error - 执行失败
  5. cancelled - 用户拒绝批准

当工具需要批准时,典型的流程是

  1. 模型调用工具
  2. 工具执行暂停
  3. 提示用户批准或拒绝
  4. 工具执行(如果批准)或被取消(如果拒绝)
  5. 对话继续,显示结果

启用审批

可以通过在定义中设置 needsApproval: true 来标记工具需要批准

typescript
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";

// Step 1: Define tool with approval requirement
const sendEmailDef = toolDefinition({
  name: "send_email",
  description: "Send an email to a recipient",
  inputSchema: z.object({
    to: z.string().email(),
    subject: z.string(),
    body: z.string(),
  }),
  outputSchema: z.object({
    success: z.boolean(),
    messageId: z.string(),
  }),
  needsApproval: true, // This tool requires approval
});

// Step 2: Create server implementation
const sendEmail = sendEmailDef.server(async ({ to, subject, body }) => {
  // Only executes if approved
  await emailService.send({ to, subject, body });
  return { success: true, messageId: "..." };
});

服务器端审批

在服务器上,具有 needsApproval: true 的工具将暂停执行并等待批准

typescript
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { sendEmail } from "./tools";

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: openaiText("gpt-5.2"),
    messages,
    tools: [sendEmail],
  });

  return toServerSentEventsResponse(stream);
}

客户端审批处理

客户端接收审批请求并可以做出响应

typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";

function ChatComponent() {
  const { messages, sendMessage, addToolApprovalResponse } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
  });

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.parts.map((part) => {
            // Check for approval requests
            if (
              part.type === "tool-call" &&
              part.state === "approval-requested" &&
              part.approval
            ) {
              return (
                <div key={part.id} className="approval-prompt">
                  <p>Approve: {part.name}</p>
                  <pre>{JSON.stringify(part.arguments, null, 2)}</pre>
                  <button
                    onClick={() =>
                      addToolApprovalResponse({
                        id: part.approval!.id,
                        approved: true,
                      })
                    }
                  >
                    Approve
                  </button>
                  <button
                    onClick={() =>
                      addToolApprovalResponse({
                        id: part.approval!.id,
                        approved: false,
                      })
                    }
                  >
                    Deny
                  </button>
                </div>
              );
            }
            // ... render other parts
          })}
        </div>
      ))}
    </div>
  );
}

审批 UI 示例

这是一个更完整的审批 UI 组件

typescript
function ApprovalPrompt({ part, onApprove, onDeny }) {
  const args = JSON.parse(part.arguments);

  return (
    <div className="border border-yellow-500 rounded-lg p-4 bg-yellow-50">
      <div className="font-semibold mb-2">
        🔒 Approval Required: {part.name}
      </div>
      <div className="text-sm text-gray-600 mb-4">
        <pre className="bg-gray-100 p-2 rounded text-xs overflow-x-auto">
          {JSON.stringify(args, null, 2)}
        </pre>
      </div>
      <div className="flex gap-2">
        <button
          onClick={onApprove}
          className="px-4 py-2 bg-green-600 text-white rounded-lg"
        >
          ✓ Approve
        </button>
        <button
          onClick={onDeny}
          className="px-4 py-2 bg-red-600 text-white rounded-lg"
        >
          ✗ Deny
        </button>
      </div>
    </div>
  );
}

具有审批的客户端工具

客户端工具也可以需要批准

typescript
// tools/definitions.ts
const deleteLocalDataDef = toolDefinition({
  name: "delete_local_data",
  description: "Delete data from local storage",
  inputSchema: z.object({
    key: z.string(),
  }),
  outputSchema: z.object({
    deleted: z.boolean(),
  }),
  needsApproval: true, // Requires approval even on client
});

// Client: Create implementation
const deleteLocalData = deleteLocalDataDef.client((input) => {
  // This will only execute after approval
  localStorage.removeItem(input.key);
  return { deleted: true };
});

const { messages, addToolApprovalResponse } = useChat({
  connection: fetchServerSentEvents("/api/chat"),
  tools: [deleteLocalData], // Automatic execution after approval
});

示例:电子商务购买

typescript
// Define tool with approval requirement
const purchaseItemDef = toolDefinition({
  name: "purchase_item",
  description: "Purchase an item from the store",
  inputSchema: z.object({
    itemId: z.string(),
    quantity: z.number(),
    price: z.number(),
  }),
  outputSchema: z.object({
    orderId: z.string(),
    total: z.number(),
  }),
  needsApproval: true,
});

// Create server implementation
const purchaseItem = purchaseItemDef.server(async ({ itemId, quantity, price }) => {
  const order = await createOrder({ itemId, quantity, price });
  return { orderId: order.id, total: price * quantity };
});

用户将在购买之前看到一个审批提示,显示商品、数量和价格。只有在用户批准后,工具才会执行。

最佳实践

  • 对敏感操作使用审批 - 发送电子邮件、付款、删除数据
  • 显示清晰的信息 - 在批准之前显示工具将执行的操作
  • 提供上下文 - 以可读格式显示工具参数
  • 优雅地处理拒绝 - 如果工具被拒绝,不要中断对话
  • 超时处理 - 考虑审批请求的超时时间

下一步