CopilotKit Frontend Tools 实战指南:用 useFrontendTool 让 Agent 直接调用 React 应用内函数
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
导读
Frontend Tools(又称 "in-app actions",应用内动作)是 CopilotKit 提供的一种交互机制:它允许 Agent 在对话过程中直接调用运行在 React 前端里的函数,而不是只能调用部署在后端的工具。本文以本仓库 LangGraph(FastAPI)集成示例中的 frontend-tools Demo(README)为核心骨架,完整讲解useFrontendTool的注册方式、参数约束、客户端 handler 的执行链路,以及 CopilotKit 如何"自动把前端工具广告给 Agent",并给出可直接复制的完整代码与运行、测试验证方式。
读完本文你将掌握:如何用useFrontendTool把任意 React 函数注册为 Agent 可调用的工具;如何用 Zod 约束工具参数;Agent 与前端 handler 之间"推理 → 调用 → 回传结果"的完整闭环是如何工作的;以及如何用 Playwright 测试验证前端工具被真实调用。
这个 Demo 演示了什么
frontend-tools演示的核心思想:让 Agent 基于自然语言对话自行决定何时调用前端函数。
以本 Demo 为例,页面注册了一个名为change_background的前端工具,它接收一个 CSS 背景值(支持渐变色),并把页面背景实时切换为该值。用户不必点击任何按钮,只需用自然语言向聊天侧边栏发号施令,Agent 就会在合适时机调用这个前端函数。
你可以直接尝试以下提问(也是 Demo 内置推荐话术):
- "Change the background to a blue-to-purple gradient"(把背景改为蓝紫渐变)
- "Make the background a sunset theme"(做成日落主题)
- "Set the background to black"(把背景设为黑色)
从 manifest.yaml 可以看到,frontend-tools被归类为interactivity(交互性)类型 Demo,其高亮代码文件为 page.tsx 与后端图 frontend_tools.py,并归属于features: frontend-tools特性。
完整代码拆解:一个可运行的 Frontend Tool
下面是在本仓库中真实运行的前端工具完整注册代码(page.tsx):
"use client"; import React, { useState } from "react"; import { CopilotKit, CopilotSidebar, useFrontendTool, } from "@copilotkit/react-core/v2"; import { z } from "zod"; import { Background, DEFAULT_BACKGROUND } from "./background"; import { useFrontendToolsSuggestions } from "./suggestions"; function Chat() { const [background, setBackground] = useState<string>(DEFAULT_BACKGROUND); useFrontendTool({ name: "change_background", description: "Change the page background. Accepts any valid CSS background value — colors, linear or radial gradients, etc.", parameters: z.object({ background: z .string() .describe("The CSS background value. Prefer gradients."), }), handler: async ({ background }) => { setBackground(background); return { status: "success" }; }, }); useFrontendToolsSuggestions(); return ( <Background background={background}> <CopilotSidebar agentId="frontend_tools" defaultOpen /> </Background> ); } export default function FrontendToolsDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent="frontend_tools"> <Chat /> </CopilotKit> ); }代码结构非常清晰,几个关键点:
- 顶层
CopilotKitProvider:通过runtimeUrl="/api/copilotkit"连接到 Next.js 的 CopilotKit Runtime 路由(见 api/copilotkit/route.ts),并通过agent="frontend_tools"指定当前对话所绑定的后端 Agent。 CopilotSidebar:一个预构建的侧边栏聊天 UI,agentId="frontend_tools"与 Provider 的 agent 名称保持一致,defaultOpen让侧边栏默认展开。useFrontendTool:在组件内注册前端工具。它把工具"挂"到 CopilotKit 运行时上,运行时负责把工具描述同步给后端 Agent。
背景容器本身是一个受控组件(background.tsx):默认背景为纯靛蓝#4f46e5,通过内联style={{ background }}渲染,并带有data-testid="frontend-tools-background"测试锚点和 700ms 的背景过渡动画——这为后面的自动化测试提供了可断言的目标。
useFrontendTool 的 API 契约:类型定义与生命周期
useFrontendTool的底层类型定义在核心包 packages/core/src/types.ts 的FrontendTool中,其字段契约如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 工具名,Agent 通过该名字发起调用,必须唯一 |
description | string? | 工具功能描述,是 Agent 判断"何时调用"的关键依据 |
parameters | StandardSchemaV1 | 参数 schema(本 Demo 用 Zod 的z.object),约束并校验 Agent 传入的参数 |
handler | (args, context) => Promise<unknown> | 在前端执行的处理器,参数已按 schema 校验,返回结果回传给 Agent |
followUp | boolean? | 是否把结果作为后续对话继续推进 |
agentId | string? | 将工具限定到指定 Agent;不填则为全局可用 |
available | boolean? | 是否对 Agent 可见,默认true;设为false可在不注销的情况下临时隐藏工具 |
webmcp | boolean \| WebMCPToolConfig? | 是否同时通过 WebMCP API(document.modelContext)暴露给浏览器端 Agent |
handler 的context(FrontendToolHandlerContext)还携带toolCall、触发该工具的agent实例以及AbortSignal(在调用stopAgent()时触发,可用于协作式取消)。
ReactFrontendTool(packages/react-core/src/v2/types/frontend-tool.ts)在核心类型之上追加了一个可选的render字段,用于为工具调用渲染自定义 UI。
注册与卸载的生命周期
从 use-frontend-tool.tsx 的实现可以看到注册的生命周期行为:
- 挂载时注册:组件挂载后,通过
copilotkit.addTool(tool)注册工具;如果同名工具已存在,会打印警告并先removeTool再覆盖,保证"最新注册生效"。 - 重注册依赖:effect 依赖
tool.name、tool.available、copilotkit以及序列化后的deps与webmcp配置——这意味着当你需要动态改变工具可用性或参数时,可以传入deps触发重新注册。 - 卸载时清理:组件卸载时调用
removeTool注销工具,但刻意不删除已渲染的工具调用记录,以保证聊天历史中的工具调用 UI 仍然可见。
另外,如果提供了render,该实现会通过copilotkit.addHookRenderToolCall同步注册渲染器,即使parameters未定义(例如 HITL 确认对话框这类无参数工具)也会注册渲染逻辑。
客户端 handler 如何执行:参数校验与结果回传
handler 在前端执行时遵循"先校验、后执行、再回传"的流程:
- Agent 在对话中决定调用
change_background,CopilotKit 运行时把 Agent 传来的参数交给 Zod schema(z.object({ background: z.string() }))进行校验。 - 校验通过后调用 handler:
handler: async ({ background }) => { setBackground(background); return { status: "success" }; }。 - handler 内同步 React 状态(
setBackground),实现即时 UI 变更——在本 Demo 中即把背景色切换到渐变色。 - 返回值
{ status: "success" }作为工具调用结果回传给 Agent,Agent 可以基于该结果继续生成回复。
由于 handler 运行在客户端,因此它可以访问浏览器 API、React 状态、浏览器存储等任何前端资源——这正是"应用内动作"的威力所在。本仓库还提供了更进一步的示例:在 frontend-tools-async Demo 中,handler 是async的,Agent 会等待一个模拟的客户端异步操作(notes 数据库查询)完成后再使用返回的结果。
后端 Agent 如何"自动看到"前端工具
原文档指出:"CopilotKit automatically advertises the tool to the agent"。这背后的机制在 SDK 的 LangGraph 中间件中实现。
在本示例中,后端 Agent 是一个零自定义工具的 LangGraph 图(frontend_tools.py):
from langchain.agents import create_agent from langchain_openai import ChatOpenAI from copilotkit import CopilotKitMiddleware graph = create_agent( model=ChatOpenAI(model="gpt-4o-mini"), tools=[], middleware=[CopilotKitMiddleware()], system_prompt="You are a helpful, concise assistant.", )注意tools=[]——Agent 本身不定义任何工具,但通过挂载CopilotKitMiddleware,前端注册的change_background会在每次模型调用前被注入到 LLM 的工具列表中。
注入逻辑位于 Python SDK 的 copilotkit_lg_middleware.py:
- 注入(before_model):中间件从 CopilotKit 上下文中读取前端注册的
actions,将其与请求中已有的工具合并(merged_tools = [*request.tools, *extra_tools, *frontend_tools]),再交给模型(见该文件约 L595-L632)。 - 拦截(after_model):模型返回工具调用后,中间件把"名字属于前端工具"的调用从 AIMessage 中摘出(
frontend_tool_calls),只把后端工具调用留给 ToolNode 执行,同时把被拦截的调用写入copilotkit.intercepted_tool_calls状态(见 copilotkit_lg_middleware.py)。 - 恢复(after_agent):在本轮 agent 执行结束前,把前端工具调用还原回原始 AIMessage,使聊天历史保持完整(见 copilotkit_lg_middleware.py)。
也就是说:前端工具不需要后端注册任何对应代码,Agent 的 LLM 每次请求时都会收到"广告",并且前端工具调用会被中间件自动拦截转发到客户端执行,执行结果再以 ToolMessage 形式回到 Agent 的消息流中。
前端与后端的对接还依赖 Runtime 路由。在 api/copilotkit/route.ts 中,frontend_tools这个 agent 名被映射到 LangGraph 的frontend_tools图:
agents["frontend_tools"] = createAgent("frontend_tools");createAgent内部通过LangGraphAgent连接AGENT_URL(默认http://localhost:8123)指向 FastAPI 侧的 LangGraph 服务。Provider 中的agent="frontend_tools"与后端图名、agentId三者保持一致,是 Demo 能跑通的对接前提。
让用户更容易触发:静态建议提示
为了让用户更容易上手,Demo 还通过useConfigureSuggestions注册了一组静态建议胶囊(suggestions.ts):
import { useConfigureSuggestions } from "@copilotkit/react-core/v2"; export function useFrontendToolsSuggestions() { useConfigureSuggestions({ suggestions: [ { title: "Sunset theme", message: "Make the background a sunset gradient." }, { title: "Forest theme", message: "Switch to a deep green forest gradient." }, { title: "Cosmic theme", message: "Make it a navy → magenta cosmic gradient." }, ], available: "always", }); }suggestions数组每项包含title(胶囊显示文案)与message(点击后发送给 Agent 的实际消息);available: "always"表示建议在对话全程可见。从核心包类型 types.ts 可以看出StaticSuggestionsConfig的可用性枚举为"before-first-message"、"after-first-message"、"always"、"disabled"(默认为"before-first-message")。
建议胶囊与前端工具配合后,用户点击 "Sunset theme" 就会把"Make the background a sunset gradient."发送给 Agent,Agent 随即调用change_background完成 UI 变更——这是一个典型的"建议 → 推理 → 应用内动作"闭环。
如何验证工具真的被调用:Playwright 测试
本仓库为 frontend-tools 编写了完整的端到端测试(tests/e2e/frontend-tools.spec.ts)。测试的设计思路值得借鉴:不依赖 LLM 生成的文字,而是断言前端工具执行后的可观察副作用(内联样式变化)。
测试要点:
- 页面加载:断言聊天输入框(
Type a message)与背景容器(data-testid="frontend-tools-background")可见。 - 默认背景:断言初始内联样式包含
#4f46e5(solid indigo 默认值)。 - 建议胶囊渲染:断言 Sunset / Forest / Cosmic 三个建议按钮出现。
- Forest 主题生效:点击 "Forest theme" 胶囊后,轮询背景的
style属性,直到它不再包含默认色#4f46e5(超时 45s)。 - Sunset 主题触发渐变:点击 "Sunset theme" 后,轮询
style属性直到匹配/linear-gradient|radial-gradient/,证明 Agent 确实通过change_background写入了渐变 CSS 值。
test("Sunset theme pill triggers a gradient change", async ({ page }) => { await page.getByRole("button", { name: /Sunset theme/i }).click(); const bg = page.locator('[data-testid="frontend-tools-background"]'); await expect .poll( async () => { const s = (await bg.getAttribute("style")) ?? ""; return /linear-gradient|radial-gradient/.test(s); }, { timeout: 45000 }, ) .toBe(true); });该测试直接印证了完整调用链:建议胶囊发送消息 → 后端 Agent 推理 → 中间件把前端工具注入 LLM → LLM 调用change_background→ 中间件拦截并转发到客户端 → handler 执行setBackground→ 背景样式变更。
运行与进一步探索
要在本地体验该 Demo,可参考集成示例的通用运行方式:
- 启动 FastAPI 侧的 LangGraph Agent 服务(
frontend_tools图位于 src/agents/src/frontend_tools.py),使其监听http://localhost:8123。 - 启动 Next.js 前端,打开
/demos/frontend-tools路由,[api/copilotkit/route.ts](https://link.gitcode.com/i/ee030fba46d09de665a39d2935d423af)会通过AGENT_URL环境变量连接后端。 - 在侧边栏聊天中输入"Change the background to a blue-to-purple gradient"等指令,即可看到 Agent 实时修改页面背景。
如果想深入扩展,仓库中还提供了两个相邻 Demo 值得对照阅读:
- frontend-tools-async:
useFrontendTool的异步 handler 用法,Agent 会等待客户端异步操作结果。 - hitl-in-app:基于
useFrontendTool的异步 handler 实现应用级人工审批弹窗,是"前端工具 + 异步完成回调"的更高级形态。
小结
通过本文,你已经完整掌握了 CopilotKit Frontend Tools(In-App Actions)的核心机制:用useFrontendTool在前端注册带 Zod 参数约束的工具,由 CopilotKit 运行时自动向后端 Agent 广告工具,借助CopilotKitMiddleware在前端/后端之间完成"注入 → 推理 → 拦截 → 执行 → 回传"的闭环,并通过建议胶囊与 Playwright 测试让交互更友好、更可验证。这套模式非常适合所有"需要 Agent 直接驱动前端 UI 状态"的场景,例如主题切换、面板显隐、表单联动、应用内确认等。
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考