CopilotKit × Google ADK 声明式 UI 渲染实战:JSON Render 演示的架构与 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 与 Google ADK 集成演示中的declarative-json-render(BYOC json-render)场景展开,系统讲解如何让 ADK Agent 以结构化 JSON 描述 UI(组件树 + 属性),再由前端@json-render/react渲染器把流式 JSON 实时"翻译"成可交互的 React 组件。你将掌握该演示的完整架构链路、前置条件、逐步骤 QA 验收清单与预期结果,并通过源码级分析理解流式解析、组件白名单校验、错误回退等底层实现原理,可直接复用到你自己的 BYOC(Bring Your Own Component)声明式 UI 项目中。
一、场景定位:什么是 BYOC json-render 演示
在 showcase/integrations/google-adk 中,CopilotKit 与 Google ADK 后端协作,实现了一种"让大模型输出 JSON、前端按 JSON 渲染组件"的声明式 UI 模式。byoc_json_render是其中的一个 Agent 端点,它对应演示页面/demos/declarative-json-render,其 QA 验收文档位于 qa/declarative-json-render.md。
与传统的"模型返回纯文本"不同,该演示中 ADK Agent 的输出是一个包含{ root, elements }扁平元素表的 JSON spec:@json-render/react的<Renderer />在 JSON 解析完成后将其绘制为真实组件。这是一种典型的BYOC思路——不在前端硬编码每种 UI 形态,而是用受控的组件目录(catalog)约束模型可生成的组件类型,让 Agent 动态"拼装"仪表盘。
注:ADK 集成中
byoc_json_render与byoc-hashbrown共享同一个byoc_agent实例,Agent 的响应同时携带两种线格式(ui[]数组与root/elements映射),各自前端渲染器只提取自己关心的键。详见 byoc_agents.py。
二、前置条件
依据 QA 文档,运行与验证该演示需要满足以下条件:
| 前置项 | 说明 |
|---|---|
| 演示部署 | 演示页面部署在/demos/declarative-json-render |
| ADK Agent 后端 | 在${AGENT_URL}/byoc_json_render可访问且健康 |
| 环境变量 | Next.js 应用中配置GOOGLE_API_KEY与AGENT_URL |
| 前端依赖 | @json-render/core+@json-render/react已加入package.json(该仓库锁定为0.18.0,见 package.json) |
| Agent 注册 | byoc_json_render在src/agents/registry.py中注册,映射到byoc_agent |
后端启动方式:package.json的dev脚本使用concurrently同时启动 Next.js(next dev --turbopack)与 uvicorn 的 ADK FastAPI 服务(默认监听0.0.0.0:8000,支持PORT环境变量覆盖,见 agent_server.py):
npm run devGOOGLE_API_KEY用于驱动 Gemini 模型(DEFAULT_MODEL = "gemini-3.1-flash-lite",见 shared_chat.py)。若未设置,entrypoint.sh 默认仅告警,但 Gemini 相关的聊天能力会在请求时返回结构化错误;设置REQUIRE_GOOGLE_API_KEY=1可改为启动即失败(fail-fast)。
三、前端接线:从 CopilotKit 到 json-render 渲染器
3.1 页面与 Runtime 挂载
演示页 page.tsx 使用<CopilotKit>包裹,runtimeUrl指向专用API 路由/api/copilotkit-declarative-json-render,并绑定agent="byoc_json_render":
<CopilotKit runtimeUrl="/api/copilotkit-declarative-json-render" agent={AGENT_ID} >这条专用路由 route.ts 与默认的多 Agent/api/copilotkit运行时隔离,通过@ag-ui/client的HttpAgent把请求转发到 Python 侧 ADK 端点:
const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; const byocJsonRenderAgent = new HttpAgent({ url: `${AGENT_URL}/byoc_json_render`, headers, // 透传 x-aimock-context 等入站请求头 }); const runtime = new CopilotRuntime({ agents: { byoc_json_render: byocJsonRenderAgent }, });3.2 聊天组件与自定义 Assistant 消息视图
chat.tsx 渲染<CopilotChat>,并通过messageView.assistantMessage把默认的助手气泡替换为自定义的JsonRenderAssistantMessage:
<CopilotChat agentId={AGENT_ID} messageView={{ assistantMessage: JsonRenderAssistantMessage }} />这意味着每条助手消息都会先经过自定义渲染器:若内容能解析为合法的 json-render spec,就渲染组件树;否则回退到默认气泡。
3.3 建议提示(Suggestion Pills)
suggestions.ts 通过useConfigureSuggestions注册三条建议,available: "always"表示欢迎屏始终展示:
export const BYOC_JSON_RENDER_SUGGESTIONS = [ { title: "Sales dashboard", message: "Show me the sales dashboard with metrics and a revenue chart" }, { title: "Revenue by category",message: "Break down revenue by category as a pie chart" }, { title: "Expense trend", message: "Show me monthly expenses as a bar chart" }, ];这三条建议正是 QA 文档 Step 1 中期望出现的三个标题,与 E2E 测试 declarative-json-render.spec.ts 断言一一对应。
四、QA 验收步骤详解
以下步骤完全继承 QA 文档的验收清单,并补充了可验证的源码证据。
Step 1:页面加载
- 导航至
/demos/declarative-json-render。 - 聊天输入框(chat composer)可见。
- 欢迎屏出现三个建议提示:"Sales dashboard"、"Revenue by category"、"Expense trend"(数据源见上文
suggestions.ts)。 - 控制台无报错。
E2E 对应断言:page.locator('textarea, [placeholder*="message"]').first()可见(10 秒超时),三个建议标题均可见。
Step 2:Sales dashboard 建议
点击 "Sales dashboard" 后:
- 60 秒内,助手气泡内出现
data-testid="json-render-root"包裹层。 - 包裹层内渲染
data-testid="metric-card"(KPI 指标卡)。 - 包裹层内渲染图表(
data-testid="bar-chart"或data-testid="pie-chart")。 - 渲染完成后不展示任何原始 JSON 文本——流式 JSON 被组件替换。
json-render-root由 json-render-renderer.tsx 输出;metric-card与图表分别由 metric-card.tsx 与 charts/bar-chart.tsx(以及 pie-chart)渲染。
Step 3:Revenue by category
点击 "Revenue by category" 后,60 秒内出现data-testid="pie-chart",包含多个分类扇区与图例(legend)。对应 E2E:spec 第 57-65 行。
Step 4:Expense trend
点击 "Expense trend" 后,60 秒内出现data-testid="bar-chart",X 轴带月份标签(month labels)。
Step 5:自由表单提示
输入 "Show me a metric for quarterly revenue" 并发送:
- 至少渲染一个
metric-card; - 控制台无报错。
这一步验证模型在未命中预设建议时,仍能按 prompt 约束输出合法 spec。
Step 6:多轮对话
- 在上一轮渲染可见后,发送后续提示(如 "Now break that down by region")。
- 出现新的助手消息与新的 json-render 渲染,且之前的渲染保留在对话记录中。
这验证了多轮上下文中每条消息的独立渲染与历史留存。
Step 7:格式错误输出处理
- 若 Agent 偶尔返回非 JSON 文本(如追问 "tell me a joke" 强制触发),聊天应回退为默认助手气泡渲染纯文本。不允许崩溃或卡在加载转圈。
这正是渲染器中的兜底逻辑(见下文"错误回退机制")。
五、预期结果与验收基准
QA 文档明确了三项核心预期:
- 渲染时限:建议触发后在 60 秒内完成渲染。预算略高于 hashbrown 演示——因为 JSON
{ root, elements }spec 比 hashbrown 的 token 流更冗长(结构更 verbose)。 - 零未捕获异常:控制台无 uncaught errors。
- 流式降级:在 JSON 尚未解析完成前,流式内容回退为纯文本;一旦 JSON 解析成功,立即切换为渲染组件。
对应地,Playwright E2E 测试 为第 2、3、4 步都设置了 60000ms 超时断言,与文档 60 秒基准一致。
六、源码级原理:流式 JSON 如何变成 UI
6.1 解析管线:parseSpec与extractJsonObject
json-render-renderer.tsx 是核心:
extractJsonObject(raw)(第 70-99 行):剥离可能的代码围栏(json ... ),然后做花括号配平扫描——逐字符跟踪{/}深度、字符串状态与转义,返回第一个平衡的完整 JSON 对象字符串。这是对"模型输出夹杂散文或代码围栏"的容错处理。parseSpec(content)(第 43-67 行):对提取到的文本执行JSON.parse,然后校验结构:root必须是字符串;elements必须是对象;root必须指向elements中存在的键;- 每个元素必须有字符串
type,且type 必须在ALLOWED_TYPES白名单(MetricCard、BarChart、PieChart)内; props若存在必须是对象。
任一环节失败即返回null,渲染器随即回退到默认气泡(if (!spec) return <CopilotChatAssistantMessage {...props} />)。这就是 Step 7"坏输出不崩溃"的实现保障。
6.2 组件目录:catalog与 zod 模式
catalog.ts 用@json-render/core的defineCatalog声明模型可用的三种组件及其属性模式(zod schema):
MetricCard:label: string、value: string、trend: string | null;BarChart:title、description: string | null、data: Array<{label, value}>;PieChart:与 BarChart 相同的结构。
每个组件还配有description,用于在模型侧描述组件用途(如 PieChart 用于"把总量拆分为分类扇区")。
6.3 组件注册表:registry
registry.tsx 通过defineRegistry把目录中的组件名映射到真实 React 实现。其中MetricCard的实现会转发 children——这对应elements中的children数组,使 Agent 可以把图表嵌套在指标卡之下(一个root为 MetricCard、children引用 PieChart/BarChart 的仪表盘树):
MetricCard: ({ props, children }) => ( <div className="flex w-full flex-col items-stretch gap-3"> <MetricCard {...(props as MetricCardComponentProps)} /> {children} </div> ),spec 的类型定义见 types.ts:root: string、elements: Record<id, { type, props, children?: string[] }>。
6.4 ADK Agent:如何保证输出合法 JSON
Python 侧 byoc_agents.py 定义byoc_agent = LlmAgent(...),关键配置:
instruction=_BYOC_SYSTEM_PROMPT:统一 prompt 详细规定了ui[]与root/elements双格式的组件名、属性、示例响应(Sales dashboard 示例),并要求"不允许代码围栏、不允许前言、必须整体是合法 JSON";tools=[]:不挂任何后端工具,全部仪表盘数据由模型内联生成,前端流式 JSON 解析器可渐进重建 UI;generate_content_config=types.GenerateContentConfig(response_mime_type="application/json", temperature=0.2):Gemini 侧强制 JSON 对象输出模式,temperature=0.2在保证模式遵守的同时保留样本数据的变化;after_model_callback=stop_on_terminal_text:见 shared_chat.py,该回调仅在最终完整文本轮且finish_reason=STOP时终止 Agent 循环,避免流式中间分片或带 function_call 的混合响应导致提前结束。
6.5 后端注册与端点挂载
registry.py 中:
"declarative-hashbrown": AgentSpec(byoc_agent), "byoc_json_render": AgentSpec(byoc_agent),agent_server.py 遍历注册表,为每个 agent 名在/<agent_name>挂载 ADK FastAPI 端点——于是${AGENT_URL}/byoc_json_render即byoc_json_render的可达 HTTP 地址,与前端HttpAgent的 URL 拼接逻辑闭环。
七、常见验收陷阱与排查建议
- 60 秒超时仍未渲染:优先检查
AGENT_URL是否指向健康的 ADK 服务、GOOGLE_API_KEY是否有效;其次查看网络面板确认/api/copilotkit-declarative-json-render的 SSE 流是否持续输出。 - 渲染出的是纯文本而非组件:说明
parseSpec返回了null,多为模型输出的 JSON 中出现了白名单之外的type、root指向不存在的元素 id,或 JSON 本身畸形。可打开控制台观察是否有 spec 校验相关的告警。 - 控制台报错但页面可用:注意
stop_on_terminal_text对 Gemini 混合 text+function_call 响应的保护逻辑;若你自定义了 Agent,请保留该回调以规避"无限调用工具"或"提前终止"两类问题。
八、总结
通过本文你可以清晰地看到一条完整链路:ADK Agent(byoc_agent,强制 JSON 输出)→ FastAPI 端点/${AGENT_URL}/byoc_json_render→ CopilotKit 专用 Runtime 路由 → 自定义JsonRenderAssistantMessage→parseSpec白名单校验 →@json-render/react的<Renderer />绘制组件树。QA 文档中的七步验收覆盖了页面加载、三类建议、自由表单、多轮与坏输出回退,而仓库中的 catalog.ts、registry.tsx 与 byoc_agents.py 则是支撑这些验收的底层实现——理解它们,你就能把"Agent 输出 JSON、前端渲染组件"的 BYOC 模式迁移到自己的产品中。
【免费下载链接】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),仅供参考