
在早期的大模型函数调用Function Calling实现中Agent 与后端系统的交互极其笨拙且缓慢。如果用户问了一句复杂的问题“帮我查下我的最新订单物流到哪了顺便看看我账户里还有没有可用的退货免运费券。”在串行工具调用模式下系统必须经历如下漫长的“乒乓球式”网络拉锯用户发提问 $\rightarrow$ 模型决定调用“查订单 Tool”后端执行查订单 RPC $\rightarrow$ 结果回传模型模型根据订单号决定调用“查物流 Tool”后端执行查物流 RPC $\rightarrow$ 结果回传模型模型再决定调用“查优惠券 Tool”后端执行查券 RPC $\rightarrow$ 结果回传模型模型最终生成文字回答。整个流程经历了整整4 轮大模型推理 3 次串行 RPC端到端耗时轻松突破 8 到 12 秒在讲求毫秒级体验的电商客服场景中用户早就关闭了窗口。随着现代大模型如 GPT-4o、DeepSeek-V4、GLM 5.3全面支持并行工具调用Parallel Tool Calling配合 Spring AI 1.x 与 Java 24 虚拟线程我们终于可以将这一多轮拉锯压缩为一次极速的并发聚合操作。并行工具调用的底层报文契约现代大模型在充分理解了用户复合意图后不会再“挤牙膏”式地一次只抛出一个函数。它会在单次响应的tool_calls数组中一口气下发多个需要并发执行的指令{ role: assistant, content: null, tool_calls: [ { id: call_ord_001, type: function, function: { name: queryLatestOrderFunction, arguments: {\userId\:\U8848123\} } }, { id: call_cpn_002, type: function, function: { name: queryUserCouponsFunction, arguments: {\userId\:\U8848123\,\couponType\:\FREIGHT_INSURANCE\} } } ] }注意这里大模型在一个响应里同时派发了两个工具调用并分配了唯一的call_id。后端必须分别并发执行这两个工具并将它们的结果分别封装为独立的ToolResponseMessage回填给模型。结合 Java 24 虚拟线程的并发执行器实现如果后端在收到这两个工具调用后依旧用 for 循环串行执行那就暴殄天物了。在 Java 24 环境下我们利用轻量级虚拟线程Virtual Thread构建高并发聚合调度器Component public class ParallelToolExecutionDispatcher { private static final Logger log LoggerFactory.getLogger(ParallelToolExecutionDispatcher.class); // 专属的虚拟线程执行器为每个并发工具分配一个虚拟线程 private final ExecutorService toolExecutor Executors.newVirtualThreadPerTaskExecutor(); private final ApplicationContext applicationContext; public ParallelToolExecutionDispatcher(ApplicationContext applicationContext) { this.applicationContext applicationContext; } public ListToolResponseMessage.ToolResponse dispatchParallel(ListAssistantMessage.ToolCall toolCalls) { log.info(收到大模型并行工具调用指令包含 {} 个并发任务, toolCalls.size()); // 1. 将所有工具调用封装为异步并行任务 ListCompletableFutureToolResponseMessage.ToolResponse futures toolCalls.stream() .map(call - CompletableFuture.supplyAsync(() - executeSingleTool(call), toolExecutor)) .toList(); // 2. 设置整体超时门限如 2.5 秒等待所有工具并发返回 CompletableFutureVoid allDone CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); try { allDone.get(2500, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.warn(部分工具执行超时触发快速降级返回); } catch (Exception e) { log.error(并发工具执行异常, e); } // 3. 收集所有成功或超时的执行结果 return futures.stream() .map(future - { try { return future.getNow(new ToolResponseMessage.ToolResponse(unknown, unknown, {\error\:\执行超时\})); } catch (Exception e) { return new ToolResponseMessage.ToolResponse(unknown, unknown, {\error\:\内部异常\}); } }) .toList(); } private ToolResponseMessage.ToolResponse executeSingleTool(AssistantMessage.ToolCall call) { String functionName call.name(); String argumentsJson call.arguments(); // 从 Spring 上下文查找对应的 Tool Bean 并执行 FunctionCallback callback applicationContext.getBean(functionName, FunctionCallback.class); String executionResult callback.call(argumentsJson); return new ToolResponseMessage.ToolResponse(call.id(), functionName, executionResult); } }生产级链路整合与 Spring AI 回填在 Spring AI 客户端中我们将并发执行完成的ListToolResponse打包成一条ToolResponseMessage作为大模型第二次推理的入参public ChatResponse executeAgentConversation(String userQuery) { // 1. 第一轮推理大模型生成多个并行 Tool Calls Prompt initialPrompt new Prompt(userQuery); ChatResponse responseStep1 chatModel.call(initialPrompt); AssistantMessage assistantMessage responseStep1.getResult().getOutput(); if (assistantMessage.hasToolCalls()) { ListAssistantMessage.ToolCall toolCalls assistantMessage.getToolCalls(); // 2. 虚拟线程并发拉取多个内部 RPC 接口数据 ListToolResponseMessage.ToolResponse toolResponses dispatcher.dispatchParallel(toolCalls); // 3. 构造回填上下文 ListMessage conversationMessages new ArrayList(); conversationMessages.add(new UserMessage(userQuery)); conversationMessages.add(assistantMessage); conversationMessages.add(new ToolResponseMessage(toolResponses)); // 4. 第二轮推理大模型吸收所有并行结果生成最终自然语言答案 return chatModel.call(new Prompt(conversationMessages)); } return responseStep1; }性能收益与实战避坑端到端耗时腰斩在双 11 智能助手压测中原本串行执行 3 个工具平均需要 4.2 秒采用并行工具调用 虚拟线程后整体耗时取决于最慢的那个 RPC 接口约 800ms加上模型推理端到端耗时被直接压制到了1.6 秒以内严格配对 call_id大模型接收回填数据时强制通过call_id进行结果对应。如果并发返回的结果中漏掉了某一个call_id模型网关会直接抛出400 Invalid parameter: tool_call_id not found隔离依赖故障并行调用的多个微服务中如果有一个微服务如冷僻的积分服务宕机挂起千万不能让它拖累其他正常服务如订单服务。并发执行器必须为每个工具设置独立的隔离仓Bulkhead与熔断器超时即输出降级 JSON确保核心业务不受单点短板拖累回填 Token 预算严格防膨胀当大模型同时拉起 35 个工具时每个工具返回的 JSON 数据会被同时塞进下一轮推理的上下文。如果某个工具返回了一个包含上千条评论的超大数组整体上下文瞬间膨胀数万 Tokens不仅直接触发模型计费暴增还极易引发上下文溢出错误。必须在工具执行层强制进行视图过滤View Projections把每个工具返回的字符量严格控制在 500 字符以内虚拟线程跨线程 TraceId 与用户凭证透传在主线程派发虚拟线程并发执行工具调用时主线程的 MDC如链路跟踪traceId、操作用户userId不会自动传递给子虚拟线程。在生产环境中推荐使用基于 Java 24 的ScopedValue或者自定义的TaskDecorator在提交虚拟线程任务时显式抓取并回填 MDC否则 SkyWalking 或 Zipkin 抓到的链路会出现大面积断层排查故障如同盲人摸象。