Spring AI实战:仿企业级数据库运维机器人(Function Calling + 流式响应 + 内存级对话记忆)
📝 前言
Function Calling(函数调用)技术让AI不再局限于简单问答,而是能够像人类一样"使用工具"完成实际任务。
本文将带你使用 Spring AI 开发一个数据库运维机器人,它能:
✅ 理解自然语言(如"数据库CPU占用率太高了")✅ 自动查询慢SQL Top 10
✅ 根据指令终止慢SQL进程
✅ 具备对话记忆,支持流式响应
技术栈:Spring Boot 3.x + Spring AI 1.1.0 + 通义千问
🎯 一、项目背景与需求分析
传统运维痛点:
- 凌晨收到告警"数据库CPU 95%"
- 手动登录服务器查询慢SQL
- 逐个分析、手动kill
AI解决方案:
- 告诉AI"帮我查一下慢SQL"
- AI自动查询并展示结果
- 说"kill掉id为3和6的慢SQL"
- AI执行后自动展示最新状态
🏗️ 二、项目结构
function-call/
├── DbOpsController.java # REST控制器
├── DbOpsTools.java # AI工具类(Function Calling)
├── DbOpsService.java # 业务逻辑层
├── DbOpsTicket.java # 工单数据模型
├── DbChatStatus.java # 工单状态枚举
└── db_ops_system_prompt.pt # 系统提示词
💻 三、环境准备
3.1 Maven依赖
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI OpenAI兼容模型(支持通义千问) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.1.0</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
💡 关键点:spring-ai-starter-model-openai 虽然是OpenAI的名字,但通过配置 base-url 可以对接任何兼容OpenAI API的模型(如通义千问、智谱AI等)。
3.2 配置文件(application.yml)
server:
port: 8013
spring:
application:
name: db-ops-bot
ai:
openai:
api-key: your-dashscope-api-key # 替换为你的通义千问API Key
base-url: https://dashscope.aliyuncs.com/compatible-mode/
chat:
options:
model: qwen-plus # 使用通义千问Plus模型
temperature: 0.7 # 创造性程度(0-1)
maxTokens: 5000 # 最大响应长度
🔑 如何获取API Key:
- 访问阿里云百炼平台:https://bailian.console.aliyun.com/
- 注册并开通通义千问服务
- 在API-KEY管理页面创建密钥
3.3 数据模型
📦 DbChatStatus.java(工单状态枚举)
package cn.eavon.llm.mentor.model;
public enum DbChatStatus {
TICKET_CREATED, // 工单已创建
INVESTIGATING, // 正在处理
RESOLVED, // 已解决
TICKET_ERROR // 处理出错
}
💡 为什么用枚举?
- 限定状态只能是预定义的几种,防止非法状态值
- 代码可读性强(TICKET_CREATED 比字符串 "created" 更清晰)
- IDE可以提供自动补全
📦 DbOpsTicket.java(工单记录)
package cn.eavon.llm.mentor.model;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
public record DbOpsTicket(
@JsonPropertyDescription("工单ID") String ticketId,
@JsonPropertyDescription("用户ID") String userId,
@JsonPropertyDescription("数据库服务器IP") String serverIp,
@JsonPropertyDescription("工单状态") DbChatStatus status,
@JsonPropertyDescription("问题描述") String issueDescription
) {
}
🔥 核心技术点:
1. record 关键字(Java 14+)
- 专门用于存储不可变数据的类
- 自动生成构造函数、getter、equals、hashCode、toString
- 等价于传统类但代码量减少70%
2. @JsonPropertyDescription
- 告诉JSON序列化器字段的含义
- 在Spring AI中,这个注解会被AI模型读取,帮助AI理解返回数据的结构
🔧 DbOpsService.java(业务逻辑层)
package cn.eavon.llm.mentor.service;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
@Service
public class DbOpsService {
private final Random random = new Random();
/**
* 查询Top 10慢SQL(模拟实现)
*/
public String getTop10SlowSql(String serverIp) {
StringBuilder result = new StringBuilder();
result.append("=== 数据库服务器 ").append(serverIp).append(" 慢SQL Top 10 ===\n");
result.append("查询时间: ").append(
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
).append("\n");
result.append("=".repeat(80)).append("\n\n");
// 模拟常见的慢SQL语句
String[] sampleSqls = {
"SELECT * FROM orders WHERE status='pending' ORDER BY create_time DESC",
"SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id=o.user_id WHERE u.age > 30",
"UPDATE inventory SET stock=stock-1 WHERE product_id IN (SELECT id FROM products WHERE category='electronics')",
// ... 更多SQL
};
for (int i = 1; i <= 10; i++) {
int sqlIndex = (i - 1) % sampleSqls.length;
long execTime = random.nextInt(5000) + 1000; // 随机执行时间 1000-6000ms
int sqlId = random.nextInt(100) + 1;
result.append("【排名 ").append(i).append("】\n");
result.append(" SQL ID: ").append(sqlId).append("\n");
result.append(" 执行时间: ").append(execTime).append(" ms\n");
result.append(" 执行次数: ").append(random.nextInt(100) + 1).append(" 次\n");
result.append(" 平均锁等待: ").append(random.nextInt(500)).append(" ms\n");
result.append(" SQL语句: ").append(sampleSqls[sqlIndex]).append("\n");
result.append("-".repeat(80)).append("\n\n");
}
result.append("\n💡 建议操作:\n");
result.append(" 1. 对于执行时间超过3000ms的SQL,建议立即终止\n");
result.append(" 2. 使用工具 kill_slow_sql 终止指定的慢SQL进程\n");
result.append(" 3. 建议为高频查询添加索引优化\n");
return result.toString();
}
/**
* 终止指定的SQL进程(模拟实现)
*/
public String killSqlProcess(String serverIp, String sqlId) {
System.out.println("正在终止服务器 " + serverIp + " 上的SQL进程 [ID: " + sqlId + "]");
boolean success = random.nextBoolean(); // 随机成功/失败
if (success) {
String message = "✅ 成功终止SQL进程 [ID: " + sqlId + "] 在服务器 " + serverIp + " 上\n" +
" 释放资源: CPU " + (random.nextInt(20) + 5) + "%, 内存 " + (random.nextInt(500) + 100) + "MB\n" +
" 操作时间: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return message;
} else {
throw new RuntimeException("❌ 终止SQL进程失败 [ID: " + sqlId + "]\n" +
" 错误原因: 进程可能已完成或不存在");
}
}
}
💡 设计思路:
为什么返回String而不是对象?
- AI大模型更擅长处理结构化的文本
- 格式化的文本可以直接展示给用户
- 减少AI解析JSON的复杂度
🛠️ DbOpsTools.java 工具层(AI的"工具箱")
package cn.eavon.llm.mentor.function;
import cn.eavon.llm.mentor.service.DbOpsService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DbOpsTools {
@Autowired
private DbOpsService dbOpsService;
@Tool(
name = "query_slow_sql",
description = "查询当前数据库服务器上执行时间最长的慢SQL,返回排名前10的慢查询语句及其详细信息。" +
"调用场景:1)用户反馈数据库服务器CPU占用率高;2)用户主动要求查看慢SQL;" +
"3)执行kill操作后需要展示最新状态。"
)
public String querySlowSql(
@ToolParam(description = "数据库服务器IP地址,从对话上下文中获取") String serverIp
) {
System.out.println("查询慢SQL - 服务器IP: " + serverIp);
String result = dbOpsService.getTop10SlowSql(serverIp);
System.out.println("慢SQL查询结果:\n" + result);
return result;
}
@Tool(
name = "kill_slow_sql",
description = "终止指定的慢SQL进程。调用前必须明确告知用户即将执行的操作。" +
"调用条件:1)用户已明确提供要kill的SQL ID列表;2)用户已确认执行kill操作。" +
"执行完成后应再次调用query_slow_sql展示最新状态。"
)
public String killSlowSql(
@ToolParam(description = "要终止的SQL进程ID列表,多个ID用逗号分隔,例如:3,6") String sqlIds,
@ToolParam(description = "数据库服务器IP地址,从对话上下文中获取") String serverIp
) {
System.out.println("执行kill操作 - 服务器IP: " + serverIp + ", SQL IDs: " + sqlIds);
String[] ids = sqlIds.split(",");
StringBuilder result = new StringBuilder();
for (String id : ids) {
String trimmedId = id.trim();
try {
String killResult = dbOpsService.killSqlProcess(serverIp, trimmedId);
result.append(killResult).append("\n");
} catch (Exception e) {
result.append("终止SQL进程失败 [ID: ").append(trimmedId).append("]: ")
.append(e.getMessage()).append("\n");
}
}
String finalResult = result.toString();
System.out.println("Kill操作结果:\n" + finalResult);
return finalResult;
}
}
🔥 核心注解详解:
💡 Function Calling 工作原理:
用户:"数据库CPU占用率太高了" ↓ AI分析用户意图,发现需要查询慢SQL ↓ AI决定调用 query_slow_sql 工具 ↓ Spring AI框架自动提取参数(serverIp) ↓ 执行 DbOpsTools.querySlowSql("192.168.1.100") ↓ 将返回结果传递给AI ↓ AI根据结果生成自然语言回复给用户🎯 关键理解:AI不是直接执行代码,而是:
- 分析用户输入
- 判断是否需要调用工具
- 决定调用哪个工具、传什么参数
- Spring AI框架执行工具
- 将执行结果返回给AI
- AI根据结果生成回复
🌐 DbOpsController.java 控制器层(REST接口)
package cn.eavon.llm.mentor.function;
import cn.eavon.llm.mentor.model.DbChatStatus;
import cn.eavon.llm.mentor.model.DbOpsTicket;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.UUID;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
@RestController
@RequestMapping("/db/ops")
@Slf4j
public class DbOpsController {
@Autowired
private OpenAiChatModel chatModel;
private ChatClient chatClient;
@Autowired
private DbOpsTools dbOpsTools;
@Value("classpath:prompts/db_ops_system_prompt.pt")
private Resource systemText;
/**
* 创建新工单
*/
@GetMapping("/newTicket")
public DbOpsTicket newTicket(String userId, String serverIp,
HttpServletResponse httpServletResponse) {
httpServletResponse.setCharacterEncoding("UTF-8");
String ticketId = UUID.randomUUID().toString();
log.info("newTicket userId:{}, serverIp:{}, ticketId:{}",
userId, serverIp, ticketId);
return chatClient
.prompt()
.user(String.format(
"我需要运维支持,我的用户id是%s,数据库服务器IP是: %s," +
"工单ID是: %s,当前状态是 %s",
userId, serverIp, ticketId, DbChatStatus.TICKET_CREATED.name()
))
.advisors(spec -> spec.param(CONVERSATION_ID, ticketId)
.param("chat_memory_retrieve_size", 100))
.call()
.entity(DbOpsTicket.class);
}
/**
* 用户提问(支持流式响应)
*/
@GetMapping("/ask")
public Flux<String> ask(String question, String ticketId,
HttpServletResponse httpServletResponse) {
httpServletResponse.setCharacterEncoding("UTF-8");
return chatClient
.prompt()
.user(question)
.tools(dbOpsTools)
.advisors(spec -> spec.param(CONVERSATION_ID, ticketId)
.param("chat_memory_retrieve_size", 100))
.stream()
.content();
}
/**
* 初始化ChatClient(应用启动时执行一次)
*/
@PostConstruct
public void init() {
// 创建对话记忆(最多保留10条消息)
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
// 构建聊天客户端
chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.defaultSystem(systemText)
.build();
}
}
关键技术点:
- @PostConstruct:应用启动时执行一次,初始化ChatClient
- ChatMemory(对话记忆):让AI记住之前的对话,不用重复提供服务器IP
- .tools(dbOpsTools):启用Function Calling,让AI可以调用工具
- CONVERSATION_ID:区分不同工单的对话历史
🐛db_ops_system_prompt.pt(系统提示词)
# Role
你是一名资深的数据库运维专家,负责快速诊断和解决数据库性能问题。
# Task
第一步:问题诊断
- 识别用户描述的性能问题关键词(CPU高、慢查询等)
- 从上下文获取服务器IP
第二步:执行诊断
- 调用 query_slow_sql 工具查询Top 10慢SQL
- 分析结果并给出建议
第三步:执行优化
- 用户要求kill时,先确认再执行
- 执行后再次查询展示最新状态
# Limit
- 仅处理数据库性能问题
- kill操作前必须获得用户确认
- 使用专业但通俗的语言
💡 提示词工程(Prompt Engineering)要点:
1. 角色定义(Role)
· 明确AI的身份和职责
· 影响AI的语言风格和专业程度
2. 任务流程(Task)
· 分步骤指导AI如何处理对话
· 类似"业务流程图"
3. 限制条件(Limit)
· 防止AI越权操作
· 定义边界和安全规则
4. 标准话术
· 提供模板,保证回复的专业性
· 避免AI自由发挥导致不规范的回复
🚀 四、运行与测试
localhost:8013/db/ops/newTicket?userId=admin001&serverIp=192.168.1.100
localhost:8013/db/ops/ask?ticketId=0138a92c-7861-4dbc-ab63-2b4751a796a8&question=确认
🎯 五、核心技术总结
技术 作用 类比 ChatClient 与AI交互的高级API JdbcTemplate Function Calling 让AI调用Java方法 AI的"工具箱" ChatMemory 对话记忆 记事本 流式响应 逐字输出 ChatGPT打字机效果 System Prompt 定义AI行为规范 员工手册
Spring AI的价值:让Java开发者无需深入学习AI,就能构建智能应用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐






所有评论(0)