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

工具注册机制

工具注册流程

工具通过以下方式注册:

  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 系统的类关系:

Spring AI Alibaba 项目源码学习(十二)-完结:Tool

关键流程

以下 PlantUML 时序图展示了工具调用的完整流程:
Spring AI Alibaba 项目源码学习(十二)-完结:Tool

实现关键点说明

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 的功能,实现复杂的业务需求。

发表评论

评论已关闭。

相关文章