Spring AI + 向量数据库:Java 工程师的 AI 应用开发实战指南
Spring AI + 向量数据库:Java 工程师的 AI 应用开发实战指南
阅读时长:15-20分钟
难度等级:⭐⭐⭐(适合有 Spring Boot 基础的 Java 工程师)
收获目标:从 0 到 1 掌握 Spring AI,能独立开发 AI 应用
一、背景 + 痛点
1.1 为什么现在要学 AI 开发?
最近半年,我身边越来越多的同事开始问这些问题:
- “老板让我做个 AI 智能客服,我该从哪入手?”
- “听说 RAG 很火,但我是做 Java 的,不会 Python 怎么办?”
- “面试总问 AI 架构,但我只会写 CRUD,怎么破?”
痛点很真实:
- ❌ AI 技术栈复杂(Python、LangChain、向量数据库…)
- ❌ Java 生态缺乏统一的 AI 开发框架
- ❌ 想做 RAG 但不知道怎么设计架构
- ❌ 官方文档大多是英文,学习曲线陡峭
1.2 Spring AI 的出现
Spring AI 是什么?
简单说,Spring AI 就是 Java 版的 LangChain,它为 Spring 生态提供了 AI 开发能力,让你用熟悉的 Spring Boot 方式开发 AI 应用。
核心优势:
- ✅ Java 开发者友好,零 Python 基础也能上手
- ✅ 与 Spring Boot 无缝集成,学习成本低
- ✅ 支持多种 AI 模型(OpenAI、Azure、通义千问等)
- ✅ 支持多种向量数据库(Redis、PGVector、Milvus 等)
- ✅ 统一的 API 抽象,切换模型零代码改动
二、Spring AI 核心概念讲解(通俗易懂)
2.1 用 Spring MVC 类比理解
先看一张对比表,帮你快速建立认知:
| Spring MVC | Spring AI | 作用 |
|---|---|---|
@Controller |
@RestController |
接口入口 |
Service |
ChatClient |
业务逻辑处理 |
Dao/Repository |
VectorStore |
数据持久化 |
Model |
Message |
数据传输对象 |
HandlerInterceptor |
PromptTemplate |
请求/响应增强 |
核心组件图解:
用户请求 → Controller → ChatClient (LLM调用) → 响应
↓
VectorStore (向量检索)
2.2 核心概念详解
(1)ChatClient - AI 对话客户端
类比:就像 RestTemplate 或 WebClient,专门用来调用 LLM 接口。
// Spring MVC 中的 HTTP 客户端
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
// Spring AI 中的 AI 客户端
ChatClient chatClient = new OpenAiChatClient(openAiApi);
String response = chatClient.prompt().user("你好").call().content();
(2)VectorStore - 向量数据库
类比:就像 JPA Repository,但存储的是向量而非普通数据。
// Spring Data JPA
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
// Spring AI VectorStore
@Repository
public interface ProductVectorStore extends VectorStore<Product> {
// 自动支持语义搜索!
}
(3)EmbeddingModel - 文本向量化
类比:就像 ObjectMapper 把对象转 JSON,EmbeddingModel 把文本转向量。
// JSON 序列化
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
// 文本向量化
EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(openAiApi);
List<Double> vector = embeddingModel.embed("苹果手机").getResults();
(4)PromptTemplate - 提示词模板
类比:就像 Thymeleaf 模板引擎,动态生成提示词。
// Thymeleaf 模板
String template = "Hello, ${name}!";
String result = template.replace("${name}", "张三");
// Spring AI PromptTemplate
String template = "你是一个{name},请回答:{question}";
Prompt prompt = new PromptTemplate(template)
.create(Map.of("name", "Java工程师", "question", "什么是RAG?"));
(5)RAG - 检索增强生成
核心流程(类比搜索引擎):
用户问题 → 向量化 → 向量数据库检索 → 相关文档 → 拼接到 Prompt → LLM 生成答案
类比:
用户搜索 → 关键词提取 → 数据库查询 → 相关结果 → 展示给用户
三、快速上手
3.1 项目初始化
Step 1:创建 Spring Boot 项目
<!-- pom.xml -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M4</version>
</dependency>
<!-- 向量数据库支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
<version>1.0.0-M4</version>
</dependency>
</dependencies>
Step 2:配置文件
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY} # 替换为你的 API Key
chat:
options:
model: gpt-3.5-turbo
temperature: 0.7
vectorstore:
redis:
uri: redis://localhost:6379
index: ai-index
prefix: ai-doc:
Step 3:第一个 AI 对话接口
@RestController
@RequestMapping("/api/ai")
@RequiredArgsConstructor
public class AiChatController {
private final ChatClient chatClient;
@PostMapping("/chat")
public String chat(@RequestBody String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
测试:
curl -X POST http://localhost:8080/api/ai/chat \
-H "Content-Type: application/json" \
-d "用Java写一个快速排序"
3.2 向量数据库快速体验
Step 1:准备文档数据
@Service
@RequiredArgsConstructor
public class DocumentService {
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
public void loadDocuments() {
List<Document> documents = List.of(
new Document("Spring Boot 是基于 Spring 框架的开发工具...",
Map.of("type", "技术文档", "category", "Java")),
new Document("Redis 是一个高性能的键值数据库...",
Map.of("type", "技术文档", "category", "数据库")),
new Document("Docker 是一个容器化平台...",
Map.of("type", "技术文档", "category", "运维"))
);
vectorStore.add(documents);
System.out.println("文档加载完成!");
}
}
Step 2:语义搜索
@GetMapping("/search")
public List<Document> search(@RequestParam String query) {
return vectorStore.similaritySearch(
SearchRequest.query(query).withTopK(3)
);
}
测试:
# 搜索 "Java 开发框架"
curl "http://localhost:8080/api/ai/search?query=Java开发框架"
# 返回 Spring Boot 相关文档(即使关键词不完全匹配)
四、进阶
4.1 Prompt Template 高级用法
@Service
@RequiredArgsConstructor
public class PromptService {
private final ChatClient chatClient;
// 定义 Prompt 模板
private static final String SYSTEM_PROMPT = """
你是一个专业的{role},请根据以下上下文回答问题:
上下文:
{context}
问题:{question}
要求:
1. 回答要专业、准确
2. 如果上下文中没有答案,请明确说明
3. 回答长度控制在{maxWords}字以内
""";
public String generateAnswer(String role, String context,
String question, int maxWords) {
PromptTemplate template = new PromptTemplate(SYSTEM_PROMPT);
Prompt prompt = template.create(Map.of(
"role", role,
"context", context,
"question", question,
"maxWords", maxWords
));
return chatClient.prompt(prompt)
.call()
.content();
}
}
4.2 流式响应(Streaming)
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
前端调用示例:
const eventSource = new EventSource('/api/ai/stream?message=你好');
eventSource.onmessage = (event) => {
console.log(event.data);
// 实时显示 AI 回复
document.getElementById('output').textContent += event.data;
};
4.3 多轮对话(Memory)
@Service
@RequiredArgsConstructor
public class ConversationService {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public String chat(String sessionId, String userMessage) {
// 获取历史对话
List<Message> history = chatMemory.get(sessionId, 10);
// 构建当前对话
List<Message> messages = new ArrayList<>(history);
messages.add(new UserMessage(userMessage));
// 调用 LLM
String response = chatClient.prompt()
.messages(messages)
.call()
.content();
// 保存到记忆
chatMemory.add(sessionId, new UserMessage(userMessage));
chatMemory.add(sessionId, new AssistantMessage(response));
return response;
}
}
4.4 Function Calling(函数调用)
@Configuration
public class FunctionConfig {
@Bean
@Description("查询天气信息")
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return request -> {
// 模拟调用天气 API
return new WeatherResponse(
request.getCity(),
"晴天",
25,
"空气质量优"
);
};
}
}
// 使用函数调用
@PostMapping("/weather")
public String askWeather(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.functions("weatherFunction") // 注册函数
.call()
.content();
}
测试:
curl -X POST "http://localhost:8080/api/ai/weather?question=北京今天天气怎么样?"
# AI 会自动调用 weatherFunction 并返回:北京今天晴天,温度25°C,空气质量优
五、实战
5.1 场景:智能客服系统(RAG)
需求分析:
- 用户提问产品相关问题
- AI 从产品文档中检索相关信息
- 基于检索结果生成专业回答
完整代码实现:
(1)文档上传与处理
@RestController
@RequestMapping("/api/customer-service")
@RequiredArgsConstructor
public class DocumentUploadController {
private final VectorStore vectorStore;
private final DocumentReader documentReader;
private final DocumentTransformer documentTransformer;
@PostMapping("/upload")
public String uploadDocument(@RequestParam("file") MultipartFile file) {
try {
// 1. 读取文档
List<Document> documents = documentReader.get(file.getInputStream());
// 2. 文档分块(重要!)
List<Document> chunks = documentTransformer.apply(documents);
// 3. 添加元数据
chunks = chunks.stream()
.map(doc -> {
Map<String, Object> metadata = new HashMap<>(doc.getMetadata());
metadata.put("uploadTime", System.currentTimeMillis());
metadata.put("fileName", file.getOriginalFilename());
return new Document(doc.getContent(), metadata);
})
.collect(Collectors.toList());
// 4. 存入向量数据库
vectorStore.add(chunks);
return "上传成功,共处理 " + chunks.size() + " 个文档块";
} catch (Exception e) {
throw new RuntimeException("文档上传失败", e);
}
}
}
(2)智能问答接口
@RestController
@RequestMapping("/api/customer-service")
@RequiredArgsConstructor
public class CustomerServiceController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private static final String RAG_PROMPT = """
你是一个专业的客服人员,请根据以下参考信息回答用户问题:
参考信息:
{context}
用户问题:{question}
回答要求:
1. 仅基于参考信息回答,不要编造内容
2. 如果参考信息中没有答案,请礼貌告知
3. 回答要友好、专业
4. 可以适当引用参考信息中的具体内容
""";
@PostMapping("/ask")
public String ask(@RequestBody String question) {
// 1. 向量检索
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.query(question)
.withTopK(3) // 取最相关的 3 个文档块
.withSimilarityThreshold(0.7) // 相似度阈值
);
// 2. 构建上下文
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// 3. 生成 Prompt
PromptTemplate template = new PromptTemplate(RAG_PROMPT);
Prompt prompt = template.create(Map.of(
"context", context,
"question", question
));
// 4. 调用 LLM
return chatClient.prompt(prompt)
.call()
.content();
}
}
(3)文档分块配置
@Configuration
public class DocumentChunkConfig {
@Bean
public DocumentTransformer tokenTextSplitter() {
return new TokenTextSplitter(
500, // chunk size
50, // overlap
5, // minChunkSizeToKeep
10000, // maxChunkSizeToKeep
true // keepSeparator
);
}
@Bean
public DocumentReader pdfReader() {
return new PdfDocumentReader();
}
}
5.2 完整测试流程
# 1. 上传产品文档
curl -X POST http://localhost:8080/api/customer-service/upload \
-F "file=@product_manual.pdf"
# 返回:上传成功,共处理 156 个文档块
# 2. 提问
curl -X POST http://localhost:8080/api/customer-service/ask \
-H "Content-Type: application/json" \
-d "如何申请退款?"
# 返回:
# 根据产品手册,您可以按照以下步骤申请退款:
# 1. 登录账户,进入"我的订单"页面
# 2. 找到需要退款的订单,点击"申请退款"
# 3. 选择退款原因,提交申请
# 4. 等待客服审核,通常1-3个工作日内完成
#
# 如有其他问题,欢迎随时咨询!
六、架构设计(重点)
6.1 企业级 AI 应用架构图
┌─────────────────────────────────────────────────────────────┐
│ 前端层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Web 端 │ │ 小程序 │ │ App 端 │ │ 开放API │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼────────────┼────────────┼────────────┼───────────────┘
│ │ │ │
┌───────┴────────────┴────────────┴────────────┴───────────────┐
│ 网关层 │
│ (Spring Cloud Gateway / Nginx) │
│ 认证授权 · 限流熔断 · 路由转发 · 日志监控 │
└───────┬──────────────────────────────────────────────────────┘
│
┌───────┴──────────────────────────────────────────────────────┐
│ 应用层 (Spring Boot) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Controller 层 (接口层) │ │
│ │ AiChatController · DocumentController · UserController│ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴───────────────────────────────┐ │
│ │ Service 层 (业务层) │ │
│ │ · ChatService (对话服务) │ │
│ │ · RagService (RAG 服务) │ │
│ │ · DocumentService (文档服务) │ │
│ │ · VectorSearchService (向量搜索服务) │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴───────────────────────────────┐ │
│ │ AI 层 (Spring AI) │ │
│ │ · ChatClient (LLM 调用) │ │
│ │ · VectorStore (向量存储) │ │
│ │ · EmbeddingModel (向量化) │ │
│ │ · PromptTemplate (提示词模板) │ │
│ │ · ChatMemory (对话记忆) │ │
│ └──────────────────────┬───────────────────────────────┘ │
└─────────────────────────┼─────────────────────────────────────┘
│
┌─────────────────────────┴─────────────────────────────────────┐
│ 数据层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 向量数据库 │ │ 关系数据库 │ │ 缓存数据库 │ │
│ │ (Redis/PGV) │ │ (MySQL) │ │ (Redis) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────┬─────────────────────────────────────┘
│
┌─────────────────────────┴─────────────────────────────────────┐
│ 外部服务层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ LLM 服务 │ │ Embedding服务│ │ 文件存储 │ │
│ │ (OpenAI/通义) │ │ (OpenAI) │ │ (OSS/S3) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────────────────────────────────────────────┘
6.2 核心设计模式
(1)策略模式 - 多模型支持
public interface LlmProvider {
String chat(String prompt);
List<Double> embed(String text);
}
@Service("openaiProvider")
public class OpenAiProvider implements LlmProvider {
// OpenAI 实现
}
@Service("qianwenProvider")
public class QianwenProvider implements LlmProvider {
// 通义千问实现
}
@Service
@RequiredArgsConstructor
public class LlmService {
private final Map<String, LlmProvider> providerMap;
@Value("${ai.provider:openai}")
private String currentProvider;
public String chat(String prompt) {
return providerMap.get(currentProvider + "Provider").chat(prompt);
}
}
(2)责任链模式 - Prompt 增强
public interface PromptEnhancer {
Prompt enhance(Prompt prompt);
}
@Component
@Order(1)
public class ContextEnhancer implements PromptEnhancer {
@Override
public Prompt enhance(Prompt prompt) {
// 添加上下文信息
return prompt;
}
}
@Component
@Order(2)
public class HistoryEnhancer implements PromptEnhancer {
@Override
public Prompt enhance(Prompt prompt) {
// 添加历史对话
return prompt;
}
}
@Service
@RequiredArgsConstructor
public class PromptChain {
private final List<PromptEnhancer> enhancers;
public Prompt process(Prompt prompt) {
for (PromptEnhancer enhancer : enhancers) {
prompt = enhancer.enhance(prompt);
}
return prompt;
}
}
(3)观察者模式 - 响应监控
@Component
public class AiResponseMonitor {
@EventListener
public void onAiResponse(AiResponseEvent event) {
// 记录响应时间
long duration = event.getEndTime() - event.getStartTime();
// 记录 Token 使用量
int tokens = event.getTokenUsage();
// 记录到监控系统
Metrics.record("ai.response.duration", duration);
Metrics.record("ai.response.tokens", tokens);
// 异常告警
if (duration > 5000) {
AlertService.send("AI 响应超时: " + duration + "ms");
}
}
}
6.3 性能优化策略
(1)缓存策略
@Service
@RequiredArgsConstructor
public class CachedChatService {
private final ChatClient chatClient;
private final CacheManager cacheManager;
@Cacheable(value = "ai:chat", key = "#prompt.hashCode()")
public String chat(String prompt) {
return chatClient.prompt().user(prompt).call().content();
}
@Cacheable(value = "ai:embed", key = "#text.hashCode()")
public List<Double> embed(String text) {
return embeddingModel.embed(text).getResults();
}
}
(2)批量处理
@Service
public class BatchEmbeddingService {
@Async
public CompletableFuture<Void> batchEmbed(List<String> texts) {
// 分批处理,避免单次请求过大
int batchSize = 100;
for (int i = 0; i < texts.size(); i += batchSize) {
List<String> batch = texts.subList(i,
Math.min(i + batchSize, texts.size()));
embeddingModel.embed(batch);
}
return CompletableFuture.completedFuture(null);
}
}
(3)异步调用
@RestController
@RequiredArgsConstructor
public class AsyncAiController {
private final ChatService chatService;
@PostMapping("/async-chat")
public CompletableFuture<String> asyncChat(@RequestBody String message) {
return CompletableFuture.supplyAsync(() ->
chatService.chat(message)
);
}
@PostMapping("/stream-chat")
public Flux<String> streamChat(@RequestBody String message) {
return chatService.chatStream(message);
}
}
6.4 安全设计
(1)敏感信息过滤
@Component
public class SensitiveDataFilter {
private static final Pattern PHONE_PATTERN =
Pattern.compile("1[3-9]\\d{9}");
private static final Pattern ID_CARD_PATTERN =
Pattern.compile("\\d{17}[0-9Xx]");
public String filter(String text) {
// 手机号脱敏
text = PHONE_PATTERN.matcher(text).replaceAll("138****8888");
// 身份证脱敏
text = ID_CARD_PATTERN.matcher(text).replaceAll("***********");
return text;
}
}
(2)Prompt 注入防护
@Component
public class PromptInjectionGuard {
private static final List<String> BLOCKED_KEYWORDS = List.of(
"忽略以上指令", "ignore previous instructions",
"系统提示", "system prompt"
);
public boolean isSafe(String userPrompt) {
String lowerPrompt = userPrompt.toLowerCase();
return BLOCKED_KEYWORDS.stream()
.noneMatch(lowerPrompt::contains);
}
}
(3)访问限流
@Configuration
public class RateLimitConfig {
@Bean
public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
FilterRegistrationBean<RateLimitFilter> registration =
new FilterRegistrationBean<>();
registration.setFilter(new RateLimitFilter());
registration.addUrlPatterns("/api/ai/*");
return registration;
}
}
public class RateLimitFilter implements Filter {
private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 10 QPS
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!rateLimiter.tryAcquire()) {
response.getWriter().write("请求过于频繁,请稍后再试");
return;
}
chain.doFilter(request, response);
}
}
七、常见坑总结(非常重要)
坑 1:向量数据库连接超时
现象:
org.springframework.ai.vectorstore.VectorStoreException:
Connection timeout to vector store
原因:
- Redis/PGVector 未正确配置
- 网络问题或防火墙限制
- 连接池配置不合理
解决方案:
spring:
ai:
vectorstore:
redis:
uri: redis://localhost:6379
timeout: 30000 # 增加超时时间
pool:
max-active: 8
max-idle: 8
min-idle: 2
坑 2:Embedding 维度不匹配
现象:
IllegalArgumentException: Embedding dimension mismatch.
Expected: 1536, Actual: 768
原因:
- 使用了不同的 Embedding 模型
- 向量数据库中已存在不同维度的数据
解决方案:
// 清空旧数据
vectorStore.delete(List.of());
// 确保使用相同的模型
@Bean
public EmbeddingModel embeddingModel() {
return new OpenAiEmbeddingModel(openAiApi,
OpenAiEmbeddingOptions.builder()
.withModel("text-embedding-ada-002") // 固定模型
.build());
}
坑 3:Token 超限
现象:
OpenAiApiException: This model's maximum context length is 4097 tokens.
However, your messages resulted in 5000 tokens.
原因:
- Prompt 过长
- 历史对话未清理
- 文档分块不合理
解决方案:
@Service
public class TokenLimitService {
private static final int MAX_TOKENS = 4000;
public String truncateIfNeeded(String text, String model) {
int maxChars = getMaxCharsForModel(model);
if (text.length() > maxChars) {
return text.substring(0, maxChars) + "...";
}
return text;
}
public void cleanOldHistory(String sessionId, int keepCount) {
List<Message> history = chatMemory.get(sessionId);
if (history.size() > keepCount) {
List<Message> toKeep = history.subList(
history.size() - keepCount, history.size());
chatMemory.clear(sessionId);
toKeep.forEach(msg -> chatMemory.add(sessionId, msg));
}
}
}
坑 4:向量检索效果差
现象:
- 搜索结果不相关
- RAG 回答不准确
原因:
- 文档分块过大或过小
- 相似度阈值设置不合理
- Embedding 模型不匹配场景
解决方案:
// 优化文档分块
@Bean
public DocumentTransformer optimizedTextSplitter() {
return new TokenTextSplitter(
300, // 减小 chunk size
50, // 增加 overlap
5, // minChunkSizeToKeep
500, // maxChunkSizeToKeep
true // keepSeparator
);
}
// 动态调整相似度阈值
public List<Document> smartSearch(String query) {
// 先尝试高阈值
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query(query)
.withTopK(5)
.withSimilarityThreshold(0.8)
);
// 如果结果太少,降低阈值
if (results.isEmpty()) {
results = vectorStore.similaritySearch(
SearchRequest.query(query)
.withTopK(10)
.withSimilarityThreshold(0.6)
);
}
return results;
}
坑 5:并发问题
现象:
- 多用户同时提问时数据混乱
- ChatMemory 线程不安全
解决方案:
@Service
public class ThreadSafeChatService {
// 使用 ConcurrentHashMap
private final ConcurrentHashMap<String, List<Message>> memoryMap =
new ConcurrentHashMap<>();
// 使用 synchronized 或 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
public String chat(String sessionId, String message) {
lock.lock();
try {
List<Message> history = memoryMap.computeIfAbsent(
sessionId, k -> new ArrayList<>()
);
// ... 业务逻辑
} finally {
lock.unlock();
}
}
}
坑 6:API Key 泄露
风险:
- 代码中硬编码 API Key
- 日志中打印敏感信息
解决方案:
# 使用环境变量
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY:}
// 日志脱敏
@Slf4j
public class SafeLogger {
public void logRequest(String prompt) {
String masked = prompt.replaceAll("sk-[a-zA-Z0-9]{20}", "sk-****");
log.info("Request: {}", masked);
}
}
坑 7:成本控制
现象:
- API 调用费用过高
- Token 使用量不可控
解决方案:
@Component
public class CostMonitor {
private final AtomicLong totalTokens = new AtomicLong(0);
private static final double PRICE_PER_1K_TOKENS = 0.002;
@EventListener
public void onTokenUsage(TokenUsageEvent event) {
long tokens = event.getTokens();
totalTokens.addAndGet(tokens);
double cost = (tokens / 1000.0) * PRICE_PER_1K_TOKENS;
log.info("本次调用消耗 {} tokens,花费 ${}", tokens, cost);
// 成本告警
if (totalTokens.get() > 1000000) {
alertService.send("Token 使用量超过 100 万!");
}
}
}
坑 8:中文支持问题
现象:
- 中文向量化效果差
- 中文搜索不准确
解决方案:
// 使用中文优化的模型
@Bean
public EmbeddingModel chineseEmbeddingModel() {
return new OpenAiEmbeddingModel(openAiApi,
OpenAiEmbeddingOptions.builder()
.withModel("text-embedding-3-small") // 或使用国内模型
.build());
}
// 中文分块优化
@Bean
public DocumentTransformer chineseTextSplitter() {
return new TokenTextSplitter(
200, // 中文可以适当减小 chunk size
30, // overlap
5,
300,
true
);
}
八、总结 + 面试加分点
8.1 核心知识点回顾
| 模块 | 核心概念 | 关键技术 |
|---|---|---|
| Spring AI | ChatClient | LLM 调用抽象 |
| 向量数据库 | VectorStore | 语义搜索存储 |
| RAG | 检索增强生成 | 文档检索 + LLM 生成 |
| Embedding | 文本向量化 | 相似度计算 |
| Prompt | 提示词工程 | 模板 + 变量替换 |
8.2 面试加分点
Q1:什么是 RAG?它解决了什么问题?
标准答案:
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合了信息检索和生成式 AI 的技术架构。它通过从外部知识库中检索相关文档,然后将这些文档作为上下文提供给 LLM,从而生成更准确、更专业的回答。
解决的问题:
- LLM 知识截止日期限制
- LLM 幻觉问题(编造内容)
- 企业私有数据无法利用
- 领域专业知识不足
Q2:向量数据库和传统数据库有什么区别?
标准答案:
核心区别:
- 存储内容:传统数据库存储结构化数据(数值、字符串),向量数据库存储高维向量(Embedding)
- 查询方式:传统数据库用精确匹配或 SQL 查询,向量数据库用相似度搜索(余弦相似度、欧氏距离)
- 应用场景:传统数据库用于事务处理,向量数据库用于语义搜索、推荐系统、AI 应用
类比: 传统数据库像查字典(精确匹配),向量数据库像搜索引擎(语义匹配)
Q3:Spring AI 和 LangChain 有什么区别?
标准答案:
Spring AI:
- 基于 Java/Spring 生态
- 与 Spring Boot 无缝集成
- 学习成本低(对 Java 开发者友好)
- 企业级支持更好
LangChain:
- 基于 Python
- 生态更丰富,社区更活跃
- 功能更完善(Agent、Tools 等)
- 适合快速原型开发
选择建议: 企业级项目用 Spring AI,快速实验用 LangChain
Q4:如何优化 RAG 的检索效果?
标准答案:
优化方向:
- 文档预处理:清洗、去噪、格式化
- 分块策略:合理设置 chunk size 和 overlap
- Embedding 模型:选择适合场景的模型(中文用中文优化模型)
- 检索策略:混合检索(向量 + 关键词)、重排序(Rerank)
- 相似度阈值:动态调整,平衡召回率和准确率
Q5:如何设计一个高可用的 AI 应用架构?
标准答案:
关键设计:
- 多模型支持:通过策略模式支持多个 LLM,故障自动切换
- 缓存层:缓存常见问题和 Embedding 结果,减少 API 调用
- 异步处理:使用消息队列处理耗时操作
- 限流熔断:防止 API 调用超限,保护系统稳定性
- 监控告警:监控响应时间、Token 使用量、错误率
- 降级策略:LLM 不可用时,返回缓存结果或兜底回复
8.3 学习路线建议
第1周:基础入门
├── Spring AI 基本概念
├── ChatClient 使用
└── 简单对话应用
第2周:向量数据库
├── Embedding 原理
├── VectorStore 使用
└── 语义搜索实现
第3周:RAG 实战
├── 文档上传与处理
├── 向量检索
└── 智能问答系统
第4周:进阶优化
├── Prompt 工程
├── 性能优化
└── 安全设计
第5周:架构设计
├── 企业级架构
├── 多模型支持
└── 监控告警
8.4 推荐资源
- 官方文档:https://docs.spring.io/spring-ai/reference/
- GitHub 仓库:https://github.com/spring-projects/spring-ai
- 向量数据库对比:https://vector-db-benchmark.com/
- Prompt 工程:https://www.promptingguide.ai/
8.5 结语
Spring AI 为 Java 开发者打开了 AI 应用开发的大门。通过本文的学习,你应该已经能够:
✅ 理解 Spring AI 的核心概念
✅ 独立开发基础的 AI 应用
✅ 实现 RAG 智能问答系统
✅ 设计企业级 AI 架构
✅ 在面试中自信地谈论 AI 技术
下一步行动:
- 搭建自己的 Spring AI 项目
- 实现一个真实的业务场景(如智能客服)
- 深入研究向量数据库和 Embedding
- 关注 Spring AI 的最新发展
AI 时代已来,Java 工程师也要跟上! 🚀
如果本文对你有帮助,请点赞、收藏、转发! 👍
相关文章推荐:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)