☰
用Agent循环阅读替代RAG:解决知识库问答精度难题,精确率从67%飙升至92%
2026/10/17 8:31:05 网站建设 项目流程

1. 从 RAG 换到 LoopAgent:不是赶时髦,是被逼的

先交代一下背景。我参与的内部知识库项目,大概 200 份 PDF 和 Word 文档,内容是制度条款、项目方案、验收记录、设备参数表这类东西。早期方案很自然地选了 RAG:文档切块、Embedding、向量库、Top-K 召回,再让大模型基于召回内容生成回答。这套东西在 Demo 阶段非常顺,但真正放到使用环境里跑了两个月之后,问题一个接一个冒出来。

最大的问题是切片。制度文档里的条款经常跨页,表格的表头和数据可能被切到不同的块里。用户问“验收时限是几个工作日”,向量召回回来的块里要么只有表头没有值,要么只有数字没有上下文,模型只能连猜带蒙。第二个问题是召回分数不可信。Top-5 里经常混进两三条“看起来相关但实际没用”的内容,真正关键的那一段反而排不进去。第三个问题是维护成本。文档一更新,就要重新切、重新 Embedding,索引生效有时间差,用户总在问“为什么文档刚更新过,你们回答的还是旧内容”。

这三个问题叠加起来,我基本确定传统 RAG 在这个场景里是结构的错,不是调参能救的。后来在技术社区里看到有人讨论 agent 循环读取的方案,核心思路很直接:不要让向量算相似度,而是让模型自己决定“读哪段、读到什么程度”。我就在模拟项目X里搭了一套轻量版,跑了大概三个月。结论是:对我们这种规模的知识库,它确实能顶替 RAG,而且精确度更好。这篇文章就是把当时的选型思路、实现细节、踩过的坑完整写出来,给同样被 RAG 折磨的人一个参考。

要提前说清楚的是,这里的“替代”不是普适结论。我的使用场景是几百份文档、更新频繁、对精确性要求高、对延迟容忍度还行。如果你的文档量到了几万份,或者用户要求秒回,那本文的结论不适用,后面我会专门说这个边界。

2. LoopAgent 到底在循环什么:阅读、判断、收敛的完整链路

想用 LoopAgent 替代 RAG,第一件事是理解它和 RAG 的本质区别。RAG 的核心假设是“语义相似的文本就是相关的”。这个假设在开放域问答里基本成立,但落到具体的制度、参数、验收记录这类文档里,经常失效。因为用户问的往往是“第几条怎么说”、“这台设备的最大负载是多少”、“哪个版本才生效”,这些问题的答案在语义上不一定和问句长得像,但位置关系、表格结构、章节编号才是关键线索。

LoopAgent 换了一个思路:不预测“哪块最像”,而是让模型一段一段地读,读完之后自己判断“这段对我回答问题有没有用”。整个检索过程变成一次有目的地阅读,而不是一次相似度排序。轻量这个词也体现在这里——它不需要向量数据库,不需要 Embedding 服务,不需要单独的召回服务,一支能调用的 LLM 加一个文件解析器就能跑。

我实现的流程分成两个阶段。第一阶段是一次性预处理,叫结构扫描;第二阶段是每次提问时的阅读循环。下面这个伪代码能反映出循环的核心逻辑:

def answer_with_loop(query, doc_list): # 第一阶段产生的文本目录:每篇文档分块后的标题、摘要、关键词 index = build_text_index(doc_list) # 第二阶段:查询时先缩小阅读范围 plan = llm.plan(query, index) cursor, notes, step = plan["start_block"], [], 0 while step < max_steps: block = read_block(cursor) # 核心:模型读一段,决定是提取、跳过、还是继续 decision = llm.read_and_decide( block=block, query=query, notes=notes ) if decision["action"] == "extract": notes.append(decision["note"]) elif decision["action"] == "continue": cursor = decision["next_block"] elif decision["action"] == "stop": break step += 1 return llm.final_answer(query, notes)

这个循环里最关键的接口是read_and_decide。它每次接收当前文档块、用户问题、以及已经收集到的笔记,让模型输出一个结构化决策。实际 Prompt 里我会强制它输出 JSON,包含三个字段:action(extract / continue / stop)、note(提取到的关键信息)、next_block(下一块的位置)。这样循环的过程完全可控,每一步都知道模型为什么要读下一段。

和 RAG 的单次检索相比,这个循环的差异性体现在三个地方。第一,信息是累积的。模型看到的不只是孤立的块,还有前面已经提取的笔记,所以它能做跨文档的比对和推理。第二,停止条件由模型自己判断。模型认为证据足够、可以回答时,就输出 stop,不会无脑读完全部。第三,答案天然带引用。循环过程里读过哪些块、提取了哪些内容,全部有记录,最后可以给用户展示“这段答案出自哪一篇文档的哪一段”。这一点对制度型问答特别重要,用户不只看答案,还要能溯源。

当然,这里有一个很现实的问题:如果文档有几千页,让模型一页页读是不现实的。所以我在预处理阶段做了一个重要的折中——不是全量精读,而是先做一遍“文本目录”扫描,把每篇文档按标题结构切成块,用便宜的小模型给每个块生成两三行摘要和若干关键词。这个目录就是代码里的text_index,它完全替代了向量索引。查询的时候,模型先看目录确定候选范围,再精准地进入相关块进行阅读循环。这样既避免了全文阅读的天文 token 开销,又保留了“模型自己判断相关性”的核心优势。

3. 落地实作:模拟项目X里的一套可复用配置

光讲原理不够,我把实际的实现步骤拆开,方便直接照着搭。我们当时的目标是替换掉整个 RAG 栈,所以每一步都要尽量简单,任何需要单独部署的组件都不考虑。

3.1 文档接入与分块策略

这一步比想象中重要。PDF 解析出来的文本经常乱序,尤其是带表格、页眉页脚的长文档。我踩过一次很深的坑:某份设备手册转出来之后,表格的数据行全在前面,表头全在后面,模型读起来完全不知道这些数字对应什么字段。

我的处理顺序是这样的:先用解析工具把 PDF 转成纯文本,然后按页码和章节标题切块;表格单独识别,转成 Markdown 形式的文本;页眉页脚直接过滤。切块的粒度控制在 500 到 1500 字之间,太大会稀释注意力,太小会丢失上下文。每个块会记录一个唯一 ID、所属文档、章节路径、页码。这个 ID 是后面引用溯源的关键。

3.2 用摘要目录替代向量索引

这是整个方案替换 RAG 的关键一步。传统 RAG 会给每个块计算向量,我直接把这一步换成了“让模型读块并输出摘要”。调用一次便宜的小模型,输入是文档块本身,输出是固定格式的 JSON,包含两个字段:summary和keywords。摘要必须保留实体信息,比如设备型号、日期、数字、条款编号,不能只写“本节介绍了设备参数”,而要写“本节列出 XX 设备在 25℃ 下的最大负载为 8kW”。

为什么强调保留实体?因为预处理摘要失真的话,后面查询时模型从目录里判断候选块就会漏。前面预扫描阶段一共 4000 多个块,用便宜模型跑一遍,大概消耗 120 万 token 的输入、40 万 token 的输出,一次性成本不高。之后文档更新时,只需对变更的文档重新生成摘要,不用像向量库那样全量重建。

3.3 查询循环的关键配置

查询侧的配置直接影响效果和成本,我最后调稳定的一组参数是这样的。max_steps设为 12,意思是单次查询最多读 12 个块,防止循环失控。候选范围由模型基于文本目录决定,通常从相关文档的相关章节切入。read_and_decide的 Prompt 会明确告诉模型:只有当笔记足以回答用户问题时才输出 stop,否则优先从当前文档的下一块继续读,当前文档读完仍有疑问时再跳到其他候选文档。

一个容易忽略的细节是笔记的维护。我要求模型每次 extract 时输出的是经过整理的规范化信息,而不是原文照抄。比如原始文本是“按本合同第十二条规定,乙方应于验收合格后 10 个工作日内提交结算资料”,模型输出的笔记应该是“验收合格后 10 个工作日内提交结算资料(依据:合同第十二条)”。这样做的好处是最终合成答案时,模型面对的是精简过的证据链,而不是一堆原始句子,答案会更紧凑。

3.4 成本与延迟的估算方式

我习惯在方案切换前把成本算清楚。以 200 份文档、约 4000 个块为例,预扫描阶段一次性成本大约几美元级别,按现网商用模型的价格估算在 5 美元以内;如果用开源小模型部署在本机,这笔费用就变成硬件电费,基本可以忽略。单次查询的 token 消耗取决于循环步数,平均在 8000 到 20000 token 之间,比 RAG 单次查询确实贵,但换回来的是更少的错误回答和零向量库维护成本。

延迟方面,LoopAgent 单次查询因多步串行交互,通常在 10 到 60 秒之间,远高于 RAG 的秒级响应。这个差距在实践里比成本更敏感,所以我也准备了降级方案:对响应速度要求高的入口仍然保留 RAG 作为快速通道,而把 LoopAgent 作为默认的精读通道。这个混合方案在项目里运行了很久,后面我会细说。

4. 实测对比:三类典型问题,差距比想象中大

为了让自己确信这套方案真的有用,我建了一个 100 道题的测试集,分成三类:精确条款类 30 道(问具体制度、数值、时间)、语义归纳类 40 道(问某篇文档的核心内容)、多跳关联类 30 道(需要跨文档比对才能回答)。每道题都标注了标准答案和答案所在文档位置。评测时分别跑 RAG 方案和 LoopAgent 方案,记录正确率、幻觉率、平均耗时。

结果对比:

问题类型RAG 正确率LoopAgent 正确率RAG 平均耗时LoopAgent 平均耗时
精确条款类67%92%2.8s21s
语义归纳类89%91%3.2s18s
多跳关联类55%84%3.5s26s

这个结果里有几个值得解读的地方。精确条款类 RAG 只有 67%,主要是切片割裂导致的;LoopAgent 因为直接读原文,条款编号和数字都能对上,正确率到了 92%。多跳关联类差距最大,RAG 的 55% 意味着近一半问题答错或答不到点子上,而 LoopAgent 因为天然支持先读 A 篇再读 B 篇的连续动作,能自己完成跨文档比对。

语义归纳类两边都维持在 90% 上下,这是符合预期的。这类问题本身不太依赖精确定位,向量召回个大概就能答。这也说明了 RAG 并非处处不如 LoopAgent,它在宽泛的语义问题上效率更高。

幻觉率差异同样明显。RAG 方案在精确条款类里的幻觉率接近 20%,模型会一本正经地编出“第七条明确规定……”这样的句子,而实际上根本没读到那条。LoopAgent 因为笔记机制强制溯源,幻觉被压到 5% 以内。原因很简单:模型的每一步决策都基于真实读过的块,没有块作为依据时,它会选择继续读或承认找不到,而不是直接编。

在自建测试集之外,我特意挑了一份带大量表格的参数手册做压力测试,连续问了 20 道关于设备规格的问题。RAG 在这组问题里表现很不稳定,表格切碎后经常把不同列的数值混在一起;LoopAgent 直接读表格的 Markdown 文本,数值对齐关系保留完整,20 道题全部答对。这让我确认了一件事:只要文档里表格是结构化呈现,循环阅读的方式比向量召回稳得多。

5. 踩坑记录:循环失控、摘要失真、和看起来能打的假象

任何方案都有一堆坑,LoopAgent 也不例外。我把它实际运行中遇到的四个主要问题写出来,每个问题都附上了处理方式。

5.1 循环失控:模型读不完也停不下来

第一个版本最大的问题是read_and_decide经常输出 continue,即使笔记已经足够回答,模型还是会继续读下一个块。结果单次查询的 token 消耗飙升到 10 万以上,延迟超过两分钟,完全不可用。我一度以为这个方案走不通,后来才意识到是 Prompt 里没把停止条件讲清楚。

解决办法是在 Prompt 里加入一个显式的“证据充分性判断”:先要求模型对照笔记检查“是否有足够信息回答用户的每个子问题”,再决定动作。同时把max_steps硬性设为 12,超出后直接进入最终合成阶段。这两个约束叠加之后,循环平均步数降到了 4 到 6 步,大部分问题在顶多两篇文档的范围内就能收敛。

5.2 预扫描摘要失真

预扫描摘要如果丢失关键实体,查询时候选块就定位不准。我第二次实测的时候发现精确条款正确率从 92% 跌到 80%,排查后定位到摘要生成 Prompt 的问题。原来摘要模型偷懒,把表格里的数值省略成“若干参数”,导致目录里没有可检索的实体信息。

对策是重写摘要模板,强制要求保留所有数字、日期、条款编号和专有名词,并且要求摘要必须能独立成句,不允许出现“本节内容较多,主要包括若干参数”这种废话。重跑预扫描后,候选块定位准确率明显回升。这件事也提醒我:目录是循环阅读的眼睛,摘要质量必须单独做评测,不能想当然。

5.3 长表格和乱序文本

PDF 解析后的表格乱序问题在前面提过,这里展开说下处理方式。我用的是一个启发式规则:检测到连续数字行时,向上寻找最近的非数字表头行,把表头和数据重新拼接。这个规则对 90% 的常规表格有效,遇到复杂合并单元格仍然会失效。更稳妥的做法是在解析阶段就引入视觉模型识别表格结构,但对轻量方案来说成本太高,我最终选择了正则规则加人工抽查。

5.4 “看起来能打”的假象

这是心态上的坑。初版 Demo 我只测了 5 个精心挑选的问题,每个问题 LoopAgent 都能给出漂亮答案,我当时差点直接宣布方案成功。还好后来建了 100 道题的测试集,才暴露出找茬式的缺陷。我的建议是:方案评估一定要从一开始就建标准问题集,哪怕只有 20 道题,也好过反复用那几个 demo 问题自欺欺人。正确率、延迟、token 消耗、幻觉率四项指标要一起记录,不能只看手感。

6. 我现在的取舍标准:什么场景值得换,什么场景别硬换

项目收尾后我做了一次复盘,整理出一套相对稳定的选型标准,希望能减少别人重走弯路的时间。

文档规模在几十到几百份、更新频繁且内容偏向制度条款、规格参数这类对精确性要求高的场景,LoopAgent 的优势很明显。它省掉了向量库运维的成本,文档更新后只重建摘要目录,当天就能生效。回答附带的引用溯源能力也是制度问答场景的刚需。

反过来,如果文档规模到了上万份,或者用户要求首响在几秒以内,LoopAgent 就不合适了。万份文档做预扫描是一次不小的开销,查询时模型要读的候选块也会增多,延迟和成本都难以接受。这种情况下,RAG 仍然是最务实的方案。我个人建议的中间态是混合部署:入口用 RAG 快速命中热区,命中置信度低的时候再切 LoopAgent 做精读。

最后分享一个我在实际运行中形成的习惯。我会在日志里给每次查询打个标记:模型输出的 action 序列里,如果出现了超过两次“continue 之后又回到同一个文档块”,大概率是候选定位有问题,应该去检查摘要目录。这个信号比单纯看正确率更早暴露索引的劣化。项目跑了三个月,我靠着这个方法及时修正了两次文档更新导致的摘要缺失,避免了用户感知到的错误回答。

循环阅读这种模式不神秘,也不高级,它只是把“检索”从数学计算变回了“阅读和判断”。在文档量可控的场景里,这一替换带来的是精确度和可解释性的显著提升,代价是延迟和 token 消耗换来的。至于值不值,取决于你的用户更在意什么。对我参与的项目来说,这个交换很划算。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询