在生成式 AI 席卷技术领域的今天,Java 开发者终于迎来了一站式的解决方案——Spring AI。这一由 Spring 官方推出的框架将大型语言模型(LLM)的调用、向量存储、提示词模板等能力以熟悉的 Spring 风格封装,使得 Java 应用能够快速集成 AI 功能。本文将通过一个完整的智能客服聊天机器人项目,带你从零配置 Spring AI,实现流式对话、历史记录管理,并探索函数调用等高级特性,让你在 Java 生态中轻松驾驭大模型。
一、项目准备与依赖配置
我们将创建一个 Spring Boot 3.2 项目,并添加 Spring AI 的 OpenAI Starter。由于 Spring AI 目前处于快速迭代阶段,需引入其里程碑仓库。
<!-- pom.xml 关键依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M3</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
在 application.yml 中配置 OpenAI API 密钥(可从 platform.openai.com 获取),并设置模型参数:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-3.5-turbo
temperature: 0.7
max-tokens: 500
环境变量 OPENAI_API_KEY 应在启动前设置,避免将密钥硬编码。至此,项目已具备调用大模型的能力。
二、第一个简单对话:Hello, Spring AI
Spring AI 提供了 AiClient 接口(实现类为 OpenAiChatClient)来发送和接收消息。我们先编写一个简单的 REST 接口测试连通性。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/ai")
public class SimpleAiController {
private final ChatClient chatClient;
public SimpleAiController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
启动项目后访问 http://localhost:8080/ai/chat?message=你好,即可获得模型回复。此时对话是无状态的,每次请求都是独立的。
三、实现带历史记忆的对话
智能客服需要上下文记忆。我们可以使用 Prompt 对象携带对话历史列表,让模型理解之前的对话。下面创建一个会话服务,使用简单的 Map 按会话 ID 存储消息历史。
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class ChatService {
private final ChatClient chatClient;
private final Map<String, List<Message>> sessionHistory = new ConcurrentHashMap<>();
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String chat(String sessionId, String userMessage) {
// 获取或创建当前会话的历史记录
List<Message> history = sessionHistory.computeIfAbsent(sessionId, key -> new ArrayList<>());
// 将用户消息加入历史
history.add(new UserMessage(userMessage));
// 构建包含历史的 Prompt
Prompt prompt = new Prompt(history, OpenAiChatOptions.builder()
.withModel("gpt-3.5-turbo")
.withTemperature(0.7f)
.build());
// 调用模型并获取回复
String assistantReply = chatClient.prompt(prompt).call().content();
// 将助手回复加入历史
history.add(new AssistantMessage(assistantReply));
// 限制历史长度,防止超出 token 限制
if (history.size() > 20) {
history.subList(0, 10).clear();
}
return assistantReply;
}
}
控制器使用该服务,并生成 sessionId 存入 cookie 或由前端传入:
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping("/chat")
public Map<String, String> chat(@RequestHeader("X-Session-Id") String sessionId,
@RequestBody Map<String, String> request) {
String userMessage = request.get("message");
String reply = chatService.chat(sessionId, userMessage);
return Map.of("reply", reply);
}
}
四、流式对话(Server-Sent Events)
为了提升用户体验,我们通常会希望像 ChatGPT 那样逐字显示回复。Spring AI 支持流式响应,可以通过 Flux<String> 返回,浏览器使用 EventSource 接收。
import reactor.core.publisher.Flux;
@GetMapping(value = "/stream", produces = "text/event-stream")
public Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
前端使用简单的 JavaScript 即可读取流:
const eventSource = new EventSource('/ai/stream?message=' + encodeURIComponent(userInput));
eventSource.onmessage = (event) => {
// 将收到的文本追加到对话框
appendToChat(event.data);
};
eventSource.onerror = () => eventSource.close();
结合历史记录,我们可以扩展 ChatService 的流式方法:
public Flux<String> streamChat(String sessionId, String userMessage) {
List<Message> history = sessionHistory.computeIfAbsent(sessionId, key -> new ArrayList<>());
history.add(new UserMessage(userMessage));
Prompt prompt = new Prompt(history);
StringBuilder fullReply = new StringBuilder();
return chatClient.prompt(prompt).stream().content()
.doOnNext(fullReply::append)
.doOnComplete(() -> history.add(new AssistantMessage(fullReply.toString())));
}
五、构建智能客服场景:提示词模板与上下文注入
客服机器人需要系统级提示词限定身份和行为。使用 SystemMessage 作为根消息,并将其添加到每次请求的历史列表中。
private static final String SYSTEM_PROMPT = """
你是一个电商平台的智能客服助手,名字叫“小慧”。
请用友好、专业的语气回答用户问题。
如果用户询问订单状态,请引导其提供订单号。
如果用户询问退货政策,请回复:我们支持7天无理由退货。
""";
public String chatWithSystem(String sessionId, String userMessage) {
List<Message> history = sessionHistory.computeIfAbsent(sessionId, key -> {
List<Message> msgs = new ArrayList<>();
msgs.add(new SystemMessage(SYSTEM_PROMPT));
return msgs;
});
// 确保系统消息始终在第一位(在历史删除时也要保留)
if (history.isEmpty() || !(history.get(0) instanceof SystemMessage)) {
history.add(0, new SystemMessage(SYSTEM_PROMPT));
}
history.add(new UserMessage(userMessage));
Prompt prompt = new Prompt(history);
String reply = chatClient.prompt(prompt).call().content();
history.add(new AssistantMessage(reply));
trimHistory(history);
return reply;
}
这样,无论对话进行多少轮,模型始终知道自己是一名客服,并遵循预设规则。
六、高级特性:函数调用(Function Calling)
智能客服经常需要查询真实数据(如运单号、订单详情),可通过函数调用让大模型产生结构化参数,然后由应用执行并返回结果。Spring AI 支持简化的 @Tool 注解和 FunctionCallback。
首先定义一个运单查询的服务,并将方法标注为 @Tool:
import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;
@Component
public class LogisticsService {
@Tool(description = "根据运单号查询物流状态")
public String queryLogistics(String trackingNumber) {
// 模拟查询数据库或第三方API
if (trackingNumber.equals("123456")) {
return "运单号123456:已到达目的地,正在派送中。";
}
return "未找到运单信息。";
}
}
然后在配置中注册 FunctionCallback Bean:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder, LogisticsService logisticsService) {
return builder
.defaultTools(logisticsService)
.build();
}
}
现在,当用户询问“我的快递123456到哪了”,大模型会自动生成函数调用请求,Spring AI 会拦截执行本地方法并将结果注入回模型,最终返回自然语言答复。整个过程对业务代码完全透明。
七、集成向量数据库实现知识库问答
为了回答关于公司政策、产品手册等非结构化知识,可以使用 Spring AI 的向量存储支持。以 Milvus 或 PGVector 为例,先将文档嵌入并存储,然后在对话中检索相关内容作为上下文。
# 添加依赖(以PGVector为例)
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>
# application.yml
spring:
ai:
vectorstore:
pgvector:
host: localhost
port: 5432
database: aistore
username: postgres
password: secret
然后通过 VectorStore 进行相似性检索,将结果拼接到系统提示词中:
String userQuestion = "退货流程是什么?";
List<Document> relevantDocs = vectorStore.similaritySearch(userQuestion);
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("n"));
String enrichedPrompt = "根据以下信息回答问题:n" + context + "n问题:" + userQuestion;
这种 RAG(检索增强生成)模式显著提升了回答的准确性和时效性。
八、总结与展望
通过本文的实战案例,我们使用 Spring AI 构建了一个功能完备的智能客服聊天机器人,涵盖基础调用、历史记忆、流式输出、提示词模板、函数调用和向量检索。Spring AI 延续了 Spring 生态的“约定优于配置”理念,让 Java 开发者无需离开舒适区就能深度集成大模型能力。
当前 Spring AI 仍处于里程碑版本,但其设计已经足够稳定用于原型和生产环境。未来它将支持更多模型(如 Azure OpenAI)、更丰富的向量存储以及更简洁的多模态交互。现在正是将 AI 能力融入 Java 应用的最佳时机,赶快在你的下一个 Spring Boot 项目中试试 Spring AI 吧。

