AI Agent 混沌工程

AI Agent 混沌工程:构建具有故障注入和自愈协议的弹性自治系统


引言

2024 年,Netflix 的 Chaos Monkey 已经八岁了。混沌工程从"随机杀一台服务器"的简单哲学,演变为云原生领域的基础实践。然而,当我们将视线转向一个更复杂的系统时——AI Agent——传统的混沌工程方法论遇到了前所未有的挑战。

一个典型的 AI Agent 系统包含以下层次:推理引擎(LLM)、规划器(Planner)、工具调用器(Tool Executor)、记忆系统(Memory System)、以及多智能体协作协议。这些组件之间的依赖关系错综复杂:一次 LLM 推理超时可能导致整个 Agent 任务死锁;一个工具的返回结果异常可能让推理引擎产生幻觉链式反应;上下文窗口溢出可能直接"洗掉" Agent 的关键记忆。

本文将深入探讨这两个核心工程问题:

  1. 如何在 AI Agent 系统中进行精确的故障注入?
  2. 如何设计自愈协议,让 Agent 在无人干预时恢复?

我们将从故障模式分类开始,逐步设计一个完整的弹性运行时,并用 Rust 给出生产级实现。


AI Agent 特有故障模式

传统分布式系统的故障模式相对稳定:网络分区、节点宕机、磁盘满。AI Agent 则引入了几类全新的故障维度。

2.1 LLM 推理退化

这是 AI Agent 独有的故障模式。不同于 HTTP 服务的明确 5xx 错误,LLM 的"软失败"包括:

  • 幻觉输出:工具名拼写错误,导致调用不存在的能力
  • 格式崩溃:要求返回 JSON 却返回 Markdown,或字段名漂移
  • 态度偏移:从确定性推理变为"我不知道"式的保守回答
  • 上下文泄漏:System Prompt 被用户输入污染

这些故障在分布式追踪中几乎不可见,但会直接导致任务失败。

2.2 工具调用级联失败

一个 Agent 通常拥有 5-20 个工具。当某个高频工具(如代码执行器)因超时返回错误时,Agent 的重试策略若设计不当,会导致:

  • 无限循环直到耗尽 Context Window
  • 任务状态不一致(半完成的子任务残留)
  • 工具费用重复产生

2.3 多智能体协作死锁

当 Agent A 等待 Agent B 的结果,而 Agent B 也在等待 Agent A 的某个状态时——恭喜你,遇到了分布式系统中最经典的死锁问题。不同的是,这种死锁在传统系统中可以通过超时中断检测,而在 Agent 对话中,双方可能都处于"等待对方回复"的礼貌状态中,超时机制也难以简单应用。


故障注入框架设计

混沌工程的核心原则是"在生产环境中注入可控故障,验证系统韧性"。对 AI Agent 系统,我们需要一个层次化的故障注入框架。

3.1 架构概览

我们的故障注入框架分为三个注入层:

┌─────────────────────────────────────────────────────┐
│               Agent 运行时(被测系统)                │
├─────────────────────────────────────────────────────┤
│  注入层 3: 上下文层 (Context Layer)                  │
│  ┌─────────────────────────────────────────────┐    │
│  │  注入层 2: 工具层 (Tool Layer)               │    │
│  │  ┌─────────────────────────────────────┐    │    │
│  │  │  注入层 1: 推理层 (Inference Layer) │    │    │
│  │  │         LLM Client                  │    │    │
│  │  └─────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────┘

3.2 推理层注入

推理层负责模拟 LLM 的各种退化行为。我们用 Rust 实现一个拦截器模式:

/// 推理层故障注入器
pub struct InferenceFaultInjector {
    config: FaultConfig,
    rng: ThreadRng,
}

#[derive(Clone, Debug)]
pub enum InferenceFault {
    /// 模拟 LLM 返回格式崩溃(非结构化输出)
    FormatCrash { crash_rate: f64 },
    /// 模拟推理超时
    Timeout { delay_ms: u64 },
    /// 模拟幻觉工具调用(工具名不存在)
    HallucinatedToolCall { rate: f64 },
    /// 模拟态度偏移(从合作变为拒绝)
    AttitudeDrift { drift_prompt: String },
    /// 模拟部分响应截断
    PartialTruncation { keep_ratio: f64 },
}

impl LLMClient for InferenceFaultInjector {
    async fn complete(&self, req: CompletionRequest) -> Result<CompletionResponse, LLMError> {
        // 故障决策
        for fault in &self.config.faults {
            if self.should_inject(fault) {
                return self.inject_fault(fault, req).await;
            }
        }
        // 无故障,正常转发
        self.inner.complete(req).await
    }
}

3.3 工具层注入

工具层更接近传统混沌工程的领域。我们需要模拟工具的延迟、错误和超时:

/// 工具调用结果
#[derive(Debug)]
pub enum ToolResult {
    Success(Value),
    Timeout,
    Error(ToolError),
    /// 工具返回了与 schema 不匹配的结果
    SchemaMismatch { expected: String, got: String },
}

/// 工具层故障注入器
pub struct ToolFaultInjector {
    /// 每个工具的故障配置
    tool_configs: HashMap<String, ToolFaultConfig>,
    /// 全局故障预算(防止所有工具同时故障)
    budget: FaultBudget,
}

impl ToolFaultInjector {
    pub async fn execute(&self, call: ToolCall) -> ToolResult {
        let config = self.tool_configs.get(&call.tool_name);

        // 检查全局故障预算
        if !self_budget.allow() {
            return self.inner.execute(call).await;
        }

        match config {
            Some(cfg) if cfg.is_active() => {
                // 故障注入逻辑
                match cfg.fault_type {
                    ToolFault::Timeout { duration } => {
                        tokio::time::sleep(duration).await;
                        ToolResult::Timeout
                    }
                    ToolFault::Error { code, message } => {
                        ToolResult::Error(ToolError { code, message })
                    }
                    ToolFault::SchemaMismatch => {
                        let fake_result = json!({"data": "这不是预期的格式"});
                        self.inner.execute_raw(call, fake_result).await
                    }
                    ToolFault::SuccessAfterNTries { attempt } => {
                        if call.attempt >= attempt {
                            self.inner.execute(call).await
                        } else {
                            ToolResult::Timeout
                        }
                    }
                }
            }
            _ => self.inner.execute(call).await,
        }
    }
}

3.4 上下文层注入

这是 AI Agent 独有的注入层。当 Context Window 接近容量上限时,我们需要模拟上下文被截断的行为:

/// 上下文层故障注入
pub struct ContextFaultInjector {
    max_tokens: usize,
}

impl ContextFaultInjector {
    /// 模拟上下文窗口溢出
    pub fn inject_overflow(&self, messages: &mut Vec<Message>, overflow_tokens: usize) {
        // 溢出总是从最古老的非系统消息开始删除
        let system_count = messages.iter()
            .take_while(|m| m.role == Role::System)
            .count();

        let remove_count = (overflow_tokens / self.avg_tokens_per_message) as usize;

        for _ in 0..remove_count {
            if messages.len() > system_count + 1 {
                // 移除第一条非系统消息(通常是最早的工具调用结果)
                messages.remove(system_count);
            }
        }
    }

    /// 注入上下文污染(模拟 Prompt Injection)
    pub fn inject_contamination(&self, messages: &mut Vec<Message>, payload: &str) {
        if let Some(last) = messages.last_mut() {
            if last.role == Role::User {
                last.content.push_str(&format!("\n\n---\n{}", payload));
            }
        }
    }
}

自愈协议设计

故障注入验证了系统在故障下的行为,而真正的弹性来自于自动恢复。AI Agent 的自愈需要超越传统的重试循环。

4.1 降级策略:Model Folding

当主模型(如 GPT-4)不可用或持续失败时,Agent 应该能自动切换到更简单的模型执行关键路径,将复杂推理任务"降级"为规则引擎:

/// 模型降级策略
pub enum DegradationLevel {
    /// 正常:使用完整模型
    Normal,
    /// 降级 1:切换到较小模型 + 简化 System Prompt
    MildDegradation,
    /// 降级 2:使用纯规则引擎(无 LLM 调用)
    CriticalDegradation,
    /// 安全模式:仅执行预设的急救流程
    SafeMode,
}

pub struct AdaptiveRuntime {
    current_level: AtomicU8,
    /// 熔断器:连续失败达到阈值则降级
    circuit_breaker: CircuitBreaker,
}

impl AdaptiveRuntime {
    pub async fn execute_with_degradation(&self, task: Task) -> TaskResult {
        let level = self.current_level.load(Ordering::Relaxed);

        match DegradationLevel::from(level) {
            DegradationLevel::Normal => {
                match self.execute_llm(task.clone()).await {
                    Ok(r) => r,
                    Err(e) => {
                        self.circuit_breaker.record_failure();
                        if self.circuit_breaker.should_degrade() {
                            self.degrade();
                            self.execute_with_degradation(task).await
                        } else {
                            Err(e)
                        }
                    }
                }
            }
            DegradationLevel::MildDegradation => {
                // 使用小模型 + 截断上下文
                let mut degraded_task = task.clone();
                degraded_task.model = "gpt-4o-mini";
                degraded_task.truncate_context_to(0.5); // 只保留一半上下文
                self.execute_llm(degraded_task).await
            }
            DegradationLevel::CriticalDegradation => {
                // 规则引擎接管
                self.rule_engine.execute(task).await
            }
            DegradationLevel::SafeMode => {
                // 执行安全模式流程:返回预设的错误信息,保存状态以便恢复
                TaskResult::safe_mode_return(task.id)
            }
        }
    }
}

4.2 指数退避与抖动:超越简单重试

AI Agent 的重试策略比传统服务调用更复杂,因为 LLM 调用是有状态的(上下文增长)。每次重试不仅要考虑时间维度,还要考虑上下文膨胀:

/// 智能重试策略
pub struct AgentRetryPolicy {
    max_attempts: u32,
    base_delay: Duration,
    max_delay: Duration,
    /// 重试时的上下文处理策略
    context_strategy: RetryContextStrategy,
}

pub enum RetryContextStrategy {
    /// 保持完整上下文(适合短期故障)
    FullContext,
    /// 重试时裁剪最早的工具结果
    /// (因为错误通常来自最近的交互,早期结果可能已被污染)
    RollbackOldestTools { keep_last_n: usize },
    /// 重试时重建 System Prompt(将之前的错误总结注入)
    InjectErrorSummary { max_error_len: usize },
}

impl AgentRetryPolicy {
    pub async fn retry<F, Fut>(&self, mut operation: F) -> Result<TaskResult, AgentError>
    where
        F: FnMut() -> Fut,
        Fut: Future<Output = Result<TaskResult, AgentError>>,
    {
        let mut attempts = 0;
        let mut last_error = None;

        loop {
            attempts += 1;

            match operation().await {
                Ok(result) => return Ok(result),
                Err(e) if attempts < self.max_attempts => {
                    last_error = Some(e.clone());

                    // 计算带抖动的延迟
                    let delay = self.calculate_delay(attempts);
                    tokio::time::sleep(delay).await;

                    // 根据策略处理上下文
                    self.prepare_retry_context(attempts, &e).await;
                }
                Err(e) => return Err(AgentError::MaxRetriesExceeded {
                    attempts,
                    last_error: Box::new(last_error.unwrap_or(e)),
                }),
            }
        }
    }

    fn calculate_delay(&self, attempt: u32) -> Duration {
        // 指数退避 + 全抖动
        let exp_delay = self.base_delay * 2u32.pow(attempt - 1);
        let capped = std::cmp::min(exp_delay, self.max_delay);
        let jitter = rand::random::<f64>() * capped.as_millis() as f64 / 2.0;
        Duration::from_millis(jitter as u64)
    }
}

4.3 多智能体死锁检测与恢复

当多个 Agent 协作时,死锁检测需要运行时层面的支持。我们引入一个轻量级的分布式追踪器:

/// 多智能体协作的依赖图
pub struct AgentDependencyGraph {
    /// Agent ID -> 当前等待的 Agent ID
    wait_edges: DashMap<AgentId, AgentId>,
}

impl AgentDependencyGraph {
    /// 检测死锁:使用 DFS 寻找环
    pub fn detect_deadlock(&self) -> Option<Vec<AgentId>> {
        let mut visited = HashSet::new();
        let mut in_stack = HashSet::new();
        let mut path = Vec::new();

        for agent_id in self.wait_edges.iter().map(|e| *e.key()) {
            if self.dfs_find_cycle(&agent_id, &mut visited, &mut in_stack, &mut path) {
                return Some(path);
            }
        }
        None
    }

    /// 死锁解除策略:中止路径中代价最高的 Agent
    pub async fn resolve_deadlock(&self, cycle: &[AgentId]) -> Result<(), AgentError> {
        // 选择中止代价最低的 Agent(基于任务进度)
        let victim = cycle.iter()
            .min_by_key(|id| self.get_task_progress(id))
            .ok_or(AgentError::DeadlockResolutionFailed)?;

        // 发送中止信号
        self.abort_agent(victim).await?;

        // 将环中其他 Agent 设为超时恢复
        for id in cycle {
            if id != victim {
                self.set_recovery_mode(id, RecoveryMode::TimeoutResume).await;
            }
        }

        Ok(())
    }
}

实战:构建弹性 Agent 运行时

现在让我们将这些理念整合为一个简单的 Agent Rust 运行时。虽然下面的代码是简化版,但它展示了核心工程决策。

use tokio::sync::{RwLock, mpsc};
use std::sync::Arc;
use std::time::Duration;

/// 弹性 Agent 运行时
pub struct ResilientAgentRuntime {
    inference: Arc<dyn LLMClient>,
    tool_executor: Arc<ToolFaultInjector>,
    context_manager: Arc<RwLock<ContextManager>>,
    retry_policy: AgentRetryPolicy,
    circuit_breaker: CircuitBreaker,
}

impl ResilientAgentRuntime {
    pub async fn run_task(&self, task: Task) -> Result<AgentOutput, AgentError> {
        // 阶段 1:初始化上下文
        let mut ctx = self.context_manager.write().await;
        ctx.initialize(task.system_prompt.clone(), task.user_input.clone());
        drop(ctx);

        // 阶段 2:执行循环
        let max_iterations = task.max_iterations;
        for iteration in 0..max_iterations {
            // 2a. 推理(带降级)
            let reasoning = self.execute_inference_with_fallback(&task).await?;

            // 2b. 解析计划
            let plan = self.parse_plan(&reasoning)?;

            // 2c. 检查终止条件
            if plan.is_final {
                return Ok(AgentOutput::from(plan));
            }

            // 2d. 执行工具调用(带重试和错误恢复)
            let tool_results = self.execute_tools_with_resilience(&plan.tools).await?;

            // 2e. 更新上下文
            let mut ctx = self.context_manager.write().await;
            ctx.append_tool_results(&tool_results);

            // 2f. 上下文健康检查
            if ctx.is_context_overflowing() {
                ctx.compact(); // 压缩历史
                self.metrics.record_compaction();
            }
        }

        Err(AgentError::MaxIterationsReached(max_iterations))
    }

    async fn execute_inference_with_fallback(&self, task: &Task) -> Result<String, AgentError> {
        // 首先尝试主模型
        match self.inference.complete(task.to_completion_request()).await {
            Ok(r) => {
                self.circuit_breaker.record_success();
                Ok(r.content)
            }
            Err(e) if e.is_timeout() => {
                self.circuit_breaker.record_failure();

                // 降级到小模型
                let mut degraded = task.clone();
                degraded.model = "fallback-model";
                degraded.max_tokens = degraded.max_tokens / 4;

                self.inference.complete(degraded.to_completion_request())
                    .await
                    .map(|r| r.content)
                    .map_err(|e| AgentError::DegradationFailed(e.to_string()))
            }
            Err(e) => Err(e.into()),
        }
    }

    async fn execute_tools_with_resilience(&self, tools: &[ToolCall]) -> Vec<ToolResult> {
        let futures = tools.iter().map(|call| {
            let executor = self.tool_executor.clone();
            let retry = self.retry_policy.clone();
            async move {
                retry.retry(|| executor.execute(call.clone())).await
            }
        });

        futures::future::join_all(futures).await
    }
}

监控与可观测性

混沌工程的闭环需要完善的监控。对 AI Agent 系统,传统的 RED 指标是不够的。我们还需要追踪以下 Agent 特有指标:

/// Agent 可观测性指标
pub struct AgentMetrics {
    /// 推理降级次数(按级别)
    degradation_counter: CounterVec,
    /// 上下文压缩率
    compaction_histogram: Histogram,
    /// 幻觉工具调用率
    hallucination_rate: Gauge,
    /// 任务完成率(按复杂度级别)
    completion_rate: CounterVec,
    /// 故障注入验证通过的韧性分数
    resilience_score: Gauge,
    /// 平均恢复时间
    recovery_time: Histogram,
}

impl AgentMetrics {
    /// 计算韧性分数(0-100)
    pub fn calculate_resilience(&self) -> f64 {
        let completion = self.completion_rate.get("success");
        let total = completion + self.completion_rate.get("failed");

        let recovery_avg = self.recovery_time.mean();
        let degradation_penalty = self.degradation_counter.get("total") as f64 * 5.0;

        let base_score = (completion / total) * 100.0;
        base_score - degradation_penalty - (recovery_avg / 1000.0)
    }
}

此外,我们建议对 Agent 的每次失败执行自动根因分析(Root Cause Analysis):

/// 失败根因分类
#[derive(Debug, Clone)]
pub enum FailureRootCause {
    LLMTimeout { model: String, latency_ms: u64 },
    HallucinatedTool { tool_name: String },
    ContextOverflow { tokens_before: usize, after_compaction: usize },
    ToolSchemaMismatch { tool_name: String, expected: String },
    ExternalServiceDown { service_name: String },
    Deadlock { agents_involved: Vec<String> },
    ClassificationFailed,
}

pub struct AgentFailureAnalyzer;

impl AgentFailureAnalyzer {
    pub fn analyze(trace: &AgentTrace) -> FailureRootCause {
        // 决策树分类
        if trace.has_deadlock_cycle() {
            return FailureRootCause::Deadlock { /* ... */ };
        }

        if let Some(tool_error) = trace.last_tool_error() {
            if tool_error.is_hallucinated() {
                return FailureRootCause::HallucinatedTool {
                    tool_name: tool_error.tool_name.clone(),
                };
            }
            if tool_error.is_schema_mismatch() {
                return FailureRootCause::ToolSchemaMismatch {
                    tool_name: tool_error.tool_name.clone(),
                    expected: tool_error.schema.clone(),
                };
            }
        }

        if trace.max_latency > Duration::from_secs(30) {
            return FailureRootCause::LLMTimeout {
                model: trace.model_used.clone(),
                latency_ms: trace.max_latency.as_millis() as u64,
            };
        }

        if trace.compaction_count > 0 {
            return FailureRootCause::ContextOverflow {
                tokens_before: trace.peak_tokens,
                after_compaction: trace.current_tokens,
            };
        }

        FailureRootCause::ClassificationFailed
    }
}

在混沌中寻找确定性

AI Agent 系统的混沌工程,本质上是在不确定的推理过程中构建确定性。我们通过三个层面实现这一目标:

  1. 注入层:精确模拟 AI 特有故障——LLM 格式化崩溃、幻觉工具调用、多智能体死锁——让这些问题在预生产环境暴露。
  2. 运行时层:通过熔断器、模型降级、上下文压缩和智能重试,使 Agent 具备自主恢复能力。
  3. 可观测层:超越传统 RED 指标,追踪 Agent 特有的韧性分数,实现基于故障模式的自动根因分析。

最终,一个弹性的 AI Agent 系统,不是永远不出错的系统,而是在出错时能优雅降级、自主恢复、并让运维人员立刻知道出了什么错的系统。

正如系统工程师的一句古老格言所说:"混沌工程的目标不是证明系统在故障时不会崩溃——而是学会在崩溃中优雅起舞。"


参考资料

  1. Netflix Chaos Engineering 经典论文: "Chaos Engineering" (Netflix Tech Blog, 2011)
  2. AWS Well-Architected Framework - Reliability Pillar
  3. OpenTelemetry Semantic Conventions for GenAI
  4. Google SRE Book - Chapter 22: Handling Overload
  5. "Resilient AI Agents" patterns from Anthropic's multi-agent research
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部