如何提高Qdrant对分块数据库语义YAML模型的检索准确性(基于数据库元数据的RAG)?
我正在构建一个文本转SQL / 数据对话系统,需要根据用户的自然语言问题检索相关的数据库元数据(表、列、度量、关系)。
为此,我正在将一个语义YAML模型(类似Looker LookML或 Cube.js架构/模式)解析成独立的块,生成嵌入向量,并将它们存储在Qdrant中以实现语义检索。
我们当前的方法
我们在不同的层级对YAML进行分块:
- 表级别(表的元数据)
- 列级别(维度、时间维度、事实)
- 度量级别(聚合、公式)
- 语义规则和关系
我们使用fastembed,采用BAAI/bge-small-en-v1.5模型对文本进行嵌入。
以下是我们分块与摄取代码的简化版本:
import yaml
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
from fastembed import TextEmbedding
embedding_model = TextEmbedding(model_name="BAAI/bge-small-en-v1.5")
def build_dynamic_content(metadata: dict) -> str:
"""Converts metadata key-values to a string for embedding."""
lines = []
for key, value in metadata.items():
clean_key = key.replace('_', ' ').title()
if isinstance(value, list):
lines.append(f"{clean_key}: {', '.join([str(v) for v in value])}")
elif isinstance(value, dict):
dict_str = ", ".join([f"{k}: {v}" for k, v in value.items()])
lines.append(f"{clean_key}: {dict_str}")
else:
lines.append(f"{clean_key}: {value}")
return "\n".join(lines)
def get_col_chunk(table_name: str, col: dict) -> dict:
metadata = {k: v for k, v in col.items() if v}
content = build_dynamic_content(metadata)
# e.g., Content becomes:
# "Name: user_id\nData Type: string\nDescription: Unique identifier for the user"
return {
"chunk_type": "column",
"table_name": table_name,
"content": content,
"metadata": metadata
}
# After generating chunks, we embed `chunk["content"]` and upsert to Qdrant...
问题
当用户提出类似“上个月我们新增了多少注册用户?”的问题时,从Qdrant检索到的分块往往不准确。
- 经常会错过正确的列分块(例如,可能检索到来自完全无关表的user_id或 created_at,或无法将signups与 users表关联起来)。
- 仅仅使用稠密向量检索(bge-small-en-v1.5)很难精确匹配关键词(比如用户说“signup date”时,找到名为signup_dt的列也很困难)。
我们怀疑导致问题的原因:
- 上下文丢失: 当我们对一个列分块进行嵌入时,嵌入的文本(内容)只包含列名和描述,并未提及它所属的表(表名只出现在载荷元数据中,且未被嵌入)。
- 短文本嵌入: 键值对字符串如 "Name: user_id\nData Type: string" 对通用嵌入模型来说语义上下文并不充足。
- 缺乏关键词匹配: 我们只做稠密语义检索,缺少词汇/关键词匹配(如BM25),对于精确的模式匹配至关重要。
我的问题
- 我们应如何重构分块文本(内容)以保留层次结构?是否应把表名和描述注入到每个列分块中(例如 "Table: users | Column: signup_dt | Description: ..."),还是有更好的方法来表示用于嵌入的数据库架构?
- 在结构化元数据 / 数据库架构上表现最好的嵌入模型有哪些?bge-small-en-v1.5是否太小或不适合此任务?
- 如何在Qdrant中为此用例实现混合检索(Dense + Sparse)?如何将稀疏向量(如BM25或 SPLADE)与我们的稠密向量并存,以确保对列名/表名等的精确关键词匹配?
- 按单个列进行分块是否走错路?是否应将整个表的架构(表+ 所有列)作为一个更大的分块来嵌入?
解决方案
对孤立的列分块进行的稠密检索是这里的主要问题。
这个分块过于薄弱:
Name: signup_dt
Data Type: timestamp
Description: ...
嵌入模型没有真正的上下文。它不知道该列属于哪张表、是否是业务日期、是否表示signup/registration、以及哪个度量在使用它。
我会把嵌入的文本改成类似这样的:
Object: column
Table: users
Table description: registered users of the application
Column: signup_dt
Column aliases: signup date, registration date, account creation date, joined date
Data type: timestamp
Business meaning: date when a user signed up
Used for: counting new signups, new users by month, registration trends
Related metrics: new_signups
所以,是的,要把表名、表描述、别名以及业务含义放到每个列分块中。仅把这些放在Qdrant的载荷中是不够的,因为载荷本身并不会被嵌入。
我也不会只存储列分块。应存储多种分块类型:
table chunk: table description + important columns
column chunk: column + table context + aliases
metric chunk: formula + related table/columns
relationship chunk: join keys and cardinality
example chunk: user question + expected SQL pattern
对于类似的问题:
How many new signups did we get last month?
你可能想要检索度量 new_signups、表 users、以及时间列 users.signup_dt。单个列分块通常不足以覆盖。
仅靠稠密嵌入也不适合模式检索。你需要混合检索。像 signup_dt、user_id、created_at、account_id 等的确切名称是词汇信号,而不仅仅是语义信号。
使用带命名向量的Qdrant:
dense vector: normal embedding
sparse vector: BM25/SPLADE-style lexical vector
然后同时查询两者并融合结果,例如使用RRF。稠密检索有助于将“registration”近似为“signup”;稀疏检索有助于对表/列/度量的精确名称。
同时手动添加别名。不要指望模型能推断出所有业务词汇:
name: signup_dt
aliases:
- signup date
- registration date
- account creation date
- new user date
used_for:
- count new signups
- monthly signups
- new users by period
bge-small-en-v1.5 可用,但单独使用可能太弱。更大的嵌入模型也许有帮助,但不会解决设计问题。更大的改进将来自更丰富的分块文本、混合检索和重新排序。