委派就是一次接口调用:主窗口发一段指令,收回一段结果。所以接口设计那几条原则全都适用。 五份回执实测:两份 JSON 能被直接解析,三份散文不行。 回的应该是「主窗口需要的字段」,不是「子智能体经历的整个过程」。
上一节算账,这一节看设计。委派这件事在软件工程里有个现成的对应物:模块之间的调用契约。 这一节和第 3 章 3.12 节是姊妹篇:那一节拿四条原则过技能,这一节拿它们过委派。
一、委派就是接口
接口有两端:调用方给什么、被调方还什么。委派也一样——指令是入参,回执是返回值。 所以接口设计的老问题在这里全都出现:入参够不够、返回值稳不稳、错误怎么表达。 入参不够:子智能体不知道项目规矩,产出不合规(4.5 节)。
返回值不稳:它回一段散文,下游得再解析一遍(这一节)。 错误没表达:它卡住了却不说,主窗口以为它在干活(4.8 节)。 三个问题都是接口问题,不是「模型不够聪明」的问题。
二、接口隔离:只回需要的字段
接口隔离的原则是「不该让调用方依赖它不需要的东西」。放到委派上:回执只写主窗口要用的那几个字段。 子智能体很容易把过程也写回来:「我先看了 A 文件,发现……然后我又看了 B……」这些对主窗口没用, 它只需要结论。 演示里五份回执:两份 JSON、三份散文。JSON 那两份能被程序直接校验字段、直接进表格、直接判阈值。
散文那三份,主窗口得先读懂、再判断、万一读错还得回问。同样的信息,一个要人看,一个不用。 所以定义子智能体的时候,正文最后必须写清返回格式,写得越死越好:几个字段、每个字段什么类型、 没有就写零。
三、依赖倒置:指令依赖抽象
如果每个子智能体的指令都要把项目规范抄一遍,那就等于「调用方依赖了具体实现」。 好的做法是让指令依赖一份规范(技能,或者一份放在仓库里的文档),而不是规范里的每一条。 这样规范改了,指令不用改;新的子智能体加进来,也不用重新写一遍规范。
实操上常有个折中:指令里写「按 tests 目录下那份约定做」,同时把最容易搞错的两三条抄进去。 抄那两三条是可以接受的,因为它们是「这个子智能体常犯的错」,不是「全部规范」。
四、单一职责:一个子智能体一件事
和第 3 章 3.12 节一样:一个子智能体干四五件事,它的 description 就没法写清,tools 也必然给宽。 演示里那份返四个字段的回执,对应的是「只做一件事」的子智能体——字段不多,是因为职责不多。 职责一多,回执字段就跟着涨:改文件的、跑测试的、写报告的活混在一起,回执里就得同时有改动清单、 测试结果、报告正文。
那时候主窗口收到的就不是「一条结论」,而是「一份报告」——委派的意义也就没了。
五、一个能直接抄的回执格式
四个字段够了:done(做完了没有)、files(改了哪些文件)、issues(发现几条问题)、 blocked(有没有卡住、卡在哪)。 done 是布尔,让主窗口一眼判断要不要继续;files 是字符串数组,方便去重和比对; issues 是整数,方便判阈值。 blocked 是字符串,没卡住写空。 这个字段最容易被漏,而它恰恰是「异常路径」的表达——没有它,卡住和干完在回执上长得一样。
多写的字段主窗口不会看,反而让它更容易漏掉关键的那一个。接口窄一点,比宽一点可靠。 如果确实需要明细,让它写进文件、回执里给路径。这样明细在磁盘上,不在主窗口里。
六、动手 Demo:五个子智能体的回执,几个能被下游直接吃
五个子智能体,五个回执:两个 JSON、三个散文。逐个试着解析,再看散文回执的隐性成本。 最后给一个能直接抄的四字段格式。
for nm, txt, kind in RETURNS:
try:
d = json.loads(txt)
got = u"可解析,%d 个字段" % len(d)
except Exception:
got = u"不可解析,%d 字" % len(txt)
实跑输出:
--------------------------------------------------------------------
五个子智能体的回执,几个能被下游直接吃
--------------------------------------------------------------------
委派是接口:主窗口发一段指令,收回一段结果。接口设计得好不好,看收回来的东西能不能被直接使用。
① 五份回执,试解析
--------------------------------------------------------------------
| 子智能体 | 返回格式 | token | 下游能不能直接用 |
|---|---|---:|---:|
| finder | JSON | 10 | 可解析,3 个字段 |
| fixer | 散文 | 46 | 不可解析,37 字 |
| tester | JSON | 10 | 可解析,3 个字段 |
| reporter | 散文 | 47 | 不可解析,32 字 |
| merger | 散文 | 33 | 不可解析,23 字 |
五个里只有 2 个能被直接解析。
另外三个是散文——下游要么用正则硬抠,要么再派一个模型去读,两条路都要花钱。
② 散文回执的隐性成本
--------------------------------------------------------------------
散文回执到了主窗口,主窗口得先「读懂」它。
这一步不免费:读一遍是 token,判断数字对不对又是 token,万一读错还得回问。
而 JSON 回执可以被程序直接校验字段、直接进表格、直接判阈值。同样的信息,一个要人看,一个不用。
③ 接口隔离在委派里是什么意思
--------------------------------------------------------------------
接口隔离的原则是「不该让调用方依赖它不需要的东西」。
放到委派上就是:回的应该是「主窗口需要的那几个字段」,不是「子智能体经历的整个过程」。
子智能体很容易把过程也写回来:「我先看了 A 文件,
发现……然后我又看了 B……」这些对主窗口没用——它只需要结论。
所以定义子智能体的时候,正文最后必须写清返回格式。
写得越死越好:三个字段、每个字段什么类型、没有就写零。
④ 一个能直接抄的返回格式
--------------------------------------------------------------------
| 字段 | 含义 | 类型 |
|---|---|---|
| done | 做完了没有 | true/false |
| files | 改了哪些文件 | 字符串数组 |
| issues | 发现几条问题 | 整数 |
| blocked | 有没有卡住、卡在哪 | 字符串,没卡住写空 |
! 四个字段够了。多写的字段主窗口不会看,反而让它更容易漏掉关键的那一个。
接口窄一点,比宽一点可靠。
①五份回执里只有两份能被直接解析。 另外三份是散文——下游要么用正则硬抠,要么再派一个模型去读,两条路都要花钱。 ②散文回执的隐性成本:主窗口得先「读懂」它。 读一遍是 token,判断数字对不对又是 token,万一读错还得回问。 而 JSON 回执能被程序直接校验字段、直接进表格、直接判阈值。同样的信息,一个要人看,一个不用。 ③接口隔离放到委派上就是:回的应该是「主窗口需要的那几个字段」,不是「子智能体经历的整个过程」。 所以定义子智能体时,正文最后必须写清返回格式,写得越死越好:几个字段、什么类型、没有写零。 ④能直接抄的四字段:done、files、issues、blocked。 多写的字段主窗口不会看,反而让它更容易漏掉关键的那一个。接口窄一点,比宽一点可靠。
30 秒实验
- 把 fixer 那份散文改成四个字段的 JSON,重跑第 ① 步。可解析率变成多少?改动只发生在哪一层?
- 给回执加第五个字段 notes(自由文本),重跑一遍。它会让下游更容易用还是更难用?为什么?
- 把 issues 从整数改成一段描述(「大概十来条」),重跑一遍。下游还能不能判阈值? 这说明字段类型为什么要写死?
常见误区
- 让子智能体「详细汇报」。详细汇报回到主窗口就是一堆散文,等于把隔离又取消了。
- 回执只写 done,不写 blocked。卡住和干完在回执上长得一样,主窗口只能猜。
- 把明细写进回执。明细该写进文件,回执里给路径。
- 每个子智能体的指令都抄一遍项目规范。规范改了要改 N 处,必漏一处。
- 以为回执格式是「小事」。它是委派这个接口的返回值类型,值不值得信任全看它。
自测三问(点开对答案)
1. 为什么说委派是一次接口调用? —— 因为指令是入参、回执是返回值。 入参够不够、返回值稳不稳、错误怎么表达,这三件事和模块调用完全一样。 2. 接口隔离在委派里具体指什么? —— 回执只写主窗口需要的字段,不写子智能体经历的整个过程。字段窄一点,比宽一点可靠。 3. 回执里 blocked 这个字段为什么不能省? —— 它是异常路径的表达。没有它,卡住和干完在回执上长得一样,主窗口只能猜。接口设计完了,最后一节收尾:几个实战里最容易踩的坑。
- ☐ (本篇完,下一篇 4.8 实战注意事项)
相关笔记
- 从软件工程看 Skills:技能也要按这几条原则过一遍
- 实战注意事项:四个实战坑
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引