1. 从RAG到RIG为什么先生成再检索救不了多跳问题上个月我们在内部知识库上线的RAG问答系统被业务方连续问倒了三次。三次都栽在同一类问题上A方案和B方案冲突时合同模板里哪一条优先这个报错在旧版本文档里有没有对应处理——单次检索抓回来的文档里根本没有完整答案。我当时的第一反应是加大top_k、扩窗口长度结果只是把更多噪音塞进了上下文答得更差。后来我换了个思路与其让系统替模型把检索提前做完不如让模型自己在生成过程中随时喊我需要查资料。这就是RIGRetrieval-Interleaved Generation检索交织生成的核心想法而我用来落地这个想法的开源框架就是OpenRig。1.1 标准RAG的三个死穴标准的RAG pipeline大家都熟问题进来先向量检索Top-K文档塞进上下文再让模型生成。这个流程对单点事实查询很管用比如公司的报销上限是多少。但对稍微复杂的场景它有三个天生缺陷。第一个是检索时机问题。检索发生在生成之前模型没有后悔药。问题本身表述模糊的时候模型在生成中途需要补充的信息一次性检索根本覆盖不到。第二个是多跳问题。很多真实业务问题要先查事实A再根据A查事实B最后才能推出结论。单次检索没办法做到查完A之后带着A去查B。第三个是上下文污染。为了覆盖可能性我们把top_k调大结果不相关文档一起进来模型的注意力被稀释反而把正确信息淹没掉。1.2 Agentic路线灵活但不可控既然单次检索不够很自然的想法就是上Agent让模型自己决定调用检索工具用ReAct式的循环拿到结果再继续。这个方向的能力上限确实高多跳、总结、推理都能做。但落地之后你会发现另一个麻烦不可控。模型会为了一个只需要查一次的问题连续调用三次工具会在两个证据矛盾时反复横跳不收敛会把上下文窗口塞满历史步骤导致token爆炸。每一步都要设计终止条件、防重入机制、时间限制调优成本高得吓人。我见过不少团队在这个路子上投入两三个月最后产出的系统在评测集上还行一上真实流量就各种超时和循环。RIG走的是中间路线。它不像RAG那样检索一次就拉倒也不像Agent那样把完整的工具调用权交给模型。它的设计原则很朴素模型在生成时如果发现自己缺信息就发出一个检索信号框架收到信号后去检索把结果追加回上下文模型继续生成。检索的决定权在模型手里但检索的执行是确定性的、单次的、可观测的不会像Agent那样产生自由发挥的工具调用序列。1.3 OpenRig到底解决什么问题OpenRig这个名字Open是开源Rig在这里不是机器配置的意思而是RIG模式的动词化——它就是一个把检索交织生成这套模式工程化的框架。它解决的是一类很具体的问题你的业务需要多跳知识推理但又承受不了Agent系统的复杂度和不确定性。我把它用在合同条款问答和故障诊断两个场景上效果比预想好。后面我会从原理、部署、调优、踩坑四个角度展开把我在生产环境里跑OpenRig的完整经验整理出来。如果你正在做知识库问答、智能客服、文档辅助决策这类系统这篇文章应该能帮你少走不少弯路。2. OpenRig的核心设计与工作原理OpenRig的整体架构不复杂但设计上有个关键选择它怎么知道模型什么时候需要检索。这一节把它的执行链路拆开讲清楚。2.1 一次完整的RIG执行链路一次OpenRig请求完整走下来是这样的用户query进入会话管理器初始上下文 system prompt 历史对话 query。模型开始流式生成逐token输出。模型在某个位置输出一个检索标记retrieval marker。这个标记可以是特殊token也可以是约定的文本格式OpenRig默认支持|retrieve|这种特殊token也支持函数调用风格的JSON输出。解析器捕获标记暂停生成提取标记中携带的检索query模型自己生成的查询词。检索器执行检索返回Top-K文档。结果按固定模板格式化追加到上下文中模型继续生成。重复步骤2-6直到模型输出结束标记或达到max_iterations上限。值得注意的是模型发出的检索query不是用户原话。模型在生成过程中已经产生了对问题的中间理解所以它生成的检索词往往比原始query更精准。比如用户问这个错误在哪个版本被修复模型生成到一半发现需要版本号它会自己生成v2.3.1 changelog bug fix这样的检索query。2.2 检索标记与生成循环的实现细节标记机制是整个框架的灵魂。OpenRig采用了一个很务实的方案不用特殊token做训练而是用提示词约束 生成时解析。也就是说框架在system prompt里明确告诉模型如果你需要补充信息输出|retrieve|需要查询的内容|endofretrieve|推理时解析器去匹配这个结构。这样做的好处是不需要对模型做任何微调任何支持复杂system prompt的模型都能跑。代价是模型偶尔不遵守格式或者在不该检索的时候乱发信号。这个坑我后面专门讲。底层循环的伪代码逻辑大致是这样def run(question, config): context build_initial_context(question) iterations 0 while iterations config.max_iterations: # 流式生成并监听检索标记 for chunk in llm.stream(context, stop_markersconfig.stop_markers): if parser.detect_retrieve(chunk.text): retrieve_query parser.extract_query(chunk.text) docs retriever.search(retrieve_query, top_kconfig.top_k) context append_docs(context, docs, chunk.prefix_text) break else: # 没有检索标记说明生成正常结束 return context iterations 1 return context # 达到迭代上限强制返回这个循环最关键的参数是max_iterations。设大了模型可以多跳几次但延迟和token开销跟着涨设小了多跳问题又解决不了。我生产环境里一般设3极少有查询需要超过3次检索。2.3 与Agent框架相比OpenRig省掉了什么用过LangGraph或自研ReAct框架的人应该有同感Agent方案的复杂度大头在工具注册、状态管理、循环控制、终止条件。OpenRig把这些都砍掉了换来的是一个更窄的边界。维度单次RAGOpenRig (RIG)Agent (ReAct)检索触发方式生成前一次性生成中按需多次工具调用循环模型自由度低中高是否有多跳能力弱强强上下文增长固定每次检索新增步骤日志累积调试难度低中高可预测性高中高低这个表格不是想说Agent不好而是想说明它们解决的问题不一样。如果你的需求是让模型自主决定怎么完成任务Agent是对的如果你的需求是让模型在回答知识类问题时自己补检索OpenRig这种RIG框架更划算。我团队里现在有部分简单需求直接先用OpenRig跑跑不动了再升级到Agent这个成本曲线平缓很多。3. 本地部署OpenRig从零跑通一个多跳问答服务3.1 环境准备与依赖选型OpenRig对运行环境的要求不算苛刻。我的部署机器是一台32核CPU、128GB内存、单张RTX 4090的服务器LLM推理用vLLM起了一个Qwen2.5-32B-Instruct服务向量库用Milvus检索器混用了向量检索和BM25。依赖清单大致如下Python 3.10一个OpenAI兼容的LLM推理服务vLLM、TGI、Ollama都行向量数据库Milvus / Qdrant / Chroma均可小规模用Chroma最省事OpenRig本体pip install openrig安装完成之后先确认LLM服务能用OpenAI SDK调用curl http://localhost:8000/v1/models \ -H Authorization: Bearer EMPTY如果返回模型列表说明推理服务就绪可以进入下一步。3.2 配置向量库和检索器OpenRig的检索接口设计成可插拔。你只需要实现一个search(query, top_k)返回文档列表的函数或者直接用内置的Milvus适配器。我习惯把配置写在一个YAML里统一管理# openrig.yaml model: base_url: http://localhost:8000/v1 name: Qwen2.5-32B-Instruct max_tokens: 512 retriever: type: milvus host: localhost port: 19530 collection: contracts embedding_model: BAAI/bge-large-zh-v1.5 top_k: 5 score_threshold: 0.35 rig: max_iterations: 3 marker_style: special_token # 可选 special_token / json append_format: evidence这里有个我踩过坑的参数score_threshold。设置太低垃圾文档大量进入上下文设置太高正确的检索被过滤掉。我建议先跑一批真实查询把相似度分数分布打出来再定阈值不要凭感觉拍脑袋。3.3 启动服务并验证检索触发配置写好后启动服务只需要一个Python脚本。OpenRig对外暴露的核心对象是RigSessionfrom openrig import RigSession, RigConfig config RigConfig.from_yaml(openrig.yaml) session RigSession(config) question 供应商延迟交付合同里有没有自动顺延交货期的条款顺延期间的质量责任怎么算 answer, trace session.ask(question) print(answer) print(检索过程) for step in trace.retrieval_steps: print(f第{step.round}轮检索: {step.query}) print(f命中文档: {[d.title for d in step.docs]})跑完之后如果一切正常日志里能看到多次检索事件每次检索的query都是模型在生成过程中自动产出的。第一次跑通的时候我还挺兴奋因为它确实做到了模型查到一半主动说我还需要看另一个条款。不过这里有个现象值得注意模型第一次生成的检索query往往和用户原话高度重合。真正显示RIG价值的是第二轮、第三轮检索那时候的query会带着上一轮结果的上下文信息比如顺延条款中没有质量责任说明查找违约条款中关于交付延期责任的规定。这种带有推理痕迹的检索词是普通RAG给不了的。4. 关键参数调优与评测方法4.1 迭代次数和检索阈值的权衡max_iterations是最需要认真调的参数。从直觉上说多跳问题自然需要多次检索但每一轮检索都会带来三个成本延迟、token、上下文污染风险。我做过一组实验用合同问答的50条测试问题在max_iterations分别为1、2、3、5时评测max_iterations正确率平均延迟平均token消耗148%2.1s870271%3.4s1350379%4.7s2050578%8.2s4100数据看得很清楚3轮之后正确率不再上升成本和延迟却几乎翻倍。这就是典型的收益递减曲线。我现在的建议是新业务一律从3起跳用评测数据说话不要为了省延迟砍到2除非你的业务确认没有多跳需求。score_threshold的调法前面提到过另一种更高效的方案是加一个reranker。OpenRig支持在检索器后面挂rerank层用bge-reranker-v2-m3把Top-K初筛结果再排一遍只保留排序前2。加上reranker之后我的正确率从79%提到86%延迟增加不到0.5秒性价比很高。4.2 延迟预算一次多跳查询到底花了多少时间很多团队最终不是被正确率劝退的是被延迟劝退的。这里给一个真实的耗时拆解让你心里有底LLM首token生成1.5s32B模型40904-bit量化第一轮检索和嵌入0.3s第二轮生成1.8s第二轮检索0.3s最终生成1.2s总计约5.1s对一个内部知识库问答来说5秒是可接受的但如果你要做面向C端的实时客服就得想办法压。我试过几条路径用更小的模型14B做前三跳生成最后用32B汇总把embedding模型换成更快的小模型把Milvus索引换成内存态。综合下来延迟能压到3秒左右正确率只降2%。4.3 用评测脚本量化收益OpenRig自称是面向检索增强生成的工程框架所以它很重视评测闭环。项目里带了一个openrig eval命令可以用起来。它支持两种模式一是跑公开多跳数据集比如HotpotQA的子集二是跑你自己的JSONL标注集。我强烈建议用后者因为公开数据集和业务分布差异太大。评估数据格式很简单JSONL里每行一个问题、标准答案、以及可选的支持文档列表openrig eval \ --config openrig.yaml \ --dataset ./data/contract_eval.jsonl \ --metric accuracy retrieval_precision token_overhead输出是一张表格除了最终答案正确率还会报告每次检索的有效率——也就是这次检索到的文档是否真的支持了最终答案。这个指标很有用它能把模型乱检索但碰巧答对的情况暴露出来。我在初版配置上跑出来retrieval_precision只有42%说明模型有一半的检索是没必要的后面通过修改system prompt才把它拉到75%。5. 实测中踩过的坑与规避方案5.1 模型不配合检索标记时有时无用OpenRig第一个星期最让我头疼的问题是模型经常不输出检索标记。明明上下文里没有答案它还是硬编一个。排查之后发现原因在system prompt的写法上。OpenRig默认的prompt只写了如果需要更多信息请使用检索标记。但指令微调模型对如果需要这种条件型指令理解得不够坚决。我把提示词改成强约束版本效果立竿见影提示 检索触发条件要写硬规则不要写建议。例如你必须检查当前上下文是否包含足够信息回答问题。如果信息不充分你必须先输出检索标记再生成最终回答。禁止在信息不充分时直接回答。另外不同模型的格式偏好差别很大。Qwen和Yi对特殊token风格接受度高GPT系和Claude系更习惯JSON函数调用。如果发现模型经常格式错误把marker_style从special_token换成json解析稳定性和生成遵守率都会好不少。5.2 上下文膨胀失控多轮检索意味着多轮文档追加上下文长度是线性甚至超线性增长的。第一轮检索塞5个文档第二轮又塞5个加上前一轮的文档还在上下文很快就几千token了。上下文一长模型注意力开始涣散后面检索的结果反而覆盖了前面有用的证据。我的对策是控制每轮追加的文档数量和对旧证据做压缩。具体做法top_k设5但经reranker后只保留2个结果第二轮开始把第一轮的原始文档替换成模型生成的简短摘要。这样既保留了证据链又不让上下文无限膨胀。OpenRig提供了append_format参数可以选full或summarysummary模式下框架会自动要求模型对上一轮证据做三句话以内的总结。5.3 检索到坏文档导致越绕越远RIG一个隐蔽的失败模式是模型在错误证据的引导下生成一个同样错误的检索query然后一路错下去。比如最初检索到一份已废止的合同条款模型基于它继续追问检索出的更多文档都是围绕旧版的最终答案完全跑偏。这个问题单靠OpenRig参数解决不了得从数据侧动手。我做了三件事第一在向量库里给文档加上生效/失效元数据检索时过滤失效文档第二对高冲突域比如合同版本单独建索引避免新旧版本混在一起第三在提示词里加一条如果发现检索结果与当前上下文证据冲突优先信任最新的生效文档并说明冲突。第三点尤其重要——它让模型对自己产生的证据链有批判意识而不是被上一轮结果牵着走。除了以上三个大坑还有个评测期容易踩的隐蔽问题模型在缺少检索标记的情况下偶尔会假装用了检索。它生成的内容里直接写根据相关资料显示但OpenRig的trace里根本没有检索事件。这本质上是模型幻觉的变体。排查方法也很简单检查trace里每一步的证据来源凡是最终答案引用了不存在于证据链的内容都算失败样例。6. 什么样的业务适合上OpenRig跑了大半年之后我对这个框架的边界有了比较清晰的认识。OpenRig不是万金油但也不是小众玩具。最适合它的场景有几类内部知识库问答用户问题经常需要连续查多个知识点才能回答比如合同、规章制度、技术文档。诊断式客服用户报一个现象系统需要先查现象对应的模块再查该模块的常见故障再查修复步骤。文档辅助决策比如某个操作是否合规需要对比多个制度条款后得出结论。这几类业务的共同点是问题本身有结构答案需要跨文档拼接同时用户能接受3到5秒的延迟。反过来如果你的业务是高频简单问答、低延迟强需求或者你的语料非常单一、一次检索就有九成把握那老老实实用经典RAG反而更好。OpenRig的价值恰好就是让够用和过度聪明之间多了一个中间档。最后分享一个我个人的运维习惯OpenRig跑起来之后一定要把它的trace日志接进监控。每次检索事件、每轮query、每一跳的延迟都记录下来。我后来发现很多系统变蠢的case都不是框架坏了而是embedding模型那侧的数据没更新或者新文档进了向量库但score_threshold把它们全过滤掉了。有了trace日志这些问题十分钟就能定位。工具再智能也不如你手里有一条完整的证据链可查。
阅读完成 · 觉得有帮助?