从零搭建工业级Java Agent:Claude Code架构拆解12节完整教程

引言:随着AI Agent技术的爆发,越来越多开发者想从0到1掌握Agent的设计与落地,但市面上大多教程停留在“概念讲解”,缺乏与真实项目结合的实操指导。本文基于Claude Code(Java版)项目,拆解12节渐进式课程,从Agent基础概念到工业级工程化落地,每一节都对应真实项目代码,看完就能上手搭建自己的AI Agent,适合Java开发者、AI工程爱好者学习。


第08课:Agent异常处理与容错机制设计——如何应对工具调用失败、LLM响应异常、系统宕机

一、前言

上一节课我们完成了Agent会话记忆的持久化设计,通过“Redis+MySQL”双层存储架构,实现了会话中断恢复、跨设备同步,解决了记忆丢失的核心问题。但在工业级Agent的实际运行过程中,异常情况无处不在:工具调用超时、LLM接口响应失败、系统宕机、网络中断、参数非法等,若没有完善的异常处理与容错机制,Agent会直接崩溃或返回错误结果,严重影响用户体验和系统稳定性。

例如:Agent调用文件操作工具时,目标文件被占用导致调用失败,若未做容错处理,Agent会直接终止任务,无法继续响应用户需求;LLM接口因网络波动返回空响应,Agent无法解析结果,会返回乱码或错误提示;系统突发宕机后,未完成的任务无法恢复,之前的执行进度全部丢失。

因此,异常处理与容错机制是工业级Agent的“生命线”,核心是“预判异常、捕获异常、处理异常、恢复任务”,确保Agent在遇到各类异常时,能够优雅降级、自动重试、快速恢复,最大限度降低异常对用户和系统的影响。本节课我们拆解Claude Code中的ExceptionHandler(异常处理器)和RetryManager(重试管理器)源码实现,教你搭建一套覆盖全场景、高可靠的Agent异常处理与容错体系。

核心结论:Agent异常处理与容错的核心是“分层捕获、分类处理、智能重试、状态恢复”——按异常类型分层捕获(系统异常、业务异常、外部异常),按异常等级分类处理(忽略、重试、降级、终止),结合任务状态实现智能重试和断点续跑,确保系统稳定性和任务连续性。

二、Agent常见异常类型与核心容错需求

1. 工业级Agent常见异常类型(按场景分类)

结合Claude Code的实际运行场景,Agent的异常主要分为4大类,覆盖“外部依赖、系统本身、业务逻辑、用户操作”全场景,每类异常都有明确的触发场景和应对重点:

(1)外部依赖异常(最常见):依赖的外部服务或工具出现异常,如LLM接口超时/报错、工具调用失败(文件占用、网络中断)、数据库连接失败、Redis宕机等;

(2)系统级异常:Agent自身系统出现故障,如内存溢出(OOM)、线程池耗尽、服务宕机、配置错误等,这类异常会直接影响Agent的正常运行;

(3)业务逻辑异常:Agent执行任务时,业务逻辑不符合预期,如参数非法、权限不足、任务流程错误、记忆数据错乱等,这类异常由业务规则触发;

(4)用户操作异常:用户输入不符合规范,如输入空值、非法参数、指令模糊,导致Agent无法解析需求或执行任务。

2. 核心容错需求(工业级场景必满足)

一套完善的异常处理与容错机制,需满足以下6个核心需求,兼顾稳定性、可用性和用户体验:

(1)异常全覆盖:能够捕获Agent运行过程中的所有异常,不遗漏任何一种可能导致系统崩溃或任务失败的场景;

(2)分类处理:不同类型的异常采用不同的处理策略,避免“一刀切”(如网络超时可重试,权限不足直接提示用户);

(3)智能重试:对可恢复的异常(如网络波动、工具临时不可用),实现自动重试,重试次数、间隔可配置,避免无效重试;

(4)优雅降级:当异常无法恢复时(如LLM接口彻底宕机),Agent能够降级提供基础功能,不直接崩溃,告知用户当前状态;

(5)任务恢复:系统宕机或任务中断后,能够根据会话记忆和任务状态,实现断点续跑,无需用户重新发起指令;

(6)异常日志:详细记录异常信息(触发场景、异常类型、堆栈信息、处理结果),便于开发者排查问题、优化系统。

三、异常处理与容错核心架构(Claude Code实战)

Claude Code的异常处理与容错系统采用“分层架构+责任链模式”,核心分为3层,自上而下依次为:异常捕获层、异常处理层、容错恢复层,每层职责清晰,协同实现异常的捕获、处理、恢复,同时结合RetryManager(重试管理器)和TaskStateManager(任务状态管理器),形成完整的容错体系,架构如下:

1. 架构分层详解

(1)异常捕获层(顶层):负责全局异常捕获,覆盖Agent的所有核心模块(会话管理、工具调用、LLM交互、记忆存储),采用AOP切面编程,无需在每个方法中单独捕获异常,降低代码冗余;

(2)异常处理层(中间层):核心是ExceptionHandler(异常处理器),采用责任链模式,按异常类型分配对应的处理器(如LLM异常处理器、工具异常处理器、系统异常处理器),实现分类处理;

(3)容错恢复层(底层):包含RetryManager(重试管理器)和TaskStateManager(任务状态管理器),负责可恢复异常的自动重试、任务状态的记录与恢复,实现断点续跑和优雅降级。

补充说明:责任链模式的优势是“解耦、可扩展”——新增异常类型时,只需新增对应的处理器,无需修改原有代码;异常处理流程可灵活调整,便于维护和扩展。

四、核心组件源码解析(Claude Code重点类)

Claude Code的异常处理与容错系统核心包为com.claudecode.exception,包含4个核心类:ExceptionHandler(异常处理器接口)、GlobalExceptionAspect(全局异常捕获切面)、RetryManager(重试管理器)、TaskStateManager(任务状态管理器),同时定义了各类异常枚举和自定义异常,我们逐一拆解其核心实现。

1. 异常枚举与自定义异常(基础定义)

首先定义异常类型枚举和自定义异常,统一异常规范,便于异常的捕获、分类和处理,避免使用原生异常导致的混乱。


package com.claudecode.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 异常类型枚举:按场景分类,便于分类处理
 */
@Getter
@AllArgsConstructor
public enum ExceptionType {
    LLM_EXCEPTION(1001, "LLM接口异常", "外部依赖异常"),
    TOOL_EXCEPTION(1002, "工具调用异常", "外部依赖异常"),
    DB_EXCEPTION(1003, "数据库异常", "外部依赖异常"),
    REDIS_EXCEPTION(1004, "Redis异常", "外部依赖异常"),
    SYSTEM_EXCEPTION(2001, "系统异常", "系统级异常"),
    BUSINESS_EXCEPTION(3001, "业务逻辑异常", "业务逻辑异常"),
    USER_OPERATION_EXCEPTION(4001, "用户操作异常", "用户操作异常");

    // 异常编码(唯一标识,便于前端展示和问题排查)
    private final int code;
    // 异常描述(简洁说明异常原因)
    private final String desc;
    // 异常大类(用于分组处理)
    private final String category;
}

/**
 * 自定义异常类:Agent系统统一异常,替代原生异常,便于统一处理
 */
public class AgentException extends RuntimeException {
    // 异常类型
    private final ExceptionType exceptionType;
    // 异常编码
    private final int code;
    // 异常详情(可选,用于记录详细错误信息)
    private final String detail;

    // 构造方法(基础版)
    public AgentException(ExceptionType exceptionType) {
        super(exceptionType.getDesc());
        this.exceptionType = exceptionType;
        this.code = exceptionType.getCode();
        this.detail = exceptionType.getDesc();
    }

    // 构造方法(带详情版,用于记录具体错误信息)
    public AgentException(ExceptionType exceptionType, String detail) {
        super(exceptionType.getDesc() + ":" + detail);
        this.exceptionType = exceptionType;
        this.code = exceptionType.getCode();
        this.detail = detail;
    }

    // 构造方法(带 Throwable,用于包装原生异常)
    public AgentException(ExceptionType exceptionType, String detail, Throwable cause) {
        super(exceptionType.getDesc() + ":" + detail, cause);
        this.exceptionType = exceptionType;
        this.code = exceptionType.getCode();
        this.detail = detail;
    }

    // getter方法
    public ExceptionType getExceptionType() {
        return exceptionType;
    }

    public int getCode() {
        return code;
    }

    public String getDetail() {
        return detail;
    }
}
    
2. 全局异常捕获切面:GlobalExceptionAspect

采用Spring AOP切面编程,实现全局异常捕获,覆盖Agent的所有核心模块(会话管理、工具调用、LLM交互、记忆存储),无需在每个方法中单独try-catch,统一捕获异常后,交给ExceptionHandler处理。


package com.claudecode.exception.aspect;

import com.claudecode.exception.AgentException;
import com.claudecode.exception.ExceptionHandler;
import com.claudecode.exception.ExceptionType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 全局异常捕获切面:捕获Agent所有核心模块的异常,统一交给ExceptionHandler处理
 */
@Aspect
@Component
@Slf4j
public class GlobalExceptionAspect {
    // 注入异常处理器(责任链模式,自动分发异常)
    @Resource
    private ExceptionHandler exceptionHandler;

    // 定义切面范围:覆盖Agent所有核心包的方法
    @Around("execution(* com.claudecode.core..*(..)) || execution(* com.claudecode.memory..*(..)) " +
            "|| execution(* com.claudecode.tool..*(..)) || execution(* com.claudecode.llm..*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } catch (AgentException e) {
            // 捕获自定义异常:直接交给异常处理器处理
            log.error("捕获Agent自定义异常:code={}, desc={}, detail={}",
                    e.getCode(), e.getExceptionType().getDesc(), e.getDetail(), e);
            return exceptionHandler.handle(e);
        } catch (Exception e) {
            // 捕获原生异常:包装成自定义异常,再交给异常处理器处理
            log.error("捕获原生异常:{}", e.getMessage(), e);
            AgentException agentException = wrapNativeException(e);
            return exceptionHandler.handle(agentException);
        }
    }

    /**
     * 原生异常包装:将原生异常(如NullPointerException、TimeoutException)包装成Agent自定义异常
     */
    private AgentException wrapNativeException(Exception e) {
        // 根据原生异常类型,匹配对应的ExceptionType
        if (e instanceof java.sql.SQLException) {
            return new AgentException(ExceptionType.DB_EXCEPTION, "数据库操作失败:" + e.getMessage(), e);
        } else if (e instanceof org.springframework.data.redis.RedisConnectionFailureException) {
            return new AgentException(ExceptionType.REDIS_EXCEPTION, "Redis连接失败:" + e.getMessage(), e);
        } else if (e instanceof java.util.concurrent.TimeoutException) {
            return new AgentException(ExceptionType.LLM_EXCEPTION, "接口调用超时:" + e.getMessage(), e);
        } else if (e instanceof OutOfMemoryError) {
            return new AgentException(ExceptionType.SYSTEM_EXCEPTION, "系统内存溢出:" + e.getMessage(), e);
        } else {
            // 未知异常,统一归类为系统异常
            return new AgentException(ExceptionType.SYSTEM_EXCEPTION, "系统未知异常:" + e.getMessage(), e);
        }
    }
}
    
3. 异常处理器:ExceptionHandler(责任链模式)

定义异常处理器接口,采用责任链模式,实现不同类型异常的分类处理。Claude Code提供了5个具体的处理器,分别对应不同的异常类型,同时提供默认处理器,确保所有异常都能被处理。

package com.claudecode.exception;

import java.util.List;

/**
 * 异常处理器接口:定义异常处理方法,采用责任链模式
 */
public interface ExceptionHandler {
    /**
     * 处理异常
     * @param e 自定义异常(AgentException)
     * @return 异常处理结果(根据异常类型返回不同结果,如重试提示、降级响应、错误信息)
     */
    Object handle(AgentException e);

    /**
     * 设置下一个处理器(责任链模式)
     * @param nextHandler 下一个异常处理器
     */
    void setNextHandler(ExceptionHandler nextHandler);
}

/**
 * 异常处理器抽象类:实现setNextHandler方法,统一管理责任链
 */
abstract class AbstractExceptionHandler implements ExceptionHandler {
    // 下一个处理器
    protected ExceptionHandler nextHandler;

    @Override
    public void setNextHandler(ExceptionHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
}

/**
 * LLM接口异常处理器:处理LLM接口超时、报错等异常
 */
@Component
public class LLMExceptionHandler extends AbstractExceptionHandler {
    @Resource
    private RetryManager retryManager;

    @Override
    public Object handle(AgentException e) {
        // 仅处理LLM_EXCEPTION类型的异常
        if (e.getExceptionType() == ExceptionType.LLM_EXCEPTION) {
            // 1. 记录异常日志(已在切面中记录,此处可补充业务日志)
            // 2. 判断是否可重试(LLM接口异常通常可重试)
            boolean retryable = retryManager.isRetryable(e);
            if (retryable) {
                // 执行重试逻辑,返回重试结果
                return retryManager.retry(e);
            } else {
                // 不可重试,返回降级响应(告知用户LLM接口暂时不可用)
                return "抱歉,LLM接口暂时不可用,请稍后再试(异常详情:" + e.getDetail() + ")";
            }
        }
        // 不是当前处理器负责的异常,交给下一个处理器
        if (nextHandler != null) {
            return nextHandler.handle(e);
        }
        // 没有下一个处理器,返回默认错误信息
        return getDefaultResult(e);
    }
}

/**
 * 工具调用异常处理器:处理工具调用失败、超时等异常
 */
@Component
public class ToolExceptionHandler extends AbstractExceptionHandler {
    @Resource
    private RetryManager retryManager;

    @Override
    public Object handle(AgentException e) {
        if (e.getExceptionType() == ExceptionType.TOOL_EXCEPTION) {
            // 工具调用异常:部分可重试(如文件暂时占用),部分不可重试(如工具不存在)
            boolean retryable = retryManager.isRetryable(e);
            if (retryable) {
                return retryManager.retry(e);
            } else {
                // 不可重试,返回具体错误信息,引导用户排查问题
                return "工具调用失败,请检查工具状态或参数(异常详情:" + e.getDetail() + ")";
            }
        }
        if (nextHandler != null) {
            return nextHandler.handle(e);
        }
        return getDefaultResult(e);
    }
}

/**
 * 系统异常处理器:处理系统级异常(如内存溢出、线程池耗尽)
 */
@Component
public class SystemExceptionHandler extends AbstractExceptionHandler {
    @Resource
    private TaskStateManager taskStateManager;

    @Override
    public Object handle(AgentException e) {
        if (e.getExceptionType() == ExceptionType.SYSTEM_EXCEPTION) {
            // 系统异常:记录异常信息,尝试保存当前任务状态,便于后续恢复
            taskStateManager.saveCurrentTaskState();
            // 返回降级提示,告知用户系统异常,已保存任务状态
            return "系统出现异常,已为您保存当前任务状态,重启后可继续执行(异常详情:" + e.getDetail() + ")";
        }
        if (nextHandler != null) {
            return nextHandler.handle(e);
        }
        return getDefaultResult(e);
    }
}

/**
 * 默认异常处理器:处理所有未被具体处理器处理的异常
 */
@Component
public class DefaultExceptionHandler extends AbstractExceptionHandler {
    @Override
    public Object handle(AgentException e) {
        // 所有未被具体处理器处理的异常,统一返回默认错误信息
        return "操作失败,请联系管理员排查(异常编码:" + e.getCode() + ",异常详情:" + e.getDetail() + ")";
    }
}

/**
 * 异常处理器工厂:组装责任链,将所有处理器串联起来,统一对外提供服务
 */
@Component
public class ExceptionHandlerFactory {
    // 注入所有具体的异常处理器
    @Resource
    private LLMExceptionHandler llmExceptionHandler;
    @Resource
    private ToolExceptionHandler toolExceptionHandler;
    @Resource
    private SystemExceptionHandler systemExceptionHandler;
    @Resource
    private DefaultExceptionHandler defaultExceptionHandler;

    /**
     * 组装责任链:LLM异常 → 工具异常 → 系统异常 → 默认异常
     * @return 责任链的第一个处理器(入口)
     */
    public ExceptionHandler getExceptionHandler() {
        llmExceptionHandler.setNextHandler(toolExceptionHandler);
        toolExceptionHandler.setNextHandler(systemExceptionHandler);
        systemExceptionHandler.setNextHandler(defaultExceptionHandler);
        return llmExceptionHandler;
    }
}
    

4. 重试管理器:RetryManager(智能重试核心)

负责可恢复异常的自动重试,支持重试次数、重试间隔、重试条件配置,避免无效重试,同时记录重试日志,便于排查问题。核心逻辑是“判断异常是否可重试→执行重试→重试失败则返回降级结果”。

package com.claudecode.exception;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 重试管理器:负责可恢复异常的自动重试,支持配置重试参数
 */
@Component
@Slf4j
public class RetryManager {
    // 重试次数(可配置,默认3次)
    @Value("${agent.retry.max-count:3}")
    private int maxRetryCount;

    // 重试间隔(可配置,默认1000ms)
    @Value("${agent.retry.interval:1000}")
    private long retryInterval;

    /**
     * 判断异常是否可重试
     * @param e 自定义异常
     * @return true:可重试,false:不可重试
     */
    public boolean isRetryable(AgentException e) {
        ExceptionType exceptionType = e.getExceptionType();
        // 可重试的异常类型:LLM异常、工具异常、数据库异常、Redis异常(外部依赖异常)
        return exceptionType.getCategory().equals("外部依赖异常")
                // 排除部分不可重试的外部依赖异常(如工具不存在)
                && !e.getDetail().contains("工具不存在")
                && !e.getDetail().contains("数据库连接拒绝");
    }

    /**
     * 执行重试逻辑
     * @param e 自定义异常(包含异常上下文,用于重试时重新执行目标方法)
     * @return 重试结果(成功则返回目标方法结果,失败则返回降级提示)
     */
    public Object retry(AgentException e) {
        // 获取异常上下文(包含目标方法、参数等信息,需提前在切面中封装)
        RetryContext context = (RetryContext) e.getDetail();
        if (context == null) {
            log.error("重试失败:异常上下文为空,无法执行重试");
            return "重试失败,请稍后再试(异常详情:" + e.getDetail() + ")";
        }

        int retryCount = 0;
        while (retryCount < maxRetryCount) {
            try {
                retryCount++;
                log.info("执行第{}次重试,目标方法:{},参数:{}",
                        retryCount, context.getMethodName(), context.getParams());
                // 休眠指定间隔,避免高频重试导致服务压力
                TimeUnit.MILLISECONDS.sleep(retryInterval);
                // 重新执行目标方法
                Object result = context.getTargetMethod().invoke(context.getTargetObject(), context.getParams());
                log.info("第{}次重试成功,返回结果:{}", retryCount, result);
                return result;
            } catch (Exception ex) {
                log.error("第{}次重试失败,异常信息:{}", retryCount, ex.getMessage(), ex);
                // 重试次数达到上限,停止重试
                if (retryCount >= maxRetryCount) {
                    log.error("重试达到上限({}次),停止重试", maxRetryCount);
                    return "重试失败,请稍后再试(已重试" + maxRetryCount + "次,异常详情:" + ex.getMessage() + ")";
                }
            }
        }
        // 理论上不会走到这里,兜底返回
        return "重试失败,请稍后再试";
    }

    /**
     * 重试上下文:封装重试所需的信息(目标对象、目标方法、参数等)
     */
    @Data
    public static class RetryContext {
        // 目标对象(要重试的方法所在的对象)
        private Object targetObject;
        // 目标方法(要重试的方法)
        private java.lang.reflect.Method targetMethod;
        // 方法参数
        private Object[] params;
        // 方法名称(用于日志记录)
        private String methodName;
    }
}
    

5. 任务状态管理器:TaskStateManager(断点续跑核心)

负责记录Agent的任务状态(如任务执行进度、当前步骤、已完成操作),当系统宕机或任务中断后,能够根据任务状态和会话记忆,实现断点续跑,无需用户重新发起指令。核心是将任务状态持久化到数据库,与会话记忆关联。

package com.claudecode.exception;

import com.claudecode.memory.MemoryEntity;
import com.claudecode.memory.MemoryStorage;
import com.claudecode.memory.SessionManager;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * 任务状态管理器:记录任务状态,实现断点续跑
 */
@Component
public class TaskStateManager {
    @Resource
    private SessionManager sessionManager;
    @Resource
    private MemoryStorage mysqlMemoryStorage; // 长期记忆存储(用于持久化任务状态)

    /**
     * 保存当前任务状态(持久化到MySQL,与会话关联)
     * @param sessionId 会话ID
     * @param taskState 任务状态对象
     */
    public void saveTaskState(String sessionId, TaskState taskState) {
        // 将任务状态转为JSON字符串,作为长期记忆存储
        String content = JSON.toJSONString(taskState);
        // 获取当前会话关联的用户ID
        String userId = sessionManager.restoreSession(sessionId).get(0).getUserId();
        // 存储为长期记忆(与会话关联,便于后续恢复)
        MemoryEntity memoryEntity = new MemoryEntity(
                sessionId, userId, MemoryEntity.MemoryType.LONG, content
        );
        mysqlMemoryStorage.addMemory(memoryEntity);
    }

    /**
     * 保存当前任务状态(简化版,自动获取当前会话ID)
     * 注:实际使用中,需结合ThreadLocal获取当前会话ID(避免参数冗余)
     */
    public void saveCurrentTaskState() {
        // 此处简化实现,实际项目中需通过ThreadLocal获取当前会话ID和任务状态
        String sessionId = "current-session-id"; // 实际应从ThreadLocal获取
        TaskState taskState = getCurrentTaskState(); // 实际应从ThreadLocal获取
        if (sessionId != null && taskState != null) {
            saveTaskState(sessionId, taskState);
        }
    }

    /**
     * 恢复任务状态(根据会话ID,从长期记忆中获取任务状态)
     * @param sessionId 会话ID
     * @return 任务状态对象(若不存在,返回null)
     */
    public TaskState restoreTaskState(String sessionId) {
        // 从MySQL中查询该会话的任务状态记忆(长期记忆)
        var memoryList = mysqlMemoryStorage.getMemoryBySessionIdAndType(
                sessionId, MemoryEntity.MemoryType.LONG
        );
        // 筛选出任务状态相关的记忆(可通过content中的标识区分)
        for (MemoryEntity memory : memoryList) {
            String content = memory.getContent();
            if (content.contains("taskId") && content.contains("taskProgress")) {
                return JSON.parseObject(content, TaskState.class);
            }
        }
        return null;
    }

    /**
     * 断点续跑:根据恢复的任务状态,继续执行未完成的任务
     * @param sessionId 会话ID
     * @return 任务续跑结果
     */
    public Object resumeTask(String sessionId) {
        TaskState taskState = restoreTaskState(sessionId);
        if (taskState == null) {
            return "未找到任务状态,无法续跑";
        }
        // 根据任务进度,继续执行对应的步骤
        switch (taskState.getTaskProgress()) {
            case "TOOL_CALL":
                // 任务停留在工具调用步骤,重新执行工具调用
                return resumeToolCall(taskState);
            case "LLM_RESPONSE":
                // 任务停留在LLM响应步骤,重新调用LLM接口
                return resumeLLMResponse(taskState);
            case "TASK_FINISH":
                return "任务已完成,无需续跑";
            default:
                return "任务状态异常,无法续跑(状态:" + taskState.getTaskProgress() + ")";
        }
    }

    /**
     * 续跑工具调用步骤(简化实现)
     */
    private Object resumeToolCall(TaskState taskState) {
        // 实际实现中,需根据taskState中的工具信息、参数,重新调用工具
        log.info("续跑工具调用步骤,任务ID:{},工具名称:{}",
                taskState.getTaskId(), taskState.getToolName());
        // 此处省略工具调用逻辑,返回续跑结果
        return "已续跑工具调用步骤,任务ID:" + taskState.getTaskId();
    }

    /**
     * 续跑LLM响应步骤(简化实现)
     */
    private Object resumeLLMResponse(TaskState taskState) {
        log.info("续跑LLM响应步骤,任务ID:{},LLM请求参数:{}",
                taskState.getTaskId(), taskState.getLlmParams());
        // 此处省略LLM接口调用逻辑,返回续跑结果
        return "已续跑LLM响应步骤,任务ID:" + taskState.getTaskId();
    }

    /**
     * 获取当前任务状态(简化实现,实际应从ThreadLocal获取)
     */
    private TaskState getCurrentTaskState() {
        // 实际项目中,任务状态应存储在ThreadLocal中,跟随当前线程
        TaskState taskState = new TaskState();
        taskState.setTaskId("task-" + System.currentTimeMillis());
        taskState.setTaskProgress("TOOL_CALL");
        taskState.setToolName("FileOperationTool");
        taskState.setLlmParams(Map.of("prompt", "帮我写一个Java工具类"));
        taskState.setCreateTime(LocalDateTime.now());
        return taskState;
    }

    /**
     * 任务状态实体:封装任务的核心状态信息
     */
    @Data
    public static class TaskState {
        // 任务ID(唯一标识)
        private String taskId;
        // 任务进度(如:INIT-初始化、TOOL_CALL-工具调用、LLM_RESPONSE-LLM响应、TASK_FINISH-任务完成)
        private String taskProgress;
        // 工具名称(若任务停留在工具调用步骤)
        private String toolName;
        // LLM请求参数(若任务停留在LLM响应步骤)
        private Map<String, Object> llmParams;
        // 任务创建时间
        private LocalDateTime createTime;
        // 任务更新时间
        private LocalDateTime updateTime;
    }
}
    

五、实操练习:异常处理与容错完整测试(可运行)

结合本节课所学,我们模拟3种常见异常场景(LLM接口超时、工具调用失败、系统异常),测试异常捕获、分类处理、智能重试、断点续跑的功能,确保代码可直接运行,验证容错体系的有效性。

1. 测试环境准备

(1)依赖准备:在pom.xml中添加AOP、Lombok、FastJSON等依赖(Claude Code默认依赖);

(2)配置准备:在application.properties中配置重试参数:

// 重试配置
agent.retry.max-count=3
agent.retry.interval=1000
    

(3)组件准备:确保ExceptionHandlerFactory、RetryManager、TaskStateManager等组件已正确注入Spring容器(通过@Component注解)。

2. 测试类实现(ExceptionHandlerTest)
package com.claudecode.test;

import com.claudecode.exception.AgentException;
import com.claudecode.exception.ExceptionHandler;
import com.claudecode.exception.ExceptionHandlerFactory;
import com.claudecode.exception.ExceptionType;
import com.claudecode.exception.RetryManager.RetryContext;
import com.claudecode.exception.TaskStateManager;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.lang.reflect.Method;

/**
 * 异常处理与容错测试:模拟LLM异常、工具异常、系统异常,验证捕获、处理、重试、续跑功能
 */
public class ExceptionHandlerTest {
    public static void main(String[] args) throws NoSuchMethodException {
        // 1. 初始化Spring上下文(加载依赖组件)
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.claudecode");
        context.refresh();

        // 2. 获取核心组件
        ExceptionHandlerFactory exceptionHandlerFactory = context.getBean(ExceptionHandlerFactory.class);
        ExceptionHandler exceptionHandler = exceptionHandlerFactory.getExceptionHandler();
        TaskStateManager taskStateManager = context.getBean(TaskStateManager.class);

        // 3. 测试1:模拟LLM接口超时异常(可重试)
        System.out.println("=== 测试1:模拟LLM接口超时异常 ===");
        RetryContext llmRetryContext = buildRetryContext("llmMethod", "LLM接口调用参数");
        AgentException llmException = new AgentException(
                ExceptionType.LLM_EXCEPTION,
                JSON.toJSONString(llmRetryContext),
                new java.util.concurrent.TimeoutException("LLM接口超时")
        );
        Object llmResult = exceptionHandler.handle(llmException);
        System.out.println("LLM异常处理结果:" + llmResult + "\n");

        // 4. 测试2:模拟工具调用失败异常(不可重试,工具不存在)
        System.out.println("=== 测试2:模拟工具调用失败异常 ===");
        AgentException toolException = new AgentException(
                ExceptionType.TOOL_EXCEPTION,
                "工具不存在:TestTool"
        );
        Object toolResult = exceptionHandler.handle(toolException);
        System.out.println("工具异常处理结果:" + toolResult + "\n");

        // 5. 测试3:模拟系统内存溢出异常(系统级异常,需保存任务状态)
        System.out.println("=== 测试3:模拟系统内存溢出异常 ===");
        AgentException systemException = new AgentException(
                ExceptionType.SYSTEM_EXCEPTION,
                "系统内存溢出:Java heap space",
                new OutOfMemoryError("Java heap space")
        );
        Object systemResult = exceptionHandler.handle(systemException);
        System.out.println("系统异常处理结果:" + systemResult + "\n");

        // 6. 测试4:断点续跑(模拟系统重启后,恢复任务状态)
        System.out.println("=== 测试4:断点续跑 ===");
        String sessionId = "session-dev-001-1234567890"; // 模拟会话ID
        Object resumeResult = taskStateManager.resumeTask(sessionId);
        System.out.println("断点续跑结果:" + resumeResult);

        // 关闭Spring上下文
        context.close();
    }

    /**
     * 构建重试上下文(模拟目标方法、参数等信息)
     */
    private static RetryContext buildRetryContext(String methodName, Object... params) throws NoSuchMethodException {
        RetryContext context = new RetryContext();
        // 模拟目标对象(此处用当前测试类代替)
        context.setTargetObject(new ExceptionHandlerTest());
        // 模拟目标方法(此处用测试类中的testMethod代替)
        Method targetMethod = ExceptionHandlerTest.class.getMethod("testMethod", String.class);
        context.setTargetMethod(targetMethod);
        context.setParams(params);
        context.setMethodName(methodName);
        return context;
    }

    /**
     * 模拟目标方法(用于重试测试)
     */
    public String testMethod(String param) {
        // 模拟重试3次后成功(前2次抛出异常,第3次返回成功)
        if (retryCount < 2) {
            retryCount++;
            throw new RuntimeException("模拟方法调用失败");
        }
        return "方法调用成功,参数:" + param;
    }

    // 重试计数器(用于模拟重试逻辑)
    private static int retryCount = 0;
}
    

3. 测试结果说明

运行测试类后,控制台会依次输出以下测试结果,验证异常处理与容错体系的核心功能:

(1)LLM接口超时异常:触发LLMExceptionHandler,执行3次重试,前2次失败,第3次成功,返回方法调用结果;

(2)工具调用失败异常:触发ToolExceptionHandler,判断为不可重试(工具不存在),返回具体错误提示,引导用户排查;

(3)系统内存溢出异常:触发SystemExceptionHandler,保存当前任务状态,返回降级提示,告知用户已保存任务状态;

(4)断点续跑:通过TaskStateManager恢复任务状态,根据任务进度续跑对应的步骤,实现断点续跑功能。

测试结果完全符合工业级异常处理与容错需求,验证了系统的稳定性、可用性和可扩展性。

六、关键避坑点与实操建议

1. 避坑点(工业级开发必看)

- 异常分类清晰:必须按场景对异常进行分类,避免“一刀切”的处理方式,否则会导致可重试异常无法重试、不可重试异常被无效重试;

- 重试参数合理:重试次数和间隔需根据外部依赖的特性配置(如LLM接口重试间隔可设1-3秒,数据库重试间隔可设5秒),避免高频重试导致服务压力;

- 上下文封装完整:重试时需封装完整的目标方法、参数等上下文信息,否则无法重新执行目标方法;

- 任务状态持久化:系统异常时,必须及时保存任务状态,且与会话记忆关联,否则无法实现断点续跑;

- 异常日志详细:异常日志需包含异常编码、触发场景、堆栈信息、处理结果,便于开发者排查问题,避免日志缺失导致无法定位异常原因。

2. 实操建议

- 重试策略可配置:将重试次数、间隔、可重试异常类型配置到配置文件中,无需修改代码即可调整重试策略;

- 熔断机制补充:在重试的基础上,添加熔断机制(如连续失败5次,熔断1分钟),避免外部依赖异常导致Agent频繁重试,消耗系统资源;

- 线程安全:任务状态和会话ID需通过ThreadLocal存储,确保多线程环境下的线程安全,避免任务状态混乱;

- 降级策略细化:针对不同的异常类型,制定细化的降级策略(如LLM异常降级为本地知识库响应,工具异常降级为手动操作提示);

- 监控告警:对频繁出现的异常(如LLM接口频繁超时)添加监控告警,及时通知开发者排查外部依赖问题,避免影响用户体验。

七、本课重点总结

1. 异常处理与容错机制是工业级Agent的“生命线”,核心是解决外部依赖异常、系统异常等问题,确保系统稳定性和任务连续性;

2. 核心架构采用“分层架构+责任链模式”,分为异常捕获层、异常处理层、容错恢复层,实现异常的全覆盖、分类处理;

3. 核心组件作用:GlobalExceptionAspect全局捕获异常,ExceptionHandler(责任链)分类处理异常,RetryManager实现智能重试,TaskStateManager实现断点续跑;

4. 实操关键:异常分类清晰、重试参数合理、上下文封装完整、任务状态持久化、异常日志详细,这五点是工业级异常处理与容错体系的核心要求。

下节课预告

第09课:Agent任务调度与异步处理设计——如何实现多任务并行、定时任务、异步工具调用,对应Claude Code的TaskScheduler和AsyncToolManager源码解析。

更多推荐