去年,我们开始在公司内部搭建一个文本到SQL的助手。目标很朴实:有人在内部用俄语问一句“列出未使用的存储卷”或者“这个系统每个月烧多少钱”,助手自动找到数据、写出SQL、执行、返回答案。仓库是ClickHouse,接近900张表,整个链路跑在本地网络里——问题、schema、数据都不出墙。

一开始我们也走了最直觉的路线:把仓库的schema一股脑塞进提示词,让LLM自己摸索。Demo跑起来顺滑得不行,可一上真实仓库,崩盘几乎是分秒的事。两个致命的卡点冒了出来:模型选错表,这还算轻的;更要命的是,就算它偏偏撞上了正确的表,也完全不知道该怎么安全地把它们连起来。

打开网易新闻 查看精彩图片

第二种出错方式的破坏力大得多。模型会像个在黑暗仓库里乱撞的新人:先DESCRIBE一张表,再SELECT另一张,换一个相邻的列再试一次,改个WHERE条件又跑一轮——为回答一个问题,它能用几十个差不多的查询把ClickHouse揍一顿,直到超时。好消息是我们不用给云端付token账单,因为模型就是本地的;坏消息是代价全变成了延迟、被占用的GPU时间和毫无意义的数据库负载。这个教训几乎是用焊枪烙在我们脑子里的:“我需要哪些表?”和“我该怎么连它们?”是两种完全不同的知识,绝对不能把它们揉在一坨巨型提示里。

从那以后,我们把管线拆开,下面这几个要点,就是让本地LLM停止“发明”JOIN的关键。

一、用Markdown文件管理schema知识,告别一锅粥
巨型提示很难维护、审查、比对。我们也不想自创一套配置语言自虐,于是选了最朴素的方案:每个业务领域一个Markdown文件。文件里写着领域标识、俄语和英语的关键词、示例问题,让后续的检索和匹配有据可循。这样添加、修正一个领域的知识,就像改文档一样省心。

二、表检索:向量搜索+pg_trgm,再加一口RRF
找对表不能只靠语义相似度,我们混合了两路信号:一路是向量搜索(bge-m3嵌入),捕捉提问和表描述之间的语义关联;另一路是pg_trgm——Postgres的三元组模糊匹配插件,把俄语关键词、表名、列名的硬匹配兜住。两路分数用倒数排名融合(Reciprocal Rank Fusion)捏在一起,既不怕同义词跑偏,也不怕精确术语对不上。

三、建一张“风险加权连接图”,让模型只走安全通道
即便召回候选表,