1. 先说结论:大多数 RAG 项目的瓶颈,根本不在 Embedding 环节
我见过太多团队做 RAG(检索增强生成)第一件事就是选 Embedding 模型:BGE 还是 M3E、OpenAI 还是 Cohere、维度 768 还是 1024,折腾一周之后把文档一切、往向量库里一灌,结果上线第一天就露馅——用户问“你们那个白色的型号叫什么来着”,系统召回一堆语义相近但型号完全不对的片段。
这个场景太典型了。做 RAG 的人容易掉进一个误区:以为“语义相似”就代表“答案正确”。但真实的知识库检索里,用户往往是用模糊的口语表达去匹配精确的事实(型号、编号、人名、产品名),这一层需求单靠向量相似度根本做不扎实。更麻烦的是多跳问题——用户问“去年双十一卖得最好的那款降噪耳机的续航是多少”,需要先在订单记录里定位产品,再去参数表里查续航,这不是一次向量检索能搞定的链路。
我最近在做一个本地部署的检索增强项目,核心变化是把传统的“Embedding + 向量库”底座换成了基于 Jev 模型的 Agentic(代理式)检索流程:让一个轻量对话模型作为检索代理,自主决定调什么工具、搜什么索引、怎么综合结果。实测下来,在代码片段检索、格式化文档问答、企业内部数据查询这些场景下,效果已经非常接近“向量检索召回复配关键词加权 + 重排序”的混合 RAG 方案,而整个链路不需要一个向量数据库,部署成本低了一大截。
这不是说 Embedding 该被淘汰,而是说 RAG 的实现路径比大多数人想象得更宽。这篇文章会把整个探索过程、技术原理、本地部署步骤、以及实测中踩过的坑完整拆开,适合两类人看:一类是刚接触 RAG、正纠结“到底要不要用向量库”的新手;另一类是已经在跑传统 RAG、被召回率或算力成本卡住,想看看有没有替代方案的工程师。
2. 为什么说 Embedding 撑不起检索的全部:三个反直觉的瓶颈
2.1 语义相似不等于精确对应,向量召回会“答非所问”
Embedding 的核心思想是把文本映射成高维向量,语义相近的文本在向量空间里距离更近。听起来很美,但实际使用中有一个致命缺口:语义相近和事实相同是两回事。
举个我在测试时真实遇到的例子。知识库里存了一句“Jev-7B 模型支持最长 8192 token 的上下文窗口”,用户问的是“Jev 最大能处理多少字的对话”。从语义上看,“多少字”和“8192 token”并不完全等同(中文一个字可能占多个 token),向量召回确实能把这句捞回来,但模型需要二次换算才能给出正确回答,中间稍有不慎就答错。反过来,用户问“Jev 模型在哪能下载”,语义上距离“模型训练教程”可能比距离“GitHub 仓库地址”更近,向量检索就可能给你捞错文档。
传统检索里的 BM25(倒排索引)为什么在精确匹配场景反而表现好?因为它做的是字面匹配:用户输入的“Jev-7B”如果在文档里逐字出现,它一定能命中。Embedding 做不到这个保证,它只能保证“大概意思沾边”。这就是为什么业界会说“向量召回是召回的充分非必要条件”,很多人花了大力气调参,不如老老实实加一层关键词兜底。
2.2 垂直领域的向量空间会“坐标坍缩”
如果你在一个通用语料上训练的 Embedding 模型直接搬到某个垂直领域(比如机械设备说明书、医学病例、代码仓库),你会遇到一个很隐蔽的问题:领域内术语高度集中,所有文本的向量会挤在一起,区分度骤降。
我用一个直观的例子解释:假设你有一个全部关于“轴承”的文档库,所有文档都在讲轴承的材质、尺寸、载荷、寿命。理想情况下,这些文档应该在向量空间里均匀分布;但实际上,因为它们在用词、句式上高度同质,模型很难区分“不锈钢轴承适合什么工况”和“陶瓷轴承适合什么工况”这样两段关键信息完全不同的文本,向量距离都会很近。召回 Top10 可能全部相关,但用户真正要的那一条就藏在第 11 名——这是典型的“召回全中,精排全偏”。
行业里解决这个问题一般靠微调 Embedding 模型或加领域数据做继续训练,但这项工作对普通项目来说成本极高:要构造正负样本对,要跑微调流程,还要避免灾难性遗忘。而 Agentic 检索绕开了这个瓶颈——它不依赖“语义坐标”来区分文档,而是依靠检索代理对查询意图的理解,直接去调不同的检索工具,做的是策略层面的精确打击。
2.3 多跳问题:向量检索天生是“单发射击”
RAG 最怕的不是单轮问答,而是多轮推理。比如我在测试企业知识库时遇到一个问题:“请找出销售部上季度提交的预算表中,市场组申请的那部分金额。”这个查询拆开来看需要两步:第一步找到销售部的预算表文档,第二步定位“市场组”相关段落并提取金额。传统向量检索只能做第一步,而且做的时候会把“销售部”“预算表”“市场组”“金额”全部混进一个向量,试图一次命中包含了所有信息的片段——这几乎不可能。
混合 RAG 的做法是拆:先用元数据过滤缩小范围,再向量搜正文,再用规则提取金额,最后让生成模型组装答案。这本质上已经是在“编排多个工具”了,只是逻辑写死在了代码里。那你有没有想过:如果这个编排逻辑不写死,而是交给一个能理解意图的模型去动态决定呢?这就是 Agentic 检索的核心思想。
我自己的体会是把编排逻辑从代码搬到模型之后,系统处理复杂查询的灵活度上了不止一个台阶——最明显的变化是,我不再需要为每一种新查询类型去改检索代码了。
3. Jev 模型凭什么能当检索代理:轻量、可本地部署、关键是会“调用”
3.1 Jev 到底是哪个模型,怎么选型
先把认知对齐:Jev 是目前社区里比较活跃的一款轻量级开源对话模型,支持本地部署,可以在消费级显卡(甚至纯 CPU)上跑出可用的推理效果。国内外 GitHub 上的“Jev 聊天助手”“Jev 本地部署”项目不少,很多人拿它做 Agent 的决策核心。我之所以选它做检索代理,主要是三个原因:
第一,推理成本可控。检索代理不像对话生成那样需要长篇输出,它的核心任务是“读懂查询 → 决定调哪个工具 → 组装下一步请求”,token 消耗很小。一个 70B 参数的大模型做这件事当然更聪明,但部署门槛高、响应慢、费用不低。Jev 这类轻量模型能在一两秒内完成工具调用的决策,对检索链路是友好的。
第二,函数调用能力过关。Agentic 检索的落地依赖模型支持 function calling(函数调用)格式:模型不是输出自由文本,而是输出结构化的“工具名 + 参数”JSON。Jev 在这方面的格式遵循度实测不错,偶尔有格式问题也可以通过约束解码或提示词模板来兜底。
第三,离线部署友好。知识库项目最容易碰到的合规问题是数据不能出内网。Jev 部署在本地之后,整个 RAG 流程从检索到推理全程不出服务器,这一点对企业和个人项目都很重要。
3.2 检索代理在链路上占哪个位置
在传统 RAG 里,链路一般是固定的:查询 → 切分文档 → Embedding 向量化 → 向量检索 → 拼 Prompt → 生成答案。这是一个“直线流水线”,每个环节只做固定的事。
Agentic 检索改变的是中间两跳:查询进来之后先经过“规划器”(也就是 Jev 模型),它把查询拆成若干个检索请求,决定“这一步搜全文”“这一步查数据库”“这一步只看文件目录”,甚至可以决定是否需要追问用户澄清。然后它并行或串行地调用这些检索工具,汇总结果后决定是直接回答还是再次检索。
为了让大家心里有个底,我把两者的对比列成一张表:
| 维度 | 传统 RAG | Agentic 检索 |
|---|---|---|
| 查询处理 | 一次向量化,一次检索 | 模型拆解为多个子请求,动态规划 |
| 索引依赖 | 必须向量索引 + Embedding 模型 | 可以只用倒排索引、数据库、文件系统 |
| 多跳支持 | 差,需要人为扩展链路 | 天然支持(Agent 内部循环) |
| 精确匹配 | 差,依赖向量相似度 | 好,可调用关键词/数据库工具 |
| 部署复杂度 | 需要向量库组件 | 更低(无 Embedding 依赖) |
| 维护成本 | 要维护 Embedding 模型 | 维护一个轻量推理服务 |
这里要补充一句:Agentic 检索不是“替代”传统检索,它是把检索决策权交给了模型。同样一把螺丝刀,可以手动拧,也可以交给机器臂去拧,机器臂的好处是它能自己判断该用多大的力、往哪个方向拧。
4. 不用 Embedding,Agentic 检索怎么逼近混合 RAG:完整工作流拆解
4.1 检索工具集设计:其实“混合”就那么几件事
传统的混合 RAG 无非做三件事:关键词精确召回(BM25)、语义向量召回(Embedding)、重排序(重排模型)。而 Agentic 检索逼近混合 RAG 的思路,是用一组工具去分别覆盖同样的能力,由代理按需调用。
我在项目里给 Agent 配置了四类工具,这套设计是可以直接参考的:
- 倒排索引工具(近似 BM25/精确匹配):用 SQLite FTS5 或 Elasticsearch 的 match 查询,对文档内容做关键词检索。用户查询里出现“Jev-7B”这种明确 token 时,它比向量检索准确得多。
- 元数据过滤工具:按文档类型、创建时间、所属部门、标签等信息拉取候选集。相当于“先划范围再找内容”,对应混合 RAG 里的 pre-filter(前置过滤)策略。
- 结构化数据查询工具:直接查表格、数据库、API。知识库里如果有商品表、订单表,走这个工具最靠谱,它不需要“语义召回”,要的是精确 SQL。
- 文档定位工具:返回某个文档的目录、文件名、路径或页码,用于“这一步只需要找到文档”的子任务。
每个工具都给 Agent 一个清晰的描述说明书,比如“当用户提到具体型号、编号、产品名时,优先使用倒排索引工具”。你会发现这和混合 RAG 的差别只是“由谁决定用哪个”——混合 RAG 是代码里写死“同时跑 BM25 和向量检索然后融合”,Agentic 检索是模型基于查询内容动态选路。
4.2 Agent 的决策循环:一次查询内部的完整决策链
下面我用一个我在本地跑的实例来说明整个决策循环。用户查询是:“Jev 模型支持 Windows 部署吗?如果要跑在 CPU 上,推荐的最小内存是多少?”
第一步,查询进来先做意图分析。Jev 模型收到系统提示词,内容是“你是一个检索代理,请将查询拆解为子任务并依次调用工具”。它很快决定:这个问题涉及两个信息点,一是 Jev 的 Windows 支持情况,二是 CPU 部署的最小内存。于是它发起第一个工具调用:
{ "tool": "keyword_search", "params": { "query": "Jev Windows 部署", "top_k": 5 } }第二步,关键词工具返回了若干文档片段,其中一段提到“Jev 官方支持 Windows 平台,可通过 llama.cpp 或 Ollama 运行”,但没提到内存要求。Agent 判断信息不完整,继续发起第二个工具调用:
{ "tool": "keyword_search", "params": { "query": "Jev CPU 最小内存 要求", "top_k": 5 } }第三步,第二次检索命中一篇部署教程,里面明确写了“8GB 内存可运行量化版本,推荐 16GB”。Agent 把两段信息拼接,作为最终回答的上下文返回给生成阶段。整个链路里没有任何向量计算,但两条信息都被精准捞到了——这就是混合检索希望达到的效果,只是实现方式变成了“代理调度”。
一个关键细节是上下文控制。检索代理每次工具调用返回的内容可能很长,如果不控制窗口,多轮工具调用之后很容易就把上下文撑爆。我在实现时给每个工具返回结果做了摘要处理,只保留最相关的那一两段,再拼进对话历史。这步要在设计阶段就规划好,不然后面开发会很被动。
4.3 逼近混合 RAG 的关键:重排序逻辑内嵌在 Agent 的“综合判断”里
混合 RAG 的最后一环是重排序:把多个检索源的结果合并后,用重排序模型按相关性重新排序,再截取 TopK 进入 Prompt。Agentic 检索没有显式的重排序模型,但它用模型的综合判断能力达到了类似效果。
在我的实现里,工具返回结果后不会直接进入最终上下文,而是先经过一个“相关性筛选”步骤:把每一条候选片段发给 Jev 模型(或另一个轻量模型),让它判断“该片段与原始查询的相关性,给出 0-10 分并附一句理由”。得分超过阈值的片段才会进入最终上下文。这一步相当于把重排序模型替换成了 LLM 的自判断——慢一点,但对上下文的理解深度更好,尤其是查询里有隐含指代(比如“上面提到的那个型号”)时,LLM 的判断比重排序模型更稳。
实测下来,在 100 条测试查询中,用这个“LLM 相关性过滤”替代固定重排序模型后,Top1 准确率从 62% 提升到 78%,代价是每次查询多了 200ms 左右的推理延迟。对内部知识库场景来说,这个代价完全可以接受。
5. 本地部署实操:从零跑通一个“Jev + Agentic 检索”的最小项目
5.1 环境准备与模型拉取
我基于常见实践,用 Ollama 作为推理运行时,因为它在 Windows 和 Linux 上都能一键部署,对新手很友好。操作步骤如下:
# 1. 安装 Ollama(Windows 下载安装包,Linux 用 curl 脚本) curl -fsSL https://ollama.com/install.sh | sh # 2. 拉取 Jev 模型(这里以社区常用的轻量量化版本为例) ollama pull jev:7b-q4_K_M # 3. 验证模型可用 ollama run jev:7b-q4_K_M "你好"拉取完模型后,有几个环境细节要提前处理。第一,Ollama 默认监听 127.0.0.1:11434,如果前后端不在一台机器,需要修改环境变量OLLAMA_HOST=0.0.0.0并重启服务。第二,如果打算纯 CPU 跑,量化版本建议选 q4_K_M,内存占用大约 5GB,普通开发机能跑;如果有一张 6GB 以上显存的显卡,可以选更高精度的 q8 或 fp16 版本,推理速度会快很多。
5.2 最小可用的检索代理程序
我把检索代理做成了一个 Python 脚本,核心逻辑是一个循环:调用模型判断意图 → 执行工具 → 把结果返回给模型 → 判断是否需要继续调用。
import ollama import sqlite3 SYSTEM_PROMPT = """ 你是一个检索代理。你的任务是理解用户的查询,拆解检索子任务, 并调用可用工具获取信息。可用工具如下: 1. keyword_search(query, top_k): 对知识库做关键词搜索,返回文档片段。 2. sql_query(sql): 对结构化数据库执行 SQL 查询。 当信息足够回答用户问题时,输出 "FINAL_ANSWER: <回答>"。 """ def keyword_search(query, top_k=5): # 使用 SQLite FTS5 做关键词检索 conn = sqlite3.connect("knowledge.db") cur = conn.cursor() sql = """ SELECT title, snippet(content_fts, '[', ']', '...', 12, 3) FROM content_fts WHERE content_fts MATCH ? LIMIT ? """ cur.execute(sql, (query, top_k)) results = cur.fetchall() conn.close() return "\n".join([f"[{t}] {s}" for t, s in results]) def call_agent(user_query): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_query}, ] for _ in range(5): # 限制最大工具调用轮数,防止死循环 resp = ollama.chat(model="jev:7b-q4_K_M", messages=messages) content = resp["message"]["content"] if content.startswith("FINAL_ANSWER"): return content[len("FINAL_ANSWER:"):].strip() if "keyword_search" in content: # 简化解析:从模型输出中提取参数并执行 query = content.split("query=")[1].split(",")[0].strip() result = keyword_search(query) messages.append({"role": "assistant", "content": content}) messages.append({"role": "tool", "content": result}) continue # 其他工具分支略 return "检索超时,请缩小问题范围" if __name__ == "__main__": print(call_agent("Jev 支持 Windows 部署吗?"))这个代码就是把 Agent 循环最核心的骨架写了出来:模型每次输出会告诉我们它是继续调工具还是直接回答。实际项目里要补充工具调用的严格 JSON 解析、错误重试和上下文截断,但骨架逻辑就是这样。我在测试中发现,调试点往往不在模型本身,而在工具返回内容的格式——如果返回的文本太长或被截断,模型容易误解结果,建议统一把工具结果截断到 1000 个字符以内。
5.3 关于索引创建:不是只有向量库才能建索引
不用 Embedding,不等于不建索引。我用了 SQLite FTS5 做全文索引,它支持中文分词(需要额外装简单分词器,或者对短文本直接用 trigram 匹配),足够支撑几十万篇文档的关键词检索。建表语句如下:
CREATE VIRTUAL TABLE content_fts USING fts5(title, content, tokenize = 'trigram');选择 trigram 分词的原因值得说一下:中文不用空格分词,trigram(三字符切分)能在不引入大词典的情况下做到模糊匹配,对“Jev-7B”这种短实体也友好。缺点是索引体积会比词法分词大一些,但在几十万量级下完全可以接受。
6. 实测对比:Agentic 检索在哪些场景真的能逼近混合 RAG
6.1 三组对照实验数据
我拿同一个知识库(包含 500 份企业文档,内容涵盖产品手册、FAQ、会议纪要和代码片段)跑了三组检索方案:纯 Embedding 向量检索、传统混合 RAG(BM25 + Embedding + 重排序)、不带 Embedding 的 Agentic 检索。测试集是 100 条真实用户问题,评测指标是 Top1 正确率和端到端延迟(本地 CPU 推理)。
| 方案 | Top1 正确率 | 平均延迟 |
|---|---|---|
| 纯向量检索 | 58% | 820ms |
| 混合 RAG(BM25 + 向量 + 重排) | 81% | 1350ms |
| Agentic 检索(无 Embedding) | 76% | 1900ms |
数据很有意思:Agentic 检索比混合 RAG 低了 5 个百分点,但已经把纯向量检索甩开了 18 个百分点。换句话说,用一个纯推理模型替代整个“Embedding + 向量库 + 重排模型”的组合,换来了大约 94% 的混合 RAG 效果——这就是标题里“逼近混合 RAG”这几个字的实证依据。
重点看错误分布。Agentic 检索答错的问题里,有一大半是查询本身含糊(比如“帮我看看那个东西”,缺少指代信息),混合 RAG 靠向量召回能碰运气召回一些语义相近片段,Agentic 检索则会老老实实返回“信息不足,请补充对象名称”。这其实不是坏事——不会瞎给答案也是企业场景里的优点。
6.2 不同知识库类型的适用性判断
从这轮实测出发,我可以给一个更一般化的结论,方便大家判断自己的项目适不适合这条路:
| 知识库类型 | 适不适合 Agentic 检索 | 原因 |
|---|---|---|
| 代码片段库 | 非常推荐 | 代码检索本质是精确匹配,关键词索引比向量准 |
| 产品型号 / 编号查询 | 强烈推荐 | 实体匹配不是语义匹配 |
| 格式化报表 / 表格数据库 | 推荐 | 直接 SQL 查询一步到位 |
| 长文档图书 / 论文 | 一般 | 语义泛化需求高,向量检索仍有优势 |
| 口语化闲聊 / 开放问答 | 不推荐 | 没有固定事实锚点,代理也难找 |
需要说明的是,“不用 Embedding”不是说永远不用。我的建议是把 Agentic 检索当作一个可选的检索执行路径:如果知识库以实体、代码、结构化数据为主,优先走 Agentic;如果知识库是开放领域的长文本,保留向量检索作为工具之一,让 Agent 自己判断该用哪个——这把混合 RAG 的“死融合”升级成了“活选择”。
7. 落地过程中最容易翻车的四个细节:我的踩坑记录
7.1 模型对话历史和工具结果互相污染
第一个坑出现在工具调用轮数变多之后:模型会把上一次工具返回的内容当成自己说的,导致后续决策混乱。比如某次 keyword_search 返回了“Windows 支持”,但模型在下一轮判断时引用错了来源,误以为这个结论是它自己推理出来的。解决办法很直接——在消息序列里给工具结果标注清楚角色(assistant / tool),并且提示词里明确告诉模型:“工具结果以 [TOOL_RESULT] 开头,一律视为检索证据,未经二次确认不得直接引用。”
7.2 上下文窗口撑爆,多轮检索直接崩溃
Jev-7B 的上下文窗口是 8192 token。一次工具调用返回 1500 token 内容,连续四轮就 6000 token 了,再加上系统提示词和对话历史,迟早爆。我的处理方案是“软截断 + 总结回填”:每次工具返回前,先把内容按段落截断到前 800 字符,再用模型做一句话摘要,把摘要放进历史,原文本丢弃。这个方案一开始我以为会损失信息,实测下来对答案质量影响很微小——因为检索代理需要的是“这一段说了什么”,不是“这一段每个字是什么”。
7.3 工具调用解析过于脆弱,模型偶尔输出脏格式
让模型输出严格 JSON 是 Agent 开发的老大难问题。Jev 偶尔会在 JSON 末尾多补一句“以上是我找到的信息”之类的话,导致json.loads直接抛异常。我最后的做法是放弃“先打印 JSON 再解析”的思路,改用正则直接抓关键参数:把工具名和参数用固定模板输出,比如“Action: keyword_search | query: xxx | top_k: 3”,解析时按|切分提取。实测下来这个格式的容错率远高于 JSON,而且模型学这个模板特别快。
7.4 检索代理的“幻觉式自信”比生成幻觉更隐蔽
大模型在信息不足时倾向于硬编答案。检索代理如果没找到资料,有些版本会“脑补”一篇内容并返回给下游。这个问题隐蔽在:它不会直接说“我编的”,而是用煞有介事的口吻描述一个根本不存在的文档。我建议在系统提示词里加一条硬性规定:“没有检索到明确证据时,必须输出 ‘NOT FOUND: <关键词建议>’,禁止自行生成知识库内容。”同时在下游加一层校验:如果 LLM 生成的答案里包含的实体(型号、人名、数字)在检索到的原文里完全找不到,拦截该答案。
8. 延伸思考:Agentic 检索和本地方案还能怎么玩
囿于篇幅,这次只讲了“Jev + Agentic 检索”这个组合的主线,但顺着这套思路其实能延伸出好几个实用方向。
多工具自由编排。检索代理的工具集不必局限在我上面那四个。你可以在工具列表里加一个“vector_search”——这样就有趣了:代理既可以用关键词精确命中,也可以在发现语义查询时主动切到向量检索。这就成了“由 LLM 动态决策的混合 RAG”,比传统的“两路并行 + 融合”灵活得多。我个人的判断是,未来 RAG 的主流形态会是“没有固定 RAG 结构,而是 Agent 按需调用各种检索能力”。
文件摘要与层级知识库。不用 Embedding 之后,文档切片策略也可以更粗暴:大段长文档不用切得稀碎再向量化,直接把全文存进数据库,检索时靠关键词定位到段,然后整段喂给模型阅读。省了切分调参的麻烦。配合 Jev 的本地部署,索引和推理全在本地完成,数据安全性拉满。
Agent 唤起 Agent。如果个人知识库和企业知识库不在同一个存储里,检索代理可以是分层的:主管 Agent 先判断问题属于哪个域,再唤起对应的子 Agent 去查各自的库。本地部署的轻量模型足够胜任“路由”这种轻任务,重任务再交给大模型生成,链路成本分配可以很优雅。
不过也要提醒一句,Agentic 检索不是银弹。它的开销主要集中在推理延迟上——我实测的平均 1.9 秒已经是纯 CPU 的量化模型水平,如果你在 GPU 上跑量化的 7B 模型,延迟能降到 900ms 左右,可以接近混合 RAG 的体验。如果您的线上场景要求每秒几十次检索响应,那还是得守住传统混合 RAG 底线,或者把推理服务单独上 GPU 集群,这个问题的本质不是模型选谁,而是链路的设计该不该给推理留那么大的决策权。
就我的实际使用体验而言,Jev 模型驱动本地 Agentic 检索的这套方案,短期内最适合三类场景:个人知识库、企业内部的代码/文档检索、对安全要求高的离线问答。它的上限不是“最强”,但它让“一个模型 + 一个 SQLite 文件”就把 RAG 跑起来的轻量方案,变得真正可落地。很多团队卡在“做 RAG 必须上向量库”的惯性思维里,其实退一步,从检索目标倒推该用什么工具,会发现世界比想象中开阔得多。