学习笔记 · Obsidian
Frontend SDK 与 Generative UI
核心结论
LangChain 前端 SDK 不是“把 token 追加进气泡”的组件库,而是 agent 运行时的 UI 适配层:
create_agent/compiled LangGraph backend ⇄ stream protocol ⇄useStream/injectStream
前端得到的不只有 messages,还包括 tool lifecycle、interrupt、durable thread、custom state、checkpoint history 和 nested execution。React、Vue、Svelte 使用各自 @langchain/* 包的 useStream,Angular 使用 injectStream;视觉层可以完全自定义。
文档当前以 v1 frontend SDK 为基线。Python backend 在 TypeScript 侧不能直接 typeof agent 推导类型,应定义与 agent state schema 对齐的 interface,再作为 stream generic;assistantId 使用 langgraph.json 中的 graph 名。
从运行时状态设计 UI
| 运行时能力 | UI 应表达的状态 |
|---|---|
| Durable thread | thread ID、恢复/新建会话、跨刷新/设备回归 |
| Tool call | running、finished、error、参数、结果、call ID |
| Interrupt | 待审动作、允许的 decision、审批人输入、恢复状态 |
| Checkpoint | 节点、时间、state snapshot、next/tasks、分支来源 |
| Nested execution | 主 agent、named subagent、普通 subgraph 的层级 |
| Custom state | todo、引用、文件、指标、业务对象,不局限于 chat |
UI 的职责是让用户能看见、理解、控制、恢复 agent 工作,而不是把所有内部 JSON 原样展示。
消息与输出呈现
Markdown
useStream 持续累积 AI message text,视图每次更新解析 Markdown。React 可用 react-markdown + remark-gfm 直接生成 React elements;Vue/Svelte/Angular 使用 marked 等输出 HTML 时必须经 DOMPurify 清洗,防止模型文本被解析为 XSS。普通聊天长度全量重解析通常足够;超长响应出现卡顿后再用 animation frame 节流或增量解析。
Reasoning blocks
标准 contentBlocks 可能包含 reasoning 与 text。视图应:
- 仅在 provider 实际返回 reasoning 时展示;
- 默认折叠、与最终回答视觉区分并支持无障碍操作;
- 过滤空 block,保留多个 reasoning/text 周期的原始顺序;
- 不把“reasoning 字符数”误当正确性或置信度,也不依赖其稳定存在。
Structured output
前端示例从 AI message 的结构化 tool call args 读取 typed payload,映射为卡片、表格、步骤或图表。流式参数可能是部分对象:必须检查 required fields、校验类型并提供 loading/fallback;扁平、按显示顺序生成的 schema 更利于 progressive rendering。业务动作不能基于未完成 args 提前执行。
Tool UI 与客户端执行
Tool-call cards
stream.toolCalls 把 AI tool call 与后续 ToolMessage 组装为统一对象。以 callId 保持同一个卡片从 running 更新到 finished/error;并行工具要独立完成。按 tool name 选择专用卡片,同时保留紧凑的 generic fallback。结果在收窄/校验前是 unknown,不能直接信任。
Headless tools
当能力只存在于浏览器/设备(geolocation、clipboard、camera、file picker、IndexedDB、canvas)时:
- backend 注册同名、同 schema 的 headless tool;
- tool 调用触发 interrupt,而不是服务端执行;
- frontend 用
.implement(...)提供本地实现并交给useStream; - client 执行后把 JSON-serializable 结果 resume 给 graph。
服务端 schema 与前端实现要共享/契约测试;不要返回 DOM/file handle 等不可序列化对象。隐私本地化并不等于无风险,剪贴板、相机、定位等仍需权限提示和 HITL。
HITL 前端协议
当 agent interrupt 时,stream.interrupt 暴露 actionRequests 与对应 reviewConfigs。UI 只显示允许的 approve、edit、reject、respond,收集所有 pending action 的决定后,用 resume command 继续运行。
审批卡必须显示工具名、完整影响、参数和原因;edited args 再做 schema 校验;刷新后仍从 checkpoint 恢复待审状态;审批/拒绝/编辑要有操作者与时间审计。复杂业务表单可以让工具直接 interrupt() 一个 JSON-serializable card descriptor,前端按类型渲染并把决定持久写回 transcript。
对话并发、断线与历史分支
Message queue
multitaskStrategy: "enqueue" 允许用户在当前 run 执行时提交后续消息;submission queue 提供 entries/size/cancel/clear。取消只影响尚未开始的条目;停止当前 run 要用 stream.stop()。UI 应显示排队位置、支持取消、限制队列规模并让失败条目不阻塞后续处理。
Join & rejoin
持久保存 threadId,组件重新挂载同一 thread 即可观察持续运行的任务。关键区别:
stream.disconnect()(等价于 stop withcancel:false)只断开客户端,server run 继续;stream.stop()默认同时取消 server run。
移动网络、页面离开、后台挂起和多设备接力都应使用 disconnect/rejoin 语义,并明确展示连接与运行状态。
Branching chat 与 time travel
LangGraph 每个 node 后持久化 ThreadState checkpoint,包含 checkpoint metadata、完整 values、tasks 和 next。Branching chat 用消息 metadata 找到此前 checkpoint,编辑/重生成时 fork;time travel 从 threads.getHistory 建时间线,并以 forkFrom: {checkpointId} 从某一状态重新执行。
恢复旧 checkpoint 不删除原历史,而是创建新分支;UI 必须确认操作、显示当前分支/来源,并按需 diff state。checkpoint 可含敏感 state,普通用户界面不应默认暴露完整 JSON;管理/调试界面要授权和脱敏。
Generative UI 三段谱系
核心问题是“谁创作界面”:
| 模式 | 谁定义组件/布局 | 收益 | 风险/成本 |
|---|---|---|---|
| Controlled | 应用预写组件,agent 只选择组件和 props | 品牌、可访问性、安全、测试最可控 | 每个能力要提前开发组件 |
| Declarative | 应用注册 component catalog,agent 生成受 schema 约束的 JSON spec | 能组合未预先写死的布局,同时限制组件集合 | 需校验部分 spec、action 与递归复杂度 |
| Open-ended | 外部系统/MCP server 提供 UI,应用在 sandbox 中承载 | 表达力最大、生态能力可独立演进 | 最不确定、最难保持品牌/性能/安全,必须隔离 |
Controlled
常用承载:components-as-tools、tool-call rendering、custom state rendering 与 reasoning。适合高流量、品牌关键、合规或无障碍要求严格的核心流程。
Declarative
以 json-render 为例:用 Zod 定义 catalog 的组件 props 与描述,registry 映射到真实组件,agent 返回 root + elements 的 flat spec。streaming 时只渲染 type/props 已完整的元素,Renderer 处于 loading 以忽略尚未到达的 children。catalog 越聚焦越容易稳定;actions、URL、文本、层级深度和组件数量仍需验证。
A2UI 是另一种 declarative spec:dynamic schema 由次级模型生成布局和数据,灵活;fixed schema 由前端固定树、agent 只填数据,更快更可预测。
Open-ended
MCP Apps 等外部 UI 通常在 iframe 中呈现。必须将其视为不可信第三方:sandbox、最小 capability、严格 postMessage origin/schema、网络/CSP 限制、数据最小化和用户授权。需要确定品牌与 accessibility 时应退回 declarative 或 controlled。
UI 集成选择
| 集成 | 最适合 | 接入模型 |
|---|---|---|
| CopilotKit | 完整 chat runtime + structured generative UI/A2UI | LangGraph deployment 增加 endpoint,React runtime/renderers |
| AI Elements | shadcn/ui 风格、可组合的 message/tool/reasoning 组件 | 直接消费 stream.messages 等状态 |
| assistant-ui | 带 thread runtime 的 headless React chat | 用 external-store adapter 把 useStream 接入 provider |
| OpenUI | 数据丰富 dashboard/report | agent 输出 openui-lang,由 Renderer 渐进呈现;可由 Deep Agents 并行生成 panels |
useStream 本身 UI-agnostic。先按产品需要选择控制层级与运行时能力,再选组件库;不要为了组件外观牺牲 durable thread、interrupt 和 checkpoint 语义。
逐页覆盖索引
| 页面 | 学习结论 |
|---|---|
| Frontend overview | SDK 架构、durable/runtime 能力、type inference、消息/动作/对话/高级流式导航。 |
| Markdown messages | 流式 Markdown 解析、各框架库选择、XSS 清洗与长消息性能。 |
| Tool calling | assembled tool call、专用 cards、类型收窄、并行与 running/finished/error。 |
| Headless tools | backend schema + client implementation + interrupt/resume 的浏览器/设备执行模式。 |
| Human-in-the-loop | interrupt payload、四种 decision、批量动作、自定义表单与恢复。 |
| Branching chat | message checkpoint metadata、编辑/重生成与分支语义。 |
| Reasoning tokens | reasoning/text blocks、折叠组件、空块/多周期/无 reasoning 边界。 |
| Structured output | typed tool args、自定义组件、部分数据防护与 progressive rendering。 |
| Message queues | enqueue、队列可见性、cancel/clear、follow-up 和新 thread。 |
| Join & rejoin | thread binding、disconnect 与 cancel 区别、断线重连。 |
| Time travel | checkpoint timeline/state inspection、forkFrom、interrupt 标记与审计/调试。 |
| Generative UI overview | controlled/declarative/open-ended 的控制力与表达力谱系。 |
| Controlled UI | 预置组件、tool/state/reasoning rendering,最高可预测性。 |
| Declarative UI | catalog/registry/spec/Renderer、json-render、A2UI 与流式校验。 |
| Open-ended UI | MCP Apps、第三方 UI、sandbox 与最小信任。 |
| Integrations overview | CopilotKit、AI Elements、assistant-ui、OpenUI 的定位对比。 |
| CopilotKit | Python AG-UI bridge/custom endpoint、Deep Agent 与动态组件树。 |
| AI Elements | shadcn/ui chat primitives 直接连接 useStream。 |
| assistant-ui | external-store runtime bridge、消息转换与 thread UI slots。 |
| OpenUI | openui-lang、system prompt、Renderer、hoisting/progressive rendering、并行 dashboard。 |