把合法集合告诉模型:有限论域缺口的两面与设计空间
状态:Research Note 版本:
0.3.0日期:2026-09-08 研究问题:系统已经知道哪些参数值、哪些标识符是合法的,为什么模型仍然可以写出不存在的规格与句柄?把“合法集合”表达给模型有哪些做法,各自的代价是什么,Aira 该走哪条? 起因:标准件 MVP 暴露 AMIR §6.1 的“可变符号参数”缺口;退役 provider projection 时发现 RFC-0002 §13.2.1 的“逐轮收窄”是同一缺口的模型接口一面。 证据来源:当前工作树实测(packages/aira-contracts、apps/server、apps/web)+ 外部论文与供应商文档。 0.2.0 修订:追加编译器实测(§2.3、§4.1),据此关闭待验证 #4、重新判定方案 D 与 E。 0.3.0 修订:方案 E 已落地(§2.4),replace-program不再接收节点标识符;关闭待验证 #7。
1. 结论摘要
Section titled “1. 结论摘要”- 缺口不是“我们不知道合法集合”,而是“我们知道却用错了信道”。 网关每轮已经把完整的模型上下文
(
revisionId、全部nodes、全部parameters及其值与 range)发给模型——见packages/aira-cad-tools/src/tools.ts的exactModelContext。合法标识符已经在提示里,只是以数据形式, 不是以类型形式。[实测] - 更根本的一层:计划工具当前
strict: false,schema 根本不是解码期约束。 连已有的枚举 (unit: mm|deg|scalar、side: inside|outside)都只是事后校验,不参与生成。所以 “把句柄物化成 enum”这条路,在打开严格模式之前是无效的。[实测] - 严格模式打不开是有原因的:计划 schema 用了
pattern(7)、minLength(5)、maxLength(4)、minItems(4)、maxItems(6)、uniqueItems(3)、exclusiveMinimum(1)、minimum(5)、maximum(4)、anyOf(2)。 主流严格结构化输出只支持 JSON Schema 的一个受限子集,这些关键字大多不在其中。[实测 + 供应商文档] - 代价是真实的:计划被拒后走
needs-repair,增长基准 规定“单次任务最多 2 次修复,第 3 份计划被拒即失败”。一次编造的标识符就消耗掉三分之一的修复预算。[实测] - 外部证据给出一个反直觉的约束:格式限制会损害推理。Tam 等的 Let Me Speak Freely?(arXiv 2408.02442)实测在格式限制下 LLM 推理能力显著下降, 且限制越严退化越大。所以“把所有合法值塞进 schema”不是无代价的胜利。[论文结论]
- 另一条外部证据指向更便宜的解法:Lee 等的 Don’t Adapt Small Language Models for Tools; Adapt Tool Schemas to the Models(ACL 2026) 报告仅通过改工具组件的命名使其贴近预训练分布,整体提升至多 17%, 而schema 错配错误(模型幻觉出 schema 里不存在的名字)下降 80%。这与本缺口是同一失败模式。[论文结论]
- 实测追加一:一个文档至多一个
exact.program节点,这是编译器已经强制的。createProgramPatch在document.bodies非空时拒绝build-program,理由是“在既有实体上新建程序会让它的模型图悬空”。 因此方案 E(结构性消除句柄引用)成立——replace-program的nodeId是可推导的。[实测,已落地] 该字段已于 2026-09-08 从计划 schema 移除:宿主自行解析那个唯一节点,模型不再有机会写错它(§2.4)。 - 实测追加二:方案 D 不是“未做”,是“做了一半,且有一处报错方向相反”。
26 个
EXACT_CAD_PLAN_*诊断里有 5 个已经回传合法集合或可操作细节,其中BUILD_REQUIRES_EMPTY_MODEL会列出现有程序节点 id 与全部可编辑参数。但模型真正编造标识符的三条路径都没有沿用这个写法, 其中set-parameters遇到不存在的parameterId时抛的是PARAMETER_NOT_NUMERIC—— 把“名字不存在”报成“类型不对”,会把修复引向错误方向。[实测,已修] - 判定:不走“把全部合法句柄物化成大枚举”这条路。分三层解决—— ① 命名与形状先改(最便宜、已有证据支持);② 把小而稳定的论域进 schema(标准件规格、单位、有限模式); ③ 大而易变的句柄集合留在上下文,但改用可校验的引用形式,并让诊断把合法集合当场回给模型。
2. 缺口的准确形式
Section titled “2. 缺口的准确形式”系统里存在两类“有限集合”,都无法表达给模型:
| 面 | 合法集合 | 当前形态 | 后果 |
|---|---|---|---|
| 参数侧 | 标准件规格 M1.6…M64、材料牌号、螺纹标记、型材系列 |
只存在于程序源码字面量与 TS 类型 | 改规格 = 改源码,不是改参数(AMIR §6.1) |
| 句柄侧 | 当前 Revision 的 nodeId、parameterId、namedSubEntities 名 |
以 JSON 上下文发给模型,schema 里是自由字符串 | 模型可编造不存在的 id,只能事后拒绝 |
两者同源:AMIR 支持“可变的数值”与“不可变的符号”,不支持“可变的符号 + 有限论域”
(Parameter.type 只有 7 种数值/布尔类型,range 只表达连续区间;ConstAtom 有符号枚举但不可变)。
2.1 实测:计划里哪些字段是自由字符串
Section titled “2.1 实测:计划里哪些字段是自由字符串”EXACT_CAD_PLAN_SCHEMA 中以 id = { type: 'string', pattern: '^[A-Za-z0-9._:-]+$' } 定义的字段,
本文写作时有四个:requestId、nodeId(replace-program)、parameterId 与 parameter(set-parameters)。
其中 nodeId 已按 §2.4 取消,下文对它的分析保留为该判定的推导过程。
另有两组名称数组同样是自由字符串(^[A-Za-z][A-Za-z0-9_.:-]{0,127}$):affects(守恒声明)与
expectation.named(创建期的量测声明)。
这些名称必须取自当前 evaluation 的 namedSubEntities 或当前文档的节点/参数表——这个集合在提交计划的那一刻
是完全确定的,宿主知道,模型也被告知了,但 schema 不知道。
2.2 实测:合法集合已经发给模型了
Section titled “2.2 实测:合法集合已经发给模型了”exactModelContext 的注释原文写明它投影的正是“计划能够引用的东西”:identity、roots、bodies、
带 source 的 nodes、带值的 parameters、以及 evaluation。也就是说:
- 模型收到了全部合法
nodeId与parameterId; - 模型收到了每个参数的
type、value、unit、range; - 但计划 schema 对这些字段只说“是一个符合正则的字符串”。
所以这不是信息缺失问题,是信息形态问题。 同一份事实,以数据形式给了模型(可被忽略), 没有以约束形式给解码器(不可被违反)。
2.3 实测:句柄集合小到不需要枚举——它只有一个元素
Section titled “2.3 实测:句柄集合小到不需要枚举——它只有一个元素”apps/web/src/authoring/exact-cad-plan.ts 的 createProgramPatch 在文档已有 body 时拒绝 build-program,
注释原文说明理由是“在既有实体上新建程序会让它的模型图悬空”。也就是说每个文档至多一个 exact.program 节点。
这个事实改变了整个问题的量级:replace-program 的 nodeId 不是“从一个大集合里选一个”,
而是“填写那个唯一确定的值”。让模型填一个它必须从上下文抄来、且只有一个正确答案的字段,
是纯粹的失败机会——方案 E(消除该字段)优于任何约束该字段的做法。
参数侧不同:p.* 参数数量随模型声明增长,是真正的多元素集合,方案 E 不适用,只能靠 D/B/C。
2.4 落地:replace-program 不再接收节点标识符
Section titled “2.4 落地:replace-program 不再接收节点标识符”2026-09-08 的变更把上一节的推论执行到底。三处同时改:
| 位置 | 变更 |
|---|---|
packages/aira-contracts/src/exact-cad-plan.ts |
replace-program 的两个变体(source 与 edits)都从 required 与 properties 去掉 nodeId;additionalProperties: false 因此把仍然发送该字段的旧计划判为不合法 |
apps/web/src/authoring/exact-cad-plan.ts |
编译器改为自己解析唯一的 exact.program 节点,node.put 与守恒报告都用解析结果;PROGRAM_NOT_FOUND 从“你给的 id 不对”变成“这个模型没有程序,改发 build-program” |
packages/aira-cad-tools/src/tools.ts、apps/web/src/ai/agent-client.ts |
指令与上下文提示同步改写为“它总是作用于本模型唯一的程序,不接收节点标识符” |
守恒报告的 ExactCadConservationReport.nodeId 保留,语义从“模型声明的目标”改为“宿主解析到的目标”——
它是诊断在说明自己量测了什么,不是计划的输入。核查确认它的 12 个消费者(网关输出投影、
工作台执行、守恒摘要等)全部只做透传与展示,没有一个要求这个值来自模型。
取消字段本身会制造一个新的坏诊断,必须一并修掉。 计划步骤是 9 个变体的 anyOf,
additionalProperties: false 意味着一个多余字段会让九个变体同时失败,
而 ajv.errorsText(allErrors) 会把每个变体的抱怨全部回放——实测 54 条子句,
其中没有任何一条说“把 nodeId 删掉”。也就是说:本变更之后最可能发生的错误
(模型带着旧习惯继续发 nodeId)恰好落进全仓库最差的诊断路径。这与 §4.1 的判据直接冲突。
修法不是给 nodeId 开特例,而是补一条通用规则:被所有变体拒绝的字段,就不属于计划语言;
被部分变体接受的字段(如 edits)拒绝次数更少,计数本身就把两者精确分开。同一条计数规则
顺带覆盖“编造步骤 kind”——此时每个变体都在 kind 上报 const 失配,于是回报合法 kind 列表
而不是把该步骤的每个字段都说成未知。实测结果:
| 输入 | 修订前 | 修订后 |
|---|---|---|
replace-program + 多余 nodeId |
54 条子句,不指向修复 | plan/steps/0.nodeId is a field of no step kind; remove it and resend. 并说明 replace-program 不接收它 |
字段拼写错误(affcts) |
同上 | 直接点名 affcts |
编造的 kind(bore-hole) |
同上 | 回报 8 个合法 kind |
缺少必填 affects |
54 条子句 | 不变——缺字段无法用同一条计数规则识别,留作后续 |
顺带消掉一处重复:BUILD_REQUIRES_EMPTY_MODEL 原本内联重建了一份可编辑参数清单,
与 PARAMETER_NOT_FOUND 用的 numericParameterList 逻辑逐字相同,现在合并为同一个函数。
该诊断也不再回报程序节点 id——模型已经无处可用它。
净效果:计划里的自由标识符字段从 4 个降到 3 个(requestId、parameterId、parameter),
其中 requestId 由宿主而非模型的先验决定其正确性,真正需要模型从上下文抄写的只剩参数侧两个。
3. 更根本的一层:schema 现在不是解码期约束
Section titled “3. 更根本的一层:schema 现在不是解码期约束”exactCadPlanTool 声明 strict: false,其 inputSchema 只带一个 validate 回调——
schema 的作用是生成之后校验,不是生成之时约束。
这意味着:即使现在就把 nodeId 写成 enum: ['n.program', 'n.finish'],模型也照样可以写别的,
只是会在校验阶段被拒。“物化成 enum”要产生实效,前提是严格模式(解码期 grammar masking)。
严格结构化输出的工作方式是编译一份 grammar,在解码时屏蔽任何会破坏 schema 的 token;
它保证模型不会产出无效枚举值。但它只支持 JSON Schema 的一个受限子集,而计划 schema 大量使用
子集外的关键字(§1.3 的计数)。因此打开严格模式需要先简化 schema,这本身是一次有取舍的改动:
被移除的 pattern/minItems/maximum 等约束会从“解码期不可违反”退回“事后校验”,
净效果取决于哪些约束更重要。
[待验证] DeepSeek 在当前 AI SDK 路径上对严格模式的实际支持程度未实测。多数供应商提供 OpenAI 兼容端点,但是否真正执行 strict 与 schema 约束并不一致,需要按实际 provider 实测, 不能按文档推定。
4. 代价:一次编造的标识符值多少
Section titled “4. 代价:一次编造的标识符值多少”计划被宿主拒绝后进入 needs-repair,模型得到诊断并重写。增长基准的判据是
“单次任务最多 2 次修复,第 3 份计划被拒即失败”。所以:
- 一次编造的
nodeId消耗 1/3 的修复预算; - 修复轮要重新发送完整上下文与重新推理,成本按 AI 通道计量,是当前产品目标里被明确测量的量;
- 失败模式还会污染诊断质量——模型看到的是“这个 id 不存在”,而不是“合法的是这几个”。
[待验证] 现有基准结果里没有按“失败原因”分类的统计,因此编造标识符在全部失败中的占比未知。 这是决定投入优先级的关键数字,应在下一轮基准运行时单独记录。
4.1 实测:诊断质量本身是变量,而且曾经指错方向
Section titled “4.1 实测:诊断质量本身是变量,而且曾经指错方向”编译器的 26 个 EXACT_CAD_PLAN_* 诊断分成两类:
后续更新(2026-09-09):下表记录当日状态。REWRITE_DISCARDS_SOURCE 的源码行相似度检查已退役;
当前完整源码与片段编辑共用几何约束,保留要求通过 affects 和 expect.preserve 表达,见工作进度 §7。
| 类别 | 例子 | 是否给出修复所需信息 |
|---|---|---|
| 已回传合法集合/可操作细节(5 个) | BUILD_REQUIRES_EMPTY_MODEL(列出程序节点 id 与全部可编辑参数)、SOURCE_EDIT_NOT_FOUND(给出最接近的源码)、SOURCE_EDIT_AMBIGUOUS(给出出现次数)、REWRITE_DISCARDS_SOURCE(给出保留行数)、PARAMETER_EXISTS |
是 |
| 模型编造标识符的三条路径 | PROGRAM_NOT_FOUND、PARAMETER_NOT_NUMERIC、CREATED_PARAMETER_UNKNOWN |
修订前:否 |
其中一处是方向性错误而非信息不足:
const parameter = document.parameters[parameterId];if (!parameter || parameter.type === 'Bool') throw new Error(`EXACT_CAD_PLAN_PARAMETER_NOT_NUMERIC:${parameterId}`);模型写了一个不存在的 parameterId,得到的诊断是“这个参数不是数值型”。模型据此会去换值或换类型,
而真正的问题是名字不存在。这类诊断不只是浪费一轮修复,它把修复引向错误方向。
三处已于 2026-09-08 按 BUILD_REQUIRES_EMPTY_MODEL 的既有写法修正:不存在的名字单列
PARAMETER_NOT_FOUND 并附上本模型声明的全部可编辑参数(label=id);PROGRAM_NOT_FOUND 直接给出
该文档唯一的程序节点 id,或在没有程序节点时告诉模型改用 build-program;
CREATED_PARAMETER_UNKNOWN 列出该步骤实际声明的参数名。
这说明方案 D 的成本比预估更低:模式已经存在于同一个文件里,缺的只是把它用在最需要的三处。
5. 外部先例与它们给出的约束
Section titled “5. 外部先例与它们给出的约束”5.1 约束解码有效,但有代价
Section titled “5.1 约束解码有效,但有代价”严格结构化输出通过编译 grammar、在解码时屏蔽非法 token 来保证合规,因此模型不可能产出无效枚举值。 代价是:schema 复杂度直接转化为延迟,深层嵌套、长描述与大枚举集合都会增加开销, 并提高模型拒答或截断的概率。
5.2 格式限制会损害推理
Section titled “5.2 格式限制会损害推理”Let Me Speak Freely?(Tam 等,arXiv 2408.02442)的核心结论: 在格式限制下 LLM 推理能力显著下降,且限制越严格退化越大。
这条对本缺口是直接的反向约束:把“当前 Revision 的全部合法句柄”物化成一个大枚举, 既增加延迟又收紧格式,可能把“编造标识符”换成“整体建模质量下降”。换来的未必划算。
5.3 引擎之间的覆盖差异是真实的
Section titled “5.3 引擎之间的覆盖差异是真实的”JSONSchemaBench(Geng 等,2025)用 1 万个真实 schema 评测了 Guidance、Outlines、llama.cpp、XGrammar、OpenAI、Gemini 六个系统,三个维度(效率、约束类型覆盖、输出质量) 上差异显著;论文明确指出“保证合规”与“实践中有效”之间存在认知空白。
结论:不能假设“写进 schema 就一定被执行”。 任何依赖严格模式的设计都必须对实际 provider 实测, 并保留宿主侧权威校验作为唯一真实边界——这也正是 RFC-0002 原规则 2 的要求(投影只能收窄,不构成有效性证据)。
5.4 更便宜的杠杆:改命名,不是加约束
Section titled “5.4 更便宜的杠杆:改命名,不是加约束”Adapt Tool Schemas to the Models(Lee 等,ACL 2026)提出 PA-Tool: 用“peakedness”(预训练熟悉度信号)为工具组件重新命名,使其贴近模型预训练分布。结果: MetaTool 与 RoTBench 上整体提升至多 17%,而schema 错配错误下降 80%—— schema 错配的定义正是“模型幻觉出 schema 里不存在的名字”,与本缺口是同一失败模式。
这条给出一个重要提示:在加约束之前,先看命名与形状是否让模型容易做对。
当前的 n.program、p.width 这类 id 是否对模型友好、replace-program 与 set-parameters 的
职责切分是否符合模型的先验,都是零成本可测的。
6. 设计空间
Section titled “6. 设计空间”| 方案 | 做法 | 有效前提 | 代价 | 判定 |
|---|---|---|---|---|
| A. 全量物化 | 把当前合法句柄集合逐轮编译进 schema 的 enum |
严格模式可用 | 大枚举增加延迟、收紧格式、按 §5.2 可能损害推理;schema 需逐轮重建 | 不采用(作为默认) |
| B. 小论域进 schema | 只把小且稳定的集合进 schema:单位、模式枚举、标准件规格 | 严格模式可用 | 极小;这些集合本就是常量 | 采用 |
| C. 命名与形状对齐 | 按 §5.4 调整 id 与步骤命名、精简 schema 关键字以便开启严格模式 | 无 | 一次性重构;需实测 | 优先采用 |
| D. 诊断回传合法集合 | 拒绝时不只说“不存在”,把当前合法集合与最接近的候选一并返回 | 无 | 极小 | 已做(§4.1,三处;其余诊断按需跟进) |
| E. 结构性消除 | 让计划不需要引用句柄:replace-program 作用于唯一的程序节点,set-parameters 用参数名而非 id |
无——唯一性已由编译器强制(§2.3) | 一次 schema 变更 + 计划编译器改动 | 已做(节点侧,§2.4);参数侧待验证 label 唯一性 |
方案 E 现在有了实证依据(§2.3):文档至多一个 exact.program 节点是编译器已经强制的不变量,
所以 replace-program 的 nodeId 是可推导的,字段本身可以取消——不能编造一个不存在的字段。
消除引用比约束引用更彻底,且不依赖严格模式、不增加 schema 复杂度、不触发 §5.2 的推理退化。
节点侧已经执行完(§2.4):代价确实只是一次 schema 变更加编译器改动,且核查确认该字段的四个消费点
(节点查找、两条诊断、node.put、守恒报告)全部可由宿主推导,没有第二个消费者依赖模型显式指定。
[待验证] set-parameters 的 parameterId 能否同样以参数名(label)取代——参数名由模型自己在
build-program/replace-program 里声明,可能比宿主生成的 p.<runId>.<n> 更贴近模型的先验,
但需先确认 label 在文档内唯一。这一条不能照搬节点侧的结论:节点侧成立是因为候选只有一个,
参数侧候选是多个,消除的是“id 形状”而不是“选择本身”。
7. 判定与顺序
Section titled “7. 判定与顺序”D 先做→ 已做:三处编造标识符路径的诊断现在回传合法集合,其中一处方向性错误一并修正(§4.1)。 其余 21 个诊断按实际失败分布逐步跟进,不预先统一改写。- C 次之:命名对齐 + schema 关键字精简。后者是打开严格模式的前提,前者按 §5.4 是已被量化的杠杆。 两者都要先测 provider 的严格模式实际行为(§3 的待验证项)。
- B 随参数系统一起:
Parameter增加符号类型与choices之后,标准件规格这类小论域自然进 schema。 这条与 AMIR §6.1 的裁决是同一件事。 E 提到句柄侧首选→ 节点侧已做(§2.4):replace-program不再接收nodeId。set-parameters的参数名替代仍待验证 label 唯一性,且性质不同(消除 id 形状,不消除选择本身)。- A 不作为默认:仅在 D/C/E 之后仍有可测量的编造率时,对特定小集合局部启用。
边界:以上任何方案都不改变权威边界——宿主校验、守恒检查与事务校验始终是唯一的有效性判据, 模型侧约束永远只是前置过滤(RFC-0002 原规则 2)。
8. 待验证
Section titled “8. 待验证”- [待验证] DeepSeek 在当前 AI SDK 路径上是否真正执行 strict 与 schema 约束。
- [待验证] 编造标识符在全部计划拒绝中的占比——基准需按失败原因分类记录。
- [待验证] 精简 schema 关键字以开启严格模式的净收益:失去的事后约束 vs 得到的解码期约束。
文档是否恒为单→ 已确认:编译器在文档已有 body 时拒绝exact.program节点build-program, 该不变量是被强制的,不是巧合(§2.3)。- [待验证] §5.4 的 peakedness 方法在本项目 id 命名上的可迁移性;论文对象是小模型与通用工具集, 与本项目的单一专用工具不完全同构。
- [待验证] 参数
label在文档内是否唯一(决定set-parameters能否用名字取代parameterId)。 取消→ 已确认无:四个消费点全部可由宿主 推导,守恒报告的同名字段改由宿主填写(§2.4)。replace-program.nodeId后是否有非模型消费者依赖它旧计划形状的过渡成本→ 已处理:多余字段现在得到点名诊断而不是 54 条子句(§2.4)。 仍**[待验证]**的是频次:下一轮基准应记录“多余 nodeId”这一类拒绝的实际占比,用来判断 是否需要更强的手段(例如在指令里显式说明该字段已取消)。- [待验证] 缺少必填字段仍走
errorsText回放路径。同一条“被所有变体拒绝”的计数规则对required不成立(affects只被 2 个变体要求),需要按kind定位变体才能改善。