ProxySQL RAG 子系统完全实现指南:基于 MCP 的检索增强生成能力
ProxySQL RAG 子系统完全实现指南:基于 MCP 的检索增强生成能力
ProxySQL 的 RAG(Retrieval-Augmented Generation)子系统为 LLM 驱动的应用提供了完整的检索能力:文档与向量化嵌入存储在基于 SQLite 的向量数据库中,并通过 MCP 协议暴露一组检索工具,支持全文检索(FTS5)、语义检索(sqlite3-vec)、混合检索以及从源数据库回捞权威数据。阅读本文,你将掌握该子系统的数据模型、全部七个 MCP 工具的参数语义、混合检索两种模式的实现原理、安全与性能设计,以及如何在实战中启用和验证这套能力。
实现总览:从蓝图到落地
doc/RAG/RAG_IMPLEMENTATION_COMPLETE.md 是一份实现完成声明,标志着 ProxySQL RAG 子系统已按照蓝图文档中的 v0 交付物全部落地。核心成果包括:
- RAG Tool Handler:新建
RAG_Tool_Handler类,继承自MCP_Tool_Handler,实现了全部要求的 MCP 工具。 - 数据库集成:在
AI_Features_Manager中新增完整的 RAG schema。 - MCP 集成:将 RAG 工具处理器挂载到
MCP_Thread,并在ProxySQL_MCP_Server中注册/mcp/rag端点。 - 配置体系:在
GenAI_Thread中新增一组genai_rag_*配置变量。 - 测试与文档:配套了 MCP 端测试脚本、schema 单测以及两份使用文档。
从源码结构看,整个子系统由 RAG_Tool_Handler.h(头文件,声明工具列表、参数校验、统计计数)与 RAG_Tool_Handler.cpp(约 3056 行,实现全部检索逻辑)构成,是整个 RAG 能力的主体。
数据库 Schema:七张核心对象的职责划分
RAG 数据模型在 AI_Features_Manager.cpp 中初始化,存放于向量数据库(默认路径 /var/lib/proxysql/ai_features.db)。各对象职责如下:
| 对象 | 类型 | 职责 |
|---|---|---|
rag_sources |
普通表 | 摄入(ingestion)配置控制面,记录后端连接信息、表名、主键列、文档映射与分块配置 |
rag_documents |
普通表 | 权威文档(canonical documents),含 pk_json、标题、正文、元数据 |
rag_chunks |
普通表 | 分块后的检索单元,含 chunk_index、正文与分块元数据 |
rag_fts_chunks |
FTS5 虚拟表 | 关键词检索索引(tokenize = 'unicode61',chunk_id 不索引) |
rag_vec_chunks |
vec0 虚拟表 | 向量检索索引,维度取自 genai_vector_dimension(默认 1536) |
rag_sync_state |
普通表 | 增量摄入的同步状态跟踪 |
rag_chunk_view |
视图 | 调试用便捷视图,仅展示未删除的文档与分块 |
rag_sources 表完整定义(来自源码)体现了"控制面"的设计:
CREATE TABLE IF NOT EXISTS rag_sources (
source_id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
enabled INTEGER NOT NULL DEFAULT 1,
backend_type TEXT NOT NULL, -- mysql / pgsql
backend_host TEXT NOT NULL,
backend_port INTEGER NOT NULL,
backend_user TEXT NOT NULL,
backend_pass TEXT NOT NULL,
backend_db TEXT NOT NULL,
table_name TEXT NOT NULL,
pk_column TEXT NOT NULL,
where_sql TEXT, -- 附加过滤条件
doc_map_json TEXT NOT NULL, -- 源列到文档字段的映射
chunking_json TEXT NOT NULL, -- 分块策略
embedding_json TEXT, -- 嵌入配置
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
rag_vec_chunks 使用 sqlite3-vec 的 vec0 虚拟表语法创建,维度由 GenAI 模块的 genai_vector_dimension 变量动态决定:
CREATE VIRTUAL TABLE IF NOT EXISTS rag_vec_chunks USING vec0(
embedding float[1536],
chunk_id TEXT,
doc_id TEXT,
source_id INTEGER,
updated_at INTEGER
);
rag_chunk_view 将分块与文档联结,过滤掉已删除记录,便于排查检索结果是否涉及失效数据:
CREATE VIEW IF NOT EXISTS rag_chunk_view AS
SELECT c.chunk_id, c.doc_id, c.source_id, d.source_name, d.pk_json,
COALESCE(c.title, d.title) AS title, c.body,
d.metadata_json AS doc_metadata_json,
c.metadata_json AS chunk_metadata_json, c.updated_at
FROM rag_chunks c
JOIN rag_documents d ON d.doc_id = c.doc_id
WHERE c.deleted = 0 AND d.deleted = 0;
索引设计上,rag_sources 建有 enabled 与后端定位索引(backend_type, backend_host, backend_port, backend_db, table_name);rag_documents 建有 (source_id, updated_at) 与 (source_id, deleted) 索引;rag_chunks 建有 (doc_id, chunk_index) 唯一索引以及 (source_id, doc_id)、deleted 索引,为增量同步与过滤查询提供支撑。
配置变量:GenAI 线程中的 RAG 参数
RAG 配置变量在 GenAI_Thread.cpp 中初始化,默认值如下:
| 变量 | 默认值 | 语义 |
|---|---|---|
genai_rag_enabled |
false |
RAG 功能总开关 |
genai_rag_k_max |
50 |
单次检索返回结果数上限 |
genai_rag_candidates_max |
500 |
混合检索候选集上限 |
genai_rag_query_max_bytes |
8192 |
查询串长度上限(字节) |
genai_rag_response_max_bytes |
5000000 |
响应体大小上限(字节) |
genai_rag_timeout_ms |
2000 |
操作超时(毫秒) |
RAG_Tool_Handler 构造函数把这些值作为兜底默认,并在 refresh_runtime_dependencies() 中从全局 GloGATH->variables 实时取用(RAG_Tool_Handler.cpp),因此执行 LOAD genai VARIABLES TO RUNTIME 后无需重建处理器即可生效——这正是 refresh_runtime_dependencies 存在的意义。
启用 RAG 的标准操作(经由 ProxySQL admin 接口):
-- 启用 GenAI 模块
SET genai.enabled = true;
-- 启用 RAG 特性
SET genai.rag_enabled = true;
-- 可选:按需调整检索参数
SET genai.rag_k_max = 50;
SET genai.rag_candidates_max = 500;
SET genai.rag_timeout_ms = 2000;
-- 加载配置到运行时
LOAD genai VARIABLES TO RUNTIME;
MCP 工具族:七个工具的完整参数语义
RAG 工具通过 ProxySQL_MCP_Server 注册的 /mcp/rag 端点暴露(ProxySQL_MCP_Server.cpp),遵循 MCP JSON-RPC 协议。工具清单与输入 Schema 由 get_tool_list() 动态生成(RAG_Tool_Handler.cpp)。
rag.search_fts:基于 FTS5 的关键词检索
参数:query(必填)、k(默认 10,上限 50)、offset(分页偏移,默认 0)、filters、return。
内部生成的 SQL 使用 bm25(rag_fts_chunks) 作为相关性分数,并将 BM25 原始值(越低越好)归一化为 0–1 的"越高越好"分数:score = 1 / (1 + |bm25_raw|)。查询串会先经过 escape_fts_query() 将单引号翻倍转义,防止 FTS 注入,同时拦截 ;、--、/*、DROP、DELETE 等危险片段(RAG_Tool_Handler.cpp)。
{
"query": "mysql performance",
"k": 5,
"filters": {
"source_names": ["stackexchange"],
"tags_any": ["mysql", "optimization"],
"min_score": 0.1
},
"return": {
"include_title": true,
"include_metadata": true,
"include_snippets": false
}
}
rag.search_vector:基于向量嵌入的语义检索
参数:query_text(必填)、k、filters、return,可选 embedding(指定模型)与 query_embedding(直接传入 dim + values_b64 的预计算向量)。
实现上先通过 GloGATH->embed_documents({query_text}) 生成查询嵌入,再以 sqlite3-vec 的 MATCH 语法检索。值得注意的工程细节:SQL 中 LIMIT 必须与 MATCH 位于同一查询层级,因此源码采用子查询方式将向量召回(rag_vec_chunks 内层按 distance 排序取 top-k)与元数据过滤(外层 JOIN)分离(RAG_Tool_Handler.cpp)。
rag.search_hybrid:两种模式的混合检索
参数:query(必填)、k、mode(fuse 或 fts_then_vec)、filters,以及按模式区分的嵌套参数对象。
fuse 模式(并行融合 + Reciprocal Rank Fusion):FTS 与向量检索并行执行,各自取 fts_k/vec_k(默认各 50)条候选,按 chunk_id 合并后计算加权 RRF 分数:
fused_score = w_fts / (rrf_k0 + rank_fts) + w_vec / (rrf_k0 + rank_vec)
rrf_k0 默认 60,w_fts/w_vec 默认 1.0。compute_rrf_score() 的实现正是 weight / (k0 + rank)(RAG_Tool_Handler.cpp)。结果同时返回 score_fts、score_vec、融合后的 score 以及 debug.rank_fts/debug.rank_vec 排名信息。
{
"query": "sql query optimization",
"mode": "fuse",
"k": 5,
"fuse": {
"fts_k": 50,
"vec_k": 50,
"rrf_k0": 60,
"w_fts": 1.0,
"w_vec": 1.0
}
}
fts_then_vec 模式(候选生成 + 向量重排):先用 FTS5 生成宽泛候选(candidates_k 默认 200,受 candidates_max 约束),再针对候选 chunk_id 列表执行向量检索并取 rerank_k(默认 50)条按距离排序。适合 FTS 召回精度高、需要语义精排的场景。
{
"query": "sql query optimization",
"mode": "fts_then_vec",
"k": 5,
"fts_then_vec": {
"candidates_k": 200,
"rerank_k": 50,
"vec_metric": "cosine"
}
}
rag.get_chunks / rag.get_docs:内容回取
rag.get_chunks以chunk_ids数组为输入,return可控制include_title、include_doc_metadata、include_chunk_metadata(正文body恒返回),通过rag_chunks LEFT JOIN rag_documents联表取回分块及所属文档元数据。rag.get_docs以doc_ids数组为输入,return可控制include_body、include_metadata,返回文档的source_id、source_name、pk_json、标题与正文。
两者都会对 ID 中的单引号做转义处理并拒绝含 \、; 等危险字符的输入。
rag.fetch_from_source:回捞源数据库权威数据
这是 RAG 避免"知识过期"的关键工具。它通过 fetch_rag_source_config() 从 rag_documents JOIN rag_sources 查出该文档对应的源后端配置(host、port、user、table、pk 等),随后按 backend_type 分发到 MySQL(dial_mysql + mysql_real_escape_string)或 PostgreSQL(dial_pgsql + PQescapeLiteral)路径(RAG_Tool_Handler.cpp)。
参数:doc_ids(必填)、columns(白名单列,缺省为 *)、limits(max_rows 默认 10 上限 100,max_bytes 默认 200000 上限 1000000)。
安全设计贯穿始终:列名与表名必须通过 is_valid_sql_identifier() 校验,主键值经过方言对应的转义函数,标识符按方言加反引号/双引号包裹,最终查询附带 LIMIT 2 以约束返回规模。
rag.admin.stats:运营统计
无参数。按 rag_sources 聚合各来源的文档数与分块数:
SELECT s.source_id, s.name,
COUNT(d.doc_id) as docs,
COUNT(c.chunk_id) as chunks
FROM rag_sources s
LEFT JOIN rag_documents d ON d.source_id = s.source_id
LEFT JOIN rag_chunks c ON c.source_id = s.source_id
GROUP BY s.source_id, s.name;
过滤体系:统一的 build_sql_filters
三个搜索工具共享 build_sql_filters()(RAG_Tool_Handler.cpp),支持的过滤维度:
- 来源过滤:
source_ids(整数数组)、source_names(字符串数组,经危险字符校验后子查询匹配) - 文档过滤:
doc_ids(字符串数组) - 元数据过滤(面向 StackExchange 风格
metadata_json):post_type_ids($.PostTypeId)、tags_any/tags_all($.Tags的 LIKE 匹配,ESCAPE '\')、created_after/created_before($.CreationDate范围) - 分数阈值:
min_score,在结果构建阶段对归一化后的分数进行过滤
所有字符串型过滤值都经过 '、\、; 字符检查,拒绝含危险字符的输入,从源头规避 SQL 注入。
安全性、性能与统计观测
安全特性(源码逐一印证):
- 查询长度校验:
validate_query_length()对照query_max_bytes; - 参数边界钳制:
validate_k()(1–k_max,非法回退 10)、validate_candidates()(1–candidates_max,非法回退 50); - FTS 注入防护:
escape_fts_query()单引号翻倍 + 危险关键字拦截; - 参数化查询能力:
execute_parameterized_query()基于sqlite3_prepare_v2+sqlite3_bind_text/bind_int实现(RAG_Tool_Handler.cpp); - 回捞操作的白名单与行/字节双重限制。
性能特性:全部搜索均使用 LIMIT 收敛返回规模;向量检索采用"子查询先取 top-k 再联表过滤"的结构,避免对全表做向量匹配;FTS 依赖 FTS5 索引与 BM25 排序;所有查询均为只读语句,配合 SQLite3DB 连接管理。
统计观测:track_tool_invocation() 在每次工具调用后记录 count、first_seen、last_seen、sum_time、min_time、max_time,按 endpoint → tool_name → schema_name 三级映射组织(RAG_Tool_Handler.cpp),并可通过 get_tool_usage_stats_resultset() 导出为 SQLite 结果集(含 reset 选项),供管理接口消费。
实战验证:测试脚本与端到端调用
用测试脚本验证功能
仓库提供了两套验证途径:
# 1. 通过 MCP 端点测试 RAG 功能(连通性、工具发现、工具描述、admin.stats)
./scripts/mcp/test_rag.sh
# 2. 单测 RAG 数据库 schema 创建
cd test/rag
make test_rag_schema
./test_rag_schema
scripts/mcp/test_rag.sh 依次执行四项检查:/mcp/rag 端点可达性、tools/list 工具发现、tools/describe(校验 rag.admin.stats 描述)、tools/call 调用统计工具(校验响应含 sources 字段),并输出红绿统计汇总。
用 curl 直接调用 MCP 工具
前提:ProxySQL 运行中且 GenAI/RAG 已启用,MCP 服务监听 6071 端口(HTTPS)。完整调用示例见 doc/rag-examples.md,以下为精选命令:
# 工具发现
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":"1"}' \
https://127.0.0.1:6071/mcp/rag
# 关键词检索
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"rag.search_fts","arguments":{"query":"mysql performance","k":5}},"id":"1"}' \
https://127.0.0.1:6071/mcp/rag
# 语义检索
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"rag.search_vector","arguments":{"query_text":"database optimization techniques","k":5}},"id":"1"}' \
https://127.0.0.1:6071/mcp/rag
# 混合检索(fuse 模式)
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"rag.search_hybrid","arguments":{"query":"sql query optimization","mode":"fuse","k":5}},"id":"1"}' \
https://127.0.0.1:6071/mcp/rag
# 回取分块内容
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"rag.get_chunks","arguments":{"chunk_ids":["chunk1","chunk2"]}},"id":"1"}' \
https://127.0.0.1:6071/mcp/rag
# 运营统计
curl -k -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"rag.admin.stats"},"id":"1"}' \
https://127.0.0.1:6071/mcp/rag
每个响应均附带 stats 计时信息(ms、k_requested、k_returned),便于评估检索延迟。
推荐运行参数与调优方向
基于文档与实现,实战部署建议:
- 超时:单次操作
genai_rag_timeout_ms设置在 250–2000ms 区间,平衡召回质量与响应延迟; - 结果规模:
k_max=50、candidates_max=500的默认值足以覆盖多数场景,超大规模文档库可上调候选上限,但需同步关注response_max_bytes; - 源连接:
fetch_from_source每次调用会新建后端连接,高频回捞场景建议引入连接池化; - 增量摄入:
rag_sync_state表已就位,为后续基于updated_at的增量同步与poll模式轮询预留了扩展空间(可参照 AI_Features_Manager.cpp 的 sync state 定义)。
小结
ProxySQL RAG 子系统以"SQLite 向量库 + MCP 工具面"的组合,为数据库代理场景注入了完整的检索增强能力:FTS5 与 sqlite3-vec 双索引支撑关键词/语义/混合三类检索,fetch_from_source 打通与 MySQL/PostgreSQL 源库的权威数据回捞,rag.admin.stats 与工具级调用统计提供可观测性,而贯穿全部工具的参数校验、转义与限额机制构成了纵深防御。结合 doc/rag-documentation.md、doc/rag-examples.md 以及 scripts/mcp/test_rag.sh 与 test/rag/test_rag_schema.cpp,你可以快速完成从启用、验证到二次集成的全流程。