CopilotKit 异步前端工具(Frontend Tools Async)实战:基于 Langroid 集成的客户端工具端到端实现与 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 Langroid 集成中的frontend-tools-async示例展开,深入讲解如何通过useFrontendTool在浏览器端注册异步前端工具(async frontend tool):Agent 发起工具调用后,由客户端 handler 执行一次真实的异步操作(本例为模拟本地笔记数据库查询),再将结果交还给 Agent 汇总并渲染到聊天流中。读完本文,你将掌握异步前端工具的核心 API 形态、前后端接线方式、加载态渲染策略,以及如何依据仓库内的 QA 清单与 E2E 测试对这一链路做端到端验证。
本文对应的 QA 清单位于 showcase/integrations/langroid/qa/frontend-tools-async.md,Demo 源码位于 showcase/integrations/langroid/src/app/demos/frontend-tools-async/。
一、功能定位:什么是异步前端工具
CopilotKit 的前端工具(Frontend Tools)机制,允许 LLM Agent 调用注册在浏览器端的函数,而不是只能调用服务端工具。当 handler 内部需要等待异步操作(如请求 IndexedDB、读取本地缓存、调用浏览器 API、模拟网络往返)时,就构成了"异步前端工具"场景——Agent 会等待该 Promise resolve 之后,再基于返回结果继续生成回答。
在 Langroid 集成中,该能力由frontend-tools-async这一 demo 专门演示。从 showcase/integrations/langroid/manifest.yaml 中的功能声明可以看到,frontend-tools-async被列为独立的 demo 条目,描述为:
useFrontendTool with an async handler — agent awaits a client-side async operation (simulated notes DB query) and uses the returned result
同时该集成也声明了frontend-tools(同步版)与frontend-tools-async(异步版)两项能力。两者的核心区别在于:同步版 handler 直接返回结果(如setBackground),而异步版 handler 返回一个Promise,render回调需要经历"pending → complete"的状态转换,从而驱动出加载态 UI。
二、Demo 入口与页面装配
页面入口位于 showcase/integrations/langroid/src/app/demos/frontend-tools-async/page.tsx,整体装配结构如下:
"use client"; import React from "react"; import { CopilotChat, CopilotKit, useConfigureSuggestions, useFrontendTool, } from "@copilotkit/react-core/v2"; import { z } from "zod"; import { NotesCard, type Note } from "./notes-card"; import { NOTES_DB, sleep } from "./fake-notes-db"; 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> ); }要点:
- Provider 配置:
<CopilotKit>指定了runtimeUrl="/api/copilotkit"(Next.js API 路由)和agent="frontend-tools-async"。这个 agent 名称会随请求发送给运行时,用于路由到对应的后端 Agent 实例(详见下文"后端接线")。 - 聊天 UI:页面使用
@copilotkit/react-core/v2提供的预置CopilotChat组件,agentId与 Provider 的 agent 名保持一致。 - Schema 校验:工具参数使用
zod定义,CopilotKit 会将其转换为 Agent 可识别的 JSON Schema。
页面还通过useConfigureSuggestions预置了三条建议提示(suggestion pills),方便用户一键发起测试:
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 标签搜索。
三、核心实现:useFrontendTool 注册异步工具
query_notes工具通过useFrontendTool钩子注册,完整代码如下(节选自 page.tsx):
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 }) => { 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} /> ); }, });对该实现逐项拆解:
3.1 工具声明(name / description / parameters)
name: "query_notes":Agent 在推理时使用的工具标识,后端 Agent 不需要为它定义任何工具,因为实际执行完全发生在浏览器端。description:对搜索行为做了精确描述——对标题、摘要、标签做大小写不敏感的子串匹配,最多返回 5 条。描述质量直接影响 LLM 选择该工具的准确性。parameters:用zod定义单个keyword字符串参数,并通过.describe()补充说明。CopilotKit 会将该 schema 注入给 Agent。
3.2 异步 handler:客户端侧的"数据库"往返
handler: async ({ keyword }: { keyword: string }) => { await sleep(500); // 模拟本地 DB 查询延迟 const q = keyword.toLowerCase(); const matches = NOTES_DB.filter(...).slice(0, 5); return { keyword, count: matches.length, notes: matches }; };- handler 是
async函数,返回Promise,这正是"异步前端工具"的关键特征。 await sleep(500)用 500ms 模拟一次本地数据库往返。sleep与模拟数据都定义在 fake-notes-db.ts:sleep(ms)是对setTimeout的 Promise 封装;NOTES_DB是 7 条确定性数据(n1–n7),文件注释明确说明"Demo-only fixture",真实应用中应替换为 IndexedDB、缓存拉取或任何客户端自有数据源。固定且确定的数据保证了测试与截图可复现。
- 搜索逻辑对
title、excerpt、tags三个字段做小写化后的子串匹配,命中后截取前 5 条,返回结构包含keyword、count和notes,便于渲染层与 Agent 两侧使用。
3.3 render 回调:加载态与结果态
render: ({ args, result, status }) => { const loading = status !== "complete"; ... return <NotesCard loading={loading} keyword={...} notes={parsed.notes} />; };status会经历非"complete"(pending)到"complete"(handler resolve)的状态转换,loading = status !== "complete"即加载态判定。- 在异步 handler 未返回期间,UI 显示加载态;resolve 后切换到结果态。这对应 QA 清单中的"loading state shows briefly while the async handler resolves"验证点。
args为 Agent 传入的原始参数(此时结果可能还未解析),渲染层用args?.keyword ?? parsed.keyword做兜底。
四、渲染层:NotesCard 与加载态呈现
结果卡片定义在 notes-card.tsx,文件注释强调它与其它工具渲染 cell 共用同一套渲染路径,但notes数组完全来自浏览器端异步 handler 的返回。
关键 UI 行为:
- 加载态:显示
Querying local notes DB...文案与...占位符; - 结果态:显示匹配数量(
N matches)、关键词标题(Matching "<keyword>"),并逐条渲染笔记(标题、摘要、标签 chips); - 空结果:显示
No notes matched.的斜体空态文案。
组件暴露了稳定的测试锚点,供 QA 与 E2E 使用:
| testid | 含义 |
|---|---|
notes-card | 卡片外层容器 |
notes-keyword | 关键词标题(Matching "<keyword>") |
notes-list | 匹配结果<ul> |
note-n1…note-n7 | 每条笔记行 |
五、后端接线:CopilotKit Runtime 与 AG-UI 协议
前端工具的执行虽然在浏览器完成,但工具调用的编排仍由后端 Agent 发起,因此需要运行时桥接。Langroid 集成的桥接路由位于 showcase/integrations/langroid/src/app/api/copilotkit/route.ts:
const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; function createAgent(path = "/") { return new HttpAgent({ url: `${AGENT_URL}${path}` }); }- Langroid Agent 后端以独立进程运行在 8000 端口,前端通过
HttpAgent以AG-UI 协议转发请求。 - 路由注册了
"frontend-tools-async"这个 agent 名称。文件注释明确说明:frontend-tools 变体在后端没有专用工具,前端通过useFrontendTool注册 handler,由 Agent 调用它们。 - 启动方式由
cli-startdemo 给出:npx copilotkit@latest init --framework langroid。
这条链路的工作流为:用户提问 → Agent 决定调用query_notes→ 请求经 runtime 路由到前端注册的 handler → 浏览器执行异步查询并返回结果 → 结果回传给 Agent → Agent 基于结果生成回答 →render依据最终状态渲染 NotesCard。
六、QA 验证指南:逐条解读测试步骤
以下是 frontend-tools-async.md 中的完整 QA 步骤,结合实现细节逐条解读:
步骤 1:导航到 /demos/frontend-tools-async
启动 Langroid 集成开发环境后,在浏览器访问/demos/frontend-tools-async路由。页面应展示完整的聊天界面(CopilotChat),并出现三条建议提示(suggestion pills)。
可验证依据:E2E 测试 frontend-tools-async.spec.ts 的第一个用例page loads with composer and 3 pills断言了输入框占位符Type a message与三个按钮(Find project-planning notes、Search for 'auth'、What do I have about reading?)的可见性。
步骤 2:提问 "Find my notes about project planning."
点击第一条建议,或直接在输入框输入该消息。Agent 应识别出需要查询本地笔记,并调用query_notes前端工具,传入keyword="project planning"。
预期结果:异步 handler 对NOTES_DB执行大小写不敏感搜索,命中 n1("Q2 project planning kickoff")与 n5("Project planning retrospective notes")两条笔记。
步骤 3:验证 notes-card 渲染出匹配关键词
聊天流中应出现NotesCard,其标题显示Matching "project planning",列表包含 n1 与 n5 两条笔记。
可验证依据:E2E 用例project-planning pill → Notes DB card with project-planning notes断言notes-keyword匹配/Matching\s+[""“]project planning[""”]/i,且note-n1、note-n5可见。
步骤 4:验证加载态在异步 handler 解析期间短暂出现
由于 handler 内部await sleep(500),卡片在结果返回前会短暂显示Querying local notes DB...加载文案。验证这一点的要点是:卡片必须先以加载态挂载,再过渡到结果态。
可验证依据:harness 探针 d5-frontend-tools-async.ts 使用"settled shape"断言——卡片挂载后轮询等待其进入两种最终形态之一(非空notes-list或No notes matched空态)。若异步 handler 卡死未 resolve,卡片会永远停留在loading=true,断言将超时失败,从而捕获"异步 handler 挂起"的回归。
步骤 5:尝试 "Search my notes for anything related to auth" 并验证 auth 标签笔记出现
点击第二条建议,Agent 应调用query_notes(keyword="auth")。handler 会命中 n2("Planning: migrate auth to passkeys",tags 含auth),卡片显示Matching "auth"与 n2 笔记。
可验证依据:E2E 用例auth pill → Notes DB card with auth-related notes断言关键词标题与note-n2可见,并额外做了反回归断言:通用兜底助手文案I'm your showcase assistant不得出现(防止其它 fixture 拦截了该提示词)。
七、自动化验证:E2E 测试设计
E2E 测试 showcase/integrations/langroid/tests/e2e/frontend-tools-async.spec.ts 共包含 4 个用例,覆盖 QA 清单的全部场景:
- 页面加载:composer 与 3 条建议 pills 可见。
- project planning 查询:点击 pill → NotesCard 可见 → 关键词标题为 project planning → n1、n5 渲染 → 反回归断言通用计划模板文案不出现。
- auth 查询:点击 pill → NotesCard 可见 → 关键词标题为 auth → n2 渲染 → 反回归断言 showcase-assistant 兜底文案不出现。
- reading 查询:点击 pill → 关键词为 reading → 匹配数
1 match→ n4 渲染 → 笔记内容("Book recommendations"、"Thinking Fast and Slow" 等)与reading标签 chip 可见 → 并断言 Agent 的第二轮叙述文案(引用该笔记标题与标签的固定措辞)。 - 同一线程内连续触发三个 pills:每个 pill 各自渲染自己的 NotesCard(卡片数量依次 1→2→3),验证多轮对话中异步工具链路的健壮性。测试注释还记录了此前的一个 aimock 多 pill 回归 bug 及其修复方式(通过
toolCallId串联 fixture、移除hasToolResult门控)。
这些测试通过确定性 aimock fixture 与真实客户端NOTES_DB配合,实现了对异步工具端到端往返的"真通过"验证。
八、与同步前端工具的对比
Langroid 集成中还有一个同步版前端工具 demofrontend-tools(切换页面背景色),见 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); return { status: "success" }; }, });两者的差异与选型建议:
- 同步场景(如修改本地状态、切换 UI):handler 立即返回,
render通常不需要加载态,可参考frontend-toolsdemo 的CopilotSidebar形态。 - 异步场景(如查询本地数据库、调用浏览器 API、等待网络往返):handler 返回
Promise,必须处理 pending 状态,render中通过status !== "complete"驱动加载态,这正是frontend-tools-asyncdemo 的核心价值。
从 manifest 看,两者在features列表中分别声明为frontend-tools与frontend-tools-async,说明这是 CopilotKit 能力矩阵中的两个独立能力项。
九、小结与排查建议
异步前端工具让 Agent 在不经过后端网络往返的前提下,安全地操作浏览器本地数据与能力,是构建"个人数据助手"类应用(本地笔记搜索、文件元数据查询、浏览器书签检索等)的高效模式。
结合本文的仓库证据,做功能验证时可参考以下排查思路:
- 卡片未出现:检查
useFrontendTool的name与后端 Agent 工具约定是否一致,以及agent="frontend-tools-async"是否在 route.ts 的 agent 注册表中。 - 一直处于加载态:handler 的 Promise 未 resolve(如异步逻辑抛错或挂起),可用 harness 的 settled-shape 断言快速定位。
- 关键词不匹配:确认 Agent 传给 handler 的
keyword参数与建议提示一致,并核对NOTES_DB中对应笔记的字段内容。 - 结果未回传 Agent:检查 handler 的返回结构是否可被 Agent 理解(建议返回结构化对象,便于 LLM 摘要)。
以上所有验证步骤、源码与测试用例均可从当前仓库中直接复现与深入研读。
【免费下载链接】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),仅供参考