docs(s16): softer March-like Chinese prose (v21)

This commit is contained in:
Xinlu Lai
2026-08-13 03:33:30 +08:00
parent e8cf4b299e
commit c9e1803e77

View File

@@ -4,74 +4,74 @@
[s15](../s15_integrated_harness/) → `s16` → [s17](../s17_goal_loop/) [s15](../s15_integrated_harness/) → `s16` → [s17](../s17_goal_loop/)
> *别把计划只留在聊天里。* 谁先谁后由脚本,每一步怎么判断给模型。 > *计划不必只活在对话里。* 先后顺序交给脚本,每一步判断给模型。
> >
> **Harness 层**: 编排 — 在单个 Agent 循环上面,再跑一套多 Agent 脚本。 > **Harness 层**: 编排 — 在单个 Agent 循环之上,再运行一套多 Agent 脚本。
## 问题 ## 问题
到这一章,你已经会让模型在循环里读文件、改代码、看报错 一路学到这里,你已经见过模型如何在循环里读文件、改代码、看报错。
可有些活,你其实早就想好了顺序:先几个角度审一遍,再找人挑刺,最后汇总。顺序如果只记在对话里,模型很容易做到一半就喊「做完了」;让它自己评自己,分数又往往偏高;上下文压几轮之后,「别动 X」这种约束也可能悄悄不见 可有些事情,顺序其实一开始就清楚:先几个角度看看,再请另一位核对,最后汇总。若这些步骤只靠聊天记着,时间一长就容易乱——做到一半以为结束了;自己复查时又容易放过问题;上下文压几轮之后,原先那句「请别改动 X」也可能渐渐淡掉
聊天记不住并行,也扛不住中途崩掉再接着跑。你缺的不是一个更会聊天的模型,而是一份**写下来的步骤**。 对话很适合探索。它不太擅长稳住并行、固定结果的形状,也不太擅长中断之后从同一个地方接着做。这时你需要的,往往不是一个更会聊天的模型,而是一份**写下来的步骤**。
## 解决方案 ## 解决方案
```text ```text
你的对话 ──► Workflow(...) ──► 一条结果回来 对话 ──► Workflow(...) ──► 结果回来
脚本agent / pipeline / parallel 脚本agent / pipeline / parallel
变量 + journal中间结果放这里,别塞回对话 变量 + journal中间结果放这里
``` ```
子 Agent 还是负责想。**脚本**负责循环、分发合并。中间结果放进变量和 journal主对话。 子 Agent 仍然负责思考。**脚本**负责循环、分发合并。中间结果放进变量和 journal必再挤回主对话。
一句**把「怎么排步骤」从临场发挥,变成写死的结构。** 可以记一句:**让结构来保管步骤,让模型来做判断。**
![静态 harness 动态 workflow](images/dynamic-vs-static.png) ![静态 harness 动态 workflow](images/dynamic-vs-static.png)
*:事先写好的通用流水线。右:为这次任务临时裁出来的编排。* *左:事先写好的通用流。右:为这次任务量身写下的编排。*
在 Claude Code 里,入口大致有两种。**动态**:模型为这次任务写一段 JS`script` / `scriptPath`)。**已保存**:跑了的脚本放进仓库,用 `name` + `args`调。外面还有一种事先用 SDK 写死的静态编排。本章是 **Python 教学运行时**,不嵌 JS 引擎:想法对齐,演示走「已保存」这路。产品里模型本来就交脚本,我们只是在这里跑 JS 在 Claude Code 里,常见有两种用法。**动态**:模型为这次任务写一段 JavaScript`script` / `scriptPath`)。**已保存**:跑了的脚本留在仓库,用 `name` `args`次唤起。此外还有用 SDK 事先写好的静态编排。本章是一个 **Python 教学运行时**,不嵌 JS 引擎:想法与产品对齐,演示走「已保存」这路。产品里模型本来就可以提交脚本,我们只是在这里用更易读的 Python 来讲清楚
## 工作原理 ## 工作原理
**1. 三个词就够用** **1. 先认识三个词**
```text ```text
agent 一帮手,一件事(可带 schema拿到能往下传的 JSON agent 一帮手,完成一件事(可带 schema得到便于传递的 JSON
pipeline 每个条目自己走完各阶段(默认这样,不互相等) pipeline 每一项各自走完各阶段(默认如此,不互相等
parallel 等所有结果到齐再往下(屏障,用) parallel 等所有结果再继续(屏障,偶尔使用)
``` ```
有人失败了也别整队停工`parallel`失败的那一格变成 `null``pipeline` 丢掉那一。合并先把空滤掉。 某一步不顺利时,不必让整次运行停住`parallel`出问题的那一格变成 `null``pipeline` 则跳过那一。合并之前,先把空滤掉即可
**2. 想续跑,小本子,别靠聊天记录** **2. 若要接着跑,小本记下进度**
调用 `agent()`journal 按**调用顺序**记一笔。续跑时,从头核对:还没改过的前缀直接复用;到第一处改动,后面全部重跑。真的 JS 运行时不`Date.now()` / `Math.random()`不然本子对不齐。教学脚本也尽量写成确定的。 每调用一次 `agent()`journal 按**调用顺序**记一笔。再次运行时,从前往后核对:尚未改动的前缀可以直接复用;到第一处变化,之后的步骤再重新执行。真的 JS 运行时不宜使`Date.now()` / `Math.random()`否则记录很难对齐。教学脚本也尽量写成确定的。
```text ```text
journal [A] [B] [C] [D] journal [A] [B] [C] [D]
续跑 复用 复用 ✂ 重跑 续跑 复用 复用 ✂ 重跑
``` ```
**3. 一个例子:拆开审,再找人对着挑** **3. 一个例子串起来**
`review-changes` 不是单一招式。它是「拆开干」外面,套了一层「别人来挑刺」:几个维度各自 `pipeline(audit, verify)`,验证阶段`parallel` 并行检查,只留下仍然站得住的问题 `review-changes` 并不只是一种固定招式。它先把工作拆开,再请另一路核对:多个维度各自 `pipeline(audit, verify)`验证阶段用 `parallel` 并行检查,最后只留下仍然成立的发现
```text ```text
correctness ── 审 ── 验证 ──┐ correctness ── 审 ── 核对 ──┐
security ── 审 ── 验证 ──┤── confirmed security ── 审 ── 核对 ──┤── confirmed
performance ── 审 ── 验证 ──┤ performance ── 审 ── 核对 ──┤
style ── 审 ── 验证 ──┘ style ── 审 ── 核对 ──┘
``` ```
```python ```python
# code.py 节选,看形状就行 # 摘自 code.py看形状
async def sample_workflow(ctx, args): async def sample_workflow(ctx, args):
ctx.phase("Review") ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify) results = await ctx.pipeline(DIMENSIONS, audit, verify)
@@ -79,28 +79,28 @@ async def sample_workflow(ctx, args):
return {"confirmed": confirmed} return {"confirmed": confirmed}
``` ```
这样一来,队伍没法提早收工,作者也不当自己的裁判,步骤也不会被聊天一轮轮改 这样安排之后,步骤不容易半途收束,作者也不必兼任唯一的检查者,流程也不必随着聊天一轮轮改
<details> <details>
<summary>六种常见形状</summary> <summary>六种常见形状</summary>
![六种 Workflow 模式](images/six-workflow-patterns.png) ![六种 Workflow 模式](images/six-workflow-patterns.png)
| 名 | 人话 | 怎么拼 | | 名 | 含义 | 如何组合 |
|------|------|--------| |------|------|----------|
| Classify-And-Act | 先分,再交给对的人 | `agent` → 分支 → `agent` | | Classify-And-Act | 先分,再交给合适的帮手 | `agent` → 分支 → `agent` |
| Fanout-And-Synthesize | 拆开干,再合并 | `pipeline` / `parallel` → 汇总 | | Fanout-And-Synthesize | 分头进行,再汇总 | `pipeline` / `parallel` → 汇总 |
| Adversarial Verification | 别让自己给自己打分 | 产出 → `parallel(verify)` → 过滤 | | Adversarial Verification | 请另一路来核对,而不是只听自己 | 产出 → `parallel(verify)` → 过滤 |
| Generate-And-Filter | 先多几份,再筛 | `parallel` 生成 → 过滤 | | Generate-And-Filter | 先多准备几份,再筛 | `parallel` 生成 → 过滤 |
| Tournament | 两两比,决出更好的 | 裁判 `agent` | | Tournament | 两两比较,留下更好的 | 裁判 `agent` |
| Loop Until Done | 有新发现就继续 | `while` + 停止条件 + `budget` | | Loop Until Done | 有新发现就继续 | `while` + 停止条件 + `budget` |
`review-changes` 大约等于 Fanout + Adversarial。做调研时常再叠:分发 → 过滤 → 验证 → 汇总。 `review-changes` 大约 Fanout Adversarial 的组合。做调研时,常再叠:分发 → 过滤 → 核对 → 汇总。
</details> </details>
<details> <details>
<summary>动态、已保存、静态,以及官方原语图</summary> <summary>动态、已保存、静态,以及官方示意</summary>
```python ```python
# 教学示意 # 教学示意
@@ -113,21 +113,21 @@ Workflow({ "name": "review-changes", "args": { "changes": "..." } })
</details> </details>
<details> <details>
<summary>输入不可信时,把读和写隔开</summary> <summary>遇到不可信输入时,把读取与行动分开</summary>
读工单的 Agent同时拿着开 PR 的钥匙。一边只读、整理成摘要;另一边只看摘要再动手 负责阅读工单的 Agent同时握有发起变更的权限。可以让一侧只读、整理成摘要;另一侧只根据摘要行动
```text ```text
积压 → [读区: 读 / 去重 / 摘要] → [可信区: 行] 待办 → [读区: 读 / 去重 / 摘要] → [行动区: 行]
``` ```
![隔离分流](images/quarantine-triage.png) ![隔离分流](images/quarantine-triage.png)
</details> </details>
计划到底谁说了算s06 是一次性派出去s13 是长期队友加邮箱s15 是单循环里聊着决定**s16 把步骤写进脚本,进度记在 journal**s17 则在门口问:整件事做完了没有 不妨问一句:计划由谁保管s06 是一次性委托s13 是长期协作的队友s15 在单次循环的对话里决定下一步**s16 把步骤写进脚本,进度记在 journal**s17 则关心整件事是否已经完成
平时改几个文件s15 或一 s06 多半够了。Workflow 又费 token 又费协调——**只有步骤必须比单次对话活得更久**,再拿出来用 日常改几个文件s15 或一 s06 往往就够。Workflow 会多花一些 token,也需要一点协调——**当步骤需要比单次对话活得更久**,再请它来帮忙
## 试一试 ## 试一试
@@ -136,8 +136,8 @@ python s16_workflow_runtime/code.py demo
python s16_workflow_runtime/code.py resume python s16_workflow_runtime/code.py resume
``` ```
第一次跑,看 Review 到 Verify。同一 run 再一次,多应显示 `cached`(理想情况 `agents=0 tokens=0`)。想挂进上一章的完整程序,不加参数直接 `code.py` 第一次运行,可以留意从 Review 到 Verify。同一 run 再执行一次,多数步骤应显示 `cached`(理想情况 `agents=0 tokens=0`)。若想放进上一章的完整程序,不加参数直接运行 `code.py` 即可
s15 是那个循环;这里只多了一个 `Workflow` 工具。[s17](../s17_goal_loop/) 问的是另一件事:该停了吗 s15 是那个循环;这里只多了一个 `Workflow` 工具。[s17](../s17_goal_loop/) 会接着问:是否可以停下了
<!-- translation-sync: zh@v20, en@v19, ja@v19 --> <!-- translation-sync: zh@v21, en@v19, ja@v19 -->