ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CopilotKit 双向共享状态(Read + Write)实战:LangGraph Python 偏好注入与 Agent 笔记回写全解析

CopilotKit 双向共享状态(Read + Write)实战:LangGraph Python 偏好注入与 Agent 笔记回写全解析 CopilotKit 双向共享状态Read Write实战LangGraph Python 偏好注入与 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 仓库中langgraph-python集成演示的 Shared State (Read Write) Demo 为主线完整拆解 UI 与 Agent 之间双向读写同一状态对象的前后端实现前端如何用useAgent(...)订阅状态变化、用agent.setState(...)写入偏好后端如何用 LangChain 中间件把 UI 写的状态注入系统提示词、用set_notes工具把 Agent 的观察回写到 UI。读完后你将掌握在 CopilotKit LangGraph Python 栈中实现UI 写、Agent 读Agent 写、UI 读双向共享状态的完整套路与验证方法。1. Demo 要解决的问题双向共享状态该演示位于 shared-state-read-write/README.md部署后对应路由/demos/shared-state-read-write见 manifest.yaml 中的route: /demos/shared-state-read-write配置。它演示的是 UI 与 Agent 之间的双向共享状态——两侧都读写同一个状态对象UI → Agent写侧边栏偏好表单姓名、语气、语言、兴趣通过agent.setState(...)写入state.preferences后端中间件每轮读取该字段并注入系统提示词。Agent → UI读Agent 的set_notes工具写入state.notes侧边栏笔记卡片在 Agent 每次更新时自动重渲染。往返闭环Round-trip在侧边栏编辑偏好可以直观地改变 Agent 下一条回复的语气、语言甚至让它用你的名字称呼你。状态对象由两个切片构成见 page.tsx 中的RWAgentState定义// - preferences is WRITTEN by the UI via agent.setState(). // - notes is WRITTEN by the agent via its set_notes tool and READ // by the UI via useAgent(). interface RWAgentState { preferences: Preferences; // UI 写、Agent 读 notes: string[]; // Agent 写、UI 读UI 也可写回清空 }其中Preferences的数据模型preferences-card.tsx为export interface Preferences { name: string; tone: formal | casual | playful; language: string; interests: string[]; }2. 前端实现一个 agentId 打通读写两条链路2.1 页面骨架与 CopilotKit Provider入口页 page.tsx 用CopilotKitProvider 声明运行时地址与绑定的 agentCopilotKit runtimeUrl/api/copilotkit agentshared-state-read-write DemoContent / /CopilotKitagent属性值为shared-state-read-write它对应后端在 route.ts 中注册的一行agents[shared-state-read-write] createAgent(shared_state_read_write);而后端图本身则通过 langgraph.json 暴露给 LangGraph 部署shared_state_read_write: ./src/agents/shared_state_read_write.py:graph2.2 读方向useAgent订阅每一次状态变更页面核心逻辑集中在DemoContent组件。读取侧只有一处关键调用page.tsx#L44-L47const { agent } useAgent({ agentId: shared-state-read-write, updates: [UseAgentUpdate.OnStateChanged], });updates: [UseAgentUpdate.OnStateChanged]使组件订阅 Agent 的每一次状态变更。随后把agent.state解构为类型化的视图数据const agentState agent.state as RWAgentState | undefined; const preferences agentState?.preferences ?? INITIAL_PREFERENCES; const notes agentState?.notes ?? [];只要 Agent 通过set_notes工具修改了state.notes这个 hook 就会触发重渲染侧边栏面板随即反映新值。CopilotKit、useAgent、UseAgentUpdate均从copilotkit/react-core/v2导入。2.3 写方向agent.setState统一入口所有 UI 侧写入都走同一个agent.setState调用覆盖两类场景page.tsx#L59-L871表单编辑 → 写偏好。注意实现里同时保留了 Agent 已写入的notes避免 UI 写偏好时把 Agent 的笔记覆盖掉const handlePreferencesChange (next: Preferences) { agent.setState({ preferences: next, notes, // preserve what the agent has written } as RWAgentState); };源码注释点明了其效果在 Agent 下一轮PreferencesInjectorMiddleware会把这个对象从状态中读回并加入系统提示词——UI 的写入由此看得见地引导模型行为。2清空笔记 → 写回 Agent 撰写的切片。这是对同一字段的双向演示notes既由 Agent 写也可由 UI 写回const handleClearNotes () { agent.setState({ preferences, notes: [] } as RWAgentState); };3首次会话播种。页面用一次性useEffect把初始偏好和空笔记写入 Agent 状态保证 Agent 在第一轮就有东西可读useEffect(() { if (!agentState?.preferences) { agent.setState({ preferences: INITIAL_PREFERENCES, notes: [], } as RWAgentState); } }, []);这意味着共享状态是会话级的刷新页面后偏好回到默认值tone: casual、language: English、空姓名与兴趣、空笔记。2.4 布局层受控表单 纯渲染读卡片demo-layout.tsx 组织了整体布局主区两列栅格放置偏好卡与笔记卡视口小于xl/1280px 时纵向堆叠右下角挂载默认打开的CopilotSidebarCopilotSidebar agentIdshared-state-read-write defaultOpen{true} labels{{ chatInputPlaceholder: Chat with the agent... }} /两张卡片刻意做了职责分离这是该 Demo 值得学习的一点PreferencesCard写侧是普通受控表单组件本身不感知 agent任何编辑都通过onChange冒泡到父层再由父层汇入agent.setState({ preferences: ... })。它提供姓名输入框、语气下拉Formal/Casual/Playful、语言下拉English/Spanish/French/German/Japanese、以及 7 个可切换的兴趣徽章Cooking、Travel、Tech、Music、Sports、Books、Movies。卡片底部还有一个data-testidpref-state-json的 JSON 预览框实时打印当前Preferences方便肉眼核对写入内容。NotesCard读侧只做渲染标题 Agent Scratch pad空态文案为 the agent will make observations about you and note them here!有笔记时以编号列表展示并提供 destructive 样式的 Clear 按钮onClear回调即 2.3 的写回逻辑。2.5 建议气泡useConfigureSuggestionssuggestions.ts 用useConfigureSuggestions注册了三条始终可见的起始建议恰好覆盖三条交互路径useConfigureSuggestions({ suggestions: [ { title: Greet me, message: Say hi and introduce yourself. }, { title: Remember something, message: Remember that I prefer morning meetings and that I dont eat dairy., }, { title: Plan a weekend, message: Suggest a weekend plan based on my interests. }, ], available: always, });3. 后端实现create_agent 中间件 状态写回工具后端实现全部在 shared_state_read_write.py。官方文档 shared-state-setup.mdx 给出的接入步骤正是引用本文件shared-state-setup代码区域说明该文件是把 CopilotKit 中间件接入create_agent这一模式的参考实现。3.1 状态 Schema在 Agent 状态上扩展两个切片class Preferences(TypedDict, totalFalse): name: str tone: str # formal | casual | playful language: str # English, Spanish, ... interests: list[str] class AgentState(BaseAgentState): Bidirectional shared state between UI and agent. - preferences is written by the UI (via agent.setState). - notes is written by the agent (via the set_notes tool) and read by the UI. preferences: Preferences notes: list[str]AgentState继承自langchain.agents.AgentState提供messages等基础字段前端agent.setState写入的 JSON 字段与这里的 schema 一一对应。3.2 Agent 写方向set_notes工具与Command(update...)tool def set_notes(notes: list[str], runtime: ToolRuntime) - Command: Replace the notes array in shared state with the full updated list. Use this tool whenever the user asks you to remember something, or when you have an observation about the user worth surfacing in the UIs notes panel. Always pass the FULL notes list (existing notes any new ones), not a diff. Keep each note short ( 120 chars). return Command( update{ notes: notes, messages: [ ToolMessage( contentNotes updated., nameset_notes, idstr(uuid.uuid4()), tool_call_idruntime.tool_call_id, ) ], } )这里有两个值得注意的契约见 shared_state_read_write.py#L54-L75全量替换而非增量 diff工具契约明确要求每次传完整的笔记列表已有 新增不要传 diff。QA 文档 qa/shared-state-read-write.md 中专门有回归项验证这一点——先记住早起开会、不吃奶制品再追加住在柏林笔记列表应变长且保留旧条目从而证明 Agent 按契约传了全量列表。Command(update...)双写一次返回同时更新notes状态切片并往messages里补一条带正确tool_call_id的ToolMessage保证消息链在 LangGraph 中合法闭合。状态更新经由 AG-UI 协议流回前端后前端useAgent订阅触发重渲染——这就是 README 所说笔记卡片随 Agent 更新实时刷新的底层通路。3.3 UI 写方向落地PreferencesInjectorMiddleware每轮注入系统提示词这是UI 写的状态如何被 Agent 读到的核心——一个 LangChainAgentMiddlewareshared_state_read_write.py#L78-L133class PreferencesInjectorMiddleware(AgentMiddleware[AgentState, Any]): Injects the UI-supplied preferences into the system prompt. Every turn, we read the latest preferences from agent state and prepend a SystemMessage that tells the LLM about them. This is how UI-written state becomes visible to the agent. state_schema AgentState def _build_prefs_message(self, prefs: Preferences) - SystemMessage | None: if not prefs: return None lines [The user has shared these preferences with you:] if prefs.get(name): lines.append(f- Name: {prefs[name]}) if prefs.get(tone): lines.append(f- Preferred tone: {prefs[tone]}) if prefs.get(language): lines.append(f- Preferred language: {prefs[language]}) interests prefs.get(interests) or [] if interests: lines.append(f- Interests: {, .join(interests)}) lines.append( Tailor every response to these preferences. Address the user by name when appropriate. ) return SystemMessage(content\n.join(lines)) def wrap_model_call(self, request, handler): prefs request.state.get(preferences) or {} prefs_message self._build_prefs_message(prefs) if prefs_message is None: return handler(request) return handler(request.override(messages[prefs_message, *request.messages]))实现要点每轮重读wrap_model_call同步与awrap_model_call异步两个钩子在每次模型调用前从request.state[preferences]读取最新值因此 UI 在对话中途改了表单下一轮立即生效无需前端把偏好重新塞进消息。空态跳过preferences为空时直接return handler(request)不注入任何系统消息——QA 文档将其列为容错用例清空姓名与兴趣后问 Who am I?Agent 应正常作答而非报错。注入方式request.override(messages[prefs_message, *request.messages])把偏好SystemMessage前置到消息列表头部与create_agent自身的system_prompt并存。3.4 图装配create_agent一行式配置graph create_agent( modelChatOpenAI(modelgpt-5.4), tools[set_notes], middleware[CopilotKitMiddleware(), PreferencesInjectorMiddleware()], state_schemaAgentState, system_prompt( You are a helpful, concise assistant. The users preferences are supplied via shared state and will be added as a system message at the start of every turn. Always respect them. When the user asks you to remember something, or when you observe something worth surfacing in the UI, call set_notes with the FULL updated list of short note strings (existing notes new). ), )shared_state_read_write.py#L137-L151装配上的分工清晰CopilotKitMiddleware()来自仓库的copilotkitPython SDKfrom copilotkit import CopilotKitMiddleware负责把 CopilotKit 运行时与 Agent 之间的状态同步、工具调用等协议细节接入图——没有它前端的agent.setState写入就无法进入图状态。PreferencesInjectorMiddleware()本 Demo 自定义的状态 → 提示词桥梁。system_prompt中显式教模型set_notes的使用契约全量列表、短字符串与工具 docstring 呼应双保险。state_schemaAgentState声明图状态结构是前端 JSON 字段与后端切片对上的依据。注意此处模型为ChatOpenAI(modelgpt-5.4)运行前提见 qa/shared-state-read-write.md 的前置条件OPENAI_API_KEY已配置、LANGGRAPH_DEPLOYMENT_URL指向暴露了shared-state-read-write图的 LangGraph 部署。4. 实战验证交互路径与自动化测试4.1 README 给出的手工验证路径先编辑侧边栏偏好然后依次尝试对应 README.md 的 How to Interact 一节Say hi and introduce yourself. —— 观察 Agent 是否按当前语气/语言/称呼作答Remember that I prefer morning meetings and that I dont eat dairy. —— 观察侧边栏笔记卡片实时长出条目Suggest a weekend plan based on my interests. —— 观察计划是否贴合所选兴趣。QA 清单qa/shared-state-read-write.md进一步给出可勾选的验收项例如姓名填 Atai、语气改formal后JSON 预览应同步显示新值发 What do you know about me? 应在 10 秒内得到引用姓名、正式语气、语言与兴趣的回复即中间件注入生效点击 Clear 后追问 What do you remember about me?Agent 不应再引用已清空的笔记写回生效多轮测试中把语气改playful、加Music兴趣后请求一句俳句问候再追问 Do it again in French.验证偏好跨轮持久且无需重发。4.2 Playwright 端到端测试tests/e2e/shared-state-read-write.spec.ts 固化了上述行为其中最有价值的两个用例是负向断言Greet me 用例断言回复包含shared-state co-pilot且不包含通用兜底文案 showcase assistant. I can help with weather, charts…。注释记录了回归背景——Say hi and introduce yourself. 曾错误命中feature-parity.json中裸userMessage: hi兜底 fixture回复了与共享状态无关的通用语修复方式是添加更长子串匹配的 d5 fixture 使其优先命中。Plan a weekend 用例断言回复提及interests panel且不包含通用内容营销五步计划Research the topic… Outline key points——同样是对错误 fixture 兜底的防回归。其余用例验证两张卡片与三条建议气泡的挂载、可见性data-testidpreferences-card、notes-card、Greet me等按钮。5. 小结可复用的双向共享状态模式把这个 Demo 抽象出来是一个可以直接套用的模式环节前端React后端LangGraph Python状态定义RWAgentStateTS 接口page.tsxAgentState(BaseAgentState)PreferencesTypedDictUI 写useAgent(...)返回的agent.setState({...})CopilotKitMiddleware将写入落入图状态Agent 读—自定义中间件wrap_model_call/awrap_model_call中request.state.get(preferences)注入SystemMessageAgent 写—tool返回Command(update{notes: ...})UI 读useAgent({ updates: [UseAgentUpdate.OnStateChanged] })订阅 组件重渲染AG-UI 协议将状态变更推给前端UI 写回可选同一agent.setState覆盖 Agent 切片如清空notes下一轮中间件/工具读到新值三个容易踩的坑本 Demo 源码里都给了答案写偏好时同时携带其他切片以保留 Agent 已写内容notes保活set_notes契约是全量替换而非 diff需在工具 docstring 与 system prompt 中双重强调状态按会话存在首值靠页面useEffect播种刷新即回默认。相关源码入口速查前端 page.tsx、demo-layout.tsx、preferences-card.tsx、notes-card.tsx、suggestions.ts后端 shared_state_read_write.py注册与部署见 route.ts 与 langgraph.json。【免费下载链接】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),仅供参考
返回列表