8.4 LangChain 前端(分支对话)
分支对话
编辑消息、重新生成回复、在对话分支间自由切换
与 AI 智能体的对话很少是线性的。你可能想要改写问题、重新生成不满意的回复,或探索完全不同的对话方向,同时不丢失之前的内容。
分支对话为聊天界面带来了类似版本控制的能力:每次编辑都会创建新分支,你可以在不同分支间自由切换。
该功能需要 LangGraph Agent Server 支持。
使用 langgraph dev 本地运行智能体,或部署到 LangSmith 即可使用。
什么是分支对话?
分支对话将对话视为一棵树,而不是一个列表。
每条消息都是一个节点,编辑消息或重新生成回复会从该点创建分叉。
原始路径会作为同级分支保留,用户可在不同对话轨迹之间来回切换。
核心能力:
- 编辑任意用户消息:重写历史提示,从该点重新运行智能体
- 重新生成任意 AI 回复:对同一输入让智能体生成不同答案
- 分支导航:通过消息级分支控件切换不同对话版本
配置带历史记录的 useStream
要启用分支功能,需传入 fetchStateHistory: true,让 useStream 获取分支操作所需的检查点元数据。
import type { BaseMessage } from "@langchain/core/messages";
interface AgentState {
messages: BaseMessage[];
}
React 示例
import { useStream } from "@langchain/react";
const AGENT_URL = "http://localhost:2024";
export function Chat() {
const stream = useStream<typeof myAgent>({
apiUrl: AGENT_URL,
assistantId: "branching_chat",
fetchStateHistory: true,
});
return (
<div>
{stream.messages.map((msg) => {
const metadata = stream.getMessagesMetadata(msg);
return (
<Message
key={msg.id}
message={msg}
metadata={metadata}
onEdit={(text) => handleEdit(stream, msg, metadata, text)}
onRegenerate={() => handleRegenerate(stream, metadata)}
onBranchSwitch={(id) => stream.setBranch(id)}
/>
);
})}
</div>
);
}
理解消息元数据
getMessagesMetadata(msg) 返回每条消息的分支信息:
interface MessageMetadata {
branch: string;
branchOptions: string[];
firstSeenState: {
parent_checkpoint: Checkpoint | null;
};
}
| 字段 | 说明 |
|---|---|
| branch | 当前消息版本的分支 ID |
| branchOptions | 该消息位置所有可用的分支 ID 列表 |
| parent_checkpoint | 这条消息之前的检查点,用作编辑/重新生成的分叉点 |
当消息只有一个版本时,branchOptions 只有一项;
编辑或重新生成后,会新增分支 ID,可在多个版本间切换。
编辑消息(创建新分支)
编辑用户消息并创建新分支:
- 从消息元数据获取
parent_checkpoint - 使用该检查点提交编辑后的消息
- 智能体从该点重新运行,创建新分支
function handleEdit(
stream,
originalMsg,
metadata,
newText
) {
const checkpoint = metadata.firstSeenState?.parent_checkpoint;
if (!checkpoint) return;
stream.submit(
{
messages: [{ ...originalMsg, content: newText }],
},
{ checkpoint }
);
}
编辑后:
- 消息的
branchOptions增加新条目 - 视图自动切换到新分支
- 智能体从分叉点重新运行
- 原始版本保留,可通过分支切换器访问
重新生成回复
不修改输入,只重新生成 AI 回复:
- 从 AI 消息元数据获取
parent_checkpoint - 传入
undefined输入 + 父检查点提交 - 智能体生成新回复,创建新分支
function handleRegenerate(
stream,
metadata
) {
const checkpoint = metadata.firstSeenState?.parent_checkpoint;
if (!checkpoint) return;
stream.submit(undefined, { checkpoint });
}
每次重新生成都会为该位置的 AI 消息创建新分支,用户可对比不同回复。
构建分支切换器
当消息存在多个分支时,显示紧凑的内联控件,展示当前版本序号与导航箭头。
function BranchSwitcher({ metadata, onSwitch }) {
const { branch, branchOptions } = metadata;
if (branchOptions.length <= 1) return null;
const currentIndex = branchOptions.indexOf(branch);
const hasPrev = currentIndex > 0;
const hasNext = currentIndex < branchOptions.length - 1;
return (
<div className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
<button
disabled={!hasPrev}
onClick={() => onSwitch(branchOptions[currentIndex - 1])}
>
◀
</button>
<span className="min-w-[3ch] text-center">
{currentIndex + 1}/{branchOptions.length}
</span>
<button
disabled={!hasNext}
onClick={() => onSwitch(branchOptions[currentIndex + 1])}
>
▶
</button>
</div>
);
}
调用 stream.setBranch(branchId) 可瞬间切换分支(所有数据已预加载)。
底层原理:分支如何工作
LangGraph 将每一次状态变化都保存为检查点。
当你带 checkpoint 参数提交时,后端会从该点分叉,而不是追加到当前对话。最终形成树结构:
用户:React 是什么?
└─ AI:React 是一个 JS 库…(分支A)
└─ AI:React 是一个 UI 框架…(分支B,重新生成)
用户:讲讲 Hooks(分支A)
└─ AI:Hooks 是函数…
用户:讲讲 JSX(从A编辑而来)
└─ AI:JSX 是一种语法扩展…
所有分支都会持久保留,切换分支不会删除任何数据。
完整消息组件(含编辑/重生成/分支切换)
可直接复制使用:
function MessageWithBranching({ message, metadata, stream }) {
const [isEditing, setIsEditing] = useState(false);
const [editText, setEditText] = useState(message.content);
const isHuman = message._getType() === "human";
const isAI = message._getType() === "ai";
const hasBranches = metadata.branchOptions.length > 1;
return (
<div className="relative py-2">
{isEditing ? (
<EditForm
text={editText}
onChange={setEditText}
onSave={() => {
handleEdit(stream, message, metadata, editText);
setIsEditing(false);
}}
onCancel={() => {
setEditText(message.content);
setIsEditing(false);
}}
/>
) : (
<>
<div className={isHuman ? "text-right" : "text-left"}>
<div className={
isHuman
? "inline-block rounded-lg bg-blue-600 px-4 py-2 text-white"
: "inline-block rounded-lg bg-gray-100 px-4 py-2"
}>
{message.content}
</div>
</div>
<div className="mt-1 flex items-center gap-2 opacity-0 group-hover:opacity-100">
{isHuman && (
<button className="text-xs text-gray-400" onClick={() => setIsEditing(true)}>
编辑
</button>
)}
{isAI && (
<button className="text-xs text-gray-400" onClick={() => handleRegenerate(stream, metadata)}>
重生成
</button>
)}
{hasBranches && (
<BranchSwitcher
metadata={metadata}
onSwitch={(id) => stream.setBranch(id)}
/>
)}
</div>
</>
)}
</div>
);
}
function EditForm({ text, onChange, onSave, onCancel }) {
return (
<div className="space-y-2">
<textarea
className="w-full rounded-lg border p-3"
value={text}
onChange={(e) => onChange(e.target.value)}
rows={3}
/>
<div className="flex gap-2">
<button className="rounded bg-blue-600 px-4 py-1.5 text-white" onClick={onSave}>
保存并重新运行
</button>
<button className="rounded border px-4 py-1.5" onClick={onCancel}>
取消
</button>
</div>
</div>
);
}
最佳实践
- 必须开启 fetchStateHistory,否则无法获取分支信息
- 仅在有多分支时显示分支切换器,避免冗余
- 悬停显示控件,保持界面简洁
- 分支切换器保持紧凑,内联展示
- 切换分支时保持滚动位置
- 明确标识当前分支(颜色点/标签)
- 流式输出时禁用编辑/重生成
- 取消编辑时恢复原文本
- 深度分支树也要保证性能
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)