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
// ...
}
}

工具注册机制

工具注册流程

工具通过以下方式注册:

  1. 直接注册:通过 ReactAgent.Builder.tools() 方法注册
  2. Interceptor 工具:通过 ModelInterceptor.getTools() 方法提供
  3. 工具解析器:通过 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);

工具调用流程

工具调用步骤

  1. LLM 生成工具调用:AgentLlmNode 调用 LLM,LLM 返回包含工具调用的 AssistantMessage
  2. 工具调用解析:AgentToolNode 解析 AssistantMessage 中的工具调用
  3. 工具执行:通过工具拦截器链执行工具调用
  4. 响应生成:将工具执行结果封装为 ToolResponseMessage
  5. 状态更新:更新状态中的消息列表

关键代码

/**
* 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));
}

工具扩展机制

自定义工具实现

开发者可以通过以下方式扩展工具:

  1. 实现 ToolCallback 接口:创建自定义工具
  2. 使用 FunctionToolCallback:将函数转换为工具
  3. AgentTool:将 Agent 转换为工具

工具类型

  • 函数工具:通过 FunctionToolCallback 将 Java 函数转换为工具
  • Agent 工具:通过 AgentTool 将 Agent 转换为工具
  • Interceptor 工具:通过 ModelInterceptor.getTools() 提供工具

关键类关系

以下 PlantUML 类图展示了 Tool 系统的类关系:

关键流程

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

实现关键点说明

1. 工具注册机制

工具通过多种方式注册:

  • 直接注册:通过 Builder 的 tools() 方法
  • Interceptor 工具:通过 ModelInterceptor.getTools()
  • 动态解析:通过 ToolCallbackResolver

2. 工具调用流程

工具调用经过以下步骤:

  1. LLM 生成工具调用请求
  2. AgentToolNode 解析工具调用
  3. 通过拦截器链执行工具
  4. 生成工具响应
  5. 更新状态

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);

总结说明

核心设计理念

  1. 统一接口:所有工具实现 ToolCallback 接口
  2. 灵活注册:支持多种工具注册方式
  3. 拦截器支持:工具调用支持拦截器链
  4. Agent 工具化:Agent 可以作为工具被调用

关键优势

  • 灵活性:支持多种工具类型和注册方式
  • 可扩展性:易于添加新的工具实现
  • 可组合性:Agent 可以作为工具,实现 Agent 嵌套
  • 拦截器支持:支持工具调用的拦截和修改

解决的问题

  • 工具集成:统一工具接口,简化工具集成
  • Agent 嵌套:通过 AgentTool 实现 Agent 嵌套调用
  • 动态工具:通过 ToolCallbackResolver 支持动态工具加载
  • 工具拦截:通过拦截器支持工具调用的增强

使用场景

  • 函数工具:将业务函数封装为工具
  • Agent 工具:将 Agent 封装为工具,实现多 Agent 协作
  • Interceptor 工具:通过拦截器提供内置工具
  • 动态工具:通过解析器动态加载工具

Tool 系统为 Agent Framework 提供了强大的工具能力,使开发者能够灵活地扩展 Agent 的功能,实现复杂的业务需求。

Spring AI Alibaba 项目源码学习(十二)-完结:Tool的更多相关文章

  1. OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa

    元数据最本质.最抽象的定义为:data about data (关于数据的数据).它是一种广泛存在的现象,在许多领域有其具体的定义和应用. JDBC中的元数据,有数据库元数据(DatabaseMeta ...

  2. Linux0.11源码学习(二)

    Linux0.11源码学习(二) linux0.11源码学习笔记 参考资料:https://github.com/sunym1993/flash-linux0.11-talk 源码查看:https:/ ...

  3. spring boot 2.0 源码分析(二)

    在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...

  4. [spring源码学习]十、IOC源码-conversionService

    一.代码示例 1.我们在之前的Person类里新增一个两个属性,分别是客户的兴趣和生日,兴趣爱好有很多,我们使用list进行保存,生日使用日期进行保存 public class Person { pr ...

  5. Spring源码学习(二)AOP

    ----ProxyFactoryBean这个类,这是AOP使用的入口---- AOP有些特有的概念,如:advisor.advice和pointcut等等,使用或配置起来有点绕,让人感觉有些距离感,其 ...

  6. Spring源码学习(六)-spring初始化回调方法源码学习

    1.spring官方指定了三种初始化回调方法 1.1.@PostConstruct.@PreDestory 1.2.实现 InitializingBean DisposableBean 接口 1.3. ...

  7. ABP源码分析十二:本地化

    本文逐个分析ABP中涉及到locaization的接口和类,以及相互之间的关系.本地化主要涉及两个方面:一个是语言(Language)的管理,这部分相对简单.另一个是语言对应得本地化资源(Locali ...

  8. SparkStreaming(源码阅读十二)

    要完整去学习spark源码是一件非常不容易的事情,但是咱可以积少成多嘛~那么,Spark Streaming是怎么搞的呢? 本质上,SparkStreaming接收实时输入数据流并将它们按批次划分,然 ...

  9. 浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...

  10. java源码学习(二)Integer

    Integer类包含了一个原始基本类型int.Integer属性中就一个属性,它的类型就是int. 此外,这个类还提供了几个把int转成String和把String转成int的方法,同样也提供了其它跟 ...

随机推荐

  1. ROS-noetic+UR5上安装robotiq_85_gripper夹爪

    夹抓(未配置控制器,无中间过程) ROS-noetic+UR5上安装robotiq_85_gripper夹爪 夹抓(详细配置过程,依赖最新代码) 在UR5机械臂末端添加robotiq 2f 85夹爪并 ...

  2. AI安全-提示词注入

    攻击技术分类 高级 Prompt Injection 技术 绕过与混淆策略 防御机制与对抗策略 攻防手册 靶场练习 类型 描述 Prompt Injection 利用精心设计的提示词操控模型行为,绕过 ...

  3. google_test

    Google_Test 这里学习一下相关googleTest的功能. 全文来源于:GoogleTest.笔者只进行翻译和自我理解. 安装与启动 首先我们创建一个属于自己的工作区(文件夹).创建工作区的 ...

  4. 关于处理大批量数据下载和查询时,怎么进行限流和熔断处理(AI)

    在处理大批量数据下载和查询时,限流和熔断是保障系统稳定性的关键手段.它们分别从"控制流量输入"和"阻断故障传播"两个维度保护系统,避免因过载或依赖服务异常导致整 ...

  5. Qwen3技术报告

    原文: https://mp.weixin.qq.com/s/3RXdXT8hzlsMp_Uk_BvpfQ 全文摘要 本文介绍了最新的 Qwen 模型家族--Qwen3,它是一个大型语言模型系列,旨在 ...

  6. hollow

    hollow 原题链接 给你一个由 \(n\) 段若干个 \(0\) 或 \(1\) 组成的序列,每次可以选择一段区间翻转,每次操作后问最长不下降子序列长度. 显然地,我们可以把连续的相同数字看成一个 ...

  7. Oracle MISSING00000文件故障恢复

    前几天介绍了自己开发的Oracle Recovery Tools工具,今天有客户遇到故障,数据库由于断电无法正常启动,第三方对其进行了重建控制文件操作,但是有两个数据文件没有加入到重建控制文件脚本中, ...

  8. Java 字段命名避坑: success和isSuccess

    前几天开发时踩了个坑,前端拿不到 isSuccess 的值,调接口看返回也确实有数据,排查了半天才发现是字段命名的问题.今天就把这个踩坑过程整理出来,希望能帮大家少走弯路. Java Bean 的 g ...

  9. Java对手机号加*处理

    在实际的生活中,对于敏感数据,都不会轻易展示出来.如在诸多应用中,手机号中间4位是*号,身份证号中出生信息是*号,那么这些数据都是在后端经过加密后返回给前端的. 1.手机号对中间4位加密 phone. ...

  10. for (int i = 0; i < v.size() - 1; i++)

    std::vector<int> v;     for (int i = 0; i < v.size() - 1; i++)     {         int x = v[i]; ...