实战:以 Agno 集成 Demo 为例)
基于工具调用的生成式 UITool-Based Generative UI实战以 Agno 集成 Demo 为例【免费下载链接】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 的生成式 UI 体系里除了Agent 自己定义状态、前端订阅渲染的gen-ui-agent模式还有一条更贴近工程直觉的路径Agent 在后台调用一个普通工具工具返回结构化数据前端拿到结果后把它渲染成自定义 React 组件而不是一段纯文本。本文基于仓库中 Agno 集成 下的 gen-ui-tool-based Demo 展开完整讲解这条工具结果 → 自定义组件的渲染链路并对比useRenderTool与useComponent两条 API 的异同。读完本文你将掌握如何在 CopilotKit Agno 的架构里注册可被模型调用的渲染工具、用 Zod schema 约束参数、处理流式状态以及用 e2e 测试验证整条链路。Demo 全貌一次看图说话的 Agent 对话先看这个 Demo 在界面上做了什么。打开/demos/gen-ui-tool-based路由页面没有多余 chrome只有一个居中、占满全屏宽度的CopilotChat聊天窗口下面挂了三颗建议药丸suggestion pills建议标题触发消息Sales bar chartShow me a bar chart of quarterly sales for Q1, Q2, Q3, Q4.Traffic pie chartShow me a pie chart of website traffic by source.Market shareShow a pie chart of smartphone market share by brand.用户点任意一颗Agent 就会在后台调用对应的图表工具前端随即在聊天流中插入一张自定义渲染的柱状图或饼图 SVG。这三条建议来自 suggestions.ts它直接调用了copilotkit/react-core/v2的useConfigureSuggestions并设置available: always让建议常驻显示。整个页面的布局与路由注册见 page.tsxCopilotKit runtimeUrl/api/copilotkit agentgen-ui-tool-based把前端指向 Next.js 侧的运行时路由CopilotChat agentIdgen-ui-tool-based声明当前对话使用的 Agent。这个gen-ui-tool-based是运行时里别名注册的一个 Agent 名称详见下文。核心机制useComponent如何把工具结果变成React 组件Demo 的前端注册逻辑非常精简整个Chat()组件只有三件事注册柱状图渲染器、注册饼图渲染器、挂载建议。注册代码位于 page.tsxuse client; import React from react; import { CopilotChat, CopilotKit, useComponent, } from copilotkit/react-core/v2; import { BarChart, barChartPropsSchema } from ./bar-chart; import { PieChart, pieChartPropsSchema } from ./pie-chart; import { useSuggestions } from ./suggestions; function Chat() { useComponent({ name: render_bar_chart, description: Display a bar chart with labeled numeric values., parameters: barChartPropsSchema, render: BarChart, }); useComponent({ name: render_pie_chart, description: Display a pie chart with labeled numeric values., parameters: pieChartPropsSchema, render: PieChart, }); useSuggestions(); return ( div classNameflex justify-center items-center h-screen w-full div classNameh-full w-full max-w-4xl CopilotChat agentIdgen-ui-tool-based classNameh-full rounded-2xl / /div /div ); }关键在useComponent。查看它的源码实现 packages/react-core/src/v2/hooks/use-component.tsx可以发现它其实是对useFrontendTool的一层封装export function useComponent(config: { name: string; description?: string; parameters?: TSchema; render: ComponentTypeNoInferInferRenderPropsTSchema; agentId?: string; followUp?: boolean; }, deps?: ReadonlyArrayunknown): void { const prefix Use this tool to display the ${config.name} component in the chat. This tool renders a visual UI component for the user.; const fullDescription config.description ? ${prefix}\n\n${config.description} : prefix; useFrontendTool({ name: config.name, description: fullDescription, parameters: config.parameters, render: ({ args }) { const Component config.render; return Component {...(args as InferRenderPropsTSchema)} /; }, agentId: config.agentId, followUp: config.followUp, }, deps); }useComponent会把组件的注册信息翻译成一条前端工具frontend tool声明工具名就是name模型可读的说明自动拼上Use this tool to display the xxx component in the chat前缀parameters即工具入参的 JSON Schema。当 Agent 决定调用render_bar_chart时模型会按照 schema 产出{ title, description, data }这类参数运行时把这些参数作为args透传给render最终BarChart {...args} /被挂载进聊天流实现工具调用结果 自定义组件。为什么模型能看到这个工具—— Agno 侧的前置条件值得特别说明的是CopilotKit 前端把组件注册成了 frontend tool但Agno 的 AG-UI 接口不会把请求里的工具定义转发给模型——模型只能看到声明在Agent上的工具。因此要让模型知道render_bar_chart的存在必须在 Agno 后端声明一个同名、同参数的工具。这组 Demo 复用的 Agno Agent 位于 src/agents/main.py其中定义了query_data等数据工具同名渲染工具的声明范式可以参考 docs/setup/frontend-tools-setup.mdx 给出的模式from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools import tool tool(external_executionTrue) def render_bar_chart(title: str, data: list[dict]): Render a bar chart in the chat. Call this whenever the user asks for a chart or a comparison. Args: title (str): A concise chart title. data (list[dict]): Items shaped {label, value}. agent Agent( modelOpenAIChat(idgpt-4o), dbdb, tools[render_bar_chart], instructionsSYSTEM_PROMPT, )这里有三个要点external_executionTrue告诉 Agno 该工具由外部浏览器执行。运行时Agno 会暂停这次 run把工具调用发回前端浏览器渲染完组件后run 再带着结果恢复。函数体可以留空因为真正执行它的是 React 侧。名称与参数必须和useComponent注册完全一致否则前端注册的渲染器无法被匹配。docstring 是模型唯一能读到的使用说明所以要在这里写清楚何时调用如Call this whenever the user asks for a chart or a comparison和每个参数的语义。暂停与恢复外部工具必须有 DBexternal_executionTrue带来一个容易被忽略的部署前提Agno 在把 run 暂停、等待浏览器返回结果之前需要把 run 持久化。若声明了外部工具却不给Agent配数据库浏览器应答后 run 将无法恢复。官方示例使用 SqliteDb 作为本地开发存储from agno.db.sqlite import SqliteDb db SqliteDb(db_filetmp/agno.db)SqliteDb需要安装sqlalchemy生产环境建议改用可共享的持久化存储如PgDb因为临时容器里的本地文件无法在另一个实例上恢复一次被暂停的 run。这条限制同样适用于 Demo 中用到的其他external_execution工具例如 main.py 里的manage_sales_todos、request_user_approval、change_background、book_call、generate_task_steps等。渲染器组件用 Zod schema 约束工具结果的形状useComponent的parameters字段决定了模型产出参数的强类型形状。Demo 的柱状图与饼图分别用 Zod 定义了几乎相同的 schemabar-chart.tsx 与 pie-chart.tsximport { z } from zod; export const barChartPropsSchema z.object({ title: z.string().describe(Chart title), description: z.string().describe(Brief description or subtitle), data: z.array( z.object({ label: z.string(), value: z.number(), }), ), }); export type BarChartProps z.infertypeof barChartPropsSchema;这个 schema 同时承担两个职责约束模型输出模型调用render_bar_chart时工具参数必须形如{ title: string, description: string, data: [{ label: string, value: number }] }字段语义由.describe()提示推导组件 Props 类型z.infer直接给出BarChartProps类型组件内部拿到的是类型安全的title / description / data。空数据兜底两个图表组件都做了空数据兜底当data缺失、非数组或长度为 0 时渲染一张带标题、描述和 No data available 占位文案的卡片bar-chart.tsx、pie-chart.tsx。这让渲染器对模型没按预期产出数据的场景依然能优雅展示而不是抛错或渲染空白。柱状图Recharts 新条目的滑入动画柱状图组件bar-chart.tsx基于 Recharts 构建ResponsiveContainer自适应宽度、CartesianGrid画横向虚线网格、XAxis/YAxis设置刻度字号与颜色、Tooltip定制白底圆角样式。有趣的是它的入场动画处理——用一个useRef集合记录已经渲染过的条形索引只有新到达的条形才触发barSlideIn动画从translateY(40px)opacity: 0滑入避免每次数据流更新时整张图都重播动画const seen useRef(new Setnumber()); const isNew (i: number) { if (seen.current.has(i)) return false; seen.current.add(i); return true; };搭配CHART_COLORS七色调色板按索引取色isAnimationActive{false}关闭 Recharts 自带动画、用自定义 shape 包裹Rectangle实现逐条滑入。这种写法很适合流式数据场景Agent 的数据是分片到达的组件需要区分首帧和增量更新。饼图纯 SVG 手写的环形图饼图组件pie-chart.tsx没有依赖图表库直接手写 SVG用2 * Math.PI * radius计算周长每片扇形通过strokeDasharray与strokeDashoffset组合成甜甜圈切片transform: scaleX(-1)反转方向、rotate(-90)让第一片从顶部开始。图下方还渲染了图例列表每项包含色点、标签、数值toLocaleString()千分位和百分比。这证明工具结果 → 自定义组件的能力并不绑定某个图表库——任何能接收结构化 props 的 React 组件都可以成为渲染器。与useRenderTool的对比两种工具结果上屏路径Demo 的 README 提到了useRenderTool这是 CopilotKit 中更通用、更细粒度的工具渲染 API。它与useComponent的分工如下useRenderTool工具渲染按工具名注册 rendererrenderer 接收args、result、status三个维度从而展示inProgress / executing / complete等不同状态use-render-tool.tsx 的源码注释给出了完整示例。它面向的是后端真实执行的工具如get_weather、search_flights前端只是把调用过程和结果可视化出来。useComponent生成式 UI 组件把组件本身注册成一个由前端执行的外部工具模型按 schema 产出参数、前端把参数作为 props 渲染组件。它面向的是生成 UI这个动作本身README 中描述的args / result / status在useComponent的视角下args即组件 props组件内部自行表达加载与完成态。在 Agno 集成里这两者都有对应 DemouseRenderTool的范式见 tool-rendering Demo含stock-card、flight-list-card等按工具名注册的渲染器useComponent的范式就是本文的gen-ui-tool-based。此外仓库还提供了 headless-complete Demo它用useRenderToolCall在完全自定义的聊天 UI 里手动组合生成式 UI。三者在 manifest.yaml 中分别被标记为tool-rendering、gen-ui-tool-based与headless-complete特性方便横向对比。注useRenderTool的parameters接受任何 Standard Schema V1 兼容库Zod、Valibot、ArkType 等useComponent的parameters同样遵循这一约定仓库示例统一使用 Zod。运行时接线gen-ui-tool-based如何被路由到 Agno Agent前端runtimeUrl/api/copilotkit指向的 Next.js 运行时路由位于 src/app/api/copilotkit/route.ts。它通过 AG-UI 协议把 CopilotKit 请求代理到独立的 Agno 后端进程默认http://localhost:8000可用AGENT_URL覆盖const AGENT_URL process.env.AGENT_URL || http://localhost:8000; function createMainAgent() { return new HttpAgent({ url: ${AGENT_URL}/agui }); } // gen-ui-tool-based 被别名到 main agent const mainAgentNames [ // ... tool-rendering, gen-ui-tool-based, // ... ]; for (const name of mainAgentNames) { agents[name] createMainAgent(); }这里的关键设计是同一个 AgnomainAgent 通过别名注册了多个前端 Agent 名称。gen-ui-tool-based只是mainAgentNames数组里的一个字符串最终与tool-rendering、prebuilt-sidebar、headless-complete等名称一样都指向createMainAgent()生成的同一个HttpAgent。运行时用createCopilotRuntimeHandler({ runtime, basePath: /api/copilotkit, mode: single-route })注册为单一 POST 路由另有一个 GET 健康检查接口返回agent_status、OPENAI_API_KEY是否设置等诊断信息。这样每个 Demo 单元格的前端工具/组件注册如render_bar_chart都能在各自的 agentId 作用域下正确生效。e2e 测试如何验证图表确实被渲染进聊天流这条链路不是只靠人肉验证。仓库提供了针对该 Demo 的 Playwright e2e 测试 tests/e2e/gen-ui-tool-based.spec.ts覆盖了三类断言页面装配进入/demos/gen-ui-tool-based后聊天输入框可见且三颗建议药丸Sales bar chart、Traffic pie chart、Market share都以data-testidcopilot-suggestion的形式出现生成式 UI 真的上屏分别发送 Show me a pie chart of revenue by category 与 Show me a bar chart of monthly expenses断言copilot-assistant-message容器内的svg可见60 秒超时兼容 Agent 生成耗时基础对话发送 Hello 后断言 assistant 消息可见。这套测试从用户视角证明了模型理解自然语言 → 选择图表工具 → 产出符合 schema 的参数 → 前端把结果渲染成 SVG 组件的完整闭环。QA 手册 qa/tool-rendering.md 还针对useRenderTool风格的工具卡片列出了更细的验收清单加载态、天气卡片字段、主题色等可作为扩展参考。小结Tool-Based Generative UI 的落地要点在 CopilotKit Agno 中落地工具结果 → 自定义组件的生成式 UI需要同时握住四根线前端注册useComponent({ name, description, parameters, render })把组件声明为一个可由模型调用的前端工具参数 schema 同时约束模型输出与组件 Props 类型后端配对在 AgnoAgent上声明同名、同参的tool(external_executionTrue)渲染工具docstring 写清调用时机外部工具需要给 Agent 配 DB开发用SqliteDb生产用持久化存储才能完成暂停 → 浏览器渲染 → 恢复运行时接线在 copilotkit/route.ts 把gen-ui-tool-based等名称别名到后端 Agent前端CopilotKit agent...与CopilotChat agentId...需与之一致可验证性用 e2e 测试断言自然语言 → SVG 组件上屏让整条链路可回归、可引用。如果想继续深入可以对照阅读 tool-rendering Demo按工具名注册 renderer、展示加载/完成态和 headless-complete Demo在自定义聊天 UI 里手动组合生成式 UI三种方案覆盖了从开箱即用到完全掌控的完整光谱。【免费下载链接】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),仅供参考