8.9 KiB
AI reasoning_content 回传与“继续思考”实验研究报告
日期:2026-08-23
范围:OpenCodeGo / deepseek-v4-flash,OpenAI 兼容 /chat/completions
状态:实验性研究,未集成到主项目
摘要
本报告记录了为验证“将模型上一轮 reasoning_content 截断/修改后回传,模型是否能够继续合理思考”而执行的一系列实验。
结论是:仅发送 reasoning_content 或“上一轮思维链”并不稳定;成功率最高的方案是伪造一段 tool call 历史,并把续写要求、输出格式和需要引用的内容放入 tool 结果中。
与本研究相关的代码在 AiV2.Tests/Program.cs 中,默认不会执行。
目标
验证以下问题:
- 模型能否在不追加新的 user 消息时,基于 assistant 的
reasoning_content继续生成最终回答? - 截断后的
reasoning_content是否仍然能被模型读取和引用? - 伪造
tool_calls+tool结果历史是否比普通多轮对话更有效? - 工具结果中的措辞是否会显著影响模型对“目标思维链”的定位?
- 标记在思维链中的位置是否影响模型的可召回性?
实验方法与基础设施
所有实验使用:
- 当前应用配置文件中的 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_content,content 不发送)
结果: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_content,tool 结果中不出现标记值。
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 |
| D:tool_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 | 1(HTTP 503) |
| D | 50% | 5 / 5 | 0 / 5 | 0 |
| D | 75% | 5 / 5 | 2 / 5 | 0 |
| D | 末尾 | 4 / 5 | 2 / 5 | 0 |
关键发现
- tool call 是当前最有效的载体。
把“继续推理、输出格式、引用要求”放进 tool 结果,模型会把这些内容当作任务输入,而不是普通 user 消息。
- 措辞决定模型能否正确定位思维链。
“当前推理”和“本次 tool_calls 消息中携带的 reasoning_content”比“上一轮思维链”更稳定。不要使用“上一轮”“截断点”这些容易引起歧义的词。
- 标记在思维链开头/中部时最容易召回。
50% 和 75% 位置均为 5/5;末尾位置明显下降。这说明不要把关键事实放在 reasoning_content 末尾。
- “正确写出标记”和“明确说没有”不是互斥的。
模型有时先否定、后在同一输出中写出标记。统计时两者可能同时为真,人工审阅必须以完整 reasoning 和正文为准。
- 服务端不稳定。
D/25% 有一次 HTTP 503,属于服务端错误,不是模型失败。OpenCodeGo 未返回 usage,因此无法评估成本/token。
- 主项目不应直接启用该机制。
当前实验只能证明“部分情况下有效”,不足以作为生产依赖。
推荐方案
推荐消息结构
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,只有续写请求注入tools与tool_choice=none;缓存命中为 best-effort。 - 保留推理在其末尾依次追加
[INTERNAL_REASONING_TRUNCATED]与中文收尾句;收尾句不插入中间 checkpoint,也不提及“token limit/被截断”等技术细节。 - 续写工具结果包含“进入收尾阶段、立即停止展开、直接输出最终结果”和“最后一句已经宣告收尾,请立即执行”等强化措辞。
- UI 在触发推理保护后提供两个可折叠日志:首次请求消息与续写请求完整消息;用户可展开查看完整
messages、tools、tool_choice和收尾指令。 - 默认测试新增
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。 - 默认行为:不执行。
- 启用条件:
ARR_AI_E2E=1且ARR_AI_E2E_TOOL=1。 - 输出:Markdown 报告,路径通过
ARR_AI_TOOL_REPORT_PATH指定。
主项目中的实验性 ChatMessage 扩展、AiReasoningContinuationSettings、UI 开关、回传/降级逻辑和 tool call 历史构造均已移除。