Day18 | Agentic RAG——让模型自己决定查不查、查什么、查几次
苦猿的大模型日记 · Day18 · Agentic RAG——让模型自己决定查不查、查什么、查几次-帮普通人把AI学进简历系列
前言:同一个问题,普通 RAG 答不上来
用户问:"你们家 X3 和 X5 这两款扫地机,哪款更适合养猫的家庭?"
你心里咯噔一下,因为这个问题你公司的 RAG 系统答错过三次。
普通 RAG 把它当一个 query 走完整条流水线:embedding → Milvus 召回 top-3 → bge-reranker 精排 → 拼 prompt → DeepSeek 生成。出来的答案是——
"X3 续航 120 分钟,X5 续航 180 分钟,建议根据家庭面积选择。"
看起来像那么回事。
但答错了。
它根本没理解"养猫"这两个字背后藏着什么——防缠绕主刷能不能搞定猫毛、滤网清理频次、噪音会不会吓到猫、有没有避让宠物模式——这些信息散在 5 份不同的文档里,一次召回根本捞不全。
普通 RAG 是一锤子买卖——查一次、答一次、结束。但真实世界里的复杂问题,从来不是一次查询能搞定的。
过去两篇,我把 RAG 从头跑通了,又把召回准确率从 50% 拉到 90%。但有一个根本前提,我从来没动过——整条流水线是写死的,模型只是被动跑一遍。
今天,把这个前提拆掉。
Agentic RAG 不是新模型,是新模式。把"检索"从代码里硬编码的步骤,变成模型可以自己调用的"工具"——区别不在于模型多强,在于谁来做决策。
PART 01:普通 RAG 的天花板——它只会"被动应答"
很多人召回准确率拉到 90%+,会觉得"我的 RAG 已经无敌了"。
未必。召回再准,也救不了三种问题。
硬伤 1:只会问一次
用户问"X3 适合养猫家庭吗?"——普通 RAG 把这当一个 query 走完一遍就结束。
但用户的真实需求是三步:先查 X3 规格参数 → 再查猫毛处理能力 → 再对比同价位其他型号值不值。普通 RAG 一步就交卷了,剩下两步被压缩进了 LLM 的"幻觉补全"里。
模型不敢说"我需要再查一次"——它的代码里没有这个出口。
硬伤 2:不知道自己不知道
召回的文档里如果根本没提"猫毛处理",普通 RAG 还是会硬答——它会从一些边角信息里编出来一个看似合理的答案。
它没有"我不知道"这个出口。
GPT-4 也会犯这个错。Claude 也会。DeepSeek 也会。不是模型问题,是架构问题——普通 RAG 默认"必须给答案",模型被逼着编。
硬伤 3:不会用工具
用户问"X3 现在多少钱?"——文档里只有 3 个月前的价格表。
普通 RAG 会拿旧价格答,而不是去调实时价格 API。它的代码里根本没有"调外部 API"这一环。
一个反直觉判断
这三大硬伤,靠前面讲的那些召回调优都救不回来。
召回再准,也救不了"该查两次的问题被压缩成一次"。也救不了"文档里压根没有这个信息"。也救不了"需要调外部工具"。
这就是 Agentic RAG 的入口——让模型从被动跑流水线,变成主动决定"我要不要查、查什么、查完够不够"。
PART 02:核心范式——ReAct 决策环
Agentic RAG 听起来很玄,剥开外壳就是ReAct(Reasoning + Acting)—— 2022 年 Google 那篇论文提出来的范式。今天所有 Agent 框架,LangGraph、AutoGPT、CrewAI,底层全是它。
一个完整决策环,四步:
- Think(思考):模型先想"这个问题我需要查吗?查什么?"
- Act(行动):调用一个工具(检索、计算器、外部 API)
- Observe(观察):看工具返回了什么
- Loop(循环或结束):够答了就答,不够就再 Think 一次
用一个真实例子走一遍
还是那个"X3 适合养猫家庭吗"。Agent 的完整决策轨迹是这样的——
Think 1:"需要查 X3 的规格和猫相关功能" Act 1:调用 rag_search(query="X3 规格 防缠绕 滤网") Observe 1:返回 3 个 chunk,提到防缠绕但没提滤网清理频次 Think 2:"滤网清理频次没查到,再查一次" Act 2:调用 rag_search(query="X3 滤网清理 养宠物") Observe 2:返回 1 个 chunk,提到每周清理一次 Think 3:"信息够了,可以答了" Answer:"X3 配备防缠绕主刷,适合养猫家庭;滤网建议每周清理一次……"这就是 Agentic RAG 跟普通 RAG 最本质的区别——普通 RAG 把这段决策轨迹预先写死在代码里,Agentic RAG 让模型自己生成这段轨迹。
一个反直觉点
很多人以为 ReAct 是"循环直到完美",所以会无限优化下去。
不是。
ReAct 是"循环直到模型自己判断够了"。所以——
模型推理能力才是 Agentic RAG 的真正瓶颈,不是召回。
这就是为什么 DeepSeek-V3、Claude 这种推理强的模型跑 Agentic RAG 效果更好。推理弱的模型可能查一次就草草收尾,或者陷入"查不够→再查→还是不够"的死循环。
PART 03:实战第一刀——把"检索"包装成 Tool
这一节是全文最重的代码段。用 DeepSeek 的 function calling(OpenAI 兼容格式),把前面搭好的检索函数包成一个 tool,让模型自己决定要不要调。
Step 1:把检索包成可调用函数
复用前面搭好的栈:bge-m3 embedding → Milvus 召回 → bge-reranker 重排。把它包成一个干净的函数:
# tools.py —— 把检索包成 Agent 可调用的工具 from FlagEmbedding import BGEM3FlagModel, FlagReranker embed_model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=True) reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True) def rag_search(query: str, top_k: int = 5) -> str: """在产品知识库里检索相关信息。 Args: query: 检索查询,建议具体而非笼统 top_k: 返回的文档数量,默认 5 Returns: 匹配到的文档片段,按相关性排序 """ # 标准配方:dense 召回 top-50 → 重排 → top-k vec = embed_model.encode([query])[0] candidates = vectorstore.similarity_search_by_vector(vec, k=50) pairs = [[query, d.page_content] for d in candidates] scores = reranker.compute_score(pairs, normalize=True) ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])[:top_k] return "\n\n".join([d.page_content for d, _ in ranked])Step 2:给工具写 schema
DeepSeek 的 function calling 跟 OpenAI 一模一样。给工具写个 schema,告诉模型"这个工具叫什么、什么时候该用、参数长什么样":
tools = [{ "type": "function", "function": { "name": "rag_search", "description": "在产品知识库里检索相关信息。当用户问及具体产品规格、政策、流程时调用。", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "具体的检索查询"}, "top_k": {"type": "integer", "description": "返回文档数,默认 5"} }, "required": ["query"] } } }]一个关键工程坑
description 写得清楚不清楚,直接决定 Agent 会不会调用它。
模型决策完全靠 description 来判断"该不该用这个工具"——
- 写得太笼统(
"检索信息"),模型会乱调,啥问题都先去知识库翻一遍 - 写得太具体(
"只在用户提到 X3 时调用"),模型会漏调,X5 来了就忘了用
好的 description 应该回答三个问题:这个工具做什么、什么时候该用、什么时候不该用。
反直觉点
工具数量别贪多。
3 个写得清楚的工具 > 10 个写得模糊的工具。
模型在多工具场景下,选择准确率会断崖式下降——Anthropic 自己测过,工具数从 5 加到 20,调用准确率能掉 30 个百分点。
别一上来就给 Agent 配 10 个工具。先把 1 个调好。
PART 04:跑通一个最小 Agentic RAG
把 PART 03 的工具,接到 DeepSeek 的对话主循环里,跑通一个会自己决定查几次的 Agent。
完整可运行代码:
# agent.py —— 最小 Agentic RAG 主循环 from openai import OpenAI import json client = OpenAI( api_key="你的 DeepSeek API Key", base_url="https://api.deepseek.com/v1" ) def agentic_rag(user_query: str, max_turns: int = 5) -> str: messages = [ { "role": "system", "content": ( "你是一个会主动检索知识库的助手。" "遇到不确定的事实问题,先调用 rag_search 查资料," "查到足够信息再回答。如果查不到,直接说不知道。" ) }, {"role": "user", "content": user_query} ] for turn in range(max_turns): response = client.chat.completions.create( model="deepseek-chat", messages=messages, tools=tools, tool_choice="auto" # 关键:让模型自己决定调不调 ) msg = response.choices[0].message messages.append(msg) # 模型决定不调工具 → 直接答,结束循环 if not msg.tool_calls: return msg.content # 模型决定调工具 → 执行 → 把结果塞回 messages for call in msg.tool_calls: args = json.loads(call.function.arguments) result = rag_search(**args) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result }) return "(超过最大轮次,强制结束)"同一个问题,两个版本对比
用户问:"我们 X3 扫地机适合养猫家庭吗?滤网多久清一次?"
普通 RAG——一次召回 → 答 → 大概率漏掉"滤网清理"那块,因为 query 偏向"适合养猫家庭",滤网信息排到了 top-10 开外。
Agentic RAG(上面这个循环)——模型自己拆成 2 次检索:先查 X3 养猫相关,再单独查滤网清理 → 各自召回 → 综合答 → 完整。
三个工程坑
坑 1:延迟是普通 RAG 的 2-5 倍。每次决策都要一次 LLM 调用 + 可能的检索。别把它当默认方案——按 PART 06 的决策表用。
坑 2:max_turns必须设上限。不设的话,模型可能陷入"查不够→再查→还是不够→再查"的死循环,烧 token 烧到天荒地老。5 轮是经验值,绝大多数问题 3 轮内能搞定。
坑 3:流式输出必须开。Agentic 一轮动辄几秒,用户等得没耐心。开 streaming,先吐"我先查一下 X3 的规格..."这种过渡话术,体验会好很多。
PART 05:让 Agent 会"反思"——Self-RAG
PART 04 那个循环能用,但有个问题:模型拿到检索结果后,直接信了。
它不会判断"这些结果是不是真的回答了问题"。如果召回跑偏了,模型也跟着跑偏。
Self-RAG(2023 年 ICLR 那篇)就是解这个的——核心思想是让模型输出"反思 token":[Retrieve][Relevant][No Relevant][Generate],每一步都先判断一下。
不用真去训练模型,靠 prompt 也能模拟个七八成。
反思 prompt(追加到 system message)
每次拿到检索结果后,先在内心判断三点: 1. 检索结果是否真的回答了用户问题? (是 → 继续;否 → 改写 query 重查) 2. 是否有明显遗漏的关键信息? (是 → 补查) 3. 信息之间是否冲突? (是 → 再查一次确认) 不要直接告诉用户你在反思,直接决定要不要再调用 rag_search。三种"反思触发"场景
场景 1:召回结果跑题。query 太宽,返回的 chunk 跟问题无关。反思后改写更具体——比如把"X3 适合养猫吗"改成"X3 防缠绕主刷 猫毛 测试"。
场景 2:召回结果不完整。用户问 A 和 B,只查到 A。反思后补查 B。
场景 3:召回结果冲突。两份文档说法不一致(旧版手册说续航 120 分钟,新版说 150 分钟)。反思后再查一次确认。
反直觉点
反思不是免费的。
每次反思 = 多一次 LLM 调用 + 可能的检索。复杂查询的延迟可能直接翻倍。
生产环境通常只开"召回结果跑题"这一种反思——其他场景靠 prompt 让模型自己判断要不要补查就行,不用显式反思。
不要为了显得"智能"就把三种反思全开,那是给自己挖坑。
PART 06:何时该上 Agentic RAG
这一节最重要。
Agentic RAG 不是普通 RAG 的升级版,是另一种东西。用错场景,反而更糟。
决策表
| 场景 | 该用哪个 | 理由 |
|---|---|---|
| 用户问"退货政策是什么" | 普通 RAG | 单跳问题,一次召回就够,Agentic 是浪费 |
| 用户问"X3 比 X5 适合养猫吗" | Agentic RAG | 多跳问题,需要拆解 + 多次检索 |
| 用户问"上周销量为啥跌了" | Agentic + 多工具 | 需要查销售库 + 查事件库 + 交叉分析 |
| 用户问"今天天气如何" | 直接调 API | 不需要知识库,外部 API 一次搞定 |
| 客服 FAQ(答案明确且单一) | 普通 RAG | Agentic 反而引入不确定性 |
| 复杂业务咨询(多约束条件) | Agentic RAG | 唯一能搞定的方案 |
三个上线坑
坑 1:延迟爆炸。Agentic 一轮 = 1 次 LLM + 可能的检索,多轮就是数倍延迟。实时聊天场景必须给前端加"思考中..."动画 + 流式输出。
坑 2:token 成本翻 3-10 倍。每次决策、每次反思都要 LLM 调用。生产环境必须监控 token 用量,否则账单会让你怀疑人生。一个朋友上了 Agentic RAG 之后,月度 API 账单从 800 涨到 6500——他没监控。
坑 3:可解释性下降。Agentic 决策是黑盒,用户问"为啥你查了 3 次"——你必须保留决策日志,方便 debug 和审计。
一个收尾判断
Agentic RAG 的核心价值不在"答得准",而在"能处理普通 RAG 处理不了的复杂问题"。
别为了时髦而升级。先问自己一句:我的场景真的需要多轮检索吗?
如果不需要,普通 RAG + 前面讲的那套调优已经够打了。
结尾
普通 RAG 像查字典——你问一个词,它翻一页。
Agentic RAG 像请了个研究员——你问一个问题,它会自己决定翻几本书、每本翻到哪一页、翻完够不够、不够再去借一本。
RAG 的尽头不是更准的召回,而是会自己决定查不查、查几次、查完够不够的 Agent。
但 Agent 不是答案,是一种新工程。它会让你答得更准,也会让你的延迟翻倍、账单翻倍、debug 难度翻倍。
值不值得,看你的问题复杂到什么程度。
下一篇 Day19——当一个 Agent 干不过来时,怎么让多个 Agent 分工合作。Multi-Agent 不是堆人数,是另一套协作范式。关注苦猿,下篇见。
互动时间:你做过的最复杂的 RAG 场景,模型一次查不出来的那种,是什么样?评论区聊聊,我下篇可能就拆它。
— END —
苦猿 · 帮普通人把 AI 学进简历
