Files
AnotherReplayReader/AI_REASONING_CONTINUATION_RESEARCH.md
T
2026-08-23 22:34:30 +02:00

255 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI reasoning_content 回传与“继续思考”实验研究报告
日期:2026-08-23
范围:OpenCodeGo / `deepseek-v4-flash`OpenAI 兼容 `/chat/completions`
状态:实验性研究,未集成到主项目
## 摘要
本报告记录了为验证“将模型上一轮 `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`,默认跳过。
执行方式:
```powershell
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 推理 + 正文再回传
消息结构:
```text
system → user → assistant(reasoning_content + content) → user
```
结果:协议层接受 `reasoning_content`,模型通常会重新推导,但无法确认它真正延续了旧思维链。
### 2. 只发 assistant 推理,不追加 user
消息结构:
```text
system → user → assistant(reasoning_contentcontent 不发送)
```
结果:OpenCodeGo 接受最后一条 assistant 消息并生成完成,但新 reasoning 往往从题目重新开始。
### 3. 伪造 tool call 历史
消息结构:
```text
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
对比三种措辞:
```text
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 消息。
2. **措辞决定模型能否正确定位思维链。**
“当前推理”和“本次 tool_calls 消息中携带的 reasoning_content”比“上一轮思维链”更稳定。不要使用“上一轮”“截断点”这些容易引起歧义的词。
3. **标记在思维链开头/中部时最容易召回。**
50% 和 75% 位置均为 5/5;末尾位置明显下降。这说明不要把关键事实放在 `reasoning_content` 末尾。
4. **“正确写出标记”和“明确说没有”不是互斥的。**
模型有时先否定、后在同一输出中写出标记。统计时两者可能同时为真,人工审阅必须以完整 reasoning 和正文为准。
5. **服务端不稳定。**
D/25% 有一次 HTTP 503,属于服务端错误,不是模型失败。OpenCodeGo 未返回 `usage`,因此无法评估成本/token。
6. **主项目不应直接启用该机制。**
当前实验只能证明“部分情况下有效”,不足以作为生产依赖。
## 推荐方案
### 推荐消息结构
```text
system
→ user(原始任务)
→ assistant(
content 不发送,
reasoning_content,
tool_calls: [{ id, type: "function", function: { name, arguments } }]
)
→ tool(
tool_call_id,
content: "工具结果:请继续你本次 tool_calls 消息中携带的 reasoning_content。
请原样写出其中的内部记忆标记;如果没有,请明确回答“标记不存在”。
请使用 Markdown 输出。"
)
```
请求级配置:
```json
{
"tools": [
{
"type": "function",
"function": {
"name": "analysis_hint",
"description": "提供继续上一个内部推理的提示",
"parameters": {
"type": "object",
"properties": {
"instruction": { "type": "string" }
},
"required": ["instruction"]
}
}
}
],
"tool_choice": "none"
}
```
### 推荐提示词措辞
推荐:
```text
请继续你当前正在进行的内部推理。
请继续你本次 tool_calls 消息中携带的 reasoning_content。
```
不推荐:
```text
上一轮思维链在这里被截断,请继续。
```
## 对主项目的建议
- 保留现有 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 历史构造均已移除。