04 | 召回前置准备:搭好召回所需的四个数据库

04 | 召回前置准备:搭好召回所需的四个数据库

项目地址:github.com/frontzhm/n2...

每一步对应的完整代码都在仓库里,跟着文档卡住了就去翻源码。

这是一篇系列文,请按照顺序阅读。


终态回顾:召回三步分别需要什么

回到流程图,关键词抽取完之后紧接着三路并行召回:

markdown 复制代码
抽取关键词 →  ┌─ recall_column  --- 用关键词在字段向量库里搜→ 找到相关字段
            ├─ recall_metric  --- 用关键词在指标向量库里搜→ 找到相关指标
            └─ recall_value   --- 用关键词在 ES 里搜    → 找到相关枚举值

倒推一下,要跑通这三步,必须提前准备好:

召回步骤 需要的数据库 存什么 怎么搜
recall_column Qdrant(向量库) 字段的中文描述向量 关键词 → 向量 → 余弦相似度 TOP20
recall_metric Qdrant(向量库) 指标的中文描述向量 同上
recall_value Elasticsearch 维度表的枚举值文本 关键词 → 模糊匹配
(所有召回的数据来源) MySQL 元数据库 表结构、字段定义、指标定义 SQL 精确查询

再倒推一层:向量库里的字段/指标向量,以及 ES 里的枚举值,又是从哪来的?

→ 都来自 MySQL 元数据库。元数据库是"数据的源头",存的是"数据库里有哪些表、每个表有哪些字段、每个字段叫什么、有没有别名,还有哪些预定义的指标,以及指标和字段的关系"。向量库和 ES 里的数据,全部从元数据库导入。

所以这篇文章要做的,就是按依赖顺序把这三个库建好

markdown 复制代码
① MySQL 元数据库(源头)
    ↓ 导入数据
② Qdrant 向量库(存字段向量 + 指标向量)
③ Elasticsearch(存维度枚举值)

另外还有 ④ MySQL 数据仓库(dw)------这是最终执行 SQL 的目标库,放业务数据。本文先搭它的空壳表结构,写入一些测试数据,后续执行 SQL 时用。

这四个数据库全用 Docker 启动,一行 docker-compose up -d 搞定。


全景架构图

复制代码
┌─────────────────────────────────────────────────┐
│                    Docker                        │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │  MySQL   │  │  Qdrant  │  │  Elastic │       │
│  │  :3306   │  │  :6333   │  │  :9200   │       │
│  │          │  │          │  │          │       │
│  │ meta:  │  │ 字段向量  │  │ 枚举值   │       │
│  │  表/字段 │  │ 指标向量  │  │ 文本索引 │       │
│  │  指标定义│  │          │  │          │       │
│  │          │  │          │  │          │       │
│  │ dw:    │  │          │  │          │       │
│  │  业务数据│  │          │  │          │       │
│  └──────────┘  └──────────┘  └──────────┘       │
│                                                  │
│  ┌──────────────────────┐                        │
│  │  BGE Embedding       │  ← 中文嵌入模型        │
│  │  :8081               │    文本 → 向量         │
│  └──────────────────────┘                        │
└─────────────────────────────────────────────────┘

第一步:拷贝 Docker 文件 + 配置环境变量

Docker 相关文件全部放在 docker/ 目录下。完整文件请从仓库拷贝:

ruby 复制代码
https://github.com/frontzhm/n2sql-agent/tree/main/docker

目录结构一览:

python 复制代码
docker/
├── docker-compose.yml         # 一键启动四个服务
├── README.md                  # 启动说明
├── mysql/
│   ├── meta.sql               # meta 库 DDL(元数据库表结构)
│   └── dw.sql                 # dw 库 DDL + 测试数据
├── elasticsearch/
│   ├── Dockerfile             # 带 IK 中文分词插件的 ES 镜像
│   └── plugins/
│       └── elasticsearch-analysis-ik-*.zip
└── embedding/                 # BGE 模型数据卷

四个服务一览:

服务 端口 用途
MySQL 8.0 3306 元数据库 meta + 数据仓库 dw
Qdrant v1.16 6333 字段向量库 + 指标向量库
Elasticsearch 8.19(含 IK 分词) 9200 维度枚举值全文检索
Kibana 5601 ES 可视化管理(调试用)
BGE Embedding(text-embeddings-inference) 8081 中文文本 → 1024 维向量

配置环境变量

MySQL 的密码通过 .env 文件管理(不硬编码在 docker-compose.yml 里):

bash 复制代码
# 项目根目录下,复制模板文件
cp .env.example .env

# 编辑 .env,填上你的密码(本地开发用简单密码即可)
# MYSQL_ROOT_PASSWORD=Yan.123
# MYSQL_PASSWORD=Yan.123

.env 会被 .gitignore 排除,不会提交到仓库,密码安全有保障。

下载 BGE 中文嵌入模型

embedding 服务需要 BAAI/bge-large-zh-v1.5 模型(约 1.3GB)。用 uv 一行命令下载:

bash 复制代码
# 通过 huggingface_hub 下载模型到 docker/embedding/ 下
uv run --with huggingface_hub huggingface-cli download \
  BAAI/bge-large-zh-v1.5 \
  --local-dir docker/embedding/bge-large-zh-v1.5

uv run --with huggingface_hub 不需要提前安装依赖,uv 会自动创建临时环境执行命令,用完即走。

BGE(BAAI General Embedding)是智源研究院开源的中文嵌入模型,bge-large-zh-v1.5 把中文文本编码为 1024 维向量,是国内中文语义搜索的标配。


第二步:设计 MySQL 元数据库(meta)

要存什么?

元数据库的本质是用结构化数据描述"数据库里有什么"。需要三张核心表:

内容 谁用
table_info 每张表的名称、角色(dim 维度 / fact 事实)、描述 合并节点按表分组、过滤表
column_info 每个字段的列名、类型、角色(PK/FK/维度/度量)、描述、中文别名 召回字段、生成 SQL
metric_info 每个指标的名称、描述、别名、依赖哪些字段 召回指标、生成 SQL

加上 column_metric 关联表(一个指标可能关联多个字段)。

从 meta_config.yaml 到建表 SQL

复制 conf的两个文件。

/meta_config.yaml,这是元数据的配置文件------一篇 YAML 描述了 5 张表和 2 个指标。代码会读取它,写入 MySQL。

后续会读取它,构建元数据库。

另一个app_config.yaml,也后续用到,先放这吧。


第三步:设计 Qdrant 向量数据库

要存什么?

向量库需要存两个 Collection(集合),分别用于字段召回和指标召回。

Collection 每条记录代表 向量内容 元信息(payload)
columns 一个数据库字段 字段中文描述(description + alias 拼接)的 1024 维向量 字段名、表名、类型、角色、别名
metrics 一个业务指标 指标中文描述(description + alias 拼接)的 1024 维向量 指标名、描述、别名、依赖字段列表

向量是怎么生成的?

yaml 复制代码
字段的元数据(来自 meta 库):
   name: order_amount
   description: 订单金额
   alias: [销售额, 订单金额, 收入]
   ↓ 拼接成文本
   "订单金额 销售额 订单金额 收入"
   ↓ 调用 BGE Embedding 服务 (localhost:8081)
   → [0.023, -0.451, 0.789, ..., 0.112]  ← 1024 个浮点数
   ↓ 存入 Qdrant

同样地,用户输入的关键词(如 "销售额")也会走同样的流程变成向量,然后在 Qdrant 里找余弦相似度最高的 TOP 20 条记录。

Qdrant 是不需要预先建表的,Collection 在程序第一次导入数据时自动创建。


第四步:设计 Elasticsearch 值索引

要存什么?

把维度表中 sync: true 的字段的所有不同取值同步到 ES。

meta_config.yaml 为例:

维度表 字段 sync ES 中存什么
dim_region province true 省份的所有值(北京、上海...)
dim_region region_name true 大区的所有值(华东、华南...)
dim_customer gender true 性别的所有值(男、女)
dim_product category true 品类的所有值(服装、电子...)
dim_product brand true 品牌的所有值(苹果、华为...)
dim_region region_id false 不同步(主键没有召回价值)

ES 索引结构设计

json 复制代码
// 索引名: data_agent(对应 app_config.yaml 里 es.index_name)
// 每条记录代表一个维度值
{
  "id": "dim_region_province_北京",       // 唯一ID: {表名}_{字段名}_{值}
  "table_name": "dim_region",             // 所属表
  "column_name": "province",              // 所属字段
  "value": "北京",                        // 枚举值
  "column_description": "订单所属的省份名称" // 字段描述(辅助理解)
}

搜索行为

用户输入 "上海":

css 复制代码
ES 搜索: value 字段模糊匹配 "上海"
  ↓
返回: [
  { "table_name": "dim_region", "column_name": "province", "value": "上海" },
  { "table_name": "dim_region", "column_name": "region_name", "value": "华东" }
]

召回的值会被挂到对应字段的 examples 列表中,这样 LLM 在生成 SQL 的 WHERE 条件时,就能引用真实存在的值,而非自己编造。


第五步:设计 MySQL 数据仓库(dw)

最终 SQL 在这里执行。

docker/mysql/dw.sql --- 建表 + 灌数据一条龙(4 张维度表 + 1 张事实表,含 95 条订单记录)

mySQL启动的时候,就已经有测试数据了。


第六步:一键启动所有服务

安装并打开 Docker 桌面软件。

shell 复制代码
cd docker
docker compose up -d

首次启动会拉取镜像(MySQL ~500MB、Qdrant ~200MB、ES ~1GB),需要几分钟。后续启动就很快了。

检查各服务是否正常:

shell 复制代码
# MySQL(-p 后面跟 .env 里设的密码)
docker exec mysql mysql -uyan -pYan.123 -e "SHOW DATABASES;"
# 应该看到 meta 和 dw

# Qdrant
curl http://localhost:6333/health
# 返回 {"title":"qdrant - vector search engine","version":"..."}

# Elasticsearch
curl http://localhost:9200
# 返回集群信息

# Kibana(ES 可视化管理界面)
open http://localhost:5601

# BGE Embedding
curl http://localhost:8081/embed \
  -H "Content-Type: application/json" \
  -d '{"inputs":"测试"}'
# 返回一个 1024 维的向量数组

总结:四个数据库的分工

bash 复制代码
用户说:"上个月北京男女销售额对比"

① MySQL meta(元数据库)
   → 知道有哪些表、哪些字段、哪些指标
   → 是一切数据的"说明书"

② Qdrant(向量库)
   → 把字段和指标的描述变成向量存起来
   → 用户的"销售额" ➔ 向量 ➔ 命中 order_amount 字段

③ Elasticsearch
   → 把维度表里 sync=true 的取值全同步过来
   → 用户的"北京" ➔ 模糊匹配 ➔ 命中 dim_region.province

④ MySQL dw(数据仓库)
   → 存放实际业务数据
   → 生成的 SQL 在这里执行,返回查询结果

现在四个数据库都就绪了,接下来的文章就可以正式开始写根据配置同步meta数据库和召回节点的代码------从元数据库读数据、灌入 Qdrant 和 ES、再用关键词去查。


当前目录结构

bash 复制代码
n2sql-agent/
│   ├── conf/
│   │   ├── meta_config.yaml      # 元数据定义:表、字段、指标
│   │   └── app_config.yaml       # 连接配置:各服务的 host/port
│   └── ...
├── docker/
│   ├── docker-compose.yml        # 一键启动四个服务
│   ├── mysql/
│   │    ├── meta.sql          # meta 库 DDL(元数据库表结构)
│   │    └── dw.sql            # dw 库 DDL + 测试数据
│   ├── qdrant/
│   ├── elasticsearch/
│   └── embedding/
├── app/
├── main.py
├── .env.example
└── ...

科普 Docker

一句话理解

Docker 是一个"打包工具"------把程序连同它依赖的所有环境(操作系统库、配置文件、启动脚本)打成一个镜像,在任何机器上都能一键启动,环境完全一致。

打个比方:你写了一份菜谱(Dockerfile / docker-compose.yml),Docker 按菜谱做出一道菜(container),不管在谁的厨房(Windows / Mac / Linux),做出来的味道一模一样。

核心概念

概念 说明 本项目对应
镜像 Image 程序的"安装包"快照,包含代码 + 运行环境 mysql:8.0qdrant/qdrant:v1.16
容器 Container 镜像运行起来的实例,一个隔离的小虚拟机 docker compose up -d 后跑起来的 mysql 进程
Dockerfile 自定义镜像的配方文件 elasticsearch/Dockerfile:在官方 ES 基础上装了 IK 分词插件
docker-compose.yml 编排多个容器一起启动 一键启动 MySQL + Qdrant + ES + Embedding
Volume 数据卷 容器删了数据不丢的持久化存储 mysql_dataes_data 等,数据库文件存在宿主机上

本项目用到了哪些

  • 四个服务全部用 Docker 跑,不用你手动装 MySQL、配置 ES
  • 唯一需要手动下载的只有 BGE 模型文件(太大,不适合打进镜像)
  • 开发环境随便折腾,删了 docker compose down -v 就恢复出厂设置

科普 MySQL

一句话理解

MySQL 是全世界最流行的关系型数据库。你把数据按照"表(行 × 列)"的方式存进去,然后用 SQL 语句去查。

核心概念

概念 说明 本项目对应
数据库 Database 一个独立的数据隔离单元,不同业务放不同库 meta(元数据)、dw(数据仓库)
表 Table 按行列组织的数据集合,每行一条记录 dim_customer(客户维度表)、fact_order(订单事实表)
主键 Primary Key 每行数据的唯一身份证号 customer_idorder_id
外键 Foreign Key 关联到另一张表的指针 fact_order.customer_iddim_customer.customer_id
DDL "建表语句",定义表长什么样子 meta.sqldw.sql
DML "增删改查",操作数据 INSERT INTO dim_customer VALUES (...)

本项目用 MySQL 做什么

sql 复制代码
meta 库(元数据库)
  └─ 存的是"说明书":有哪些表、每个表有哪些字段、字段的中文别名、预定义的指标
     → 代码从这里读取元数据,导入 Qdrant 和 ES

dw 库(数据仓库)
  └─ 存的是"实际数据":客户、商品、订单......
     → 最终生成的 SQL 在这里执行,返回查询结果给用户

科普 Elasticsearch

一句话理解

Elasticsearch(简称 ES)是一个搜索引擎。你丢进去一堆文本,它帮你建好索引,然后你能用关键词模糊搜索、拼音搜索、分词搜索,秒级返回结果。

如果把 MySQL 比作 Excel(精确查找),ES 就像 Google(模糊搜索)。

核心概念

概念 说明 本项目对应
索引 Index 相当于 MySQL 的"表",存一类文档 data_agent:存所有维度枚举值
文档 Document 索引中的一条记录,JSON 格式 {"table_name": "dim_region", "column_name": "province", "value": "上海"}
分词器 Analyzer 把文本切成词语的工具,中文分词的灵魂 IK 分词器 :把"上海市浦东新区"切成 上海 / 浦东 / 新区
倒排索引 ES 快的秘密------从词倒推它在哪些文档里 搜"上海" → 立刻知道第 3、7、15 号文档包含这个词

本项目用 ES 做什么

用户说"上海",ES 在维度值索引里搜到 dim_region.province = '上海'。这个值被挂到字段的 examples 列表里,LLM 写 SQL 的 WHERE 条件时就能用真实的数据库值,不会瞎编。

为什么不用 MySQL 做这件事? MySQL 的 LIKE '%上海%' 是全表扫描,百万行数据能卡半分钟;ES 的倒排索引是 O(1) 查词定位,百万行也毫秒返回。


科普 Embedding

一句话理解

Embedding(嵌入 / 向量化)是把文字变成一串数字(向量)的技术。文字越相似,它们对应的向量在数学空间里距离越近。

arduino 复制代码
"销售额"   → [0.023, -0.451, 0.789, ..., 0.112]
"订单金额"  → [0.019, -0.438, 0.801, ..., 0.105]
"上海"     → [0.891, 0.234, -0.156, ..., -0.678]

"销售额"和"订单金额"的向量距离很近(因为它们语义相近),但和"上海"距离很远。

核心概念

概念 说明 本项目对应
向量 Vector 一串浮点数,代表文字在语义空间里的坐标 BGE 模型输出 1024 维向量
嵌入模型 Embedding Model 把文字变成向量的 AI 模型 BAAI/bge-large-zh-v1.5
语义相似度 两段文字的向量越近,语义越像 "销售额" ↔ "订单金额" 余弦相似度 0.98
跨语言匹配 中文查询 → 命中英文字段名 "订单金额" → 命中 order_amount

本项目用 Embedding 做什么

这是召回字段和指标的核心:

ruby 复制代码
① 初始化时:
   把 meta 库里每个字段的中文描述(description + alias)→ BGE → 向量 → 存入 Qdrant

② 用户查询时:
   用户输入"销售额" → BGE → 向量 → 在 Qdrant 里找余弦最相似的 TOP 20 个字段
   → 其中 `order_amount`(描述:"订单金额")排第一

为什么不用 ES? ES 做的是"字面匹配",搜"销售额"找不到 order_amount(因为没有一个字相同)。Embedding 做的是"语义匹配"------知道"销售额"说的就是 order_amount

相关推荐
A洛1 小时前
Codex 实战:一句话完成 Temu 商品图采集与印花抠图自动化
运维·人工智能·自动化·codex
JaneConan1 小时前
鸿蒙 韶非 UI 系列:能力调用 startAbilityForResult,跳能力拿回参,鸿蒙能力路由入门
后端·harmonyos
晴空了无痕1 小时前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes
用户8356290780511 小时前
如何使用 Python 在 Excel 中添加、编辑和删除超链接
后端·python
花椒技术1 小时前
原本要 2 天的服务端冒烟前置审查,为什么 3 分半就能出报告?|QA 质量交付实践(三)
后端·ai编程·测试
甲维斯1 小时前
Kimi K3 修Bug,越修越多!
人工智能
Litluecat2 小时前
2026年7月20日科技热点新闻
人工智能·科技·新闻·每日·速览
达达尼昂2 小时前
AI 编程的工程化实践:Flutter AI Harness 的设计与落地
人工智能·后端·全栈
廋到被风吹走2 小时前
【AI】从“卖能力“到“卖信任“,合规与安全成为新战场
人工智能·安全