CopilotKit × CrewAI 前端工具(Frontend Tools)实战:从 QA 检查单到useFrontendTool端到端实现
【免费下载链接】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
本篇技术指南以 CrewAI(Crews)集成 showcase 中的frontend-tools演示为核心,完整还原该功能的 QA 检查流程,并逐层深入仓库源码:前端如何通过useFrontendTool注册浏览器端工具、CrewAI Flow 后端如何下发工具声明与执行结果、Playwright E2E 又如何把人工检查单自动化。读完你将掌握在 CopilotKit v2 应用中让 Agent 直接操控页面 UI(改变背景、查询本地数据等)的完整实现与验证方法。
一、这份 QA 检查单在验证什么
qa/frontend-tools.md是一份针对Frontend Tools(前端工具)功能的人工验收清单,它描述的核心场景非常典型:用户用自然语言要求 Agent 修改页面 UI,Agent 决定调用浏览器端注册的某个工具,工具 handler 在前端本地执行并立即改变页面状态。
完整检查步骤(原文档全文):
- 导航到
/demos/frontend-tools; - 验证背景容器(
data-testid="background-container")可见; - 发送消息"Change the background to a blue-to-purple gradient";
- 验证背景样式已更新;
- 验证 Agent 确认了这次修改。
这五步覆盖了一次"前端工具"交互的完整链路:页面就绪 → 工具宿主可见 → 自然语言触发 → 副作用生效 → Agent 确认。它不检查聊天里说了什么花哨的话,而是盯住"页面真实状态变了没有"这一可观察结果——这也是后面 Playwright 测试的设计哲学。
注意:实际实现中背景容器的data-testid为frontend-tools-background(见 background.tsx),检查单里的background-container是同一语义的别名;你在按单验收时以页面 DOM 中实际的data-testid="frontend-tools-background"为准。
二、Demo 源码:一次useFrontendTool注册的全过程
该 demo 的页面入口是 page.tsx,整段核心代码只有几十行,却展示了前端工具的四个关键要素:宿主组件、工具注册、参数 schema、handler 副作用。
2.1 工具宿主:一个可被 Agent 操纵的 Background 组件
// src/app/demos/frontend-tools/background.tsx "use client"; export const DEFAULT_BACKGROUND = "#4f46e5"; // 默认纯靛蓝(indigo) export function Background({ background, children }) { return ( <div >// src/app/demos/frontend-tools/page.tsx(节选) 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); // 唯一的副作用:更新 React state return { status: "success" }; // 结果回传给 Agent }, });拆解这四个字段,就能理解 CopilotKit v2 前端工具的模型:
| 字段 | 作用 | 在本 demo 中的取值 |
|---|---|---|
name | 工具的唯一标识,Agent(以及后端运行时)靠它路由调用 | change_background |
description | 向 LLM 描述工具能力边界,直接影响模型是否决定调用 | 明确允许纯色 / 线性渐变 / 径向渐变等合法 CSS background 值 |
parameters | 基于 Zod 的参数 schema,随工具声明一起下发到后端,作为函数调用的参数约束 | background: string,并提示"优先用渐变" |
handler | 浏览器端执行体,返回结果会被回传给 Agent 继续对话 | 调用setBackground更新页面,返回{ status: "success" } |
页面其余部分把"宿主 + 聊天"组合起来:<Background>包裹<CopilotSidebar agentId="frontend_tools" defaultOpen />,最外层由<CopilotKit runtimeUrl="/api/copilotkit" agent="frontend_tools">提供运行时。也就是说,工具执行完全发生在浏览器,但工具声明与调用决策由 Agent 驱动——这正是"前端工具"与普通后端工具的本质区别。
2.3 提示词药丸:降低 LLM 输入不确定性的配套设计
demo 还通过 suggestions.ts 提供了三个建议消息(suggestion pill):
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", });这些药丸把用户输入收敛为少数几个确定句式,既提升演示体验,也大幅降低了 LLM 生成随机提示词对 E2E 测试稳定性的影响(下文会看到测试正是基于它们做确定性断言)。
三、后端视角:CrewAI Flow 如何配合前端工具
前端工具并非纯前端把戏,工具调用指令来自 Agent。后端对应实现是 frontend_tool_flow.py,并在 agent_server.py 中通过add_crewai_flow_fastapi_endpoint(app, frontend_tool_flow, "/frontend-tools")注册为 FastAPI 端点,前端runtimeUrl="/api/copilotkit"与之打通。
这段 Flow 代码值得细读,其中有三个与前端工具强相关的设计点:
SYSTEM_PROMPT = ( "You are a concise showcase assistant. When a supplied frontend tool can " "fulfill the user's request, you MUST call it; never claim that you lack " "access and never substitute a prose answer. After the browser returns a " "tool result, summarize it briefly." )系统提示强制工具优先:只要前端工具能满足用户请求,"必须调用,禁止用文字敷衍"。这直接保证了检查单第 3 步"Change the background…"能触发真正的工具调用,而不是模型回答一段"抱歉我做不到"。
工具声明来自运行时:
tools=self.state.copilotkit.actions or None——Agent 可用的工具清单由 CopilotKit 运行时注入(即前端useFrontendTool注册的change_background声明会以 AG-UI actions 的形式出现在这里),Flow 本身不重复定义工具。tool_choice 的策略切换:当存在工具声明且最后一条消息来自用户时,
tool_choice="required"(强制本次必须走工具),否则回落"auto"。这意味着用户一发消息,模型就会被引导着调用前端工具,第二次对话轮(携带工具结果)时才允许自由总结——对应检查单第 5 步"Agent 确认修改"。前端工具结果不在后端伪造:Flow 里明确注释"不要在这里制造 tool result"。流式的前端工具调用会直接结束本次 Flow 运行,由 CopilotKit 在浏览器执行 handler,并把权威结果在下一个请求中带回续跑。
self.state.messages.append(response.choices[0].message)只保留消息本身。这就是"浏览器拥有前端工具执行权"这一架构决策的源码证据。
四、从人工检查单到自动化:Playwright E2E 如何逐条对应
人工 QA 清单容易漏测,仓库用 frontend-tools.spec.ts 把五步检查单完整自动化了。对照关系如下:
| QA 检查单步骤 | 对应自动化断言 |
|---|---|
导航到/demos/frontend-tools | test.beforeEach中page.goto("/demos/frontend-tools") |
| 背景容器可见 | page.locator('[data-testid="frontend-tools-background"]').toBeVisible(),且初始 inline style 含默认值#4f46e5 |
| 提问改变背景 | 点击 Sunset / Forest / Cosmic 药丸(等价于发送自然语言提示词) |
| 背景样式更新 | expect.poll轮询 inline style:不再包含#4f46e5,或匹配/linear-gradient\|radial-gradient/ |
| Agent 确认修改 | 断言不依赖 LLM 文本,而是以副作用为准(见下) |
4.1 断言策略:测副作用,不测模型措辞
测试注释明确写出了设计哲学:"We assert on the observable side effect (inline style changes) rather than on any LLM-generated text."例如 Sunset 主题的断言:
await expect .poll(async () => { const s = (await bg.getAttribute("style")) ?? ""; return /linear-gradient|radial-gradient/.test(s); }, { timeout: 45000 }) .toBe(true);为什么用 45 秒轮询而不是立即断言?因为链路是:药丸点击 → 提示词发给 Agent → CrewAI Flow 调 LLM → 模型决定调用change_background→ 声明回到浏览器 → handler 执行setBackground。整条链路耗时不定,轮询(poll)正是对这种"最终一致"副作用的正确断言方式。
4.2 前端工具与 aimock 夹具的关系
测试还提到 aimock 特性对齐夹具(feature-parity fixture)覆盖了 "sunset-themed gradient" 提示词,真实 LLM 则处理自由输入。这意味着该集成支持确定性夹具与真实模型两套运行模式:夹具模式下工具调用由预录数据驱动,便于 CI 稳定复现;真实模型模式(如 Railway 部署)则走完整 LLM 决策。这也是为什么人工 QA 和自动化测试都可以放心依赖"药丸句式"。
五、异步变体:query_notes——前端工具的另一半能力
frontend-tools是"同步副作用"的经典案例;而 frontend-tools-async 演示了前端工具的异步 + 自定义渲染能力,其 QA 检查单为:
- 导航到
/demos/frontend-tools-async; - 提问 "Find my notes about project planning";
- 验证
NotesCard(data-testid="notes-card")随查询关键词渲染; - 验证匹配的笔记(n1、n5、n7)出现在
data-testid="notes-list"中; - 再问 "Search my notes for auth",验证结果更新。
对应实现 frontend-tools-async/page.tsx 展示了两个新特性:
5.1 异步 handler:纯客户端数据查询
handler: async ({ keyword }) => { await sleep(500); // 模拟本地 DB 往返延迟 const q = keyword.toLowerCase(); const matches = NOTES_DB.filter((n) => n.title.toLowerCase().includes(q) || n.excerpt.toLowerCase().includes(q) || (n.tags ?? []).some((t) => t.toLowerCase().includes(q)) ).slice(0, 5); // 最多返回 5 条 return { keyword, count: matches.length, notes: matches }; },NOTES_DB来自 fake-notes-db.ts,是内存中的 7 条假笔记;工具完全在浏览器内完成查询,后端零参与。sleep(500)让加载态可见,也顺带验证了异步路径的时序。
5.2 render:为工具结果定制 UI 卡片
render: ({ args, result, status }) => { const loading = status !== "complete"; const parsed = parseJsonResult<{ keyword?: string; count?: number; notes?: Note[] }>(result); return ( <NotesCard loading={loading} keyword={args?.keyword ?? parsed.keyword ?? ""} notes={parsed.notes} /> ); },render回调让前端工具不只是"改个样式",还能把工具返回的结构化结果画成专属组件。NotesCard 暴露data-testid="notes-card"、notes-keyword、notes-list、note-n1…note-n7等一系列测试锚点,加载中显示 "Querying local notes DB...",完成显示匹配数并逐条渲染标题、摘录和标签胶囊。
对应的 frontend-tools-async.spec.ts 做了更彻底的验证:断言关键词标题为Matching "project planning"、列表中出现note-n1/note-n5、auth 场景出现note-n2、reading 场景出现note-n4(含 "Book recommendations" 与《Thinking Fast and Slow》等书目文本),并包含一个反回归断言——通用模板文案不得误出现。最后一个测试甚至在同一线程里依次点击三个药丸,验证每次点击都渲染出自己的 NotesCard(这是对 aimock 多药丸 bug 的回归测试,原 bug 是工具结果门控导致首轮夹具被跳过、卡片永不渲染)。
六、功能在集成中的定位与运行方式
在 manifest.yaml 中,frontend-tools与frontend-tools-async作为两个独立 demo 注册在interactivity(交互性)类别下:
- id: frontend-tools name: Frontend Tools (In-App Actions) description: Agent invokes client-side handlers registered with useFrontendTool route: /demos/frontend-tools - id: frontend-tools-async name: Frontend Tools (Async) description: useFrontendTool with an async handler route: /demos/frontend-tools-async同时manifest.yaml声明该集成整体具备frontend-tools、frontend-tools-async、hitl-in-app(通过useFrontendTool实现的应用内人工审批弹窗)等能力,说明前端工具是这套 CrewAI 集成中"应用内交互"体系的基础设施。
想本地体验该演示,可按 manifest 提供的标准方式初始化并运行:
# 克隆官方 starter(基于本集成模板) npx copilotkit@latest init --framework crewai-crews # 在仓库内直接运行本 showcase 则需要: # 1) 安装 Python 依赖(含 copilotkit、crewai、litellm 与 ag_ui_crewai) # 2) 配置 LLM 环境变量(Flow 中使用 openai/gpt-5.4 等模型) # 3) 启动 FastAPI 后端与 Next.js 前端,然后访问 /demos/frontend-tools进入页面后,可依次按第一节的检查单人工验收;本地起好 Playwright 后,也可直接执行 frontend-tools.spec.ts 与 frontend-tools-async.spec.ts 完成自动化验收。
七、小结:把"前端工具"复用到你自己的页面
回到frontend-tools这份 QA 检查单,它的五步背后其实是一套可复用的最小模式,任何希望"Agent 能操纵我的页面"的应用都可以照搬:
- 准备宿主:用 React state 持有页面中 Agent 要操控的值,把它渲染进带
data-testid的容器; - 注册工具:
useFrontendTool+ Zod schema,description写清能力边界,handler里只做前端副作用并返回结果; - 提示引导:用
useConfigureSuggestions提供确定性提示词药丸; - 后端放权:CrewAI Flow 通过
copilotkit_stream拿到运行时注入的 actions,强制工具调用,但不伪造工具结果,把执行权留给浏览器; - 以副作用验收:人工 QA 看页面状态,自动化 QA 用
expect.poll轮询 DOM 副作用——两边盯住同一件事。
从change_background改背景,到query_notes的异步查询与自定义卡片渲染,这套机制把"LLM 决策"与"页面执行"优雅地分层:模型负责决定,浏览器负责动手。当你需要 Agent 操作本地数据、弹出审批、切换主题或渲染结构化结果时,frontend-tools 与 frontend-tools-async 两份源码就是可直接参考的完整范本。
【免费下载链接】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),仅供参考