> 把 LLM 限制成闭集选择,隐私就从"承诺"变成"结构"——顺带还能跑在 3B 上
一个自动填表的扩展,最自然的做法是把 {字段标签, 选项列表, 用户档案 JSON, 简历全文} 一起发给模型,问"这里该填什么"。
// TABLE_OF_CONTENTS
结论先行¶
一个自动填表的扩展,最自然的做法是把
{字段标签, 选项列表, 用户档案 JSON, 简历全文} 一起发给模型,问"这里该填什么"。
换成三个各自封闭的小任务之后,三件事同时变好了:
| 任务 | 输入 | 输出 |
|---|---|---|
| 分类 | 字段的元数据(标签、类型、name),不含档案 | 从一个封闭枚举里选一个 FieldType |
| 匹配 | 页面自己的选项列表 | 选其中之一 |
| 起草 | 问题文本 | 自由文本,但从不自动写入 |
- 可验证:闭集选择的输出要么在集合里要么不在,几乎没有幻觉空间; 开放式的"这里该填什么"两样都不占。
- 隐私变成结构性的:日常路径上的模型调用根本看不到档案和简历。 这不是一条政策,是一个可以从代码路径上读出来的事实。
- 提示词小到能跑本地 3B 模型,而且准确率够用——于是"本地优先"不是口号。
第 2 条是最值钱的:限制模型被要求做的事,可以把一个隐私属性从"我们承诺不会" 变成"它拿不到"。
为什么¶
"承诺型隐私"和"结构型隐私"的差别在于谁需要被信任:
- 承诺型:你要相信实现没写错、日志没记下、以后的版本不改、供应商不留存。 每一条都要持续维护,而且外部无法核验。
- 结构型:那条路径上根本没有那份数据。 审计的方法是读调用点的参数,一次就能确认,而且回归测试能钉住它。
所以这不只是"更安全一点",而是把一个需要长期信任的东西换成了一个能被检查的东西。
为什么闭集同时解决了幻觉¶
开放式生成里,"错误"是一个连续谱——似是而非的值没有自然的判别边界。 闭集里,错误是离散的:选项不在集合里 → 直接拒绝; 在集合里但置信度不够 → 跳过。 验证逻辑因此是可写的,而且很短。
这也是为什么第三个任务(起草 essay)被单独划出来并且从不自动写入—— 它是唯一真正开放的生成,所以它唯一的安全出口是"必须由人确认"。 开放生成不是不能用,是不能让它直接产生副作用。
小模型够用是结果,不是前提¶
提示词里没有简历全文和档案 JSON 之后,上下文从几千 token 降到几十。 不是"我们选了个小模型所以要压缩提示词",而是"任务切小之后,小模型就够了"。 顺序反过来会得到一个勉强能跑的系统;顺着走会得到一个本地优先是默认值的系统。
怎么用¶
给一个 LLM 功能做这项改造,按顺序问:
- 这个调用真正需要做的决定是什么? 写成一句话。 如果能写成"从 N 个候选里选一个",它就是闭集任务。
- 为了做这个决定,模型最少需要看到什么? 逐个字段问"去掉它还能决定吗"。能,就去掉。 这一步通常会发现大部分上下文是"顺手给的",不是必需的。
- 哪些输入是敏感的? 如果第 2 步之后它们已经不在参数里, 把这件事写成测试(断言该调用点的参数不含这些键), 否则它会在下一次重构里悄悄回来。
- 剩下的开放生成单独关起来,并确保它不能直接产生副作用。
出处¶
自己的一个自动填表浏览器扩展(私有未发布)的设计决策记录: "LLM 做分类和匹配,不做自由联想",以及它替换掉的那个 "把档案和简历一起发过去问该填什么"的原设计。 相关:两类错误代价差一个数量级时,"不做"必须是一等输出,不是兜底分支、 为噪声写的过滤器会精确杀掉最强的那条输入——显式指令必须走旁路。