全文分七部分:一、世界 | 二、对世界的理解 | 三、测试者与被测者的分工 | 四、测试者的设计思路 | 五、被测者的设计思路 | 六、交互的约定 | 七、项目结构与文档分工。
一句话概括:SHRDLU 通过对积木世界的提问和操作判定理解。测试者提供一个受限世界与一套问句;被测者观察这个世界,根据要求回答问题或执行操作等;测试者依据世界的当前状态判定被测者的理解程度。
世界是一个积木世界:一张桌面,加上若干物体。
每个物体有三个属性:形状、颜色、大小。属性的组合足以唯一确定一个物体。例如,"红色的大方块" 这样的描述可以唯一确定一个物体。
每个物体在任一时刻只处在以下三种位置状态中的一种:在桌面上、在另一个物体上、或者被拿起;同一时刻仅允许有一个物体被拿起。
物体可以被移动:拿起、放下、放到另一个物体上。移动只改变位置,不改变属性。
本项目关心的是被测者对世界的理解。
理解不能由被测者自述,只能从它在两类交互里的表现看出来。测试者把世界交给被测者,然后就这个世界向它提问,也要求它改变这个世界:
- 提问:测试者就世界的当前状态发问,被测者作答,看它答得对不对。
- 操作:测试者要求被测者改变世界。一条操作指令的落实分两件事:
- 更新被测者自己那份世界认识(由被测者自行管理,并在后续回答提问时表现出来)
- 回复一条形式化的操作调用(格式由测试者给定,测试者据此判定被测者对当前指令的理解)
更新状态而跟上世界的变化是更深一层的理解,而且与记忆有关。 测试开始时,测试者把世界的初始状态交给被测者;此后世界随操作而改变,测试者不再展示新的世界。被测者要记住自己做过什么、世界随之变成什么样,才能说清世界现在是什么样。
测试的载体是一份场景:初始世界的快照、一套操作,以及最初以哪种形式把世界交给被测者(图像或符号信息)。场景加上指令序列与题库,就是一份可用的考卷;自动出题使用固定种子,同一场景与同一种子必得同一份考卷。
测试台由两部分组成:测试者(env,程序 shrdlu-env)与被测者(testee,程序 shrdlu-testee)。
测试者掌握世界的当前状态,负责提问、要求操作、判分与记录。被测者在测试开始时得到世界的一份信息,此后由自己发出的调用推进自己对世界的认识。
交互分两部分,双方在其中的分工是:
- 问答:测试者发问句,被测者回答句。
- 操作:测试者发指令,被测者回操作调用或拒绝;测试者照这个调用执行,并把它施加到自己维护的那份 被测者通过操作表现出的世界状态上。
分工落在三条边界上:
- 世界信息只交付一次:测试者在测试开始时把世界的初始状态交给被测者,此后发出的是问句与指令,世界在操作之后变成什么样,由被测者从自己发出的调用推出来。
- 一问一答:测试者发一条、被测者回一条;它给出的操作调用由测试者照办,测试者不回话。
- 判分只看两样:被测者发出的操作调用、它给出的回答。
世界的当前状态的持有者 + 问句求值器 + 判卷者。
它自己维护两份世界状态:一份是 世界的当前状态(按考卷里的指令实际执行而演化),一份是 被测者通过操作表现出的世界状态(按被测者发来的调用演化)。判定时机械比对。
一次测试依次走完三段:先只提问(对初始状态)、再要求操作(逐条指令、逐条核对)、最后再提问一次(对变化后的状态)。提问与操作必须分开评:混在一批里时,一个操作做错之后被测者回答的是它自己那个已经偏离的状态,后面的问句就失去了意义。
| 段 | 考的是 | 判定 |
|---|---|---|
| 第一步:只回答问题 | 对初始状态的理解 | 答句归一后与世界的当前状态给出的答案比对 |
| 第二步:做若干操作,每个操作之后核对状态 | 操作是否理解、状态是否按语义演化 | 逐条指令按"该做/不该做 × 做了/拒绝了"判(合法指令必须执行,非法指令拒绝了才算对,去执行非法操作直接判不理解);每个操作之后对拍两份世界状态(对拍用来定位从第几个操作起、在哪些物体上不同,不额外扣分) |
| 第三步:再回答问题 | 对状态变化的理解 | 以 被测者通过操作表现出的世界状态为准;同时也与世界的当前状态比一次,这一次比较只作记录 |
三条规则:
- 段内严格:一段里全部答对才算这一段理解。P0 的题都是封闭式、答案唯一;定指不唯一的题不计入分母,并提示出题方。
- 段与段互不连累:第三步以被测者自己的状态为准,所以前面做错的操作不会让后面的问句失去意义。唯一的例外是执行非法操作:那是明确的不理解,直接给结论。
- 结论逐段呈现:整次测试另有一个结论,它是三段结论的合取;呈现形式是"第一步 10/10 理解、第二步 4/4 理解、第三步 5/10 不理解"这样的分段清单,而不是只给一个总判定。
两轮问什么题由测试者决定:现行做法是把同一份题库问两遍,两轮答案成对,才能把"没看懂交来的世界"与"没跟上世界的变化"分开;两轮不必相同,题目如何分配属于出题设计。
接口层面的失败(初始观察就崩、回报不是合法 JSON)不属于任何一段,会让整次测试作废并单独记录;段内的协议错误只算该段出错。
- 求值器是机械的:只做"问句语法 → 对状态求值",不做推理、规划或学习。这是测试者与"世界模型"的区别:测试者的状态是执行出来的,它不需要推断。
- 测试者不判断被测者的"思路":它不知道、也不猜被测者内部记着什么,只比对回答与世界的当前状态。
被判断者。声称理解这个世界,通过回答问题来证明。
测试开始时它收到一次观察,只有这一次;此后要响应的只有两条消息:提问与操作。这两条消息各自要做什么,见 §2;线上形态见 §6。
- 它交出的只有回答与操作调用:测试者判分只看这两样(这条边界见 §3);它不必交出任何状态,也不要求它导出自己的世界模型(导出会强制符号化)。
测试者与被测者是两个各自独立的程序(也允许同处一个进程),两边不共享内存,因此每一次交互都要序列化成一行一个 JSON 对象:UTF-8 文本,一行一条消息,以换行结束。
这些消息与函数调用一一对应:消息类型就是函数名,消息里的其余字段就是入参,回复就是返回值。
| 函数调用 | 线上形态 |
|---|---|
| 函数名 | 消息的 type 字段:observe / question / command |
| 入参 | 消息的其余字段:state(或 image)、text |
| 返回值 | 回复那一条消息的 type 与字段:answer / action / refuse |
| 测试者 → 被测者 | 入参 | 被测者 → 测试者 |
|---|---|---|
observe |
state:世界的初始状态,或 image:一张图 |
没有回复;整次测试只发一次 |
question |
text:一个问句 |
answer:text 为封闭式答句 |
command |
text:一条指令 |
action:op 与 args;或 refuse:reason |
约定:
- 一问一答:每条消息恰好得到一条回复,次序与发送次序一致;
observe是唯一没有回复的消息。调用栈由这个配对关系替代:测试者发一条,就停下来等被测者回一条。 - 拒绝也算回复:拒绝要带
reason。不回、回空、回一行不是 JSON 的内容,都按没有回复计(判定见 §4.2)。 - 指称用
id:线上出现的对象标识一律是id(observe的状态里、action的args里);问句与指令是自然语言描述,被测者负责把描述对应到自己那份模型里的id上。 - 被测者不必交出状态:它发出的
action就是对那一步操作的答复,测试者照这个调用执行;它自己那份认识不进入判定。 - 用什么语言实现都可以,只要按上表收发消息。
字段级规范(字段名、取值、边界情形)见 docs/设计协议.md §5。
用 Python 实现时不必自己收发 JSON:接口的完整约定见 shrdlu/testee/api.py,实现其中的 Testee 即可,基类负责与线上协议互转。用 --testee 模块:类(或 shrdlu-testee --impl 模块:类)运行。
from shrdlu.testee.api import Action, Observation, Refuse, Testee
class MyTestee(Testee):
def observe(self, observation: Observation) -> None: ...
def question(self, text: str): return "yes"
def command(self, text: str): return Action("put_on", ("a", "b"))| 要求 | 是否规定实现方式 |
|---|---|
| 让双方导出世界模型 | 否决:规定了实现方式(必须符号化) |
| 让双方回答受约束的问句 | 采纳:不规定实现方式(视觉模型可不建符号世界,看图回答即可) |
取 SHRDLU 自身的问句库,全部可由测试者机械求值。
实际实现的是封闭式的五类:
attr(属性:颜色/大小/形状)、rel(关系是否:on / on the table / clear / supported)、count、exists、all。下表中的位置类("绿块在哪里?" → 关系描述)与指称类("红块上面是什么?" → 对象描述)不做:它们的答句是开放式的,与 §6.4"格式需可机械比对"冲突。要做得先定出一套关系描述/指称的规范答句格式。
- 必须表态:拒答 / 含糊视为答错(防止用回避换取过关)。
- 格式需可机械比对(见 §6.4 的答句形态)。
七个命令行程序各管一段:
shrdlu-env:主持一次测试并判分,退出码给出结论。shrdlu-testee:把某个被测者实现当作程序来跑(自带一个最小示例;真正的实现由使用者自己写)。shrdlu-scene:生成场景,并检查一份场景是否合格。shrdlu-exam:给场景配上指令序列与题库,产出一份可用的考卷。shrdlu-play:逐步观看测试过程,每一步停下来显示两份世界状态,可暂停与插话。shrdlu-render:把场景画成等轴测图(SVG 或 PNG)。shrdlu-report:把整次测试写成一个自足的 HTML 文件。
代码分三层:
shrdlu/:共享的世界模型与线上协议(world/snapshot/protocol/ops/questions/settings/transport)。shrdlu/env/:测试者一侧(host主循环与逐段判分、answers判分、commands指令生成、scenario/generator/worldgen/authoring出题、render渲染、webreport报告、impls选实现,以及六个命令行程序cli/)。shrdlu/testee/:被测者一侧(api接口、example最小示例、nlp英文解析、registry实现名册,以及cli/testee_main.py)。
需要更细的内容时按上表查找;命令行用法与参数见 README-env.md,规范细节见 docs/设计协议.md,测试者的能力与保证见 docs/env功能介绍.md,编写被测者见 docs/testee开发需求.md。
写法:同一内容只在它的落点里完整陈述;其他文档最多一段摘要,并注明"见 X §N"。规范与现状冲突时,以 docs/设计协议.md 与代码为准。
| 文档 | 面向谁 | 公开范围 | 唯一落点(只在这里完整写) |
|---|---|---|---|
本文(README.md) |
想了解项目的人与维护者 | 公开 | 世界与判定的正式介绍、项目结构、设计决策与理由 |
README-env.md |
使用者 | 公开 | 七个程序如何运行:参数、退出码、例子与速查 |
docs/设计协议.md |
双方实现者 | 公开 | 规范:线上格式、世界模型、操作、问句、测试结构与判分规则、记录字段 |
docs/env功能介绍.md |
运行测试的人 | 公开 | 测试者提供什么、保证什么、不提供什么、稳定性承诺与边界 |
docs/testee开发需求.md |
编写被测者的人 | 公开 | 契约:按消息顺序要做什么、自由度、接入骨架、自测与常见错误 |
shrdlu.ini |
使用者 | 公开 | 常设参数与默认值(带注释);命令行只说这一次要什么 |
LICENSE |
使用者 | 公开 | MIT 许可证文本 |
公开的六份(本文、README-env.md、docs/设计协议.md、docs/env功能介绍.md、docs/testee开发需求.md 与 shrdlu.ini)构成对外正式材料。 |
本项目以 MIT 许可证发布,全文见 LICENSE。