ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CopilotKit × Mastra:Agentic Generative UI 实战——让长时任务 Agent 在聊天流中实时渲染进度状态卡片

CopilotKit × Mastra:Agentic Generative UI 实战——让长时任务 Agent 在聊天流中实时渲染进度状态卡片 CopilotKit × MastraAgentic Generative UI 实战——让长时任务 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本文围绕 CopilotKit 官方 showcase 中 Mastra 集成演示的「Agentic Generative UIgen-ui-agent」展开演示的后端 deep agent 自己定义了steps状态结构并通过自定义set_steps工具把每一步的pending → in_progress → completed状态变化推送到客户端前端用 v2useAgentHook 订阅这份实时状态把一张可原地更新的进度卡片渲染进聊天转录区。读完后你可以掌握“后端 Agent 拥有状态、前端状态驱动渲染”这一 Agentic Generative UI 的完整实现链路——包括工作记忆working memory的确定性写入、AG-UISTATE_SNAPSHOT事件流以及如何避免“每条状态消息都新增一张卡片”的经典 bug。一、演示定位Agent 全权控制聊天里出现什么该演示位于 gen-ui-agent 目录官方说明非常凝练The agent renders custom UI as it works through long-running tasks, streaming status updates and intermediate results into the chat.即Agent 在执行长时任务的过程中渲染自定义 UI把状态更新和中间结果流式写入聊天。README 同时指出前端通过useAgentRender这一思路把「Agent 发出的 UI 类型」映射为 React 组件从而让 Agent 完全掌控转录区transcript里出现的内容README 的完整描述以 showcase manifest 为准其本身是陪伴演示源码的一份开发者笔记。在 showcase 的 manifest.yaml 中这个单元登记为- id: gen-ui-agent name: Agentic Generative UI description: Long-running agent tasks with generated UI tags: - generative-ui route: /demos/gen-ui-agent highlight: - src/app/demos/gen-ui-agent/page.tsx - src/app/api/copilotkit/route.ts也就是说manifest 声明了两个关键文件前端页面page.tsx与 CopilotKit 运行时路由。下面按「前端订阅 → 后端 Agent 与工具 → 状态落盘机制 → 事件流 → 测试验证」的顺序深入源码。二、前端入口绑定 Agent 并挂载演示页演示入口 page.tsx 用一个CopilotKitProvider 包住聊天界面并把runtimeUrl指向本地 CopilotKit 路由、把agent指定为gen-ui-agentCopilotKit runtimeUrl/api/copilotkit agentgen-ui-agent div classNameflex justify-center items-center h-screen w-full div classNameh-full w-full max-w-4xl Chat / /div /div /CopilotKit运行链路是/demos/gen-ui-agent页面 → 浏览器请求/api/copilotkit→ CopilotKit 路由 把逻辑 agent idgen-ui-agent解析为 Mastra 实例genUiAgent路由中的 agent 别名映射表里有gen-ui-agent: genUiAgent这一条目并显式校验了genUiAgent必须存在于 Mastra 配置中→ 经ag-ui/mastra适配器以 AG-UI 事件流回传。演示页还注册了三个触发长时任务的建议提示suggestions.tsuseConfigureSuggestions({ suggestions: [ { title: Plan a product launch, message: Plan a product launch for a new mobile app. }, { title: Organize a team offsite, message: Organize a three-day engineering team offsite. }, { title: Research a competitor, message: Research our top competitor and summarize their strengths and weaknesses. }, ], available: always, });这类「规划发布 / 组织团建 / 调研竞品」的请求正是需要多步推进的长时任务能充分展示进度卡片的状态流转。三、状态订阅v2useAgentmessageView.children单卡片模式Chat组件是前端渲染策略的核心function Chat() { const { agent } useAgent({ agentId: gen-ui-agent, updates: [UseAgentUpdate.OnStateChanged], }); useSuggestions(); const steps (agent.state as AgentState | undefined)?.steps ?? []; const status agent.isRunning ? inProgress : complete; return ( CopilotChat agentIdgen-ui-agent classNameh-full rounded-2xl messageView{{ children: ({ messageElements, interruptElement }) ( MessageListWithState messageElements{messageElements} interruptElement{interruptElement} steps{steps} status{status} / ), }} / ); }这里有三个值得展开的设计点只订阅状态变化。useAgent({ updates: [UseAgentUpdate.OnStateChanged] })让组件仅在 Agent 状态state.steps变化时重渲染而不是订阅每一条消息。前端类型AgentState { steps?: Step[] }与后端 Zod schema 一一对应。status直接由运行态推导。agent.isRunning ? inProgress : complete——Agent 运行中时卡片显示转圈动画运行结束则整体打勾无需后端额外发一个「完成」标记。messageView.children插槽注入单张卡片。这是本演示与旧方案最本质的区别状态卡片不再挂到某条具体消息上而是由自定义消息列表 message-list-with-state.tsx 统一编排——先渲染消息本体再在消息之后、interrupt 元素之前渲染唯一一张InlineAgentStateCarddiv>export const GenUiAgentState z.object({ steps: z .array( z.object({ id: z.string(), title: z.string(), status: z.enum([pending, in_progress, completed]), }), ) .default([]), });这个 Zod schema 会挂到 MastraMemory的workingMemory.schema上。其作用是给steps一个固定的落盘槽位只有 schema 声明了这个字段set_steps的写入才有地方落AG-UI 适配器的STATE_SNAPSHOT快照才会把它带出去前端才能读到agent.state.steps。4.2set_steps工具整列表覆盖永远不是 diff工具的完整实现见 gen-ui-agent.tsconst StepSchema z.object({ id: z.string().describe(Unique identifier for the step.), title: z.string().describe(Short description of the step.), status: z .enum([pending, in_progress, completed]) .describe(Current status of the step.), }); export const setStepsTool createTool({ id: set_steps, description: Publish the current plan step statuses. Call this every time a step transitions (including the first enumeration of steps). Always pass the FULL list of steps with their current statuses — never a diff., inputSchema: z.object({ steps: z .array(StepSchema) .describe(The full list of steps with their current statuses (pending, in_progress, completed).), }), execute: async (inputData, executionContext) { const steps inputData.steps ?? []; await writeStepsToWorkingMemory(executionContext, steps); return JSON.stringify({ published: steps.length, updated: true as const }); }, });工具描述本身就是在训练模型每次状态迁移包括首次列出全部步骤都要调用它且永远传完整列表永远不传增量 diff。工具返回值是一段简短的 JSON 回执{ published: n, updated: true }让模型在下一轮拿到结构良好的 tool-result 继续推进链路。4.3 Agent 装配脚本化系统提示词 步数上限genUiAgent的完整装配export const genUiAgent new Agent({ id: gen-ui-agent, name: Gen UI Agent, tools: { setStepsTool }, model: openai(gpt-4o-mini), defaultOptions: { stopWhen: stepCountIs(12), }, instructions: You are an agentic planner. For each user request, follow this exact sequence: 1. Plan exactly 3 concrete steps and call \set_steps\ ONCE with all three steps at statuspending. 2. Step 1: call \set_steps\ with step 1 at statusin_progress, then call \set_steps\ again with step 1 at statuscompleted. 3. Step 2: ... (同理) 4. Step 3: ... (同理) 5. Send ONE final conversational assistant message summarizing the plan, then stop. Do not call any more tools after step 3 is completed. Rules: never call set_steps in parallel — always wait for one call to return before the next. Always pass the FULL list of steps (with their current statuses) to set_steps; never a diff. ..., memory: new Memory({ storage: new LibSQLStore({ id: gen-ui-agent-memory, url: WORKING_MEMORY_DB_URL }), options: { workingMemory: { enabled: true, schema: GenUiAgentState } }, }), });三个工程细节值得注意系统提示词即执行脚本。它把整个任务固定为「1 次初始全 pending 调用 每个步骤 2 次迁移调用in_progress、completed 7 次工具调用 1 条收尾消息」并要求串行调用上一轮返回后才发下一轮这保证了前端能观察到确定性的状态动画序列。stepCountIs(12)的由来。源码注释解释3 个步骤 × 2 次迁移 1 次初始 1 条收尾消息约等于 8 个模型回合而 AI SDK 的默认停止条件会在第 3 个步骤到达 completed 之前终止 agentic loop只有 2/3 落地所以把步数上限提到 12 让完整流程跑完。这是 MastraAI SDK 引擎与 LangGraph图跑到节点结束在递归预算上的等价物。工作记忆持久化。LibSQLStore的 URL 默认为file:./mastra-memory.db可用MASTRA_WORKING_MEMORY_URL覆盖源码注释解释了为什么不直接用内存库——开发模式下 Next.js HMR 重启会让内存库里的用户状态无声丢失。五、确定性状态写入writeStepsToWorkingMemory的完整链路这是本演示最关键、也最容易被忽略的机制。Mastra 默认的工作记忆契约是「LLM 驱动」——系统提示词让模型记得调用updateWorkingMemory。这本质上是非确定性的任何一轮模型忘了调用UI 状态就停在旧值。源码注释working-memory.ts直言对「目的就是把后端状态槽位展示到 UI」的演示来说这是一个无声的 UX bug。因此采用的模式是工具自己在execute里直接写工作记忆而不是指望模型另外调用一次。set_steps的execute调用 writeStepsToWorkingMemory该函数执行五步通过mastra.getAgentById(agentId)解析 Agent 实例agentId取自工具执行上下文回退为genUiAgent通过agent.getMemory()拿到 Memory注意此处是await——当前mastra/core中getMemory返回 Promise不 await 会读到undefined导致写入静默无效读取现有工作记忆JSON 字符串则JSON.parse容错失败返回{}把新steps合并进已有 payload{ ...existing, steps }保住其他字段调memory.updateWorkingMemory({ threadId, resourceId, workingMemory: JSON.stringify(merged) })落盘。该文件还刻意对 Mastra Memory 的 beta API 做了特性探测getAgentById/getAgent、getWorkingMemory、updateWorkingMemory逐一typeof检查任一环节缺失或抛错只输出结构化错误日志而不中断运行——因为「状态更新」是 fire-and-forget 的副作用工具的主体结果不应因它而失败。事件流从工作记忆到STATE_SNAPSHOT写盘之后AG-UI 的 Mastra 适配器在每次 run 循环中工作记忆发生变化时发出STATE_SNAPSHOT事件快照携带steps字段前端useAgent({ updates: [OnStateChanged] })收到快照后更新agent.state.stepsInlineAgentStateCard随之原地重渲染。整条链路是LLM 调 set_steps(完整 steps) → execute 内 writeStepsToWorkingMemory 直接写 LibSQL 工作记忆 → AG-UI Mastra 适配器发出 STATE_SNAPSHOT(steps: [...]) → 前端 useAgent(OnStateChanged) 收到快照 → InlineAgentStateCard 原地更新单卡片无重复对比同文件注释Shared StateStreaming演示是唯一走内置updateWorkingMemory工具的路径因为它需要按 token 流式输出STATE_DELTA而gen-ui-agent这类「整份状态一次更新」的演示都走自定义set_*工具 单次 run 末STATE_SNAPSHOT的路径。这个取舍快照 vs 增量在源码注释里被明确区分是理解整个状态体系的关键。六、进度卡片状态驱动的三态渲染InlineAgentStateCard.tsx 把Step类型与后端对齐id / title / status并按状态迁移链pending → in_progress → completed做视觉映射头部运行中且未全部完成时显示旋转 Spinner “Step N of M”完成后显示绿色对勾 “All M steps complete”尚无步骤时显示 “Planning…”。每一步completed为绿色实心对勾圆标 删除线文字in_progress为紫色旋转图标 加粗文字pending为灰色序号圆标 常规文字。测试锚点卡片与步骤分别带data-testidagent-state-card/data-testidagent-step以及data-status属性——这些属性正是 e2e 回归测试的断言依据下一节。const total steps.length; const done steps.filter((s) s.status completed).length; const headline status complete || (total 0 done total) ? All ${total} steps complete : total 0 ? Step ${Math.min(done 1, total)} of ${total} : Planning…;七、E2E 验证测试钉住的三条契约演示的 Playwright 测试 gen-ui-agent.spec.ts 把这个演示的核心行为固化成了回归契约单卡片契约renders a single agent-state-card that updates in place发送 “Plan a product launch for a new mobile app.”等卡片和第一个步骤出现后断言agent-state-card数量为 1等运行结束spinner 消失后再断言一次数量仍为 1。注释说明这是针对「每次set_steps都新增一张卡片」的历史 bug 的回归测试——一次 7 次调用的运行曾产生 7 张堆叠卡片。终态完整性eventually marks every step as completed等待所有步骤出现后断言data-statuscompleted的步骤数恰好为 3且总步骤数为 3无孤儿步骤。动画不短路steps animate through pending before completing (no fixture short-circuit)针对早期 aimock 夹具“一次调用就把三步全发成 completed、卡片直接以终态挂载、没有逐步动画”的问题做回归由于回放响应极快、浏览器可能观察不到瞬态pending测试断言最终态——步骤出现且卡片已渲染。其中 aimock 说明值得留意showcase 支持录制回放aimock回放模式下整条 7 次set_steps链路在数秒内跑完这也是测试超时设置相对宽裕60s–120s的原因。真实 LLM 模式下同样的行为由系统提示词脚本化保证。八、运行与延伸阅读在 mastra showcase 目录下package.json常用脚本为pnpm devnext dev --turbopack启动演示浏览器访问/demos/gen-ui-agentpnpm test跑 vitest 单测pnpm test:e2eplaywright test跑上述 e2e 回归关键依赖版本copilotkit/react-core1.68.2v2 的useAgent所在包、ag-ui/mastra1.1.0AG-UI 适配器、mastra/core1.48.0、ai5.xstepCountIs来自ai包。延伸阅读均在本仓库 mastra 集成内agents/index.ts同目录下的sharedStateReadWriteAgentUI 写、Agent 读 Agent 写、UI 读的双向共享状态与sharedStateStreamingAgent内置updateWorkingMemory驱动的逐 tokenSTATE_DELTA流式共享状态是本演示的两个近亲三者共同构成“Agent 状态 → UI”的完整光谱working-memory.tswriteNotesToWorkingMemory/writeTodosToWorkingMemory/writeDelegationsToWorkingMemory使用与writeStepsToWorkingMemory完全相同的模式可直接复用CopilotKit 路由逻辑 agent idgen-ui-agent到 Mastra 实例genUiAgent的别名映射与resourceId绑定。小结这个演示把 Agentic Generative UI 拆成了四个可复用的决策——(1) 状态槽位由后端 schema 声明GenUiAgentState.steps(2) 状态写入由工具代码确定性执行set_steps→writeStepsToWorkingMemory不依赖 LLM 的自觉性(3) 传输走 AG-UISTATE_SNAPSHOT前端只订阅OnStateChanged(4) 渲染走messageView.children单卡片原地更新杜绝重复卡片。把这套组合照搬到你自己的 Mastra Agent再配合data-testid写一条“单卡片”e2e 断言就能得到一个行为可验证、不会随状态消息膨胀的 Agentic 进度界面。【免费下载链接】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),仅供参考
返回列表