gogo-agent

工具并行执行&失败重试

在这套差旅助手里,Agent 每一轮 reasoning 之后往往要调用一批工具:查差旅政策、查订单、查目的地天气/资讯、调外部平台(tuniu / 签证 MCP)、甚至把子 Agent 当工具委托出去。这些调用有两个共同特征,直接…

TL;DR

在这套差旅助手里,Agent 每一轮 reasoning 之后往往要调用一批工具:查差旅政策、查订单、查目的地天气/资讯、调外部平台(tuniu / 签证 MCP)、甚至把子 Agent 当工具委托出去。这些调用有两个共同特征,直接…

在这套差旅助手里,Agent 每一轮 reasoning 之后往往要调用一批工具:查差旅政策、查订单、查目的地天气/资讯、调外部平台(tuniu / 签证 MCP)、甚至把子 Agent 当工具委托出去。这些调用有两个共同特征,直接决定了性能优化必须落在"工具层": 第一,多数工具是 I/O 密集且相互独立——查政策和查天气之间没有依赖,如果一个个串行等,端到端延迟就是各工具耗时之和;能并行发出去,延迟就压缩到最慢那个。 第二,很多工具依赖不可控的外部服务——外部 API 会超时、会 5xx、会限流。没有重试,一次网络抖动就让整轮对话失败;没有熔断,一个持续挂掉的下游会被 LLM 反复选中、反复超时,既拖慢响应又浪费 Token。 gogo-agent 的应对是分层治理:用 ToolkitConfig.parallel(true) 在调度层并行发起工具调用来抢延迟;用框架的 ExecutionConfig 给单次工具调用做"超时 + 有限重试 + 退避"来吸收瞬时抖动;用自研的 ToolCircuitBreakerHook 给持续失败的工具做"熔断卸载 + 指数退避恢复"来止损。下面逐层看真实代码。

并行执行

并行不是全局默认,而是按 Agent 显式开启。开关就是 ToolkitConfig 的一个布尔位。 agent/ItineraryPlanAgent.java(第 63–69 行):

@Bean(name = "itineraryPlanAgent")
@Scope("prototype")
public ReActAgent build() {
    // 开启并行执行
    Toolkit toolkit = new Toolkit(ToolkitConfig.builder()
            .parallel(true)          // ★ 唯一的并行开关:本轮多个工具调用并行发起
            .build());
    toolkit.registration().tool(policyTools).apply();
    toolkit.registration().tool(travelOrderReadTools).apply();
    toolkit.registration().tool(bookingReadTools).apply();
    toolkit.registration().tool(queryUserInfoTools).apply();
    toolkit.registration().tool(apiKeyTools).apply();
    toolkit.registration().tool(destinationLiveTools).apply();
    toolkit.registration().tool(itineraryPlannerTool).apply();
    toolkit.registration().mcpClient(weatherMcpClient).enableTools(weatherMcpEnabledTools).apply();
    toolkit.registration().mcpClient(oriznVisaMcpClient).enableTools(oriznMcpEnabledTools).apply();
    // …(下略:子 Agent 作为工具、SkillBox、PlanNotebook)

agent/BookingAgent.java(第 80–82 行)用的是同一套写法:

Toolkit toolkit = new Toolkit(ToolkitConfig.builder()
        .parallel(true)
        .build());

关键在于只有这两个 Agent 开了并行——它们恰好是工具最多、外部依赖最重的:规划 Agent 挂了政策/订单/预订/用户/ApiKey/目的地实况/规划器 7 个本地工具 + 2 个 MCP + 1 个审核子 Agent;预订 Agent 要同时摸订单、预订记录、取消、用户、ApiKey。工具越多、越独立,并行省下的延迟越可观。 作为对照,编排层和轻量 Agent 都用默认串行的 new Toolkit()(无 ToolkitConfig):InfoAgent(第 53 行)、MasterAgent、ItineraryManageAgent、ItineraryReviewAgent 均是如此。这是一个务实的取舍:MasterAgent 主要做意图路由、几乎只把子 Agent 当工具挨个调,并行没意义;信息类 Agent 工具少,串行也够快,反而省去并行带来的上下文透传与并发心智。 需要说明并行的边界:这里的并行发生在 AgentScope 框架的工具调用调度层(框架负责把本轮 LLM 决策出的多个 tool call 并发执行)。工具内部的纯计算并不会自动并行。

工具失败重试

单次工具调用的容错交给 AgentScope 框架的 ExecutionConfig,在基类里集中定义、各子 Agent 复用。BaseSubAgent.java(常量第 35–43 行,方法第 165–172 行)

/** 子 Agent 工具执行超时(分钟) */
protected static final int TOOL_TIMEOUT_MINUTES = 1;
/** 子 Agent 工具最大尝试次数(含首次) */
protected static final int TOOL_MAX_ATTEMPTS = 3;
/** 子 Agent 工具初始退避时间(秒) */
protected static final int TOOL_INITIAL_BACKOFF_SECONDS = 3;

/**
 * 子类默认的工具执行配置:最多等 1 分钟、最多 3 次尝试(含首次)、初始退避 3 秒、仅网络错误时重试。
 */
public ExecutionConfig getToolConfig() {
    return ExecutionConfig.builder()
            .timeout(Duration.ofMinutes(TOOL_TIMEOUT_MINUTES))         // 单次工具最多等 1 分钟
            .maxAttempts(TOOL_MAX_ATTEMPTS)                            // 含首次,最多 3 次
            .initialBackoff(Duration.ofSeconds(TOOL_INITIAL_BACKOFF_SECONDS))  // 首次退避 3s
            .retryOn(error -> error instanceof java.io.IOException)    // ★ 只对网络类 IOException 重试
            .build();
}

超时给到分钟级——因为工具里包含"子 Agent 当工具调"这种重活(一次可能是几十秒的 LLM 推理),所以超时放宽到 1 分钟而非秒级。 retryOn 只认 IOException——只有网络类瞬时故障才值得重试,业务性错误(参数不对、无权限)重试没有意义、还浪费时间,所以被排除在外。 带退避——首次失败等 3 秒再试,避免抖动瞬间对下游"火上浇油"。 装配方式是各子 Agent 在 builder 上 .toolExecutionConfig(getToolConfig()):ItineraryPlanAgent(第 130 行)、BookingAgent(第 115 行)、InfoAgent(第 76 行)等都复用这份基类配置。

工具熔断

✅引入工具级熔断机制避免重复失败

先看一个真实痛点。ItineraryPlanAgent 在规划行程时会调天气工具,如果工具存在网络抖动、服务不可用、连续返回错误,会发生什么? ReAct 循环是「推理 → 调工具 → 看结果 → 再推理」的闭环。工具报错后,模型看到错误信 LLMentor

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。