环境:SpringBoot3.4.0
1. 简介
大型语言模型(LLMs)在接收到充分的上下文信息及明确的问题时,能够高效地生成源代码。类似地,当它们通过DDL(数据定义语言)语句获取到表格与列的信息,并收到关于生成SQL语句输出格式的清晰指示时,LLMs同样擅长构建SQL查询。
接下来,我们将通过Spring AI与LLM交互的实例来生成SQL语并执行该SQL语句。下面是具体的流程:
- 用户创建一个包含对LLM的指令、DDL语句以及用户问题的提示。
- LLM根据这些信息生成SQL查询。
- 应用程序将SQL查询执行到数据库中。
- 在这个演示中,只允许执行'SELECT'查询。
- 查询结果会返回给API用户。对于任何创建、插入或更新查询的请求,系统都会返回为不支持的操作。
下面是我们要最终完成的效果:
最终响应的结果会根据我们在后台配置的提示词模板而生成的。
2. 实战案例
2.1 依赖管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M6.1</version>
</dependency>
这里我们使用阿里的大模型平台。引入jdbc依赖是为了在生成SQL语句后执行该SQL语句。
配置文件
spring:
ai:
dashscope:
# 申请的key
api-key: sk-xxxooo
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
chat:
options:
model: qwen-turbo
以上环境就配置好了,接下来我们还需要准备DDL脚本和数据脚本。
2.2 SQL脚本
DDL脚本
create table T_USER (
id int not null auto_increment,
username varchar(255) not null,
email varchar(255) not null,
password varchar(255) not null,
primary key (id)
);
create table T_ACCOUNT (
id int not null auto_increment,
accountNumber varchar(255) not null,
user_id int not null,
balance decimal(10, 2) not null,
openDate date not null,
primary key (id),
foreign key (user_id) references T_USER(id)
);
数据脚本
INSERT INTO T_USER (username, email, password)
VALUES
('pack1', 'pack1@qq.com', '123123-1'),
('pack2', 'pack2@qq.com', '123123-2'),
('pack3', 'pack3@qq.com', '123123-3'),
('pack4', 'pack4@qq.com', '123123-4'),
('pack5', 'pack5@qq.com', '123123-5'),
('pack6', 'pack6@qq.com', '123123-6'),
('pack7', 'pack7@qq.com', '123123-7'),
('pack8', 'pack8@qq.com', '123123-8'),
('pack9', 'pack9@qq.com', '123123-9'),
('pack10', 'pack10@qq.com', '123123-10');
INSERT INTO T_ACCOUNT (accountNumber, user_id, balance, openDate)
VALUES
('ACC001', 1, 1000.00, '2022-03-19'),
('ACC002', 1, 500.00, '2024-05-17'),
('ACC003', 2, 1500.00, '2020-02-29'),
('ACC004', 2, 200.00, '2021-07-10'),
('ACC005', 3, 800.00, '2025-05-09'),
('ACC006', 4, 3000.00, '2022-07-09'),
('ACC007', 4, 100.00, '2021-02-10'),
('ACC015', 10, 2000.00, '2023-10-09');
接下来,我们需要进行如下的配置,当服务启动后自动执行上面的2个脚本文件。
spring:
sql:
init:
schema-locations: classpath:script/schema.sql
data-locations: classpath:script/data.sql
platform: mysql
mode: always
如上配置后当我们的应用启动后,将自动的执行这2个脚本。
2.3 提示词模板定义
切记,编写一个详细且清晰的提示对于生成能够被应用程序直接执行的正确SQL语句非常重要。以下提示要求生成一个SQL SELECT语句,该语句能够从数据库中检索出所需数据。其他create/insert、update、delete操作不支持执行。你可以选择允许这些语句,但不执行它们,仅将生成的查询作为API输出返回。
模板文件sql-prompt-template.tml:
根据提供的DDL语句,编写一个SQL查询来回答QUESTION部分提出的问题。
只生成SELECT查询。不要在回答的开头或结尾添加任何文本或标记。移除诸如```、sql、\n等标记。
只要问题不是查询类的(如:INSERT、UPDATE或DELETE操作),
直接回复不支持该操作。如果无法回答问题,请说明DDL不支持回答该问题。
QUESTION
{question}
DDL
{ddl}
我们将会以上面的格式将数据提交到大模型。
2.4 编写Controller接口
该API使用ChatClient API将输入提示发送给大型语言模型(LLM),并接收生成的SQL查询。如果生成了有效的查询,它将使用JdbcTemplate来执行该查询并返回响应。
@RestController
public class SqlController {
// 加载DDL脚本
@Value("classpath:script/schema.sql")
private Resource ddlResource;
// 加载提示词模板
@Value("classpath:/sql-prompt-template.tml")
private Resource sqlPromptTemplateResource;
private final ChatClient aiClient;
private final JdbcTemplate jdbcTemplate;
public SqlController(ChatClient.Builder aiClientBuilder, JdbcTemplate jdbcTemplate) {
this.aiClient = aiClientBuilder.build();
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping(path = "/sql")
public AiResponse sql(@RequestBody AiRequest request) throws IOException {
String schema = ddlResource.getContentAsString(Charset.defaultCharset());
String query = aiClient.prompt()
.advisors(new SimpleLoggerAdvisor())
.user(
userSpec -> userSpec
.text(sqlPromptTemplateResource)
.param("question", request.text())
.param("ddl", schema)
)
.call().content() ;
if (query.toLowerCase().startsWith("select")) {
return new AiResponse(query, jdbcTemplate.queryForList(query));
}
throw new AiException(query);
}
public static record AiRequest(String text) {
}
public static record AiResponse(String sqlQuery, List<Map<String, Object>> results) {
}
}
上面代码中我们添加了SimpleLoggerAdvisor切面来记录日志,所以我们需要进入如下的日志配置:
logging:
level:
'[org.springframework.ai.chat.client.advisor]': debug
2.5 测试
首先,我们进行两表关联查询,输入如下内容:
SQL正确的生成,并执行了该sql将数据返回。控制台输出日志
接下来,我们输入非select的操作
控制台日志输出
完全按照我们的提示词进行输出。