用 LLM 给 1245 张数据库表写描述反而让检索效果崩了
给数据库表做索引时,最直觉的操作就是让 LLM 把那些像 v_pmpm 这样看不懂的表名翻译成人类语言的描述,然后把描述和表名一起存入向量库或全文索引。理论上这能弥补用户查询词(英文)与表名(缩写/代码)之间的鸿沟。但我实测一个包含 1245 张表的真实 Schema 时,结果完全相反:检索召回率不仅没升,反而暴跌。
最离谱的是,原先直接搜 contacts 能排在第 3 位的表,在加上了 LLM 生成的准确描述后,竟然掉到了 40 名以后。这意味着描述写得越准确,检索越没用。
为什么描述写得越准确,检索反而越差?
问题出在 BM25 的 IDF(逆文档频率)机制上。BM25 衡量一个词的权重取决于它在整个语料库中出现的频率:
idf(t) = log(1 + (N - n_t + 0.5) / (n_t + 0.5))
简单说,一个词在越少的文档里出现,它的权重(区分度)就越高。
但在 CRM 这种业务场景下,LLM 描述表时会陷入一个陷阱:虽然它写得没错,但它习惯用同一套词汇。比如描述 contacts 表时会提到 "contacts",描述 contact_lists 时也会提到 "contacts",甚至描述审计表或用户表时,因为业务相关性,LLM 也会写上 "contacts"。
结果就是,在 1245 个文档里,"contact" 这个词竟然出现了 1072 次。在算法眼里,这个词已经从一个“高价值的关键词”变成了像 "the" 或 "and" 一样的“停用词”。
我核对了一下数据,在只有表名时,"contact" 的 IDF 值是 4.27(高区分度);但在加上 LLM 描述后,这个值直接掉到了 0.15。这意味着用户输入 "contacts" 时,系统认为这个词毫无区分度,无法帮你定位到核心表。
长度归一化带来的二次打击
如果说 IDF 崩塌只是让排名变平,那么 BM25 的长度归一化(Length Normalization)则直接把排名给反转了。
BM25 有个参数 b,用来惩罚过长的文档。逻辑是:一个长文档里出现一次关键词,不如短文档里出现一次关键词那么“相关”。
但在数据库 Schema 场景里,最核心的表(比如 contacts 表)通常字段最多,列名最长,再加上 LLM 生成的描述,它的文档长度远超平均值。而那些边缘表(比如 contact_import_log)只有几个字段,加上一句简单的描述后依然很短。
结果就变成了:
- 核心表: 包含关键词 → 文档太长 → 被惩罚 → 排名后移。
- 边缘表: 包含关键词 → 文档短 → 权重高 → 排名上升。
这两个因素叠加在一起,导致原本最相关的表被排到了后面,而一些无关紧要的日志表反而冲到了前面。
避坑指南:怎么做才有效?
如果你也在做 Text-to-SQL 或者文档增强索引,千万不要盲目相信「LLM 总结 → 索引」这个链路。
第一,不要把 LLM 描述和原始名称放在同一个检索字段里。
建议采用双路检索或权重分离。给表名(Schema Name)极高的权重,给 LLM 描述较低的权重。不要让 LLM 生成的自然语言稀释了原始标识符的唯一性。
第二,控制描述的词汇多样性。
在 Prompt 里强制要求 LLM 避免使用泛化词,或者在索引前对描述进行关键词提取,而不是直接索引整段话。
第三,针对数据库场景,考虑禁用或调整长度惩罚。
在处理 Schema 这种结构化数据的元数据时,文档长度与相关性并不成反比(核心表字段多是正常现象),此时降低 b 参数的值可以缓解排名反转的问题。
这次实操让我意识到,所谓的「增强」如果破坏了数据的分布特性,实际上就是一种「噪声」。
这种纯靠捐款吊命的科研模式也太心酸了,要是真崩了,那几个核心库谁来维护?