Rag测试

default

从混合检索到GraphRAG:RAG系统优化实录

做一个企业内部文档问答系统,技术选型时走了不少弯路,这里把整个优化过程记录下来,希望对遇到类似问题的朋友有点参考价值。

一、起步:经典的混合检索方案

最开始我设计了一套标准的RAG流水线,架构大概是这样的:

离线阶段:文档经过文本分割后,同时生成向量存入FAISS向量库,并构建BM25索引(保存为bm25_index.pkl)。

在线阶段:用户提问后,向量检索和BM25关键词检索两路并行,通过RRF(倒数排名融合)将两路结果合并,取Top-N文档片段拼成Prompt,送给LLM生成答案。

这套方案的好处是成熟、可控,所有组件都是自己维护的。FAISS跑在内存里速度很快,BM25对专有名词(比如产品型号"XZ-1024")的精确匹配也很管用。RRF融合不需要调参,直接按排名位置融合,避免了向量分数和BM25分数量纲不一致的问题。

上线后基本能用,但逐渐暴露出几个问题:

  1. 多跳推理基本废了。比如问"和A公司合作的那个项目,后来接手的人是谁?",文档里"项目"、“A公司”、“接手人"这三个实体分散在不同段落,向量检索只能找到语义相似的片段,没法把关系串起来。

  2. 主题概括类问题答得稀烂。“这份合同主要涉及哪些风险点?"——纯向量检索只能找到含"风险"这个词的片段,缺少对整体结构的把握。

  3. Rerank环节一直没加上。RRF融合后的Top-N里经常混进一些"看似相关但实际无关"的噪声,喂给LLM后偶尔会编造答案。当时想着加Reranker要额外部署模型,就一直拖着。

二、第一次优化:补上Reranker

犹豫了两个月后终于动手加了Reranker。选的是BGE系列的本地方案——数据不能出内网,没法用Cohere的API。

部署过程比想象中简单:

1
2
3
4
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True)
pairs = [[query, doc] for doc in candidate_docs]
scores = reranker.compute_score(pairs, normalize=True)

把RRF融合后的Top-50丢给Reranker重排,只取Top-3送给LLM。效果立竿见影——之前偶尔出现的"幻觉"基本消失了,答案的精准度明显提升。代价是每次请求多了大概100多ms的推理时间,还在可接受范围内。

但Reranker解决不了根本问题——它只是让检索结果更精准,但检索本身能覆盖的信息范围没变。多跳推理和主题概括依然吃力。

三、转向GraphRAG:为什么选EdgeQuake

后来了解到GraphRAG的思路——不光做向量检索,还要从文档里抽取出实体和关系,构建知识图谱。查询时同时走向量检索和图遍历,把"语义相似"和"关系推理"结合起来。

调研了一圈,排除了几个选项:

  • 微软GraphRAG:太重了,索引成本高得离谱。
  • LightRAG(Python版):算法本身很认可,但它是研究型项目,部署要维护Python环境、装一堆依赖,对我们Java技术栈的团队来说运维成本太高。
  • Neo4j + 自研:从头造轮子,周期不可控。

最后看到EdgeQuake——它是LightRAG算法的Rust重实现,有几个点很打动我:

  1. 单二进制部署:没有Python运行时,没有一堆pip依赖,一个可执行文件搞定。
  2. PostgreSQL统一存储:向量(pgvector)和图(Apache AGE)都在一个数据库里,不用同时维护Neo4j + Milvus两套东西。
  3. 内置多租户:原生支持workspace隔离,我们刚好需要按部门隔离文档。
  4. 性能优势:Rust实现比Python快10-50倍,内存占用也低得多。

四、EdgeQuake部署实录

部署过程比想象中顺利,官方文档写得比较全。

4.1 最快路径:Docker Compose一键启动

如果想最快跑起来,一行命令就够了:

1
curl -fsSL https://raw.githubusercontent.com/raphaelmansuy/edgequake/edgequake-main/docker-compose.quickstart.yml | docker compose -f - up -d

这会拉取三个镜像: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: 编译二进制

1
2
3
git clone https://github.com/raphaelmansuy/edgequake.git
cd edgequake
cargo build --release

编译出来的二进制在target/release/edgequake,大约15MB。

Step 2: 准备PostgreSQL

需要PostgreSQL 15+,加上两个扩展:pgvector(向量存储)和Apache AGE(图存储)。

安装扩展稍微麻烦一点:

1
2
3
4
5
6
7
# pgvector
git clone --branch v0.7.4 https://github.com/pgvector/pgvector.git
cd pgvector && make && make install

# Apache AGE
git clone --branch PG16/v1.6.0-rc0 https://github.com/apache/age.git
cd age && make && make install

然后创建数据库并启用扩展:

1
2
3
4
5
6
CREATE DATABASE edgequake OWNER edgequake;
\c edgequake
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS age;
LOAD 'age';
SELECT create_graph('edgequake_graph');

Step 3: 配置并启动

1
2
3
export DATABASE_URL="postgresql://edgequake:password@localhost:5432/edgequake"
export OPENAI_API_KEY="sk-xxx"  # 或配置Ollama
./target/release/edgequake

4.3 接入Java应用

EdgeQuake提供REST API,我的Java应用(Spring Boot)用RestTemplate调用就行。

核心操作就几个:

创建Workspace(相当于LightRAG里的working_dir):

1
2
3
curl -X POST http://localhost:8080/api/v1/workspaces \
  -H "Content-Type: application/json" \
  -d '{"name": "My Workspace", "description": "..."}'

上传文档

1
2
curl -X POST http://localhost:8080/api/v1/workspaces/{workspace_id}/documents \
  -F "file=@document.pdf"

查询(支持6种模式:naive/local/global/hybrid/mix/bypass):

1
2
3
curl -X POST http://localhost:8080/api/v1/query \
  -H "Content-Type: application/json" \
  -d '{"workspace_id": "ws_xxx", "query": "问题文本", "mode": "hybrid"}'

五、踩过的坑

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是值得的——内部知识库问答场景里,答案质量远比那几百毫秒的延迟重要。

七、一些感想

回头来看,整个优化过程其实是一个不断"发现瓶颈→解决瓶颈"的循环:

  1. 先用最简单的方案跑起来,验证可行性
  2. 发现检索精度不够→加Reranker
  3. 发现语义检索解决不了关系推理→换GraphRAG

每一步都解决了当时最疼的问题,没有一开始就上最复杂的方案——那样可能项目根本启动不了。

EdgeQuake目前还在快速迭代中(写这篇文章时最新版本是v0.19.0),API偶尔会有不兼容的变动。如果追求绝对稳定,可以等等再上生产;如果像我一样愿意接受一定的迭代成本,它目前的表现已经足够好了。

最后想说一句:没有银弹。GraphRAG不是万能的,如果你的场景只是简单的FAQ问答,传统RAG加个Reranker完全够用。只有在确实遇到多跳推理、关系查询这类问题时,才值得引入GraphRAG的复杂度。

Gear(夕照)的博客。记录开发、生活,以及一些不足为道的思考……