ProxySQL RAG 子系统文件结构与实现指南:基于 MCP 的检索增强生成工具全景解析
ProxySQL RAG 子系统文件结构与实现指南:基于 MCP 的检索增强生成工具全景解析
导读:本文以 doc/RAG/RAG_FILE_SUMMARY.md 为骨架,系统梳理 ProxySQL 中 RAG(Retrieval-Augmented Generation,检索增强生成)子系统的完整文件布局、核心实现、MCP 工具清单、数据库 Schema、配置变量与测试体系。读完本文,你将掌握
rag.*系列 MCP 工具的调用方式、FTS5/向量/混合三种检索模式的原理与参数,以及如何基于现有源码为 ProxySQL 扩展 RAG 能力。
一、RAG 子系统概览:一张文件布局全景图
RAG 子系统是 ProxySQL GenAI 插件(位于 plugins/genai/)内部一套完整的检索增强生成能力,其职责是:将外部数据源(MySQL/PostgreSQL)中的文档经切块(chunking)与向量化(embedding)后落入 SQLite 向量数据库,再通过 MCP 协议以 /mcp/rag 端点向外提供关键词检索(FTS5)、语义检索(向量)与混合检索能力。
从 RAG_FILE_SUMMARY.md 的记录看,整个实现被划分为新增文件与修改文件两大块。需要特别说明的是:摘要文档中标注的 include/RAG_Tool_Handler.h 与 lib/RAG_Tool_Handler.cpp,在当前仓库中实际位于 plugins/genai/include/RAG_Tool_Handler.h 与 plugins/genai/src/tool_handlers/RAG_Tool_Handler.cpp,即作为 GenAI 插件的一部分编译进 ProxySQL。
1.1 新增文件(核心实现)
| 文件路径 | 职责 |
|---|---|
| plugins/genai/include/RAG_Tool_Handler.h | RAG_Tool_Handler 类头文件,继承 MCP_Tool_Handler,声明 7 个 MCP 工具、配置参数、统计结构与辅助函数 |
| plugins/genai/src/tool_handlers/RAG_Tool_Handler.cpp | 全部 RAG 功能实现:检索、过滤、评分、安全校验、统计与响应格式化 |
| test/rag/test_rag_schema.cpp | Schema 验证测试:检查 6 张表 + 1 个视图是否存在于向量数据库 |
test/build_rag_test.sh |
RAG 测试的简易编译脚本 |
| test/Makefile | 新增 RAG 测试编译目标 |
| doc/rag-documentation.md、doc/rag-examples.md | 完整文档与"Golden"示例 |
| scripts/mcp/test_rag.sh | 通过 MCP 端点验证 RAG 功能的测试脚本 |
1.2 修改文件(集成点)
| 文件路径 | 改动内容 |
|---|---|
plugins/genai/include/MCP_Thread.h / src/MCP_Thread.cpp |
新增 RAG tool handler 成员,负责初始化与清理 |
plugins/genai/src/ProxySQL_MCP_Server.cpp |
注册 /mcp/rag 端点 |
| plugins/genai/src/AI_Features_Manager.cpp | 新增 RAG 数据库 Schema 创建逻辑 |
plugins/genai/include/GenAI_Thread.h / src/GenAI_Thread.cpp |
新增 6 个 RAG 配置变量及其初始化 |
| scripts/mcp/README.md | 架构图与工具列表中加入 RAG |
这一布局的核心设计思想是:RAG 逻辑完全内聚于 RAG_Tool_Handler 一个类中,外部只通过 MCP 工具契约与 GenAI_Thread 配置变量两个触点交互,因此无论是挂载新工具还是调整检索参数,都无需改动 MCP 服务器骨架。
二、核心实现:RAG_Tool_Handler 类解剖
RAG_Tool_Handler 是整个 RAG 子系统的执行中枢。从 RAG_Tool_Handler.h 的头文件可以看到其核心成员与设计:
class RAG_Tool_Handler : public MCP_Tool_Handler {
private:
SQLite3DB* vector_db; // 向量数据库连接(含 FTS5 与 vec0 扩展)
AI_Features_Manager* ai_manager; // 共享资源管理器,提供 vector_db
Discovery_Schema* catalog; // 用于操作日志的发现目录
// 运行时配置快照(从 GenAI_Thread 读取)
int k_max; // 最大返回结果数(默认 50)
int candidates_max; // 混合检索最大候选数(默认 500)
int query_max_bytes; // 查询最大字节数(默认 8192)
int response_max_bytes; // 响应最大字节数(默认 5000000)
int timeout_ms; // 操作超时毫秒数(默认 2000)
};
2.1 生命周期:init / close / refresh_runtime_dependencies
三个生命周期方法的职责边界非常清晰(RAG_Tool_Handler.cpp):
init():调用refresh_runtime_dependencies()拉取最新配置,校验vector_db是否可用,若配置了catalog_path则初始化Discovery_Schema用于日志落盘;refresh_runtime_dependencies():这是"运行时热更新"的关键——它会从全局单例GloGATH(GenAI_Threads_Handler)的variables结构读取genai_rag_*系列变量,覆盖本类的默认值。这意味着执行LOAD genai VARIABLES TO RUNTIME后,无需重启即可让检索限额生效;close():释放 catalog,析构函数自动调用。
2.2 JSON 参数安全提取:六个 get_json_* 辅助函数
由于 MCP 工具入参是 JSON,所有参数都经由一组类型安全的提取器处理(源码 L463-L601):
get_json_string(key, default):字符串提取,非字符串类型会自动dump()转字符串;get_json_int(key, default):整数提取,兼容字符串形式的数字(如"5"),转换失败回退默认值;get_json_bool(key, default):布尔提取,兼容"true"/"1"字符串与非零整数;get_json_string_array/get_json_int_array:数组提取,逐元素过滤非法类型。
这套工具奠定了"外部输入不可信"的编程基线——任何字段缺失、类型错误都不会抛异常,而是静默回退到默认值。
三、七个 MCP 工具详解(输入 Schema 全收录)
get_tool_list()(源码 L1259-L1595)完整定义了工具的 JSON Schema。以下按"检索类 → 获取类 → 管理类"分组逐项解析。
3.1 检索类工具
rag.search_fts — 关键词检索(FTS5)
- 必填:
query(string) - 可选:
k(默认 10,上限 50)、offset(分页偏移,默认 0) filters:源/文档/标签/日期过滤(见 3.4)return:include_title(默认 true)、include_metadata(默认 true)、include_snippets(默认 false)
FTS 查询在执行前会经过 escape_fts_query() 处理:将单引号 ' 加倍为 ''(SQLite FTS5 标准转义法),既防止 FTS 注入,又允许查询文本中的合法单引号(源码 L665-L678)。
rag.search_vector — 语义检索(sqlite3-vec)
- 必填:
query_text(string) - 可选:
k(默认 10,上限 50)、filters、return embedding:可指定model(允许由模型名动态生成嵌入)query_embedding:直接传入预计算向量,包含dim(维度)与values_b64(Base64 编码的 float32 数组)——这条路径允许调用方离线算好查询向量再提交,省去在线嵌入开销
rag.search_hybrid — 混合检索(双模式)
- 必填:
query(string) - 可选:
k、filters、mode(fuse或fts_then_vec,默认实现按文档约定为 fuse 优先)
Mode A(fuse)——并行 + 互逆排序融合(RRF):FTS 与向量检索并行执行,各自取 fts_k/vec_k(默认 50)条结果,再按 Reciprocal Rank Fusion 公式融合:
RRF(rank) = weight / (k0 + rank)
其中 rrf_k0 平滑参数默认 60,w_fts/w_vec 权重默认 1.0。该公式的数学实现见 compute_rrf_score(),完全对应当前实现:"weight / (k0 + rank)"。
Mode B(fts_then_vec)——候选生成 + 重排:先用 FTS 生成 candidates_k(默认 200)个候选,再用向量检索对其中 rerank_k(默认 50)条做精排,vec_metric 指定相似度度量(默认 cosine)。适合先召回再精排的经典 RAG 流水线。
normalize_score()(源码 L985-L989)为后续分数归一化预留了扩展点,当前按原样返回分数。
3.2 获取类工具
rag.get_chunks — 按 chunk_id 获取切片
- 必填:
chunk_ids(string 数组) return:include_title、include_doc_metadata、include_chunk_metadata(均默认 true)
rag.get_docs — 按 doc_id 获取文档
- 必填:
doc_ids(string 数组) return:include_body、include_metadata(均默认 true)
rag.fetch_from_source — 回源重取权威数据(最具 ProxySQL 特色的工具)
- 必填:
doc_ids - 可选:
columns(指定要回取的列,属于列白名单机制) limits:max_rows(默认 10,上限 100)、max_bytes(默认 200000,上限 1000000)
该工具的行为是:从 rag_documents 表读出该文档对应的源连接信息(backend_type/host/port/user/password/db/table/pk_column/where_sql/pk_json),然后直连源数据库执行带主键谓词的回查(源码 L1008-L1233):
- MySQL 后端:
dial_mysql()建立连接,经mysql_real_escape_string转义字面量、反引号包裹标识符后执行SELECT ... WHERE <pk> = <literal> AND (<where_sql>) LIMIT 2; - PostgreSQL 后端:走
dial_pgsql(),使用PQescapeLiteral转义、双引号包裹标识符; - 结果超过 1 行会报错(主键应唯一),0 行则提示"未找到源行"。
安全细节:所有标识符先过 is_valid_sql_identifier() 校验(首字符须为字母或下划线,其余须为字母/数字/下划线/$),主键列名与表名白名单化,杜绝注入。
3.3 管理类工具
rag.admin.stats — 运行统计
入参为空对象,返回该端点上每个 (tool, schema) 组合的调用统计。底层由 ToolUsageStats 结构支撑(头文件 L102-L128),字段包括:
count、first_seen、last_seen、sum_time、min_time、max_time
并通过 track_tool_invocation()(源码 L61-L71)在 pthread_mutex 保护下线程安全地累加;get_tool_usage_stats_resultset(reset) 可导出为 SQLite3_result 结果集(可选 reset 清零),供 SQLite 管理接口查询。
3.4 通用过滤器:build_sql_filters 支持的完整过滤面
build_sql_filters()(源码 L799-L954)被三类检索工具共用,按文档声明支持 9 种过滤条件:
| 过滤器 | 类型 | 底层 SQL 行为 |
|---|---|---|
source_ids |
int[] | c.source_id IN (...) |
source_names |
string[] | 先查 rag_sources.name IN (...) 再映射到 source_id |
doc_ids |
string[] | c.doc_id IN (...) |
post_type_ids |
int[] | json_extract(d.metadata_json,'$.PostTypeId') IN (...) |
tags_any |
string[] | 元数据 Tags 字段 LIKE '%<tag>%',OR 组合 |
tags_all |
string[] | 同上,AND 组合 |
created_after |
date-time | json_extract(d.metadata_json,'$.CreationDate') >= '...' |
created_before |
date-time | <= 方向 |
min_score |
number | 结果分数阈值(响应侧过滤) |
注入防护贯穿始终:字符串类过滤值在拼接前逐一检查是否含 '、\、;,命中即整体返回 false 拒绝执行。
四、数据库 Schema:七张表/视图支撑两级索引
RAG 的持久化层由 AI_Features_Manager.cpp 在向量数据库(默认路径 /var/lib/proxysql/ai_features.db)中创建,test/rag/test_rag_schema.cpp 将其固化为契约:6 张表 + 1 个视图。
// 表清单(test_rag_schema.cpp 中 RAG_TABLES)
rag_sources // 数据源注册(source_id, name, enabled, backend_type/host/port/user/pass/db, table_name, pk_column, where_sql)
rag_documents // 规范文档(doc_id 主键, source_id 外键, title, body, metadata_json, pk_json, deleted 等)
rag_chunks // 切块内容(chunk_id 主键, doc_id/source_id 外键, title, body, chunk_metadata)
rag_fts_chunks // FTS5 虚拟表(chunk_id UNINDEXED, title, body)→ 关键词索引
rag_vec_chunks // vec0 虚拟表(embedding float[dim], chunk_id, doc_id)→ 向量索引
rag_sync_state // 同步状态(source_id 主键, mode 默认 'poll', cursor_json 默认 '{}')
rag_chunk_view // 调试视图,关联切片与其文档/源信息
从源码可确认三组关键设计:
- 双索引并存:
rag_fts_chunks走 SQLite FTS5 虚拟表实现词法检索,rag_vec_chunks走vec0虚拟表(sqlite3-vec 扩展)承载float[dim]向量,二者通过chunk_id与rag_chunks关联——这正好对应摘要文档中"FTS and vector support"的表述; - 元数据即 JSON:
rag_documents.metadata_json直接存储 JSON(如 StackExchange 场景的PostTypeId、CreationDate、Tags),过滤时用json_extract()下探,避免了为每类元数据建列的僵化; - 回源能力闭环:
rag_sources中保存了后端连接串与主键列、rag_documents.pk_json保存主键值,二者结合构成rag.fetch_from_source的"回源凭据",这也是该子系统的独特价值——检索结果永远可追溯到权威数据。
五、运行时配置:六个 genai_rag_* 变量
配置变量声明于 GenAI_Thread.h,默认值与语义如下:
| 变量 | 默认值 | 含义 |
|---|---|---|
genai.rag_enabled |
false |
RAG 功能总开关 |
genai.rag_k_max |
50 |
单次检索最大返回条数(validate_k() 的上限) |
genai.rag_candidates_max |
500 |
混合检索候选数上限(validate_candidates() 的上限) |
genai.rag_query_max_bytes |
8192 |
查询文本最大字节数(validate_query_length() 校验) |
genai.rag_response_max_bytes |
5000000 |
响应体最大字节数 |
genai.rag_timeout_ms |
2000 |
单次检索操作超时(毫秒) |
参数校验逻辑(源码 L615-L651)体现"宽进严出":k 非法(≤0)回退默认 10,超上限则截断到 k_max;candidates 非法回退 50,超限截断到 candidates_max。
六、启用与调用:从 SQL 到 MCP 的完整路径
6.1 三步启用(源自 RAG_COMPLETION_SUMMARY.md)
-- 1. 打开 GenAI 模块
SET genai.enabled = true;
-- 2. 打开 RAG 特性
SET genai.rag_enabled = true;
-- 3. 加载配置到运行时(触发 refresh_runtime_dependencies 生效)
LOAD genai VARIABLES TO RUNTIME;
之后所有工具经 /mcp/rag 端点对外暴露,由 ProxySQL_MCP_Server.cpp 注册的路由分发到 RAG_Tool_Handler::execute_tool()。
6.2 调用示例(最小可用 JSON)
// 关键词检索
{ "tool": "rag.search_fts", "arguments": { "query": "proxy mysql", "k": 5 } }
// 语义检索(预计算向量)
{ "tool": "rag.search_vector",
"arguments": { "query_text": "query latency",
"query_embedding": { "dim": 768, "values_b64": "..." } } }
// 混合检索(fuse 模式,自定义权重)
{ "tool": "rag.search_hybrid",
"arguments": { "query": "connection pooling",
"mode": "fuse",
"fuse": { "fts_k": 50, "vec_k": 50, "rrf_k0": 60,
"w_fts": 1.0, "w_vec": 1.0 } } }
// 带过滤的检索
{ "tool": "rag.search_fts",
"arguments": { "query": "galera", "k": 10,
"filters": { "source_names": ["stackoverflow"],
"tags_any": ["mysql", "replication"],
"created_after": "2020-01-01T00:00:00Z" } } }
// 回源重取
{ "tool": "rag.fetch_from_source",
"arguments": { "doc_ids": ["doc_1001"],
"columns": ["Title", "Body"],
"limits": { "max_rows": 1, "max_bytes": 50000 } } }
6.3 验证路径
仓库提供三层验证手段:
- Schema 验证:test/rag/test_rag_schema.cpp 打开
/var/lib/proxysql/ai_features.db,逐表逐视图比对sqlite_master,全部存在才返回 0;编译入口在 test/Makefile 的 RAG 测试目标; - 端到端验证:scripts/mcp/test_rag.sh 直接经 MCP 端点调用检索工具断言返回;
- 人工验证:执行
LOAD genai VARIABLES TO RUNTIME后,通过rag.admin.stats观察(tool, schema)维度的调用计数与耗时分布,可快速定位热点工具与慢查询。
七、安全与性能设计要点(源码级佐证)
将 RAG_FILE_SUMMARY.md 声明的安全与性能特性落实到代码位置:
安全
- FTS 注入防护:
escape_fts_query()单引号加倍(L665-L678); - SQL 注入防护:标识符白名单
is_valid_sql_identifier()、字符串过滤值危险字符拦截、mysql_real_escape_string/PQescapeLiteral字面量转义; - 参数化查询:
execute_parameterized_query()走prepare_v2+sqlite3_bind_text/bind_int,供内部查询复用(L727-L784); - 限额与超时:k/候选数/查询字节/响应字节/毫秒超时五重限制,全部可在运行时调整。
性能
- 预编译语句:内部查询统一 prepare/bind,避免重复解析开销;
- 双索引策略:FTS5 负责词法召回、vec0 负责向量召回,各取所长;
- 连接管理:回源场景即时建立、用完即释放后端连接,且
LIMIT 2约束回查行数; - 统计开销可控:计数在互斥锁内仅做原子累加,导出时才序列化。
八、小结与扩展指引
RAG_FILE_SUMMARY.md 虽只是一份文件清单,但它完整勾勒了 ProxySQL RAG 子系统的工程边界。以本文为索引,你可以继续深挖:
- 工具调用示例与完整 JSON 响应格式:doc/rag-examples.md;
- 检索架构与数据模型设计:doc/RAG/architecture-data-model.md、doc/RAG/architecture-runtime-retrieval.md;
- 切块与嵌入实践:doc/RAG/chunking-and-embedding-guide.md;
- RAG 向 LLM 提供上下文的桥接设计:doc/LLM_Bridge/ARCHITECTURE.md。
若要在新场景中扩展(例如新增 rag.search_timeseries 工具),只需遵循既有模式:在 RAG_Tool_Handler::get_tool_list() 注册 Schema → 在 execute_tool() 增加分支 → 补充 test/rag/ 下的验证用例,即可无缝接入既有 MCP 框架与统计体系。