前言
网上掀起了 DeepSeek 安全隐患的热潮?具体是有人发现:在新对话里什么问题都不问,只输入一个
<think>,模型可能直接进入“已思考”状态,开始自说自话、角色扮演,甚至输出看似无关的推理片段。有人把它称为隐藏咒语,也有人怀疑这是训练数据泄漏。那它到底是安全漏洞,还是机器学习里的前缀续写效应?本文就从这个现象出发,拆解<think>背后的自回归生成、CoT 分布和工程边界。
一、先说结论:这不是咒语,而是前缀改变了生成分布
💡 核心结论: <think> 未闭合更像是给模型提供了一个“思考区块正在开始、但还没有结束”的强前缀。模型会沿着这个前缀继续生成,于是输出更容易进入 reasoning / CoT 风格的续写分布。
这件事可以拆成一条因果链:
输入 <think>
-> 模型看到一个类似“思考开始”的前缀
-> 后续 token 概率分布向 reasoning / CoT 内容倾斜
-> 如果没有明确问题作为锚点,生成方向就更容易漂移
-> 输出可能出现故事、计算、角色扮演、无关解释等训练分布中的模式片段
这里最关键的词是:前缀。
大语言模型不是在“理解完整问题后再查数据库回答”,而是根据当前上下文不断预测下一个 token。一个不同的开头,会改变后面所有内容的概率分布。
二、现象对照:为什么新对话里的 <think> 更明显
在观察实验中,一个重要条件是:只有开新对话,<think> 的效果才明显。
这很合理。因为旧对话里已经存在上下文,模型有历史问题、历史回答、任务方向作为锚点;而新对话几乎没有上下文,裸 <think> 就会成为最强的上下文信号。
可以用下面几组输入做对照:
| 输入内容 | 观察重点 | 可能说明 |
|---|---|---|
<think> | 是否进入长段思考、自述、跑偏输出 | 未闭合思考前缀可能触发 reasoning 续写 |
<think></think> | 是否比未闭合版本更稳定 | 闭合标签削弱“继续写思考”的压力 |
<abc> | 是否只是解释普通字符串 | 测试未知 XML/HTML 样式标签本身是否足以触发漂移 |
1+1 等于几 | 是否稳定回答 2 | 明确任务锚点会约束生成方向 |
<think> | 是否被当作普通文本 | 转义形式可用于判断前端/模型是否按标签处理 |
从这些对照可以看到:普通问题会把模型拉回明确任务,而裸 <think> 更容易让模型进入“我应该开始思考点什么”的状态。
更精确地说,<abc> 测的是“未知标签形式”的影响,<think> 测的是“已知 reasoning / CoT 结构标记”的影响。两者对比的意义在于:如果 <abc> 没有明显触发同类输出,而 <think> 触发了,那么关键就不是尖括号或标签形式本身,而是 think 这个字符串与“思考开始”模式之间的强关联。
三、从机器学习角度看:模型为什么会“继续写”
先补一个核心定义:什么是 CoT / reasoning 分布?
CoT 是 Chain-of-Thought,也就是“思维链”。这类训练文本通常长得像:
问题
思考过程:我们可以先……然后……注意这里可能有陷阱……
最终答案
它的特征是包含大量中间推理、自我修正、步骤拆解和草稿式表达。模型学过大量这类文本后,就会学到一种模式:遇到“开始思考”的信号时,后面更可能接一段推理过程,而不是直接输出最终答案。
所以本文说的 reasoning / CoT 续写分布,不是一个神秘开关,而是模型在训练中学到的一类文本模式:先打草稿,再给答案。
大语言模型的基础生成方式是自回归生成。简单说,就是:
P(下一个 token | 前面所有 token)
模型每一步都根据前面已经出现的内容,预测下一个最可能出现的 token。然后把新 token 接到上下文后面,再继续预测下一个。
所以模型的工作方式不是:
看到问题 -> 找标准答案 -> 输出答案
而更接近:
看到前缀 -> 判断后面最像什么 -> 继续写下去
这就是为什么 <think> 这种前缀有影响。它不是普通的三个单词,而是一个很像 reasoning 模型内部格式的结构提示。
如果模型在训练或对齐过程中大量见过类似结构:
<think>
这里是一段推理、分析、尝试、修正……
</think>
最终答案……
那么当它在新对话开头看到一个未闭合的 <think> 时,后续分布自然会更靠近“继续写思考内容”。
可以把这个过程想象成一张巨大的高维概率地形图。用户输入就像把一颗小球放到某个地形点上:
| 输入类型 | 小球落点 | 后续趋势 |
|---|---|---|
| 普通问题 | 标准问答区域 | 沿着“解释问题、给出答案”的路径前进 |
<abc> | 未知标签区域 | 可能询问含义,也可能解释输入本身 |
<think> | 思考链深谷入口 | 更容易沿着“先推理、再输出”的谷底继续滚动 |
<think> + 明确问题 | 思考链入口 + 任务锚点 | 既可能展示推理痕迹,也更容易回到具体问题 |
因此,“乱说”不是模型突然失控,而是它被放到了“思考链”这条深谷的起点;在没有具体问题导航时,它只能顺着概率地形继续滑下去。
四、为什么“未闭合”比“闭合”更容易触发续写
未闭合结构会给语言模型一种很强的补全压力。
比如人类看到:
<div>
这里是一段内容
也会自然期待后面出现:
</div>
语言模型同样会学习这种结构延续关系。<think> 的特殊之处在于,它不只是一个普通 XML/HTML 样式标签,还容易和 reasoning / CoT 的格式联系起来。
对比一下:
| 写法 | 结构状态 | 模型可能倾向 |
|---|---|---|
<think> | 思考区块刚开始,未闭合 | 继续写思考内容 |
<think></think> | 空思考区块已闭合 | 询问用户具体问题或解释输入 |
<abc> | 未知普通标签 | 解释字符串或询问意图 |
<think> | 转义文本 | 更可能被当成普通文本 |
💡 核心结论: 真正重要的不是 <think> 这几个字符“神秘”,而是它在模型分布里像一个“思考内容开始”的结构前缀。
五、为什么它会输出看似“训练数据碎片”的内容
当模型只有 <think>,但没有真实问题时,它缺少生成锚点。
正常情况下,用户会问:
请解释 Transformer 的注意力机制
这时模型的输出会被“Transformer”“注意力机制”“解释”这些关键词约束。
但如果新对话里只有:
<think>
模型知道“好像要开始思考”,却不知道“思考什么”。于是它只能从训练过程中学到的大量模式里继续采样。可能是数学题,可能是对话,可能是故事,也可能是某种角色扮演。
这会让输出看起来像“从训练数据里冒出来的碎片”。
为什么会出现故事、角色扮演或看起来很具体的场景?一个合理解释是:许多 reasoning 样本本身就不是纯公式,而是带有场景的任务文本。例如数学题会写“我手里有 3 个苹果”,代码题会描述一个业务场景,角色类指令会要求模型先分析身份、目标和约束。对模型来说,这些都是“思考链”训练分布的一部分。
当 <think> 把模型推向这类分布,但又没有真实问题限制方向时,模型就可能抽到某个场景化模式:一会儿像数学题,一会儿像小说开头,一会儿像角色扮演。这更像是训练分布中的模式回声,而不是可以直接判定为逐字泄漏。
但这里必须谨慎区分两个概念:
| 说法 | 是否可以直接下结论 | 说明 |
|---|---|---|
| 训练分布中的模式片段 | 可以谨慎使用 | 表示模型生成了训练中常见的风格、结构、任务形式 |
| 疑似训练数据碎片 | 可以作为待验证说法 | 需要继续检索输出文本是否能匹配公开语料 |
| 训练数据泄漏 | 不建议直接写死 | 需要大段逐字一致证据和可重复实验 |
⚠️ 误区:输出像训练数据,就等于训练数据泄漏
正确理解: 大模型确实可能记忆并复现训练数据片段,但“风格像”“结构像”“主题像”都不能直接证明逐字泄漏。要证明泄漏,至少需要把输出文本拿去检索,确认是否存在大段逐字一致来源。
六、DeepSeek API 文档给了哪些工程证据
DeepSeek 官方 reasoning model API 文档里有一个非常重要的工程事实:deepseek-reasoner 的输出中,思考内容和最终回答是分开的。
| 字段 | 含义 | 工程处理建议 |
|---|---|---|
reasoning_content | 模型生成的 CoT / 思考内容 | 可以展示、折叠、记录,但不要直接带回下一轮请求 |
content | 最终回答 | 多轮对话中作为 assistant 内容加入历史消息 |
官方文档还说明:如果输入消息序列中包含 reasoning_content 字段,API 会返回 400 错误。因此,下一轮对话要移除旧的 reasoning_content,只保留最终回答 content。
✅ 正确处理 reasoning 输出的最小示例
from openai import OpenAI
client = OpenAI(
api_key="<DeepSeek API Key>",
base_url="https://api.deepseek.com"
)
messages = [
{"role": "user", "content": "9.11 和 9.8 哪个更大?"}
]
response = client.chat.completions.create(
model="deepseek-reasoner",
messages=messages
)
reasoning_content = response.choices[0].message.reasoning_content
content = response.choices[0].message.content
# 下一轮只保留最终回答 content,不把 reasoning_content 放回 messages
messages.append({"role": "assistant", "content": content})
messages.append({"role": "user", "content": "strawberry 里有几个 r?"})
这说明 reasoning 内容本来就应该作为一个独立通道处理,而不是粗暴混进正文、标题、TTS 或下一轮上下文。
七、这和 Prompt Injection 有什么关系
从安全角度看,<think> 现象只是一个入口更低的例子。更大的问题是:模型如何区分“用户普通文本”和“系统结构信号”?
如果一些原本只应该由系统后端生成的结构标记,被用户、网页、RAG 文档或工具返回值伪造,就可能出现更复杂的问题。
| 场景 | 可能风险 |
|---|---|
普通聊天输入 <think> | 输出漂移、进入思考续写 |
| RAG 文档夹带伪造角色标签 | 模型误把文档内容当成指令 |
| Agent 工具返回伪造系统提示 | 模型可能误判权限和操作意图 |
| 标题生成/TTS 混入 reasoning 内容 | 用户体验混乱,甚至泄露不该展示的中间内容 |
所以工程上通常需要:
- 对用户输入做必要的转义和边界隔离;
- 不让用户文本直接伪装成系统控制 token;
- 将 reasoning 内容与最终回答分离处理;
- 对 RAG 文档和工具返回值标记来源;
- 对自动标题、摘要、TTS 等任务流过滤
<think>或 reasoning 内容。
八、这篇文章里哪些结论需要保留边界
为了避免把现象写成过度结论,下面这些说法不建议直接使用:
⚠️ 误区:DeepSeek 输入
<think>就说明训练数据泄漏正确理解: 目前更稳妥的说法是:
<think>未闭合可能让模型进入 reasoning 续写分布,并生成训练分布中常见的模式内容。是否构成训练数据泄漏,需要逐字检索和可重复实验。
⚠️ 误区:截图能证明 tokenizer 一定把
<think>当作特殊 token id正确理解: 截图只能证明产品层输出行为异常或有差异,不能直接证明内部 tokenization。要证明这一点,需要 tokenizer 配置、API 复现、模型模板或官方说明。
⚠️ 误区:一次截图就能证明稳定触发
正确理解: 单次截图适合做现象展示,不适合证明概率结论。要证明“稳定触发”,需要每组输入重复测试,并记录时间、模式、入口和完整输出。
总结
| 关键问题 | 稳妥结论 |
|---|---|
<think> 为什么会影响输出? | 它像一个 reasoning / CoT 的开始前缀,会改变后续生成分布 |
| 为什么新对话更明显? | 新对话没有历史锚点,<think> 成为最强上下文信号 |
| 为什么会乱写? | 模型必须根据前缀继续预测,下游分布缺少具体任务约束 |
| 是否等于训练数据泄漏? | 不能直接下结论,需要逐字检索和可重复实验 |
| 工程上该注意什么? | 分离 reasoning_content 和 content,不要把旧思考链带回下一轮 |
💡 核心结论: <think> 未闭合不是所谓隐藏咒语,而是一个前缀触发分布变化的机器学习现象。它让模型更像是在继续写一段未完成的思考过程;当没有明确问题约束时,输出就可能漂移到训练分布中的各种模式。
如果要继续验证,最有价值的实验不是多截几张“跑偏图”,而是做系统对照:<think>、<think></think>、<abc>、普通问题、完整转义 <think>,每组重复多次,再把疑似训练片段拿去逐字检索。这样才能把“有趣现象”推进到“可信结论”。