Java + AI 实战:Spring AI 从入门到企业级落地
本文档面向 Java 开发者,手把手教你用 Spring AI 框架集成大语言模型,构建生产级 AI 应用。
全程可实操,代码可复制,拿来即用。
目录
- 背景与趋势
- [技术选型:为什么选 Spring AI?](#技术选型:为什么选 Spring AI?)
- 环境准备
- [快速入门:第一个 AI 聊天接口](#快速入门:第一个 AI 聊天接口)
- 核心功能实操
- 5.1 Prompt Template(提示词模板)
- 5.2 Output Parser(结构化输出)
- 5.3 多模型切换(OpenAI / DeepSeek / 本地 Ollama)
- 5.4 Function Calling(函数调用)
- 5.5 RAG 检索增强生成
- [完整项目:AI 智能客服助手](#完整项目:AI 智能客服助手)
- 生产环境部署建议
- 常见问题与排错
- 总结与资源
1. 背景与趋势
2024-2025 年,大语言模型(LLM)已经从"玩具"走向"生产力工具"。企业级 AI 集成面临三大痛点:
| 痛点 | 说明 |
|---|---|
| 多模型切换 | OpenAI、DeepSeek、通义千问、本地模型,业务需要灵活切换 |
| 结构化输出 | AI 返回的是文本,业务需要 JSON、表格等结构化数据 |
| 检索增强(RAG) | 让 AI 基于自己的知识库回答,而非胡编乱造 |
Spring AI 正是为了解决这些问题而生------它是 Spring 官方推出的 AI 框架,类比于"JDBC 之于数据库,Spring AI 之于大模型"。
2. 技术选型:为什么选 Spring AI?
| 维度 | Spring AI | LangChain4j | Semantic Kernel (微软) |
|---|---|---|---|
| 生态整合 | ⭐⭐⭐⭐⭐ Spring Boot 原生 | ⭐⭐⭐⭐ Spring 友好 | ⭐⭐⭐ 偏向 .NET |
| 模型支持 | OpenAI、DeepSeek、Ollama、通义、Azure 等 20+ | OpenAI、Azure、Ollama | OpenAI、Azure |
| RAG 能力 | 内置向量数据库支持 | 内置 | 需要额外配置 |
| 学习成本 | ⭐⭐⭐⭐⭐ 熟悉 Spring 即可 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 生产成熟度 | Spring 官方,稳定迭代 | 社区活跃 | 微软背书 |
结论: 如果你团队以 Spring Boot 为主,选 Spring AI 是最平滑的路径。
3. 环境准备
3.1 基础环境
| 工具 | 版本要求 | 说明 |
|---|---|---|
| JDK | 17+ | Spring Boot 3.x 要求 |
| Maven | 3.8+ | 或 Gradle 8.x |
| IDE | IntelliJ IDEA / VS Code | 推荐 IDEA Ultimate |
| Spring Boot | 3.2+ | 配合 Spring AI 1.0+ |
3.2 获取 API Key
- OpenAI:https://platform.openai.com/api-keys
- DeepSeek:https://platform.deepseek.com/api_keys
- Ollama(本地):https://ollama.com/download(免费,无需 Key)
3.3 创建 Spring Boot 项目
访问 https://start.spring.io/,填入:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x 或 3.3.x
- Dependencies: Spring Web + Spring AI OpenAI
或者直接复制以下 pom.xml:
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.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-ai-demo</artifactId>
<version>1.0.0</version>
<name>spring-ai-demo</name>
<description>Java + AI 实战项目</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M5</spring-ai.version>
</properties>
<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-openai-spring-boot-starter</artifactId>
</dependency>
<!-- Spring AI Ollama(本地模型) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- Lombok(可选,减少样板代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<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>
</repositories>
<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>
</plugins>
</build>
</project>
3.4 配置文件
application.yml:
yaml
spring:
application:
name: spring-ai-demo
ai:
openai:
api-key: ${OPENAI_API_KEY:sk-your-key-here}
chat:
options:
model: gpt-4o-mini
temperature: 0.7
# 可选:Ollama 本地模型
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5:7b
server:
port: 8080
4. 快速入门:第一个 AI 聊天接口
4.1 主启动类
java
package com.example.ai;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiDemoApplication.class, args);
}
}
4.2 Controller --- 最简单的 AI 对话
java
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
/**
* 简单对话接口
* GET /api/chat?message=你好
*/
@GetMapping
public String chat(@RequestParam(defaultValue = "你好,请介绍一下你自己") String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
4.3 测试
启动项目后访问:
bash
curl http://localhost:8080/api/chat?message=用Java写一个快速排序算法
返回示例:
以下是用Java实现的快速排序算法:
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// ...
}
✅ 第一个 AI 接口完成!前后端分离架构,前端随便调。
5. 核心功能实操
5.1 Prompt Template(提示词模板)
硬编码提示词不够灵活,用模板来管理。
java
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/template")
public class PromptTemplateController {
private final ChatClient chatClient;
public PromptTemplateController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
/**
* 模板化提示词
* GET /api/template/translate?text=Hello World&language=中文
*/
@GetMapping("/translate")
public String translate(
@RequestParam String text,
@RequestParam(defaultValue = "中文") String language) {
String template = """
你是专业的翻译助手。
请将以下文本翻译成{language},只返回翻译结果,不要额外说明。
文本:{text}
""";
PromptTemplate promptTemplate = new PromptTemplate(template);
Map<String, Object> params = Map.of(
"text", text,
"language", language
);
return chatClient.prompt(promptTemplate.create(params))
.call()
.content();
}
}
测试:
GET /api/template/translate?text=Good morning&language=法语
→ "Bonjour"
5.2 Output Parser(结构化输出)
AI 返回的文本如果要做下游处理,需要转成结构化数据。
方式一:自定义解析
java
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RestController
@RequestMapping("/api/parse")
public class OutputParserController {
private final ChatClient chatClient;
public OutputParserController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
/**
* 让 AI 返回 JSON 格式
* GET /api/parse/book?topic=Java并发编程
*/
@GetMapping("/book")
public String recommendBook(@RequestParam String topic) {
String response = chatClient.prompt()
.user("""
推荐一本关于'%s'的书籍,按以下JSON格式返回:
{
"title": "书名",
"author": "作者",
"rating": 评分,
"reason": "推荐理由"
}
只返回JSON,不要其他内容。
""".formatted(topic))
.call()
.content();
// 提取 JSON 部分(防止 AI 有多余文字)
Pattern pattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL);
Matcher matcher = pattern.matcher(response);
if (matcher.find()) {
return matcher.group();
}
return response;
}
}
方式二:Spring AI 内置 BeanOutputParser(推荐)
java
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/parse")
public class BeanOutputController {
private final ChatClient chatClient;
public BeanOutputController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
// 定义输出结构
public record BookRecommendation(
String title,
String author,
double rating,
String reason
) {}
/**
* 结构化输出 ------ 自动解析为 Java 对象
* GET /api/parse/book2?topic=微服务架构
*/
@GetMapping("/book2")
public BookRecommendation recommendBook2(@RequestParam String topic) {
var converter = new BeanOutputConverter<>(BookRecommendation.class);
String json = chatClient.prompt()
.user("推荐一本关于'" + topic + "'的书籍")
.user(converter.getFormatInstruction()) // 自动注入格式指令
.call()
.content();
return converter.convert(json); // 自动解析为 Java 对象
}
}
访问结果(自动返回 JSON):
json
{
"title": "微服务架构设计模式",
"author": "Chris Richardson",
"rating": 4.7,
"reason": "全面覆盖微服务设计、部署、治理的经典之作"
}
5.3 多模型切换(OpenAI / DeepSeek / Ollama)
生产环境中你不可能只绑死一个模型。Spring AI 通过 @Qualifier 实现多模型共存。
java
package com.example.ai.config;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiModelConfig {
@Value("${spring.ai.openai.api-key}")
private String openaiApiKey;
/**
* OpenAI 模型 Bean
*/
@Bean("openAiChatModel")
public ChatModel openAiChatModel() {
var api = OpenAiApi.builder()
.apiKey(openaiApiKey)
.build();
return OpenAiChatModel.builder()
.openAiApi(api)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o-mini")
.temperature(0.7)
.build())
.build();
}
/**
* DeepSeek 模型 Bean(兼容 OpenAI SDK)
*/
@Bean("deepSeekChatModel")
public ChatModel deepSeekChatModel() {
var api = OpenAiApi.builder()
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.baseUrl("https://api.deepseek.com")
.build();
return OpenAiChatModel.builder()
.openAiApi(api)
.defaultOptions(OpenAiChatOptions.builder()
.model("deepseek-chat")
.temperature(0.7)
.build())
.build();
}
/**
* Ollama 本地模型 Bean(免费,无需 Key)
*/
@Bean("ollamaChatModel")
public ChatModel ollamaChatModel() {
var api = OllamaApi.builder()
.baseUrl("http://localhost:11434")
.build();
return OllamaChatModel.builder()
.ollamaApi(api)
.defaultOptions(OllamaOptions.builder()
.model("qwen2.5:7b")
.temperature(0.7)
.build())
.build();
}
}
使用多模型:
java
@RestController
@RequestMapping("/api/chat")
public class MultiModelController {
private final ChatModel openAiModel;
private final ChatModel deepSeekModel;
private final ChatModel ollamaModel;
public MultiModelController(
@Qualifier("openAiChatModel") ChatModel openAiModel,
@Qualifier("deepSeekChatModel") ChatModel deepSeekModel,
@Qualifier("ollamaChatModel") ChatModel ollamaModel) {
this.openAiModel = openAiModel;
this.deepSeekModel = deepSeekModel;
this.ollamaModel = ollamaModel;
}
/**
* 切换模型
* GET /api/chat/switch?model=openai&message=你好
* model 可选: openai / deepseek / ollama
*/
@GetMapping("/switch")
public String switchChat(
@RequestParam(defaultValue = "openai") String model,
@RequestParam String message) {
ChatModel chatModel = switch (model) {
case "deepseek" -> deepSeekModel;
case "ollama" -> ollamaModel;
default -> openAiModel;
};
return chatModel.call(message);
}
}
5.4 Function Calling(让 AI 调用你的 Java 方法)
这是最实用的功能------让 AI 能查询数据库、调用 API、执行业务逻辑。
定义工具函数
java
package com.example.ai.tool;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Function;
@Component
public class WeatherService implements Function<WeatherService.Request, WeatherService.Response> {
public record Request(String city) {}
public record Response(String city, String weather, double temperature, String advice) {}
@Override
public Response apply(Request request) {
// 实际项目中这里调用真实天气 API
return new Response(
request.city(),
"晴",
24.5,
"天气不错,适合外出活动 🌤️"
);
}
}
注册 Function Calling
java
package com.example.ai.controller;
import com.example.ai.tool.WeatherService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/fc")
public class FunctionCallingController {
private final ChatClient chatClient;
public FunctionCallingController(ChatClient.Builder builder, WeatherService weatherService) {
this.chatClient = builder
.defaultTools(weatherService) // 注册工具
.build();
}
/**
* AI 会自动判断何时调用天气工具
* GET /api/fc/weather?question=北京今天天气怎么样?
*/
@GetMapping("/weather")
public String askWeather(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
效果: 用户问"北京今天天气怎么样?",AI 自动调用 WeatherService 获取真实天气数据,再组织成自然语言返回。
5.5 RAG 检索增强生成
RAG(Retrieval-Augmented Generation)让 AI 基于你自己的文档回答问题。
5.5.1 添加依赖
xml
<!-- 向量数据库(PGvector 示例) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
<!-- 嵌入模型(文本向量化) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
5.5.2 文档入库
java
package com.example.ai.service;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.util.List;
@Service
public class DocumentIngestionService {
private final VectorStore vectorStore;
private final ResourceLoader resourceLoader;
public DocumentIngestionService(VectorStore vectorStore, ResourceLoader resourceLoader) {
this.vectorStore = vectorStore;
this.resourceLoader = resourceLoader;
}
/**
* 启动时加载知识库文档
*/
@PostConstruct
public void loadDocuments() {
// 读取 classpath 下的文档
Resource resource = resourceLoader.getResource("classpath:docs/knowledge-base.txt");
TextReader reader = new TextReader(resource);
List<Document> documents = reader.get();
// 文本分块(Chunk)
TokenTextSplitter splitter = new TokenTextSplitter();
List<Document> chunks = splitter.apply(documents);
// 向量化并存入向量数据库
vectorStore.add(chunks);
System.out.println("✅ 知识库加载完成,共 " + chunks.size() + " 个文档块");
}
}
5.5.3 RAG 问答
java
package com.example.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.web.bind.annotation.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/rag")
public class RagController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public RagController(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder.build();
this.vectorStore = vectorStore;
}
/**
* 基于知识库的问答
* GET /api/rag/ask?question=我们的产品支持哪些支付方式?
*/
@GetMapping("/ask")
public String ask(@RequestParam String question) {
// 1. 在知识库中检索相关内容
var results = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3)
);
// 2. 构建上下文
String context = results.stream()
.map(doc -> doc.getText())
.collect(Collectors.joining("\n---\n"));
// 3. 让 AI 基于上下文回答
return chatClient.prompt()
.user("""
请基于以下知识库内容回答问题。
如果知识库中没有相关信息,请如实说"知识库中没有相关记载"。
知识库:
%s
问题:%s
""".formatted(context, question))
.call()
.content();
}
}
6. 完整项目:AI 智能客服助手
将以上技术整合为一个完整的 AI 智能客服系统。
6.1 项目结构
spring-ai-demo/
├── src/main/java/com/example/ai/
│ ├── SpringAiDemoApplication.java
│ ├── config/
│ │ └── AiModelConfig.java # 多模型配置
│ ├── controller/
│ │ ├── ChatController.java # 基础聊天
│ │ ├── PromptTemplateController.java # 提示词模板
│ │ ├── OutputParserController.java # 结构化输出
│ │ ├── MultiModelController.java # 多模型切换
│ │ ├── FunctionCallingController.java # 函数调用
│ │ └── RagController.java # RAG 问答
│ ├── service/
│ │ ├── CustomerService.java # 客服业务逻辑
│ │ └── DocumentIngestionService.java # 知识库加载
│ └── tool/
│ ├── WeatherService.java # 天气查询工具
│ └── OrderService.java # 订单查询工具
├── src/main/resources/
│ ├── application.yml
│ ├── docs/
│ │ └── knowledge-base.txt # 知识库文档
│ └── prompts/
│ └── customer-service.st # 客服提示词模板
└── pom.xml
6.2 智能客服核心逻辑
java
package com.example.ai.service;
import com.example.ai.tool.OrderService;
import com.example.ai.tool.WeatherService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Service
public class CustomerService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public CustomerService(
ChatClient.Builder builder,
VectorStore vectorStore,
WeatherService weatherService,
OrderService orderService) {
this.chatClient = builder
.defaultTools(weatherService, orderService) // 注册工具
.build();
this.vectorStore = vectorStore;
}
/**
* 智能客服统一入口
*/
public String answer(String question, String userId) {
// 1. 检索知识库
var docs = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3)
);
String context = docs.stream()
.map(d -> d.getText())
.collect(Collectors.joining("\n---\n"));
// 2. 调用 AI(提示词中注入上下文 + 用户信息)
return chatClient.prompt()
.user("""
你是某电商平台的智能客服助手。
请基于以下知识库和工具回答用户问题。
知识库:
%s
用户ID:%s
用户问题:%s
要求:
- 回答友好、专业
- 如果知识库中有答案,优先使用知识库
- 如果需要查订单,调用订单查询工具
- 如果不知道,不要编造
""".formatted(context, userId, question))
.call()
.content();
}
}
6.3 订单查询工具
java
package com.example.ai.tool;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@Component
public class OrderService implements Function<OrderService.Request, OrderService.Response> {
// 模拟订单数据
private static final Map<String, OrderInfo> ORDERS = new ConcurrentHashMap<>();
static {
ORDERS.put("ORD001", new OrderInfo("ORD001", "已发货", "顺丰快递", "SF1234567890"));
ORDERS.put("ORD002", new OrderInfo("ORD002", "配送中", "中通快递", "ZT9876543210"));
ORDERS.put("ORD003", new OrderInfo("ORD003", "已完成", "圆通快递", "YT5555555555"));
}
public record Request(String orderId) {}
public record Response(String orderId, String status, String courier, String trackingNo) {}
@Override
public Response apply(Request request) {
OrderInfo info = ORDERS.get(request.orderId());
if (info == null) {
return new Response(request.orderId(), "未找到", "", "");
}
return new Response(info.orderId(), info.status(), info.courier(), info.trackingNo());
}
private record OrderInfo(String orderId, String status, String courier, String trackingNo) {}
}
6.4 前端对接示例(HTML)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AI 智能客服</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#chat-box { height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; background: #f9f9f9; }
.msg { margin: 8px 0; padding: 8px 12px; border-radius: 8px; }
.user { background: #007aff; color: white; text-align: right; margin-left: 30%; }
.ai { background: white; border: 1px solid #ddd; text-align: left; margin-right: 30%; }
#input-area { display: flex; gap: 8px; }
#input-area input { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
#input-area button { padding: 8px 20px; background: #007aff; color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<h2>🤖 AI 智能客服</h2>
<div id="chat-box">
<div class="msg ai">您好!我是智能客服助手,请问有什么可以帮助您的?</div>
</div>
<div id="input-area">
<input type="text" id="message" placeholder="请输入您的问题..." onkeypress="if(event.key==='Enter') send()">
<button onclick="send()">发送</button>
</div>
<script>
async function send() {
const input = document.getElementById('message');
const msg = input.value.trim();
if (!msg) return;
// 显示用户消息
appendMsg('user', msg);
input.value = '';
// 调用后端 API
try {
const res = await fetch(`/api/chat?message=${encodeURIComponent(msg)}`);
const data = await res.text();
appendMsg('ai', data);
} catch (err) {
appendMsg('ai', '😅 网络异常,请稍后重试');
}
}
function appendMsg(role, text) {
const box = document.getElementById('chat-box');
const div = document.createElement('div');
div.className = 'msg ' + role;
div.textContent = text;
box.appendChild(div);
box.scrollTop = box.scrollHeight;
}
</script>
</body>
</html>
7. 生产环境部署建议
7.1 配置文件分离
yaml
# application-prod.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY} # 通过环境变量注入,不要写死
ollama:
base-url: http://ollama-service:11434 # Docker 内部服务名
启动:
bash
java -jar spring-ai-demo.jar --spring.profiles.active=prod
7.2 Docker 部署
dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/spring-ai-demo.jar app.jar
EXPOSE 8080
ENV SPRING_PROFILES_ACTIVE=prod
ENTRYPOINT ["java", "-jar", "app.jar"]
yaml
# docker-compose.yml
version: '3.8'
services:
ai-app:
build: .
ports:
- "8080:8080"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- ollama
- postgres
ollama:
image: ollama/ollama
volumes:
- ollama-data:/root/.ollama
ports:
- "11434:11434"
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: vectordb
POSTGRES_USER: ai
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
ollama-data:
pgdata:
7.3 关键生产建议
| 建议项 | 说明 |
|---|---|
| API Key 管理 | 使用 Vault / K8s Secret / 环境变量,绝不硬编码 |
| 限流 | 接入 Sentinel / Resilience4j,防止 API 调用超预算 |
| 缓存 | 高频问题的回答可以 Redis 缓存,减少 API 调用 |
| 监控 | 记录每次 AI 调用的耗时、Token 消耗、成功率 |
| 回退策略 | 模型不可用时降级到本地小模型或固定回复 |
| 内容安全 | 对 AI 输出做敏感词过滤、内容审核 |
8. 常见问题与排错
Q1:启动报错 "No bean named 'chatModel' available"
原因: 没有自动配置 ChatModel。
解决: 检查 pom.xml 是否有 Spring AI Starter 依赖,以及 application.yml 中 spring.ai.openai.api-key 是否正确配置。
Q2:中文乱码
解决: 确保 application.yml 中无 BOM,JVM 参数加 -Dfile.encoding=UTF-8。
Q3:调用 OpenAI 超时
yaml
spring:
ai:
openai:
client:
connect-timeout: 30s
read-timeout: 60s
Q4:Ollama 连接被拒绝
解决: 确保 Ollama 已启动并监听正确端口:
bash
ollama serve
curl http://localhost:11434/api/tags
Q5:Token 消耗太快
优化建议:
- 使用更小的模型(
gpt-4o-mini代替gpt-4o) - 设置
maxTokens限制输出长度 - 使用缓存减少重复请求
- 精简 System Prompt
9. 总结与资源
学完本文,你已掌握:
- ✅ Spring AI 项目搭建与配置
- ✅ 基础聊天接口(ChatClient)
- ✅ 提示词模板(PromptTemplate)
- ✅ 结构化输出(OutputParser / BeanOutputConverter)
- ✅ 多模型切换(OpenAI / DeepSeek / Ollama)
- ✅ 函数调用(Function Calling)
- ✅ RAG 检索增强生成
- ✅ 完整项目:AI 智能客服
- ✅ 生产环境部署
推荐资源
| 资源 | 链接 |
|---|---|
| Spring AI 官方文档 | https://docs.spring.io/spring-ai/reference/ |
| Spring AI GitHub | https://github.com/spring-projects/spring-ai |
| Ollama 模型库 | https://ollama.com/library |
| DeepSeek 平台 | https://platform.deepseek.com/ |
| PGVector 项目 | https://github.com/pgvector/pgvector |
下一步学习
- 接入 多模态(图片理解、语音交互)
- 集成 MCP 协议,让 AI 能操作更多外部工具
- 构建 AI Agent,支持多轮自主决策
- 实现 流式输出(SSE),提升用户体验
写在最后:
AI 不是银弹,但结合业务场景的 AI 应用一定是未来的核心竞争力。
本文所有代码均可直接运行,建议打开 IDE 边看边敲。
如果在实操中遇到问题,欢迎留言交流!
*本文由 AI 辅助生成