2718 字
约 9 分钟
14
S21-X16 · 智能体拓扑与交接:Single-Agent、Orchestrator-Workers 与 Parent-Child 上下文传递

多智能体的失败很少发生在"模型不够强",而是发生在拓扑没定清:谁有权决策、上下文在哪一层被裁掉、任务交接时丢了哪几个字段。这三件事没写进代码,就一定在凌晨的日志里出现。这篇把四种常见拓扑摆出来对照,然后往下钻到交接协议与长任务检查点。


1. 四种拓扑的定位

拓扑 结构 解决什么 主要代价
Single-Agent 一个循环,一组工具 线性任务、上下文可控 工具一多就选错
Orchestrator-Workers 主管分派 + 工人执行 可并行、子任务异构 汇聚与冲突裁决
Evaluator-Optimizer 生成者 + 评审者循环 有明确质量标准 循环不收敛
Parent-Child(Sub-Agent) 主代理派生隔离上下文子代理 上下文预算隔离 交接信息丢失

判断顺序是:任务能不能并行 → 质量有没有可判定标准 → 上下文会不会超窗。三个都否,就该老实写 Single-Agent。


2. Single-Agent:工具超过八个就开始选错

def run_single(task, tools, max_steps=12):
    ctx = [{"role": "user", "content": task}]
    for step in range(max_steps):
        msg = llm(ctx, tools=tools, tool_choice="auto")
        ctx.append(msg)
        if not msg.tool_calls:
            return msg.content
        for call in msg.tool_calls:
            ctx.append(exec_tool(call, tools))   # 结果以 tool 角色回填
    return "step_budget_exhausted"
信号 含义 处理
反复调用同一个工具 参数不对或返回没读懂 精简返回体,给出示例参数
工具选择在两个相似工具间摇摆 工具描述重叠 合并工具或明确"何时用哪个"
步数打满无结论 任务本身需要分解 升级到 Orchestrator-Workers
上下文越来越长、答非所问 历史堆积挤掉任务说明 摘要或外置状态

工具描述的质量比工具数量更决定成败。两个名字相近、描述都写"查询数据"的工具,模型必然误选。


3. Orchestrator-Workers:扇出容易,汇聚难

import asyncio

async def orchestrate(goal, plan, workers):
    subs = plan(goal)                       # [{id, task, worker}]
    results = await asyncio.gather(*[
        workers[s["worker"]].run(s["task"]) for s in subs
    ], return_exceptions=True)
    ok = [r for r in results if not isinstance(r, Exception)]
    if len(ok) < len(subs) * 0.6:           # 汇聚前先判完成率
        return {"status": "partial", "results": ok}
    return {"status": "ok", "merged": merge(goal, ok)}
汇聚策略 做法 适用 风险
拼接 按顺序合并文本 各子任务互不依赖 重复内容膨胀
裁决 主管按判据选一份 结论互斥 裁决依据必须显式
投票 多工人同任务取多数 有唯一答案 成本翻倍
结构化合并 按字段合并 JSON 抽取类任务 字段冲突要定义优先级

扇出数要有上限。子任务从 5 涨到 20,延迟不一定涨,但 token 成本线性涨,而质量在第 8 个之后基本不动。


4. Evaluator-Optimizer:两个角色、一个终止条件

要素 生成者(Optimizer) 评审者(Evaluator)
输入 任务 + 上轮问题清单 产物 + 判据清单
输出 新版本产物 pass/fail + 具体问题
禁止 顺便改没被指出的部分 提"更好一点"这类不可判定意见
终止 评审 pass 或轮次上限 连续两轮问题集合相同则判死锁

评审意见必须能被验证。写"表达可以更清晰"等于没写;写"第 3 条缺少失败时的返回值说明"才是可执行反馈。

def optimize_loop(task, gen, review, max_rounds=3):
    draft, prev_issues = gen(task), None
    for r in range(max_rounds):
        ok, issues = review(draft)
        if ok:
            return draft, r
        if issues == prev_issues:            # 同一批问题反复出现 → 死锁
            return draft, r
        prev_issues = issues
        draft = gen(task, fix=issues)        # 只修被指出的问题
    return draft, max_rounds

5. Sub-Agent:上下文隔离是买来的,不是免费的

Parent-Child 的核心动作是派生一个带独立上下文的子代理,只回传结论。它买到的是预算隔离,付出的是信息丢失。

维度 同上下文内分工 Sub-Agent 隔离
上下文预算 共享,互相挤占 各自独立,互不污染
信息保真 全部可见 只保留显式回传的部分
成本 一份上下文反复计费 各自计费,总量更高
可恢复性 主循环崩溃即全丢 子任务可单独重跑
调试难度 单一轨迹 多轨迹需关联 id

典型误用:把"需要主代理已知的约束"放在子代理内部,结果子代理产出不满足约束的方案,主代理还得再审一遍——这就等于付了隔离成本却没得到隔离收益。


6. Parent-Child 交接协议:字段比措辞重要

{
  "handoff_id": "h-2026-0017",
  "parent_id": "root",
  "child_id": "worker-extract",
  "objective": "从附件中抽取全部付款条款,输出 JSON 数组",
  "constraints": ["金额保留两位小数", "缺失字段填 null,不要猜"],
  "inputs": {"file": "contract.pdf", "pages": "12-18"},
  "output_schema": {"type": "array", "items": {"type": "object"}},
  "budget": {"max_steps": 8, "max_tokens": 6000, "deadline_s": 90},
  "return": {"must_include": ["result", "steps_used", "unresolved"]}
}
字段 缺了会怎样 谁负责填
objective 子代理自由发挥 父代理(一句话、可判定)
constraints 输出不可用 父代理
output_schema 回传格式随机,合并失败 父代理
budget 子代理长跑拖垮总时延 父代理
return.must_include 不知道子代理卡在哪 双方约定

unresolved 这个字段价值极高:它把"子代理遇到的问题"变成结构化数据,父代理才能决定重试、换工具还是上报人。


7. Long-Running 任务:检查点与外置草稿

长任务的失败模式不是"算错",而是"跑到第 40 步时进程没了,全部重来"。解法是把状态落到文件系统,而不是留在上下文里。

import json, os, pathlib

STATE = pathlib.Path("/work/run-state.json")

def checkpoint(step, plan, done, notes):
    tmp = STATE.with_suffix(".tmp")
    tmp.write_text(json.dumps(
        {"step": step, "plan": plan, "done": done, "notes": notes},
        ensure_ascii=False), encoding="utf-8")
    os.replace(tmp, STATE)          # 原子替换,避免半写文件

def resume():
    if STATE.exists():
        return json.loads(STATE.read_text(encoding="utf-8"))
    return {"step": 0, "plan": [], "done": [], "notes": []}
做法 抗崩溃 上下文开销 适合
只靠对话历史 否 高(越跑越贵) 短任务
Planning-with-Files 是 低(按需读片段) 多步长任务
状态存数据库 是 低 需要并发与查询
全量重跑 是(但贵) — 幂等且极短的任务

Planning-with-Files 的要点是计划本身也是一个文件:每步完成后回写状态与下一步,恢复时只读计划,不读全部历史。这让"第 41 步重启"的成本从"重跑 40 步"降到"读一个文件"。


8. Code-as-Action 与工具调用的分工

维度 Tool Call Code-as-Action
表达力 受工具签名限制 任意组合、循环、条件
安全边界 每个工具自带权限 需要沙箱(容器/解释器)
可审计 调用参数即日志 需要记录代码与输出
失败形态 参数错、超时 语法错、运行时异常、死循环
适合 高频、语义明确的动作 数据整形、批量计算、流程粘合

判据:动作是"一件事"就用工具,是"一串事"就写代码。让模型连续调用六个工具做数据清洗,既慢又容易在中间步骤丢字段;给它一个能执行代码的沙箱反而更省。


9. Meta-Agent:什么时候才值得多一层

Meta-Agent 指"负责设计/调度其他代理的代理"。它只在两种情况下有价值:拓扑本身要随任务变化,或者代理数量已经多到人工配置不动。

条件 是否值得引入 Meta-Agent
固定两类请求、固定拓扑 否,硬编码更快更稳
任务数 > 20 且结构各异 是,需要动态生成拓扑
已在用 Orchestrator 且规则可枚举 否,先枚举规则
需要按成本/时延自动换策略 是,但要带预算上限

引入后必须同时引入拓扑审计:记录每次生成的拓扑与实际执行路径,否则出错时无法归因。


10. 排错:六种协作失效

现象 原因 处理
工人产出不满足约束 约束没进 handoff 把 constraints 写进交接体
汇聚结果重复度高 子任务划分有重叠 按内容边界而非按长度切
评审循环不终止 判据不可判定 改成清单式可验证项
子代理超时拖垮整体 budget 未设 每个交接体强制 deadline
长任务重跑后行为不一致 状态未落盘 引入检查点 + 原子替换
成本翻倍但质量不变 扇出数过大 给扇出上限,按边际收益调

11. 边界:这些情况不该拆多代理

场景 更该做的 理由
单轮问答、格式转换 直接单次调用 拆开只增加失败点
上下文远未超窗 单代理 + 结构化提示 隔离收益不存在
质量无客观判据 人工抽检 + 评测集 Evaluator 无据可依
任务强顺序依赖 单代理流水线 并行无意义
团队还没有轨迹日志 先补可观测 拆完无法排错

12. 自测

  1. 什么时候该从 Single-Agent 升级到 Orchestrator-Workers?判断依据是什么?
  2. Evaluator-Optimizer 里,"连续两轮问题集合相同"为什么可以判为死锁?
  3. Sub-Agent 隔离上下文的代价具体是哪一项?什么情况下等于白付这个成本?
  4. 交接体里 unresolved 字段解决的是什么问题?缺了会怎样?
  5. Planning-with-Files 让"第 41 步重启"变便宜,靠的是哪个设计取舍?
  6. Code-as-Action 相比 Tool Call,多出来的安全与审计成本分别落在哪?
S21-X16 · 智能体拓扑与交接:Single-Agent、Orchestrator-Workers 与 Parent-Child 上下文传递
https://www.clxhxhhr.top/posts/6012/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。