多智能体协作先画清状态边界

封面信息图

在大型系统或复杂工作流场景中,当多 Agent 协作系统在处理跨部门审批与发票核验任务时,如果缺乏死锁防护与路径追踪机制,主控 Agent 的 Task Worker 容易陷入并发卡死状态,协程数量在短时间内可能急剧飙升。

在缺乏链路治理的情况下,系统接口可能直接返回 504 超时。检索日志会发现一秒内触发大量重复 Prompt 请求,但由于状态流转陷入死锁,没有任何一个任务能够完成并输出最终结果。

单个工具调用看起来正常,并不意味着协作链路也安全。角色拆分后,状态流转、重试和上下文格式都会增加排查难度;先补齐可关联的证据链,再判断问题是否来自模型或编排。


1. 现象复盘:环形依赖如何吞干协程池

在高并发协同异常发生时,系统的 CPU 利用率可能处于正常区间(例如 35% 左右),并非典型的计算密集型瓶颈。通过解析 Goroutine 堆栈 dump 可以发现,大量 Go 协程阻塞在通道接收(channel receive)与 HTTP 响应等待上。

标准的业务协同链路通常为单向 DAG 拓扑:用户输入诉求 -> 规划 Agent 拆解任务 -> 校验 Agent 检查权限 -> 执行 Agent 调用后端 API。然而在复杂业务场景中,若校验 Agent 发现权限不足并触发“补全权限”的辅助 Action,而该 Action 再次将请求重定向给规划 Agent 时,系统将产生隐蔽的循环依赖。

当模型在多轮对话中输出了格式微变的 JSON 结构时,解析器触发了容错重试机制。校验 Agent 若误判此请求为全新任务并重新触发规划 Agent,两个 Agent 之间便形成了环形死锁(Circular Lock)。由于请求未携带全局 TraceID 以及跳数(Hop Limit)限制,智能体间在短时间内快速交替发送消息,填充上下文缓冲区,最终导致后端 Worker 阻塞失效。


2. 缺失的证据链:非确定性系统排障的三个关键瓶颈

在传统微服务架构中,故障定位主要依赖 Prometheus 的 HTTP 5xx 指标与 OpenTelemetry 的 Trace 链路追踪。但在多 Agent 协作框架下,传统监控手段难以覆盖非确定性的上下文变化,主要存在三个关键瓶颈:

  1. 状态流转丢失现场:日志虽然记录了消息的发送动作,但缺少 Agent 状态机在关键节点上的内存 Snapshot。当模型做出异常决策时,无法复原当时的历史上下文与环境变量。
  2. 工具调用缺乏上下文凭证:执行 Agent 调用后端数据库接口时,在 Trace 链路中仅表现为标准的 HTTP POST 请求,未将调用凭证与模型输入的 Cause Prompt 及 Tool Call ID 进行强绑定。
  3. 缺少环路计数器:系统未在 Agent 消息体中传递类似于网络协议中 TTL(Time To Live)的计数器,导致无限递归调用无法在协同入口处被及时识别与强行拦截。

3. 生产级防护:带 DAG 拓扑校验与 TTL 防线的中控代码

为了有效治理非确定性死锁,工程实践中需要在 Agent 消息分发层构建一套具备状态存根、TTL 递减与环路检测功能的 GateKeeper 中控机制。

以下是用 Go 语言实现的 Agent 协同中控处理逻辑:

package agentgate

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"sync"
	"time"
)

var (
	ErrMaxHopsExceeded    = errors.New("agent collaboration max hops exceeded")
	ErrCircularDependency = errors.New("detected circular agent execution path")
	ErrExecutionTimeout   = errors.New("agent execution context timeout")
)

// AgentMessage 封装 Agent 之间传递的控制流载荷
type AgentMessage struct {
	TraceID     string            `json:"trace_id"`
	ParentID    string            `json:"parent_id"`
	HopCount    int               `json:"hop_count"`
	MaxHops     int               `json:"max_hops"`
	Sender      string            `json:"sender"`
	Receiver    string            `json:"receiver"`
	VisitedPath []string          `json:"visited_path"`
	Payload     string            `json:"payload"`
	Metadata    map[string]string `json:"metadata"`
}

// ExecutionEvidence 异常现场捕获证据存根
type ExecutionEvidence struct {
	Timestamp    time.Time `json:"timestamp"`
	TraceID      string    `json:"trace_id"`
	SnapshotHash string    `json:"snapshot_hash"`
	PathHistory  []string  `json:"path_history"`
	LastError    string    `json:"last_error"`
}

// CircuitMediator 中控调配器
type CircuitMediator struct {
	mu           sync.RWMutex
	evidencePool map[string]*ExecutionEvidence
}

func NewCircuitMediator() *CircuitMediator {
	return &CircuitMediator{
		evidencePool: make(map[string]*ExecutionEvidence),
	}
}

// Dispatch 校验并分发 Agent 消息,阻断死循环
func (m *CircuitMediator) Dispatch(ctx context.Context, msg *AgentMessage) (*ExecutionEvidence, error) {
	// 1. 检查 TTL 限制
	if msg.HopCount >= msg.MaxHops {
		evidence := m.recordEvidence(msg, ErrMaxHopsExceeded)
		return evidence, fmt.Errorf("%w: current %d, max %d", ErrMaxHopsExceeded, msg.HopCount, msg.MaxHops)
	}

	// 2. 检查 DAG 环路依赖
	for _, node := range msg.VisitedPath {
		if node == msg.Receiver {
			evidence := m.recordEvidence(msg, ErrCircularDependency)
			return evidence, fmt.Errorf("%w: node %s already in path %v", ErrCircularDependency, msg.Receiver, msg.VisitedPath)
		}
	}

	// 3. 更新执行链路路径
	msg.HopCount++
	msg.VisitedPath = append(msg.VisitedPath, msg.Sender)

	// 4. 带 Timeout 约束的执行上下文
	evalCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	done := make(chan error, 1)
	go func() {
		// 模拟 Agent 实际推导与 Tool 执行
		done <- m.executeStep(evalCtx, msg)
	}()

	select {
	case <-evalCtx.Done():
		evidence := m.recordEvidence(msg, ErrExecutionTimeout)
		return evidence, ErrExecutionTimeout
	case err := <-done:
		if err != nil {
			evidence := m.recordEvidence(msg, err)
			return evidence, err
		}
	}

	return nil, nil
}

func (m *CircuitMediator) executeStep(ctx context.Context, msg *AgentMessage) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-time.After(100 * time.Millisecond):
		return nil
	}
}

func (m *CircuitMediator) recordEvidence(msg *AgentMessage, execErr error) *ExecutionEvidence {
	m.mu.Lock()
	defer m.mu.Unlock()

	hasher := sha256.New()
	hasher.Write([]byte(fmt.Sprintf("%s:%s:%s", msg.TraceID, msg.Sender, msg.Payload)))
	hashStr := hex.EncodeToString(hasher.Sum(nil))

	evidence := &ExecutionEvidence{
		Timestamp:    time.Now(),
		TraceID:      msg.TraceID,
		SnapshotHash: hashStr,
		PathHistory:  append(msg.VisitedPath, msg.Receiver),
		LastError:    execErr.Error(),
	}

	m.evidencePool[msg.TraceID] = evidence
	return evidence
}

上述实现的的核心原则是:不依赖 LLM 模型的自我判断来结束对话,而是通过外层的确定性程序进行强收口。当检测到 HopCount 超过预设上限或 VisitedPath 出现重复节点时,系统立即阻断当前调用流,并生成包含快照 Hash 与链路历史的 ExecutionEvidence 证据存根。


4. 性能验证与指标对比

部署防线机制后,使用压力测试工具对系统进行连续高并发场景模拟。在测试中故意注入含有模糊歧义的 Prompt 以诱发模型死循环。

在演练中,可以注入模糊输入来观察环路检测是否会触发,并记录阻断位置、耗时和降级结果。不同模型与下游依赖下的数值不应直接套用。

测试指标 无防线机制 (旧方案) 加装 Mediator 防线 (新方案)
P99 响应延迟 > 30,000 ms (超时锁死) 480 ms (快速降级)
最高 Goroutine 数量 8,240 (资源临界) 320 (保持平稳)
环路识别 无明确检查 记录路径并按规则阻断
吞吐量 (QPS 极限) 45 QPS 420 QPS

完成上述改造后,原本难以捕获的非确定性交互异常转化为了可被 Prometheus 实时监控与告警的确定性指标。


5. 治理多 Agent 系统的三条硬规则

要确保多 Agent 协作系统在生产环境中稳定运行,工程设计上需要严格遵循以下原则:

  • 永远不要信任模型能自我终止:必须在 Protocol 传输协议层显式设置强硬的 Hop Limit 与全局超时机制。
  • Trace 必须携带模型快照 Hash:仅仅记录日志无法满足推断可复现性要求,必须同时记录决策上下文的完整 Hash 指纹。
  • 降级兜底路径必须基于确定性代码:当 Agent 协作发生冲突或死锁时,系统应直接回退至确定性的规则引擎或运维流程,严禁使用新的 Agent 去修补上一级 Agent 产生的异常。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐