ARTICLE DETAIL

资讯详情

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

CopilotKit × Microsoft Agent Framework (.NET):实现 UI 与 Agent 双向共享状态的 Shared State Read/Write 演示

CopilotKit × Microsoft Agent Framework (.NET):实现 UI 与 Agent 双向共享状态的 Shared State Read/Write 演示 CopilotKit × Microsoft Agent Framework (.NET)实现 UI 与 Agent 双向共享状态的 Shared State Read/Write 演示【免费下载链接】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/integrations/ms-agent-dotnet集成中的shared-state-read-write演示为对象完整拆解UI 与 Agent 之间双向读写同一份共享状态的实战实现前端如何用一个agent.setState(...)把偏好表单写入 Agent 状态、用useAgent(...)订阅 Agent 回写的便签.NET 后端如何从 AG-UI 共享状态读出偏好并注入系统提示词、再通过set_notes工具和状态快照事件把数据写回 UI。读完本文你能掌握在 CopilotKit Microsoft Agent Framework.NET技术栈下搭建双向共享状态功能的前后端完整链路并能对照源码理解状态如何在一次对话回合内完成读—注入—执行—写回的闭环。演示目标同一份状态两侧都能读也能写演示的 READMEREADME.md将其定位为Bidirectional shared state——UI 与 Agent 双方都读写同一个状态对象具体拆成三条行为线UI → agent写侧边栏偏好表单name / tone / language / interests通过agent.setState(...)写入state.preferences后端每一轮对话都会读取它并注入系统提示词。agent → UI读Agent 的set_notes工具写入state.notes侧边栏的Agent Scratch pad卡片在 Agent 每次更新时自动重新渲染。Round-trip闭环验证在侧边栏修改偏好后Agent 的下一轮回复会明显被带偏——语气tone、回复语言language、称呼name都会随之变化。状态对象的前端类型定义在 page.tsx 中是整个演示的合同// Shape of the bidirectional shared state. // - 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; notes: string[]; }其中Preferences定义在 preferences-card.tsxexport interface Preferences { name: string; tone: formal | casual | playful; language: string; interests: string[]; }对应的初始值INITIAL_PREFERENCES为{ name: , tone: casual, language: English, interests: [] }。后端 C# 侧存在同构的SharedStatePreferencesrecord见 SharedStateReadWriteAgent.cs其Empty缺省值与前端完全一致空 name、casual、English、空 interests。值得注意的是README 中提到的 Python 参考实现路径src/agents/shared_state_read_write.py来自本演示的原始 langgraph-python 版本在当前 ms-agent-dotnet 集成中实际后端是 C# 文件 agent/SharedStateReadWriteAgent.cs文件头注释明确说明了它与 Python/Google ADK 参考实现的行为对齐shape parity。实操步骤先改偏好再发三条建议消息README 给出的交互路径是先编辑侧边栏偏好然后依次尝试三条消息对应 suggestions.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, });Say hi and introduce yourself.验证 UI → agent 方向——如果你填了 name 并选了playfulAgent 会按名字称呼你并用俏皮语气打招呼。Remember that I prefer morning meetings and that I dont eat dairy.验证 agent → UI 方向——Agent 调用set_notes后侧边栏 Scratch pad 里出现 Prefers morning meetings 和 Does not eat dairy 两条便签。Suggest a weekend plan based on my interests.验证 Round-trip——Agent 依据你勾选的 interestsCooking / Travel / Tech / Music / Sports / Books / Movies 七个选项见INTEREST_OPTIONS给出周末计划。页面上还有一处便于调试的细节偏好卡片底部preferences-card.tsx会以pre实时打印当前state.preferences的 JSONdata-testidpref-state-json让UI 写进共享状态的到底是什么一眼可见。前端实现一个 Provider、一个订阅、一个写入口1. CopilotKit Provider 绑定 Agent页面组件的入口只有 6 行Provider 通过runtimeUrl指向 Next.js 的 CopilotKit API 路由用agent指定要绑定的 Agent idexport default function SharedStateReadWriteDemo() { return ( CopilotKit runtimeUrl/api/copilotkit agentshared-state-read-write DemoContent / /CopilotKit ); }侧边栏聊天组件在 demo-layout.tsx 中同样以agentIdshared-state-read-write关联同一个 Agent保证聊天、建议按钮与侧边栏卡片操作的是同一份会话状态。2. agent → UI用 useAgent 订阅状态变更这是 README Technical Details 第一条。page.tsx 中const { agent } useAgent({ agentId: shared-state-read-write, updates: [UseAgentUpdate.OnStateChanged], }); const agentState agent.state as RWAgentState | undefined; const preferences agentState?.preferences ?? INITIAL_PREFERENCES; const notes agentState?.notes ?? [];updates: [UseAgentUpdate.OnStateChanged]让组件订阅 Agent 的每一次状态变更只要后端在某轮结束时推送了包含notes新值的状态快照该 hook 就会触发重渲染notes-card.tsx里的便签列表随之更新。这是agent → UI方向能够实时刷新的唯一机制——NotesCard 组件本身完全不接触 agent只是被父级传入notes后纯渲染源码注释原话we never touch agent state ourselves — we just render it。3. UI → agent用 agent.setState 写入README 第二条对应的写入口在 page.tsx// WRITE: every edit in the sidebar goes straight into agent state. const handlePreferencesChange (next: Preferences) { agent.setState({ preferences: next, notes, // preserve what the agent has written } as RWAgentState); }; // WRITE: let the user clear the agent-authored notes from the UI. const handleClearNotes () { agent.setState({ preferences, notes: [] } as RWAgentState); };两个要点值得注意写入是整对象而非字段级 patch每次改偏好都要带上当前的notes否则会覆盖掉 Agent 之前写入的便签Clear 按钮则是同一通道反向清空notes。这演示了同一个字段notes上双向写入的完整能力——Agent 写、UI 也能写。表单组件与 Agent 解耦PreferencesCard是一个受控表单onChange把新值冒泡到父级后由父级路由进agent.setState卡片组件自身不知道 agent 的存在状态装配逻辑全部上提一层。页面挂载时还有一次性的 seeduseEffect(() { if (!agentState?.preferences) { agent.setState({ preferences: INITIAL_PREFERENCES, notes: [], } as RWAgentState); } }, []);目的是让 Agent 在第一轮对话时就有可读的偏好数据而不是等用户手动编辑过表单之后才生效。后端实现.NET从 AG-UI 共享状态读偏好、经工具写回README 的第三条 Technical Detail 在 Python 参考实现中表现为PreferencesInjectorMiddleware.wrap_model_call读取request.state[preferences]并前置一条SystemMessage以及set_notes工具返回Command(update{notes: ...})。在当前的 .NET 实现里这些职责由SharedStateReadWriteAgent一个DelegatingAIAgent装饰器在单次流式执行中完成整体流程与 Python 版行为一致。路由与装配Agent 在 Program.cs 中挂载到 AG-UI 端点与前端agentshared-state-read-write对应var sharedStateReadWriteFactory new SharedStateReadWriteAgentFactory(builder.Configuration, loggerFactory, jsonOptions.Value.SerializerOptions); app.MapAGUI(/shared-state-read-write, sharedStateReadWriteFactory.CreateAgent());工厂SharedStateReadWriteAgent.cs用 OpenAI 客户端的gpt-4o-mini构建内层ChatClientAgent并注册了唯一的工具set_notesvar setNotes AIFunctionFactory.Create( (FuncListstring, string)(notes { ArgumentNullException.ThrowIfNull(notes); _store.SetNotesForActiveThread(notes); return $ok: {notes.Count} notes; }), options: new() { Name set_notes, Description Replace the notes list with the FULL updated list (existing notes new). Pass plain short note strings., SerializerOptions _jsonSerializerOptions, });工具契约与 Python 参考版一致总是用完整的新列表替换 notes 数组而不是提交 diff源码注释明确标注 this matches the documentedset_notescontract。每轮回合如何读取 UI 写入的 preferences在RunStreamingAsyncSharedStateReadWriteAgent.cs中偏好数据从 AG-UI 桥接层放进的ChatClientAgentRunOptions.AdditionalProperties[ag_ui_state]里读出——这就是前端agent.setState({ preferences, notes })在后端的落点internal static bool TryGetAgUiState(AgentRunOptions? options, out JsonElement state) { if (options is ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } props } props.TryGetValue(ag_ui_state, out JsonElement element) element.ValueKind JsonValueKind.Object) { state element; return true; } state default; return false; }读出后调用MergeFromInbound合入按线程隔离的存储合并不含偏好的字段时容错回退到旧值然后构建系统提示词并前置到消息列表var inboundPreferences TryReadPreferences(options) ?? TryReadPreferences(messageList); var inboundNotes TryReadNotes(options); _store.MergeFromInbound(thread, inboundPreferences, inboundNotes); var systemPrompt BuildPreferencesSystemPrompt(_store.GetPreferences(thread));BuildPreferencesSystemPrompt生成的系统消息既包含人类可读的行也原样内嵌 preferences JSON并附上约束指令Tailor every response to these preferences. Address the user by name when appropriate.。基础系统提示词还规定当用户要求记住某事时必须调用set_notes传入完整的新便签列表。这正是 round-trip 生效的机制——UI 写的偏好每轮都被注入模型据此调整语气/语言/称呼。agent → UI 的回写工具写 Store轮末发快照set_notes工具并不直接发消息给客户端而是把便签写入SharedStateReadWriteStore一轮流式输出结束后装饰器补发一个状态快照更新// Emit the post-turn state snapshot so the UIs useAgent hook sees // tool-driven mutations to notes as well as the canonical copy of preferences. await foreach (var snapshotUpdate in EmitSnapshotAsync(thread, cancellationToken)) { yield return snapshotUpdate; }EmitSnapshotAsync序列化{ preferences, notes }快照为application/json的DataContentSharedStateReadWriteAgent.cs。按照文件头注释.NET 的 AG-UI 桥接层会把这种DataContent(application/json)更新解释为状态快照事件推给客户端——前端useAgent订阅到的OnStateChanged正是由此触发从而完成agent → UI的最后一公里。按线程隔离的 Store 与 AsyncLocal 细节SharedStateReadWriteStoreSharedStateReadWriteAgent.cs以AgentThread的引用身份为 key 维护每会话的{ preferences, notes }槽位没有线程时回退到实例级全局槽位。一个从源码注释中可以读出的工程细节set_notes工具闭包收不到AgentThread参数因此装饰器在执行内层 Agent 前调用_store.SetActiveThread(thread)把当前线程绑定进AsyncLocal执行完在finally中恢复否则工具写入会落进全局槽位导致便签从 UI 上消失或在并发会话间泄漏。此外MergeFromInbound里偏好永远以入站为准UI 是 preferences 的唯一事实源而入站notes只在首次观察时采纳避免运行时重放的旧快照覆盖工具刚写入的新值。该演示还有确定性的演示回复分支TryBuildDeterministicReply三条建议按钮的消息在后端走固定文案其中 Remember something 会确定性写入两条便签保证演示在模型行为波动下依然可复现。后端单元测试位于 agent/tests/SharedStateAgentTests.csBuildPreferencesSystemPrompt特意声明为internal以便单测覆盖。小结双向共享状态的四步闭环把前后端串起来一次完整回合的数据流是UI 写表单变更 →agent.setState({ preferences, notes })→ 经/api/copilotkit到达 .NET 后端后端读RunStreamingAsync从ag_ui_state取出preferencesBuildPreferencesSystemPrompt注入系统消息模型据此调整回复后端写模型调用set_notes整列表替换写入按线程隔离的 StoreUI 读轮末DataContent(application/json)快照经 AG-UI 桥接成为状态快照事件useAgent({ updates: [OnStateChanged] })触发侧边栏便签卡片重渲染。这套模式的价值在于状态本身是会话级、双向可写的单一事实源UI 表单和聊天输入框操作同一对象而谁写的由字段决定preferences归 UInotes归 Agent两侧各自只需一种原语——setState写、useAgent读——就能实现跨进程的实时同步。所有代码均可在 showcase/integrations/ms-agent-dotnet/src/app/demos/shared-state-read-write 目录与 showcase/integrations/ms-agent-dotnet/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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表