CopilotKit 异步前端工具(Frontend Tools Async)实战:基于 Langroid 集成的客户端工具端到端实现与 QA 验证指南
2026/9/13 10:04:59 网站建设 项目流程

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 返回一个Promiserender回调需要经历"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、缓存拉取或任何客户端自有数据源。固定且确定的数据保证了测试与截图可复现。
  • 搜索逻辑对titleexcerpttags三个字段做小写化后的子串匹配,命中后截取前 5 条,返回结构包含keywordcountnotes,便于渲染层与 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-n1note-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 端口,前端通过HttpAgentAG-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 notesSearch 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-n1note-n5可见。

步骤 4:验证加载态在异步 handler 解析期间短暂出现

由于 handler 内部await sleep(500),卡片在结果返回前会短暂显示Querying local notes DB...加载文案。验证这一点的要点是:卡片必须先以加载态挂载,再过渡到结果态

可验证依据:harness 探针 d5-frontend-tools-async.ts 使用"settled shape"断言——卡片挂载后轮询等待其进入两种最终形态之一(非空notes-listNo 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 清单的全部场景:

  1. 页面加载:composer 与 3 条建议 pills 可见。
  2. project planning 查询:点击 pill → NotesCard 可见 → 关键词标题为 project planning → n1、n5 渲染 → 反回归断言通用计划模板文案不出现。
  3. auth 查询:点击 pill → NotesCard 可见 → 关键词标题为 auth → n2 渲染 → 反回归断言 showcase-assistant 兜底文案不出现。
  4. reading 查询:点击 pill → 关键词为 reading → 匹配数1 match→ n4 渲染 → 笔记内容("Book recommendations"、"Thinking Fast and Slow" 等)与reading标签 chip 可见 → 并断言 Agent 的第二轮叙述文案(引用该笔记标题与标签的固定措辞)。
  5. 同一线程内连续触发三个 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-toolsfrontend-tools-async,说明这是 CopilotKit 能力矩阵中的两个独立能力项。

九、小结与排查建议

异步前端工具让 Agent 在不经过后端网络往返的前提下,安全地操作浏览器本地数据与能力,是构建"个人数据助手"类应用(本地笔记搜索、文件元数据查询、浏览器书签检索等)的高效模式。

结合本文的仓库证据,做功能验证时可参考以下排查思路:

  1. 卡片未出现:检查useFrontendToolname与后端 Agent 工具约定是否一致,以及agent="frontend-tools-async"是否在 route.ts 的 agent 注册表中。
  2. 一直处于加载态:handler 的 Promise 未 resolve(如异步逻辑抛错或挂起),可用 harness 的 settled-shape 断言快速定位。
  3. 关键词不匹配:确认 Agent 传给 handler 的keyword参数与建议提示一致,并核对NOTES_DB中对应笔记的字段内容。
  4. 结果未回传 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询