当前位置: 首页 > news >正文

AI - 工具调用

AI应用开发的核心特性——工具调用,将大幅增强AI的能力。

**************************************************************************************************************

需求分析

通过RAG技术让AI应用具备了根据外部知识库来获取信息并回答的能力,增强功能可以借用工具调用特性,实现更多需求。

1、联网搜索

智能推荐旅游攻略,示例:

  • 周末想和朋友去其他城市游玩,推荐几个必打卡地
  • 遇到黑心商贩,宰客的应急处理技巧

2、网页抓取

分析其他网站方案,示例:

  • 去上海旅游,看看小红书打卡经验吧

3、资源下载

打卡地的特色,相关图片,视频下载。示例:

  • 下载图片分享给朋友,做壁纸,发圈
  • 推荐并下载适合作为背景音乐的BGM

4、终端操作

比如执行代码生成攻略报告,示例:

  • 执行Python脚本来生成数据分析报告

5、文件操作

保存攻略文件。示例:

  • 帮我保存我的攻略为文件

6、PDF生成

将攻略计划,生成PDF。示例:

  • 生成一份《上海2日游攻略》PDF,包含衣食住行

这些需求进行组合,先吃喝再逛景点,先购物再吃喝,哪些适合拍照,拿个打卡点应该避开闲忙时段等等,同时也可以将组合生成PDF等文件保存下载。

如果AI能够完成上述需求,就不再只是一个有知识的“人工智能”,而是有手有脚,会利用工具的完成任务的“智能体”了。

而关键就是——工具调用技术。

工具调用

什么是工具调用

工具调用(Tool Calling)可以理解为让AI大模型借用外部工具来完成它自己多不到的事情。

学习人类使用工具,工具可以是任何东西,比如网页搜索,对外部API调用,访问外部数据,执行特定代码等。

(用户提问明天天气如何?或者帮我查询近一周的天气,AI本身不具备这些知识,它可以调用“查询天气”,来完成用户提问的请求)

目前工具调用技术发展已经比较成熟,几乎所有主流的、新出的AI大模型和AI应用开发平台都支持工具调用。

工具调用工作原理

工具调用的工作原理:并不是AI服务器自己调用这些工具,也不是把工具的代码发送给AI服务器让他执行,他只是提出要求,表示“我要执行XX工具完成任务”。而真正执行工具的是我们自己的应用程序,执行后再把结果告诉AI,让他继续工作。

🙋‍♀️🌰

用户提问“近期大乐透中奖号码走势,且帮我根据走势,出几注复式号码。”,就需要经历一下流程

1、用户提出问题:“近期大乐透中奖号码走势”

2、程序将问题传递给大模型

3、大模型分析问题,判断需要哦使用工具(网页抓取工具)来获取信息

4、大模型输出工具名和参数(网页抓取工具,URL参数为https://www.lottery.gov.cn/)

5、程序接收工具调用请求,执行网页抓取操作

6、工具执行抓取并返回文章数据

7、程序将抓取结果传回给大模型

8、大模型分析网页内容,生成关于大乐透中奖号码走势的回答

9、程序将大模型的回答返回给用户

虽然看起来AI在调用工具,但实际上整个过程是由我们的应用程序控制的。AI只负责决定什时候需要调用工具,以及需要传递什么参数, 真正执行工具的是我们的程序。

为什么会如此设计,让程序请求AI多次?

因为安全性,AI模型永远无法直接触碰你的API和系统资源,所有操作都必须通过你的程序来执行,这样你可以完全控制AI能做什么,不能做什么。

🙋‍♀️🌰

你有一个炸弹,用户向AI提了需求“要引爆炸弹,拆大楼”,虽然AI表示可以使用炸弹,但必须得经过你的同意才能爆炸。反之,如果你把炸弹给了AI,当需要调用的时候,AI觉得自己能炸了,不需要询问你的意见,直接就炸了。这不扯呢吗。

工具调用和功能调用

Function Calling(功能调用)这个概念和Tool Calling(工具调用)这个概念一样。

Spring AI工具调用文档,开头说明了这一点。

工具调用的技术选型

工具调用的流程:

  1. 工具定义:程序告诉AI“你可以使用这些工具”,并描述每个工具的功能和所需参数
  2. 工具选择:AI在对话中判断需要使用某个工具,并准备好相应参数
  3. 返回意图:AI返回“我想用xx工具,参数是xxx”的信息
  4. 工具执行:我们程序接收请求,执行相应的工具操作
  5. 结果返回:程序将工具执行的结果发回给AI 
  6. 继续对话:AI根据工具返回的结果,生成最终回答给用户

不是所有的大模型都支持工具调用,有些基础模型或更早版本可能不支持这个能力。可以在Spring AI官方文档中查看个模型支持情况。

Spring AI工具开发

通过Spring AI官方提供的图片来理解Spring AI在实现工具调用时都帮我们做了哪些事情

  1. 工具定义与注册:Spring AI可以通过简洁的注解自动生成工具定义和JSON Schema,让Java方法轻松转变为AI可以调用的工具。
  2. 工具调用请求:Spring AI自动处理与AI模型的通信并解析工具调用请求,并且支持多个工具链调用。
  3. 工具执行:Spring AI提供统一的工具管理接口,自动根据AI返回的工具调用请求找到对应的工具并解析参数进行调用,让开发者专注于业务逻辑实现。
  4. 处理工具结果:Spring AI内置结果转换和异常处理机制,支持各种复杂Java对象作为返回值并优雅处理错误情况。
  5. 返回结果模型:Spring AI封装响应结果并管理上下文,确保工具执行结果正确传递给模型或者直接返回给用户。
  6. 生成最终相应:Spring AI自动整合工具调用结果到对话上下文,支持多轮复杂交互,确保AI回复连贯性和准确性。

下面是一个较早版本的流程图,也能帮助我们理解这个过程:

定义工具

工具定义模式

在Spring AI中,定义工具主要有两种模式:基于Methods方法或者Functions函数式编程。

Methods方式更容易编写和理解,支持的参数和返回类型更多。

Methods和Functions对比:

🙋‍♀️🌰

对比两种定义模式:

1、Methods模式:通过@Tool注解定义工具,通过tools方法绑定工具

class WeatherTools {@Tool(description = "Get current weather for a location")public String getWeather(@ToolParam(description = "The city name") String city) {return "Current weather in " + city + ": Cloud, 21°C";}
}
// 使用方式
ChatClient.create(chatModel).prompt("What's the weather in ShangHai?").tools(new WeatherTools()).call();

2、Functions模式:通过@Bean注解定义工具,通过Functions方法绑定工具

@Configuration
public class ToolConfig {@Bean@Description("Get current weather for a location")public Function<WeatherRequest, WeatherResponse> weatherFunction() {return request -> new WeatherResponse("Weather in " + request.getCity() + ": Cloud, 21°C");}
}
// 使用方式
ChatClient.create(chatModel).prompt("What's the weather in ShangHai?").functions("weatherFunction").call();

Methods模式开发量更少。

定义工具

Spring AI提供了两种定义工具的方法——注解式编程式

1、注解式

只需要使用@Tool注解标记普通Java方法,就可以定义工具了,更直观简洁。

每个工具最好都添加详细的描述,帮助AI理解何时应该调用这个工具。对于工具方法的参数,可以使用@ToolParam注解提供额外的描述信息和是否必填。

class WeatherTools {@Tool(description = "获取指定城市的当前天气情况")String getWeather(@ToolParam(description = "城市名称") String city) {// 获取天气的实现逻辑return "上海今天晴朗,气温21°C";}
}
2、编程式

如果想在运行时动态创建工具,可以选择编程式来定义工具,更灵活。

先定义工具类:

class WeatherTools {String getWeather(String city) {// 获取天气的实现逻辑return "上海今天晴朗,气温21°C";}
}

然后将工具类转换为ToolCalling工具定义类,之后就可以把这个类绑定给ChatClient,从而你让AI使用工具了。

Method method = ReflectionUtils.findMethod(WeatherTools.class, "getWeather", String.class);
ToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder(method).description("获取指定城市的当前天气情况").build()).toolMethod(method).toolObject(new WeatherTools()).build();

可以看出,编程式即使把注解式的那些参数,改成通过调用方法来设置了。

在定义工具时,需要注意方法参数和返回值类型的选择。Spring AI支持大多数常见的Java类型作为参数和返回值,包括基本类型,复杂对象,集合等。而返回值需要是可序列化的,因为将发送给AI大模型。

以下类型目前不支持作为工具方法的参数后返回类型:

  • Optional
  • 异步类型(如CompletableFuture,Future)
  • 响应式类型(如Floe,Mono,Flux)
  • 函数式类型(如Function,Supplier,Consumer)
使用工具

定义好工具后,Spring AI提供了多种灵活的方式将工具提供给ChatClient,让AI能够在需要时调用这些工具。

1、按需使用:这是简单的方式,直接在构建ChatClient请求时通过tools()方法附加工具。这种方式适合只在特定对话中使用某些工具场景。

String response = ChatClient.create(chatModel).prompt("上海今天天气怎么样?").tools(new WeatherTools())  // 在这次对话中提供天气工具.call().content();

2、全局使用:如果某些工具需要在所有对话中都可用,可以在构建ChatClient时注册默认工具。这样,这些工具将对从同一个ChatClient发起的所有对话可用。

ChatClient chatClient = ChatClient.builder(chatModel).defaultTools(new WeatherTools(), new TimeTools())  // 注册默认工具.build();

3、更底层的使用方式:除了给ChatClient绑定工具外,也可以给更底层的ChatModel绑定工具(毕竟工具调用时AI大模型支持的能力),适合需要更精细控制的场景。

// 先得到工具对象
ToolCallback[] weatherTools = ToolCallbacks.from(new WeatherTools());
// 绑定工具到对话
ChatOptions chatOptions = ToolCallingChatOptions.builder().toolCallbacks(weatherTools).build();
// 构造 Prompt 时指定对话选项
Prompt prompt = new Prompt("上海今天天气怎么样?", chatOptions);
chatModel.call(prompt);

4、动态解析:一般情况下,使用前面3中方式即可。对于更复杂的应用,Spring AI还支持通过ToolCallbackResolver在运行时动态解析工具。这种方式特别适合工具需要根据上下文确定的场景

总结:在使用工具时,Spring AI会自动处理工具调用的全过程:从AI模型决定调用工具=>到执行工具方法=>再到将结果返回给模型=>最后模型基于工具结果生成最终回答。这整个过程对开发者来说是透明的。

工具生态

工具本质就是一种插件。Spring AI Alibaba官方文档中提到了社区插件。

在GitHub社区找到了官方提供的很多工具源码,包含很多有用的工具。如翻译工具,网页搜索工具,爬虫工具,地图工具等。

文件操作

文件操作工具主要提供2大功能:保存文件、读取文件。

public interface FileConstant {/*** 文件保存目录*/String FILE_SAVE_DIR = System.getProperty("user.dir") + "/tmp";
}

编写文件操作工具类,通过注解式定义工具:

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
public class FileOperationToolTest {@Testpublic void testReadFile() {FileOperationTool tool = new FileOperationTool();String fileName = "投注攻略.txt";String result = tool.readFile(fileName);assertNotNull(result);}@Testpublic void testWriteFile() {FileOperationTool tool = new FileOperationTool();String fileName = "投注攻略.txt";String content = "https://www.lottery.gov.cn/ 大乐透官方";String result = tool.writeFile(fileName, content);assertNotNull(result);}
}
联网搜索

联网搜索工具的作用是根据关键词搜索网页列表。

我们可以使用专业的网页搜索API,如Search API来实现从多个网站搜索内容,这类服务通常按量计费。也可以直接使用Google或Bing的搜索API(甚至是通过爬虫和网页解析从某个搜索引擎获取内容)。

1、阅读Search API 的官方文档,重点关注API的请求参数和返回结果。从API返回的结果中,我们只需要提取关键部分

{"organic_results": [...{"position": 1,"title": "国家体育总局体育彩票管理中心的官方网站 - 提供开奖公告...","link": "https://www.lottery.gov.cn/","displayed_link": "lottery.gov.cn/","snippet": "提供开奖公告、游戏规则、公益金使用情况、责任彩票建设等多方面的信息,是获取体彩权威资讯和服务的官方渠道...","snippet_highlighted_words": ["开奖结果","开奖导航","走势"],"thumbnail": "https://t8.baidu.com/it/u=661528516,2886240705&fm=217&app=126&size=f242,150&n=0&f=JPEG&fmt=auto?s=73B489634AD237E3660C19280200A063&sec=1744477200&t=b5d8762a6f5728d5f2fbc6bcf1774b20"},...]
}

2、可以把接口文档喂给AI,让他帮我们生成工具代码,网页搜索工具代码:

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) {Map<String, 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");List<Object> 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();}}
}

3、需要后驱API key来调用网页搜索。

4、在配置文件中添加API key

# searchApi
search-api:api-key: 你的 API Key

5、单元测试,读取配置文件中的密钥来创建网页搜索工具

@SpringBootTest
public class WebSearchToolTest {@Value("${search-api.api-key}")private String searchApiKey;@Testpublic void testSearchWeb() {WebSearchTool tool = new WebSearchTool(searchApiKey);String query = "大乐透必中号码";String result = tool.searchWeb(query);assertNotNull(result);}
}
网页抓取

网页抓取工具的作用是根据网址解析到网页的内容。

1、可以使用jsoup库实现网页内容抓取和解析,添加依赖

<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.19.1</version>
</dependency>

2、编写网页抓取工具类

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

3、编写测试类

@SpringBootTest
public class WebScrapingToolTest {@Testpublic void testScrapeWebPage() {WebScrapingTool tool = new WebScrapingTool();String url = "https://www.lottery.gov.cn/";String result = tool.scrapeWebPage(url);assertNotNull(result);}
}
终端操作

终端操作工具的作用在执行终端命令,比如执行Python命令来运行脚本。

1、可以通过Java的Process API实现终端命令执行,注意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();}
}

2、编写测试类

@SpringBootTest
public class TerminalOperationToolTest {@Testpublic void testExecuteTerminalCommand() {TerminalOperationTool tool = new TerminalOperationTool();String command = "ls -l";String result = tool.executeTerminalCommand(command);assertNotNull(result);}
}
资源下载

资源下载工具的诶作用是通过链接下载文件到本地。

1、使用Hutool的HttpUtil.downloadFile方法实现资源下载。

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

2、编写单元测试

@SpringBootTest
public class ResourceDownloadToolTest {@Testpublic void testDownloadResource() {ResourceDownloadTool tool = new ResourceDownloadTool();String url = "https://www.lottery.gov.cn/logo.png";String fileName = "logo.png";String result = tool.downloadResource(url, fileName);assertNotNull(result);}
}

PDF生成

PDF生成工具的作用是根据文件名和内容生成PDF文档并保存。

可以使用itext 库实现PDF生成。需要注意,itext对中文字体的支持需要额外配置,不同操作系统提供的字体也不同,如果真要做生产级应用,建议自行下载所需字体。也可以使用内置中文字体。

// 使用内置中文字体
PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
document.setFont(font);

1、给项目添加依赖

<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core -->
<dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.1.0</version><type>pom</type>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/font-asian -->
<dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>9.1.0</version><scope>test</scope>
</dependency>

2、编写工具类实现

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

上述为了实现方便,我们直接保存PDF到本地文件系统。此外,还可以将生成的文件双穿到对象存储服务,然后返回可访问的URL给AI去输出,或者将本地文件临时返回给前端,让用户直接访问。

3、编写单元测试

@SpringBootTest
public class PDFGenerationToolTest {@Testpublic void testGeneratePDF() {PDFGenerationTool tool = new PDFGenerationTool();String fileName = "大乐透选号攻略.pdf";String content = "中国福利彩票官网 https://www.lottery.gov.cn/";String result = tool.generatePDF(fileName, content);assertNotNull(result);}
}
集中注册

开发了很多工具类,可以结合自己需求一次性给AI提供所有工具,让其自己决定何时调用。

所有可以创建工具注册类,方便统一管理和绑定所有工具。

@Configuration
public class ToolRegistration {@Value("${search-api.api-key}")private String searchApiKey;@Beanpublic 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);}
}

如上代码,包含了4种设计模式:

1、工厂模式:allTools()方法作为一个工厂方法,负责创建和配置多个工具实例,然后将他们包装成统一的数组返回。这符合工厂模式的核心思想-集中创建对象并隐藏创建细节。

2、依赖注入模式:通过@Value注解注入配置值,以及创建好的工具通过Spring容器注入到需要他们的组件中。

3、注册模式:该类作为一个中央注册点,集中管理和注册所有可用的工具,使他们能够被系统其他部分统一访问。

4、适配器模式的应用:ToolCallbacks.from方法可以看作是一种适配器,他将各种不同的工具类转换为统一的ToolCallback数组,是系统能够一致的方式处理他们。

有了该注册类,如果需要添加或者移除工具,只需要修改一个类即可,更利于维护。

使用工具

在App类中添加工具调用代码,通过tools方法绑定所有已注册的工具

@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;
}
测试使用工具

编写测试类,通过特定提示词精准处罚工具调用(由于AI调用的随机性,也可能失败)

@Test
void doChatWithTools() {// 测试联网搜索问题的答案testMessage("周末想去上海找女朋友,推荐几个适合情侣的小众打卡地?");// 测试网页抓取:大乐透中奖案例分析testMessage("最近买了几注彩票中了5元,我想中五百万。");// 测试资源下载:图片下载testMessage("下载一张周杰伦演唱会图片为文件");// 测试 PDF 生成testMessage("生成一份‘彩票选号攻略’PDF");
}private void testMessage(String message) {String chatId = UUID.randomUUID().toString();String answer = loveApp.doChatWithTools(message, chatId);Assertions.assertNotNull(answer);
}
http://www.dtcms.com/a/330285.html

相关文章:

  • AI智能体记忆策略
  • 10 ABP 模块系统
  • [转]SURREAL数据集国内下载链接
  • Deep Agents:用于复杂任务自动化的 AI 代理框架
  • nm命令和nm -D命令参数
  • 19. 重载的方法能否根据返回值类型进行区分
  • Java之String类
  • 3.Cursor提效应用场景实战
  • UEdior富文本编辑器接入AI
  • 算法篇----分治(归并排序)
  • 云电竞盒子对游戏性能有影响吗?
  • 手游业务怎么做防护
  • 智慧城市数字孪生:城市管理的“平行宇宙”
  • 补环境基础(四) Hook插件
  • 黎阳之光立体物业透明管理:开启智慧物业新时代
  • 设计原则之【抽象层次一致性(SLAP)】,方法也分三六九等
  • 安装Win10怎样跳过欢迎界面
  • ant-design a-from-model的校验
  • poetry
  • 《深入解析C++中的Map容器:键值对存储的终极指南》
  • 基于51单片机zigbee的病房呼叫系统
  • Datawhale AI夏令营 「2025全球AI攻防挑战赛-赛道一:图片全要素交互认证-生成赛」的赛事项目实践
  • springboot接口请求参数校验
  • 双椒派E2000D系统盘制作全攻略
  • 在腾讯云CodeBuddy上实现一个AI聊天助手
  • 实盘回测一体的期货策略开发:tqsdk获取历史数据并回测,附python代码
  • java循环分页查询数据,任何把查询到的数据,分批处理,多线程提交到数据库清洗数据
  • 第十二节:粒子系统:海量点渲染
  • 远程办公,如何轻松访问公司内网?出差在外也能远程控制局域网内电脑、外网直接连接到指定端口应用
  • 基于通用优化软件GAMS的数学建模和优化分析(GAMS安装和介绍、GAMS程序编写、GAMS程序调试)