基于 Spring AI 开发智能邮件分类器
-
-
- [🏗️ 1. 项目架构与流程](#🏗️ 1. 项目架构与流程)
- [📦 2. 环境准备与项目初始化](#📦 2. 环境准备与项目初始化)
-
- [**1. 确保环境符合要求**](#1. 确保环境符合要求)
- [**2. 创建Spring Boot项目**](#2. 创建Spring Boot项目)
- [**3. 添加Spring AI依赖**](#3. 添加Spring AI依赖)
- [**4. 配置API Key与连接**](#4. 配置API Key与连接)
- [⚙️ 3. 核心代码实现](#⚙️ 3. 核心代码实现)
-
- [**1. 实体类 (定义请求与响应格式)**](#1. 实体类 (定义请求与响应格式))
- [**2. 服务层 (核心AI交互逻辑)**](#2. 服务层 (核心AI交互逻辑))
- [**3. 控制层 (暴露REST API)**](#3. 控制层 (暴露REST API))
- [🧪 4. 运行与测试](#🧪 4. 运行与测试)
- [🐳 5. 部署上线 (Docker化)](#🐳 5. 部署上线 (Docker化))
-
- [**1. 编写 Dockerfile**](#1. 编写 Dockerfile)
- [**2. 构建镜像并运行**](#2. 构建镜像并运行)
- [**3. 验证部署**](#3. 验证部署)
- [💡 深入与进阶方向(体现你的ML优势)](#💡 深入与进阶方向(体现你的ML优势))
-
将详细介绍如何使用 Spring AI 开发一个完整的智能邮件分类器。这个方案充分利用了Java和Spring Boot经验,并引入了AI能力。
🏗️ 1. 项目架构与流程
下图清晰地展示了本Demo的核心架构和数据流转,它遵循经典的三层架构模式:
Spring Boot 应用
返回分类结果
处理响应
返回JSON
HTTP Client
Postman/浏览器
HTTP请求
含邮件文本
Controller层
EmailController
Service层
EmailClassifyService
Spring AI 抽象层
ChatClient
HTTP 调用
大模型API
如DeepSeek/OpenAI
HTTP响应
分类结果
📦 2. 环境准备与项目初始化
1. 确保环境符合要求
- Java : 版本 17 或以上 (Spring AI 3.x 要求)。
- 项目管理工具: Maven 或 Gradle。
- IDE: 推荐 IntelliJ IDEA。
- 获取API Key : 访问 DeepSeek 或 OpenAI 平台注册并获取API Key。
2. 创建Spring Boot项目
推荐使用 start.spring.io 快速生成项目,需选择:
- Project: Maven
- Language: Java
- Spring Boot: 3.3.x 版本
- Dependencies : 添加 Spring Web 和 Lombok (简化代码)。
3. 添加Spring AI依赖
在生成项目的 pom.xml 中,手动添加Spring AI的依赖。由于Spring AI未在官方Starter中,需指定其仓库。
关键配置如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <!-- 确保使用3.x版本 -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>email-ai-classifier</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>email-ai-classifier</name>
<description>智能邮件分类器Demo - Spring Boot + Spring AI</description>
<properties>
<java.version>17</java.version>
<!-- 指定Spring AI版本 -->
<spring-ai.version>1.0.0-M3</version>
</properties>
<repositories>
<!-- Spring AI 里程碑仓库 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- 中央仓库作为备用 -->
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
</repository>
</repositories>
<dependencies>
<!-- Spring Boot 核心Web starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI OpenAI Starter (兼容DeepSeek等OpenAI API格式的模型) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- 参数验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 开发工具:热部署、Lombok -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- 编译使用Java 17 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4. 配置API Key与连接
在 src/main/resources/application.yml 中配置。务必注意将API Key放在环境变量中,而非直接写在配置文件里,这是重要的安全实践。
yaml
# ========================
# 应用基础配置
# ========================
server:
port: 8080
servlet:
context-path: /
# 针对可能的大模型响应,适当调整超时时间
tomcat:
max-http-form-post-size: 2MB
# 响应超时设置(毫秒)
connection-timeout: 30s
spring:
application:
name: email-ai-classifier
# ========================
# Spring AI 配置 (核心)
# ========================
ai:
openai:
# !!! 安全警告:务必通过环境变量配置API Key !!!
# 在运行环境设置:DEEPSEEK_API_KEY 或 OPENAI_API_KEY
api-key: ${AI_API_KEY:} # 默认值为空,强制从环境变量获取
# 国内模型DeepSeek专用端点(如果使用OpenAI官方服务,则删除此配置)
base-url: https://api.deepseek.com
# 聊天模型通用配置
chat:
options:
# 模型名称:
# - DeepSeek: deepseek-chat 或 deepseek-reasoner
# - OpenAI: gpt-3.5-turbo, gpt-4, gpt-4o-mini等
model: deepseek-chat
# 温度值:控制随机性 (0.0-2.0),分类任务建议较低值
temperature: 0.1
# 最大输出token数
max-tokens: 500
# 开启JSON格式输出(如果模型支持)
# response-format: { type: "json_object" }
# HTTP客户端配置
client:
connect-timeout: 10s # 连接超时
read-timeout: 30s # 读取超时(大模型响应可能较慢)
# ========================
# 日志配置(便于调试)
# ========================
output:
ansi:
enabled: ALWAYS # 启用彩色日志,便于阅读
logging:
level:
root: INFO
com.example: DEBUG # 你的项目包名
org.springframework.ai: DEBUG # 查看Spring AI详细日志
org.springframework.web: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n"
# ========================
# 自定义应用配置
# ========================
app:
classifier:
# 支持的分类类别(与Prompt中保持一致)
categories: ["产品咨询", "售后服务", "商务合作", "投诉反馈", "垃圾推广"]
# 是否启用缓存(后续扩展)
cache-enabled: false
# 最大邮件内容长度(字符)
max-content-length: 5000
# ========================
# 生产环境可选配置(部署时考虑)
# ========================
---
# 多环境配置示例:生产环境配置
# 通过启动参数激活:--spring.profiles.active=prod
spring:
config:
activate:
on-profile: prod
ai:
openai:
chat:
client:
# 生产环境更长的超时设置
read-timeout: 60s
# 生产环境日志级别调高
logging:
level:
root: WARN
com.example: INFO
org.springframework.ai: INFO
在启动应用前,设置环境变量:
- Linux/Mac :
export DEEPSEEK_API_KEY=your_api_key_here - Windows (CMD) :
set DEEPSEEK_API_KEY=your_api_key_here - 在IDEA中 :可通过
Run/Debug Configurations的Environment variables字段添加。
⚙️ 3. 核心代码实现
1. 实体类 (定义请求与响应格式)
创建 EmailClassificationRequest.java 和 EmailClassificationResponse.java。
java
// EmailClassificationRequest.java
import lombok.Data;
@Data
public class EmailClassificationRequest {
private String emailContent;
private String sender; // 可选字段,用于未来扩展
}
java
// EmailClassificationResponse.java
import lombok.Data;
@Data
public class EmailClassificationResponse {
private String originalContent;
private String category; // 如:"咨询"、"投诉"
private String reasoning; // AI做出此分类的简要原因,便于调试
private Long timestamp;
}
2. 服务层 (核心AI交互逻辑)
创建 EmailClassifyService.java。这里是Prompt工程的核心,你机器学习背景的价值在此体现:设计出让AI稳定、准确输出的提示词。
java
// EmailClassifyService.java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import java.time.Instant;
@Service
@Slf4j
public class EmailClassifyService {
private final ChatClient chatClient;
// 通过构造器注入ChatClient
public EmailClassifyService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public EmailClassificationResponse classifyEmail(EmailClassificationRequest request) {
// 1. 构造精确的Prompt - 这是分类准确的关键
String systemPrompt = """
你是一个专业的邮件分类AI助手。请严格按照以下规则对用户提供的邮件内容进行分类:
1. **类别列表**:仅限【产品咨询】、【售后服务】、【商务合作】、【投诉反馈】、【垃圾推广】。
2. **输出格式**:你必须以严格的JSON格式回答,且只包含以下两个字段:
- "category": 字符串,必须是上述五个类别之一。
- "reasoning": 字符串,用一句话中文解释分类依据。
3. **分类逻辑**:
- 询问功能、价格、规格的,归为【产品咨询】。
- 寻求维修、退换货、使用帮助的,归为【售后服务】。
- 提及合作、采购、联盟的,归为【商务合作】。
- 表达不满、要求赔偿、批评服务的,归为【投诉反馈】。
- 广告、推销、明显无关内容的,归为【垃圾推广】。
请确保判断基于邮件主旨,不要自行臆断。
""";
// 2. 调用AI
String aiJsonResponse = chatClient.prompt()
.system(systemPrompt) // 设定系统角色和规则
.user("请分类此邮件内容:" + request.getEmailContent()) // 用户输入
.call() // 执行调用
.content(); // 获取文本响应
log.info("AI原始响应: {}", aiJsonResponse);
// 3. 解析AI的JSON响应 (生产环境建议用Jackson/ObjectMapper)
// 此处为简化示例,实际解析需处理异常
String category = extractFromJson(aiJsonResponse, "category");
String reasoning = extractFromJson(aiJsonResponse, "reasoning");
// 4. 构建返回对象
EmailClassificationResponse response = new EmailClassificationResponse();
response.setOriginalContent(request.getEmailContent());
response.setCategory(category);
response.setReasoning(reasoning);
response.setTimestamp(Instant.now().toEpochMilli());
return response;
}
// 简单的JSON解析工具方法(仅用于Demo,生产环境请使用标准库)
private String extractFromJson(String json, String key) {
// 示例:简单查找 "category": "产品咨询" 这样的模式
String pattern = "\"" + key + "\":\\s*\"([^\"]+)\"";
java.util.regex.Pattern r = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher m = r.matcher(json);
if (m.find()) {
return m.group(1);
}
return "解析失败";
}
}
3. 控制层 (暴露REST API)
创建 EmailClassificationController.java。
java
// EmailClassificationController.java
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/email")
@Slf4j
public class EmailClassificationController {
private final EmailClassifyService classifyService;
public EmailClassificationController(EmailClassifyService classifyService) {
this.classifyService = classifyService;
}
@PostMapping("/classify")
public EmailClassificationResponse classify(@Valid @RequestBody EmailClassificationRequest request) {
log.info("收到分类请求,发件人: {}, 内容长度: {}",
request.getSender(),
request.getEmailContent().length());
return classifyService.classifyEmail(request);
}
// 一个简单的健康检查接口,用于验证服务是否启动
@GetMapping("/health")
public String health() {
return "Email Classification Service is up!";
}
}
🧪 4. 运行与测试
-
启动应用 :运行
Application主类。看到Started Application日志即成功。 -
健康检查 :浏览器访问
http://localhost:8080/api/email/health。 -
功能测试 :使用 Postman 或 cURL 测试分类接口。
bashcurl -X POST http://localhost:8080/api/email/classify \ -H "Content-Type: application/json" \ -d '{ "emailContent": "你好,这款产品的保修期是多久?如果坏了怎么维修?", "sender": "customer@example.com" }'预期响应:
json{ "originalContent": "你好,这款产品的保修期是多久?如果坏了怎么维修?", "category": "产品咨询", "reasoning": "邮件内容涉及产品保修和维修询问,符合产品咨询特征。", "timestamp": 1743225600000 }
🐳 5. 部署上线 (Docker化)
1. 编写 Dockerfile
在项目根目录创建 Dockerfile:
dockerfile
# 使用轻量级JDK运行时镜像
FROM openjdk:17-jdk-slim AS builder
# 将Maven包装器复制到容器中
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
# 下载依赖(利用Docker缓存层,加速构建)
RUN ./mvnw dependency:go-offline
# 复制源代码并打包
COPY src ./src
RUN ./mvnw clean package -DskipTests
# 运行时阶段,使用更小的JRE镜像
FROM openjdk:17-jre-slim
# 将上阶段打包好的jar包复制过来
COPY --from=builder target/*.jar app.jar
# 暴露Spring Boot默认端口
EXPOSE 8080
# 设置JVM参数,优化容器内运行体验
ENTRYPOINT ["java", "-jar", "/app.jar"]
2. 构建镜像并运行
bash
# 1. 在项目根目录(Dockerfile所在目录)构建镜像
docker build -t email-ai-classifier .
# 2. 运行容器,将宿主机的8080端口映射到容器的8080端口,并传递API Key环境变量
docker run -d -p 8080:8080 \
--name email-classifier \
-e DEEPSEEK_API_KEY="你的实际API密钥" \
email-ai-classifier
3. 验证部署
访问 http://你的服务器IP:8080/api/email/health,看到成功信息即表示服务已在云端运行。
💡 深入与进阶方向(体现你的ML优势)
完成以上基础Demo后,你可以利用你的机器学习背景进行深度扩展:
-
高级Prompt工程:
- 实现 Few-shot Learning,在Prompt中提供几个高质量的分类示例。
- 要求AI输出置信度分数,对低置信度的结果进行人工复核流程设计。
-
引入向量数据库(RAG):
- 将历史邮件和分类规则存入 ChromaDB 或 Milvus。
- 在调用大模型前,先检索相似历史案例,将结果作为上下文注入Prompt,使分类更精准、更符合业务历史。
-
构建分类评估体系:
- 创建测试集,编写脚本批量调用接口,计算分类的准确率、召回率。
- 对比不同Prompt策略或不同模型(如DeepSeek vs GPT-3.5)在你这特定任务上的性能,形成实验报告。
这个项目完全在你的技术射程之内。如果在配置依赖、编写Dockerfile或设计更复杂的Prompt时遇到具体问题,可以随时追问。