Files
AnotherReplayReader/AI_REASONING_CONTINUATION_RESEARCH.md
T
2026-09-08 18:39:04 +02:00

9.6 KiB
Raw Blame History

AI reasoning_content 回传与“继续思考”实验研究报告

日期:2026-08-23
范围:OpenCodeGo / deepseek-v4-flashOpenAI 兼容 /chat/completions
状态:实验性研究,未集成到主项目;主项目曾短暂集成推理保护,后因实测影响输出质量而移除(详见 PLAN_ai_analysis_v2.md

摘要

本报告记录了为验证“将模型上一轮 reasoning_content 截断/修改后回传,模型是否能够继续合理思考”而执行的一系列实验。

结论是:仅发送 reasoning_content 或“上一轮思维链”并不稳定;成功率最高的方案是伪造一段 tool call 历史,并把续写要求、输出格式和需要引用的内容放入 tool 结果中。

与本研究相关的实验代码已从 AiV2.Tests/Program.cs 中移除,不再保留可执行实验入口。

目标

验证以下问题:

  1. 模型能否在不追加新的 user 消息时,基于 assistant 的 reasoning_content 继续生成最终回答?
  2. 截断后的 reasoning_content 是否仍然能被模型读取和引用?
  3. 伪造 tool_calls + tool 结果历史是否比普通多轮对话更有效?
  4. 工具结果中的措辞是否会显著影响模型对“目标思维链”的定位?
  5. 标记在思维链中的位置是否影响模型的可召回性?

实验方法与基础设施

所有实验使用:

  • 当前应用配置文件中的 OpenCodeGo / deepseek-v4-flash
  • stream=false 的一次性 OpenAI 兼容请求
  • 初始问题: A 比 B 高 20%B 比 C 高 25%,那么 A 比 C 高多少?
  • 确定性记忆标记: TOKEN_MARK=731942

专用测试位于 AiV2.Tests/Program.cs,默认跳过。

执行方式:

dotnet build .\AiV2.Tests\AiV2.Tests.csproj /p:AiV2TestsBuilding=true
$env:ARR_AI_E2E = "1"
$env:ARR_AI_E2E_TOOL = "1"
$env:ARR_AI_TOOL_REPORT_PATH = "AI_reasoning_continuation_position_sweep_report.md"
& .\AiV2.Tests\bin\Debug\net461\AiV2.Tests.exe

尝试过程

1. 普通多轮:assistant 推理 + 正文再回传

消息结构:

system → user → assistant(reasoning_content + content) → user

结果:协议层接受 reasoning_content,模型通常会重新推导,但无法确认它真正延续了旧思维链。

2. 只发 assistant 推理,不追加 user

消息结构:

system → user → assistant(reasoning_contentcontent 不发送)

结果:OpenCodeGo 接受最后一条 assistant 消息并生成完成,但新 reasoning 往往从题目重新开始。

3. 伪造 tool call 历史

消息结构:

system
→ user
→ assistant(content 不发送, reasoning_content, tool_calls)
→ tool(tool_call_id, 工具结果)

请求同时声明 tools,并将 tool_choice 设置为 "none"

工具结果中可以注入:

  • “继续当前推理”指令
  • 输出格式要求
  • 需要引用的旧思维链内容
  • “如果没有该内容,明确回答不存在”的边界条件

这一步开始观察到:模型可能在部分运行中读取并引用 reasoning_content

4. 确定性记忆标记

为避免模型从工具结果中照抄标记,标记值只注入到 reasoning_contenttool 结果中不出现标记值。

5. 措辞 A/B

对比三种措辞:

A:上一轮思维链
B:当前推理
C:调用工具之前的思维链
D:本次 tool_calls 消息中携带的 reasoning_content

去掉“上一轮”“截断点”等歧义词后,B/D 的命中率明显更高。

6. 标记位置扫描

使用成功率最高的两种措辞:

  • B:当前推理
  • D:本次 tool_calls 消息中携带的 reasoning_content

标记插入完整思维链的 25%、50%、75%、末尾四个位置,每个组合重复 5 次。

结果

措辞 A/B(每个组合 3 次)

工具提示措辞 正确写出标记 明确说标记不存在
A:上一轮思维链 2 / 3 1 / 3
B:当前推理 3 / 3 0 / 3
C:调用工具之前的思维链 2 / 3 1 / 3
Dtool_calls 携带的 reasoning_content 3 / 3 0 / 3

位置扫描(每个组合 5 次)

措辞 标记位置 正确写出标记 明确说没有 请求失败
B 25% 4 / 5 1 / 5 0
B 50% 5 / 5 1 / 5 0
B 75% 5 / 5 1 / 5 0
B 末尾 3 / 5 3 / 5 0
D 25% 4 / 5 0 / 5 1HTTP 503
D 50% 5 / 5 0 / 5 0
D 75% 5 / 5 2 / 5 0
D 末尾 4 / 5 2 / 5 0

关键发现

  1. tool call 是当前最有效的载体。

把“继续推理、输出格式、引用要求”放进 tool 结果,模型会把这些内容当作任务输入,而不是普通 user 消息。

  1. 措辞决定模型能否正确定位思维链。

“当前推理”和“本次 tool_calls 消息中携带的 reasoning_content”比“上一轮思维链”更稳定。不要使用“上一轮”“截断点”这些容易引起歧义的词。

  1. 标记在思维链开头/中部时最容易召回。

50% 和 75% 位置均为 5/5;末尾位置明显下降。这说明不要把关键事实放在 reasoning_content 末尾。

  1. “正确写出标记”和“明确说没有”不是互斥的。

模型有时先否定、后在同一输出中写出标记。统计时两者可能同时为真,人工审阅必须以完整 reasoning 和正文为准。

  1. 服务端不稳定。

D/25% 有一次 HTTP 503,属于服务端错误,不是模型失败。OpenCodeGo 未返回 usage,因此无法评估成本/token。

  1. 主项目不应直接启用该机制。

当前实验只能证明“部分情况下有效”,不足以作为生产依赖。

推荐方案

推荐消息结构

system
→ user(原始任务)
→ assistant(
     content 不发送,
     reasoning_content,
     tool_calls: [{ id, type: "function", function: { name, arguments } }]
   )
→ tool(
     tool_call_id,
     content: "工具结果:请继续你本次 tool_calls 消息中携带的 reasoning_content。
              请原样写出其中的内部记忆标记;如果没有,请明确回答“标记不存在”。
              请使用 Markdown 输出。"
   )

请求级配置:

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "analysis_hint",
        "description": "提供继续上一个内部推理的提示",
        "parameters": {
          "type": "object",
          "properties": {
            "instruction": { "type": "string" }
          },
          "required": ["instruction"]
        }
      }
    }
  ],
  "tool_choice": "none"
}

推荐提示词措辞

推荐:

请继续你当前正在进行的内部推理。
请继续你本次 tool_calls 消息中携带的 reasoning_content。

不推荐:

上一轮思维链在这里被截断,请继续。

对主项目的建议

  • 保留现有 UI 对 reasoning_content 的展示。
  • 不要在生产管线中自动发送/截断/修改 reasoning_content
  • 如未来需要接入,优先使用 tool call 载体,并将关键指令、格式和引用要求放入 tool 结果。
  • 不要假设 reasoning_content 的末尾内容一定能被模型召回。
  • 增加原始响应/请求日志,以便区分“模型未看到”和“模型看到了但没引用”。

主项目集成状态(2026-08-23,历史记录;该功能已全部移除)

以下内容是当时的集成状态,不代表当前代码。当前主项目已移除推理保护及所有相关代码/测试。

  • 主项目曾落地模型级“推理保护”实验开关,默认关闭,仅在 IsStream=true 的模型上生效。
  • 累计推理 token 达到阈值后停止读取当前 SSE 响应,保留部分推理和正文;最多发起一次续写。
  • 续写采用本报告推荐的伪造 tool call 历史:assistant(reasoning_content + tool_calls)tool(tool_call_id + 收尾指令)
  • 首请求不携带 tools,只有续写请求注入 toolstool_choice=none;缓存命中为 best-effort。
  • 保留推理在其末尾依次追加 [INTERNAL_REASONING_TRUNCATED] 与中文收尾句;收尾句不插入中间 checkpoint,也不提及“token limit/被截断”等技术细节。
  • 续写工具结果包含“进入收尾阶段、立即停止展开、直接输出最终结果”和“最后一句已经宣告收尾,请立即执行”等强化措辞。
  • UI 在触发推理保护后提供两个可折叠日志:首次请求消息与续写请求完整消息;用户可展开查看完整 messagestoolstool_choice 和收尾指令。
  • UI 进一步改为阶段化时间线:推理保护事件与诊断在续写思考块之前输出;修订保留旧版本并折叠,最新版本展开;机器可读声明 JSON 与验证结果独立展示。
  • UI 正文在成功提取机器可读声明后会移除 JSON 原文;推理保护诊断显示可读摘要(消息数变化、新增 assistant/tool 消息),完整续写 JSON 作为折叠附件;总览、修订和回查的 user prompt 会以折叠日志展示。
  • 默认测试新增 ReasoningGuard 套件后总计 173 项通过;真实 API A/B 仍需要用户手工运行 ARR_AI_E2E=1 验证。
  • 可选真实环境测试为 OpenCodeGoGuardE2e:设置 ARR_AI_GUARD_E2E=1 启用,报告路径可用 ARR_AI_GUARD_REPORT_PATH 指定。

专用测试状态

  • 原测试代码:AiV2.Tests/Program.cs 中的 OpenCodeGoFakeToolCallTests
  • 现状:已随推理保护一起移除,不再保留可执行实验入口。

主项目中的实验性 ChatMessage 扩展、AiReasoningContinuationSettings、UI 开关、回传/降级逻辑和 tool call 历史构造均已移除。