企业智能检索这套底座,过去几年换了三代:从纯向量 RAG,到混合检索加重排的增强 RAG,再到让 Agent 自己决定何时检索、检索什么、检索几轮的 Agentic RAG。我把三代各自的取舍、接入架构和 token 成本账整理成这篇笔记,示例接口统一走 https://4sapi.com 的 OpenAI 兼容风格。

一个绕不开的两难:老系统答不对,升级又怕账单失控

上了第一代或第二代检索系统的团队,多半熟悉同一组抱怨。一边是业务方:演示环境里问答如流水,一上真实业务,稍微绕个弯的问题就开始出问题——问"A 供应商交付延期影响了哪些下游项目,这些项目的负责人分别该采取什么动作",系统要么只答了供应商,要么把两条不相关的检索片段缝在一起硬答,幻觉就是这么来的。另一边是成本方:听说第三代 Agentic RAG 能解决这些问题,一看"多轮检索加多轮生成"的机制,第一反应是账单会不会直接翻几倍。

这两难都是真实的。复杂多跳问题确实是第一代、第二代的结构性短板,第三代确实有解法;但第三代每一次"决定要不要检索、用什么工具检索、证据够不够"都是一次完整的模型调用,token 是真金白银。我的结论写在前面:不升级的焦虑没必要,全量升级也不理智,正确姿势是分级路由——让简单问题留在便宜的流水线里,只把真正复杂的问题送进 Agent 循环,再用缓存把重复消耗压下去。这篇就按这个思路展开。

原理速览:三代演进到底改了什么

每一代都在解决上一代搞不定的问题,架构形态的变化非常清晰:

第一代 基础 RAG(单轮流水线)
  问题 ──> Embedding ──> 向量库 top-k ──> 拼提示词 ──> 一次生成 ──> 答案
  (检索一次、生成一次,流程到此结束)

第二代 增强 RAG(增强过的单轮流水线)
              ┌─> 向量检索 ───┐
  问题 ──> 查询改写 ──┤               ├─> 合并去重 ──> 重排 ──> 一次生成 ──> 答案
              └─> 关键词检索 ─┘
  (检索仍只有一轮,但召回与排序质量被大幅拉升)

第三代 Agentic RAG(Agent 检索循环)
  问题 ──> Agent ──> 判断:证据够吗?
                        │ 够 ────────> 汇总生成 ──> 答案
                        │ 不够 ──> 选工具(向量 / 关键词 / 结构化)──> 检索
                        │              └──> 改写查询 ──> 回到判断
                        └ 达到跳数上限 ──> 强制汇总 ──> 答案
  (何时检索、检索什么、检索几轮,由 Agent 当场决定)

三代不是替代关系。第二代没有淘汰第一代,第三代也不会把前两代扫进垃圾桶——它们各自有最划算的适用场景,后面对照表里展开。

第一代基础 RAG:简单可靠,但一跳之外全是盲区

基础 RAG 的关键词是"接上":向量检索加直接生成,把私有知识第一次接进模型的上下文。它解决的是从无到有——不接检索,问内部制度、内部文档,模型只能靠训练时固化的公开知识硬编;接上之后,答案有出处、可追溯,幻觉问题在简单事实场景里得到实质缓解。成本上它也是最便宜的一代:一次 Embedding、一次向量检索、一次生成,token 消耗约等于提示词加答案,延迟通常在两三秒量级。

问题同样明显,而且大多是结构性的。第一,它是"一次检索定终身"的鼻祖——检索一轮就锁定证据,检索结果里没有的东西,答案里就不会有,没有任何补救机会。第二,向量检索靠语义相似度工作,对专有名词、产品型号、内部代号这类精确匹配需求天然偏弱,Embedding 空间里的"相似"和"相关"并不总是重合,缩写和口语化提问更容易失手。第三,复杂多跳问题基本无解:问"A 供应商交付延期影响了哪些下游项目",这类问题需要先查供应商记录、再顺藤摸瓜查项目、再查负责人,单轮流水线连第一步该查什么都不知道,只能把整个问题扔给向量检索,召回什么算什么。第四,流程里没有自我校验环节,检索片段质量差的时候,模型照样一本正经地往下编。

一句话总结第一代:它把"有依据地回答简单问题"做成了标准件,代价是在复杂问题面前几乎不设防。

第二代增强 RAG:把单轮检索做到极致,也把天花板焊死

增强 RAG 是过去几年企业落地的主力形态。核心思路是承认单轮框架,然后在框架内榨出每一分质量:

这些增强的收益是系统性的,尤其体现在术语密集的业务场景。具体幅度各家数据、切分方式、评测集差异太大,脱离语境的基准数字只会误导,这里只给相对关系,不给编造的数字。

遗留问题同样清楚。它仍然是"一次检索定终身":改写、扩展、混合、重排,全都发生在那唯一的一轮里,第一轮没检到的证据,后续再也没有机会补。多跳问题里"第二步依赖第一步的结果"这个结构性困难,增强 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 建议在动架构之前逐项过一遍:

清单里最容易被忽略的是最后一条:降级路径。第三代系统出问题时能退回第二代流水线,业务方才敢放心把流量切过来;没有降级路径的升级方案,评审阶段就该被拦下来。

成本与风险提示:多跳放大与忠实度评测

成本端集中强调三点。第一,多跳成本放大是结构性的,不是调参能消除的:每多一跳就多一次决策调用、一次工具执行、一次结果注入,token 消耗随跳数近似线性上涨,演示口径下单次问答到 10k token 以上很正常。第二,账单要按路由分层看:单轮流量、增强流量、Agent 流量分开统计,混在一起算平均值只会掩盖问题——平均值好看、Agent 分层爆表,是最常见的报表陷阱。第三,合规是底线:所有接入和计费优化都建立在服务商计费规则允许的范围之内,靠预算、限流、缓存、路由这些正规手段把钱管住,不走任何绕过限制的偏门。

风险端重点说幻觉与忠实度。Agentic RAG 并不自动消灭幻觉——检索环节给了证据,汇总生成环节照样可能超出证据自由发挥,甚至把多跳过程中拿到的弱相关片段缝成看似合理的长答案,多跳结构反而给这类错误提供了更长的藏身空间。所以评测指标必须成体系地建:忠实度衡量答案是否被检索证据支撑,召回衡量该检到的证据有没有检到,幻觉率统计无中生有的比例,平均 token 成本/每次问答管住钱袋子。四个指标一起看,缺任何一个都会误判升级收益——只看忠实度会漏掉成本失控,只看成本会漏掉质量塌方。

总结

企业智能检索的三代演进,本质是"检索控制权"的逐步下放:第一代基础 RAG 用向量检索加直接生成把私有知识接进模型,解决了"答不了"的问题,留下复杂多跳无解的短板;第二代增强 RAG 用混合检索、重排、查询改写把单轮检索做到极致,解决了"检不准"的问题,留下"一次检索定终身"的天花板;第三代 Agentic RAG 让 Agent 决定何时检索、检索什么、检索几轮,解决了多跳推理问题,代价是 token 成本与延迟数倍放大。接入侧的三件套——检索工具化、路由分级、缓存策略——是把第三代用得起的工程前提;升级前先量化,用忠实度、召回、幻觉率、平均 token 成本/每次问答四个指标建立基线,再让大部分简单流量留在便宜流水线里,是把账单和质量同时管住的最稳路径。示例代码里的接口基于 https://4sapi.com 的 OpenAI 兼容风格,api_key 全程走环境变量。各自团队的检索系统停在第几代、升级的账单压力卡在哪一步,欢迎在评论区聊聊。