-
Notifications
You must be signed in to change notification settings - Fork 610
Description
背景 / Background
回退到第 N 轮,从那里开始一个新的思路,相当于在对话时间线上「开分支」。
在 OpenAI 的新 Responses API 中,引入了 previous_response_id 机制,可以让服务端记住整条对话链(包括输入和输出),后续请求只需要发送「新一轮的用户输入 + previous_response_id」即可继续会话,甚至可以从同一个 previous_response_id 开出多条不同分支。
需求 / What I’d like Kode to support
希望 Kode 在对接 OpenAI(尤其是 GPT-5.x / Codex 系列 / Responses API-only 模型)时:
1. 优先使用 Responses API + previous_response_id 来维持上下文,而不是每次都在本地手动拼接完整历史;
2. 在 CLI 的交互层面上:
• 保持现在「可以回退到任意历史轮次」的 UX;
• 当用户从某一轮回退并继续对话时,内部使用这一轮对应的 response.id 作为新的 previous_response_id,在服务端开出一个新的会话分支;
3. 在用户显式编辑历史内容 / 压缩上下文 / 超出保留期时,能够自动退回到「本地构造 input[]、不传 previous_response_id」的兼容模式。
简单说:
本地是「对话树」,服务端是「线性链条」,希望 Kode 自动帮我把这两者对齐好。
预期行为 / Expected behavior
以一个典型例子说明预期效果:
1. 用户进入 Kode,连续对话 5 轮,形成节点:T1 → T2 → T3 → T4 → T5;
2. 每一轮对应一次 responses.create 调用,并返回 response.id,内部存为:
• Tn.responseId = ;
3. 用户使用 Kode 的 UI/快捷键「回退到 T3」;
4. 用户输入新的问题 Q’;
5. Kode 对 OpenAI 的调用为:
const res = await client.responses.create({
model: "gpt-5.1-codex", // 示例
input: [{ role: "user", content: QPrime }],
previous_response_id: T3.responseId,
});
-
这次返回的新 response.id 记为 T3b.responseId,在本地对话树上形成分支:
• 旧分支:T1 → T2 → T3 → T4 → T5
• 新分支:T1 → T2 → T3 → T3b → …
用户在 UI 中可以自由切换不同分支继续聊,OpenAI 后端通过不同的 previous_response_id 分别维持上下文,无需每次由 Kode 手动重发全部历史。
实现建议 / Proposed implementation
下面是一个尽量不侵入整体架构的实现思路,仅供参考:
- 抽象本地对话结构为一棵树
interface TurnNode {
id: string; // 本地节点 id
parent: string | null;
responseId: string | null; // OpenAI responses.create 返回的 id
userMessage: string;
assistantMessage: string;
createdAt: number;
historyEdited: boolean; // 任意祖先是否被编辑/裁剪过
}
interface ConversationTree {
root: string;
nodes: Map<string, TurnNode>;
current: string; // 当前光标所在节点
}
- 发送请求时的决策逻辑
• 如果当前节点 current 及其祖先都没有被用户编辑/裁剪,且 responseId 仍然有效:
• 调用 responses.create 时:
const res = await client.responses.create({
model,
input: [{ role: "user", content: userInput }],
previous_response_id: nodes[current].responseId,
});
• 否则(例如用户改了历史消息、手动清理了部分上下文、或者超过一定时间阈值):
• 回退为「兼容模式」:根据当前分支的所有 messages 手动构造 input[],不传 previous_response_id;
• 把这次返回的 response.id 当作新链的起点。
-
回退(rewind)和分支(fork)
• 回退:只移动 tree.current = somePastTurnId,不与后端交互;
• 在回退位置发送新请求时:
• 如果满足上面的 previous_response_id 条件,就从该节点继续往下链,形成分支;
• 在本地树里挂一个新的子节点即可。 -
可选:配置项
新增一个高级配置项,例如:
{
"openai": {
"useResponsesPreviousId": true,
"maxPrevIdAgeMinutes": 60
}
}
方便用户在有特殊需求(比如严格手动控制 prompt)的情况下关闭这一优化。
为什么值得做 / Why this is useful
• 更好的模型表现
对于 GPT-5 / Codex 这类针对长链式“agent 工作流”优化的模型,让服务端来管理上下文,而不是每次本地字符串拼接,会更接近它们在官方产品中的使用方式,也有利于利用服务器端的优化(缓存、压缩等)。
• 更低的 token 开销
重复历史 prompt 会浪费大量 token;previous_response_id 允许后续请求只发送「新输入」,其余上下文由服务端复用,理论上可以显著降低消耗。