Vanilla RAG Memory Benchmark

August 7, 2026 · View on GitHub

一个面向服务器部署的朴素 RAG 基线,同时提供 Agent Memory Leaderboard 兼容的同步 Add/Search API、本地 Smoke 和公开数据回放工具。

当前版本:0.6.0

系统边界

项目包含两条互相独立但复用 Embedding 的链路:

离线基准:JSONL -> 切块 -> Embedding -> FAISS -> Top-k -> Answer -> 指标

赛事服务:Add -> SQLite 持久化向量 -> user_id 隔离 -> Search -> 证据列表

赛事服务只返回记忆证据,不生成最终答案。官方平台负责 Answer 和 Eval。 本地工具只能用于预检和公开数据分析,不能生成官方 Smoke/Full 成绩。

课程比赛提交时以平台页面要求为准;如果平台接收 GitHub 仓库地址,由平台拉取 本仓库并构建运行,不需要参赛者提供公网服务器。仓库不得提交 .env、API 密钥、 本地数据库、模型缓存、公开评测原始数据或运行结果。

服务器要求

  • Linux 服务器;
  • Python 3.10 至 3.12,或 Docker;
  • 首次启动能访问 Hugging Face,或者提前准备本地模型目录;
  • 建议至少 8 GB 内存;完整公开集实验建议使用 GPU;
  • 自行托管参赛时需要公网 HTTPS 地址。

默认模型是 BAAI/bge-small-en-v1.5。可以通过 EMBEDDING_MODEL 改成 Hugging Face 模型名或服务器本地目录。建立数据库以后不要直接更换模型;不同 模型的向量维度可能不一致。如需更换,应使用新的数据库文件重新写入数据。

英文 BGE 模型默认使用其检索查询前缀。其他模型可通过 EMBEDDING_QUERY_PREFIX 设置前缀;显式设置为空字符串可关闭前缀。模型和查询 前缀共同构成数据库的 embedding 身份。

Python 部署

cd vanilla-rag-benchmark
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

export MEMORY_API_KEY='replace-with-a-long-random-secret'
export MEMORY_DB_PATH='artifacts/memory.db'
export EMBEDDING_MODEL='BAAI/bge-small-en-v1.5'
export MEMORY_RETRIEVAL_MODE='hybrid'
export MEMORY_CONTEXT_MESSAGES='0'
export MEMORY_LEXICAL_WEIGHT='0.5'
export MEMORY_OPTION_VIEW_WEIGHT='0'
export MEMORY_RESULT_WINDOW='1'
export MEMORY_RESULT_WINDOW_SEED_K='20'

uvicorn vanilla_rag.api:app --app-dir src --host 0.0.0.0 --port 8000 --workers 1

第一次启动需要加载或下载模型,因此 /health 可能需要等待。正式部署建议在 Uvicorn 前增加 Nginx/Caddy HTTPS 反向代理。当前 SQLite 方案使用单个 Uvicorn worker,避免每个进程重复加载模型。

Docker 部署

docker build -t vanilla-rag-memory:0.6.0 .
docker run --rm -p 8000:8000 \
  -e MEMORY_API_KEY='replace-with-a-long-random-secret' \
  -e EMBEDDING_MODEL='BAAI/bge-small-en-v1.5' \
  -e MEMORY_RETRIEVAL_MODE='hybrid' \
  -e MEMORY_CONTEXT_MESSAGES='0' \
  -e MEMORY_LEXICAL_WEIGHT='0.5' \
  -e MEMORY_OPTION_VIEW_WEIGHT='0' \
  -e MEMORY_RESULT_WINDOW='1' \
  -e MEMORY_RESULT_WINDOW_SEED_K='20' \
  -v "$(pwd)/artifacts:/app/artifacts" \
  vanilla-rag-memory:0.6.0

模型缓存较大,正式服务器可额外挂载 Hugging Face 缓存目录。不要把密钥写入 Dockerfile、镜像、GitHub 或公开 URL。

数据库会记录首次使用的 embedding 模型身份。如果后续配置成不同模型,服务会 拒绝打开已有数据库,避免不同模型的向量被静默混用。

也可以复制 .env.example 为服务器上的 .env,修改密钥后使用 Compose:

docker compose up --build -d
docker compose logs -f memory-api

赛事 API

Health

curl http://127.0.0.1:8000/health

Health 不需要鉴权。Add 和 Search 支持以下任意一种鉴权:

X-Api-Key: <MEMORY_API_KEY>
Authorization: Bearer <MEMORY_API_KEY>
Authorization: Token <MEMORY_API_KEY>

Add

curl -X POST http://127.0.0.1:8000/add \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $MEMORY_API_KEY" \
  -d '{
    "request_id":"local:sample:chunk-0",
    "messages":[{"role":"user","timestamp":1704067200000,"content":"Ada Lovelace wrote notes about the Analytical Engine."}],
    "user_id":"local:sample:user-0",
    "session_id":"local:sample:session-0"
  }'

Add 是同步接口。返回 HTTP 200 时,数据已写入 SQLite 且可以立即 Search。相同 request_id 和相同内容重复提交是幂等的;同一 ID 配合不同内容返回 HTTP 409。

curl -X POST http://127.0.0.1:8000/search \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $MEMORY_API_KEY" \
  -d '{
    "query":"Who wrote notes about the Analytical Engine?",
    "options":["A. Ada Lovelace","B. Grace Hopper"],
    "user_id":"local:sample:user-0",
    "top_k":100
  }'

Search 只访问完全相同 user_id 下的记忆,按相关性降序返回最多 top_k 条证据。 0.6.0 默认使用 MEMORY_RETRIEVAL_MODE=hybrid,以 Dense + BM25/RRF 召回种子消息。Search 对前 20 个种子各补充同一会话中的前后相邻消息,去重后再用原始 排名补足 top_k。该结果窗口不改变已存向量,且严格保持用户和会话隔离。

MEMORY_RETRIEVAL_MODE=dense 保留 0.3.0 的朴素稠密基线,hybrid 保留原始的 Dense + BM25/RRF 实验模式。MEMORY_CONTEXT_MESSAGESMEMORY_OPTION_VIEW_WEIGHTMEMORY_RRF_KMEMORY_LEXICAL_WEIGHT 控制增强检索。上下文编码会改变已存向量的 语义身份;从 dense 切换到启用上下文的 enhanced 时必须使用新的数据库文件重新写入。

0.6.0 公开数据验证

0.6.0 冻结配置使用 BAAI/bge-small-en-v1.5、Dense + BM25/RRF、词法权重 0.5、RRF k=60、结果窗口 1 和种子数 20。在 LoCoMo-Refined 的 1,382 道 公开题上,Top K=100 的 Evidence Recall 为 0.927652。使用官方评测代码中的开放题 Answer 模板和 Accuracy Judge 模板,并以 gpt-4o-mini 生成及评分时,Judge Accuracy 为 76.9175%(1,063/1,382)。该实验用于公开数据回归,不是官方 Smoke 或 Full 成绩。

0.5.0 公开数据验证

0.5.0 在带原始会话时间戳的公开 LoCoMo10 固定 200 题分层样本上验证。相对于相同 Embedding、BM25 权重和 Top K=90 的无结果窗口基线,Evidence Recall 从 0.7924 提升到 0.8763,零召回问题从 29 降至 14。使用 gpt-4o-mini 的端到端 Judge Accuracy 从 49.5% 提升到 54.0%,逐题比较为胜 21、负 12。该实验仅用于公开数据回归,不是官方 Smoke 或 Full 成绩。

0.4.0 公开数据验证

0.4.0 冻结前在公开 LoCoMo10 数据的 1,533 个有答案、有证据问题上进行了配对验证, 每个系统使用相同消息、BAAI/bge-small-en-v1.5Top K=90。该实验没有读取或分析 任何官方 Smoke 私有负载。

模式Evidence Recall@90零召回问题全召回问题Answer literal hit
dense (0.3.0 基线)0.79652241,1240.3183
enhanced (0.4.0)0.88221091,2670.3399

逐题比较为增强版胜 228、平 1,259、负 46;10 个会话的平均 Evidence Recall 均未下降。 16 路并发、160 次 Search 的本地负载测试无失败,Search P95 为 0.349 秒。以上结果仅用于 公开数据回归和工程选型,不是官方榜单成绩。

本地 Smoke

服务启动后执行:

python scripts/local_smoke.py \
  --base-url http://127.0.0.1:8000 \
  --api-key "$MEMORY_API_KEY"

脚本会验证 Health、Add 字段回传、幂等、冲突、Search Schema、选择题、空用户和 跨用户隔离。它不会联系赛事官网,也不会消耗官方 Smoke 配额。

完成后重启容器,再执行一次脚本并手动搜索之前写入的用户,以验证挂载目录和 数据库恢复。正式 Smoke 前还应在服务器上进行并发和长时间稳定性测试。

安装依赖并配置环境变量后,可先执行服务器环境检查和小规模负载测试:

python scripts/server_preflight.py
python scripts/load_test.py \
  --base-url http://127.0.0.1:8000 \
  --api-key "$MEMORY_API_KEY" \
  --users 8 --searches-per-user 10 --concurrency 4

本地赛事流程回放

scripts/local_benchmark.py 接受规范化 JSONL。每行代表一个隔离样本:

{"id":"sample-001","session_id":"session-001","messages":[{"role":"user","timestamp":1704067200000,"content":"memory text"}],"questions":[{"id":"q-001","question":"question text","answer":"gold answer","options":["A","B"]}]}

示例回放:

python scripts/local_benchmark.py \
  --input data/sample_competition.jsonl \
  --output artifacts/sample_api_results.jsonl \
  --base-url http://127.0.0.1:8000 \
  --api-key "$MEMORY_API_KEY" \
  --run-label sample-v1 \
  --top-k 100

脚本按官方的 20 条消息/2,000 词边界组装 Add,逐题调用 Search,并计算简单的 answer_in_context_rate。公开数据集需要先转换成上述规范格式。该指标用于排错, 不是官方总分。

LoCoMo-Refined 和 LongMemEval 的常见公开格式可以用内置转换器处理:

python scripts/prepare_public_data.py locomo \
  --input /data/locomo.json \
  --output data/normalized/locomo.jsonl

python scripts/prepare_public_data.py longmemeval \
  --input /data/longmemeval.json \
  --output data/normalized/longmemeval.jsonl

公开仓库可能调整字段;转换后先抽查 messagesquestionsevidence_texts,再进行整套回放。其他数据集使用本节展示的规范 JSONL 作为 稳定接入边界。

离线 Vanilla RAG

服务器安装依赖后:

export PYTHONPATH=src

python -m vanilla_rag.cli index \
  --documents data/sample_documents.jsonl \
  --output artifacts/sample_index \
  --chunk-size 160 \
  --chunk-overlap 40

python -m vanilla_rag.cli ask \
  --index artifacts/sample_index \
  --question "Who developed the Analytical Engine?" \
  --top-k 3 \
  --generator none

python -m vanilla_rag.cli evaluate \
  --index artifacts/sample_index \
  --questions data/sample_questions.jsonl \
  --top-k 3 \
  --generator none \
  --output artifacts/sample_results.jsonl

自定义模型建索引时,模型名称会写入索引元数据。Ask/Evaluate 不指定 --embedding-model 时会自动复用该名称。

启用端到端回答:

export OPENAI_API_KEY='...'
export OPENAI_BASE_URL='https://api.openai.com/v1'

python -m vanilla_rag.cli evaluate \
  --index artifacts/sample_index \
  --questions data/sample_questions.jsonl \
  --top-k 5 \
  --generator openai \
  --chat-model gpt-4o-mini \
  --output artifacts/llm_results.jsonl

离线指标包括真实的多相关文档 Recall@k、MRR、答案证据覆盖率、EM 和 Token F1。 没有相关文档标注或没有启用生成时,对应汇总字段返回 null,不会伪装成零分。

HotpotQA 转换

python -m vanilla_rag.cli prepare-hotpot \
  --input /data/hotpot_dev_distractor_v1.json \
  --output-dir data/hotpot_dev

HotpotQA 是普通多跳 RAG 基准,不是当前 AML 官网列出的正式文本套件。LoCoMo、 LongMemEval、PersonaMem、CLBench 和 BEAM 等公开集应转换为本项目的规范 JSONL 后再进行 API 回放。

测试

PYTHONPATH=src python -m unittest discover -s tests -v

当前单元测试不需要下载模型,覆盖切块、指标、SQLite 持久化、幂等和用户隔离。 FastAPI、SentenceTransformer、Docker 和真实模型的端到端验证需要在安装完整依赖 的服务器上执行。

评测数据清理

查看数据库统计:

python scripts/memory_admin.py --db artifacts/memory.db stats

按照评测 user_id 前缀删除某次运行的数据:

python scripts/memory_admin.py --db artifacts/memory.db \
  purge-prefix --user-prefix 'eval:<run_id>:' --confirm

删除前应保留官方要求的必要审计信息,但不得保留评测原文副本超过许可期限。

正式评测前检查

  1. 固定 Git Commit、镜像标签、依赖、模型和配置;
  2. 本地 Smoke 全部通过;
  3. 重启恢复、并发、限流和长时间运行通过;
  4. 公开数据集无缺失样本,跨用户泄漏率为零;
  5. 公网 HTTPS Health/Add/Search 可从外部访问;
  6. 使用官方 Smoke 修正集成差异;
  7. 最后才启动 Full。

公开赛事契约摘要见 docs/competition-contract.md