【AI微服务】【Spring AI Alibaba】 ③ Spring AI Alibaba Agent 核心执行流程源码解析

📖目录

  • 前言
  • [1. Agent 执行核心组件概览](#1. Agent 执行核心组件概览)
    • [1.1 主要组件说明](#1.1 主要组件说明)
  • [2. Agent 执行流程详解](#2. Agent 执行流程详解)
    • [2.1 Agent 调用入口](#2.1 Agent 调用入口)
    • [2.2 图计算执行过程](#2.2 图计算执行过程)
  • [3. A2A 通信机制源码解析](#3. A2A 通信机制源码解析)
    • [3.1 A2A 节点动作执行流程](#3.1 A2A 节点动作执行流程)
    • [3.2 流式生成器创建过程](#3.2 流式生成器创建过程)
    • [3.3 响应解析机制](#3.3 响应解析机制)
  • [4. NodeActionWithConfig 的作用和实现](#4. NodeActionWithConfig 的作用和实现)
    • [4.1 AgentLlmNode 实现分析](#4.1 AgentLlmNode 实现分析)
    • [4.2 AgentToolNode 实现分析](#4.2 AgentToolNode 实现分析)
  • [5. 拦截器机制详解](#5. 拦截器机制详解)
    • [5.1 ModelInterceptor 链式调用](#5.1 ModelInterceptor 链式调用)
    • [5.2 ToolInterceptor 链式调用](#5.2 ToolInterceptor 链式调用)
  • [6. 实战示例:自定义 A2A 节点](#6. 实战示例:自定义 A2A 节点)
    • [6.1 简单示例](#6.1 简单示例)
    • [6.2 执行结果](#6.2 执行结果)
      • [6.2.1 预期执行结果](#6.2.1 预期执行结果)
      • [6.2.2 预期输出](#6.2.2 预期输出)
      • [6.2.3 执行条件](#6.2.3 执行条件)
  • [7. 总结与展望](#7. 总结与展望)
    • [7.1 未来发展方向](#7.1 未来发展方向)
  • [8. 推荐阅读](#8. 推荐阅读)
    • [8.1 经典书籍推荐](#8.1 经典书籍推荐)
    • [8.2 开山之作推荐](#8.2 开山之作推荐)
  • [9. 下一步探索主题](#9. 下一步探索主题)

前言

本文基于 Spring AI Alibaba 最新版本(2025 年 11 月,兼容 Spring Boot 3.5.7 / Spring Cloud Alibaba 2023.0.x)

适合中高级 Java 开发者、AI 应用架构师阅读

🔗 官方文档https://sca.aliyun.com/en/docs/ai/overview/

在之前的两篇博文中,我们分别介绍了 Spring AI Alibaba 的技术内核Agent 的实战应用,帮助大家建立起对框架整体架构和使用方法的理解。

本文将更进一步,深入剖析 Spring AI Alibaba 中 Agent 的核心执行流程源码。我们将重点围绕以下几个方面展开:

  • Agent 执行的核心组件
  • Agent 执行流程详解
  • A2A(Agent-to-Agent)通信机制源码解读
  • NodeActionWithConfig 的作用和实现

🎯 目标读者收益

  • 深入理解 Agent 内部工作机制
  • 掌握如何调试和扩展 Agent 行为
  • 学习复杂系统的设计思路和最佳实践

1. Agent 执行核心组件概览

在正式进入源码分析之前,让我们先了解一下构成 Agent 执行流程的核心组件及其关系:
External Systems Interceptors Agent Core Components Remote Agent Server ModelInterceptor Chain ToolInterceptor Chain AgentLlmNode AgentToolNode A2aNodeActionWithConfig ReactAgent


1.1 主要组件说明

组件 作用
ReactAgent Agent 的核心控制器,负责协调各个节点
AgentLlmNode 处理 LLM 调用的节点
AgentToolNode 处理工具调用的节点
A2aNodeActionWithConfig 处理与其他 Agent 通信的节点
ModelInterceptor 模型调用拦截器链
ToolInterceptor 工具调用拦截器链

2. Agent 执行流程详解

2.1 Agent 调用入口

Agent 的调用入口通常是 call()stream() 方法。我们以 call() 方法为例,看看它的执行流程:

java 复制代码
// ReactAgent.java
    public AssistantMessage call(String message, RunnableConfig config) throws GraphRunnerException {
        return doMessageInvoke(message, config);
    }

    public AssistantMessage call(UserMessage message, RunnableConfig config) throws GraphRunnerException {
        return doMessageInvoke(message, config);
    }

    private AssistantMessage doMessageInvoke(Object message, RunnableConfig config) throws GraphRunnerException {
        // 构建消息输入
        Map<String, Object> inputs= buildMessageInput(message);
        // 执行调用并获取状态
        Optional<OverAllState> state = doInvoke(inputs, config);

        // 如果输出键不为空
        if (StringUtils.hasLength(outputKey)) {
            // 从状态中获取指定输出键的值并转换为AssistantMessage
            return state.flatMap(s -> s.value(outputKey))
                    .map(msg -> (AssistantMessage) msg)
                    .orElseThrow(() -> new IllegalStateException("Output key " + outputKey + " not found in agent state") );
        }
        // 如果没有指定输出键,则从messages状态中查找AssistantMessage
        return state.flatMap(s -> s.value("messages"))
                .map(messageList -> (List<Message>) messageList)
                .stream()
                .flatMap(messageList -> messageList.stream())
                .filter(msg -> msg instanceof AssistantMessage)
                .map(msg -> (AssistantMessage) msg)
                .reduce((first, second) -> second)
                .orElseThrow(() -> new IllegalStateException("No AssistantMessage found in 'messages' state") );
    }

java 复制代码
// Agent.java
    // 执行调用的核心方法,返回整体状态
    protected Optional<OverAllState> doInvoke(Map<String, Object> input, RunnableConfig runnableConfig) {
        // 获取并编译图结构
        CompiledGraph compiledGraph = getAndCompileGraph();
        // 调用编译图的invoke方法执行调用
        return compiledGraph.invoke(input, buildNonStreamConfig(runnableConfig));
    }

        // 同步方法,获取并编译图结构,确保线程安全
    public synchronized CompiledGraph getAndCompileGraph() {
        // 如果已编译的图结构存在,直接返回
        if (compiledGraph != null) {
            return compiledGraph;
        }

        // 获取图结构
        StateGraph graph = getGraph();
        try {
            // 检查编译配置是否存在
            if (this.compileConfig == null) {
                // 如果编译配置为空,则使用默认配置编译图结构
                this.compiledGraph = graph.compile();
            }
            // 如果编译配置存在,则使用指定配置编译图结构
            else {
                this.compiledGraph = graph.compile(this.compileConfig);
            }
        } catch (GraphStateException e) {
            // 捕获图状态异常并转换为运行时异常抛出
            throw new RuntimeException(e);
        }
        // 返回编译后的图结构
        return this.compiledGraph;
    }

2.2 图计算执行过程

图计算的核心在于 invoke() 方法,它会按照预定义的图结构依次执行各个节点:

java 复制代码
// CompiledGraph.java

    public Optional<OverAllState> invoke(OverAllState overAllState, RunnableConfig config) {
        return Optional
            .ofNullable(streamFromInitialNode(overAllState, config).last().map(NodeOutput::state).block());
    }

    // 从初始状态创建Flux流
    public Flux<NodeOutput> streamFromInitialNode(OverAllState overAllState, RunnableConfig config) {
        // 检查配置是否为空
        Objects.requireNonNull(config, "config cannot be null");
        try {
            // 创建图运行器
            GraphRunner runner = new GraphRunner(this, config);
            // 运行图并处理结果
            return runner.run(overAllState).flatMap(data -> {
                // 如果数据已完成
                if (data.isDone()) {
                    // 如果结果值存在且为NodeOutput类型,返回该值
                    if (data.resultValue().isPresent() && data.resultValue().get() instanceof NodeOutput) {
                        return Flux.just((NodeOutput)data.resultValue().get());
                    } else {
                        // 否则返回空流
                        return Flux.empty();
                    }
                }
                // 如果数据有错误
                if (data.isError()) {
                    // 从Future获取数据并处理错误
                    return Mono.fromFuture(data.getOutput()).onErrorMap(throwable -> throwable).flux();
                }

                // 从Future获取数据并返回流
                return Mono.fromFuture(data.getOutput()).flux();
            });
        }
        // 捕获异常并返回错误流
        catch (Exception e) {
            return Flux.error(e);
        }
    }

3. A2A 通信机制源码解析

A2A(Agent-to-Agent)通信是 Spring AI Alibaba 的一大特色,允许 Agent 之间进行远程调用。我们来深入分析 A2aNodeActionWithConfig 的核心实现。

3.1 A2A 节点动作执行流程

java 复制代码
// A2aNodeActionWithConfig.java
    // 应用节点动作的核心方法,接收状态和配置参数
    // 应用节点动作的核心方法,接收状态和配置参数
    @Override
    public Map<String, Object> apply(OverAllState state, RunnableConfig config) throws Exception {
        // 获取子图的运行配置
        RunnableConfig subGraphRunnableConfig = getSubGraphRunnableConfig(config);
        // 判断是否启用流式传输
        if (streaming) {
            // 创建流式生成器
            AsyncGenerator<NodeOutput> generator = createStreamingGenerator(state, subGraphRunnableConfig);
            // 将异步生成器转换为Flux流
            Flux<GraphResponse<NodeOutput>> flux = toFlux(generator);
            // 返回包含流式输出的结果映射
            return Map.of(StringUtils.hasLength(this.outputKeyToParent) ? this.outputKeyToParent : "messages", flux);
        }
        // 非流式传输处理
        else {
            // 构建发送消息的请求载荷
            String requestPayload = buildSendMessageRequest(state, subGraphRunnableConfig);
            // 向服务器发送消息并获取响应
            String resultText = sendMessageToServer(this.agentCard, requestPayload);
            // 自动检测并解析响应
            Map<String, Object> resultMap = autoDetectAndParseResponse(resultText);
            // 提取结果部分
            Map<String, Object> result = (Map<String, Object>) resultMap.get("result");
            // 从结果中提取响应文本
            String responseText = extractResponseText(result);
            // 返回包含响应文本的结果映射
            return Map.of(this.outputKeyToParent, responseText);
        }
    }

3.2 流式生成器创建过程

java 复制代码
// A2aNodeActionWithConfig.java
private AsyncGenerator<NodeOutput> createStreamingGenerator(OverAllState state, RunnableConfig config) throws Exception {
    // 构建发送流式消息的请求载荷
    final String requestPayload = buildSendStreamingMessageRequest(state, config);
    // 创建阻塞队列用于存储生成的数据
    final BlockingQueue<AsyncGenerator.Data<NodeOutput>> queue = new LinkedBlockingQueue<>(1000);
    // 确定输出键名
    final String outputKey = StringUtils.hasLength(this.outputKeyToParent) ? this.outputKeyToParent : "messages";
    // 创建字符串构建器用于累积输出内容
    final StringBuilder accumulated = new StringBuilder();

    // 创建异步生成器队列
    return AsyncGeneratorQueue.of(queue, q -> {
        // 解析Agent的基础URL
        String baseUrl = resolveAgentBaseUrl(this.agentCard);
        // 如果URL为空或空白,则添加错误输出并返回
        if (baseUrl == null || baseUrl.isBlank()) {
            StreamingOutput errorOutput = new StreamingOutput("Error: AgentCard.url is empty", "a2aNode", agentName, state);
            queue.add(AsyncGenerator.Data.of(errorOutput));
            return;
        }

        // 创建HTTP客户端
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建POST请求
            HttpPost post = new HttpPost(baseUrl);
            // 设置内容类型头
            post.setHeader("Content-Type", "application/json");
            // 设置接受头为SSE流格式
            post.setHeader("Accept", "text/event-stream");
            // 设置请求实体
            post.setEntity(new StringEntity(requestPayload, ContentType.APPLICATION_JSON));

            // 执行HTTP请求
            try (CloseableHttpResponse response = httpClient.execute(post)) {
                // 获取响应状态码
                int statusCode = response.getStatusLine().getStatusCode();
                // 如果状态码不是200,则添加错误输出并返回
                if (statusCode != 200) {
                    StreamingOutput errorOutput = new StreamingOutput("HTTP request failed, status: " + statusCode,
                            "a2aNode", agentName, state);
                    queue.add(AsyncGenerator.Data.of(errorOutput));
                    return;
                }

                // 获取响应实体
                HttpEntity entity = response.getEntity();
                // 如果实体为空,则添加错误输出并返回
                if (entity == null) {
                    StreamingOutput errorOutput = new StreamingOutput("Empty HTTP entity", "a2aNode", agentName, state);
                    queue.add(AsyncGenerator.Data.of(errorOutput));
                    return;
                }

                // 获取内容类型
                String contentType = entity.getContentType() != null ? entity.getContentType().getValue() : "";
                // 判断是否为事件流格式
                boolean isEventStream = contentType.contains("text/event-stream");

                // 如果是事件流格式
                if (isEventStream) {
                    // 创建缓冲读取器读取响应内容
                    try (BufferedReader reader = new BufferedReader(
                            new InputStreamReader(entity.getContent(), StandardCharsets.UTF_8))) {
                        String line;
                        // 逐行读取响应内容
                        while ((line = reader.readLine()) != null) {
                            // 去除行首尾空白字符
                            String trimmed = line.trim();
                            // 如果不是以"data:"开头,则跳过
                            if (!trimmed.startsWith("data:")) {
                                continue;
                            }
                            // 提取JSON内容部分
                            String jsonContent = trimmed.substring(5).trim();
                            // 如果是结束标记,则跳出循环
                            if ("[DONE]".equals(jsonContent)) {
                                break;
                            }
                            // 尝试解析JSON内容
                            try {
                                // 解析JSON内容为Map
                                Map<String, Object> parsed = JSON.parseObject(jsonContent,
                                        new TypeReference<Map<String, Object>>() {
                                        });
                                // 提取结果部分
                                Map<String, Object> result = (Map<String, Object>) parsed.get("result");
                                // 如果结果不为空
                                if (result != null) {
                                    // 从结果中提取文本内容
                                    String text = extractResponseText(result);
                                    // 如果文本不为空
                                    if (text != null && !text.isEmpty()) {
                                        // 累积文本内容
                                        accumulated.append(text);
                                        // 添加流式输出数据到队列
                                        queue.add(AsyncGenerator.Data
                                            .of(new StreamingOutput(text, "a2aNode", agentName, state)));
                                    }
                                }
                            }
                            // 捕获解析异常并忽略
                            catch (Exception ignore) {
                            }
                        }
                    }
                }
                // 非SSE格式处理
                else {
                    // 读取完整响应体
                    String body = EntityUtils.toString(entity, "UTF-8");
                    // 尝试解析响应体
                    try {
                        // 解析响应体为Map
                        Map<String, Object> resultMap = JSON.parseObject(body,
                                new TypeReference<Map<String, Object>>() {
                                });
                        // 提取结果部分
                        Map<String, Object> result = (Map<String, Object>) resultMap.get("result");
                        // 从结果中提取文本内容
                        String text = extractResponseText(result);
                        // 如果文本不为空
                        if (text != null && !text.isEmpty()) {
                            // 累积文本内容
                            accumulated.append(text);
                            // 添加流式输出数据到队列
                            queue.add(AsyncGenerator.Data.of(new StreamingOutput(text, "a2aNode", agentName, state)));
                        }
                    }
                    // 捕获异常并添加错误输出
                    catch (Exception ex) {
                        queue.add(AsyncGenerator.Data
                            .of(new StreamingOutput("Error: " + ex.getMessage(), "a2aNode", agentName, state)));
                    }
                }
            }
        }
        // 捕获异常并添加错误输出
        catch (Exception e) {
            StreamingOutput errorOutput = new StreamingOutput("Error: " + e.getMessage(), "a2aNode", agentName, state);
            queue.add(AsyncGenerator.Data.of(errorOutput));
        }
        // 最终添加完成信号
        finally {
            queue.add(AsyncGenerator.Data.done(Map.of(outputKey, accumulated.toString())));
        }
    });
}

3.3 响应解析机制

java 复制代码
// A2aNodeActionWithConfig.java
private Map<String, Object> autoDetectAndParseResponse(String responseText) {
    // 如果响应文本包含"data: ",则认为是流式响应
    if (responseText.contains("data: ")) {
        // 解析流式响应
        return parseStreamingResponse(responseText);
    }
    // 否则是标准JSON响应
    else {
        // Standard JSON response
        // 解析为JSON对象并返回
        return JSON.parseObject(responseText, new TypeReference<Map<String, Object>>() {
        });
    }
}

---

private Map<String, Object> parseStreamingResponse(String responseText) {
    // 按换行符分割响应文本
    String[] lines = responseText.split("\n");
    // 存储最后一个结果
    Map<String, Object> lastResult = null;
    // 遍历每一行
    for (String line : lines) {
        // 去除行首尾空格
        line = line.trim();
        // 如果行以"data: "开头
        if (line.startsWith("data: ")) {
            // 提取JSON内容,移除"data: "前缀
            String jsonContent = line.substring(6); // remove "data: " prefix
            try {
                // 解析JSON内容为Map对象
                Map<String, Object> parsed = JSON.parseObject(jsonContent,
                        new TypeReference<Map<String, Object>>() {
                        });
                // 从解析结果中获取result部分
                Map<String, Object> result = (Map<String, Object>) parsed.get("result");
                // 如果result不为空
                if (result != null) {
                    // 如果result包含"artifact"键或lastResult为空
                    if (result.containsKey("artifact") || lastResult == null) {
                        // 更新lastResult
                        lastResult = result;
                    }
                }
            }
            // 捕获异常,继续处理下一行
            catch (Exception e) {
                continue;
            }
        }
    }

    // 如果lastResult仍为空,抛出异常
    if (lastResult == null) {
        throw new IllegalStateException("Failed to parse any valid result from streaming response");
    }
    // 创建结果Map
    Map<String, Object> resultMap = new HashMap<>();
    // 将lastResult放入resultMap中
    resultMap.put("result", lastResult);
    // 返回结果Map
    return resultMap;
}

4. NodeActionWithConfig 的作用和实现

NodeActionWithConfig是所有节点动作的基接口,定义了节点执行的核心逻辑:

java 复制代码
// NodeActionWithConfig.java
public interface NodeActionWithConfig {
    Map<String, Object> apply(OverAllState state, RunnableConfig config) throws Exception;
}

4.1 AgentLlmNode 实现分析

java 复制代码
// AgentLlmNode.java
@Override
public Map<String, Object> apply(OverAllState state, RunnableConfig config) throws Exception {
    // 检查并管理迭代计数器
    final AtomicInteger iterations;
    if (!config.context().containsKey(MODEL_ITERATION_KEY)) {
        // 如果上下文中没有迭代计数器,则创建新的计数器并放入上下文
        iterations = new AtomicInteger(0);
        config.context().put(MODEL_ITERATION_KEY, iterations);
    } else {
        // 如果存在迭代计数器,则获取并递增
        iterations = (AtomicInteger) config.context().get(MODEL_ITERATION_KEY);
        iterations.incrementAndGet();
    }

    // 检查和管理消息
    if (state.value("messages").isEmpty()) {
        // 如果消息为空则抛出异常
        throw new IllegalArgumentException("Either 'instruction' or 'includeContents' must be set for Agent.");
    }
    // 获取消息列表
    @SuppressWarnings("unchecked")
    List<Message> messages = (List<Message>) state.value("messages").get();
    // 增强用户消息,添加输出模式定义
    augmentUserMessage(messages, outputSchema);
    // 渲染模板化的用户消息
    renderTemplatedUserMessage(messages, state.data());

    // 创建ModelRequest构建器
    ModelRequest.Builder requestBuilder = ModelRequest.builder()
            .messages(messages)
            .options(toolCallingChatOptions)
            .context(config.metadata().orElse(new HashMap<>()));
    // 如果系统提示词不为空,则添加到请求中
    if (StringUtils.hasLength(this.systemPrompt)) {
        requestBuilder.systemMessage(new SystemMessage(this.systemPrompt));
    }
    // 构建ModelRequest
    ModelRequest modelRequest = requestBuilder.build();

    // 添加流式支持
    boolean stream = config.metadata("_stream_", new TypeRef<Boolean>(){}).orElse(true);
    if (stream) {
        // 创建基础处理器,实际调用模型并支持流式传输
        ModelCallHandler baseHandler = request -> {
            try {
                // 发起流式请求获取响应
                Flux<ChatResponse> chatResponseFlux = buildChatClientRequestSpec(request).stream().chatResponse();
                // 返回流式响应的ModelResponse
                return ModelResponse.of(chatResponseFlux);
            } catch (Exception e) {
                // 异常处理,记录错误并返回异常信息
                logger.error("Exception during streaming model call: ", e);
                return ModelResponse.of(new AssistantMessage("Exception: " + e.getMessage()));
            }
        };

        // 链接拦截器(如果有的话)
        ModelCallHandler chainedHandler = InterceptorChain.chainModelInterceptors(
                modelInterceptors, baseHandler);

        // 执行链接后的处理器
        ModelResponse modelResponse = chainedHandler.call(modelRequest);
        // 返回处理结果
        return Map.of(StringUtils.hasLength(this.outputKey) ? this.outputKey : "messages", modelResponse.getMessage());
    } else {
        // 非流式模式下的基础处理器
        ModelCallHandler baseHandler = request -> {
            try {
                // 发起非流式请求获取响应
                ChatResponse response = buildChatClientRequestSpec(request).call().chatResponse();

                // 处理响应消息
                AssistantMessage responseMessage = new AssistantMessage("Empty response from model for unknown reason");
                if (response != null && response.getResult() != null) {
                    responseMessage = response.getResult().getOutput();
                }

                // 返回响应的ModelResponse
                return ModelResponse.of(responseMessage, response);
            } catch (Exception e) {
                // 异常处理,记录错误并返回异常信息
                logger.error("Exception during streaming model call: ", e);
                return ModelResponse.of(new AssistantMessage("Exception: " + e.getMessage()));
            }
        };

        // 链接拦截器(如果有的话)
        ModelCallHandler chainedHandler = InterceptorChain.chainModelInterceptors(
                modelInterceptors, baseHandler);

        // 执行链接后的处理器
        ModelResponse modelResponse = chainedHandler.call(modelRequest);
        // 获取token使用情况
        Usage tokenUsage = modelResponse.getChatResponse() != null ? modelResponse.getChatResponse().getMetadata()
                .getUsage() : new EmptyUsage();

        // 构建更新后的状态
        Map<String, Object> updatedState = new HashMap<>();
        updatedState.put("_TOKEN_USAGE_", tokenUsage);
        updatedState.put("messages", modelResponse.getMessage());
        // 如果指定了输出键,则也放入该键对应的数据
        if (StringUtils.hasLength(this.outputKey)) {
            updatedState.put(this.outputKey, modelResponse.getMessage());
        }

        // 返回更新后的状态
        return updatedState;
    }
}

4.2 AgentToolNode 实现分析

java 复制代码
// AgentToolNode.java
@Override
public Map<String, Object> apply(OverAllState state, RunnableConfig config) throws Exception {
    // 获取消息列表
    List<Message> messages = (List<Message>) state.value("messages").orElseThrow();
    // 获取最后一条消息
    Message lastMessage = messages.get(messages.size() - 1);

    // 更新状态映射
    Map<String, Object> updatedState = new HashMap<>();
    // 工具调用产生的额外状态
    Map<String, Object> extraStateFromToolCall = new HashMap<>();
    // 如果最后一条消息是AssistantMessage
    if (lastMessage instanceof AssistantMessage assistantMessage) {
        // 执行工具函数
        List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();

        // 遍历所有工具调用
        for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
            // 使用拦截器链执行工具调用
            ToolCallResponse response = executeToolCallWithInterceptors(toolCall, state, config, extraStateFromToolCall);
            // 将响应添加到工具响应列表
            toolResponses.add(response.toToolResponse());
        }

        // 创建工具响应消息
        ToolResponseMessage toolResponseMessage = new ToolResponseMessage(toolResponses, Map.of());

        // 将工具响应消息放入更新状态
        updatedState.put("messages", toolResponseMessage);
    // 如果最后一条消息是ToolResponseMessage
    } else if (lastMessage instanceof ToolResponseMessage toolResponseMessage) {
        // 获取已存在的响应列表
        List<ToolResponseMessage.ToolResponse> existingResponses = toolResponseMessage.getResponses();
        // 创建所有响应的列表
        List<ToolResponseMessage.ToolResponse> allResponses = new ArrayList<>(existingResponses);

        // 收集已执行的工具名称
        Set<String> executedToolNames = existingResponses.stream()
                .map(ToolResponseMessage.ToolResponse::name)
                .collect(Collectors.toSet());

        // 遍历所有工具调用
        for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
            // 如果工具已经执行过,则跳过
            if (executedToolNames.contains(toolCall.name())) {
                continue;
            }

            // 使用拦截器链执行工具调用
            ToolCallResponse response = executeToolCallWithInterceptors(toolCall, state, config, extraStateFromToolCall);
            // 将响应添加到所有响应列表
            allResponses.add(response.toToolResponse());
        }

        // 创建新的消息列表
        List<Object> newMessages = new ArrayList<>();
        // 创建新的工具响应消息
        ToolResponseMessage newToolResponseMessage = new ToolResponseMessage(allResponses, Map.of());
        // 添加新的工具响应消息
        newMessages.add(newToolResponseMessage);
        // 添加移除标记
        newMessages.add(new RemoveByHash<>(assistantMessage));
        // 将新消息列表放入更新状态
        updatedState.put("messages", newMessages);

    // 如果最后一条消息既不是AssistantMessage也不是ToolResponseMessage,则抛出异常
    } else {
        throw new IllegalStateException("Last message is not an AssistantMessage or ToolResponseMessage");
    }

    // 合并工具调用产生的额外状态
    updatedState.putAll(extraStateFromToolCall);
    // 返回更新后的状态
    return updatedState;
}

5. 拦截器机制详解

拦截器是 Spring AI Alibaba 中非常重要的扩展机制,允许在关键节点插入自定义逻辑。

5.1 ModelInterceptor 链式调用

java 复制代码
// InterceptorChain.java
    public static ModelCallHandler chainModelInterceptors(
            List<ModelInterceptor> interceptors,
            ModelCallHandler baseHandler) {

        // 如果拦截器列表为空或长度为0,直接返回基础处理器
        if (interceptors == null || interceptors.isEmpty()) {
            return baseHandler;
        }

        // 初始化当前处理器为基础处理器
        ModelCallHandler current = baseHandler;

        // 从最后一个拦截器开始向前包装(右到左组合)
        // 这样确保第一个拦截器是最外层的
        for (int i = interceptors.size() - 1; i >= 0; i--) {
            // 获取当前位置的拦截器
            ModelInterceptor interceptor = interceptors.get(i);
            // 保存下一个处理器引用
            ModelCallHandler nextHandler = current;

            // 创建一个包装器,调用拦截器的 interceptModel 方法
            current = request -> interceptor.interceptModel(request, nextHandler);
        }

        // 返回组合后的处理器
        return current;
    }

5.2 ToolInterceptor 链式调用

java 复制代码
// InterceptorChain.java
    public static ToolCallHandler chainToolInterceptors(
            List<ToolInterceptor> interceptors,
            ToolCallHandler baseHandler) {

        // 如果拦截器列表为空或长度为0,直接返回基础处理器
        if (interceptors == null || interceptors.isEmpty()) {
            return baseHandler;
        }

        // 初始化当前处理器为基础处理器
        ToolCallHandler current = baseHandler;

        // 从最后一个拦截器开始向前包装(右到左组合)
        // 这样确保第一个拦截器是最外层的
        for (int i = interceptors.size() - 1; i >= 0; i--) {
            // 获取当前位置的拦截器
            ToolInterceptor interceptor = interceptors.get(i);
            // 保存下一个处理器引用
            ToolCallHandler nextHandler = current;

            // 创建一个包装器,调用拦截器的 interceptToolCall 方法
            current = request -> interceptor.interceptToolCall(request, nextHandler);
        }

        // 返回组合后的处理器
        return current;
    }

6. 实战示例:自定义 A2A 节点

6.1 简单示例

为了更好地理解 A2A 机制,我们来编写一个简单的示例:

java 复制代码
// A2aExample.java
public class A2aExample {
    public static void main(String[] args) throws Exception {
        // 创建远程 Agent Card
        AgentCardWrapper agentCard = AgentCardWrapper.builder()
                .name("remote-agent")
                .url("http://localhost:8080/api/a2a")
                .build();
        
        // 创建 A2A 节点动作
        A2aNodeActionWithConfig a2aAction = new A2aNodeActionWithConfig(
                agentCard, 
                "remote-agent", 
                true, 
                "result", 
                "请分析以下文本的情感倾向", 
                false);
        
        // 构建测试状态
        Map<String, Object> testData = Map.of(
                "messages", List.of(new UserMessage("今天天气真好,心情也很愉快"))
        );
        OverAllState state = new OverAllState(testData);
        
        // 创建运行配置
        RunnableConfig config = RunnableConfig.builder().build();
        
        // 执行 A2A 调用
        Map<String, Object> result = a2aAction.apply(state, config);
        
        System.out.println("A2A 调用结果: " + result);
    }
}

6.2 执行结果

基于提供的 A2aExample.java 代码,预期的执行结果如下:

6.2.1 预期执行结果

当运行这个示例代码时,程序会执行以下操作:

  1. 创建远程 Agent Card

    java 复制代码
    AgentCardWrapper agentCard = AgentCardWrapper.builder()
            .name("remote-agent")
            .url("http://localhost:8080/api/a2a")
            .build();

    这将创建一个名为 "remote-agent" 的代理卡片,指向本地服务器的 /api/a2a 端点。

  2. 创建 A2A 节点动作

    java 复制代码
    A2aNodeActionWithConfig a2aAction = new A2aNodeActionWithConfig(
            agentCard, 
            "remote-agent", 
            true, 
            "result", 
            "请分析以下文本的情感倾向", 
            false);

    这将创建一个 A2A 节点动作,配置为:

    • 使用刚才创建的 agentCard
    • Agent 名称为 "remote-agent"
    • 包含内容 (includeContents = true)
    • 输出键为 "result"
    • 指令为 "请分析以下文本的情感倾向"
    • 不启用流式传输 (streaming = false)
  3. 构建测试状态

    java 复制代码
    Map<String, Object> testData = Map.of(
            "messages", List.of(new UserMessage("今天天气真好,心情也很愉快"))
    );
    OverAllState state = new OverAllState(testData);

    创建一个包含用户消息 "今天天气真好,心情也很愉快" 的状态。

  4. 创建运行配置并执行 A2A 调用

    java 复制代码
    RunnableConfig config = RunnableConfig.builder().build();
    Map<String, Object> result = a2aAction.apply(state, config);

6.2.2 预期输出

程序将尝试向 http://localhost:8080/api/a2a 发送一个 HTTP 请求,请求内容是一个 JSON-RPC 消息,包含:

  • 用户消息:"今天天气真好,心情也很愉快"
  • 指令:"请分析以下文本的情感倾向"

然后程序会等待服务器响应。根据服务器的实现,可能的输出包括:

  1. 成功情况

    复制代码
    A2A 调用结果: {result=分析结果...}

    其中 "分析结果..." 是远程 Agent 对文本情感分析的结果。

  2. 连接失败情况

    如果本地服务器未运行或无法访问 /api/a2a 端点,可能会抛出类似以下的异常:

    复制代码
    Exception in thread "main" java.lang.IllegalStateException: HTTP request failed, status: 404

    复制代码
    Exception in thread "main" java.net.ConnectException: Connection refused
  3. 服务器返回错误

    如果服务器返回错误响应,可能会输出类似:

    复制代码
    A2A 调用结果: Error: HTTP request failed, status: 500

6.2.3 执行条件

需要注意的是,这个示例假定存在一个在 http://localhost:8080/api/a2a 运行的远程 Agent 服务,能够处理 JSON-RPC 格式的请求并返回适当的响应。在实际运行前,需要确保有这样的服务正在运行。


7. 总结与展望

通过本文的深入分析,我们可以看到 Spring AI Alibaba Agent 的执行流程具有以下特点:

  1. 模块化设计:通过 NodeActionWithConfig 接口将不同类型的节点操作抽象出来
  2. 拦截器机制:提供了强大的扩展能力,可以在关键节点插入自定义逻辑
  3. A2A 通信:支持 Agent 间的远程调用,构建分布式智能体系统
  4. 流式处理:支持流式响应处理,提高用户体验

7.1 未来发展方向

  1. 更智能的调度算法:基于任务特征和资源状态进行动态调度
  2. 更强的安全机制:增强 Agent 间通信的安全性和可信度
  3. 更丰富的交互模式:支持多模态输入和输出

8. 推荐阅读

8.1 经典书籍推荐

  1. 《人工智能:一种现代的方法》(Artificial Intelligence: A Modern Approach) - Stuart Russell & Peter Norvig

    • 这是 AI 领域的经典教材,涵盖了从基础理论到前沿技术的全面内容
    • 对理解 Agent 设计原理非常有帮助
  2. 《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software) - Erich Gamma 等

    • 虽然不是专门讲 AI,但其中的模式思想对设计复杂的 Agent 系统很有启发
  3. 《Clean Architecture》 - Robert C. Martin

    • 介绍了如何设计清晰、可维护的软件架构,对构建大型 Agent 系统很有价值

8.2 开山之作推荐

  1. 《Software Agents》 - Michael Wooldridge

    • 这是 Agent 技术领域的开山之作之一,系统地介绍了 Agent 的理论基础和实现技术
  2. 《Multiagent Systems: Algorithmic, Game-Theoretic, and Logical Foundations》 - Yoav Shoham & Kevin Leyton-Brown

    • 从算法、博弈论和逻辑三个角度深入探讨多 Agent 系统的设计与实现

9. 下一步探索主题

基于本文的分析,以下几个主题值得进一步深入研究:

  1. Agent 间协作协议设计

    • 代码入口:A2aNodeActionWithConfig.sendMessageToServer()
    • 探索如何设计高效的 Agent 间通信协议
  2. 动态 Agent 编排机制

    • 代码入口:ReactAgent.invoke()
    • 研究如何根据任务特征动态调整 Agent 执行顺序
  3. Agent 安全与隐私保护

    • 代码入口:ModelInterceptor.interceptModel()
    • 探索在拦截器中实现安全检查和隐私保护机制
  4. 大规模 Agent 系统性能优化

    • 代码入口:AsyncGeneratorQueue
    • 研究如何优化大规模并发 Agent 系统的性能

📝 版权声明 :本文为原创,遵循 CC 4.0 BY-SA 协议。转载请附原文链接及本声明。

🔖 关键词:#SpringAI #AlibabaCloud #Agent #智能体 #源码解析 #A2A #NodeAction #Interceptor

相关推荐
轻竹办公PPT3 小时前
PPT生成效率提升的方法:AI生成PPT实战说明
人工智能·python·powerpoint
华大哥3 小时前
spring cloud微服务实战:consul+Feign/Ribbon服务注册和远程调用
spring cloud·微服务·ribbon·consul·java-consul
Das13 小时前
【计算机视觉】04_角点
人工智能·计算机视觉
SEO_juper3 小时前
零基础快速上手:亚马逊CodeWhisperer实战入门指南
人工智能·机器学习·工具·亚马逊·codewhisperer
RanceGru3 小时前
LLM学习笔记7——unsloth微调Qwen3-4B模型与vllm部署测试
人工智能·笔记·学习·语言模型·vllm
如意鼠3 小时前
大模型教我成为大模型算法工程师之day20: 预训练语言模型 (Pre-trained Language Models)
人工智能·算法·语言模型
囊中之锥.3 小时前
机器学习第二部分----逻辑回归
人工智能·机器学习·逻辑回归
_Li.3 小时前
机器学习-DeepSeekR1
人工智能·机器学习
CodeLinghu3 小时前
「 LLM实战 - 企业 」基于 markdown-it AST 的 Markdown 文献翻译实现详解
人工智能·ai