学 AI 产品 · 专业 AI 产品经理播客第 4 章 · T4 解剖 DeepSeek Harness:一切皆插件的 Agent 底座 · EP 09
第 4 章 · EP 09

工具输出契约:值与展示分离 · 文件编辑的工程学:先读后写

时长 14:57音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场 · 同一个结果,两份呈现1:41
1:41一个值,三份投影1:38
3:19界面只认卡片,不认工具名1:26
4:45改展示和改值,二选一不许混1:35
6:21渲染长在哪 · 三家三种答案1:19
7:41一本观测账本2:19
10:00写入有路走,编辑一步不让1:57
11:58同一条规则,三种浓度1:28
13:27可带走的原则1:30
解读全文

dsh09 · 工具输出契约:值与展示分离 · 文件编辑的工程学:先读后写

  • 模块:T4 解剖 DeepSeek Harness:一切皆插件的 Agent 底座
  • 集页:https://xueai-podcast.pages.dev/t/dsh09/
  • 取材课节:工具输出契约:值与展示分离、文件编辑的工程学:先读后写(2 节,素材 9273 字)
  • 来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本页文字稿为音频的配套解读,内容取自小山学堂课程素材,属二次演绎版本。
行号与源码核对日期为 2026-08-13,随版本演进可能变化。

一、本集要解决什么

两件看起来不相干的事:同一个工具结果,模型看的和人看的凭什么可以不一样;以及智能体改文件之前,怎么确保它确实读过这个文件。

贯穿两件事的线索是同一招——把判断依据从"当下看起来是什么"换成"一份可验证的凭证"。

场景旧依据(不可验证)凭证(可验证)
工具结果渲染出来的文本schema 校验过的规范值
文件编辑模型"我记得读过"观测账本 + 版本凭证

凡是模型说"我记得是这样"的地方,都值得问一句:有没有东西能替它作证。


二、能力地图 · 一个值,三份投影

阶段职责位置
execute()产出规范 JSON 值(canonical value)工具本体
output.schema校验该值(强制)index.ts 第 211–219 行
render(args, value)纯投影 → 模型内容块(强制)同上
presentationMeta(args, value)纯投影 → 可回放 UI 数据(可选)同上
presentResult(args, result)纯投影 → 一张带 card 标签的卡片presentation.ts

关键约束:render 与 presentResult 都是纯函数,不做 I/O。因为它们在实时流式输出和会话日志回放两条路径上都要跑,跑出来必须一样。只要有一处偷偷读了当前时间或磁盘状态,回放就会与当时不一致。

value 只活在执行期

持久化的 tool/result 事件只存 content、error 和 meta,规范值从不落盘。

回放可以重现每一张卡片和每一段模型文本,却重建不了中间值。
—— docs/subsystems/tools.zh.md「结果仅承载产出」一节

三、UI 契约 · 只认 card,不认工具名

渲染意图(render intent)是带 card 标签的联合类型,值域六种:

generic · terminal · diff · read · search · web

客户端只需对 card 做一次 switch,不需要认识任何工具名。换一个搜索后端 provider,工具实现整个换掉,只要它照样产出 search 卡,UI 一行不用改。

代价与收益

渲染长在工具身上(CC 式)渲染翻译成数据(DSH 式)
表达力像素级控制受限于六种卡片
换客户端重写渲染层无需改动
回放需重新执行渲染代码直接重放数据

选型判据:你的渲染需要被"重新执行",还是只需要被"重新展示"?前者放工具里,后者做成数据。

截断必须亮牌

  • search 卡强制携带 truncated 与 total(presentation.ts 第 223–231 行);
  • read 卡携带 offset 与 totalLines,可画出"显示 N 行,共 M 行"。

很多误导人的界面,问题就出在把截断结果画得跟完整结果一个样。


四、值与展示二选一 · 写死在类型里

post-execute 放行时,换 content 和换 value 只能二选一。这不是文档约定,是类型定义(index.ts 第 593–600 行 PostToolDecision):

  • 分支一:允许带 content,同时把 value 的类型标成 never;
  • 分支二:允许带 value,把 content 标成 never;
  • 分支三:block,把纠正性反馈变成错误结果。

TypeScript 中 never 没有任何合法取值——想在一个决定里同时塞两个字段,编译器直接报错。

为什么不许同时换

动作层次后果
换 content展示层值保持原样,只改模型看到的文本
换 value数据层注册表拿新值重过 schema,重算 content 与 meta,三份投影同源

允许同时换 → 出现"文本说 A、值是 B"的分裂结果。

内容替换是展示策略。想对程序隐藏值的插件必须换值或 block——光改文本瞒不住直接拿值的程序。

两处兜底

  1. render 抛异常 / 值没过 schema / presentationMeta 产出非 JSON → 全部转成 JSON 安全的 isError(第 1793 行起 createSuccessResult);
  2. 第三方工具没写 presentCall / presentResult → 客户端回退 generic 卡(第 79–83 行注释)。

五、文件编辑 · 观测账本

三件套(docs/tool-catalog.zh.md):read(窗口化带行号)、edit(字面量替换)、write(整文件创建或覆盖)。

账本状态只有三种:

状态含义
未见表里压根没这个文件的条目
present@vN读到过,读到的是版本 vN(后端签发的不透明新鲜度凭证)
absent确认过这个路径不存在(如 read 扑空)

每次 read / write / edit 成功后,工具发出 fs/observed 事件,插件同步记账。

单槽瀑布 vs 多槽瀑布

多槽(上一集 pre-execute)单槽(本集 fs/write-intent)
语义每位监听器都能表态、能短路只有第一位说话,独占决策权
适用放行这类顺序敏感判断守卫条件这类必须一人拍板

关键:插件只对账本给出守卫条件,真正的检查由后端在原子临界区完成——先验版本再匹配再替换。从查完到写入之间的空隙被消灭了。

凡是"先检查再执行"的逻辑,都值得问一句:中间那段空隙有没有人管。

整个策略插件不到 140 行,核心就是 writeIntent(第 61–71 行)与 editIntent(第 78–88 行)两个查账函数。


六、write 有路走,edit 一步不让

工具未读过读过
writecreateIfAbsent:不存在则创建,存在则拒绝(FS_NOT_OBSERVED)replaceIfVersion:版本对上才替换
edit直接 FS_NOT_OBSERVED;账本记 absent → FS_NOT_FOUND带版本守卫上路

翻译成人话:新建文件不用先读,覆盖别人的文件不行。

错误顺序即可用性

版本检查排在字面量匹配之前,所以拿过期内容编辑报的是 FS_STALE_VERSION,不会退化成误导性的匹配失败。

如果顺序反过来,模型拿到"没找到匹配",会以为自己写错了字符串而反复重试,永远想不到真正原因是文件早被人改过了。

匹配那关:old_string 必须恰好命中一次——多处报 FS_AMBIGUOUS_EDIT,零处报 FS_EDIT_NOT_FOUND,除非显式 replace_all。匹配、行尾处理、陈旧检查、原子替换全在同一临界区内完成(docs/subsystems/filesystem.zh.md 第 151 行)。

三个易漏细节

  1. 防线可拔:卸掉插件,write/edit 退回无条件裸行为,工具 schema 一字不变——工具只分发事件,从不直接调策略;
  2. 授权只看新鲜度:不分整读还是窗口读,只要文件没变,读 10 行也能授权整个文件的 edit;
  3. 条件注册:部署没有 ctx.attachments 就不注册 read_image(第 718 行)。

七、横向对比 · 同一条规则,三种浓度

产品浓度机制证据
DeepSeek Harness版本凭证版本对不上 → FS_STALE_VERSION,物理上不给写fs-observation-policy 插件
Claude Code运行时检查规则写进工具说明书,原文含"this tool will error"study/chapters/14-all-prompts.md 第 1124 行
Grok Build提示语匹配失败时在报错文案里加一句"建议重新读一遍"search_replace/mod.rs 第 111–113 行

CC 的差别在挂载位置:检查长在 FileEditTool 自己身上;DSH 抽成独立插件,read / edit / write / str_replace_editor 四个工具共享同一本账,工具本体零权限代码。

Grok 留下了斗争痕迹:配置里 skip_read_before_edit 字段注释标着"已废弃的运行时空操作",说明先读后写曾是硬开关、后来松了绑。

公平地说:Grok 在编码坑那关备了 unicode_normalized_fallback(第 103–110 行),智能引号、长横线这类肉眼难辨的字符匹配失败时可归一化重试;DSH 的 edit 目前只按行尾规范化后精确匹配。各有取舍。


八、审查清单

设计工具输出契约时:

  1. 规范值是否带强制 schema?
  2. 模型文本与 UI 卡片是否都是该值的纯函数投影?
  3. 投影函数是否真的无 I/O(回放要重跑)?
  4. UI 是否只认 card 标签而不认工具名?
  5. 截断是否强制亮牌(truncated / total)?
  6. 换 content 与换 value 的二选一是否写进类型而非文档?

设计文件编辑防线时:

  1. 是否有可验证的"读过"凭证,而不是靠模型自述?
  2. 版本检查是否排在字面量匹配之前?
  3. 检查与执行是否在同一原子临界区内?
  4. 防线是否可拔(工具零权限代码)?

九、约束说明

  • 行号口径:基于 2026-08-13 对 deepseek-harness-master 本地仓库核对。
  • 演示口径:课程中的交互演示(卡片、版本号、文件内容)为教学化模拟;投影关系与判定逻辑对应真实源码。
  • 证据边界:关于 Grok 是否具备统一卡片词汇机制,已核对材料未见等价物,本条结论基于已公开证据保留;CC 对过期读取的检测实现,已核对书稿未展示细节,同样保留。
  • 本页用途:文字稿仅供阅读,音频以集页播放器为准;题目页内容不进入音频。

十、实践提示

提示一 · 值是源头,其余都是投影

先把规范值定义清楚,再让模型文本和 UI 卡片都从它投影出来。反过来做,你会得到两份需要人工保证一致的数据——而它们迟早会不一致。

提示二 · 投影必须是纯函数

只要它要在回放时重跑一次,任何隐藏输入(时间、磁盘、随机数)都会变成回放的不确定性。把"纯"写进注释,再靠 review 守住它。

提示三 · 能用类型表达的禁令,别写成文档约定

换 content 与换 value 二选一,写在类型里是编译器报错,写在文档里是评审时吵架。类型能消灭的问题类别,不要留给运行时。

提示四 · 防线的浓度要显式选择

提示语浓度、运行时检查浓度、版本凭证浓度——选错不是强度差异,是可靠性量级差异。先想清楚这条规则失守一次的代价有多大,再决定用哪一级。

提示五 · 错误的指向性就是可用性

版本检查排在匹配之前,报"陈旧"而非"没找到",模型才能一次性做对动作。设计错误码时问一句:拿到这个错误的模型,会采取正确的下一个动作吗?


十一、课堂练习(附推导)

接入一个第三方 sql_query 工具,查询返回 1200 行但只保留前 50 行。写出:value 的 schema 大致长什么样;render 给模型的文本要不要包含全部 50 行;presentResult 选六种卡片中的哪一种,截断信息放哪。最后一问:安全插件想对模型隐藏手机号列,该换 content 还是换 value?
  1. schema:至少包含 rows、total、truncated 三个字段。rows 是保留下来的 50 行,total 是 1200,truncated 为 true。
  2. render 文本:给模型的文本应当截断并声明截断(例如给出前若干行 + "共 1200 行,仅展示 50 行"),不应把 50 行全部塞进上下文——体量必须有人管,且必须让模型知道结果不完整。
  3. 卡片选择:可选 generic 或 search 类卡片。截断信息必须放在卡片字段里(truncated / total),而不是只写在文本里——这样 UI 永远不会把砍过的结果画成完整结果。
  4. 换 content 还是换 value:换 value。因为 Code Mode 里的程序直接消费规范值,只改文本瞒不住它们;换 value 才会让注册表重过 schema 并重算三份投影。

十二、Takeaway

  • 工具产出一个带 schema 的值,模型文本和 UI 卡片都是它的纯函数投影;
  • 改哪份投影就走哪个通道,二选一不许混,且这条禁令写在类型里;
  • UI 只认 card 标签不认工具名,换实现不动界面;
  • 持久化只存投影不存值:回放能复现所有展示,值本身随执行结束消失;
  • 先读后写 = 观测账本 + 版本凭证:没读过 FS_NOT_OBSERVED,读过但被外部改动 FS_STALE_VERSION;判定只看账本不看运气。

来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)

本内容改编自小山学堂课程素材,为二次演绎版本。