工程化 / 大模型应用工程化
16. 并行与多智能体:什么时候值得拆给多个助手
原文备份 · 第 18 / 21 篇 · 查看极客时间原知识库 ↗
本篇目录 · 11 节
来源:大模型应用工程化
你准备检查笔记助手的下一次迭代。历史搜索需要核对“第51条记录”这样的边界,复习资料导出需要检查卡片、引用与文件内容。两件事都重要,但搜索检查不必等待导出结果,导出也不需要先知道搜索是否通过。
这时,分给两个助手似乎很自然。可如果它们同时修改 storage.py,或者都把结果写进同一个 report.json,省下的等待时间很快就会变成排查时间:到底检查了哪个版本?后写入的结果有没有盖掉前一个?谁负责判断整个任务完成?
上一篇解决了“另一个会话怎样接着干”。这一篇解决“两个工作者怎样同时干”。我们先用可运行的离线实验观察分工与集成,再把这些规则用到 Kimi Code。你不需要为实验开通模型服务。
先区分多 Agent 与并行
多 Agent 指多个具有各自任务和上下文的模型助手参与工作;并行指多个工作在时间上重叠。两者可以结合,也可以分开:一个主助手先让审查助手返回意见,再自己修改,是多 Agent 协作,但主要步骤仍然串行;同时跑两个固定测试脚本,则有并行,却没有模型助手。
本篇配套实验属于后一种。代码是作者参考实现,两个工作项实际运行 Python 子进程,既不冒充 Kimi 的生成记录,也不把普通脚本包装成“自主智能体”。这样做,是为了让你先看清版本、目录、交付和验收这些可以检查的事实。
当前项目保留第三部分以来的分工:笔记助手在线运行时仍使用 DeepSeek 官方 API,Kimi Code 用来辅助开发。增加开发助手数量,不会自动改变应用的模型供应商。
先画依赖,再决定人数
本轮的目标是“在同一个正确版本上,完成搜索边界与导出产物的机械检查”。它的依赖关系如下:
确认输入版本,创建本次运行目录
│
┌───────┴────────┐
│ │
A:搜索边界测试 B:导出复习资料
│ │
│ 验证导出产物
│ │
└───────┬────────┘
│
收齐证据,复核版本,判断整轮状态
│
人工核对语义
A 与 B 可以同时开始,B 内部却必须先导出、后验证。把所有箭头都去掉并不会更快,只会让验证器读到尚未生成的文件。
判断能否拆分,可以先问三个具体问题:这个工作项是否需要另一个工作项尚未产出的结果?是否会修改同一份状态?完成后能否用清楚的接口交回结果?
| 项目中的情况 | 更合适的安排 | 原因 |
|---|---|---|
| 搜索测试与固定导出检查 | 有限并行 | 输入已确定,输出可分开 |
| 两人同时修改搜索函数 | 先确定唯一负责人 | 共享修改容易覆盖,测试版本难确认 |
| 改数据库字段,再改依赖字段的页面 | 先定接口,再按依赖推进 | 下游不能猜上游最后会采用哪个字段 |
| 从功能和权限两个角度只读审查同一差异 | 可以并行 | 可以共享读取,但意见仍需统一核对 |
| 修复一个明显拼写错误 | 单助手串行 | 分派和收集可能比修改本身还费事 |
这些是本项目的安排建议,不是“复杂任务必须多 Agent”的定律。任务边界越模糊,增加助手越容易放大模糊。
起点:使用本篇独立快照
下载完整练习包,解压到新的练习目录。也可以查看材料说明。使用 Python 3.11 或更高版本;本篇只需要标准库,不安装 SDK、不配置 Key。
所有命令都在解压后的 lesson-16 目录运行,不是在内层 project 中。先确认这里有 parallel_lab.py、verify_parallel.py 和 project。项目的38个交付文件与第15篇相同,搜索功能已经正确实现。
不要覆盖自己原来的项目、Git 历史或运行数据。快照内也保留了前几篇的 Harness、Loop 和接手工具,供对照阅读;本次只运行下面的新实验。第15篇的接手脚本仍要求单人串行,本篇不会自动让它具备并发能力。
使用前先看当前任务。它把职责写成了可以执行的规则:
| 角色 | 负责什么 | 可以写哪里 |
|---|---|---|
| 协调器 | 记录输入、分派、等候、汇总 | 新运行目录的 context.json、integration.json,并创建子目录 |
A:search |
运行四项搜索边界测试 | 本次 search 目录;测试另有自己的临时 SQLite |
B:export |
导出三张样例卡片,再验证 | 本次 export 目录,包括 artifact |
共享源码只读。每个工作项都知道自己的输出位置,协调器承担最终集成责任。
这里的文件归属由可信脚本实现,也要求操作者遵守;目录划分不是操作系统沙箱。运行期间不要编辑输入,也不要让另一个工具修改同一工作区。开始和结束时核对指纹,可以发现最终版本变化,却不能证明运行中从未发生过“修改后又改回来”。
运行一次正常并行
macOS/Linux 终端执行:
python3 parallel_lab.py --run parallel-01
python3 verify_parallel.py --run parallel-01
Windows PowerShell 执行:
py -3 parallel_lab.py --run parallel-01
py -3 verify_parallel.py --run parallel-01
后续命令继续以 python3 展示;Windows 使用同样的参数,将解释器替换为 py -3。每次尝试使用新名称;已有目录会被拒绝覆盖。
第一条命令正常输出如下。JSON 会显示在一行,这里仅为阅读排版:
{
"mode": "offline_script_workers",
"schedule": "parallel",
"fixture": "valid",
"worker_limit": 2,
"status": "needs_review",
"inputs_unchanged": true,
"workers": {"search": "passed", "export": "passed"},
"commands_executed": 3,
"semantic_review": "pending",
"actual_api_calls": 0
}
两个工作项为什么执行了三条命令?A 运行一次搜索测试;B 先运行导出器,再运行导出验证器。工作项数量、命令数量和模型请求数量是三种不同的统计,不能混在一起。
第二条命令应输出:
{"result": "pass", "workers_checked": 2, "status": "needs_review", "semantic_review": "pending"}
needs_review 表示本轮机械检查已满足要求,还有人工语义核对。pending 没有因为工作者变多就消失。固定样例的引用和文件结构通过,不代表真实模型面对任意笔记都能答对。
打开 parallel-runs/parallel-01,你会看到:
context.json 本轮输入指纹
search/
result.json A的结果和命令回执
search.stdout.log
search.stderr.log unittest四项结果
export/
result.json B的结果和两条命令回执
export.stdout.log
export.stderr.log
verify.stdout.log
verify.stderr.log
artifact/
review.md
manifest.json
integration.json 协调器最后形成的结论
先看 search/search.stderr.log:应包含四项测试、Ran 4 tests 和末尾的 OK。测试覆盖空历史、恰好50个命中、51个命中、位于旧记录中的唯一命中。正常的测试报告写在 stderr 并不等于失败,要结合退出码和测试结果判断。
再打开 export/artifact/review.md,检查三张卡片及“作者构造的固定样例”“语义审核:待人工核对”等标记。没有 Key 也能完成,是因为它读取教学样例,而没有现场生成模型答案。
收到“完成”以后,协调器还要检查什么
每个 result.json 都包含工作项身份、输入指纹、命令参数、退出码及日志指纹。这里的“交付接口”就是双方事先约定的这些字段及产物位置,不必一开始就搭建消息队列。
本课有两层检查。parallel_lab.py 等待两个工作项结束,确认返回状态和输入前后指纹,再写汇总;verify_parallel.py 则从文件重新核对证据,没有导入协调器的完成判断逻辑。它会检查工作项是否齐全、身份是否正确、命令顺序和退出码是否符合预期、日志是否匹配,并重新检验真实导出文件。
因此,“汇总里有一个绿色字段”还不够。缺失导出文件时,整套证据仍然不合格。
验证器检查的是本地教学记录的一致性。哈希可以帮助发现改动,不是数字签名;能同时改源码、日志和回执的人也可能伪造一整套材料。它不证明模型回答质量,更不是对恶意助手的隔离机制。
如果迁移到真实开发任务,协调者还要看最终差异:A 修了一个函数,B 的测试却基于旧函数运行,即使两份报告都说通过,也不能推导出集成后的版本通过。把改动组合起来以后,必须针对组合后的状态运行相关检查。
故意让一个工作项失败
执行新的反例:
python3 parallel_lab.py --run failed-01 --fixture bad-quote
python3 verify_parallel.py --run failed-01
第一条命令退出码为1。摘要中的关键字段应为:
{
"status": "blocked",
"workers": {"search": "passed", "export": "failed"},
"commands_executed": 2,
"semantic_review": "pending",
"actual_api_calls": 0
}
这是特意选择的错误引文样例。导出器会拒绝不在对应笔记原文中的引用,B 不再执行下一步验证,也不生成完整产物目录。A 仍然完成搜索检查;失败不会抹掉已经取得的另一份证据。
第二条命令同样应退出1,拒绝把反例当作一次完成的正常运行。要查看上一条退出码,macOS/Linux 使用 echo $?,PowerShell 使用 $LASTEXITCODE;必须紧接需要检查的命令查看。
读 export/export.stderr.log,定位具体引用错误。这里不需要重跑已经通过的搜索,也不应该为了获得“全绿”放宽引文规则。真实任务中,先确认导出失败的原因和修复范围,再决定开启新一轮工作。
本实验选择“让另一项独立工作收尾,再汇总阻塞”。如果两项会产生相互依赖的外部副作用,这个策略就不一定适合。取消一个等待中的任务,也不等于撤销已经执行的写入;退出或超时后都要依据实际现场判断。
实现里真正限制了什么
可以打开协调器源码,找到这段:
with ThreadPoolExecutor(max_workers=2) as pool:
jobs = [pool.submit(worker, n, run, fixture, baseline)
for n in ('search', 'export')]
results = [job.result() for job in jobs]
两个任务先提交,然后收集结果。代码没有“提交A、等A完成、再提交B”。线程在这里主要负责等候各自的 Python 子进程;它不意味着给同一个模型开两个思考线程。Python 的执行器文档说明了工作线程上限与等待结束的行为。
固定工作项最多两个,正常最多三条子命令,没有递归分派,没有失败重试。每条子命令设置30秒超时;超时会阻止该工作项继续。这不是整轮严格30秒的承诺:B 有顺序步骤,启动和收尾也需要时间。当前子命令不再创建进程树;不要直接把此超时处理当成任意外部工具的全进程清理方案。Python subprocess 文档说明了 run 的超时与子进程处理范围。
如果进程被中断、磁盘写入失败,可能只留下部分记录,没有 integration.json。缺失最终记录就没有完整完成证据。本脚本不负责自动续跑;保留现场,确认旧进程结束,再按第15篇的方法判断下一步。
还可以用新名称改为串行:
python3 parallel_lab.py --run serial-01 --serial
python3 verify_parallel.py --run serial-01
正常业务结果应一致,schedule 变成 serial。这组任务很小,进程启动和协调本身可能占去不少时间,作者没有据此给出加速倍数。选择并行要看真实任务耗时和协调成本,而不是看屏幕上出现几个工作者。
在 Kimi Code 中试一次受限的分工
截至2026年10月4日,官方文档区分主 Agent 与子 Agent:子 Agent 使用独立上下文,任务描述需要明确传入;explore 用于只读探索,coder 可以执行开发操作,plan 侧重规划。子 Agent 会独立消耗模型 token。具体能力与权限以安装版本为准。Kimi Code 的 Agent 文档
本机只核实了 kimi --version 为2.0.2及帮助信息,没有实际发起这段练习。以下是给读者选择的开发服务操作,会消耗 Kimi 服务额度;它不需要调用笔记助手的 DeepSeek API。
如果你已有可用的 Kimi Code,在本篇目录先执行:
kimi --version
kimi --help
kimi --plan
帮助中应有 --plan。它让会话先从计划开始,本课先讨论如何分派,不据此承诺任何版本都完全不能写文件。向主助手提供下面的任务,再查看它提出的安排:
当前只做第16篇的只读审查。先读AGENTS.md和docs/parallel/TASK.md。
请先给出分工方案,不立刻执行。拟使用最多两个explore子Agent:
A:读docs/specs/search.md、project/storage.py、verify_search_boundary.py,
核对“先过滤全部历史再取50条”的规格、实现与测试预期。
B:读export_review.py、verify_export.py、project/study_task.py、
project/fixtures/valid.json,核对导出与独立验证之间的对应关系。
给每个子Agent完整的文件路径、单一问题和只读约束,
不要假定它们看得到本会话前面的讨论。
两项都不得写文件、执行shell、调用应用模型或继续转派。
每项只返回:结论、代码依据、尚未验证的事项。
主会话负责核对冲突,不把静态阅读写成测试已经通过。
完成这一轮后停止,不自行开始修复。
注意这里使用的是 docs/specs/search.md,不是旧归档规格。先核对工作卡中的路径是否存在,再决定是否让主助手按方案分派。不要把旧会话的默认任务混进来。
若方案符合范围,可以继续发:“按上述两项只读任务执行,收齐后汇总,保持全部限制。”这一步才是实际模型协作。返回后,把其判断与前面的本地命令结果比较:阅读发现“测试缺少某个边界”,不等于现有测试失败;运行通过,也不能自动反驳它指出的未覆盖场景。
--agent 在本机帮助中用于选择新会话的主 Agent 配置,不是“开几个助手”的数量开关。本课通过任务说明限制分派范围,不发明一个 --agents 2 参数。若你安装的版本没有文档描述的子 Agent 行为,保留同样的工作卡,由一个主助手分两次审查也能练习接口与验收。
真正并行修改代码,还要多一道隔离
本课特意从共享只读输入开始。如果今后让两个助手改代码,仅给它们起不同名字远远不够。
至少应先确定文件负责人和公共接口,让每个助手在自己的工作副本中修改。可以用独立副本或 Git worktree 承载不同分支;仅在同一目录创建两个分支名,并不会得到两份可同时修改的磁盘文件。临时数据库、端口和测试输出也要分开,不能只隔离源码。Git 官方文档介绍了同一仓库下多个工作树的机制。
假设 A 改导出返回结构,B 改页面消费这个结构,双方必须先约定字段及错误行为;或者先让 A 完成,再让 B 跟进。协调者按依赖顺序集成,查看冲突与最终差异,再运行组合版本的相关测试。两个助手对各自副本都说“完成”,还只是两份局部交付。
也不要把多数投票当成事实验证。三个助手读了同一份过时规格,完全可能得出同一个错误结论。意见分歧应回到规格、源码、可复现输入和实验结果,而不是按票数决定业务行为。
成本应该算整轮
并行可能缩短等待,但通常仍要支付两份输入和输出,以及主助手分派、阅读、核对结果的成本。独立上下文能隔开讨论,也意味着必要材料可能被重复传入。
比较方案时至少记录完成时间、总用量、集成返工和结果质量。两项各需要十分钟的工作,在没有依赖、资源充足时,核心执行时间可以重叠;但加上分派和集成,不应把总耗时直接承诺为十分钟。这只是估算思路,不是本课测速结果。
给自己的第一次模型协作定一个小范围:最多两个子任务,每项一个明确问题,只返回必要证据,禁止自行扩大任务,遇到缺信息就报告,主助手汇总一轮后停止。先从服务界面确认自己的可用额度;人数上限只是结构约束,不是金额硬上限。
配套分工工作卡可以用于下一次任务。若你填不出“允许写哪里”或“完成后交什么”,先完善任务,再增加助手。
卡住时按证据查
| 现象 | 检查与处理 |
|---|---|
| 提示运行目录已存在 | 使用新名称,不删旧证据后假装首次执行 |
| A 通过、B 失败 | 看B的第一条失败回执与日志;整轮保持阻塞 |
| 汇总正常、独立验证失败 | 检查缺失文件、输入指纹、工作项身份与日志;不要只改状态字段 |
| 超时或没有最终汇总 | 保留现场,确认进程结束;不假定已经回滚或可以盲目重放 |
| Kimi 返回了源码分析,但没有测试记录 | 如实记为静态审查,另运行本地验证 |
| 并行没有更快 | 检查任务是否太小、存在隐藏依赖或共享资源竞争,必要时回到串行 |
作者实际在 macOS、Python 3.11 环境运行了并行、串行、错误引文和多种证据损坏检查;还通过同步屏障确认两个工作函数可以同时处于执行状态。超时分支和执行期间的版本变化使用了测试注入,没有冒称真实服务故障。没有做真实 Kimi/DeepSeek 请求、Windows 实机或性能基准测试。
现在试着回答:A 的检查通过,B 的产物丢了,能不能交付?不能。两个助手都分析出了同一问题,是否已经证明问题存在?还需要代码或实验依据。两项都通过以后,是否可以跳过人工语义核对?本轮仍然是 pending。
本篇结束时,业务源码保持原来的正确状态,你新增的是一套能分工、收证据和拒绝虚假完成的工作方式。下一篇会把视角移到集成之后:怎样把验证过的修改整理成可以交付、检查兼容性并在必要时回滚的版本。