四种无向量RAG方案深度测评:BM25、GraphRAG、树搜索与智能体实战对比
当我们谈论RAG(检索增强生成)时,向量检索几乎是默认的起点。它通过语义相似度来寻找相关文档,这听起来很智能,但问题恰恰出在这里:它优化的是“语义相似度”,而不是逻辑准确性。
举个例子,“不允许退货的政策”和“允许退货的政策”这两个查询,在向量空间里可能产生几乎相同的嵌入(embedding)。模型理解的不是逻辑上的对立,而是向量空间中的邻近关系。这就像让一个只懂“距离”的人去判断是非,结果可想而知。
另一个麻烦是分块(chunking)。把长文档机械地切成片段,往往会把逻辑的连贯性一并打碎。想象一下,一个定义落在第一个片段里,而约束它的条件却落在另一个片段里。大语言模型拿到的是不完整的上下文,所以也只能靠猜。在受监管的系统里——比如法律、金融、医疗领域——这种猜测带来的不确定性,直接转化为了法律责任。
为了更直观地展示问题,我们准备了一个小型的测试语料库:四份结构化的企业文档(JSON格式),其中预先抽取好了实体与关系。这样,我们就能抛开合成数据的干扰,在真实的数据形态下进行对比。
[
{
"id": "doc1",
"title": "Refund Policy 2026",
"content": "Our refund policy allows for refunds within 30 days of purchase. Digital goods are non-refundable. Error code E-4012 indicates a failed refund transaction due to expired card.",
"entities": ["Refund", "30 days", "Digital goods", "E-4012"],
"relationships": [
["Refund", "has condition", "30 days"],
["E-4012", "means", "expired card"]
]
},
{
"id": "doc2",
"title": "Q3 Revenue Report",
"content": "In Q3 2025, our revenue grew by 15% due to the successful launch of Product X. Manufacturer Z provided critical components. However, supply chain issues with Supplier Y caused minor delays.",
"entities": ["Q3 2025", "Revenue", "Product X", "Manufacturer Z", "Supplier Y"],
"relationships": [
["Product X", "drove", "Revenue"],
["Manufacturer Z", "provided", "components"],
["Supplier Y", "caused", "delays"]
]
}
]
接下来的测试思路很明确:四种不同的检索方法都查询同一份数据集,但每种方法我们都特意挑选最能发挥其长处的查询类型。结果也颇具启发性:没有任何一种方法是通用的银弹。
方法一:BM25词法检索(Lexical retrieval)
当你的目标是查找具体标识符时——比如错误码、条款编号、产品名称——BM25是无可争议的王者。它不理解含义,只做一件事:统计某个词在一篇文档中间出现的频率(TF),并衡量该词在整个语料库中的稀有程度(IDF)。这本质上就是Google搜索最底层在做的事情。
来看一段核心代码:
from rank_bm25 import BM25Okapi
tokenized_corpus = [doc['content'].lower().split() for doc in data]
bm25 = BM25Okapi(tokenized_corpus)
query = "error code E-4012"
tokenized_query = query.lower().split()
doc_scores = bm25.get_scores(tokenized_query)
测试结果如何?
Query: 'error code E-4012'
[doc1] Refund Policy 2026 - Score: 2.2774
[doc2] Q3 Revenue Report - Score: 0.0000
[doc3] Cloud Migration - Score: 0.0000
[doc4] Financial Stability - Score: 0.0000
Best match: Refund Policy 2026
精准命中,零噪声。对比之下,基于向量的RAG可能会从另一个上下文中返回一份讲“transaction errors”的文档,仅仅因为“error”这个词的embedding很相似。而BM25只认字面:E-4012,要么找到,要么找不到。
当然,它的弱点也很明显。查询“OOM error”绝对匹配不到讲“out of memory crash”的文档,因为它不懂同义词。
方法二:GraphRAG 知识图谱遍历
当你需要把多个来源的事实串联起来,回答一个“多跳”(multi-hop)问题时,图谱检索的优势就显现了。比如:“是谁为推动了营收增长的那个产品提供了组件?”回答这个问题需要连接多个节点。
构建图谱的代码并不复杂:
import networkx as nx
G = nx.DiGraph()
for doc in data:
for entity in doc["entities"]:
G.add_node(entity, source_doc=doc['id'])
for subj, pred, obj in doc["relationships"]:
G.add_edge(subj, obj, relation=pred)
测试结果清晰地展示了推理路径:
Query: What drove Revenue?
Relationship found: [Product X] --(drove)--> [Revenue]
Additional fact: [Manufacturer Z] --(provided)--> [components]
看到了吗?两跳,两个事实,外加一条完整的因果链。如果换成基于向量的RAG,它可能只会返回一个提到“营收”的文档片段,却无法揭示“营收因何增长”这个关键上下文。图谱提供的是因果逻辑,而不仅仅是相关文本。
不过,构建图谱的前提是拥有干净、结构化的数据。如果面对的是没有格式的PDF或零散的会议笔记,实体抽取的质量会急剧下降,图谱也就失去了用武之地。
方法三:Tree Search(PageIndex)文档树导航
面对一份结构良好但篇幅很长的文档时——比如年度报告、法律合同、技术规范——我们更希望系统能像一位熟练的分析师那样,先快速扫一眼目录和摘要,而不是笨拙地从第一页读到最后一页。
它的工作方式很直观:
- 把文档解析为一棵树:标题 → 章节 → 子章节,并为每个节点生成简短摘要。
- AI模型首先只在顶层读取标题和摘要。
- 选出最相关的节点,“进入”它,读取其子章节。
- 重复这个过程,直到定位到最相关的文本片段,此时才取出全文用于生成答案。
测试结果如下:
Query: 'financial stability risks'
Scanning node: Documents
-> Analyzing: Refund Policy 2026 - skipped
-> Analyzing: Q3 Revenue Report - skipped
-> Analyzing: Cloud Migration - skipped
-> Analyzing: Annual Report - Financial Stability - HIT
Selected node: doc4 - Annual Report - Financial Stability
Content: The board has approved a new budget for Q4 to mitigate risks identified in Q3.
模型像人类一样,快速扫过标题,果断跳过不相关的章节,最终精准定位到目标文档。全程没有计算任何一个embedding。在真实系统中,这棵树会是多层嵌套的,每个节点的摘要都由LLM生成,以提高导航的准确性。
这种方法最大的优点在于可解释性。你可以清楚地看到系统选择了哪个章节以及选择的理由。在受监管的领域,这不再是“锦上添花”,而是合规的硬性要求。
它的局限同样明显:如果文档本身没有标题结构(如聊天记录、零散笔记),就无法生成有效的树。此外,导航过程中的多次LLM调用也意味着更高的时间和金钱成本。
方法四:Agentic Search 自主研究员
当问题异常复杂、涉及多步骤,并且你事先根本不知道该去哪里寻找答案时,就需要请出“自主研究员”了。Agent会自己规划搜索策略,调用不同的工具,评估中间结果,然后决定是继续深入还是就此停止。
下面是一个简化的Agent循环模拟:
def agent_loop(data, query):
print("Agent: First, I'll search titles for 'Refund'.")
results = tool_search_title(data, "Refund")
if not results:
print("Agent: Nothing. Searching deeper in content.")
results = tool_search_content(data, "Refund")
print(f"Agent: Got a result: {results[0]['title']}. Generating answer.")
return results[0]['content']
测试中,Agent展现了其自主决策能力:
Agent: Received query: What is your refund policy?
Agent (Thinking): Searching for refund policy documents by title.
[Tool] Searching titles for: 'Refund'
Agent: Result: ['Refund Policy 2026']. I ha ve enough information.
Answer: Our refund policy allows for refunds within 30 days...
Agent自行决定搜索策略,并在获得足够信息后适时停止。在真实系统中(搭配o3或Gemini 2.5 Pro这类强推理模型),Agent能够将复杂问题拆解成子查询,交叉验证结果,甚至回溯到之前的步骤进行修正。
毫无疑问,这是最“昂贵”的方案。每一步“思考”都是一次独立的LLM调用,查询量一旦增大,成本会迅速膨胀。此外,能力较弱的模型还可能陷入无限循环或做出错误的规划。
方法对比以及如何选择
选择方法的关键,不在于哪种听起来更“前沿”,而在于你的当前系统在哪里出了问题:
- 返回的文档相似但错误? → 考虑加入BM25或引入更多结构化信号。问题出在“精确度”上。
- 完全找不到相关文档? → 需要加强向量检索。问题出在“召回率”(recall)上。
- 复杂查询返回的是零散垃圾? → 可能需要引入Re-ranker(重排序器)或Agent层来整合与推理。
事实上,到2026年,大多数成熟的RAG系统都是混合架构:向量负责广泛的语义召回,BM25负责精确的字面匹配,Re-ranker负责对初步结果进行质量排序,而基于推理的检索(如Agent)则负责处理最棘手的那部分查询。
这里有一条非常务实的建议:从你自己的系统日志中,挑选出20到30个真实用户问题,然后用不同的方案都跑一遍,对比结果。花上一天时间做这样的评估,往往能省下未来几个月的架构争论。
本文代码
为了方便实践,我们将上述四种方法的完整代码整理成了一个开箱即用的代码仓库,并附带了共享的测试语料库。每种方法都被封装为一个独立的、轻量级的Python技能(skill),并配有一个SKILL.md描述文件。这种设计使得AI Agent能够自动发现并执行这些技能。
# Clone and set up
git clone https://gitlab.com/public-ako74programmer/ai-projects/4-vectorless-rag-approaches.git
cd 4-vectorless-RAG-approaches
setup.bat # creates venv and installs dependencies
# Activate the environment
venv\Scripts\activate
# Test individual methods
python .\agents\skills\bm25-search\bm25.py "error code E-4012"
python .\agents\skills\graphrag\graphrag.py "Revenue"
python .\agents\skills\tree-search\tree_search.py "financial stability risks"
python .\agents\skills\agentic-search\agentic_search.py "Refund"
仓库的依赖非常轻量,只有rank_bm25和networkx。每个测试案例都控制在几十行Python代码以内,力求可读、可修改,方便你直接扩展到自己的数据上。
总结
归根结底,基于向量的RAG并不差,它只是有它的局限性。而这些局限性往往只在真实的生产环境中才会暴露,不会出现在精心设计的Demo里。反过来,无向量方案也不是万能解药,每一种都有其特定的弱点。
真正的解决方案在于组合。根据你的具体问题,将这些工具以合适的方式组合起来,才能搭建出一套不仅仅是“找到信息”,而是能“找到正确信息”的系统。
最好的实践路径是:先从一种最简单的方法开始。测试它,找出它的失效边界。然后,再引入下一种方法来弥补这个弱点。如此迭代,你的系统才会越来越健壮。

