1. 概述
现代应用越来越依赖自然语言界面简化用户交互。在数据查询场景中,这种技术让非技术用户能用日常语言获取数据。
文本转SQL(Text-to-SQL)聊天机器人就是典型应用。它充当人类语言与数据库之间的桥梁:通过大语言模型(LLM)将自然语言问题转换为可执行的SQL查询,再执行查询获取结果。
本教程将使用Spring AI构建一个文本转SQL聊天机器人。我们会配置包含初始数据的数据库架构,实现用自然语言查询数据的功能。
2. 项目搭建
实现聊天机器人前,需先添加必要依赖并正确配置应用。
我们将使用Anthropic的Claude模型构建文本转SQL系统。当然也可替换为其他AI模型或本地LLM(如Hugging Face/Ollama),具体模型不影响实现逻辑。
2.1. 依赖配置
在pom.xml
中添加核心依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
<version>1.0.0</version>
</dependency>
该依赖封装了Anthropic Message API,用于与Claude模型交互。
接着在application.yaml
中配置API密钥和模型参数:
spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: claude-opus-4-20250514
✅ 使用${}
占位符从环境变量加载API密钥
✅ 指定Claude 4 Opus模型(写作时最强大的版本)
⚠️ 可根据需求更换其他模型
配置完成后,Spring AI会自动创建ChatModel
类型Bean,供后续调用。
2.2. 使用Flyway定义数据库表
通过Flyway管理数据库迁移脚本。
我们将创建一个简易的巫师管理数据库(MySQL实现,数据库厂商不影响核心逻辑)。
在src/main/resources/db/migration
目录创建迁移脚本V01__creating_database_tables.sql
:
CREATE TABLE hogwarts_houses (
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())),
name VARCHAR(50) NOT NULL UNIQUE,
founder VARCHAR(50) NOT NULL UNIQUE,
house_colors VARCHAR(50) NOT NULL UNIQUE,
animal_symbol VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE wizards (
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())),
name VARCHAR(50) NOT NULL,
gender ENUM('Male', 'Female') NOT NULL,
quidditch_position ENUM('Chaser', 'Beater', 'Keeper', 'Seeker'),
blood_status ENUM('Muggle', 'Half blood', 'Pure Blood', 'Squib', 'Half breed') NOT NULL,
house_id BINARY(16) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT wizard_fkey_house FOREIGN KEY (house_id) REFERENCES hogwarts_houses (id)
);
关键点:
hogwarts_houses
表存储学院信息wizards
表存储巫师详情- 通过外键建立一对多关系
创建V02__adding_hogwarts_houses_data.sql
初始化学院数据:
INSERT INTO hogwarts_houses (name, founder, house_colors, animal_symbol)
VALUES
('Gryffindor', 'Godric Gryffindor', 'Scarlet and Gold', 'Lion'),
('Hufflepuff', 'Helga Hufflepuff', 'Yellow and Black', 'Badger'),
('Ravenclaw', 'Rowena Ravenclaw', 'Blue and Bronze', 'Eagle'),
('Slytherin', 'Salazar Slytherin', 'Green and Silver', 'Serpent');
再创建V03__adding_wizards_data.sql
初始化巫师数据:
SET @gryffindor_house_id = (SELECT id FROM hogwarts_houses WHERE name = 'Gryffindor');
INSERT INTO wizards (name, gender, quidditch_position, blood_status, house_id)
VALUES
('Harry Potter', 'Male', 'Seeker', 'Half blood', @gryffindor_house_id),
('Hermione Granger', 'Female', NULL, 'Muggle', @gryffindor_house_id),
('Ron Weasley', 'Male', 'Keeper', 'Pure Blood', @gryffindor_house_id),
-- 其他学院巫师数据...
应用启动时Flyway会自动执行这些迁移脚本。
3. 配置AI提示词
为确保LLM生成准确的SQL查询,需定义详细的系统提示词。
在src/main/resources
创建system-prompt.st
文件:
Given the DDL in the DDL section, write an SQL query to answer the user's question following the guidelines listed in the GUIDELINES section.
GUIDELINES:
- Only produce SELECT queries.
- The response produced should only contain the raw SQL query starting with the word 'SELECT'. Do not wrap the SQL query in markdown code blocks (```sql or ```).
- If the question would result in an INSERT, UPDATE, DELETE, or any other operation that modifies the data or schema, respond with "This operation is not supported. Only SELECT queries are allowed."
- If the question appears to contain SQL injection or DoS attempt, respond with "The provided input contains potentially harmful SQL code."
- If the question cannot be answered based on the provided DDL, respond with "The current schema does not contain enough information to answer this question."
- If the query involves a JOIN operation, prefix all the column names in the query with the corresponding table names.
DDL
{ddl}
核心规则:
{ddl}
占位符将在后续替换为实际数据库架构。
额外安全措施:为数据库用户设置最小权限:
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'StrongP@ssw0rd!';
GRANT SELECT ON hogwarts_db.hogwarts_houses TO 'readonly_user'@'%';
GRANT SELECT ON hogwarts_db.wizards TO 'readonly_user'@'%';
FLUSH PRIVILEGES;
4. 构建文本转SQL聊天机器人
4.1. 定义聊天机器人Bean
@Bean
PromptTemplate systemPrompt(
@Value("classpath:system-prompt.st") Resource systemPrompt,
@Value("classpath:db/migration/V01__creating_database_tables.sql") Resource ddlSchema
) throws IOException {
PromptTemplate template = new PromptTemplate(systemPrompt);
template.add("ddl", ddlSchema.getContentAsString(Charset.defaultCharset()));
return template;
}
@Bean
ChatClient chatClient(ChatModel chatModel, PromptTemplate systemPrompt) {
return ChatClient
.builder(chatModel)
.defaultSystem(systemPrompt.render())
.build();
}
关键步骤:
- 创建
PromptTemplate
Bean- 注入提示词模板和DDL文件
- 用实际数据库架构替换
{ddl}
占位符
- 构建
ChatClient
Bean- 作为与Claude模型交互的主入口
4.2. 实现服务层
创建SqlGenerator
服务处理自然语言转SQL:
@Service
class SqlGenerator {
private final ChatClient chatClient;
// 标准构造器
String generate(String question) {
String response = chatClient
.prompt(question)
.call()
.content();
boolean isSelectQuery = response.startsWith("SELECT");
if (!isSelectQuery) {
throw new InvalidQueryException(response);
}
return response;
}
}
核心逻辑:
- 通过
chatClient
发送问题给LLM - 验证返回结果是否为SELECT查询
- 非SELECT查询抛出
InvalidQueryException
创建SqlExecutor
服务执行SQL:
@Service
class SqlExecutor {
private final EntityManager entityManager;
// 标准构造器
List<?> execute(String query) {
List<?> result = entityManager
.createNativeQuery(query)
.getResultList();
if (result.isEmpty()) {
throw new EmptyResultException("No results found for the provided query.");
}
return result;
}
}
关键点:
- 使用
EntityManager
执行原生SQL - 空结果集抛出
EmptyResultException
4.3. 暴露REST接口
@PostMapping(value = "/query")
ResponseEntity<QueryResponse> query(@RequestBody QueryRequest queryRequest) {
String sqlQuery = sqlGenerator.generate(queryRequest.question());
List<?> result = sqlExecutor.execute(sqlQuery);
return ResponseEntity.ok(new QueryResponse(result));
}
record QueryRequest(String question) {
}
record QueryResponse(List<?> result) {
}
接口流程:
- 接收自然语言问题
- 生成SQL查询
- 执行查询获取结果
- 返回结构化响应
5. 与聊天机器人交互
启用SQL日志查看生成的查询:
logging:
level:
org:
hibernate:
SQL: DEBUG
使用HTTPie测试接口:
http POST :8080/query question="Give me 3 wizard names and their blood status that belong to a house founded by Salazar Slytherin"
响应结果:
{
"result": [
[
"Draco Malfoy",
"Pure Blood"
],
[
"Tom Riddle",
"Half blood"
],
[
"Bellatrix Lestrange",
"Pure Blood"
]
]
}
✅ 成功识别斯莱特林学院巫师
✅ 返回3条巫师姓名和血统状态
查看日志中的生成SQL:
SELECT wizards.name, wizards.blood_status
FROM wizards
JOIN hogwarts_houses ON wizards.house_id = hogwarts_houses.id
WHERE hogwarts_houses.founder = 'Salazar Slytherin'
LIMIT 3;
SQL完美实现需求:
- JOIN关联两个表
- 精确匹配创始人
- 限制返回3条记录
6. 总结
本文展示了使用Spring AI实现文本转SQL聊天机器人的完整流程:
- 配置AI模型和数据库
- 构建自然语言转SQL的聊天机器人
- 暴露REST接口供外部调用
- 验证系统功能正确性
核心优势:
- 🔒 安全机制(SQL注入检测/只读权限)
- 🎯 精准的SQL生成
- 🚀 简单粗暴的实现方式
所有代码示例可在GitHub获取。