如何去拓展一个离线的大模型的能力,这里会实践下使用SpringAI的Tool Calling的功能,可以让大模型在回答时候调用我们自己的方法,得到返回值后再回答。
一、编写回调工具类
我们都知道离线模型的知识截止到训练结束,无法回答现在是日期是多少,所以这里我们在工具类中编写一个获取本地时间的方法让模型来调用
java
/**
* @author LiuYu
* @since 2025-03-27 11:44
*/@Service
public class CallingTools {
/**
* 获取用户时区内的当前日期和时间
* 此方法使用LocaleContextHolder来获取用户的时区设置,并返回当前日期和时间的字符串表示
* 主要用于需要根据用户所在时区显示当前时间的场景
*
* @return 用户时区内的当前日期和时间的字符串表示
*/
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
// 获取当前日期和时间,并转换到用户所在时区
String string = LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
System.out.println("getCurrentDateTime: " + string);
return string;
}
}
使用SpringAI提供的@Tool
注解来标注这是模型调用的工具方法,官方文档在Tools工具调用,其中description
属性是这个工具的描述,模型可以通过它来了解何时、如何调用它。
二、编写调用逻辑
在控制器中增加一个嗲用接口,把工具类传入到模型中
java
package com.renne.ai.learn.calling.controller;
import com.renne.ai.learn.calling.tools.CallingTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author LiuYu
* @since 2025-03-27 11:46
*/@RestController
@RequestMapping("/openai/calling")
public class ToolsCallingController {
private final ChatModel chatModel;
public ToolsCallingController(ChatModel chatModel) {
this.chatModel = chatModel;
// 构造时,可以设置 ChatClient 的参数
// {@link org.springframework.ai.chat.client.ChatClient};
this.openAiChatClient = ChatClient.builder(chatModel)
// 实现 Chat Memory 的 Advisor // 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
// 实现 Logger 的 Advisor .defaultAdvisors(
new SimpleLoggerAdvisor()
)
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
OpenAiChatOptions.builder()
.topP(0.7)
.build()
)
.build();
}
@GetMapping("/search-tool")
public String get(@RequestParam(value = "prompt", required = false) String prompt) {
return ChatClient.create(chatModel)
.prompt(prompt)
.tools(new CallingTools())
.call()
.content();
}
}
当我们向模型提问"现在几点了",模型会先调用我们的方法,得到结果后再回答,模型就可以准确回答了
可以看到我们工具类中打印的输出内容,确实被调用了,我们换一个方式来提问,"今天是几号",只输出了日期,模型会根据方法返回的结果,再结合问题内容进行回答。
如果这是我们问别的内容,例如"介绍一下自己",控制台没有输出,没有调用方法。
三、扩展回调工具类的方法
当模型可以通过Tool Calling来调用我们提供的资源时,我们提供什么样的资源将决定模型能拓展哪方面的能力。 如果我们开放我们数据库的资源给模型,是否模型就具有了访问数据库的能力,帮我我们查表、分析数据,来实践一下。
首先在工程中引入访问数据库的依赖,这里为了方便使用SQLite数据库
xml
<properties>
<!-- Spring Boot -->
<spring-boot.version>3.4.0</spring-boot.version>
<!-- sqlite -->
<sqlite-jdbc.version>3.36.0.3</sqlite-jdbc.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite-jdbc.version}</version>
</dependency>
在Spring Boot的配置文件中,添加连接数据库的配置信息
yml
spring:
datasource:
url: jdbc:sqlite:D:/Projects/IdeaProjects/spring-ai-learn/chat-client/src/main/resources/db/data.db # 这里修改成自己的数据库配置
driver-class-name: org.sqlite.JDBC
username:
password:
hikari:
maximum-pool-size: 1
minimum-idle: 1
扩展回调工具类中的方法,增加一个查询SQLite数据库的表结构信息的方法
java
/**
* 查询SQLite数据库的表结构信息
* 此方法使用JdbcTemplate查询SQLite数据库的表结构,并返回表结构的详细信息
*
* @param tableName 要查询的表名
* @return 表结构的详细信息
*/
@Tool(description = "查询SQLite数据库的表结构信息")
String getTableStructure(String tableName) {
System.out.println("getTableStructure: " + tableName);
String sql = "select * from sqlite_master where type='table'";
if (tableName != null && !tableName.isEmpty()) {
sql += " and name='" + tableName + "';";
}
sql += ";";
List<Map<String, Object>> tableInfo = SpringUtil.getBean(JdbcTemplate.class).queryForList(sql);
StringBuilder result = new StringBuilder();
for (Map<String, Object> map : tableInfo) {
result.append("表名: ").append(map.get("name")).append("\n");
result.append("表类型: ").append(map.get("type")).append("\n");
result.append("表结构: ").append(map.get("sql")).append("\n");
result.append("--------------------------------------------------\n");
}
System.out.println("getTableStructure: " + result);
return result.toString();
}
在向模型提问我数据库中有哪些表?
,模型调用方法回答
是否扩展一个执行SQL的方法给模型,模型就可以直接帮我们执行SQL语句了
java
/**
* 执行给定的SQL语句并返回字符串结果
* 此方法根据传入的SQL语句执行查询,并返回查询结果的字符串表示
*
* @param sql 要执行的SQL语句
* @return 查询结果的字符串表示
*/
@Tool(description = "执行给定的SQL语句并返回字符串结果")
String executeSql(String sql) {
System.out.println("执行给定的SQL语句: sql=" + sql);
List<Map<String, Object>> queryResult = SpringUtil.getBean(JdbcTemplate.class).queryForList(sql);
StringBuilder result = new StringBuilder();
if (!queryResult.isEmpty()) {
// 获取字段名
List<String> fieldNames = new ArrayList<>(queryResult.get(0).keySet());
// 添加字段名到结果中
for (String fieldName : fieldNames) {
result.append(fieldName).append(",");
}
result.append("\n");
// 添加查询结果到结果中
for (Map<String, Object> map : queryResult) {
for (String fieldName : fieldNames) {
result.append(map.get(fieldName)).append(",");
}
result.append("\n");
}
} else {
result.append("No results found.");
}
System.out.println("executeSql: " + result);
return result.toString();
}
向模型提问我用户表sys_user中有几条数据
,可以看到模型执行查询SELECT COUNT(*) FROM sys_user
语句,得到返回结果进行回答
在更换问题我的数据库部门表sys_dept中有哪些部门,只需要给我名字即可
,也可以成功返回数据,而且模型先调用了查询sys_dept
表接口的方法,得到表结构,在生成SQL 执行给定的SQL语句: sql=SELECT dept_name FROM sys_dept
,执行语句得到返回结果
在实践的过程中,存在模型生成的SQL语句不正确的情况,生成SQL的正确率也取决于使用的模型、提示词是否正确、工具提供的表述是否合理、工具方法的参数是否合理。 当我们给模型提供很大权利,可以让他帮我们处理很多事情,但是模型依然有出错的可能,如何去规避这些出错,合理利用模型的能力值得思考。