CopilotKit Shared State 实战在 Agno 集成中实现 Agent 只读共享状态Shared State Read【免费下载链接】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 与 Agno 集成中的Shared StateReading演示讲解如何让前端通过useAgent().state发布共享状态使后端 Agent 在每一轮对话中直接读取该状态并据此回答用户问题而无需前端将状态作为上下文反复发送。读完本文你将掌握agent.setState/agent.state的完整调用链、类型化AgentStateschema 的定义方式以及状态在后端 AG-UI 协议中的传输原理并能照此在自己的 CopilotKit Agno 项目中落地UI 拥有数据、Agent 只读感知的架构。一、这个 Demo 要解决什么问题在常规的 RAG / Chat 应用中前端要想让 Agent 感知当前界面状态比如表单内容、待办列表、菜谱编辑到一半的数据通常需要把状态序列化后塞进每次对话的上下文里既浪费 token又容易造成前后端状态不一致。CopilotKit 的 Shared State 机制改变了这一模式前端把状态写入 Agent 的共享状态空间后端 Agent 通过runtime.state直接查询当前应用数据。shared-state-read这个 Demo 演示的是其中的只读方向——前端负责维护和写入状态UI 是单一事实来源 single source of truthAgent 只负责读取和基于状态作答不修改它。按 manifest.yaml 的登记shared-state-read是 Agno 集成的agent-stateAgent 状态能力族演示之一与shared-state-read-write双向读写、readonly-state-agent-context只读上下文形成对照。二、Demo 的整体架构┌─────────────────────────────┐ AG-UI (HTTP) ┌──────────────────────────────┐ │ Next.js 前端 (React) │ ────────────────────▶ │ Agno 后端 (FastAPI/uvicorn) │ │ CopilotKit Runtime │ /api/copilotkit 代理 │ agent_server.py │ │ useAgent().state │ ◀──────────────────── │ session_state / runtime.state│ │ agent.setState({recipe}) │ StateSnapshotEvent │ /agui (main agent) │ └─────────────────────────────┘ └──────────────────────────────┘前端通过 page.tsx 中的CopilotKit runtimeUrl/api/copilotkit agentshared-state-read挂载运行时并指定后端代理的 Agent 名。前端页面组件菜谱编辑器与右侧CopilotSidebar标题 AI Recipe Assistant共享同一个agent实例因此侧边栏中 Agent 的回答能感知左侧表单的实时状态。三、前端如何发布共享状态3.1 挂载 Agent 并订阅状态更新在 page.tsx 中Recipe组件通过useAgent拿到 agent 句柄并订阅两类更新const { agent } useAgent({ agentId: shared-state-read, updates: [UseAgentUpdate.OnStateChanged, UseAgentUpdate.OnRunStatusChanged], });OnStateChanged当后端产生新的状态快照StateSnapshotEvent或前端写入新状态时触发重渲染OnRunStatusChanged让agent.isRunning生效用于控制按钮的加载态。3.2 首次注入初始状态组件挂载时若agent.state中还没有recipe则通过agent.setState写入初始数据见 page.tsxuseEffect(() { if (!(agent.state as RecipeAgentState | undefined)?.recipe) { agent.setState({ recipe: INITIAL_RECIPE } satisfies RecipeAgentState); } }, []);这是只读方向的关键一步前端先把菜谱种子数据发布进共享状态Agent 在第一轮对话时才有内容可读。之后用户在表单上的每一次编辑都会通过handleChange继续调用agent.setState({ recipe: next })覆盖状态。3.3 状态驱动渲染与手动运行 Agent组件直接以agent.state作为渲染数据源page.tsxconst recipe (agent.state as RecipeAgentState | undefined)?.recipe ?? INITIAL_RECIPE; const handleChange (next: RecipeData) { agent.setState({ recipe: next } satisfies RecipeAgentState); };代码注释点明了这一设计的本质单一事实来源agent.state.recipe——表单是一个构建在该状态之上的纯受控组件pure controlled component每次编辑直接流入agent.setState下一次渲染立即反映。UI 不再是状态的副本持有者而是状态的视图。Improve with AI 按钮则演示了无输入框的手动运行方式page.tsxagent.addMessage({ id: crypto.randomUUID(), role: user, content: Improve the recipe }); void copilotkit.runAgent({ agent });3.4 起步建议SuggestionsuseConfigureSuggestions为侧边栏提供了三条预置指令page.tsxCreate Italian recipe → Create a delicious Italian pasta recipe.Make it healthier → Make the recipe healthier with more vegetables.Suggest variations → Suggest some creative variations of this recipe.四、类型化 AgentState前后端共享的契约共享状态不是松散的对象而是由类型化 schema 定义的。该 Demo 的状态契约位于 types.ts其核心结构如下export interface RecipeData { title: string; skill_level: SkillLevel; // BEGINNER / INTERMEDIATE / ADVANCED cooking_time: CookingTime; // 5 min / 15 min / 30 min / 45 min / 60 min special_preferences: string[]; // High Protein / Low Carb / Spicy / ... ingredients: Ingredient[]; // { icon, name, amount } instructions: string[]; } export interface RecipeAgentState { recipe: RecipeData; } export const INITIAL_RECIPE: RecipeData { title: Make Your Recipe, skill_level: SkillLevel.INTERMEDIATE, cooking_time: CookingTime.FortyFiveMin, special_preferences: [], ingredients: [ { icon: , name: Carrots, amount: 3 large, grated }, { icon: , name: All-Purpose Flour, amount: 2 cups }, ], instructions: [Preheat oven to 350°F (175°C)], };要点RecipeAgentState是 Agent 状态空间的顶层 schemarecipe是其中的一个字段枚举值烹饪时长、难度、饮食偏好以字符串常量定义保证写入状态的值与 UI 下拉选项一一对应INITIAL_RECIPE同时承担种子数据和渲染兜底值双重职责?? INITIAL_RECIPE状态契约只在前端定义即可——后端 Agno Agent 并不需要严格的 Python 类型绑定因为状态通过 AG-UI 的 JSON 载荷传输session_state在 Python 侧是动态字典。五、后端 Agent 如何读取共享状态5.1 路由层Agent 名映射到后端端点前端请求经 Next.js 的 route.ts 转发到 Agno 后端默认http://localhost:8000可用环境变量AGENT_URL覆盖。shared-state-read属于mainAgentNames数组route.ts会被别名到默认的mainAgentconst mainAgentNames [ // ... shared-state-read, // ... ]; agents[name] createMainAgent(); // new HttpAgent({ url: ${AGENT_URL}/agui })也就是说这个只读 Demo 复用的是无工具tools[]的中性默认 Agent它没有任何修改状态的能力天然满足只读约束。5.2 状态如何进入 Agent 运行后端 agent_server.py 的 AG-UI 处理器会在每次运行前调用validate_agui_state(run_input.state, thread_id)校验前端传来的状态并将其作为session_state传给agent.arun(...)session_state validate_agui_state(run_input.state, thread_id) or {} response_stream agent.arun( inputuser_input, session_idthread_id, streamTrue, stream_eventsTrue, user_iduser_id, session_statesession_state, # ← 前端 setState 的状态在此进入 Agent run_idrun_id, )在 Agno 的RunContext中这个字典就是文档所述的runtime.state——Agent 侧通过run_context.session_state即可读取前端发布的所有字段。5.3 Agent 读取状态的代码范式虽然本 Demo 复用中性 Agent仓库中的 shared_state_read_write.py 给出了 Agent 端读取共享状态的标准范式同属共享状态能力族可对照学习def _format_preferences(prefs: Any) - str: if not isinstance(prefs, dict) or not prefs: return lines [PREFS_BLOCK_HEADER] # [shared-state-read-write] preferences: if prefs.get(name): lines.append(f- Name: {prefs[name]}) # ... tone / language / interests ... return \n.join(lines) def build_instructions(run_context: RunContext) - str: prefs_block _format_preferences( getattr(run_context, session_state, None) or {} ) if prefs_block: return f{prefs_block}\n\n{base} return base关键实现细节Agent 的instructions被设置为动态函数并配合cache_callablesFalse使得 Agno 在每一轮运行都重新求值 instructions从而让前端agent.setState的写入在下一轮立即对 LLM 可见而不是在 Agent 构造时缓存死。这正是Agent 能回答当前 UI 状态问题、无需前端把状态作为上下文发送的底层机制。5.4 状态快照回传Agno 官方的 AG-UI 路由器默认不会向客户端回传StateSnapshotEvent这会让依赖OnStateChanged订阅的读-写循环断开。为此仓库在 agent_server.py 中实现了_run_agent_with_state_snapshot复制官方路由行为在内部流结束、RunFinishedEvent之前插入一条携带最终session_state的StateSnapshotEvent并通过agent.aget_session_state读取合并后的会话库状态。前端useAgent收到该快照后触发OnStateChanged完成UI 写入 → Agent 读取 → 状态快照回传 → UI 同步的闭环。六、UI 层的受控组件与可测试性recipe-card.tsx 是整个表单的渲染实现它不持有任何本地状态所有变更都向上冒泡update(partial)合并{ ...recipe, ...partial }后调用onChange→agent.setState配料行通过updateIngredient(index, field, value)做不可变更新支持增删步骤区通过updateInstruction编辑支持增删组件暴露了data-testidrecipe-card、add-ingredient-button、ingredients-container、ingredient-card、instructions-container、improve-button等测试锚点为 E2E 验证提供稳定选择器。七、如何运行与验证7.1 启动 Demo后端与前端由一条命令同时启动见 package.jsonconcurrently next dev --turbopack PYTHONPATH. python -m uvicorn agent_server:app --host 0.0.0.0 --port 8000 --reloadNext.js 前端默认跑在 3000 端口页面路由为/demos/shared-state-readAgno 后端跑在 8000 端口AG-UI 端点为/agui前端通过AGENT_URL默认http://localhost:8000代理到后端需要配置OPENAI_API_KEYmainAgent 使用OpenAIChat(idgpt-4o)。7.2 交互验证清单按 qa/shared-state-read.md 的手工 QA 契约验证重点包括初始状态标题显示 Make Your Recipe、烹饪时长默认 45 min、难度默认 Intermediate、默认配料Carrots / All-Purpose Flour与默认步骤 Preheat oven to 350 F本地编辑改标题、切难度/时长、切换饮食偏好、增删配料与步骤表单即时更新AI 感知状态在侧边栏问 What recipe am I making?Agent 的回答应引用当前表单状态AI 回写点击 Create Italian recipe 建议后Agent 更新菜谱的标题、配料与步骤并在被变更的区块显示 ping 指示器运行态运行中 Improve with AI 按钮变为 Please Wait... 且禁用。7.3 自动化 E2E 验证仓库提供了对应的 Playwright 测试 tests/e2e/shared-state-read.spec.ts覆盖四类断言菜谱卡片recipe-card加载、侧边栏AI Recipe Assistant挂载三条起步建议按钮渲染点击add-ingredient-button后ingredient-card行数 1在侧边栏发送 What recipe am I making? 后能收到 assistant 消息。运行方式npm run test:e2e项目使用 Playwright见 package.json。八、只读模式与读写模式的边界该 Demo 刻意保持只读定位与同族的 shared-state-read-write 形成对比维度shared-state-read本文shared-state-read-write前端写入是agent.setState({recipe})是setState({preferences})Agent 写入否中性 Agent无状态写入工具是set_notes工具替换session_state[notes]状态流向UI → Agent单向感知UI ⇄ Agent双向同步状态快照随 Agent 运行回传依赖自定义 AGUI 路由的StateSnapshotEvent从架构视角看当界面数据必须由用户交互独占控制、Agent 只能基于它作答时采用只读模式最安全当Agent 需要把推理产物如笔记、委托记录写回界面时则升级到读写模式并需要后端以set_notes这类工具 状态快照回传来闭环。九、实战要点总结状态即事实来源把agent.state当作渲染数据源所有编辑通过agent.setState写入避免前端再维护一份副本类型先行用 TypeScript 接口 枚举定义AgentState保证状态值与 UI 选项、Agent 可读性三方一致种子数据首次挂载用useEffect注入INITIAL_RECIPE保证 Agent 首轮即有内容可读订阅必要更新按需选择OnStateChanged/OnRunStatusChanged前者驱动状态同步后者驱动 loading 态后端动态 instructions如需 Agent 每轮感知最新状态将 instructions 设为函数并关闭cache_callables状态快照回传是闭环前提Agno 官方 AG-UI 不主动回传StateSnapshotEvent实现读写闭环需按 agent_server.py 的方式在RunFinishedEvent前注入快照。通过 Shared StateReading模式你可以让 Agent看着屏幕回答问题而前端代码只需要维护一份状态、注册一次 setState剩下的感知与应答逻辑全部由 CopilotKit 与 Agno 运行时接管。【免费下载链接】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),仅供参考