如何提高Qdrant对分块数据库语义YAML模型的检索准确性(基于数据库元数据的RAG)?

编程语言 2026-07-07

我正在构建一个文本转SQL / 数据对话系统,需要根据用户的自然语言问题检索相关的数据库元数据(表、列、度量、关系)。

为此,我正在将一个语义YAML模型(类似Looker LookML或 Cube.js架构/模式)解析成独立的块,生成嵌入向量,并将它们存储在Qdrant中以实现语义检索。

我们当前的方法

我们在不同的层级对YAML进行分块:

  1. 表级别(表的元数据)
  2. 列级别(维度、时间维度、事实)
  3. 度量级别(聚合、公式)
  4. 语义规则和关系

我们使用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检索到的分块往往不准确。

  1. 经常会错过正确的列分块(例如,可能检索到来自完全无关表的user_id或 created_at,或无法将signups与 users表关联起来)。
  2. 仅仅使用稠密向量检索(bge-small-en-v1.5)很难精确匹配关键词(比如用户说“signup date”时,找到名为signup_dt的列也很困难)。

我们怀疑导致问题的原因:

  1. 上下文丢失: 当我们对一个列分块进行嵌入时,嵌入的文本(内容)只包含列名和描述,并未提及它所属的表(表名只出现在载荷元数据中,且未被嵌入)。
  2. 短文本嵌入: 键值对字符串如 "Name: user_id\nData Type: string" 对通用嵌入模型来说语义上下文并不充足。
  3. 缺乏关键词匹配: 我们只做稠密语义检索,缺少词汇/关键词匹配(如BM25),对于精确的模式匹配至关重要。

我的问题

  1. 我们应如何重构分块文本(内容)以保留层次结构?是否应把表名和描述注入到每个列分块中(例如 "Table: users | Column: signup_dt | Description: ..."),还是有更好的方法来表示用于嵌入的数据库架构?
  2. 在结构化元数据 / 数据库架构上表现最好的嵌入模型有哪些?bge-small-en-v1.5是否太小或不适合此任务?
  3. 如何在Qdrant中为此用例实现混合检索(Dense + Sparse)?如何将稀疏向量(如BM25或 SPLADE)与我们的稠密向量并存,以确保对列名/表名等的精确关键词匹配?
  4. 按单个列进行分块是否走错路?是否应将整个表的架构(表+ 所有列)作为一个更大的分块来嵌入?

解决方案

对孤立的列分块进行的稠密检索是这里的主要问题。

这个分块过于薄弱:

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_dtuser_idcreated_ataccount_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 可用,但单独使用可能太弱。更大的嵌入模型也许有帮助,但不会解决设计问题。更大的改进将来自更丰富的分块文本、混合检索和重新排序。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章