Spring AI alibaba 工具调用
本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别。
文章目录
- 概述
- 一、工具调用
- 1.1、工具调用流程
- 1.2、工具的定义使用
- 二、ToolCallback
- 三、工具上下文
- 四、ToolCallingManager
- 五、异常处理
概述
用户在使用AI时,可能不仅仅需要AI对于问题给出答案,而是需要AI提供一些功能的扩展,例如提供图片,资源下载,文件生成等。这些操作则是利用到了AI工具调用
的特性。
一、工具调用
1.1、工具调用流程
工具调用简单来说,就是让AI大模型使用外部工具完成特定功能。工具调用,不是让AI服务器自己调用工具,提供调用逻辑的工具取决于客户端应用程序:
- 模型只能请求一个工具调用并提供输入参数。
- 应用程序则负责执行来自输入参数的工具调用并返回结果。
模型无法直接访问到用户编写的工具的API,这是一个至关重要的安全考虑因素,工具调用的执行流程:
- 用户通过注解或编程的方式,创建工具类,自己编写利用工具要实现的业务逻辑。将工具类注册到chatClient中,可以是针对单个对话设置,也可以是全局设置。即程序告诉AI可以使用的工具,描述每个工具的参数和功能。
大模型
在对话中分析用户的问题,判断需要使用哪个工具,输出工具名称和参数的信息。Spring AI
将工具调用的请求进行分发,应用程序
执行对应的工具调用的业务逻辑。应用程序
将执行工具的结果发回给Spring AI
。Spring AI
将结果发送给大模型。大模型
根据上一步的结果,再次生成最终的回答给用户。
相当于在应用程序和大模型之间,通过Spring AI中转了一道,大模型无法直接调用应用程序编写的工具。
1.2、工具的定义使用
工具的定义,有注解式和编程式两种,注解式相对比较简单,主要是通过@Tool
和@ToolParam
注解实现,例如定义一个简单的本地文件读写工具:
- @Tool注解的description 属性,表明了加入了该注解的方法的作用。
- @ToolParam的description 属性,表明了加入了该注解的方法中参数的含义。
/*** 文件读写工具*/
public class FileOperationTool {private final String FILE_DIR = FileConstant.FILE_SAVE_DIR + "/file";@Tool(description = "Read content from a file")public String readFile(@ToolParam(description = "Name of the file to read") String fileName) {String filePath = FILE_DIR + "/" + fileName;try {return FileUtil.readUtf8String(filePath);} catch (Exception e) {return "Error reading file: " + e.getMessage();}}@Tool(description = "Write content to a file")public String writeFile(@ToolParam(description = "Name of the file to write") String fileName,@ToolParam(description = "Content to write to the file") String content) {String filePath = FILE_DIR + "/" + fileName;try {// 创建目录FileUtil.mkdir(FILE_DIR);FileUtil.writeUtf8String(content, filePath);return "File written successfully to: " + filePath;} catch (Exception e) {return "Error writing to file: " + e.getMessage();}}
}
在定义了工具类后,还需要编写一个配置类,将所有的工具类组成一个ToolCallback[]
对象:
@Configuration
public class ToolRegistration {@Beanpublic ToolCallback[] toolCallbacks(){FileOperationTool fileOperationTool = new FileOperationTool();PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();TerminalOperationTool terminalOperationTool = new TerminalOperationTool();WebScrapingTool webScrapingTool = new WebScrapingTool();WebSearchTool webSearchTool = new WebSearchTool();return ToolCallbacks.from(fileOperationTool,pdfGenerationTool,resourceDownloadTool,terminalOperationTool,webScrapingTool,webSearchTool);}
}
最后在构造chatClient时进行指定:
/*** 调用工具*/@Resourceprivate ToolCallback[] toolCallbacks;/*** 调用工具链* @param text* @param chatId* @return*/public String doChatWithTools(String text, String chatId){ChatResponse chatResponse = chatClient.prompt().user(text).advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)).tools(toolCallbacks).call().chatResponse();return chatResponse.getResult().getOutput().getText();}
二、ToolCallback
工具底层的核心是ToolCallback
接口:
其中提供了三个方法,getToolDefinition
有两个实现,分别代表了注解方式
和函数式调用
方式。
这里的ToolDefinition
是AI 模型用于确定何时以及如何调用工具的定义,可以指定工具名称,工具说明,以及工具的约束文档。
getToolMetadata
是获取工具的元信息。ToolMetadata
中包含了一个关键的returnDirect
,它代表了使用工具获取到的结果,是直接返回给用户,还是要发送给大模型再次进行处理,默认是false。可以在注解中指定:
@Tool(description = “Download a resource from a given URL”,returnDirect = false)
call则是将调用工具获取到的结果发送给大模型:
三、工具上下文
ToolContext
代表了工具上下文,是一个map的结构,它类似于ThreadLocal,可以将一些信息放入上下文中传递。
可以通过chatClient的toolContext
方法进行设置。这些信息只会在应用程序的上下文中进行传递,不会发送给AI大模型,也就避免了敏感信息泄露的问题。
四、ToolCallingManager
ToolCallingManager
是管理具调用过程的服务,主要分为了解析和调用两个步骤:
executeToolCalls
是调用工具的方法,其中主要做了如下几件事:
- 获取 AI 模型返回的所有生成结果,筛选出 “包含工具调用指令” 的结果,取第一个符合条件的结果。目的是从 AI 模型的响应中,定位到包含工具调用指令的那一条结果。
- 如果没有找到任何包含工具调用的结果,直接抛出异常。当前方法的职责就是 “执行工具调用”,如果 AI 模型根本没要求调用工具,说明调用场景不匹配(比如上游逻辑判断错误)
- 获取 AI 模型输出的具体内容,封装到
助手提示词
中。包含关键信息:工具名称(toolName)、调用参数(parameters)等。 - 调用
buildToolContext
方法,创建工具执行所需的上下文 - 根据
助手提示词
中的工具名称,找到对应的ToolCallback实现类
,调用工具的execute方法执行具体业务逻辑。 - 更新对话历史,将工具调用及结果整合到对话历史中,整合的内容包括:原始用户指令,AI 模型的工具调用指令,工具执行的结果。
- 构建并返回最终结果,构建
ToolExecutionResult
对象,其中包含了更新后的完整对话历史和是否直接将工具结果返回给用户。这个结果会被返回给ChatModel,作为下一步处理的输入
通常情况下,都是框架控制工具执行,即Spring AI 将自动截取来自模型的任何工具调用请求,调用该工具并将结果返回到模型中。,如果上述的过程,需要用户自己去控制,可以通过以下代码实现:
@Component
public class ChatToolsApp {@Resourceprivate ChatModel dashscopeChatModel;public String testChatTools() {ChatOptions chatOptions = ToolCallingChatOptions.builder().toolCallbacks(ToolCallbacks.from(new SystemDateTimeTool())).internalToolExecutionEnabled(false).build();// 创建工具调用管理器ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();// 创建初始提示Prompt prompt = new Prompt("获取当前系统的日期时间", chatOptions);// 传递用户问题给大模型 ,大模型分析用户问题,判断是否需要使用工具,响应工具名称和参数到ChatResponseChatResponse chatResponse = dashscopeChatModel.call(prompt);// ChatResponse的hasToolCalls不为空,证明大模型认为需要调用工具while (chatResponse.hasToolCalls()) {// 接收工具调用的请求,选择和大模型响应工具名称相匹配的工具,然后调用该工具,。ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);// 创建包含工具结果的新提示prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);// 再次发送请求给模型,生成最终的回答chatResponse = dashscopeChatModel.call(prompt);}// 获取最终回答System.out.println(chatResponse.getResult().getOutput().getText());return chatResponse.getResult().getOutput().getText();}}
五、异常处理
当工具调用失败时,框架抛出的异常类型是ToolExecutionException
,ToolExecutionExceptionProcessor
可以用来处理ToolExecutionException
:
它是一个接口,在实现类中,有一个alwaysThrow
属性,默认值是false,如果true将抛出一个异常,而不是向模型发送错误消息。
如果需要自定义,可以通过:
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {return new DefaultToolExecutionExceptionProcessor(true);
}