多智能体跑起来后互相抢工具、来回转交,通常不是模型能力问题,而是三层规则被混在一处:编排层写成了角色指令,角色提示词里塞了工具用法,工具权限又对所有角色放开。判断顺序建议是:先看任务流有没有明确的唤醒条件与终止条件,再看角色提示词有没有越界替别人做决定,最后看工具白名单有没有按角色收窄。这三层各自只管一段,哪一层缺规则,异常都会以“抢工具”或“反复转交”的形式暴露出来。
把编排拆成任务流、角色边界、工具权限三层分别配置和验证:任务流只写触发条件、流转规则和终止条件;角色提示词只写本角色的判断与产出契约;工具白名单按角色收窄,取角色与全局配置的交集。验证依据是日志字段(唤醒来源、转交对象、转交次数、工具调用明细与拒绝记录),不是对话观感。如果规则写不进这三层,通常说明任务本身还不适合多角色,先退回单角色加多工具。
先定任务流:谁在什么条件下被唤醒
编排层只做一件事:根据当前状态决定下一个被唤醒的角色。它不应该描述角色该说什么、该怎么思考,否则规则会变成第二份提示词,改起来互相打架。写法上建议用“条件表达式 + 目标角色 + 终止条件”三件套,条件只引用已有字段,不去猜语义。
flow:
- id: intake
when: 'event.type == "new_ticket"'
to: triage
- id: need_research
when: 'triage.result == "needs_data"'
to: researcher
max_hops_from: triage = 2
- id: answer
when: 'triage.result == "clear"'
to: writer
- id: stop
when: 'hop_total >= 6 or no_rule_matched'
to: __end__上面每个字段都能在日志里对应到一条记录,这是判断“谁被唤醒”的前提。流转日志建议至少记:trace_id、hop_index、from_role、to_role、match_rule(命中了哪条规则)、reason(上一步角色给出的原因字段)、timestamp。没有 match_rule 这一项,后面很难判断是规则没写清还是角色输出不合格。日志表结构可以按需增减,但 trace_id 和 hop_index 通常要保留,否则串不起一次完整流转。
再定角色边界:每个角色只写自己那一段提示词
角色提示词写多了,最常见的后果是每个角色都在替编排层做决定:triage 直接输出“让 researcher 去查”,researcher 又顺手把最终答复写了。收敛办法是给角色一个固定骨架,把“做什么”和“不做什么”都写清楚。
role: researcher
goal: 只根据给定资料补齐事实缺口
input: {question, known_facts[]}
do:
- 列出信息缺口
- 检索并记录来源标记
- 输出要点列表
dont:
- 不写最终答复
- 不决定下一个角色
- 不调用任何写操作工具
output: {facts:[{claim, source_ref}], gaps[]}
tools: [search_kb, fetch_doc]不该写进角色提示词的内容主要有四类:其他角色的职责描述、全局流转规则、工具的参数细节与重试策略、以及“什么时候结束整个会话”的判定。这些分别属于编排层和工具层,写进角色里会造成同一规则多处维护,改一处漏一处。输出结构建议尽量窄,字段越少,编排层的条件表达式越好写、越好验证。
最后定工具权限:白名单按角色收窄
抢同一个工具,多数情况是白名单给成了“全员可调用”。建议做两层映射:全局定义工具可授范围,角色再声明自己需要的子集,运行时取交集;不要只在一处写,否则改角色时容易顺手放大权限。
tools:
search_kb: {allow: [triage, researcher]}
fetch_doc: {allow: [researcher]}
write_ticket: {allow: [writer], require_confirm: true}
send_mail: {allow: []}
roles:
triage: {tools: [search_kb]}
researcher: {tools: [search_kb, fetch_doc]}
writer: {tools: [write_ticket]}越权调用时的表现需要提前观察清楚:有的实现是工具侧直接返回 permission_denied 一类错误,有的由编排层在调用前拦截并写审计日志,还有的会出现模型换名字重试、短时间内重复调用同一工具。只要出现非授权角色对某工具的调用记录,就应该回头核对白名单,而不是去调提示词。
用转交次数和工具调用分布看异常
日志齐了以后,统计口径建议固定三种:按 trace_id 统计 hop 总数与是否命中终止条件;按 (from_role, to_role) 统计流转边频次;按 (role, tool, result) 统计调用次数与拒绝次数。这三种口径分别回答“转了几圈”“谁和谁在互踢”“谁在越权”。
- 同一个 trace 里出现 A→B→A→B 的重复边,通常是两边都能处理同一类输入,角色边界重叠。
- hop 数分布尾部很长,但绝大多数 trace 很短,说明少数输入反复触发同一规则,规则条件写得过宽。
- 某角色工具调用数为零,可能是被别的角色架空,也可能它的工具没进白名单。
- 拒绝记录集中在某个角色上,说明该角色的提示词里仍写着它无权使用的工具名,需要同步删掉。
这些是判断思路,不是阈值。到什么数量算异常,需要结合自己业务的输入分布确认,可以先记录一段时间再定基线。
收敛不了时退回单角色加多工具
如果三层规则都写清楚了仍然反复转交,说明任务本身可能不需要多角色,只是需要多个工具。这时先把角色合并成一个 executor,把工具白名单做并集,但写操作仍然保留确认;流转规则退化成角色内部一份显式的步骤清单,由同一次调用顺序执行。
roles:
executor: {tools: [search_kb, fetch_doc, write_ticket]}
flow:
- id: single
when: 'event.type == "new_ticket"'
to: executor
max_hops_from: executor = 1
to_end: true
steps:
- search_kb
- fetch_doc
- write_ticket # require_confirm: true验证标准比较直接:同一批输入下不再出现跨角色转交记录,hop 数稳定在预期范围内,工具调用集中在计划内的那几支,越权与拒绝记录为零。确认单角色能稳定跑通之后,再按真实需要拆角色,比一上来就切分更容易定位问题出在哪一层。