小户型室内装修设计公司网站,微信公众号被收费299,石家庄大型网络销售公司,Wix做的网站在国内打不开为了助于大家学习理解#xff0c;本文会通过旅游规划应用案例演示工具调用的实际应用开发#xff1a;工具调用#xff08;入门#xff09;什么是工具调用呢#xff1f;工具调用是Tool Calling也叫Function Calling#xff0c;让AI大模型借用外部工具来完成它自己做不到的…为了助于大家学习理解本文会通过旅游规划应用案例演示工具调用的实际应用开发工具调用入门什么是工具调用呢工具调用是Tool Calling也叫Function Calling让AI大模型借用外部工具来完成它自己做不到的事情工具调用的工作原理整个过程是由我们应用程序来控制的AI拿到用户的问题发现回答不了这时候它需要使用工具来回答用户的问题后端程序判断出AI需要哪个工具然后我们让AI使用这个工具。其实就是说AI只是决定什么时候用工具需要传递什么参数真正执行工具的是我们的程序。那为什么不直接把工具丢给AI而要通过后端来执行判断把工具给AI关键是安全性如果就把工具丢给AI让AI自己决定那肯定是不行的要是AI想用就用想干嘛就干嘛那还得了而且这样也给AI服务器本身增加压力。所以呢所有操作必须由我们程序控制执行不让AI接触到你的API和系统资源。工具调用的流程1、工具定义我们程序告诉AI你可以使用哪些工具并描述工具的功能和所需参数2、工具选择AI根据用户问题判断要使用哪个工具并准备好相应参数3、返回意图AI给程序返回“我想要使用这个工具...参数是...”4、工具执行程序接收到请求执行相应的工具操作5、结果返回程序将工具执行的结果返回给AI6、继续对话AI根据根据返回的结果生成最终回答给用户要实现这些我们当然可以自主开发不过还是更推荐使用Spring AI、LangChain等开发框架Spring AI工具开发1、定义工具在Spring AI中主要有两种模式1、推荐基于Methods方法 2、Functions函数式编程。第二种了解即可咱只要用第一种基于Methods方法来定义工具就可以因为它更容易编写、理解、支持的参数和返回类型更多。Methods模式通过Tool注解定义工具通过tools方法绑定工具使用ToolParam注解提供额外的描述信息Functions模式通过Bean注解定义工具通过functions方法绑定工具2、使用工具Spring AI提供了多种灵活的方式将工具提供给ChatClient让AI能在需要时调用工具。1、按需使用 2、全局使用 3、更底层使用方式 4、动态解析主流工具开发如果在github社区没有找到能满足我们需求的工具就要自己开发一个工具那就让我们来开始开发吧文件操作工具---首先在dogaiagent根目录下新建一个tools包在tmp目录下新建一个file目录。那为什么要在tmp下建一个file呢这是因为文件操作提供了两大功能--往我们电脑保存文件和读取文件那就会涉及到一定风险如果AI脑抽了不断往我们电脑保存垃圾文件那就完蛋了所以我们要规定跟AI操作文件有关的限制在tmp临时目录下的file中。那我们继续在dogaiagent下再新建一个constant包再在包下新建一个FileConstant接口来放文件操作要用的一些常量代码如下public interface FileConstant { /** * 文件保存目录 */ String FILE_SAVE_DIR System.getProperty(user.dir) /tmp; }在tools包下新建一个FileOperationTool类来写文件操作工具读写功能代码如下/** * 文件操作工具类提供文件读写功能 */ 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(); } } }然后编写对应单元测试代码如下SpringBootTest class FileOperationToolTest { Test void readFile() { FileOperationTool fileOperationTool new FileOperationTool(); String fileName 旅游规划.txt; String result fileOperationTool.readFile(fileName); Assertions.assertNotNull(result); } Test void writeFile() { FileOperationTool fileOperationTool new FileOperationTool(); String fileName 旅游规划.txt; String content TheChosenOne---欢迎来到GitHub; String result fileOperationTool.writeFile(fileName, content); Assertions.assertNotNull(result); } }注意测试时要先写再读也就是先运行writeFile()再运行readFile()。联网搜索工具---使用专业的网页搜索API如Search API实现从多个网站搜索内容不过要计费但是呢新用户免费送100次请求那让我们来开发一个联网搜索工具吧。在tools包下新建一个WebSearchTool类然后呢很简单找到Search API的baidu将这里面文档复制给AI让AI给我们生成Java代码然后将代码复制粘贴到WebSearchTool类中代码如下/** * Web 搜索工具类提供百度搜索功能 */ public class WebSearchTool { // SearchAPI 的搜索接口地址 private static final String SEARCH_API_URL https://www.searchapi.io/api/v1/search; private final String apiKey; public WebSearchTool(String apiKey) { this.apiKey apiKey; } Tool(description Search for information from Baidu Search Engine) public String searchWeb( ToolParam(description Search query keyword) String query) { MapString, Object paramMap new HashMap(); paramMap.put(q, query); paramMap.put(api_key, apiKey); paramMap.put(engine, baidu); try { String response HttpUtil.get(SEARCH_API_URL, paramMap); // 取出返回结果的前 5 条 JSONObject jsonObject JSONUtil.parseObj(response); // 提取 organic_results 部分 JSONArray organicResults jsonObject.getJSONArray(organic_results); ListObject objects organicResults.subList(0, 5); // 拼接搜索结果为字符串 String result objects.stream().map(obj - { JSONObject tmpJSONObject (JSONObject) obj; return tmpJSONObject.toString(); }).collect(Collectors.joining(,)); return result; } catch (Exception e) { return Error searching Baidu: e.getMessage(); } } }然后在application.yml和application-local.yml中配置API Key# searchApi search-api: api-key: 你的 API Key然后编写对应的单元测试代码如下SpringBootTest class WebSearchToolTest { Value(${search-api.api-key}) private String searchApiKey; Test void searchWeb() { WebSearchTool webSearchTool new WebSearchTool(searchApiKey); String query 如何学习JavaAI; String result webSearchTool.searchWeb(query); Assertions.assertNotNull(result); } }注意如果测试出来的result为error不用担心因为AI是有可能失败的多Debug测试几次会成功的。网页抓取工具---网页抓取工具的作用是根据网址解析到网页的内容使用jsoup库来实现网页内容抓取和解析让我们来开发吧首先引入jsoup的依赖dependency groupIdorg.jsoup/groupId artifactIdjsoup/artifactId version1.21.2/version /dependency在tools包下新建一个WebScrapingTool类实现网页抓取工具代码如下/** * Web 抓取工具类提供网页抓取功能 */ public class WebScrapingTool { Tool(description Scrape the content of a web page) public String scrapeWebPage(ToolParam(description URL of the web page to scrape) String url) { try { Document doc Jsoup.connect(url).get(); return doc.html(); } catch (IOException e) { return Error scraping web page: e.getMessage(); } } }然后生成对应单元测试代码如下class WebScrapingToolTest { Test void scrapeWebPage() { WebScrapingTool webScrapingTool new WebScrapingTool(); String url https://www.baidu.com/; String result webScrapingTool.scrapeWebPage(url); Assertions.assertNotNull(result); } }注意Debug测试的result可能抓取不了网页这是因为网页是异步的没有加载时间所以看不到抓取的信息可以使用Sublime Text粘贴进去看。终端操作工具---指在终端执行命令比如执行python命令来运行脚本在tools包下新建一个TerminalOperationTool类AI生成就好了不用自己写实现终端操作工具代码如下注意这个的代码Windows和其他系统不一样其他系统代码如下public class TerminalOperationTool { Tool(description Execute a command in the terminal) public String executeTerminalCommand(ToolParam(description Command to execute in the terminal) String command) { StringBuilder output new StringBuilder(); try { Process process Runtime.getRuntime().exec(command); try (BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { output.append(line).append(\n); } } int exitCode process.waitFor(); if (exitCode ! 0) { output.append(Command execution failed with exit code: ).append(exitCode); } } catch (IOException | InterruptedException e) { output.append(Error executing command: ).append(e.getMessage()); } return output.toString(); } }Windows的代码如下public class TerminalOperationTool { Tool(description Execute a command in the terminal) public String executeTerminalCommand(ToolParam(description Command to execute in the terminal) String command) { StringBuilder output new StringBuilder(); try { ProcessBuilder builder new ProcessBuilder(cmd.exe, /c, command); // Process process Runtime.getRuntime().exec(command); Process process builder.start(); try (BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { output.append(line).append(\n); } } int exitCode process.waitFor(); if (exitCode ! 0) { output.append(Command execution failed with exit code: ).append(exitCode); } } catch (IOException | InterruptedException e) { output.append(Error executing command: ).append(e.getMessage()); } return output.toString(); } }然后生成对应单元测试代码如下SpringBootTest class TerminalOperationToolTest { Test void executeTerminalCommand() { TerminalOperationTool tool new TerminalOperationTool(); String command dir; String result tool.executeTerminalCommand(command); Assertions.assertNotNull(result); } }资源下载工具---使用Hutool的HttpUtil.downloadFile方法实现资源下载在tools包下新建一个ResourceDownloadTool类实现代码如下public class ResourceDownloadTool { Tool(description Download a resource from a given URL) public String downloadResource(ToolParam(description URL of the resource to download) String url, ToolParam(description Name of the file to save the downloaded resource) String fileName) { String fileDir FileConstant.FILE_SAVE_DIR /download; String filePath fileDir / fileName; try { // 创建目录 FileUtil.mkdir(fileDir); // 使用 Hutool 的 downloadFile 方法下载资源 HttpUtil.downloadFile(url, new File(filePath)); return Resource downloaded successfully to: filePath; } catch (Exception e) { return Error downloading resource: e.getMessage(); } } }然后生成对应单元测试测试成功会在download目录下生成logo.png测试代码如下SpringBootTest class ResourceDownloadToolTest { Test void downloadResource() { ResourceDownloadTool tool new ResourceDownloadTool(); String url https://www.baidu.com/img/bd_logo1.png; String fileName logo.png; String result tool.downloadResource(url, fileName); assertNotNull(result); } }PDF生成工具---作用是根据文件名和内容生成PDF文档并且保存使用itext库实现PDF生成工具不过itext对中文字体不太友好需要我们额外配置中文字体要做好建议自行下载需要的字体使用我们这边直接就使用itext内置的中文字体首先引入依赖dependency groupIdcom.itextpdf/groupId artifactIditext-core/artifactId version9.1.0/version typepom/type /dependency dependency groupIdcom.itextpdf/groupId artifactIdfont-asian/artifactId version9.1.0/version scopetest/scope /dependency在tools包下新建一个PDFGenerationTool类实现以下代码public class PDFGenerationTool { Tool(description Generate a PDF file with given content) public String generatePDF( ToolParam(description Name of the file to save the generated PDF) String fileName, ToolParam(description Content to be included in the PDF) String content) { String fileDir FileConstant.FILE_SAVE_DIR /pdf; String filePath fileDir / fileName; try { // 创建目录 FileUtil.mkdir(fileDir); // 创建 PdfWriter 和 PdfDocument 对象 try (PdfWriter writer new PdfWriter(filePath); PdfDocument pdf new PdfDocument(writer); Document document new Document(pdf)) { // 自定义字体需要人工下载字体文件到特定目录 // String fontPath Paths.get(src/main/resources/static/fonts/simsun.ttf) // .toAbsolutePath().toString(); // PdfFont font PdfFontFactory.createFont(fontPath, // PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); // 使用内置中文字体 PdfFont font PdfFontFactory.createFont(STSongStd-Light, UniGB-UCS2-H); document.setFont(font); // 创建段落 Paragraph paragraph new Paragraph(content); // 添加段落并关闭文档 document.add(paragraph); } return PDF generated successfully to: filePath; } catch (IOException e) { return Error generating PDF: e.getMessage(); } } }然后生成对应单元测试进行测试一下代码如下SpringBootTest class PDFGenerationToolTest { Test void generatePDF() { PDFGenerationTool tool new PDFGenerationTool(); String fileName 百度.pdf; String content 百度网址 https://www.baidu.com/; String result tool.generatePDF(fileName, content); assertNotNull(result); } }开发了这么多工具为了方便我们统一管理和绑定所有工具可以给AI一次性提供所有工具让AI决定什么时候使用我们需要创建一个集中工具注册类。在tools包下新建一个ToolRegistration类编写以下代码/** * 集中的工具注册 */ Configuration public class ToolRegistration { Value(${search-api.api-key}) private String searchApiKey; Bean public ToolCallback[] allTools() { FileOperationTool fileOperationTool new FileOperationTool(); WebSearchTool webSearchTool new WebSearchTool(searchApiKey); WebScrapingTool webScrapingTool new WebScrapingTool(); ResourceDownloadTool resourceDownloadTool new ResourceDownloadTool(); TerminalOperationTool terminalOperationTool new TerminalOperationTool(); PDFGenerationTool pdfGenerationTool new PDFGenerationTool(); return ToolCallbacks.from( fileOperationTool, webSearchTool, webScrapingTool, resourceDownloadTool, terminalOperationTool, pdfGenerationTool ); } }注意Value引的是import org.springframework.beans.factory.annotation.Value;补充它暗含了好几种设计模式工厂模式allTools() 方法作为一个工厂方法负责创建和配置多个工具实例然后将它们包装成统一的数组返回。这符合工厂模式的核心思想 - 集中创建对象并隐藏创建细节。依赖注入模式通过Value注解注入配置值以及将创建好的工具通过 Spring 容器注入到需要它们的组件中。注册模式该类作为一个中央注册点集中管理和注册所有可用的工具使它们能够被系统其他部分统一访问。适配器模式的应用ToolCallbacks.from 方法可以看作是一种适配器它将各种不同的工具类转换为统一的 ToolCallback 数组使系统能够以一致的方式处理它们。有了这个注册类如果需要添加或移除工具只需修改这一个类即可更利于维护。然后我们来给我们的项目使用这些工具在TravelApp中编写以下代码//旅游规划大师调用工具能力 Resource private ToolCallback[] allTools; public String doChatWithTools(String message, String chatId) { ChatResponse response chatClient .prompt() .user(message) .advisors(spec - spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) // 开启日志便于观察效果 .advisors(new MyLoggerAdvisor()) .tools(allTools) .call() .chatResponse(); String content response.getResult().getOutput().getText(); log.info(content: {}, content); return content; }生成对应的单元测试代码如下Test void doChatWithTools() { // 测试联网搜索问题的答案 testMessage(国庆打算和女朋友在福建约会推荐几个治愈的小众打卡地); // 测试网页抓取旅游规划案例分析 testMessage(最近学习压力太大了怎么通过旅游来放松); // 测试资源下载图片下载 testMessage(下载一张厦门鼓浪屿的图片); // 测试终端操作执行代码 testMessage(执行 Python3 脚本来生成数据分析报告); // 测试文件操作保存用户档案 testMessage(保存我的旅游规划档案为文件); // 测试 PDF 生成 testMessage(生成一份‘国庆旅游计划清单’PDF包含餐厅预订、活动流程和景点打卡); } private void testMessage(String message) { String chatId UUID.randomUUID().toString(); String answer travelApp.doChatWithTools(message, chatId); Assertions.assertNotNull(answer); }给工具类的代码打断点在 Debug 模式下观察工具的调 用过程和结果。不过这边报错了是因为itext的字体原因无法处理那我们把注释的自定义字体给它开启把内置的字体关掉就可以了。工具调用进阶知识了解即可工具底层数据结构Spring AI工具调用的核心在ToolCallback接口它是所有工具实现的基础。工具上下文实际开发应用中工具执行可能需要额外的上下文信息Spring AI通过ToolContext提供这一功能。立即返回工具执行的结果不需要再经过AI模型处理而是希望直接返回给用户Spring AI通过returnDirect属性支持这一功能。工具底层执行原理Spring AI提供了两种工具执行模式1、框架控制的工具执行 2、用户控制的工具执行它们都与ToolCallingManager这个管理AI调用全过程的核心组件有关。工具解析可观测性