📖目录
- 前言
- [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 应用架构师阅读
在之前的两篇博文中,我们分别介绍了 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 预期执行结果
当运行这个示例代码时,程序会执行以下操作:
-
创建远程 Agent Card:
javaAgentCardWrapper agentCard = AgentCardWrapper.builder() .name("remote-agent") .url("http://localhost:8080/api/a2a") .build();这将创建一个名为 "remote-agent" 的代理卡片,指向本地服务器的
/api/a2a端点。 -
创建 A2A 节点动作:
javaA2aNodeActionWithConfig a2aAction = new A2aNodeActionWithConfig( agentCard, "remote-agent", true, "result", "请分析以下文本的情感倾向", false);这将创建一个 A2A 节点动作,配置为:
- 使用刚才创建的
agentCard - Agent 名称为 "remote-agent"
- 包含内容 (
includeContents = true) - 输出键为 "result"
- 指令为 "请分析以下文本的情感倾向"
- 不启用流式传输 (
streaming = false)
- 使用刚才创建的
-
构建测试状态:
javaMap<String, Object> testData = Map.of( "messages", List.of(new UserMessage("今天天气真好,心情也很愉快")) ); OverAllState state = new OverAllState(testData);创建一个包含用户消息 "今天天气真好,心情也很愉快" 的状态。
-
创建运行配置并执行 A2A 调用:
javaRunnableConfig config = RunnableConfig.builder().build(); Map<String, Object> result = a2aAction.apply(state, config);
6.2.2 预期输出
程序将尝试向 http://localhost:8080/api/a2a 发送一个 HTTP 请求,请求内容是一个 JSON-RPC 消息,包含:
- 用户消息:"今天天气真好,心情也很愉快"
- 指令:"请分析以下文本的情感倾向"
然后程序会等待服务器响应。根据服务器的实现,可能的输出包括:
-
成功情况:
A2A 调用结果: {result=分析结果...}其中 "分析结果..." 是远程 Agent 对文本情感分析的结果。
-
连接失败情况 :
如果本地服务器未运行或无法访问
/api/a2a端点,可能会抛出类似以下的异常:Exception in thread "main" java.lang.IllegalStateException: HTTP request failed, status: 404或
Exception in thread "main" java.net.ConnectException: Connection refused -
服务器返回错误 :
如果服务器返回错误响应,可能会输出类似:
A2A 调用结果: Error: HTTP request failed, status: 500
6.2.3 执行条件
需要注意的是,这个示例假定存在一个在 http://localhost:8080/api/a2a 运行的远程 Agent 服务,能够处理 JSON-RPC 格式的请求并返回适当的响应。在实际运行前,需要确保有这样的服务正在运行。
7. 总结与展望
通过本文的深入分析,我们可以看到 Spring AI Alibaba Agent 的执行流程具有以下特点:
- 模块化设计:通过 NodeActionWithConfig 接口将不同类型的节点操作抽象出来
- 拦截器机制:提供了强大的扩展能力,可以在关键节点插入自定义逻辑
- A2A 通信:支持 Agent 间的远程调用,构建分布式智能体系统
- 流式处理:支持流式响应处理,提高用户体验
7.1 未来发展方向
- 更智能的调度算法:基于任务特征和资源状态进行动态调度
- 更强的安全机制:增强 Agent 间通信的安全性和可信度
- 更丰富的交互模式:支持多模态输入和输出
8. 推荐阅读
8.1 经典书籍推荐
-
《人工智能:一种现代的方法》(Artificial Intelligence: A Modern Approach) - Stuart Russell & Peter Norvig
- 这是 AI 领域的经典教材,涵盖了从基础理论到前沿技术的全面内容
- 对理解 Agent 设计原理非常有帮助
-
《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software) - Erich Gamma 等
- 虽然不是专门讲 AI,但其中的模式思想对设计复杂的 Agent 系统很有启发
-
《Clean Architecture》 - Robert C. Martin
- 介绍了如何设计清晰、可维护的软件架构,对构建大型 Agent 系统很有价值
8.2 开山之作推荐
-
《Software Agents》 - Michael Wooldridge
- 这是 Agent 技术领域的开山之作之一,系统地介绍了 Agent 的理论基础和实现技术
-
《Multiagent Systems: Algorithmic, Game-Theoretic, and Logical Foundations》 - Yoav Shoham & Kevin Leyton-Brown
- 从算法、博弈论和逻辑三个角度深入探讨多 Agent 系统的设计与实现
9. 下一步探索主题
基于本文的分析,以下几个主题值得进一步深入研究:
-
Agent 间协作协议设计
- 代码入口:
A2aNodeActionWithConfig.sendMessageToServer() - 探索如何设计高效的 Agent 间通信协议
- 代码入口:
-
动态 Agent 编排机制
- 代码入口:
ReactAgent.invoke() - 研究如何根据任务特征动态调整 Agent 执行顺序
- 代码入口:
-
Agent 安全与隐私保护
- 代码入口:
ModelInterceptor.interceptModel() - 探索在拦截器中实现安全检查和隐私保护机制
- 代码入口:
-
大规模 Agent 系统性能优化
- 代码入口:
AsyncGeneratorQueue - 研究如何优化大规模并发 Agent 系统的性能
- 代码入口:
📝 版权声明 :本文为原创,遵循 CC 4.0 BY-SA 协议。转载请附原文链接及本声明。
🔖 关键词:#SpringAI #AlibabaCloud #Agent #智能体 #源码解析 #A2A #NodeAction #Interceptor