Cloudflare AI Agent 客户端工具与自动续聊(Client-Side Tools and Auto-Continuation)实战指南

Cloudflare AI Agent 客户端工具与自动续聊(Client-Side Tools and Auto-Continuation)实战指南 Cloudflare AI Agent 客户端工具与自动续聊Client-Side Tools and Auto-Continuation实战指南【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents导读在AIChatAgentcloudflare/ai-chat中工具分为服务端工具与客户端工具两类服务端工具由 Worker 上的execute函数自动执行而客户端工具没有execute工具调用会通过onToolCall发送到浏览器等客户端执行。本文围绕docs/agents/client-tools-continuation.md讲解如何借助autoContinueAfterToolResult让客户端工具获得与服务端工具一致的无缝体验——LLM 调用工具 → 客户端执行 → 服务端自动续聊最终将续聊内容合并进同一条 assistant 消息。读完本文你将掌握服务端/客户端两端的完整配置、关闭自动续聊的显式续聊模式以及将客户端工具与needsApproval审批流程组合使用的完整方案并结合仓库源码理解其底层协议与自动续聊屏障barrier的实现原理。工具的两大分类服务端工具与客户端工具在AIChatAgent中工具根据是否拥有服务端execute函数被分为两类服务端工具Server tools在服务端定义了execute函数。AI SDK 会自动执行该函数并在同一轮turn内让 LLM 继续回复。例如查询天气、调用内部 API 等不需要用户参与的工具。客户端工具Client tools在服务端没有execute函数。模型发出工具调用后服务端暂停该轮回复通过tool-input-available事件将调用发送到客户端由客户端的onToolCall回调执行并返回结果。默认情况下无自动续聊这需要一次新的请求才能让对话继续。从源码实现来看客户端工具的形态定义在 packages/agents/src/chat/client-tools.tsexport type ClientToolSchema { /** 工具的唯一名称 */ name: string; /** 人类可读的工具描述 */ description?: Tool[description]; /** 使用 JSON Schema 定义输入参数 */ parameters?: JSONSchema7; };这里值得注意的一个实现细节是客户端工具的参数使用parametersJSON Schema7而非 AI SDK 的inputSchemaZod因为Zod schema 无法在网络上序列化而客户端工具 schema 需要从浏览器通过 WebSocket 传输到 Worker。服务端最终通过createToolsFromClientSchemas将这些 schema 转换为 AI SDK 的tool()定义默认不带execute模型调用它们时工具调用会被送回客户端。启用autoContinueAfterToolResult默认开启后客户端工具可以表现得像服务端工具LLM 调用工具 → 客户端执行 → 服务端收到工具结果后自动续聊整个过程仍然在同一轮对话中完成用户看到的是无缝的单一回复。服务端配置定义无execute的客户端工具在服务端只需定义一个不带execute函数的工具。AI SDK 检测到没有执行器时会暂停当前流并向客户端发送tool-input-available等待客户端回传结果import { AIChatAgent } from cloudflare/ai-chat; import { createWorkersAI } from workers-ai-provider; import { streamText, tool, convertToModelMessages, stepCountIs } from ai; import { z } from zod; export class MyAgent extends AIChatAgent { async onChatMessage() { const workersai createWorkersAI({ binding: this.env.AI }); const result streamText({ model: workersai(cf/moonshotai/kimi-k2.7-code), messages: await convertToModelMessages(this.messages), tools: { // 客户端工具没有 execute 函数 getUserLocation: tool({ description: Get the users location from their browser, inputSchema: z.object({}) }), // 服务端工具有 execute自动执行 getWeather: tool({ description: Get weather for a city, inputSchema: z.object({ city: z.string() }), execute: async ({ city }) fetchWeather(city) }) }, // 允许多步执行这样 LLM 拿到工具结果后还能继续回复 stopWhen: stepCountIs(5) }); return result.toUIMessageStreamResponse(); } }配置要点getUserLocation只有description与inputSchema没有execute因此被识别为客户端工具。本例它的输入是空对象z.object({})位置由浏览器直接获取若需要参数客户端 schema 会通过 JSON Schema 传输。getWeather拥有execute属于服务端工具由 AI SDK 自动执行、自动续步不需要客户端参与。stopWhen: stepCountIs(5)很关键自动续聊本质上是多步执行必须为streamText设置步数上限否则模型在工具结果之后可能无法继续生成最终回复。客户端配置onToolCall执行 默认自动续聊客户端使用cloudflare/ai-chat/react的useAgentChathook。通过onToolCall处理客户端工具的执行autoContinueAfterToolResult默认即为true所以通常无需显式设置——服务端收到工具结果后会自动再次调用onChatMessage()让 LLM 在同一条 assistant 消息中继续回复import { useAgent } from agents/react; import { useAgentChat } from cloudflare/ai-chat/react; function Chat() { const agent useAgent({ agent: MyAgent }); const { messages, sendMessage } useAgentChat({ agent, // 自动续聊默认开启 —— 无需显式设置 // autoContinueAfterToolResult: true, onToolCall: async ({ toolCall, addToolOutput }) { if (toolCall.toolName getUserLocation) { const pos await new Promise((resolve, reject) { navigator.geolocation.getCurrentPosition(resolve, reject); }); addToolOutput({ toolCallId: toolCall.toolCallId, output: { lat: pos.coords.latitude, lng: pos.coords.longitude } }); } } }); // Render messages... }onToolCall回调中拿到的toolCall携带toolName与toolCallId执行完成后调用addToolOutput将结果回传给服务端。从仓库的 React 实现看packages/agents/src/chat/react.tsxautoContinueAfterToolResult?: boolean— 收到客户端工具结果或审批后是否自动续聊默认true续聊内容合并进同一条 assistant 消息为false时客户端必须调用sendMessage()才能继续此时会创建新的 assistant 消息。对应的测试也验证了这一默认行为packages/ai-chat/src/react-tests/use-agent-chat.test.tsx测试构造一条state: input-available的工具调用消息在未显式传入autoContinueAfterToolResult的情况下调用addToolOutput随后断言发出的cf_agent_tool_result消息携带autoContinue: true。底层协议CF_AGENT_TOOL_RESULT与自动续聊标志客户端回传结果本质上是向服务端发送一条CF_AGENT_TOOL_RESULT消息。协议定义位于 packages/agents/src/chat/wire-types.ts{ /** 客户端向服务端发送工具结果用于客户端工具 */ type: MessageType.CF_AGENT_TOOL_RESULT; /** 本次工具调用对应的 toolCallId */ toolCallId: string; /** 工具名称 */ toolName: string; /** 工具执行输出 */ output: unknown; /** 覆盖工具 part 的状态例如自定义拒绝时用 output-error */ state?: output-available | output-error; /** 当 state 为 output-error 时的错误信息 */ errorText?: string; /** 服务端应用结果后是否自动续聊 */ autoContinue?: boolean; /** 用于续聊的客户端工具 schema客户端是权威来源 */ clientTools?: Array{ name: string; description?: string; parameters?: JSONSchema7; }; }从协议字段可以读出几个关键设计autoContinue是是否续聊的开关客户端在发送工具结果时根据autoContinueAfterToolResult设置该值见 react.tsx。clientTools字段让客户端在续聊时重新上报工具 schema因为客户端是工具定义的权威来源——服务端续聊的下一轮onChatMessage()需要知道当前有哪些客户端工具可用。state: output-error与errorText允许客户端表达执行失败或用户拒绝而不是只能用通用错误。自动续聊的完整工作流程文档给出了一个完整的端到端流程示例用户问题为 Whats the weather near me?1. 客户端发送消息 → 服务端调用 LLM 2. LLM 决定调用 getUserLocation无服务端 execute 3. 流式输出向客户端发送 tool-input-available 4. onToolCall 触发 → 客户端获取地理位置 → 发送 CF_AGENT_TOOL_RESULT 5. 服务端收到结果其中 autoContinue: true 6. 服务端等待原始流完整结束 7. 服务端再次调用 onChatMessage()续聊 8. LLM 看到位置结果调用 getWeather服务端 execute 9. LLM 回复Its sunny and 72°F near you! 10. 续聊产生的 parts 合并进同一条 assistant 消息尽管中间发生了客户端工具调用用户最终看到的仍然是一条无缝的完整回复。源码层面的自动续聊屏障这个等待原始流结束 → 触发续聊的协调逻辑在仓库中由AutoContinuationControllerpackages/agents/src/chat/auto-continuation-controller.ts统一实现。它是cloudflare/ai-chat与cloudflare/think共享的事件驱动屏障核心机制包括合并去抖coalesce/debounceCOALESCE_MS 50ms的定时器。模型可能并行发出多个客户端工具调用多个工具结果先后到达时去抖会把这些相邻结果合并为一次服务端续聊检查对应仓库 issue #1650避免对每个结果各续聊一次。双触发防护_barrierActive确保同一时刻只有一个应用排空apply-drain在运行防止重复触发续聊。稳定性门槛fireWhenStable真正的续聊只有在以下条件全部满足时才会触发模型并行工具批次已全部收到结果无未答复的兄弟工具调用对应 #1649当前没有正在流式输出的 assistant 轮次isStreamActive为false——否则无法确认并行批次是否完整提前触发会把未到达的结果错误地标记为失败没有正在进行的工具结果/审批应用操作hasPendingInteraction。无孤儿超时如果某个兄弟工具结果永远不回来屏障不会无限期占用 isolate而是保持pending状态等待后续用户轮次或聊天恢复机制修复转录。客户端续聊的启动逻辑在客户端startToolContinuationreact.tsx会在autoContinueAfterToolResult为true且当前没有其他续聊进行时通过resumeStream()恢复服务端流将续聊的增量 parts 合并进现有 assistant 消息若续聊期间用户stopstopWithToolContinuationAbort会同时取消服务端轮次并中止续聊流react.tsx。关闭自动续聊显式控制的续聊模式将autoContinueAfterToolResult设置为false时客户端必须在提供工具结果后显式发送一条后续消息对话才会继续const { messages, sendMessage, addToolOutput } useAgentChat({ agent, onToolCall: async ({ toolCall, addToolOutput: provide }) { if (toolCall.toolName getUserLocation) { const pos await getPosition(); provide({ toolCallId: toolCall.toolCallId, output: { lat: pos.coords.latitude, lng: pos.coords.longitude } }); } } autoContinueAfterToolResult: false, // 关闭自动续聊 }); // 提供工具结果后发送后续消息以继续对话 // 这会创建一条新的 assistant 消息而不是续写现有那条注意此时sendMessage()创建的是新的 assistant 消息而非合并进原消息。适合使用显式续聊模式的场景希望精确控制对话何时继续例如工具结果需要经过用户审阅、编辑或确认后再进入下一步工具结果是中间数据用户可能想先看到结果再决定是否让 LLM 继续推理。文档还提示与autoContinueAfterToolResult相关的autoSendAfterAllConfirmationsResolved选项已被弃用建议改用 AI SDK 的sendAutomaticallyWhen。同时autoContinueAfterToolResult: false模式下还有一个细节——如果并行批次中某个工具结果没有携带autoContinue但它的兄弟结果选择继续服务端仍会通过rearmForBatchauto-continuation-controller.ts重新运行屏障检查确保批次完整后再触发续聊。组合使用needsApproval审批 客户端执行客户端工具可以与needsApproval审批流程组合。例如一个需要用户授权 浏览器执行才能完成的分享位置工具// 服务端需要审批但没有 execute 的工具 const shareLocation tool({ description: Share the users location with a third party, inputSchema: z.object({ service: z.string() }), needsApproval: true // 没有 execute —— 审批通过后由客户端执行 });// 客户端处理审批然后执行 const { addToolApprovalResponse } useAgentChat({ agent, autoContinueAfterToolResult: true, onToolCall: async ({ toolCall, addToolOutput }) { if (toolCall.toolName shareLocation) { const pos await getPosition(); addToolOutput({ toolCallId: toolCall.toolCallId, output: { lat: pos.coords.latitude, lng: pos.coords.longitude } }); } } });此时的完整流程变为LLM 调用工具 → 用户审批 → 客户端执行 → 服务端自动续聊。协议层面审批由CF_AGENT_TOOL_APPROVAL消息承载同样支持autoContinue字段见 wire-types.ts因此审批通过后也能无缝衔接自动续聊。如果用户拒绝了工具可以不用泛化的错误而是通过addToolOutput传入state: output-error提供自定义的拒绝理由// 带理由地拒绝而不是泛化的错误 addToolOutput({ toolCallId: toolCall.toolCallId, state: output-error, errorText: User declined to share location });这个errorText会作为工具调用的错误输出进入模型上下文让 LLM 理解用户拒绝了以及原因从而给出得体的后续回复例如礼貌地不再追问、或询问其他可选方案。实践建议与注意事项stepCountIs步数上限启用自动续聊后一次对话可能包含客户端工具 → 续聊 → 服务端工具 → 再续聊等多步执行务必为streamText设置合理的stopWhen步数上限防止模型陷入无限工具循环。客户端 schema 上报客户端工具定义以客户端为准续聊时客户端会通过CF_AGENT_TOOL_RESULT的clientTools字段重新上报 schemaclient-tools.ts 中的createToolsFromClientSchemas负责将其转换为 AI SDK 工具。若服务端onChatMessage依赖固定工具集需要确保两种来源一致重复的工具名会触发告警且后者覆盖前者。并行工具调用模型可以并行发出多个客户端工具调用自动续聊屏障会合并去抖并等待整个批次完整后才续聊开发者无需自己处理并发结果。执行失败处理客户端执行失败时优先使用state: output-errorerrorText回传结构化错误而不是抛异常中断整个流。关联文档Chat Agents —AIChatAgent与useAgentChat的完整参考Human in the Loop — 包含needsApproval的审批模式详解【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考