Spring AI Alibaba 项目源码学习(十二)-完结:Tool
Tool 系统分析
请关注微信公众号:阿呆-bot
概述
本文档分析 Spring AI Alibaba Agent Framework 中的 Tool(工具)系统,包括工具的定义、注册、调用流程、扩展机制以及 AgentTool 的实现。
入口类说明
ToolCallback - 工具回调接口
ToolCallback 是 Spring AI 提供的工具接口,定义了工具的基本能力。
核心职责:
- 定义工具名称和描述
- 定义工具输入输出 Schema
- 执行工具调用
- 返回工具结果
AgentTool - Agent 作为工具
AgentTool 将 ReactAgent 封装为工具,使 Agent 可以作为工具被其他 Agent 调用。
核心职责:
- 将 Agent 转换为 ToolCallback
- 执行 Agent 调用
- 返回 Agent 响应
关键代码:
public class AgentTool implements BiFunction<String, ToolContext, AssistantMessage> {
private final ReactAgent agent;
public AgentTool(ReactAgent agent) {
this.agent = agent;
}
@Override
public AssistantMessage apply(String input, ToolContext toolContext) {
OverAllState state = (OverAllState) toolContext.getContext().get("state");
try {
// Copy state to avoid affecting the original state.
// The agent that calls this tool should only be aware of the ToolCallChoice and ToolResponse.
OverAllState newState = agent.getAndCompileGraph().cloneState(state.data());
// Build the messages list to add
// Add instruction first if present, then the user input
// Note: We must add all messages at once because cloneState doesn't copy keyStrategies,
// so multiple updateState calls would overwrite instead of append
java.util.List<Message> messagesToAdd = new java.util.ArrayList<>();
if (StringUtils.hasLength(agent.instruction())) {
messagesToAdd.add(new AgentInstructionMessage(agent.instruction()));
}
messagesToAdd.add(new UserMessage(input));
Map<String, Object> inputs = newState.updateState(Map.of("messages", messagesToAdd));
Optional<OverAllState> resultState = agent.getAndCompileGraph().invoke(inputs);
Optional<List> messages = resultState.flatMap(overAllState -> overAllState.value("messages", List.class));
if (messages.isPresent()) {
@SuppressWarnings("unchecked")
List<Message> messageList = (List<Message>) messages.get();
// Use messageList
AssistantMessage assistantMessage = (AssistantMessage)messageList.get(messageList.size() - 1);
return assistantMessage;
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Failed to execute agent tool or failed to get agent tool result");
}
public static ToolCallback getFunctionToolCallback(ReactAgent agent) {
// convert agent inputType to json schema
String inputSchema = StringUtils.hasLength(agent.getInputSchema())
? agent.getInputSchema()
: (agent.getInputType() != null )
? JsonSchemaGenerator.generateForType(agent.getInputType())
: null;
return FunctionToolCallback.builder(agent.name(), AgentTool.create(agent))
.description(agent.description())
.inputType(String.class) // the inputType for ToolCallback is always String
.inputSchema(inputSchema)
.toolCallResultConverter(CONVERTER)
.build();
}
}
关键特性:
- 状态隔离:通过
cloneState()创建独立状态,避免影响调用 Agent 的状态 - 指令传递:支持传递 Agent 指令
- Schema 生成:自动生成工具输入 Schema
AgentToolNode - 工具执行节点
AgentToolNode 是执行工具调用的 Graph 节点。
核心职责:
- 解析工具调用请求
- 执行工具调用
- 处理工具响应
- 支持工具拦截器
关键代码:
public class AgentToolNode implements NodeActionWithConfig {
private List<ToolCallback> toolCallbacks = new ArrayList<>();
private List<ToolInterceptor> toolInterceptors = new ArrayList<>();
private ToolCallbackResolver toolCallbackResolver;
@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<>();
if (lastMessage instanceof AssistantMessage assistantMessage) {
// execute the tool function
List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();
for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
// Execute tool call with interceptor chain
ToolCallResponse response = executeToolCallWithInterceptors(toolCall, state, config, extraStateFromToolCall);
toolResponses.add(response.toToolResponse());
}
ToolResponseMessage toolResponseMessage = new ToolResponseMessage(toolResponses, Map.of());
updatedState.put("messages", toolResponseMessage);
} else if (lastMessage instanceof ToolResponseMessage toolResponseMessage) {
// Handle incremental tool execution
// ...
} else {
throw new IllegalStateException("Last message is not an AssistantMessage or ToolResponseMessage");
}
// Merge extra state from tool calls
updatedState.putAll(extraStateFromToolCall);
return updatedState;
}
AgentLlmNode - LLM 节点
AgentLlmNode 负责调用 LLM,并将工具信息传递给模型。
关键代码:
public class AgentLlmNode implements NodeActionWithConfig {
private List<ToolCallback> toolCallbacks = new ArrayList<>();
private List<ModelInterceptor> modelInterceptors = new ArrayList<>();
private ChatClient chatClient;
@Override
public Map<String, Object> apply(OverAllState state, RunnableConfig config) throws Exception {
// add streaming support
boolean stream = config.metadata("_stream_", new TypeRef<Boolean>(){}).orElse(true);
if (stream) {
@SuppressWarnings("unchecked")
List<Message> messages = (List<Message>) state.value("messages").get();
augmentUserMessage(messages, outputSchema);
renderTemplatedUserMessage(messages, state.data());
// Create ModelRequest
ModelRequest modelRequest = ModelRequest.builder()
.messages(messages)
.options(toolCallingChatOptions)
.context(config.metadata().orElse(new HashMap<>()))
.build();
// Create base handler that actually calls the model with streaming
ModelCallHandler baseHandler = request -> {
try {
Flux<ChatResponse> chatResponseFlux = buildChatClientRequestSpec(request).stream().chatResponse();
return ModelResponse.of(chatResponseFlux);
} catch (Exception e) {
return ModelResponse.of(new AssistantMessage("Exception: " + e.getMessage()));
}
};
// Chain interceptors if any
ModelCallHandler chainedHandler = InterceptorChain.chainModelInterceptors(
modelInterceptors, baseHandler);
// Execute the chained handler
ModelResponse modelResponse = chainedHandler.call(modelRequest);
return Map.of(StringUtils.hasLength(this.outputKey) ? this.outputKey : "messages", modelResponse.getMessage());
} else {
// Non-streaming mode
// ...
}
}
工具注册机制
工具注册流程
工具通过以下方式注册:
- 直接注册:通过
ReactAgent.Builder.tools()方法注册 - Interceptor 工具:通过
ModelInterceptor.getTools()方法提供 - 工具解析器:通过
ToolCallbackResolver动态解析
关键代码:
// Extract regular tools from user-provided tools
if (CollectionUtils.isNotEmpty(tools)) {
regularTools.addAll(tools);
}
// Extract interceptor tools
List<ToolCallback> interceptorTools = new ArrayList<>();
if (CollectionUtils.isNotEmpty(modelInterceptors)) {
interceptorTools = modelInterceptors.stream()
.flatMap(interceptor -> interceptor.getTools().stream())
.collect(Collectors.toList());
}
// Combine all tools: regularTools + regularTools
List<ToolCallback> allTools = new ArrayList<>();
allTools.addAll(interceptorTools);
allTools.addAll(regularTools);
// Set combined tools to LLM node
if (CollectionUtils.isNotEmpty(allTools)) {
llmNodeBuilder.toolCallbacks(allTools);
}
AgentLlmNode llmNode = llmNodeBuilder.build();
// Setup tool node with all available tools
AgentToolNode toolNode = null;
if (resolver != null) {
toolNode = AgentToolNode.builder().toolCallbackResolver(resolver).build();
}
else if (CollectionUtils.isNotEmpty(allTools)) {
toolNode = AgentToolNode.builder().toolCallbacks(allTools).build();
}
else {
toolNode = AgentToolNode.builder().build();
}
return new ReactAgent(llmNode, toolNode, buildConfig(), this);
工具调用流程
工具调用步骤
- LLM 生成工具调用:AgentLlmNode 调用 LLM,LLM 返回包含工具调用的 AssistantMessage
- 工具调用解析:AgentToolNode 解析 AssistantMessage 中的工具调用
- 工具执行:通过工具拦截器链执行工具调用
- 响应生成:将工具执行结果封装为 ToolResponseMessage
- 状态更新:更新状态中的消息列表
关键代码:
/**
* Execute a tool call with interceptor chain support.
*/
private ToolCallResponse executeToolCallWithInterceptors(
AssistantMessage.ToolCall toolCall,
OverAllState state,
RunnableConfig config,
Map<String, Object> extraStateFromToolCall) {
// Create ToolCallRequest
ToolCallRequest request = ToolCallRequest.builder()
.toolCall(toolCall)
.context(config.metadata().orElse(new HashMap<>()))
.build();
// Create base handler that actually executes the tool
ToolCallHandler baseHandler = req -> {
ToolCallback toolCallback = resolve(req.getToolName());
String result = toolCallback.call(
req.getArguments(),
new ToolContext(Map.of("state", state, "config", config, "extraState", extraStateFromToolCall))
);
return ToolCallResponse.of(req.getToolCallId(), req.getToolName(), result);
};
// Chain interceptors if any
ToolCallHandler chainedHandler = InterceptorChain.chainToolInterceptors(
toolInterceptors, baseHandler);
// Execute the chained handler
return chainedHandler.call(request);
}
private ToolCallback resolve(String toolName) {
return toolCallbacks.stream()
.filter(callback -> callback.getToolDefinition().name().equals(toolName))
.findFirst()
.orElseGet(() -> toolCallbackResolver.resolve(toolName));
}
工具扩展机制
自定义工具实现
开发者可以通过以下方式扩展工具:
- 实现 ToolCallback 接口:创建自定义工具
- 使用 FunctionToolCallback:将函数转换为工具
- AgentTool:将 Agent 转换为工具
工具类型
- 函数工具:通过
FunctionToolCallback将 Java 函数转换为工具 - Agent 工具:通过
AgentTool将 Agent 转换为工具 - Interceptor 工具:通过
ModelInterceptor.getTools()提供工具
关键类关系
以下 PlantUML 类图展示了 Tool 系统的类关系:

关键流程
以下 PlantUML 时序图展示了工具调用的完整流程:

实现关键点说明
1. 工具注册机制
工具通过多种方式注册:
- 直接注册:通过 Builder 的
tools()方法 - Interceptor 工具:通过
ModelInterceptor.getTools() - 动态解析:通过
ToolCallbackResolver
2. 工具调用流程
工具调用经过以下步骤:
- LLM 生成工具调用请求
- AgentToolNode 解析工具调用
- 通过拦截器链执行工具
- 生成工具响应
- 更新状态
3. 拦截器支持
工具调用支持拦截器:
ToolInterceptor可以拦截工具调用- 支持请求修改和响应处理
- 支持重试、错误处理等功能
4. Agent 作为工具
Agent 可以作为工具被调用:
- 通过
AgentTool封装 - 状态隔离,不影响调用 Agent
- 支持指令传递
5. 工具解析器
支持动态工具解析:
ToolCallbackResolver接口- 可以按需加载工具
- 支持工具发现机制
6. 状态管理
工具调用可以更新状态:
- 通过
extraStateFromToolCall传递额外状态 - 工具响应添加到消息列表
- 支持状态合并
工具扩展示例
创建自定义工具
// 1. 使用 FunctionToolCallback
FunctionToolCallback tool = FunctionToolCallback.builder("myTool", (input, context) -> {
// 工具逻辑
return "result";
})
.description("My custom tool")
.build();
// 2. 实现 ToolCallback 接口
public class MyTool implements ToolCallback {
@Override
public String call(String arguments, ToolContext context) {
// 工具逻辑
return "result";
}
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
.name("myTool")
.description("My custom tool")
.build();
}
}
// 3. 将 Agent 转换为工具
ReactAgent subAgent = ReactAgent.builder()
.name("subAgent")
.description("Sub agent")
.model(model)
.build();
ToolCallback agentTool = AgentTool.getFunctionToolCallback(subAgent);
总结说明
核心设计理念
- 统一接口:所有工具实现
ToolCallback接口 - 灵活注册:支持多种工具注册方式
- 拦截器支持:工具调用支持拦截器链
- Agent 工具化:Agent 可以作为工具被调用
关键优势
- 灵活性:支持多种工具类型和注册方式
- 可扩展性:易于添加新的工具实现
- 可组合性:Agent 可以作为工具,实现 Agent 嵌套
- 拦截器支持:支持工具调用的拦截和修改
解决的问题
- 工具集成:统一工具接口,简化工具集成
- Agent 嵌套:通过 AgentTool 实现 Agent 嵌套调用
- 动态工具:通过 ToolCallbackResolver 支持动态工具加载
- 工具拦截:通过拦截器支持工具调用的增强
使用场景
- 函数工具:将业务函数封装为工具
- Agent 工具:将 Agent 封装为工具,实现多 Agent 协作
- Interceptor 工具:通过拦截器提供内置工具
- 动态工具:通过解析器动态加载工具
Tool 系统为 Agent Framework 提供了强大的工具能力,使开发者能够灵活地扩展 Agent 的功能,实现复杂的业务需求。
Spring AI Alibaba 项目源码学习(十二)-完结:Tool的更多相关文章
- OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa
元数据最本质.最抽象的定义为:data about data (关于数据的数据).它是一种广泛存在的现象,在许多领域有其具体的定义和应用. JDBC中的元数据,有数据库元数据(DatabaseMeta ...
- Linux0.11源码学习(二)
Linux0.11源码学习(二) linux0.11源码学习笔记 参考资料:https://github.com/sunym1993/flash-linux0.11-talk 源码查看:https:/ ...
- spring boot 2.0 源码分析(二)
在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...
- [spring源码学习]十、IOC源码-conversionService
一.代码示例 1.我们在之前的Person类里新增一个两个属性,分别是客户的兴趣和生日,兴趣爱好有很多,我们使用list进行保存,生日使用日期进行保存 public class Person { pr ...
- Spring源码学习(二)AOP
----ProxyFactoryBean这个类,这是AOP使用的入口---- AOP有些特有的概念,如:advisor.advice和pointcut等等,使用或配置起来有点绕,让人感觉有些距离感,其 ...
- Spring源码学习(六)-spring初始化回调方法源码学习
1.spring官方指定了三种初始化回调方法 1.1.@PostConstruct.@PreDestory 1.2.实现 InitializingBean DisposableBean 接口 1.3. ...
- ABP源码分析十二:本地化
本文逐个分析ABP中涉及到locaization的接口和类,以及相互之间的关系.本地化主要涉及两个方面:一个是语言(Language)的管理,这部分相对简单.另一个是语言对应得本地化资源(Locali ...
- SparkStreaming(源码阅读十二)
要完整去学习spark源码是一件非常不容易的事情,但是咱可以积少成多嘛~那么,Spark Streaming是怎么搞的呢? 本质上,SparkStreaming接收实时输入数据流并将它们按批次划分,然 ...
- 浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...
- java源码学习(二)Integer
Integer类包含了一个原始基本类型int.Integer属性中就一个属性,它的类型就是int. 此外,这个类还提供了几个把int转成String和把String转成int的方法,同样也提供了其它跟 ...
随机推荐
- 使用LiveGBS GB28181下级平台如何级联给上级GB28181视频平台(海康、华为、大华等平台)
@ 目录 1.什么是GB/T28181级联 2.搭建GB28181国标流媒体平台 3.获取上级接入配置信息 3.1.接入LiveGBS示例 3.2.接入第三方国标平台网关 4.配置国标级联 4.1.国 ...
- 将摄像头、NVR等输出的RTSP直播流转换成GB28181国标流
Onvif/RTSP流媒体服务 LiveNVR Onvif/RTSP流媒体服务,支持RTSP稳定拉流接入,支持Onvif协议接入,支持RTMP/HLS/HTTP-FLV/RTSP/Websocket- ...
- LiveNVR监控摄像头RTSP流web直播中服务中增加系统资源监视
RTSP/Onvif流媒体服务搭建 搭建入口,解压启动即用: https://www.liveqing.com/docs/download/LiveNVR.html 概览的意义 如何对我的单个流媒体服 ...
- 安防摄像通过GB28181协议实现云端录像存储与回放
GB28181流媒体云端录像存储 区别于数字硬盘摄像机通过GB28181的实时录像查询,有的时候,需要将普通的GB28181国标摄像机或设备的视频在云端录像存储,LiveGBS支持这种云端存储解决方案 ...
- Group By很慢,如何定位?如何优化?
前言 有些小伙伴在工作中可能遇到过这样的场景:原本运行良好的Group By查询,随着数据量的增长,执行时间从几秒变成了几分钟甚至几小时. 页面加载缓慢,用户抱怨连连,DBA着急上火. 这种性能下降往 ...
- 现代C++(Modern C++)基本用法实践:一、类型推导
概述 类型推导主要是依赖auto关键字和decltype关键字/运算符实现的,具体用法参考下面的例子. 二者特点: auto 用于声明时推导遍历 decltype 用于推导各种表达式, decltyp ...
- HEIF:更高质量、更小体积,开启 HarmonyOS 图像新体验
一.图像发展大趋势及 HarmonyOS 图像格式支持策略 自数字图像诞生以来,图像格式经历了多轮更新迭代.从早期的BMP.TIFF等无压缩或轻压缩格式,到后来广泛应用的JPEG.PNG.GIF等主流 ...
- C语言之动态内存分配与释放
本文首先介绍了通用指针类型void的特点,接着给出了在堆上动态分配内存空间主要依赖的三个函数(malloc.calloc和realloc)和内存释放函数free的使用方法和注意事项. 通用指针类型vo ...
- iOS判断字符串中含不含有汉字
转载请注明出处!!! 方法一: - (BOOL)hasChinese:(NSString *)str { for(int i=0; i< [str length];i++){ int a = [ ...
- 剑指offer-3、从尾到头打印链表
题目描述 输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回). 如输入{1,2,3}的链表如下图: 返回一个数组为[3,2,1] 0 <= 链表长度 <= 10000 ...