ProxySQL RAG 子系统完全实现指南:基于 MCP 的检索增强生成能力

原创2026-10-06 21:38:511,369 阅读
文章标签:后端数据库负载均衡

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,你可以快速完成从启用、验证到二次集成的全流程。

登录后查看全文
proxysql