长推理模型外挂搜索:Agentic RAG与Search-o1实战解析 1. 长推理模型为什么需要“外挂搜索”1.1 从“闭卷考试”到“开卷考试”的思维转变大语言模型做长链推理时最让人头疼的不是它不会算而是它算着算着就“忘了前提”。我拿一个真实场景举例你让模型推导一个多步数学证明前五步它还记得用到了某个定理到第十五步时它突然开始编造一个不存在的公式整条推理链直接崩掉。这种现象在业界有个很形象的说法叫“推理漂移”——模型在长序列生成过程中注意力被稀释早期关键信息逐渐失焦。传统做法是加大上下文窗口把更多资料一次性塞进去。但这里有个反直觉的结论上下文越长模型对中间部分的利用率反而越低。这不是模型能力问题而是注意力机制本身的特性决定的。就好比你让一个人同时看十本书再回答问题他大概率只能记住开头和结尾的几页。Search-o1 这个思路的核心突破在于它不要求模型一次性记住所有东西而是让模型在推理过程中主动“停下来查资料”。这就像从闭卷考试变成了开卷考试而且是你自己决定什么时候翻书、翻哪一页。这个“主动”二字是整个方案的精髓也是 Agentic RAG 与传统 RAG 的分水岭。1.2 传统 RAG 的局限与 Agentic RAG 的破局点传统 RAG 的工作流是“检索-拼接-生成”三步走用户提问系统去向量库捞相关文档把文档和问题拼成一个超长 prompt 丢给模型。这个流程有两个硬伤。第一检索时机是固定的只在生成开始前做一次后续推理中如果发现缺信息模型只能硬着头皮编。第二检索内容是“批量”的一次捞回来一堆文档其中很多跟当前推理步骤无关反而成了噪声。Agentic RAG 把检索变成了一个“工具”模型可以在推理的任意步骤调用它。Search-o1 更进一步它让模型学会判断“我现在需不需要查”“我该查什么”“查回来的东西怎么融入当前推理”。这三个判断分别对应检索触发、查询构造和知识融合每一个都是独立的技术点。我实测下来这种主动搜索机制在数学证明、多跳问答、代码调试这三类任务上提升最明显。数学证明需要精确引用定理多跳问答需要串联多个事实代码调试需要查 API 文档——它们的共同点是推理路径长且每一步都依赖外部精确知识。1.3 适合哪些人深入这个方向如果你正在做以下任何一件事Search-o1 的思路都值得你花时间研究一是你在微调或部署长上下文模型发现推理质量随长度下降二是你在搭建企业知识库问答系统传统 RAG 的准确率卡在某个瓶颈上不去三是你在研究 Agent 架构想搞清楚“工具调用”在推理任务中到底该怎么设计。不需要你从头训练模型Search-o1 的核心贡献是一套推理框架和训练策略你可以在现有开源模型基础上复现。下面我会从设计思路、核心细节、实操过程到问题排查把这条链路完整拆一遍。2. 核心细节解析与实操要点2.1 推理链中的“搜索标记”是怎么设计的Search-o1 最巧妙的设计在于它没有把搜索做成一个外挂模块而是把搜索行为“内化”成了模型输出的一部分。具体来说模型在生成推理链时可以输出一个特殊的标记比如|search_start|查询内容|search_end|系统检测到这个标记就暂停生成去执行检索把结果插入到标记位置然后继续生成。这个设计的好处是搜索时机完全由模型自己决定而不是由外部规则硬编码。模型在推理过程中如果觉得“这里我需要查一下勾股定理的推广形式”它就会主动输出搜索标记。这比“每生成 N 个 token 就强制检索一次”要精准得多。但这里有个训练难点模型怎么学会在什么时候输出搜索标记答案是冷启动加强化学习。冷启动阶段用人工标注的推理链做监督微调这些推理链里已经标好了哪些步骤需要搜索。强化学习阶段则用最终答案的正确性作为奖励信号让模型自己探索最优的搜索策略。注意搜索标记的格式设计很关键。标记本身要足够独特避免和正常文本混淆同时标记要短否则会占用过多 token 预算。实践中常用的是特殊 token 或者不常见的符号组合。2.2 查询构造从“关键词堆砌”到“自然语言问句”传统 RAG 的查询构造通常是拿用户原始问题直接去检索或者做简单的关键词提取。但在长推理场景下当前推理步骤需要的往往是一个很具体的子问题跟原始问题可能只沾一点边。举个例子原始问题是“证明费马小定理的欧拉推广形式”模型推到中间可能需要查“欧拉函数 φ(n) 的定义和性质”。如果拿原始问题去检索捞回来的大概率是费马小定理的百科页面而不是欧拉函数的专门资料。Search-o1 让模型自己生成查询语句它就能根据当前推理状态构造出精准的子查询。我在复现时发现查询语句的质量直接决定检索命中率。模型生成的查询最好是完整的自然语言问句而不是关键词列表。因为现代检索器无论是稠密检索还是稀疏检索对自然语言问句的语义理解能力更强。你可以把查询构造理解成“模型在问一个它自己不知道答案的问题”问得越清楚检索器答得越准。2.3 检索结果如何“无缝”融入推理链检索回来的文档不能直接往推理链里一塞就完事那样会打断推理的连贯性。Search-o1 的做法是把检索结果放在搜索标记的位置但用特殊的边界标记包起来比如|knowledge_start|...|knowledge_end|。这样模型在后续生成时能区分“这是我自己的推理”和“这是查到的外部知识”。更关键的是模型需要学会“引用”检索结果而不是“照抄”。在训练数据中好的推理链会这样写“根据检索到的欧拉函数定义φ(n) 表示小于 n 且与 n 互质的正整数个数因此...”而不是直接把定义原文贴一遍。这个“转述加应用”的能力需要通过微调来注入。我踩过的一个坑是检索结果太长会挤占推理空间。实践中需要对检索结果做截断或摘要只保留和当前子问题最相关的部分。一般控制在 200-500 token 比较合适太短信息不够太长干扰推理。2.4 训练数据的构造冷启动与迭代优化Search-o1 的训练分两个阶段。第一阶段是冷启动需要构造一批“带搜索标记的推理链”作为监督数据。这批数据怎么来可以用强模型比如 GPT-4 级别在推理时人工插入搜索步骤然后人工校验合理性。这个过程很费人力但质量直接决定后续强化学习的天花板。第二阶段是强化学习用最终答案的正确性作为奖励。这里有个细节奖励不能只看最终答案还要看推理链的合理性。否则模型可能学会“疯狂搜索”来碰运气搜十次总有一次能撞对答案。实践中会在奖励函数里加一个搜索次数的惩罚项鼓励模型用最少的搜索次数解决问题。提示如果你资源有限可以只做冷启动阶段用监督微调的方式让模型学会基本的搜索触发和查询构造。效果虽然不如完整的两阶段训练但在很多任务上已经比传统 RAG 有明显提升。3. 实操过程与核心环节实现3.1 环境准备与基础模型选型复现 Search-o1 不需要从头训练一个模型你可以基于现有的开源长推理模型做微调。我推荐从以下几个方向选型一是已经具备长链推理能力的模型比如经过数学推理微调的 7B 或 13B 模型二是上下文窗口至少 8K 的模型因为推理链加检索结果很容易超过 4K三是支持特殊 token 扩展的模型方便加入搜索标记。环境配置方面我用的是一张 24G 显存的卡做 7B 模型的 LoRA 微调。具体依赖包括 PyTorch 2.0 以上、Transformers 4.36 以上、PEFT 做参数高效微调、Faiss 做向量检索。检索器可以用现成的稠密检索模型比如 Contriever 或 BGE 系列不需要自己训练。pip install torch transformers peft datasets faiss-cpu sentence-transformers如果你要做完整的强化学习阶段还需要安装 TRL 或 VERL 这类强化学习训练框架。但我的建议是先跑通冷启动加推理的流程确认效果后再考虑上强化学习。3.2 构造冷启动数据的具体步骤冷启动数据的质量决定了模型能不能学会“该搜的时候搜”。我的做法是分三步走。第一步收集一批需要多步推理的任务比如数学竞赛题、多跳问答数据集。第二步用强模型生成推理链在生成过程中人工标注哪些步骤需要外部知识。第三步把标注好的推理链转换成训练格式搜索标记和知识边界标记都要正确嵌套。这里有个实操技巧不要所有推理步骤都标搜索只在“模型确实不知道”的地方标。比如“112”这种常识不需要搜“2024年诺贝尔物理学奖得主”这种时效性信息才需要搜。训练数据中搜索步骤的比例控制在 20%-40% 比较合理太低学不会搜索太高会让模型过度依赖检索。数据格式大概长这样{ question: 证明对于任意正整数 n存在 n 个连续正整数都是合数。, reasoning: 我们需要构造 n 个连续合数。|search_start|如何构造连续合数|search_end||knowledge_start|常用方法是利用阶乘(n1)!2, (n1)!3, ..., (n1)!(n1) 都是合数。|knowledge_end|因此取 (n1)!2 到 (n1)!(n1) 即可。 }3.3 微调训练的关键参数设置我用 LoRA 做微调rank 设为 16alpha 设为 32dropout 0.05。学习率用 2e-4cosine 调度warmup 比例 0.03。batch size 根据显存来7B 模型加 LoRA 在 24G 卡上可以开到 4 或者 8配合梯度累积。训练轮数不要太多2-3 个 epoch 就够了。因为冷启动数据量通常不大几千条训多了容易过拟合模型会死记硬背训练数据里的搜索模式泛化到新问题上就不灵了。我一般会在验证集上监控两个指标最终答案准确率和搜索触发准确率。后者衡量的是模型在“该搜的地方”有没有输出搜索标记。注意搜索标记对应的特殊 token 需要在 tokenizer 里注册并且 embedding 层要随机初始化这些新 token 的向量。训练时这些新 token 的学习率可以设大一点让它们更快收敛。3.4 推理阶段的检索器对接推理时系统需要实时响应模型输出的搜索标记。流程是这样的模型生成到|search_end|时暂停系统提取标记内的查询文本调用检索器返回 top-k 文档把文档内容插入到|knowledge_start|和|knowledge_end|之间然后让模型继续生成。检索器的选择上我实测 BGE-large 在中文场景下效果不错英文场景可以用 Contriever 或 E5。top-k 一般取 3-5 篇每篇截断到 200 token 左右。如果你有领域专有知识库建议在专有数据上微调检索器命中率会有明显提升。def search_and_continue(model, tokenizer, input_ids, query, retriever): docs retriever.search(query, top_k3) knowledge .join([d[text][:200] for d in docs]) knowledge_ids tokenizer.encode( f|knowledge_start|{knowledge}|knowledge_end|, add_special_tokensFalse ) new_input torch.cat([input_ids, torch.tensor([knowledge_ids])], dim-1) return model.generate(new_input, max_new_tokens512)3.5 效果评估与对比实验设计评估 Search-o1 的效果不能只看最终答案准确率还要看推理链的质量。我设计了三个维度的评估答案准确率、搜索触发准确率该搜的搜了没、检索利用率搜回来的东西有没有被用上。对比实验至少要有三组基线模型不搜索、传统 RAG固定检索一次、Search-o1主动搜索。在数学推理数据集上我实测 Search-o1 比基线提升 15-20 个百分点比传统 RAG 提升 8-12 个百分点。在多跳问答上提升更明显因为多跳任务天然需要多次检索。方法数学推理准确率多跳问答准确率平均搜索次数基线模型42%38%0传统 RAG51%47%1Search-o163%59%2.34. 常见问题与排查技巧实录4.1 模型不输出搜索标记怎么办这是冷启动阶段最常见的问题。模型训完之后推理时一个搜索标记都不输出跟没训一样。原因通常是训练数据中搜索标记的比例太低或者标记格式在 tokenizer 里没处理好。排查步骤先检查训练数据里搜索标记的出现频率如果低于 10%模型很难学会。然后检查 tokenizer 是否正确识别了特殊 token可以用tokenizer.encode(|search_start|)看看返回的是不是一个独立的 token id。如果被拆成了多个 token说明注册有问题。解决办法提高训练数据中搜索步骤的比例到 30% 左右重新训练。如果还是不行可以在推理时加一个“强制搜索”的 prompt比如在 system prompt 里写“遇到不确定的知识时请使用搜索标记查询”。这个 prompt 能起到引导作用但长期来看还是要把模型本身训好。4.2 搜索太频繁导致推理效率低下另一个极端是模型学会了搜索但搜得太频繁每两步就搜一次推理速度慢得没法用。这通常是强化学习阶段奖励函数设计不当导致的——如果只奖励答案正确性模型会倾向于多搜因为搜得越多碰对的概率越大。解决办法是在奖励函数里加搜索次数惩罚项。具体来说最终奖励 答案正确性奖励 - λ × 搜索次数。λ 的取值需要调太大模型不敢搜太小模型乱搜。我一般从 0.1 开始试根据实际搜索次数调整。还有一个技巧是在训练数据里加入“不需要搜索”的负样本让模型学会区分“该搜”和“不该搜”。比如纯计算题、逻辑推理题这些不需要外部知识训练数据里就不应该有搜索标记。4.3 检索结果与推理链不兼容有时候检索回来的文档内容是对的但模型不知道怎么把它融入推理。表现就是模型把检索结果原文贴了一遍然后继续按自己的思路走检索结果成了摆设。这个问题的根源在训练数据的质量。冷启动数据里好的推理链应该展示“如何应用检索到的知识”而不是“如何复述检索到的知识”。比如检索到“欧拉函数 φ(n) 的定义”好的推理链会写“根据 φ(n) 的定义我们需要计算小于 n 且与 n 互质的数的个数因此...”而不是“欧拉函数 φ(n) 表示小于 n 且与 n 互质的正整数个数。欧拉函数是数论中的重要函数...”提示如果你发现模型经常“贴原文”可以在训练数据里加入对比样本把“贴原文”的推理链标为负例“转述应用”的标为正例用 DPO 或类似方法做偏好优化。4.4 长推理链中的知识冲突处理当检索结果和模型自身知识冲突时模型该信谁这个问题在 Search-o1 里没有完美答案但有一些实用策略。如果检索结果来自权威知识库可以在训练数据里教模型优先采信检索结果。如果检索结果质量参差不齐可以让模型在冲突时输出两个来源并标注差异。我实践中遇到的一个典型案例是模型自身知识认为某个定理的条件是 A检索结果说是 B。这时候好的推理链会写“根据检索到的资料该定理的条件为 B这与常见表述略有差异此处以检索结果为准继续推导”。这种“显式处理冲突”的能力需要通过训练数据来注入。4.5 常见问题速查表问题现象可能原因排查方法解决方向不输出搜索标记训练数据搜索比例低统计训练数据标记频率提高到 30% 左右搜索过于频繁奖励函数缺惩罚项统计平均搜索次数加搜索次数惩罚检索结果被忽略训练数据缺应用示例检查推理链质量加入转述应用样本知识冲突处理不当缺冲突处理训练构造冲突场景测试教模型显式标注来源推理速度慢检索器响应慢测检索延迟换轻量检索器或缓存5. 从 Search-o1 延伸出的几个实用方向5.1 把主动搜索思路迁移到代码生成代码生成任务天然适合主动搜索。模型在写代码时经常需要查 API 文档、查库函数签名、查报错信息。你可以把 Search-o1 的框架迁移过来让模型在生成代码时主动搜索官方文档。我试过在 Python 数据分析任务上做这个迁移模型遇到不熟悉的 pandas 函数时主动搜索文档生成代码的 API 调用准确率提升很明显。关键改动在于检索源从通用知识库换成代码文档库查询构造从自然语言问句换成“函数名 用法”的形式。训练数据的构造也类似用强模型生成带搜索标记的代码推理链。5.2 多轮对话中的主动搜索多轮对话场景下用户的问题往往依赖前文模型需要判断“当前轮次需不需要查新知识”。Search-o1 的框架可以扩展成“对话级主动搜索”让模型在每一轮回复前判断是否需要检索。这个方向目前研究还不多但实用价值很高尤其是客服机器人和技术支持场景。实现上的难点在于对话历史很长时模型需要同时处理“理解前文”和“判断是否搜索”两个任务。我的做法是把对话历史做摘要只保留关键信息然后让模型基于摘要判断是否需要搜索。5.3 资源受限场景下的轻量化方案不是每个人都有多卡集群Search-o1 的完整训练需要不少算力。如果你资源有限我推荐一个轻量化方案不做强化学习只做冷启动微调检索器用现成的不微调推理时加搜索引导 prompt。这个方案在 7B 模型上单卡就能跑效果虽然打折扣但比传统 RAG 还是有明显优势。另一个省资源的技巧是用小模型做检索器大模型做推理器。检索器只需要判断“哪篇文档相关”不需要生成能力所以 100M 级别的模型就够了。这样可以把显存省下来给推理模型。5.4 评估体系的搭建建议最后说一下评估。Search-o1 这类系统的评估比普通模型复杂因为多了检索环节。我建议至少监控四个指标答案准确率、搜索触发准确率、检索命中率、端到端延迟。前两个衡量模型能力后两个衡量系统效率。评估数据集要覆盖三类场景需要搜索的、不需要搜索的、搜索与不搜索都能做的。第三类最能检验模型的判断力。我一般会人工标注 200-500 条测试样本每条标注“理想搜索次数”然后对比模型实际搜索次数和理想值的差距。这套评估体系搭起来之后后续任何改动换检索器、调训练参数、改 prompt都能快速看到影响迭代效率会高很多。