从混合检索到GraphRAG:RAG系统优化实录
做一个企业内部文档问答系统,技术选型时走了不少弯路,这里把整个优化过程记录下来,希望对遇到类似问题的朋友有点参考价值。
一、起步:经典的混合检索方案
最开始我设计了一套标准的RAG流水线,架构大概是这样的:
离线阶段:文档经过文本分割后,同时生成向量存入FAISS向量库,并构建BM25索引(保存为bm25_index.pkl)。
在线阶段:用户提问后,向量检索和BM25关键词检索两路并行,通过RRF(倒数排名融合)将两路结果合并,取Top-N文档片段拼成Prompt,送给LLM生成答案。
这套方案的好处是成熟、可控,所有组件都是自己维护的。FAISS跑在内存里速度很快,BM25对专有名词(比如产品型号"XZ-1024")的精确匹配也很管用。RRF融合不需要调参,直接按排名位置融合,避免了向量分数和BM25分数量纲不一致的问题。
上线后基本能用,但逐渐暴露出几个问题:
-
多跳推理基本废了。比如问"和A公司合作的那个项目,后来接手的人是谁?",文档里"项目"、“A公司”、“接手人"这三个实体分散在不同段落,向量检索只能找到语义相似的片段,没法把关系串起来。
-
主题概括类问题答得稀烂。“这份合同主要涉及哪些风险点?"——纯向量检索只能找到含"风险"这个词的片段,缺少对整体结构的把握。
-
Rerank环节一直没加上。RRF融合后的Top-N里经常混进一些"看似相关但实际无关"的噪声,喂给LLM后偶尔会编造答案。当时想着加Reranker要额外部署模型,就一直拖着。
二、第一次优化:补上Reranker
犹豫了两个月后终于动手加了Reranker。选的是BGE系列的本地方案——数据不能出内网,没法用Cohere的API。
部署过程比想象中简单:
|
|
把RRF融合后的Top-50丢给Reranker重排,只取Top-3送给LLM。效果立竿见影——之前偶尔出现的"幻觉"基本消失了,答案的精准度明显提升。代价是每次请求多了大概100多ms的推理时间,还在可接受范围内。
但Reranker解决不了根本问题——它只是让检索结果更精准,但检索本身能覆盖的信息范围没变。多跳推理和主题概括依然吃力。
三、转向GraphRAG:为什么选EdgeQuake
后来了解到GraphRAG的思路——不光做向量检索,还要从文档里抽取出实体和关系,构建知识图谱。查询时同时走向量检索和图遍历,把"语义相似"和"关系推理"结合起来。
调研了一圈,排除了几个选项:
- 微软GraphRAG:太重了,索引成本高得离谱。
- LightRAG(Python版):算法本身很认可,但它是研究型项目,部署要维护Python环境、装一堆依赖,对我们Java技术栈的团队来说运维成本太高。
- Neo4j + 自研:从头造轮子,周期不可控。
最后看到EdgeQuake——它是LightRAG算法的Rust重实现,有几个点很打动我:
- 单二进制部署:没有Python运行时,没有一堆pip依赖,一个可执行文件搞定。
- PostgreSQL统一存储:向量(pgvector)和图(Apache AGE)都在一个数据库里,不用同时维护Neo4j + Milvus两套东西。
- 内置多租户:原生支持workspace隔离,我们刚好需要按部门隔离文档。
- 性能优势:Rust实现比Python快10-50倍,内存占用也低得多。
四、EdgeQuake部署实录
部署过程比想象中顺利,官方文档写得比较全。
4.1 最快路径:Docker Compose一键启动
如果想最快跑起来,一行命令就够了:
|
|
这会拉取三个镜像:EdgeQuake API服务、Web UI、以及预置了pgvector和Apache AGE的PostgreSQL。
服务启动后:
- Web UI:
http://localhost:3000 - REST API:
http://localhost:8080 - 健康检查:
http://localhost:8080/health
4.2 生产部署:二进制 + 自建PostgreSQL
生产环境我选了二进制部署,因为不想在服务器上跑Docker。
Step 1: 编译二进制
|
|
编译出来的二进制在target/release/edgequake,大约15MB。
Step 2: 准备PostgreSQL
需要PostgreSQL 15+,加上两个扩展:pgvector(向量存储)和Apache AGE(图存储)。
安装扩展稍微麻烦一点:
|
|
然后创建数据库并启用扩展:
|
|
Step 3: 配置并启动
|
|
4.3 接入Java应用
EdgeQuake提供REST API,我的Java应用(Spring Boot)用RestTemplate调用就行。
核心操作就几个:
创建Workspace(相当于LightRAG里的working_dir):
|
|
上传文档:
|
|
查询(支持6种模式:naive/local/global/hybrid/mix/bypass):
|
|
五、踩过的坑
5.1 索引时间比预期长
第一次索引一批PDF文档时,速度慢得让人怀疑人生。后来看文档才知道——GraphRAG在索引阶段要调用LLM抽取实体和关系,每份文档都要过好几遍LLM,自然快不了。
官方给出的参考数据是:传统RAG索引一份文档约200ms,EdgeQuake需要5到30秒。
解决:把索引做成异步任务,不阻塞主流程。EdgeQuake的API本身支持异步处理,上传文档后通过/tasks/{track_id}轮询状态就行。
5.2 Ollama超时
一开始用Ollama跑本地模型(gemma3),查询时经常超时。排查发现是两个超时层叠在一起:Ollama本身推理慢,加上EdgeQuake的HTTP客户端超时设置比较短。
解决:换了OpenAI的API,速度立马上来了。如果坚持用本地模型,需要调大EDGEQUAKE_LLM_TIMEOUT环境变量。
5.3 Docker Compose环境变量坑
用Docker部署时,${OPENAI_API_KEY}这种变量如果没设置,容器会静默失败。
解决:启动前确认所有必需的环境变量都已export,或者用.env文件管理。
5.4 健康检查端点救了一次命
有次K8s部署时Pod一直CrashLoopBackOff,查了半天发现是就绪探针配置问题。EdgeQuake自带了/live和/ready两个健康检查端点,配好后问题解决——这种小细节在生产环境真的很救命。
六、效果对比
同样一批文档(大约200份PDF,涵盖合同、技术文档、会议纪要),我用三个版本做了对比:
| 指标 | 基础混合检索 | +Reranker | EdgeQuake |
|---|---|---|---|
| 单跳事实问答 | 92% | 96% | 94% |
| 多跳推理 | 53% | 58% | 87% |
| 主题概括 | 61% | 67% | 91% |
| 平均响应延迟 | ~320ms | ~450ms | ~680ms |
| 索引100份文档 | ~20s | ~20s | ~8min |
数据很直观:EdgeQuake在复杂问题(多跳推理、主题概括)上优势明显,但代价是索引时间和查询延迟都增加了。对我们来说,这个trade-off是值得的——内部知识库问答场景里,答案质量远比那几百毫秒的延迟重要。
七、一些感想
回头来看,整个优化过程其实是一个不断"发现瓶颈→解决瓶颈"的循环:
- 先用最简单的方案跑起来,验证可行性
- 发现检索精度不够→加Reranker
- 发现语义检索解决不了关系推理→换GraphRAG
每一步都解决了当时最疼的问题,没有一开始就上最复杂的方案——那样可能项目根本启动不了。
EdgeQuake目前还在快速迭代中(写这篇文章时最新版本是v0.19.0),API偶尔会有不兼容的变动。如果追求绝对稳定,可以等等再上生产;如果像我一样愿意接受一定的迭代成本,它目前的表现已经足够好了。
最后想说一句:没有银弹。GraphRAG不是万能的,如果你的场景只是简单的FAQ问答,传统RAG加个Reranker完全够用。只有在确实遇到多跳推理、关系查询这类问题时,才值得引入GraphRAG的复杂度。