SolonCode 的两种“后悔药”:Re-run 与 Continue 到底改了什么

2026-08-22 29 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

SolonCode Web 聊天界面底部的工具栏提供了三个操作:复制上一条回复、重新运行(Re-run)和继续运行(Continue)。后两个按钮看起来都像“让模型再执行一次”,但它们处理旧回答的方式完全相反:Re-run 删除旧回答并重新生成,Continue 保留旧回答并在其后追加内容。理解这一区别,关键不在按钮文案,而在消息状态和上下文如何变化。

两个按钮解决的是不同问题

假设用户输入了一条任务指令,系统已经生成回答 A。

点击 Re-run 时,界面会删除回答 A,再使用相同输入启动一轮生成。完成后,回答 B 占据原来回答 A 的位置。这个操作适合处理以下情况:

  • 回答方向错误,希望彻底重来;
  • 生成过程异常中断,需要从原输入重新执行;
  • 想利用模型输出的不确定性,获得另一种完整答案;
  • 不希望旧回答继续进入后续处理流程。

点击 Continue 时,回答 A 会被保留,系统在现有结果之后继续生成。它更适合:

  • 回答因长度限制而停止;
  • 代码只生成了一半;
  • 任务已经走在正确方向上,只需要补齐剩余内容;
  • 希望模型基于已有结果继续推导,而不是推翻重做。

可以把两者概括为:

Re-run:   输入 -> 删除旧回答 -> 重新生成完整回答
Continue: 输入 + 旧回答 -> 保留旧回答 -> 生成追加内容

真正的差异在状态转换

从聊天状态的角度看,Re-run 是“替换”,Continue 是“追加”。二者不能只共用一个普通的 sendMessage(),然后靠界面决定是否隐藏旧内容,因为发送给模型的上下文也可能不同。

基于来源描述,可以这样实践一个最小状态模型。下面的 Node.js 示例不依赖第三方包,用模拟模型展示两种操作对消息列表的影响。将代码保存为 demo.mjs,然后执行 node demo.mjs

const initialMessages = [
  { id: "u1", role: "user", content: "生成一个分页查询函数" },
  { id: "a1", role: "assistant", content: "旧回答:函数只写到一半……" }
];

async function callModel(messages, mode) {
  const lastUser = [...messages].reverse().find((m) => m.role === "user");
  const previousAnswer = [...messages]
    .reverse()
    .find((m) => m.role === "assistant");

  if (mode === "continue") {
    return `追加内容:基于“${previousAnswer.content}”继续完成分页逻辑。`;
  }

  return `新回答:重新处理“${lastUser.content}”,生成完整分页实现。`;
}

async function rerun(messages) {
  const next = [...messages];

  if (next.at(-1)?.role === "assistant") {
    next.pop();
  }

  const content = await callModel(next, "rerun");
  next.push({ id: crypto.randomUUID(), role: "assistant", content });
  return next;
}

async function continueRun(messages) {
  const next = [...messages];
  const content = await callModel(next, "continue");
  next.push({ id: crypto.randomUUID(), role: "assistant", content });
  return next;
}

console.log("Re-run 结果:");
console.table(await rerun(initialMessages));

console.log("Continue 结果:");
console.table(await continueRun(initialMessages));

这个示例是对交互语义的简化建模,并不代表 SolonCode 的内部源码实现。它强调了一个重要边界:Re-run 调用模型前先移除旧回答,而 Continue 调用模型时保留旧回答,使追加生成能够参考已有内容。

前端不能只处理“气泡”

如果在真实 Web 应用中实现这两个按钮,需要同时考虑界面状态、持久化记录和流式响应。

Re-run 的典型流程可以这样设计:

  1. 找到需要替换的 assistant 消息;
  2. 暂时禁用 Re-run 和 Continue,避免重复请求;
  3. 从本地状态或服务端会话中移除旧回答;
  4. 使用对应的用户输入重新发起请求;
  5. 创建新的回答气泡并接收流式片段;
  6. 请求失败时恢复旧回答,或提供明确的重试入口。

Continue 的流程则不同:

  1. 保留当前回答;
  2. 将已有对话作为上下文发给模型;
  3. 新建追加消息,或明确地把流式内容拼接到当前消息;
  4. 记录本次操作为 continue,避免后端误判为新问题;
  5. 防止用户连续点击产生多个并发追加任务。

这里有一个产品层面的决定:Continue 的结果究竟应该成为新气泡,还是拼接进原气泡?来源描述强调“保留旧回答,继续追加内容”,但没有给出数据层的具体结构。工程上更建议保留独立消息记录,再在视觉上连续展示。这样更容易撤销、审计和处理失败重试。

流式生成中的失败处理

这两个操作遇到网络中断时,风险也不同。

Re-run 通常先删除旧回答。如果新请求失败,而客户端又没有保存快照,用户会同时失去旧结果和新结果。因此,实现时应在请求成功建立之前保留旧消息快照,失败后执行回滚。

Continue 不会删除旧回答,但可能留下半截追加内容。应用需要给追加消息标记状态,例如:

{
  id: "a2",
  role: "assistant",
  content: "正在追加的部分内容……",
  operation: "continue",
  status: "interrupted"
}

有了 operationstatus,界面才能准确提供“继续该追加任务”“重新运行原问题”或“删除未完成内容”等操作,而不是把所有失败都压缩成一个含义模糊的重试按钮。

落地时检查这几件事

实现或使用这两个操作时,可以按下面的清单确认行为:

  • Re-run 是否真的从模型上下文中移除了旧回答,而不只是隐藏气泡;
  • Re-run 失败后能否恢复旧回答;
  • Continue 是否携带了已有回答所需的上下文;
  • 两个按钮在流式生成期间是否被禁用;
  • 重复点击是否会创建并发请求;
  • 服务端是否记录操作类型、父消息和请求标识;
  • Continue 产生的半截内容是否可以继续、删除或重试;
  • 重新生成后,后续对话引用的是新回答还是已删除的旧回答。

Re-run 和 Continue 的界面差异只有一个按钮,但数据语义并不相近。前者把当前回答视为可替换结果,后者把它视为下一轮生成的起点。把这两个状态转换分开建模,才能避免上下文污染、消息丢失和重复生成。


相关推荐