ProxySQL RAG 子系统文件结构与实现指南:基于 MCP 的检索增强生成工具全景解析

原创2026-10-06 10:26:331,936 阅读
文章标签:后端数据库负载均衡

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   // 调试视图,关联切片与其文档/源信息

从源码可确认三组关键设计:

  1. 双索引并存:rag_fts_chunks 走 SQLite FTS5 虚拟表实现词法检索,rag_vec_chunks 走 vec0 虚拟表(sqlite3-vec 扩展)承载 float[dim] 向量,二者通过 chunk_id 与 rag_chunks 关联——这正好对应摘要文档中"FTS and vector support"的表述;
  2. 元数据即 JSON:rag_documents.metadata_json 直接存储 JSON(如 StackExchange 场景的 PostTypeId、CreationDate、Tags),过滤时用 json_extract() 下探,避免了为每类元数据建列的僵化;
  3. 回源能力闭环: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 验证路径

仓库提供三层验证手段:

  1. Schema 验证:test/rag/test_rag_schema.cpp 打开 /var/lib/proxysql/ai_features.db,逐表逐视图比对 sqlite_master,全部存在才返回 0;编译入口在 test/Makefile 的 RAG 测试目标;
  2. 端到端验证:scripts/mcp/test_rag.sh 直接经 MCP 端点调用检索工具断言返回;
  3. 人工验证:执行 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 子系统的工程边界。以本文为索引,你可以继续深挖:

若要在新场景中扩展(例如新增 rag.search_timeseries 工具),只需遵循既有模式:在 RAG_Tool_Handler::get_tool_list() 注册 Schema → 在 execute_tool() 增加分支 → 补充 test/rag/ 下的验证用例,即可无缝接入既有 MCP 框架与统计体系。

登录后查看全文
proxysql