这篇文章的前提
上一篇建好了统一测试集:89 道题,50 道单跳事实查询、20 道多跳推理、19 道边界拒答,文档来源是 LightRAG 和 graphrag 的官方技术文档。
这篇的任务是让两个框架在同一套题上跑,然后把数字摆出来。
但在说数字之前,必须先说部署过程——因为部署本身就是选型的一部分。
两个框架的部署差异
LightRAG:pip install 即用
pip install lightrag-hku没有 Docker,没有数据库服务,知识图谱和向量索引存在本地文件里:
rag_storage/
├── graph_chunk_entity_relation.graphml # 知识图谱
├── vdb_chunks.json # 文档向量
├── vdb_entities.json # 实体向量
└── vdb_relationships.json # 关系向量初始化代码:
from lightrag import LightRAG, QueryParam
from lightrag.utils import EmbeddingFunc
rag = LightRAG(
working_dir="./rag_storage",
llm_model_func=llm_func,
embedding_func=EmbeddingFunc(
embedding_dim=1024,
max_token_size=8192,
func=embed_func,
),
)
await rag.initialize_storages() # v1.5.x 新增,必须调用
await rag.ainsert(document_text)
result = await rag.aquery(question, param=QueryParam(mode="mix"))LightRAG 1.5.x 有一个新要求:必须先调 initialize_storages(),否则会报 PipelineNotInitializedError。
QAnything:5 个 Docker 服务
QAnything v2 需要整套基础设施:
services:
elasticsearch # 关键词检索(BM25)
etcd # Milvus 的元数据存储
minio # Milvus 的对象存储
milvus-standalone # 向量数据库
mysql # 文档和知识库元数据
qanything_local # 主服务(embedding + rerank + API)启动命令:
cd QAnything
mkdir -p volumes/es/data && chmod 777 -R volumes/es/data
docker compose -f docker-compose-linux.yaml up -d等日志出现 "qanything后端服务已就绪!" 后,访问 http://localhost:8777/qanything/。
踩坑记录
部署过程踩了三个坑,每个都值得记录。
坑 1:QAnything 容器默认不用 GPU
机器有 RTX 3060,但 QAnything 容器启动日志始终显示:
embedding和rerank服务将在CPU上运行原因:docker-compose-linux.yaml 里的 qanything_local 服务没有配置 GPU 资源,而且 scripts/entrypoint.sh 里那行日志是硬编码的字符串,不是实际判断结果,即使挂了 GPU 也照样打印。
修复一:给 compose 文件加 GPU 挂载:
# docker-compose-linux.yaml
qanything_local:
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ['0']
capabilities: [gpu]修复二:同时还需要安装 NVIDIA Container Toolkit(Docker 默认看不到宿主机 GPU,需要这个桥):
# Ubuntu
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker修复三:entrypoint.sh 启动 embedding/rerank 时没传 --use_gpu 参数:
# scripts/entrypoint.sh,修改前
nohup python3 -u qanything_kernel/dependent_server/embedding_server/embedding_server.py > ...
# 修改后
nohup python3 -u qanything_kernel/dependent_server/embedding_server/embedding_server.py --use_gpu > ...
nohup python3 -u qanything_kernel/dependent_server/rerank_server/rerank_server.py --use_gpu > ...修复后,GPU 显存占用从 1.4GB 跳到 8.6GB,embedding 速度从每秒不到 1 个文档提升到约每 15 秒处理 1-2 个。
坑 2:Milvus 因内存压力崩溃
CPU 模式下 QAnything 容器吃了 27GB 内存,导致 Milvus standalone 的 etcd lease 超时崩溃退出:
"etcdserver: requested lease not found"
"connection lost detected, shuting down"结果:所有文件卡在 gray(等待向量化)状态,永远不会完成。
修复:清理 Milvus 和 etcd 的持久化数据目录后重启(etcd 里有损坏的 session 记录,不清会反复崩溃):
docker compose -f docker-compose-linux.yaml down
rm -rf volumes/milvus volumes/etcd volumes/mysql
mkdir -p volumes/milvus volumes/etcd volumes/mysql
docker compose -f docker-compose-linux.yaml up -d坑 3:user_id 拼接导致 Web 端看不到数据
QAnything 服务端对每个 API 请求的 user_id 会做拼接:
# handler.py
user_info = safe_get(req, 'user_info', "1234") # 默认值 "1234"
user_id = user_id + '__' + user_info # 最终存储的 user_id如果脚本传 user_id=zzp__1234,实际存储的是 zzp__1234__1234,而 Web 端的用户是 zzp__1234,两者不同,Web 看不到 API 创建的知识库。
修复:脚本传 user_id=zzp,服务端拼接后变成 zzp__1234,和 Web 端一致。
评测配置
两个框架使用完全相同的 LLM 和测试集:
| 配置项 | LightRAG | QAnything |
|---|---|---|
| LLM | GLM-4-flash | GLM-4-flash |
| Embedding | BGE-large-en-v1.5(SiliconFlow) | QAnything 内置(BCE embedding,GPU 推理) |
| 测试集 | 89 道题(50 单跳 + 20 多跳 + 19 边界) | 同上 |
| 查询模式 | mix(知识图谱 + 向量融合) | 混合检索(BM25 + 向量 + Rerank) |
| 文档数量 | 31 个 Markdown 文档 | 31 个 Markdown 文档 |
评测结果
核心指标对比
| 指标 | LightRAG 1.5.6 | QAnything v2 |
|---|---|---|
| 边界拒答率 | 10.5%(2/19) | 26.3%(5/19) |
| 平均延迟 | 14,674 ms | 40,519 ms |
| P90 延迟 | 19,430 ms | 52,233 ms |
| 单跳答案匹配(Jaccard) | 0.082 | 0.111 |
| 多跳答案匹配(Jaccard) | 0.178 | 0.162 |
注:答案匹配率用 Jaccard 关键词重叠计算,不是 LLM judge。两个框架的答案通常比 ground_truth 更长(加了解释),Jaccard 值偏低是正常的,用于横向对比有效,绝对值没有参考意义。
答案质量
看同一道题的真实输出:
单跳题:What is the condition under which query/document asymmetric embedding is enabled?
Ground Truth: enabled only when EMBEDDING_ASYMMETRIC=true is explicitly set
LightRAG: Query/document asymmetric embedding in LightRAG is enabled only when
the `EMBEDDING_ASYMMETRIC` setting is explicitly set to true...
[直接答出条件,简洁]
QAnything: ## Inferred Answer Section
According to the reference information, query/document asymmetric
embedding in LightRAG is enabled only when...
[答案正确,但格式冗余,带了 Markdown 标题]两个都答对了,但 LightRAG 输出更干净,QAnything 的系统 prompt 会让回答带上 ## Inferred Answer Section 这类结构化标题。
边界题:How does the RAG system handle data privacy for users in the EU under GDPR?
LightRAG: The Retrieval-Augmented Generation (RAG) system, as implemented in
LightRAG, handles data privacy for users in the EU under GDPR...
[没有拒答,用自身知识编造了一个听起来合理的答案]
QAnything: 抱歉,检索到的参考信息并未提供任何相关的信息,因此无法回答。
[正确拒答]边界拒答是 QAnything 明显强的一个维度。QAnything 的系统 prompt 里有明确的"参考信息无关时必须拒答"规则,LightRAG 的 mix 模式会优先召回知识图谱里的相关实体,即使文档里没有答案也会尝试推理,容易产生幻觉。
延迟分析
LightRAG 的延迟分布:P50=13.9s,P90=19.4s,min=8.7s,max=30.8s
QAnything 的延迟分布:P50=39.2s,P90=52.2s,min=19.6s,max=62.9s
QAnything 的延迟高有两个原因:
- Rerank 步骤:每次查询都要对召回结果做交叉编码重排,这是额外的模型推理
- LLM 调用更稳定:QAnything 每次都能召回到真实文档(source_count=100%),LLM 要处理的上下文更长
LightRAG 的 mix 模式会构建一次知识图谱查询 + 一次向量查询,然后融合结果,LLM 调用通常比 QAnything 更快,但如果图谱遍历范围大也会慢。
选哪个?
基于这次评测,给一个简单的决策参考:
优先选 LightRAG,如果:
- 需要快速验证 RAG 方案,不想花时间配置基础设施
- 团队没有运维 Milvus/ES/MySQL 的能力
- 文档之间有复杂的关联关系,需要图谱多跳推理
- 对延迟敏感(LightRAG P90 比 QAnything 快约 2.7 倍)
优先选 QAnything,如果:
- 需要更强的拒答能力(边界拒答率高出 2.5 倍)
- 有中文文档,需要中文优化的 embedding(BCE embedding 对中文效果更好)
- 需要 Web 界面让非技术人员上传文档
- 生产环境,需要 ES 全文检索 + 向量检索的混合能力
这次评测没测到的:
- 大规模文档(1 万+ 文档)下的性能
- 中文文档的检索质量(本次测试集全英文)
- 知识库更新的速度和稳定性
- QAnything 的 PDF/图表解析能力(本次只用了 Markdown)
这些会在后续文章里补充。
评测代码
完整代码在 llm-in-action/kb-02-lightrag-eval/ 和 llm-in-action/kb-02-qanything-eval/。
LightRAG 评测核心流程:
# 建库
rag = LightRAG(working_dir=STORAGE_DIR, llm_model_func=llm_func,
embedding_func=EmbeddingFunc(embedding_dim=1024, func=embed_func))
await rag.initialize_storages()
await rag.ainsert(doc_content)
# 查询
answer = await rag.aquery(question, param=QueryParam(mode="mix"))QAnything 评测核心流程:
# 建库
kb_id = api_post("new_knowledge_base", {"user_id": USER_ID, "kb_name": KB_NAME})["data"]["kb_id"]
api_post("upload_files", data={"user_id": USER_ID, "kb_id": kb_id}, files={"files": fp})
# 等待向量化完成(轮询 status=green)
while any(s != "green" for s in status_count if s != "green"):
time.sleep(15)
# 查询
result = api_post("local_doc_chat", {
"user_id": USER_ID, "kb_ids": [kb_id], "question": question,
"model": LLM_MODEL, "api_base": LLM_BASE_URL, "api_key": LLM_API_KEY,
"streaming": False
})下一篇:GraphRAG vs HippoRAG——图增强 RAG 的多跳推理测试。同样 89 道题,重点看多跳推理的提升幅度,以及知识图谱构建的时间和成本。
欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页