企业智能检索这套底座,过去几年换了三代:从纯向量 RAG,到混合检索加重排的增强 RAG,再到让 Agent 自己决定何时检索、检索什么、检索几轮的 Agentic RAG。我把三代各自的取舍、接入架构和 token 成本账整理成这篇笔记,示例接口统一走 https://4sapi.com 的 OpenAI 兼容风格。
一个绕不开的两难:老系统答不对,升级又怕账单失控
上了第一代或第二代检索系统的团队,多半熟悉同一组抱怨。一边是业务方:演示环境里问答如流水,一上真实业务,稍微绕个弯的问题就开始出问题——问"A 供应商交付延期影响了哪些下游项目,这些项目的负责人分别该采取什么动作",系统要么只答了供应商,要么把两条不相关的检索片段缝在一起硬答,幻觉就是这么来的。另一边是成本方:听说第三代 Agentic RAG 能解决这些问题,一看"多轮检索加多轮生成"的机制,第一反应是账单会不会直接翻几倍。
这两难都是真实的。复杂多跳问题确实是第一代、第二代的结构性短板,第三代确实有解法;但第三代每一次"决定要不要检索、用什么工具检索、证据够不够"都是一次完整的模型调用,token 是真金白银。我的结论写在前面:不升级的焦虑没必要,全量升级也不理智,正确姿势是分级路由——让简单问题留在便宜的流水线里,只把真正复杂的问题送进 Agent 循环,再用缓存把重复消耗压下去。这篇就按这个思路展开。
原理速览:三代演进到底改了什么
每一代都在解决上一代搞不定的问题,架构形态的变化非常清晰:
- 第一代基础 RAG:向量检索 + 直接生成。问题进来,Embedding 一下,向量库里取 top-k 片段,拼进提示词,生成一次,结束。检索一次,生成一次,全程单轮。
- 第二代增强 RAG:在单轮框架内做工程化增强。查询改写把口语化提问整理成检索友好的表达;混合检索同时跑向量检索和关键词索引,专有名词、型号、错误码靠关键词补召回;重排模型把最相关的片段顶到前面。检索仍然只发生一轮,但这一轮的质量被做到极致。
- 第三代 Agentic RAG:检索的控制权交给 Agent。何时检索、检索什么、用哪个工具、检索几轮,全部由 Agent 在推理过程中当场决定;证据不够就改写查询再来一跳,直到证据充分或达到跳数上限。多跳推理与工具化检索是这个时代的标志。
第一代 基础 RAG(单轮流水线)
问题 ──> Embedding ──> 向量库 top-k ──> 拼提示词 ──> 一次生成 ──> 答案
(检索一次、生成一次,流程到此结束)
第二代 增强 RAG(增强过的单轮流水线)
┌─> 向量检索 ───┐
问题 ──> 查询改写 ──┤ ├─> 合并去重 ──> 重排 ──> 一次生成 ──> 答案
└─> 关键词检索 ─┘
(检索仍只有一轮,但召回与排序质量被大幅拉升)
第三代 Agentic RAG(Agent 检索循环)
问题 ──> Agent ──> 判断:证据够吗?
│ 够 ────────> 汇总生成 ──> 答案
│ 不够 ──> 选工具(向量 / 关键词 / 结构化)──> 检索
│ └──> 改写查询 ──> 回到判断
└ 达到跳数上限 ──> 强制汇总 ──> 答案
(何时检索、检索什么、检索几轮,由 Agent 当场决定)
三代不是替代关系。第二代没有淘汰第一代,第三代也不会把前两代扫进垃圾桶——它们各自有最划算的适用场景,后面对照表里展开。
第一代基础 RAG:简单可靠,但一跳之外全是盲区
基础 RAG 的关键词是"接上":向量检索加直接生成,把私有知识第一次接进模型的上下文。它解决的是从无到有——不接检索,问内部制度、内部文档,模型只能靠训练时固化的公开知识硬编;接上之后,答案有出处、可追溯,幻觉问题在简单事实场景里得到实质缓解。成本上它也是最便宜的一代:一次 Embedding、一次向量检索、一次生成,token 消耗约等于提示词加答案,延迟通常在两三秒量级。
问题同样明显,而且大多是结构性的。第一,它是"一次检索定终身"的鼻祖——检索一轮就锁定证据,检索结果里没有的东西,答案里就不会有,没有任何补救机会。第二,向量检索靠语义相似度工作,对专有名词、产品型号、内部代号这类精确匹配需求天然偏弱,Embedding 空间里的"相似"和"相关"并不总是重合,缩写和口语化提问更容易失手。第三,复杂多跳问题基本无解:问"A 供应商交付延期影响了哪些下游项目",这类问题需要先查供应商记录、再顺藤摸瓜查项目、再查负责人,单轮流水线连第一步该查什么都不知道,只能把整个问题扔给向量检索,召回什么算什么。第四,流程里没有自我校验环节,检索片段质量差的时候,模型照样一本正经地往下编。
一句话总结第一代:它把"有依据地回答简单问题"做成了标准件,代价是在复杂问题面前几乎不设防。
第二代增强 RAG:把单轮检索做到极致,也把天花板焊死
增强 RAG 是过去几年企业落地的主力形态。核心思路是承认单轮框架,然后在框架内榨出每一分质量:
- 混合检索:向量检索管语义,关键词索引管精确匹配,两路结果合并去重。专有名词、错误码、型号这类查询的召回缺口被补上了一大块。
- 重排:粗召回先给个几十条,重排模型精排出前几名。排序质量上去了,进提示词的片段更少更准,反而帮 token 账单省了一笔。
- 查询改写:把口语化提问改写成检索友好的表达,补全指代、拆开长句,必要时做同义扩展。
这些增强的收益是系统性的,尤其体现在术语密集的业务场景。具体幅度各家数据、切分方式、评测集差异太大,脱离语境的基准数字只会误导,这里只给相对关系,不给编造的数字。
遗留问题同样清楚。它仍然是"一次检索定终身":改写、扩展、混合、重排,全都发生在那唯一的一轮里,第一轮没检到的证据,后续再也没有机会补。多跳问题里"第二步依赖第一步的结果"这个结构性困难,增强 RAG 根本没有触碰——改写模块再聪明,也只能猜第一步该怎么查,猜不中就全盘皆输。另外,流水线组件越来越多:改写模型、双路召回、重排模型、生成模型,每一环都有自己的延迟和费用,链路总成本涨到基础 RAG 的约 1.5 到 2 倍(演示参数,实际看各组件的调用频率),运维复杂度也在涨——任何一环升级都可能让整体行为漂移,回归测试从一次性工作变成日常动作。
第三代 Agentic RAG:把检索的决定权交给 Agent
Agentic RAG 改变的不是检索质量,而是检索的控制流。Agent 拿到问题后,先判断需不需要证据、需要什么证据,然后从工具面里挑一个动手——向量库、关键词索引、结构化查询都可以被包装成工具;拿到结果后继续推理:证据够了就作答,不够就改写查询再来一跳,直到证据充分或达到跳数上限。
这带来三个质变。第一,多跳问题有了正解路径:先查供应商记录,根据结果再查项目,再查负责人,每一步的查询由上一步的结果决定——这正是"第二步依赖第一步"的标准解法,也是前两代做不到的地方。第二,检索变得按需而定:证据充分的简单问题一跳都不用多跳,复杂问题多跳几次,资源花在刀刃上——前提是外面有路由分级兜底,否则 Agent 对简单问题也可能兴致勃勃地空转。第三,工具化检索让异构数据源第一次在同一套框架里协作:向量库管语义,关键词索引管精确匹配,结构化查询管数字和事实,Agent 按需组合,交叉验证也有了操作空间。
代价同样直白。多跳检索意味着多轮"检索 + 生成"循环:每一跳都是一次完整的模型调用(决定调什么工具、怎么调)加一次工具执行加一次结果注入,最后还要一次汇总生成。用演示参数算账:单轮 RAG 一次问答约 2k 到 5k token;三跳的 Agentic RAG,每次跳转决策约 1k 到 2k token,检索结果注入每跳再追加 1k 到 3k,加上汇总生成,一次问答轻松跑到 10k 到 25k token——单次消耗约是单轮 RAG 的 3 到 8 倍(以上全是演示参数,实际倍数取决于跳数、片段大小和上下文组织方式)。延迟同步放大,从两三秒变成十几秒甚至更久,交互设计要跟着改:加载状态、进度提示、异步返回都得起考虑。
再往下是更隐蔽的成本:Agent 的行为有随机性,同一个问题这次两跳、下次四跳,评测与回归的难度陡增;工具调用格式错误、证据差一步就停手、死循环空转,这些都需要工程护栏来兜底。护栏设计得对不对,直接决定第三代是"能力升级"还是"成本事故"。
接入架构:检索工具化、路由分级、缓存策略
把 Agentic RAG 接进企业系统,架构上我认为只有三件事必须做对。
第一件,检索工具化。把存量索引包装成 Agent 的工具面:向量库一个工具,关键词索引一个工具,结构化查询一个工具。每个工具的描述要写清楚"什么问题该来找我"——这段描述的质量直接决定 Agent 选工具的准确率,写含糊了 Agent 就会乱选,多跳成本立刻虚高。工具返回值统一格式:内容片段加来源元数据(文档名、位置、时间),没有来源的证据在汇总阶段容易被当成模型自己编的,忠实度评测会先吃亏。
第二件,路由分级。不是所有问题都配得上 Agent 循环。简单事实问题——"报销截止日是几号"这种——走第一代流水线,毫秒级检索加一次生成,成本最低;术语密集、召回敏感的常规问题走第二代增强流水线;只有复杂多跳问题才升级到第三代。路由器可以先用规则(问题长度、疑问词类型、是否含对比或归因信号),不够再上小模型分类。路由决策本身要记日志,后面按路由分层统计成本和质量,全靠这份数据说话。
第三件,缓存策略。检索结果缓存:规范化查询词加索引版本号做键,同义问法反复检索同一批文档的浪费直接砍掉。中间结论缓存:多跳任务里子问题的结论复用价值很高,"A 供应商交付状态"这个子结论在关联问题里可以直接复用,不用重新推一遍。Embedding 缓存:高频文档的向量算一次存一次。缓存命中率要单独埋点统计——它是分级路由之外最确定的省钱项,省了多少 token 要能算出来。
接入教程:分级路由骨架与 token 计量
下面用 OpenAI 兼容 SDK 风格给一个分级路由骨架:api_key 从环境变量读取,base_url 指向中转端点,简单问题分流到单轮 RAG,复杂问题进 Agent 多跳循环,每次问答统计 token 消耗。骨架里的检索函数是占位实现,接真实索引时替换函数体即可。
import json
import os
from openai import OpenAI
# OpenAI 兼容客户端:base_url 指向中转端点,api_key 一律从环境变量读取,避免硬编码进仓库
client = OpenAI(
base_url="https://4sapi.com/v1",
api_key=os.environ["LLM_API_KEY"],
)
MODEL = "gpt-4o-mini" # 示例模型名,替换成实际可用的模型
# ---- 检索工具面:把存量索引包装成 Agent 可调用的工具,接真实索引时替换函数体 ----
def vector_search(query: str, top_k: int = 5) -> str:
"""向量库检索:语义相似度匹配,适合模糊描述与近义表达。"""
return f"[向量检索] {query} 的 top_{top_k} 片段(生产环境替换为真实 Embedding 检索)"
def keyword_search(query: str) -> str:
"""关键词索引检索:专有名词、产品型号、错误码的精确匹配更可靠。"""
return f"[关键词检索] {query} 的命中条目"
def structured_query(expr: str) -> str:
"""结构化查询:库存、订单、财务数字走数据库,不走语义检索。"""
return f"[结构化查询] {expr} 的查询结果"
TOOLS = {
"vector_search": vector_search,
"keyword_search": keyword_search,
"structured_query": structured_query,
}
def build_tools_schema():
"""把工具面转成模型可用的 tools 参数,description 决定 Agent 选工具的准确率。"""
return [
{
"type": "function",
"function": {
"name": name,
"description": fn.__doc__ or name,
"parameters": {
"type": "object",
"properties": {"query": {"type": "string", "description": "检索词或查询表达式"}},
"required": ["query"],
},
},
}
for name, fn in TOOLS.items()
]
# ---- 分级路由:简单问题走单轮流水线,复杂问题才进 Agent 多跳循环 ----
COMPLEX_MARKS = ("对比", "区别", "为什么", "影响", "综合", "演进", "归因")
def classify(question: str) -> str:
"""演示级规则路由:命中复杂信号或超长问题升级 Agent,其余走单轮。"""
if any(mark in question for mark in COMPLEX_MARKS) or len(question) > 40:
return "agentic"
return "single"
def run_single_rag(question: str):
"""第一代流水线:一次向量检索加一次生成,token 成本最低。"""
docs = vector_search(question)
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": "只依据检索片段作答,片段里没有的信息要明说不知道。"},
{"role": "user", "content": f"问题:{question}\n检索片段:{docs}"},
],
)
return resp.choices[0].message.content, resp.usage.total_tokens
def run_agentic_rag(question: str, max_hops: int = 3):
"""第三代循环:Agent 决定何时检索、检索什么、检索几轮,跳数上限是安全阀。"""
messages = [
{"role": "system", "content": "需要事实依据时调用检索工具;证据足够才给最终答案,并注明用了哪些证据。"},
{"role": "user", "content": question},
]
total_tokens = 0
tools_schema = build_tools_schema()
for _ in range(max_hops):
resp = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=tools_schema,
)
msg = resp.choices[0].message
total_tokens += resp.usage.total_tokens
if not msg.tool_calls:
return msg.content, total_tokens # 证据足够,循环结束
messages.append(msg)
for call in msg.tool_calls:
fn = TOOLS.get(call.function.name)
args = json.loads(call.function.arguments or "{}")
result = fn(args.get("query", question)) if fn else "工具不存在"
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
# 达到跳数上限,强制汇总作答,防止循环空转烧 token
resp = client.chat.completions.create(model=MODEL, messages=messages)
return resp.choices[0].message.content, total_tokens + resp.usage.total_tokens
def answer(question: str):
"""入口:按复杂度分流,返回答案并打印本次问答的 token 消耗。"""
route = classify(question)
runner = run_agentic_rag if route == "agentic" else run_single_rag
text, tokens = runner(question)
print(f"[路由={route}] token消耗={tokens}")
return text
if __name__ == "__main__":
answer("报销截止日是几号") # 简单事实问题,预期走单轮流水线
answer("新规生效后 A、B 两条产品线的合规要求有什么区别") # 多跳对比问题,预期走 Agent 循环
几个实现要点值得单独说。classify 用的是演示级规则路由,生产上建议先跑日志统计:真实流量里简单问题的占比往往远超预期,路由阈值定准了,大部分 token 花销天然被压住。run_agentic_rag 里的 max_hops 是最重要的安全阀,没有跳数上限的 Agent 循环就是一张敞开的账单;每跳的 resp.usage.total_tokens 累加,就是按次核算的最小单元,接计费看板时按这个粒度上报。工具的 description 不要照抄示例,写清楚每个工具的适用场景——Agent 选错工具是多跳成本虚高的常见原因,改一段描述有时比改一周代码更见效。
三代对照表:一张表看清取舍
下表的 token 成本量级全部是演示参数,用于建立量级直觉,实际数字以自己的评测数据为准。
| 维度 | 第一代 基础 RAG | 第二代 增强 RAG | 第三代 Agentic RAG |
|---|---|---|---|
| 核心机制 | 向量检索 + 直接生成 | 混合检索、重排、查询改写 | Agent 决定何时检索、检索什么、检索几轮 |
| 解决什么 | 把私有知识接进模型,简单事实查询可答、有出处 | 单轮检索的召回与排序质量,术语密集场景大幅受益 | 复杂多跳问题,子问题拆解与跨索引取数 |
| 遗留什么 | 复杂多跳基本答不对,一次检索定终身 | 仍是一次检索定终身,第一步查不中后续无从补救 | token 成本与延迟数倍放大,行为随机、评测变难 |
| token 成本量级(演示参数) | 1x,约 2k-5k token/次问答 | 约 1.5x-2x,改写与重排各加一次调用 | 约 3x-8x,三跳循环的常见区间 |
| 适用场景 | FAQ、单点事实查询、成本敏感的高频问答 | 知识库规模大、术语密集的常规业务问答 | 研报综合、跨部门取证、排障归因类多跳任务 |
表里最值得盯着看的是最后两行。成本行提醒账单要按量级预期管理,不要拿第一代的账单去套第三代的架构;适用场景行提醒三代的正确关系是共存分工——把大多数简单流量留在第一代,把术语密集流量交给第二代,只放小比例的多跳任务进第三代(这个流量比例同样是演示参数,以路由日志统计为准),整体成本曲线才压得住。
避坑清单:升级前先量化
见过的失败升级,几乎都跳过同一个步骤:升级前先量化。直觉觉得"新的一定更好",上线后账单翻倍、指标没动,回头补评测已经晚了。下面这份 checklist 建议在动架构之前逐项过一遍:
- 抽 100 到 200 条真实业务问题(脱敏后)建评测集,单跳事实、多跳对比、无答案三类都要覆盖
- 记录现网系统的基线:忠实度、召回、幻觉率、平均 token 成本/每次问答,一项都不能缺
- 用同一套评测集分别跑三代流水线,先看指标差多少,再算账单差多少
- 给 Agentic RAG 设跳数上限和单次问答 token 预算,超限强制汇总作答
- 统计真实流量里简单问题的占比,路由阈值按这个占比定,别把高频简单查询送进多跳循环
- 缓存命中率单独埋点,检索缓存与中间结论缓存各省多少 token 要能算出来
- 约定回归节奏:模型版本或提示词一改就重跑评测集,防幻觉率悄悄回升
- 上线初期按路由分层灰度,Agent 路由单独限流,异常时能一键降级回增强 RAG 流水线
清单里最容易被忽略的是最后一条:降级路径。第三代系统出问题时能退回第二代流水线,业务方才敢放心把流量切过来;没有降级路径的升级方案,评审阶段就该被拦下来。
成本与风险提示:多跳放大与忠实度评测
成本端集中强调三点。第一,多跳成本放大是结构性的,不是调参能消除的:每多一跳就多一次决策调用、一次工具执行、一次结果注入,token 消耗随跳数近似线性上涨,演示口径下单次问答到 10k token 以上很正常。第二,账单要按路由分层看:单轮流量、增强流量、Agent 流量分开统计,混在一起算平均值只会掩盖问题——平均值好看、Agent 分层爆表,是最常见的报表陷阱。第三,合规是底线:所有接入和计费优化都建立在服务商计费规则允许的范围之内,靠预算、限流、缓存、路由这些正规手段把钱管住,不走任何绕过限制的偏门。
风险端重点说幻觉与忠实度。Agentic RAG 并不自动消灭幻觉——检索环节给了证据,汇总生成环节照样可能超出证据自由发挥,甚至把多跳过程中拿到的弱相关片段缝成看似合理的长答案,多跳结构反而给这类错误提供了更长的藏身空间。所以评测指标必须成体系地建:忠实度衡量答案是否被检索证据支撑,召回衡量该检到的证据有没有检到,幻觉率统计无中生有的比例,平均 token 成本/每次问答管住钱袋子。四个指标一起看,缺任何一个都会误判升级收益——只看忠实度会漏掉成本失控,只看成本会漏掉质量塌方。
总结
企业智能检索的三代演进,本质是"检索控制权"的逐步下放:第一代基础 RAG 用向量检索加直接生成把私有知识接进模型,解决了"答不了"的问题,留下复杂多跳无解的短板;第二代增强 RAG 用混合检索、重排、查询改写把单轮检索做到极致,解决了"检不准"的问题,留下"一次检索定终身"的天花板;第三代 Agentic RAG 让 Agent 决定何时检索、检索什么、检索几轮,解决了多跳推理问题,代价是 token 成本与延迟数倍放大。接入侧的三件套——检索工具化、路由分级、缓存策略——是把第三代用得起的工程前提;升级前先量化,用忠实度、召回、幻觉率、平均 token 成本/每次问答四个指标建立基线,再让大部分简单流量留在便宜流水线里,是把账单和质量同时管住的最稳路径。示例代码里的接口基于 https://4sapi.com 的 OpenAI 兼容风格,api_key 全程走环境变量。各自团队的检索系统停在第几代、升级的账单压力卡在哪一步,欢迎在评论区聊聊。