对应:Stanford CS224V Fall 2026 Lecture 7(10/14 Introduction to Agents for Structured and Hybrid Data)。 关于材料来源:2026 年这次课的幻灯片尚未公开。本讲依据:2025 年同主题的幻灯片(Lam & Liu,Introduction to Agents for Structured and Unstructured Data);课程表的描述(语义解析、数据库、数据库查询的表达能力、小表结构上的少样本提示、枚举类型的处理、与人工标注的比较);幻灯片引用的论文。 配套代码:
code/lecture06/(只用 Python 标准库,数据库用自带的sqlite3;05 号文件可选,需要anthropic包和 API 密钥) 预计阅读时间:3 小时左右(含运行代码)
0. 本讲导航
0.1 学完本讲,你应该能够
- 说明为什么很多知识应该查(从数据库里),而不是记(在模型里)或读(从文本里)。
- 用关系代数的几个基本操作解释:为什么随口一问就是一个形式化的查询,以及为什么语义解析器必须学会组合。
- 搭一个查数据库的 LLM 智能体:输入分类器、语义解析器、枚举值的分类(classify)、执行、回复。
- 说出评估 Text-to-SQL 的几个陷阱:字面匹配、执行准确率的误判、标注错误、问题本身的歧义。
- 解释为什么很多问题同时需要数据库和自由文本,以及以往四种做法各自在哪里失败。
0.2 先修知识
| 知识点 | 用在哪里 | 最低要求 |
|---|---|---|
| 第 1、5 讲 | 全讲 | “查出来的”比”记住的”可靠;WikiChat 的检索与核查;LLM 工程”看错误” |
| 第 3 讲 | 第 3 节 | 工具调用;LLM 的输出是”提议”,执行前要校验 |
| 第 4 讲 | 第 2 节 | 形式化的好处;SQL 的 NULL 是三值逻辑 |
| SQL | 全讲 | 讲义和代码里有极简入门(SELECT … FROM … WHERE … ORDER BY … LIMIT) |
0.3 本讲地图
第1节 为什么要知识库?语义解析是什么
第2节 随口一问就是形式化的查询:关系代数 ← 代码 01
第3节 一个查数据库的 LLM 智能体:零样本解析、枚举类型、智能体设计 ← 代码 02、05
第4节 评估:字面匹配、执行准确率、标注错误、歧义 ← 代码 03
第5节 数据库 + 自由文本:以往的四种做法 ← 代码 04
第6节 边界与讨论
第7–11节 小结、与项目的关系、练习、延伸阅读、下一讲预告
0.4 本讲的玩具世界:一个 Yelp 式的餐厅数据库
代码 restaurant_world.py 生成 600 家虚构的餐厅,存进 SQLite:
- 结构化字段:名字、地点(Palo Alto、Mountain View、Menlo Park、Sunnyvale,以及旧金山的 Bernal Heights 和 Mission District)、地址、电话、营业时间、菜系(一个 50 个值的大枚举,例如
"coffee & tea"、"cafe"、"sushi bars"、"szechuan")、价格(一个 4 个值的小枚举:cheap / moderate / expensive / luxury)、评分、评论数、招牌菜。 - 自由文本:每家 3 条评论(另一张表
reviews)。 - 隐藏特征:适合带孩子、景色好、很辣、浪漫、适合聚会、有户外座位。数据库里没有这些列,它们只出现在评论里,而且说法各不相同(“family-friendly” / “our children loved it” / “they have a kids menu”)。它们是第 5 节”混合数据”问题的标准答案。
SQLite 没有数组类型。幻灯片用 PostgreSQL 的写法 'chinese' = ANY(cuisines),本讲把菜系存成 "chinese|dim sum" 这样的文字,并注册了一个自定义的 SQL 函数 has(cuisines, 'chinese')。
关于本讲的数字:幻灯片和论文里的真实数字会注明出处;模拟实验的数字都来自 01~04 号代码的实际输出(固定随机种子,每个脚本 1 秒左右)。模拟的 LLM 解析器、标注者、评论阅读者会犯哪些错误、概率多大,都是设定的参数。05 号代码需要 API 密钥,编写讲义时没有用真实的 API 运行过。
1. 为什么要知识库?
1.1 世界上有大量结构化的数据
幻灯片列了两类:
- 公开的数据库:Schema.org(网页上的结构化数据)、Wikidata(一个有 150 亿条事实的知识图谱,第 8 讲)。
- 私有的数据库:《福布斯》2018 年的说法是”每家公司都是数据公司”。商品(零售、航班、餐厅、歌曲、图书);企业内部的人(员工、客户、学生、病人)和运营(财务、销售、交易);个人信息(日历、邮件)。
这些数据有三个共同点:规模大(几千到几亿行)、经常变化(价格、库存、营业时间)、往往是私有的。第 5 讲的开头说过,LLM 不知道长尾、实时和私有的信息——结构化数据恰好集中了这三种。
1.2 查询语言
常用的查询语言只有少数几种:
- 数据库(SQL):表格,有固定的表结构(schema)——本讲;
- 知识库(SPARQL、Neo4j 的 Cypher):图,节点是实体,边是属性——第 8 讲。
一个领域由它的表结构来定义:有哪些表、每张表有哪些列、每列是什么类型。
1.3 语义解析:自然语言 → 形式化的查询
幻灯片上的流程:
"Show me the best Japanese restaurant in Palo Alto"
│ 表结构
▼
神经语义解析器 ──► SELECT * FROM restaurants
WHERE 'japanese' = ANY(cuisines) AND location = 'Palo Alto'
ORDER BY rating DESC LIMIT 1
│
▼
数据库 / API ──► 结果 ──► 回复生成器 ──► "I searched for the best Japanese restaurant in Palo Alto
and found Daigo. It has a 4.5 rating ..."
把问题翻译成可执行的形式化查询,叫语义解析(semantic parsing);目标语言是 SQL 时,也叫 Text-to-SQL。它和 WikiChat 的思路一脉相承:答案来自数据源,而不是模型的记忆。区别在于,对结构化数据,“检索几段文字再读”既不准确也不完整(“人均 30 美元以下、评分 4.5 以上的意大利餐厅有几家?“需要的是一个查询),所以要把问题翻译成查询语言。
幻灯片说明了这一讲的范围:介绍两个问题——从数据库取数据、从混合语料(数据库 + 自由文本)取数据,并且只关注简单的查询。复杂的查询留给后面几讲(SUQL、SPINACH)。
2. 随口一问,就是一个形式化的查询
2.1 关系代数:一个了不起的计算机科学思想
幻灯片把它称为”了不起的计算机科学思想”:任何领域、任何知识库里的任何查询,都是少数几个关系代数操作的组合。
| 类别 | 操作 | SQL |
|---|---|---|
| 基本 | 选择(selection):只要满足条件的行 | WHERE |
| 基本 | 投影(projection):只要某几列 | SELECT 列 |
| 基本 | 笛卡儿积 / 连接(join):把两张表拼起来 | JOIN … ON |
| 基本 | 并(union) | UNION |
| 基本 | 差(set difference) | EXCEPT |
| 扩展 | 排序 | ORDER BY |
| 扩展 | 聚合(求和、最大、平均、计数……) | SUM、MAX、AVG、COUNT、GROUP BY |
它与领域无关、表达力强、简洁、定义清楚。所以一个把自然语言翻译成 SQL 的解析器,不需要为每个领域发明一种新语言。
2.2 一级一级加上去
代码 01 演示 2 把幻灯片上的那串问题在餐厅数据库上逐条执行:
| 问题 | 新增的 SQL 子句 | 结果 |
|---|---|---|
| Show me Chinese restaurants in Palo Alto. | WHERE location = 'Palo Alto' AND has(cuisines, 'chinese') | 14 行 |
| … with at least 4.5 stars. | AND rating >= 4.5 | 3 行 |
| Show me the address of … with at least 4 stars. | SELECT address(投影) | 9 行 |
| … reviewed by Bob. | AND id IN (SELECT restaurant_id FROM reviews WHERE author = 'Bob')(子查询) | 3 行 |
| … the top-rated ones first. | ORDER BY rating DESC(排序) | 3 行 |
| … with their reviews. | JOIN reviews ON restaurants.id = reviews.restaurant_id(连接) | 42 行 |
| How many cheap restaurants in each city, and their average rating? | COUNT(*)、AVG(rating)、GROUP BY location(聚合) | 6 行 |
演示 3 还给出了并(“意大利菜或比萨”,11 行)和差(“Bob 没评论过的”)。
两点观察:
- 每多说一个短语,SQL 就多一个子句。自然语言是组合式的,SQL 也是,两者的结构可以一一对应。
- 同一个意思有好几种 SQL 写法:“或”可以写成
UNION也可以写成OR,“没有”可以写成EXCEPT也可以写成NOT IN。这一点在评估时会造成麻烦(第 4 节)。
2.3 小测验:一共能问多少种查询?
幻灯片的小测验:“对 Yelp,一共有多少种可能的查询?“代码 01 演示 4 只算最简单的一类——每个字段最多一个条件:
| 部分 | 选择数 |
|---|---|
| 地点 | 7(6 个城市或不限) |
| 菜系 | 51 |
| 价格 | 5 |
| 最低评分 | 5 |
| 评论者 | 11 |
| 要哪些列(投影) | 2,047(11 列的任意非空子集) |
| 排序 | 5 |
| 取前几个 | 5 |
乘起来是 5,024,105,625 种,约 。这还没算”或""不”、聚合、连接、嵌套。所以语义解析器不能靠背下所有问题,它必须学会组合:把短语逐个翻译成子句,再拼起来。关系代数的价值正在于此——少数几个操作组合出无穷多的查询,而每个操作的含义都定义得清清楚楚。
3. 一个查数据库的 LLM 智能体
3.1 零样本提示
幻灯片的餐厅助手用一张 Yelp 表:名字、地址、地点、电话、营业时间、菜系(数组)、价格(枚举:cheap / moderate / expensive / luxury)、评分、评论数、评论(自由文本)、招牌菜(自由文本)。零样本提示只有一句话加表结构:
You are a semantic parser. Generate a query for a restaurant database with the
following signature:
CREATE TABLE restaurants (name TEXT, address TEXT, location TEXT, ...,
cuisines TEXT[], price ENUM ('cheap', 'moderate', 'expensive', 'luxury'),
rating NUMERIC(2,1), num_reviews NUMBER);
幻灯片的”随手评估”:LLM 出奇地好用。原因有二:LLM 熟悉餐厅这样的领域,LLM 已经会 SQL 的基本语法。在多轮对话里,它还能结合上下文:先问”Mountain View 的餐厅”,再问”意大利菜呢?“,再问”Palo Alto 呢?“,生成的 SQL 依次是:只有地点 → 加上菜系 → 换掉地点。
代码 02 演示 1 用一个模拟的 LLM 解析器重现了这些现象:
| 问题 | 解析器写的 SQL(摘要) | 结果 |
|---|---|---|
| What are some good inexpensive chinese restaurants in Palo Alto? | location = 'Palo Alto' AND has(cuisines, 'chinese') AND price = 'cheap' AND rating >= 4.0 | 2 行 |
| Give me 3 top-rated ramen places in Mountain View. | … ORDER BY rating DESC LIMIT 3 | 3 行 |
| What is the address of the most popular italian restaurant in Menlo Park? | SELECT id, name, address … ORDER BY num_reviews DESC LIMIT 1 | 1 行 |
| Show me a café in Palo Alto. | has(cuisines, 'coffee') | 0 行 |
第一个问题里,“inexpensive” 自动对应到了表结构里列出的 'cheap';“good” 被解析器自作主张地翻译成了 rating >= 4.0(幻灯片上的例子也是这样:price = 'cheap' AND rating >= 4.0 ORDER BY num_reviews DESC LIMIT 3)——这算不算对,第 4 节讨论。最后一个问题一行都没查到。
3.2 枚举类型
幻灯片观察到的主要问题是枚举类型:
- 小的枚举(比如价格的 4 个值):直接写进表结构,LLM 知道怎么用。
- 大的枚举:Yelp 的菜系有 200 多个值,例如 “brazilian”、“southern”、“soul food”、“modern european”、“champagne bars”、“latin american”、“health & medical”、“singaporean”……不可能全写进表结构。而数据库要求精确匹配。
幻灯片的例子:用户说 “show me a café”,LLM 写 'coffee' = ANY(cuisines)——可是 Yelp 里没有 “coffee” 这个菜系,只有 “coffee & tea” 和 “cafe”。代码 02 演示 2 里,“café”、“coffee shop”、“sushi”、“bbq” 这四种说法,零样本都查到 0 行。
幻灯片的解决办法:把文本字段上的 = 改写成一个分类操作。
- 在字段的全部取值 里找出和 最接近的那些值;找不到就返回空集。
- 改写规则:,其中 。
SELECT * FROM restaurants WHERE 'coffee' = ANY (cuisines)
⇓ classify('coffee', 全部菜系) = {'coffee & tea', 'cafe'}
SELECT * FROM restaurants WHERE 'coffee & tea' = ANY (cuisines) OR 'cafe' = ANY (cuisines)
这个改写由程序完成,不需要 LLM 知道全部取值——和第 5 讲”能用程序保证的事情,就不要交给 LLM”是同一个思路。
3.3 小测验:classify 怎么实现?
幻灯片把这个问题留作小测验。代码 02 演示 2 比较了四种实现,在 28 个口语说法上(标准答案是人工标注的”用户真正想要的值”):
| classify 的实现 | 精确率 | 召回率 | 提示里要放几个枚举值 |
|---|---|---|---|
| 精确匹配 | 0.0% | 0.0% | 0 |
| 字符相似度(三元组 Jaccard ≥ 0.3) | 95.2% | 52.6% | 0 |
| 检索前 10 个候选 + LLM 挑选 | 100.0% | 65.8% | 10 |
| 全部枚举值写进提示 + LLM 挑选 | 92.5% | 97.4% | 50(全部) |
- 字符相似度能救回 “sichuan” ≈ “szechuan”,但 “coffee” 和 “cafe” 长得不像,它就找不到。
- 把全部取值写进提示、让 LLM 挑,召回率最高(LLM 知道 café 就是 coffee & tea),但提示的长度随枚举的大小增长。50 个还行,200 多个、几万个(商品目录)就不行了。
- 折中:先检索出少量候选值,再让 LLM 挑。这里用字符相似度取候选,所以”长得不像”的正确值进不了候选;真实系统会用语义向量(第 5 讲的稠密检索)取候选。
- LLM 的常识也有偏差:模拟里 “curry” 只选了印度菜、漏了泰国菜,“noodle” 多选了越南菜。
200 个问题上的执行准确率(演示 3;一半的菜系用口语说法;只保留标准答案非空的问题——空结果和空结果”一致”,说明不了解析得对不对):
| 解析器 | 执行准确率 | 一行都没查到 |
|---|---|---|
| 零样本(菜系照着用户的说法写) | 63.0% | 57/200 |
| + classify:精确匹配 | 49.0% | 97/200 |
| + classify:字符相似度 | 70.5% | 32/200 |
| + classify:检索前 10 个候选 + LLM 挑选 | 79.5% | 14/200 |
| + classify:全部枚举值写进提示 + LLM 挑选 | 94.0% | 0/200 |
零样本的错误大多表现为”一行都没查到”。这是一个有用的信号:智能体可以据此改写查询,或者告诉用户”没有找到,要不要放宽条件”,而不是编一个答案。
3.4 完整的智能体设计
幻灯片:有些轮次根本不是查询,让 LLM 直接回答。完整的设计是:
用户的话 → 输入分类器 ─(需要查数据库)→ 语义解析器 → classify 改写枚举值 → SQL 编译器 → 数据库
│ │
└─(不需要查)→ LLM 直接回答 有结果 → 回复的提示词
没有结果 → "没有结果"的回复
代码 02 演示 4 跑了一段 8 轮的对话:
用户:Show me restaurants in Menlo Park. SQL:… WHERE location = 'Menlo Park' → 99 行
用户:What about italian ones? SQL:… location = 'Menlo Park' AND has(cuisines, 'italian') → 4 行
用户:How about in Palo Alto? SQL:… location = 'Palo Alto' AND has(cuisines, 'italian') → 10 行
用户:Which one is the top-rated? SQL:… ORDER BY rating DESC LIMIT 1 → 1 行
用户:Show me a café there instead. SQL:… (has(cuisines, 'cafe') OR has(cuisines, 'coffee & tea')) → 4 行
用户:What is dim sum? [不需要查数据库 → LLM 直接回答]
用户:Show me luxury vegan places in Sunnyvale with at least 4.5 stars. → 0 行 → "没有找到,要不要放宽价格或评分?"
用户:Thanks! [不需要查数据库]
- 多轮对话里,后面的话只说了一个条件,解析器要结合上一轮的查询。
- 不是每一句都需要查数据库,这是输入分类器的作用。
- 查不到时,不要编一个答案。
- 回复是根据查出来的行写的:名字、地址都来自数据库,而不是 LLM 的记忆。
幻灯片的小结:“零样本的、基于 LLM 的数据库智能体很容易搭。它们表现好吗?“——这是下一节的问题。
4. 评估
4.1 “下一步该怎么办?”
幻灯片:在标注好的数据上测查询准确率——准确率很差。小测验:“下一步该怎么办?“答案是:
亲手抽查结果!(怎么强调都不为过)
4.2 两个小测验:哪个是对的?
例 1:“What’s some great Mexican food around Bernal Heights?”
标注: SELECT … WHERE 'mexican' = ANY(cuisines) AND location = 'bernal heights'
预测: SELECT … WHERE 'mexican' = ANY(cuisines) AND location = 'bernal heights' AND rating >= 4
在代码 03 的数据库里,前者 10 行,后者 7 行。“great” 意味着”评分至少 4 分”吗?两种理解都说得通。这是歧义,不是模型的错误;但按执行准确率算,模型”错了”。
例 2(BIRD 数据集):“Name all cards with 2015 frame style ranking below 100 on EDHRec.”
标注: SELECT id FROM cards WHERE edhrecRank < 100 AND frameVersion = 2015
预测: SELECT name FROM cards WHERE edhrecRank < 100 AND frameVersion = 2015
问题说 “Name all …”,返回名字才对。标注写成了 SELECT id,按执行准确率算,返回名字的模型反而”错了”。这是标注错误。
4.3 研究组的发现过程
幻灯片记录了研究组的经历:
- 试着用少样本提示让 ChatGPT 遵守标注的约定;
- 试着去修正基准里的标注;
- 结论:“ChatGPT 比博士生的标注还好!”
这和第 5 讲的”范式转变”一致:现有 NLP 数据集里的标注错误百出,LLM 生成的结果往往更好。
4.4 模拟实验:三种指标
代码 03 模拟了两位约定不同的标注者(A 遇到 “good / great” 加上 rating >= 4,B 不加;每题随机由一位标注;各有 6% 的概率写错),以及一个写法风格不同的解析器(条件的先后顺序不同;按评分排序时加一个次要的排序键)。200 道题、35% 带 “good / great”(演示 2):
| 指标 | 解析器 |
|---|---|
| 字面匹配(规范化之后 SQL 文字完全相同) | 25.5% |
| 执行准确率(执行结果和标注的 SQL 相同) | 74.5% |
| 人工判断(在合理的理解下,结果可以接受) | 88.5% |
用同样的”人工判断”去检查标注的 SQL 本身:190/200(95.0%)可以接受——也就是说,有 5% 的标注是错的。
- 字面匹配几乎全军覆没:意思一样,写法不一样。
- 执行准确率好得多,但仍然冤枉了不少可以接受的预测。
- “标准答案”不再是天然可信的。
4.5 逐个看一遍
演示 3 把执行结果不一致的 51 道题逐个分类:
| 类别 | 题数 |
|---|---|
| 解析器错了,标注对 | 22(43%) |
| 两个都对(歧义、写法或并列名次) | 20(39%) |
| 标注错了,解析器对 | 9(18%) |
有 29 道(57%)不是解析器的错。只看一个分数,你会以为解析器有 51 个错误要修;看了具体的例子,才知道其中过半该改的是测试集(统一标注约定、改正标注错误)和评估方法。真正的解析器错误也值得细看:这里主要是 LLM 对含糊说法的常识偏差(“latin” 多选了墨西哥菜,“curry” 漏了泰国菜)和偶尔漏掉一个条件。知道了错误的类型,才知道该改哪一步。
4.6 执行准确率也会骗人
演示 4 展示了两种误判:
- 碰巧相同:数据库太小,某个条件恰好不起作用,漏掉这个条件的 SQL 也会被判”对”。200 道题里有 1 道这样的情况(价格条件去掉后结果完全不变)。
- 并列名次:“评分最高的前 个”这类题里,第 名和第 名评分并列的有 9/28 道。并列时取哪一个都对,但两条 SQL 的次要排序不同,执行结果就不同,会被判”错”。
更可靠的做法:在更大、更多样的数据库上执行;对有歧义的题允许多个标准答案;关键的结论一定要人工抽查。
4.7 小结与下一个问题
幻灯片对简单结构化查询的小结:少样本的 ChatGPT 可以为 Yelp 解析 SQL,但要注意前提——
- 餐厅是 ChatGPT 熟悉的领域;
- 表很小:11 个字段(包括 2 个自由文本字段、1 个小枚举、1 个大枚举);
- 字段名都很好懂。
换一个陌生的领域、几百张表、字段名是缩写,情况就完全不同了(BIRD、Spider 这类基准测的正是这些)。
下一个小测验:“接下来做什么?“——找一个真实用户试试(不是我们这些设计系统的人)。真实用户问的第一句话可能就是:“Find me a spicy Chinese restaurant in Palo Alto!”——“中餐""Palo Alto”在数据库里,“辣”在哪里?
5. 数据库 + 自由文本
5.1 很多信息在文本里
在 Yelp 上找餐厅时,结构化的列(地点、菜系、评分)只是一部分,大量信息在评论和招牌菜这样的自由文本里。幻灯片的例子:
- “I need a family-friendly restaurant” → “family-friendly” 在评论里;
- “I need a family-friendly restaurant in Palo Alto” → “family-friendly” 在评论里,Palo Alto 在数据库里。
**这种组合常见吗?常见。**在真实用户关于餐厅的问题里,100 个里有 55 个需要同时用到两者。分开处理的模块回答不了这些问题。混合数据也绝不限于餐厅:
| 领域 | 结构化数据 | 自由文本 | 任务 |
|---|---|---|---|
| 餐厅 | 菜系、营业时间、地址、评分 | 评论、招牌菜 | 订餐厅 |
| 商品 | 编号、价格、评分、尺寸、颜色 | 描述、评论 | 购物、点外卖 |
| 课程 | 课号、时间、老师、教室、先修课 | 描述、评价 | 选课 |
| 业务流程 | 收据、账单 | 规章制度 | 报税、报销、客服 |
| 医疗 | 病人信息、处方 | 诊断记录 | 诊断、保险理赔 |
5.2 以往的四种做法
幻灯片从最简单的开始介绍了四种做法:
1. 先分类:判断问题该查数据库还是该查文本。代表是 SK-TOD(Zhao 等,SIGDIAL 2023,“What do others think?”)——第一个包含主观知识的任务型对话数据集:基于 MultiWOZ 里剑桥地区的餐厅和酒店,用户的目标是搜索和预订;结构化部分是 13 个槽位(比 SQL 简单);文本部分是 143 个实体的 1,430 条评论(8,013 句)。流程:二分类(检索 vs 语义解析)→ 实体追踪(对话和实体名的模糊 n-gram 匹配)→ 知识选择(对话和知识片段的相关性打分)→ 用 GPT-2 / BART 生成回复。
2. 两边都做,最后挑一个:代表是 Stanford Chirpy Cardinal(2021 年 Alexa Prize 社交机器人大赛亚军):不同的模块处理不同类型的问题,并行运行各个生成器,最后挑一个。
幻灯片的小测验:“只选数据库或只选文本,弱点是什么?“——需要两者才能回答的问题,这两种做法都回答不了。
3. 全部转成文本:代表是 UniK-QA(Oguz 等,Findings of NAACL 2022):把半结构化的数据(维基百科的列表、表格、信息框)和知识库都转成文本,再用文本问答的流水线(神经检索器 + T5 阅读器)。维基百科的表格”线性化”的方式很简单:把单元格的值拼起来,用特殊符号分隔各行。幻灯片给出的精确匹配分数:
| 知识来源 | Natural Questions | TriviaQA | WebQuestions |
|---|---|---|---|
| 维基百科文本 | 49.0 | 64.0 | 50.6 |
| 维基百科表格 | 36.0 | 34.5 | 41.0 |
| Wikidata | 27.9 | 35.4 | 55.6 |
| 文本 + 表格 | 54.1 | 65.1 | 50.2 |
| 文本 + 表格 + Wikidata | 54.0 | 64.1 | 57.8 |
可以观察到:合并多个来源有帮助,但不是在所有数据集上都有帮助(WebQuestions 的答案是在 Freebase 知识库上标注的,知识库对它格外有用,单用 Wikidata 就有 55.6);而单独使用表格或知识库,在另外两个数据集上都明显不如文本。幻灯片指出了线性化的两个问题:
- 怎样把几个子句组合起来?(关系代数的操作天然就能做到)例如:“旧金山有没有能看到金门大桥美景的餐厅?”
- 在扁平的数据上很难做按列的计算(排名、最大、最小、平均、计数)。例如:“评分最高的中餐馆是哪家?”
4. 两边分别检索,再让 LLM 合起来:代表是 HybridQA 数据集(Chen 等,Findings of EMNLP 2020;不是对话式的)上的方法。HybridQA 的问题需要组合维基百科的表格和表格单元格链接到的维基百科页面(幻灯片的例子是”缅甸奥运代表团旗手”的表格)。2023 年排行榜第一的 S³HQA(Lei 等,ACL 2023)分三个阶段:
- 从表格和文本中检索信息(还要额外处理标注错误);
- 混合选择器:按相关性排序和过滤;
- 基于 LLM 的推理器:把数据库和文本的信息组合起来;用词法分析判断问题类型(计数、比较),用思维链提示。
幻灯片指出的问题:
- 数据只检索一跳:回答不了”第二次检索取决于第一次的结果”的级联多跳问题;
- 词法分析不精确;
- 缺乏完整性和组合能力:只能做一次”计数”或一次”比较”。
5.3 模拟实验
代码 04 在餐厅数据库上把四种做法都实现了一遍,再加上第 7 讲 SUQL 的思路作为对照:
做法 5(组合):先用 SQL 过滤结构化的条件,再对剩下的每一行让 LLM 读评论,最后在过滤后的完整集合上排序或计数。
五类问题各 20 道(“某家店的评论”是 SK-TOD 式的问题,例如 “Do reviewers say that Jade Table Grill serves really spicy food?”;其余例如 “Show me chinese restaurants in Palo Alto that are good for large groups.”、“What is the top-rated restaurant in Mission District that is family-friendly?”、“How many restaurants in Palo Alto are good for a date?”)。得分:集合类问题用 F1,是非题和”最高”用准确率,计数用 1 − 相对误差:
| 做法 | 只查数据库 | 某家店的评论 | 两者都要 | 两者都要 + 最高 | 两者都要 + 计数 | 阅读者调用/题 |
|---|---|---|---|---|---|---|
| 1 先分类 | 1.00 | 1.00 | 0.01 | 0.10 | 0.54 | 6.2 |
| 2 两边都做,挑一个 | 1.00 | 0.50 | 0.40 | 0.00 | 0.00 | 6.2 |
| 3 线性化成文本 | 1.00 | 0.80 | 0.48 | 0.75 | 0.30 | 10.0 |
| 4 两边各检索一跳再合并 | 1.00 | 0.50 | 0.02 | 0.25 | 0.09 | 8.0 |
| 5 组合:SQL 过滤 + 逐行阅读 | 1.00 | 1.00 | 0.96 | 0.95 | 0.83 | 42.0 |
(文本检索用 BM25,每次取前 10 家;“阅读者”认得评论里各种说法,但有 3% 的概率判断错。)
- 只查数据库的问题,五种做法都满分——结构化的查询本身不难。
- “某家店的评论”:做法 1 找到这家店、读它的评论,满分(这正是 SK-TOD 设计的场景);做法 2 挑了数据库的答案,可数据库回答不了”它辣不辣”,只能蒙对一半;做法 4 的评论检索在全库范围内取前 10 家,这家店多半不在里面。
- 两者都要的三类问题,做法 1、2 必然失败:它们每次只用一边的信息。
- 做法 3 只在检索到的前 10 家里找;“有几家”只能在这 10 家里数。
- 做法 4 的评论检索没有限定在数据库查出的范围里,交集往往很小;BM25 还会漏掉换了说法的评论。
- 做法 5 三类组合问题在 0.83~0.96 之间,剩下的误差来自阅读者 3% 的判断错误(计数时要读几十行,错误会累积,所以计数最低)。代价是阅读者的调用次数:要读的行数 = SQL 过滤后剩下的行数。
演示 3 用一道题对比了五种做法(“Show me american (traditional) restaurants in Mountain View that are good for a date.”,标准答案 4 家):做法 1 返回 9 家、全错;做法 2 返回 9 家、4 家对(没检查”适合约会”);做法 3 返回 3 家、全对(漏了一家);做法 4 返回 0 家;做法 5 返回 4 家、全对。
5.4 结论
幻灯片的结论:
- LLM 能处理简单的数据库查询;
- 很多问题需要组合从数据库和文本里取到的信息;
- “结构或文本”是不够的:先分类(SK-TOD,2023)、事后挑选(Chirpy Cardinal,2021)都不行;
- 把两者结合的做法:线性化(只检索一跳)、两边检索再合并(每边一跳)都不够;
- 我们需要更好的技术,来处理复杂的数据库查询和混合问答——这就是第 7 讲的 SUQL。
6. 边界与讨论
- 熟悉的领域、小的表。本讲的结论建立在”餐厅、11 个字段、字段名好懂”的前提上。大型企业数据库有几百张表、几千列、晦涩的缩写,表结构本身都塞不进提示词,需要先检索相关的表和列(schema linking)。
- 枚举只是”值对齐”的一种。地点(“SF” vs “San Francisco”、“Bernal” vs “Bernal Heights”)、人名、商品名都有同样的问题;classify 的思路可以推广到任何文本字段。
- 执行之前要校验。LLM 写的 SQL 是提议:只允许只读的查询,限制返回的行数和执行时间(代码 05 只执行以
SELECT开头的单条语句)。 - “查不到”要说出来。数据库返回空集时,智能体应该说明,而不是用记忆补一个答案(第 5 讲”我不知道”的数据库版本)。
- 评估方法本身需要验证。字面匹配、执行准确率都有系统性的误判;标注可能有错;问题可能有歧义。分数之外,必须看具体的例子。
7. 本讲小结
- 结构化数据规模大、经常变化、往往私有——应该查,而不是记或读。语义解析把自然语言翻译成可执行的查询,答案来自数据库。
- 关系代数:选择、投影、连接、并、差,加上排序和聚合,组合出任何领域的任何查询。随口一问就是一个形式化的查询;查询空间巨大(仅最简单的一类就约 种),解析器必须学会组合。
- 零样本的 LLM 解析器在熟悉的领域、小的表上出奇地好用;主要问题是大的枚举类型:把
=改写成 classify,在全部取值里找最接近的值。完整的智能体还需要输入分类器、多轮上下文、“没有结果”的回复。 - 评估:字面匹配几乎没用;执行准确率会冤枉歧义和并列名次,也会放过碰巧相同的结果;标注本身也会错。“亲手抽查结果!”
- 混合数据:真实用户的餐厅问题里 55% 需要同时用到数据库和文本。先分类、事后挑选、线性化、两边各检索一跳,都缺乏组合能力;“先用 SQL 过滤、再逐行读文本”的组合式执行才能回答——第 7 讲的 SUQL 把它变成一种查询语言。
8. 与项目的关系
- 如果你的领域有数据库(病人记录、商品、课程、交易),先让 LLM 写查询,而不是让它读表格。从零样本开始,用关键例子调试(第 5 讲的 LLM 工程)。
- 列出你的表结构里所有的文本字段和枚举字段:哪些是小枚举(直接写进提示),哪些是大枚举(需要 classify)?用户会用哪些说法?
- 评估时:统一标注约定;对歧义的问题允许多个答案;一定要人工抽查解析器和标注不一致的例子。
- 你的用户问题里,有多少需要同时用到数据库和文本?如果比例很高(餐厅是 55%),第 7 讲的 SUQL 就是为你准备的。
9. 练习
练习 1(写 SQL)。为下面的问题写一条 SQL(用本讲的 has(cuisines, …) 写法),然后在 restaurant_world 上执行:“What is the phone number of the most popular cheap Korean restaurant in Sunnyvale?” 它用到了哪几种关系代数操作?
参考答案
SELECT name, phone, num_reviews FROM restaurants
WHERE location = 'Sunnyvale' AND has(cuisines, 'korean') AND price = 'cheap'
ORDER BY num_reviews DESC LIMIT 1
执行结果:('Old Bowl House', '(650) 376-9521', 2392)(满足三个条件的一共 3 家)。用到了选择(WHERE 的三个条件)、投影(只要名字和电话)、排序(按评论数)和”取第一个”。“most popular”被理解成”评论数最多”——这本身也是一种约定,换成”评分最高”也说得通。
练习 2(动手改代码:classify 的阈值)。在 text2sql.py 里,把 classify_string 的 threshold 从 0.3 改成 0.2 和 0.1,精确率和召回率怎样变化?
参考答案
实测:0.3 时精确率 95.2%、召回率 52.6%;0.2 时 92.0%、60.5%;0.1 时 77.4%、63.2%。阈值越低,选进来的值越多:召回率上升,精确率下降。但召回率的提升有限——“coffee” 和 “cafe”、“bbq” 和 “barbeque” 这类说法,字符层面本来就不像,降低阈值也救不回来,还会引进一堆无关的值。字符相似度的天花板在这里,需要语义(LLM 或向量)。
练习 3(动手改代码:标注约定)。在 03_evaluation_pitfalls.py 里调用 build_test_set(world, conventions="A")(全部按 A 的约定标注:good / great 加评分条件)和 conventions="B",执行准确率和人工判断分别是多少?这说明了什么?
参考答案
实测:混用两种约定(默认)时执行准确率 74.5%;全部用 A 的约定 81.5%;全部用 B 的约定 67.5%。人工判断三种情况下都是 88.5%。同一个解析器,只是换了标注约定,执行准确率就相差 14 个百分点——解析器的习惯(加评分条件)和标注约定一致时分数高,不一致时分数低。这说明报告分数时必须说明标注约定;比较两个系统时,必须在同一套约定下比较;有歧义的问题最好允许多个标准答案。
练习 4(动手改代码:检索的 k)。在 04_hybrid_approaches.py 里用 Tools(world, k=30)(文本检索取前 30 家)重新运行做法 3 和做法 4。分数和调用次数怎样变化?做法 3 能赶上做法 5 吗?
参考答案
实测(只查数据库 / 某家店的评论 / 两者都要 / 两者都要 + 最高 / 两者都要 + 计数):
- 做法 3: 时 1.00 / 0.80 / 0.48 / 0.75 / 0.30,调用 10 次; 时 1.00 / 0.95 / 0.83 / 0.75 / 0.50,调用 30 次。
- 做法 4: 时 1.00 / 0.50 / 0.02 / 0.25 / 0.09; 时 1.00 / 0.60 / 0.18 / 0.65 / 0.25。
越大越好,但调用次数成正比地增加,而且计数和”最高”仍然不对:线性化的检索不知道”必须把 Palo Alto 的每一家都看一遍”。做法 5 的调用次数(平均 42 次)由 SQL 过滤后剩下的行数决定——该读多少就读多少,这是组合式执行的关键。
练习 5(思考:classify 的推广)。用户说 “Find me a place in SF” 或 “around Bernal”,数据库里的地点是 “Mission District”、“Bernal Heights”。classify 能直接用在地点上吗?需要注意什么?
参考思路
可以:地点也是一个取值有限的文本字段,同样可以把 location = 'SF' 改写成 location IN classify('SF', 全部地点)。但要注意:(1) 有些说法对应的是一组值(“SF” 对应旧金山的所有街区),需要地理层级的知识;(2) “nearby”、“around here” 依赖用户的位置,属于上下文,不是 classify 能解决的;(3) 地点的取值可能很多(全国的城市),需要先检索候选。这和第 4 讲的医学本体(“癫痫”包含”局灶性癫痫”)是同一类问题:取值之间有上下位关系。
练习 6(思考:混合查询怎么写)。不考虑具体的语法,写出”Palo Alto 适合带孩子的意大利餐厅,按评分排序”的执行计划:哪些部分交给数据库,哪些部分交给 LLM?执行顺序对 LLM 的调用次数有什么影响?
参考思路
计划:(1) 数据库过滤 location = 'Palo Alto' AND has(cuisines, 'italian');(2) 对剩下的每一行,让 LLM 读它的评论,回答”是否适合带孩子”;(3) 对通过的行按评分排序。如果先做 (2) 再做 (1),LLM 要读全部 600 家的评论;先做 (1),只需要读 10 家(代码 02 演示 4 里,Palo Alto 的意大利餐厅一共 10 家)。所以先做便宜的结构化过滤,再做贵的 LLM 判断。第 7 讲的 SUQL 把这一步写成 SQL 里的一个函数,例如 answer(reviews, 'is it family-friendly?') = 'Yes',并由执行器自动安排顺序。
10. 延伸阅读
课程相关
- Zhao 等. “What do others think?”: Task-Oriented Conversational Modeling with Subjective Knowledge(SK-TOD). SIGDIAL 2023. arXiv 2305.12091.
- Chi 等. Neural Generation Meets Real People: Building a Social, Informative Open-Domain Dialogue Agent(Chirpy Cardinal). SIGDIAL 2022.
- Oguz 等. UniK-QA: Unified Representations of Structured and Unstructured Knowledge for Open-Domain Question Answering. Findings of NAACL 2022.
- Chen 等. HybridQA: A Dataset of Multi-Hop Question Answering over Tabular and Textual Data. Findings of EMNLP 2020.
- Lei 等. S³HQA: A Three-Stage Approach for Multi-hop Text-Table Hybrid Question Answering. ACL 2023. arXiv 2305.11725.
Text-to-SQL 基准
- Yu 等. Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task. EMNLP 2018.
- Li 等. Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs(BIRD). NeurIPS 2023. bird-bench.github.io
下一讲
- Liu 等. SUQL: Conversational Search over Structured and Unstructured Data with Large Language Models. Findings of NAACL 2024.
11. 下一讲预告
本讲的结论是:“结构或文本”不够,我们需要能组合两者的技术。第 7 讲介绍 SUQL(Structured and Unstructured Query Language):
- 第一个把信息检索(自然语言)和数据库查询(SQL)结合起来的查询语言:在 SQL 里加入由 LLM 执行的函数,例如
answer(reviews, 'is it family-friendly?')、summary(reviews); - 语言设计:为什么是”在 SQL 上扩展”,而不是发明一种新语言;
- 执行优化:先做便宜的结构化过滤,再对少量行调用 LLM(本讲练习 6 的思路);
- 评估方法:在真实用户的问题上,怎样衡量一个混合查询系统。