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 对话客户端

类比:就像 RestTemplateWebClient,专门用来调用 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,从而生成更准确、更专业的回答。

解决的问题:

  1. LLM 知识截止日期限制
  2. LLM 幻觉问题(编造内容)
  3. 企业私有数据无法利用
  4. 领域专业知识不足
Q2:向量数据库和传统数据库有什么区别?

标准答案:

核心区别:

  1. 存储内容:传统数据库存储结构化数据(数值、字符串),向量数据库存储高维向量(Embedding)
  2. 查询方式:传统数据库用精确匹配或 SQL 查询,向量数据库用相似度搜索(余弦相似度、欧氏距离)
  3. 应用场景:传统数据库用于事务处理,向量数据库用于语义搜索、推荐系统、AI 应用

类比: 传统数据库像查字典(精确匹配),向量数据库像搜索引擎(语义匹配)

Q3:Spring AI 和 LangChain 有什么区别?

标准答案:

Spring AI:

  • 基于 Java/Spring 生态
  • 与 Spring Boot 无缝集成
  • 学习成本低(对 Java 开发者友好)
  • 企业级支持更好

LangChain:

  • 基于 Python
  • 生态更丰富,社区更活跃
  • 功能更完善(Agent、Tools 等)
  • 适合快速原型开发

选择建议: 企业级项目用 Spring AI,快速实验用 LangChain

Q4:如何优化 RAG 的检索效果?

标准答案:

优化方向:

  1. 文档预处理:清洗、去噪、格式化
  2. 分块策略:合理设置 chunk size 和 overlap
  3. Embedding 模型:选择适合场景的模型(中文用中文优化模型)
  4. 检索策略:混合检索(向量 + 关键词)、重排序(Rerank)
  5. 相似度阈值:动态调整,平衡召回率和准确率
Q5:如何设计一个高可用的 AI 应用架构?

标准答案:

关键设计:

  1. 多模型支持:通过策略模式支持多个 LLM,故障自动切换
  2. 缓存层:缓存常见问题和 Embedding 结果,减少 API 调用
  3. 异步处理:使用消息队列处理耗时操作
  4. 限流熔断:防止 API 调用超限,保护系统稳定性
  5. 监控告警:监控响应时间、Token 使用量、错误率
  6. 降级策略: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 技术

下一步行动:

  1. 搭建自己的 Spring AI 项目
  2. 实现一个真实的业务场景(如智能客服)
  3. 深入研究向量数据库和 Embedding
  4. 关注 Spring AI 的最新发展

AI 时代已来,Java 工程师也要跟上! 🚀


如果本文对你有帮助,请点赞、收藏、转发! 👍


相关文章推荐:

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐