news 2026/9/13 1:20:01

CopilotKit 前端工具异步执行:CrewAI Conversational Flows 集成中的 useFrontendTool 实战与 QA 验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CopilotKit 前端工具异步执行:CrewAI Conversational Flows 集成中的 useFrontendTool 实战与 QA 验证

CopilotKit 前端工具异步执行:CrewAI Conversational Flows 集成中的 useFrontendTool 实战与 QA 验证

【免费下载链接】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

本篇文章以 CopilotKit 仓库中 CrewAI Conversational Flows 集成示例的frontend-tools-async演示为核心,讲解如何用useFrontendTool注册一个完全运行在浏览器端的异步工具,并让后端 CrewAI Flow 通过 AG-UI 协议调用它。你将掌握前端工具从声明、参数校验、异步 handler 到自定义渲染的完整链路,以及基于 Playwright 和固定 mock 数据的端到端 QA 验证方法。

演示背景:前端工具(异步)是什么

frontend-tools-async是 CopilotKit 与 CrewAI Conversational Flows 集成中的一个专门演示(详见 manifest.yaml 中的frontend-tools-async条目),与frontend-tools(In-App Actions)的区别在于:前者的 handler 是一个async 函数——它会模拟一次客户端本地数据库查询(500ms 延迟),返回匹配结果后,再由后端 Agent 对结果进行总结,完整走通"异步前端工具"的往返链路。

这个演示解决的核心问题是:当数据只存在于浏览器端(如本地索引、IndexedDB 缓存、用户私有数据)时,如何让 LLM Agent 依然能够"查询"并"使用"这些数据。典型的应用场景是个人笔记搜索、本地收藏夹检索、客户端缓存的业务数据过滤等——数据不出浏览器,但 Agent 照常推理与作答。

演示页面路由为/demos/frontend-tools-async,其功能在集成根目录的 manifest.yaml 中被描述为"useFrontendTool with an async handler"。

前端工具异步执行的完整链路

整个链路可以拆成四个环节:页面接入、工具注册、异步 handler 执行、结果渲染。

1. 页面接入:CopilotKit Provider 与 Chat 组件

演示页面 page.tsx 用CopilotKitProvider 包住聊天界面,指定后端 Agent 名称与 runtime 地址:

export default function FrontendToolsAsyncDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent="frontend-tools-async"> <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <Chat /> </div> </div> </CopilotKit> ); }

聊天界面本身是一个标准CopilotChat组件,同时通过useConfigureSuggestions提供三个可点击的建议 pill,方便 QA 测试与用户快速触发不同关键词:

useConfigureSuggestions({ suggestions: [ { title: "Find project-planning notes", message: "Find my notes about project planning." }, { title: "Search for 'auth'", message: "Search my notes for anything related to auth." }, { title: "What do I have about reading?", message: "Do I have any notes tagged reading?" }, ], available: "always", });

三个建议分别对应 QA 清单中的两条核心检查(project planning 与 auth),外加一个 reading 标签查询,覆盖了关键词匹配的多种形态。

2. 工具注册:useFrontendTool 声明"前端工具"

核心是useFrontendToolHook。它注册一个由后端 Agent 决定何时调用、但由浏览器执行的工具。注册时包含四部分:工具名、自然语言描述、Zod 参数 schema、执行 handler,以及可选的render渲染器:

useFrontendTool({ name: "query_notes", description: "Search the user's local notes database for notes whose title, " + "excerpt, or tags contain the given keyword (case-insensitive). " + "Returns up to 5 matching notes.", parameters: z.object({ keyword: z .string() .describe("Keyword or phrase to search notes for (case-insensitive)."), }), handler: async ({ keyword }: { keyword: string }) => { await sleep(500); const q = keyword.toLowerCase(); const matches = NOTES_DB.filter((n) => { return ( n.title.toLowerCase().includes(q) || n.excerpt.toLowerCase().includes(q) || (n.tags ?? []).some((t) => t.toLowerCase().includes(q)) ); }).slice(0, 5); return { keyword, count: matches.length, notes: matches }; }, render: ({ args, result, status }) => { /* ... */ }, });

关键点:

  • description 即"工具契约":LLM 依赖这段描述决定是否调用该工具、传什么参数,因此它必须写清楚匹配字段(title/excerpt/tags)、大小写不敏感,以及最多返回 5 条。
  • Zod schema 负责参数校验:这里只声明了一个keyword字符串参数,并用.describe()补充语义,便于模型正确生成参数。
  • handler 是 async 的:它await sleep(500)模拟客户端数据库往返,然后对内存中的NOTES_DB做大小写不敏感的三字段(标题、摘要、标签)模糊匹配,slice(0, 5)限制结果数量,最后返回结构化结果{ keyword, count, notes }。这个返回值会作为工具结果回传给后端 Agent,供其总结作答。

3. 异步结果渲染:NotesCard 组件

render回调接收{ args, result, status }三个字段,将工具执行状态映射到自定义 UI。这里的结果通过共享辅助函数 parse-json-result.ts 统一解析:

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} /> ); },

parseJsonResult兼容两种结果形态——Agent 以 JSON 字符串发出时自动JSON.parse,已解析为对象时直接透传,解析失败则回退为空对象。loadingstatus !== "complete"推导,因此在 500ms 模拟延迟期间,卡片会先显示"Querying local notes DB..."的加载态。

NotesCard 是一个品牌化的结果卡片,并暴露了 QA 所需的全部测试锚点:

锚点含义
data-testid="notes-card"外层容器
data-testid="notes-keyword"标题区,展示Matching "<keyword>"
data-testid="notes-list"匹配结果的<ul>列表
data-testid="note-n1"note-n7每条笔记的独立行

卡片同时渲染匹配计数("N matches")、每条笔记的标题/摘要/标签 chips,以及空结果时的"No notes matched"占位。由于每个工具调用会渲染一张独立卡片,连续多次查询会在聊天流中形成多张卡片叠加的效果。

4. 数据源:确定性的内存笔记库

前端工具的数据来自 fake-notes-db.ts 中的NOTES_DB常量,共 7 条笔记(id 为 n1–n7),覆盖规划、认证、购物、阅读、户外、职业等主题。文件头注释明确说明:真实应用里这应是 IndexedDB、拉取的缓存或其他客户端私有数据存储;这里保持内联且确定,是为了让异步 handler 的往返在测试与截图中可复现。

结合该数据可以精确预判 QA 的匹配结果:

  • 查询 "project planning":命中 n1("Q2 project planning kickoff",tags 含 planning/project)与 n5("Project planning retrospective notes")。
  • 查询 "auth":命中 n2("Planning: migrate auth to passkeys",tags 含 auth)。
  • 查询 "reading":命中 n4("Book recommendations",tags 含 reading)。

这正是 QA 清单中每条断言能精确落到具体笔记 id 的底层依据。

后端如何配合:CrewAI Flow 只负责"发出调用"

前端工具并非由前端直接触发,而是由后端 CrewAI Flow 在推理时决定调用。仓库中的 frontend_tool_flow.py 是这一路径的后端实现:

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." ) class FrontendToolFlow(Flow[CopilotKitState]): @start() async def chat(self) -> None: response = await copilotkit_stream( await acompletion( model="openai/gpt-5.4", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, *self.state.messages, ], tools=self.state.copilotkit.actions or None, tool_choice=( "required" if self.state.copilotkit.actions and self.state.messages and self.state.messages[-1].get("role") == "user" else "auto" ), parallel_tool_calls=False, stream=True, ) ) self.state.messages.append(response.choices[0].message)

这段实现的关键设计:

  • 前端工具从copilotkit.actions注入:CrewAI Flow 自身不定义query_notes,它只把前端通过 AG-UI 上报的 actions 透传给 LLM 的tools参数。
  • System Prompt 强制调用:明确要求"能调用前端工具就必须调用,不得声称无权限、不得用散文回答替代",并在浏览器返回工具结果后做简要总结——这让演示结果确定、可断言。
  • tool_choice的按轮次策略:当存在 actions 且最后一条消息来自用户时强制required,否则回退auto,保证用户提问的那一轮必然触发工具调用。
  • 不在后端伪造工具结果:流式发出的前端工具调用即结束本次 Flow 运行,工具由浏览器执行,结果在下一请求中带回。这正是"前端工具"与"后端工具"最本质的分工区别。

这个 Flow 通过 API 路由注册给前端。在 route.ts 中,frontend-tools-asyncfrontend_toolshuman_in_the_loophitl-in-chathitl-in-appopen-gen-uiopen-gen-ui-advanced等别名一起被路由到同一个后端端点:

const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; function createAgent(path = "/chat") { const feature = path.replace(/^\/+/, ""); return new HttpAgent({ url: `${AGENT_URL}/conversational_flows/${feature}` }); } agents["frontend-tools-async"] = createAgent("/frontend-tools");

也就是说:浏览器端query_notes的工具声明会随会话请求一起送到/conversational_flows/frontend-tools,CrewAI Flow 据此生成工具调用并流式回传,前端拿到调用后在本地执行 async handler。运行时通过 AG-UI 协议代理到独立的 Python 后端(默认http://localhost:8000),并且route.ts的 GET 端点还提供/health探活与AGENT_URL环境信息,便于排查"前端连上了但 Agent 未响应"的问题。

QA 验证清单解读:从手工检查到 Playwright 自动化

关联文档 frontend-tools-async.md 本身是一份精炼的 QA 清单,包含 5 项检查:

  1. 导航到/demos/frontend-tools-async
  2. 提问 "Find my notes about project planning";
  3. 验证NotesCarddata-testid="notes-card")渲染且标题包含查询关键词;
  4. 验证匹配笔记(n1、n5)出现在data-testid="notes-list"内;
  5. 再提问 "Search my notes for auth",验证结果随查询更新。

这份清单已被仓库中的 frontend-tools-async.spec.ts 完整实现为 4 条 Playwright 用例,覆盖清单中的每一项并有所扩展:

Playwright 用例对应清单项断言要点
页面加载:composer + 3 个 pill第 1 项输入框 placeholder 可见,三个建议按钮可见
project-planning pill → Notes DB 卡片第 2–4 项卡片可见、标题含 "project planning"、列表含 n1 与 n5
auth pill → Notes DB 卡片第 5 项卡片可见、标题含 "auth"、列表含 n2
reading pill → Notes DB 卡片 + 锁定叙述扩展项卡片含 n4、标题、摘要、标签 chip、匹配数 "1 match",且第二轮回合叙述以固定短语开头
同一会话内顺序点击 3 个 pill回归项每张卡片各自渲染,卡片数依次变为 1 → 2 → 3

值得注意的测试设计细节:

  • 关键词标题即"工具结果已回传"的证据:测试断言notes-keyword标题显示Matching "project planning"等文本,这证明异步 handler 已针对 fixture 发出的query_notes(keyword=...)调用在真实NOTES_DB上执行完毕。
  • 反向断言防止误路由:project-planning 用例断言通用 plan 文案不出现、auth 用例断言 showcase-assistant 兜底文案不出现,防止其他 fixture 拦截了本应发给前端工具的 prompt。
  • aimock 多 pill 回归测试:最后一个用例专门针对"同一会话内连点多个工具 pill 时只渲染第一张卡片"的旧 bug。其修复方案是用toolCallId串联 fixture、去掉hasToolResult门控,验证所有 pill 在单会话内各自渲染自己的 Notes DB 卡片。

Python 侧同样有验证:tests/python/test_specialized_flows.py校验frontend-tools-async别名指向/frontend-tools端点,并验证query_notes工具调用的消息结构;tests/python/test_d6_fixture_parity.py校验frontend-tools-async.jsonfixture 与toolCallId(如call_d5_query_notes_project_planning_001)的匹配优先级。

前端工具的适用边界与设计建议

从本演示的源码结构可以总结出前端工具(尤其是异步版)的适用边界:

  • 适合前端工具的场景:数据或能力只存在于浏览器端——本地数据库、IndexedDB 缓存、客户端私有状态、需要用户设备参与的操作。工具声明只描述"能做什么",数据完全不出浏览器。
  • 不适合的场景:需要服务端权威计算、鉴权、共享数据的操作仍应走后端工具;前端工具的结果可信度取决于浏览器环境。
  • 务必保持 handler 确定性:演示刻意使用内存常量 + 固定延迟,让结果可被测试与截图复现。真实场景中如需稳定 QA,也应通过 mock 或受控数据源实现同样的确定性。
  • 渲染锚点是 QA 的契约:为关键渲染节点提供稳定的data-testid(如notes-cardnotes-listnote-*),并让标题直接体现工具入参(如Matching "<keyword>"),是让异步 UI 可自动化验证的关键工程实践。

如何运行与验证

本地复现该演示需先启动 Python Agent 后端,再启动 Next.js 前端(默认 Agent 地址为http://localhost:8000,可通过环境变量AGENT_URL覆盖;若需逐请求调试日志可设置SHOWCASE_ROUTE_DEBUG=1)。启动后访问/demos/frontend-tools-async,点击建议 pill 或直接输入问题,即可观察:Agent 推理 → 前端异步 handler 执行 → Notes DB 卡片渲染 → Agent 总结的完整闭环。随后可运行仓库中的 Playwright 用例(tests/e2e/frontend-tools-async.spec.ts)与 Python 侧的tests/python/test_specialized_flows.pytests/python/test_d6_fixture_parity.py验证各项断言。

总结

frontend-tools-async演示展示了 CopilotKit"前端工具"模式在 CrewAI Conversational Flows 集成中的完整实现:前端通过useFrontendTool声明工具与异步 handler,CrewAI Flow 通过copilotkit.actions感知工具并强制调用,浏览器执行后把结构化结果交还 Agent 总结,最终以自定义NotesCard渲染。配合确定性的内存数据与三层自动化验证(Playwright E2E、Python 流程测试、fixture 一致性测试),这一模式可以安全复用到任何"Agent 需要访问浏览器私有数据"的场景。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 1:18:31

Unity打造Android桌面宠物:从悬浮窗嵌入到交互实现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 1:16:02

国自然申请改革:如何在有限篇幅内展现研究深度

1. 国自然申请改革背景与核心挑战2026年国家自然科学基金申请迎来重大变革&#xff0c;最显著的变化是申请材料篇幅的大幅压缩。这一改革直接打破了延续多年的"以量取胜"评审模式&#xff0c;对科研人员的学术表达能力提出了更高要求。在有限的篇幅内既要做到简洁清晰…

作者头像 李华
网站建设 2026/9/13 1:14:49

Frank-Wolfe算法MATLAB实现:大规模约束优化的高效解法

简介&#xff1a;Frank-Wolfe算法是一种经典的约束凸优化方法&#xff0c;由J. Frank和D. Wolfe于1956年提出&#xff0c;在处理大型稀疏数据集时尤为高效&#xff0c;适合需要求解带约束目标函数最小化问题的场景。这份Matlab实现资源&#xff0c;面向正在学习优化算法原理、需…

作者头像 李华
网站建设 2026/9/13 1:08:30

ASP档案管理系统开发全攻略:环境配置、模块改造与答辩部署

简介&#xff1a;一份面向计算机专业毕业设计的ASP档案管理系统完整项目包&#xff0c;适合需要完成Web开发课题的本专科学生&#xff0c;也可作为企业文档管理开发的基础参考。系统基于ASP与Access数据库实现&#xff0c;覆盖用户登录与权限管理、档案上传下载、关键词检索、分…

作者头像 李华
网站建设 2026/9/13 1:05:27

CLM5陆面模型安装与区域模拟实践指南

1. CLM模式概述与核心价值CLM&#xff08;Community Land Model&#xff09;作为地球系统模拟领域的核心工具&#xff0c;已经发展到第5代版本&#xff08;CLM5&#xff09;。这个由美国国家大气研究中心&#xff08;NCAR&#xff09;主导开发的陆面过程模型&#xff0c;本质上…

作者头像 李华
网站建设 2026/9/13 1:03:41

Python数据可视化:Plotly交互式图表实战指南

1. 为什么选择Plotly进行数据可视化在数据分析和可视化的世界里&#xff0c;Matplotlib曾经是Python生态中的绝对主流&#xff0c;但近年来交互式图表的需求日益增长。Plotly作为一个开源的数据可视化库&#xff0c;正在迅速崛起并改变这一格局。我第一次接触Plotly是在一个需要…

作者头像 李华