Skip to content

Spring AI 面试指南 ​

更新时间:2025-02

Spring AI 是 Spring 生态系统中用于构建 AI 应用的框架,提供了与多种 AI 模型和向量数据库集成的抽象层。本文档基于 Spring AI 1.1+ 版本。

目录 ​

一、Spring AI 核心概念 ​

1.1 什么是 Spring AI? ​

Spring AI 是一个用于简化 AI 应用开发的框架,旨在将企业数据和 API 与 AI 模型连接起来。

核心特点:

  • 🔌 可移植的 API:支持多种 AI 提供商(OpenAI、Anthropic、Google、AWS、Azure、Ollama 等)
  • 🗄️ 向量数据库支持:集成主流向量数据库(Pinecone、Milvus、Chroma、PGVector 等)
  • 🛠️ 工具调用:支持 Function Calling,让模型调用客户端函数
  • 📊 可观测性:提供 AI 操作的监控和追踪
  • 🚀 Spring Boot 集成:自动配置和 Starter 支持
  • 💬 ChatClient API:流式 API,类似 WebClient/RestClient

适用场景:

  • 智能客服和聊天机器人
  • 文档问答系统(RAG)
  • 内容生成和摘要
  • 代码助手和开发工具
  • 数据分析和洞察

1.2 Spring AI 架构 ​

┌─────────────────────────────────────────────────────────┐
│                   Spring AI Application                  │
├─────────────────────────────────────────────────────────┤
│  ChatClient API  │  Advisors  │  Prompt Templates       │
├─────────────────────────────────────────────────────────┤
│  Chat Models  │  Embedding  │  Image Gen  │  Audio      │
├─────────────────────────────────────────────────────────┤
│  OpenAI │ Anthropic │ Google │ AWS │ Azure │ Ollama     │
├─────────────────────────────────────────────────────────┤
│  Vector Stores: Pinecone, Milvus, Chroma, PGVector...   │
└─────────────────────────────────────────────────────────┘

核心组件:

  1. ChatClient API:流式 API,用于与 AI 模型交互
  2. Advisors:拦截器模式,用于增强 AI 交互(如 RAG、记忆管理)
  3. Prompt Templates:提示词模板,支持占位符和参数化
  4. Chat Models:聊天模型抽象,支持多种 AI 提供商
  5. Embedding Models:文本嵌入模型,用于向量化文本
  6. Vector Stores:向量数据库,用于存储和检索文档向量

二、快速开始 ​

2.1 项目依赖 ​

xml
<dependencies>
    <!-- Spring AI OpenAI Starter -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.1.0</version>
    </dependency>
    
    <!-- Spring AI PGVector Store -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
        <version>1.1.0</version>
    </dependency>
</dependencies>

2.2 配置文件 ​

yaml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
          temperature: 0.7
      embedding:
        options:
          model: text-embedding-3-small

2.3 简单聊天示例 ​

java
@Service
public class ChatService {
    private final ChatClient chatClient;
    
    public ChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    
    public String chat(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }
}

三、ChatClient API 详解 ​

3.1 核心方法链 ​

java
chatClient.prompt()           // 创建提示
  .system(...)                // 设置系统消息
  .user(...)                  // 设置用户消息
  .options(...)               // 设置模型选项
  .advisors(...)              // 添加顾问
  .call()                     // 同步调用
  .stream()                   // 流式调用
  .content()                  // 获取内容
  .entity(Class)              // 转换为实体

3.2 系统消息和角色定义 ​

java
public String chatWithRole(String message) {
    return chatClient.prompt()
        .system("你是一位资深的 Java 架构师")
        .user(message)
        .call()
        .content();
}

3.3 模型参数配置 ​

java
public String chatWithOptions(String message) {
    return chatClient.prompt()
        .user(message)
        .options(OpenAiChatOptions.builder()
            .withModel("gpt-4-turbo")
            .withTemperature(0.8)      // 创造性(0-2)
            .withMaxTokens(2000)       // 最大令牌数
            .withTopP(0.9)             // 核采样
            .build())
        .call()
        .content();
}

3.4 结构化输出 ​

java
public Product extractProduct(String description) {
    return chatClient.prompt()
        .user("从以下描述中提取产品信息:" + description)
        .call()
        .entity(Product.class);
}

record Product(String name, String category, double price) {}

3.5 流式响应 ​

java
public Flux<String> chatStream(String message) {
    return chatClient.prompt()
        .user(message)
        .stream()
        .content();
}

四、Function Calling(工具调用) ​

4.1 什么是 Function Calling? ​

Function Calling 允许 AI 模型调用客户端定义的函数,获取实时信息或执行操作。

工作流程:

用户提问 → AI 判断需要调用函数 → 返回函数调用请求
→ 客户端执行函数 → 将结果返回给 AI → AI 生成最终答案

4.2 定义函数 ​

java
@Configuration
public class FunctionConfig {
    
    @Bean
    @Description("获取指定城市的当前天气信息")
    public Function<WeatherRequest, WeatherResponse> getWeather() {
        return request -> {
            // 调用天气 API
            return new WeatherResponse(
                request.city(),
                "晴天",
                25.0
            );
        };
    }
    
    @Bean
    @Description("执行数学计算")
    public Function<CalculatorRequest, CalculatorResponse> calculator() {
        return request -> {
            double result = switch (request.operation()) {
                case "add" -> request.a() + request.b();
                case "multiply" -> request.a() * request.b();
                default -> 0.0;
            };
            return new CalculatorResponse(result);
        };
    }
}

record WeatherRequest(String city) {}
record WeatherResponse(String city, String condition, double temperature) {}
record CalculatorRequest(String operation, double a, double b) {}
record CalculatorResponse(double result) {}

4.3 使用 Function Calling ​

java
@Service
public class FunctionCallingService {
    private final ChatClient chatClient;
    
    public FunctionCallingService(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultFunctions("getWeather", "calculator")
            .build();
    }
    
    public String ask(String question) {
        // 用户问:"北京今天天气怎么样?"
        // AI 会自动调用 getWeather 函数
        return chatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

五、RAG(检索增强生成) ​

5.1 什么是 RAG? ​

RAG(Retrieval-Augmented Generation) 通过从知识库中检索相关文档,增强 AI 模型的回答能力。

RAG 工作流程:

文档加载 → 文本分割 → 生成 Embedding → 存储到向量数据库
                                        ↓
用户提问 → 生成问题 Embedding → 向量相似度搜索 → 检索相关文档
                                        ↓
将文档作为上下文 → AI 生成答案

5.2 文档加载和处理 ​

java
@Service
public class DocumentService {
    
    public List<Document> loadPdfFile(Resource resource) {
        var reader = new PdfDocumentReader(resource);
        return reader.get();
    }
    
    public List<Document> splitDocuments(List<Document> documents) {
        var splitter = new TokenTextSplitter(
            500,  // 每个块的最大 token 数
            100   // 块之间的重叠 token 数
        );
        return splitter.apply(documents);
    }
}

5.3 向量存储 ​

java
@Service
public class VectorStoreService {
    private final VectorStore vectorStore;
    
    public VectorStoreService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }
    
    public void addDocuments(List<Document> documents) {
        vectorStore.add(documents);
    }
    
    public List<Document> similaritySearch(String query) {
        return vectorStore.similaritySearch(
            SearchRequest.query(query).withTopK(5)
        );
    }
}

5.4 RAG 实现 ​

java
@Service
public class RagService {
    private final ChatClient chatClient;
    private final VectorStore vectorStore;
    
    public RagService(ChatClient.Builder builder, VectorStore vectorStore) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }
    
    public String query(String question) {
        // 1. 检索相关文档
        var documents = vectorStore.similaritySearch(
            SearchRequest.query(question).withTopK(3)
        );
        
        // 2. 构建上下文
        String context = documents.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n"));
        
        // 3. 生成答案
        return chatClient.prompt()
            .system("根据以下上下文回答问题:\n" + context)
            .user(question)
            .call()
            .content();
    }
    
    // 使用 QuestionAnswerAdvisor 简化
    public String queryWithAdvisor(String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(new QuestionAnswerAdvisor(vectorStore))
            .call()
            .content();
    }
}

5.5 完整的 RAG 应用 ​

java
@RestController
@RequestMapping("/api/rag")
public class RagController {
    private final DocumentService documentService;
    private final VectorStoreService vectorStoreService;
    private final RagService ragService;
    
    @PostMapping("/upload")
    public String uploadDocument(@RequestParam("file") MultipartFile file) {
        Resource resource = new ByteArrayResource(file.getBytes());
        var documents = documentService.loadPdfFile(resource);
        documents = documentService.splitDocuments(documents);
        vectorStoreService.addDocuments(documents);
        return "文档已上传";
    }
    
    @PostMapping("/query")
    public String query(@RequestBody String question) {
        return ragService.query(question);
    }
}

六、Embedding 模型 ​

6.1 什么是 Embedding? ​

Embedding 是将文本转换为高维向量的过程,用于表示文本的语义信息。

6.2 使用 Embedding 模型 ​

java
@Service
public class EmbeddingService {
    private final EmbeddingModel embeddingModel;
    
    public EmbeddingService(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }
    
    public List<Double> embed(String text) {
        var response = embeddingModel.embed(text);
        return response;
    }
    
    public List<List<Double>> embedBatch(List<String> texts) {
        var response = embeddingModel.embed(texts);
        return response.stream()
            .map(EmbeddingResponse.Embedding::getOutput)
            .toList();
    }
}

6.3 计算相似度 ​

java
public double cosineSimilarity(List<Double> vec1, List<Double> vec2) {
    double dotProduct = 0.0;
    double norm1 = 0.0;
    double norm2 = 0.0;
    
    for (int i = 0; i < vec1.size(); i++) {
        dotProduct += vec1.get(i) * vec2.get(i);
        norm1 += vec1.get(i) * vec1.get(i);
        norm2 += vec2.get(i) * vec2.get(i);
    }
    
    return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}

七、Advisors(顾问) ​

7.1 什么是 Advisors? ​

Advisors 是拦截器模式,用于在 AI 交互前后执行额外逻辑。

7.2 内置 Advisors ​

java
// 1. QuestionAnswerAdvisor - RAG 支持
chatClient.prompt()
    .user(question)
    .advisors(new QuestionAnswerAdvisor(vectorStore))
    .call()
    .content();

// 2. MessageChatMemoryAdvisor - 对话记忆
chatClient.prompt()
    .user(message)
    .advisors(new MessageChatMemoryAdvisor(chatMemory))
    .call()
    .content();

// 3. PromptChatMemoryAdvisor - 提示词记忆
chatClient.prompt()
    .user(message)
    .advisors(new PromptChatMemoryAdvisor(chatMemory))
    .call()
    .content();

7.3 自定义 Advisor ​

java
public class LoggingAdvisor implements RequestResponseAdvisor {
    
    @Override
    public AdvisedRequest adviseRequest(
            AdvisedRequest request, 
            Map<String, Object> context) {
        log.info("Request: {}", request.userText());
        return request;
    }
    
    @Override
    public ChatResponse adviseResponse(
            ChatResponse response, 
            Map<String, Object> context) {
        log.info("Response: {}", response.getResult().getOutput().getContent());
        return response;
    }
}

// 使用自定义 Advisor
chatClient.prompt()
    .user(message)
    .advisors(new LoggingAdvisor())
    .call()
    .content();

八、多模型支持 ​

8.1 支持的 AI 提供商 ​

提供商ChatEmbeddingImageAudio
OpenAI✅✅✅✅
Anthropic (Claude)✅❌❌❌
Google (Gemini)✅✅❌❌
AWS Bedrock✅✅✅❌
Azure OpenAI✅✅✅✅
Ollama (本地)✅✅❌❌

8.2 切换模型提供商 ​

yaml
# OpenAI
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat.options.model: gpt-4

# Anthropic Claude
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat.options.model: claude-3-opus-20240229

# Ollama (本地)
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat.options.model: llama2

8.3 图像生成 ​

java
@Service
public class ImageService {
    private final ImageModel imageModel;
    
    public ImageService(ImageModel imageModel) {
        this.imageModel = imageModel;
    }
    
    public String generateImage(String prompt) {
        var response = imageModel.call(
            new ImagePrompt(prompt,
                ImageOptions.builder()
                    .withModel("dall-e-3")
                    .withWidth(1024)
                    .withHeight(1024)
                    .build())
        );
        return response.getResult().getOutput().getUrl();
    }
}

九、向量数据库集成 ​

9.1 支持的向量数据库 ​

  • PGVector (PostgreSQL)
  • Pinecone
  • Milvus
  • Chroma
  • Qdrant
  • Weaviate
  • Redis
  • MongoDB Atlas
  • Elasticsearch

9.2 PGVector 配置 ​

yaml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vectordb
    username: postgres
    password: password
  ai:
    vectorstore:
      pgvector:
        dimensions: 1536
        distance-type: COSINE_DISTANCE
sql
-- 创建向量扩展
CREATE EXTENSION IF NOT EXISTS vector;

-- 创建向量表
CREATE TABLE vector_store (
    id UUID PRIMARY KEY,
    content TEXT,
    metadata JSONB,
    embedding vector(1536)
);

-- 创建索引
CREATE INDEX ON vector_store USING ivfflat (embedding vector_cosine_ops);

9.3 Pinecone 配置 ​

yaml
spring:
  ai:
    vectorstore:
      pinecone:
        api-key: ${PINECONE_API_KEY}
        environment: us-east-1
        index-name: my-index
        namespace: default

十、面试题精选 ​

10.1 基础题 ​

Q1: Spring AI 的核心优势是什么?

答案:

  1. 可移植性:统一的 API 抽象,轻松切换 AI 提供商
  2. Spring 生态集成:无缝集成 Spring Boot、Spring Cloud
  3. 企业级特性:可观测性、安全性、事务支持
  4. RAG 支持:内置向量数据库集成和文档处理
  5. Function Calling:支持工具调用,扩展 AI 能力

Q2: ChatClient 和直接调用 ChatModel 有什么区别?

答案:

  • ChatClient:流式 API,支持链式调用,更易用
  • ChatModel:底层 API,更灵活但代码冗长
  • 推荐使用 ChatClient,除非需要底层控制
java
// ChatClient (推荐)
chatClient.prompt().user("Hello").call().content();

// ChatModel (底层)
chatModel.call(new Prompt("Hello")).getResult().getOutput().getContent();

Q3: 如何实现流式响应?

答案:

java
public Flux<String> chatStream(String message) {
    return chatClient.prompt()
        .user(message)
        .stream()
        .content();
}

// 在 Controller 中使用
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String message) {
    return chatService.chatStream(message);
}

Q4: 什么是 Embedding?如何使用?

答案: Embedding 是将文本转换为向量的过程,用于表示语义信息。

java
@Service
public class EmbeddingService {
    private final EmbeddingModel embeddingModel;
    
    public List<Double> embed(String text) {
        return embeddingModel.embed(text);
    }
}

应用场景:

  • 语义搜索
  • 文档相似度计算
  • RAG 系统
  • 推荐系统

Q5: 如何配置多个 AI 提供商?

答案:

java
@Configuration
public class AiConfig {
    
    @Bean
    @Primary
    public ChatClient openAiChatClient(
            @Qualifier("openAiChatModel") ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
    
    @Bean
    public ChatClient claudeChatClient(
            @Qualifier("anthropicChatModel") ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

10.2 进阶题 ​

Q6: 解释 RAG 的工作原理和实现步骤

答案:

工作原理:

  1. 离线阶段:

    • 加载文档 → 分割文本 → 生成 Embedding → 存储到向量数据库
  2. 在线阶段:

    • 用户提问 → 生成问题 Embedding → 向量相似度搜索
    • 检索相关文档 → 作为上下文传给 LLM → 生成答案

实现步骤:

java
// 1. 文档处理
var documents = documentReader.get();
documents = textSplitter.apply(documents);
vectorStore.add(documents);

// 2. 查询
var relevantDocs = vectorStore.similaritySearch(
    SearchRequest.query(question).withTopK(3)
);

// 3. 生成答案
String context = relevantDocs.stream()
    .map(Document::getContent)
    .collect(Collectors.joining("\n\n"));

return chatClient.prompt()
    .system("根据以下上下文回答:\n" + context)
    .user(question)
    .call()
    .content();

Q7: Function Calling 的实现原理是什么?

答案:

实现原理:

  1. 定义函数并注册到 Spring 容器
  2. AI 模型接收用户问题,判断是否需要调用函数
  3. 如果需要,返回函数调用请求(JSON 格式)
  4. Spring AI 解析请求,调用对应的 Java 函数
  5. 将函数返回值传回 AI 模型
  6. AI 模型基于函数结果生成最终答案

关键点:

  • 使用 @Description 注解描述函数功能
  • 函数参数和返回值必须是可序列化的
  • AI 模型会自动决定何时调用函数

Q8: 如何实现对话记忆?

答案:

java
@Service
public class ConversationService {
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    
    public ConversationService(
            ChatClient.Builder builder,
            ChatMemory chatMemory) {
        this.chatMemory = chatMemory;
        this.chatClient = builder
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
            .build();
    }
    
    public String chat(String conversationId, String message) {
        return chatClient.prompt()
            .user(message)
            .advisors(advisor -> advisor
                .param("chat_memory_conversation_id", conversationId))
            .call()
            .content();
    }
}

Q9: 如何优化 RAG 系统的性能?

答案:

优化策略:

  1. 文本分割优化:

    • 合理设置 chunk size(500-1000 tokens)
    • 设置 overlap 避免语义断裂(100-200 tokens)
  2. 检索优化:

    • 使用混合检索(向量 + 关键词)
    • 调整 topK 参数(3-5 个文档)
    • 设置相似度阈值过滤低质量文档
  3. 上下文优化:

    • 重排序(Reranking)提升相关性
    • 压缩上下文减少 token 消耗
    • 使用元数据过滤
  4. 缓存策略:

    • 缓存常见问题的答案
    • 缓存 Embedding 结果

Q10: 如何处理 AI 响应的错误和异常?

答案:

java
@Service
public class SafeChatService {
    private final ChatClient chatClient;
    
    public String chat(String message) {
        try {
            return chatClient.prompt()
                .user(message)
                .call()
                .content();
        } catch (OpenAiApiException e) {
            // API 错误(如配额超限)
            log.error("OpenAI API error", e);
            return "抱歉,服务暂时不可用";
        } catch (Exception e) {
            // 其他错误
            log.error("Unexpected error", e);
            return "抱歉,处理您的请求时出错";
        }
    }
    
    // 使用 Retry 机制
    @Retryable(
        value = {OpenAiApiException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000)
    )
    public String chatWithRetry(String message) {
        return chatClient.prompt().user(message).call().content();
    }
}

10.3 高级题 ​

Q11: 如何实现多租户 RAG 系统?

答案:

java
@Service
public class MultiTenantRagService {
    private final VectorStore vectorStore;
    private final ChatClient chatClient;
    
    public String query(String tenantId, String question) {
        // 使用元数据过滤实现租户隔离
        var documents = vectorStore.similaritySearch(
            SearchRequest.query(question)
                .withTopK(3)
                .withFilterExpression("tenantId == '" + tenantId + "'")
        );
        
        String context = documents.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n"));
        
        return chatClient.prompt()
            .system("根据以下上下文回答:\n" + context)
            .user(question)
            .call()
            .content();
    }
    
    public void addDocument(String tenantId, Document document) {
        document.getMetadata().put("tenantId", tenantId);
        vectorStore.add(List.of(document));
    }
}

Q12: 如何实现 AI 响应的可观测性?

答案:

java
@Configuration
public class ObservabilityConfig {
    
    @Bean
    public ObservationRegistry observationRegistry() {
        return ObservationRegistry.create();
    }
    
    @Bean
    public ChatClient observableChatClient(
            ChatClient.Builder builder,
            ObservationRegistry registry) {
        return builder
            .observationRegistry(registry)
            .build();
    }
}

// 自定义观察
@Service
public class ObservableChatService {
    private final ChatClient chatClient;
    private final ObservationRegistry registry;
    
    public String chat(String message) {
        return Observation.createNotStarted("ai.chat", registry)
            .lowCardinalityKeyValue("model", "gpt-4")
            .observe(() -> 
                chatClient.prompt().user(message).call().content()
            );
    }
}

Q13: 如何实现 Prompt 模板管理?

答案:

java
@Service
public class PromptTemplateService {
    private final ChatClient chatClient;
    
    // 使用 PromptTemplate
    public String generateWithTemplate(Map<String, Object> params) {
        var template = new PromptTemplate(
            "你是一位{role},请{action}关于{topic}的内容"
        );
        
        var prompt = template.create(params);
        return chatClient.prompt(prompt).call().content();
    }
    
    // 从文件加载模板
    public String generateFromFile(String templatePath, Map<String, Object> params) {
        var resource = new ClassPathResource(templatePath);
        var template = new PromptTemplate(resource);
        var prompt = template.create(params);
        return chatClient.prompt(prompt).call().content();
    }
}

// templates/article.st
// 你是一位{role}的技术作家。
// 请写一篇关于{topic}的文章,风格要{style}。
// 字数要求:{wordCount}字。

Q14: 如何实现 AI 响应的内容审核?

答案:

java
@Service
public class ModerationService {
    private final ModerationModel moderationModel;
    
    public boolean isSafe(String content) {
        var response = moderationModel.call(
            new ModerationPrompt(content)
        );
        return !response.getResult().isFlagged();
    }
}

@Service
public class SafeChatService {
    private final ChatClient chatClient;
    private final ModerationService moderationService;
    
    public String chat(String message) {
        // 1. 审核用户输入
        if (!moderationService.isSafe(message)) {
            return "您的输入包含不当内容";
        }
        
        // 2. 生成响应
        String response = chatClient.prompt()
            .user(message)
            .call()
            .content();
        
        // 3. 审核 AI 响应
        if (!moderationService.isSafe(response)) {
            return "抱歉,无法生成合适的回答";
        }
        
        return response;
    }
}

Q15: 如何实现混合检索(Hybrid Search)?

答案:

java
@Service
public class HybridSearchService {
    private final VectorStore vectorStore;
    private final FullTextSearchService fullTextSearch;
    
    public List<Document> hybridSearch(String query, int topK) {
        // 1. 向量检索
        var vectorResults = vectorStore.similaritySearch(
            SearchRequest.query(query).withTopK(topK * 2)
        );
        
        // 2. 全文检索
        var fullTextResults = fullTextSearch.search(query, topK * 2);
        
        // 3. 合并和重排序(RRF - Reciprocal Rank Fusion)
        Map<String, Double> scores = new HashMap<>();
        
        for (int i = 0; i < vectorResults.size(); i++) {
            var doc = vectorResults.get(i);
            scores.merge(doc.getId(), 1.0 / (i + 60), Double::sum);
        }
        
        for (int i = 0; i < fullTextResults.size(); i++) {
            var doc = fullTextResults.get(i);
            scores.merge(doc.getId(), 1.0 / (i + 60), Double::sum);
        }
        
        // 4. 按分数排序并返回 topK
        return scores.entrySet().stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .limit(topK)
            .map(entry -> findDocumentById(entry.getKey()))
            .toList();
    }
}

十一、最佳实践 ​

11.1 Prompt 工程 ​

java
// ❌ 不好的做法
String response = chatClient.prompt()
    .user("写一篇文章")
    .call()
    .content();

// ✅ 好的做法
String response = chatClient.prompt()
    .system("""
        你是一位资深的技术作家,擅长将复杂的技术概念
        用简单易懂的语言解释给读者。
        """)
    .user("""
        请写一篇关于 Spring AI 的技术文章,要求:
        1. 字数 1000 字左右
        2. 包含代码示例
        3. 面向 Java 开发者
        4. 风格专业但易懂
        """)
    .call()
    .content();

11.2 错误处理 ​

java
@Service
public class RobustChatService {
    private final ChatClient chatClient;
    
    @Retryable(
        value = {OpenAiApiException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public String chat(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }
    
    @Recover
    public String recover(OpenAiApiException e, String message) {
        log.error("Failed after retries", e);
        return "服务暂时不可用,请稍后再试";
    }
}

11.3 性能优化 ​

java
@Service
public class OptimizedChatService {
    private final ChatClient chatClient;
    private final Cache cache;
    
    // 1. 缓存常见问题
    @Cacheable(value = "ai-responses", key = "#message")
    public String chat(String message) {
        return chatClient.prompt().user(message).call().content();
    }
    
    // 2. 批量处理
    public List<String> chatBatch(List<String> messages) {
        return messages.parallelStream()
            .map(this::chat)
            .toList();
    }
    
    // 3. 异步处理
    @Async
    public CompletableFuture<String> chatAsync(String message) {
        return CompletableFuture.completedFuture(
            chatClient.prompt().user(message).call().content()
        );
    }
}

11.4 安全性 ​

java
@Configuration
public class SecurityConfig {
    
    // 1. API Key 管理
    @Bean
    public ChatClient secureChatClient(
            @Value("${spring.ai.openai.api-key}") String apiKey) {
        // 不要硬编码 API Key
        return ChatClient.builder(chatModel).build();
    }
    
    // 2. 速率限制
    @Bean
    public RateLimiter rateLimiter() {
        return RateLimiter.create(10.0); // 每秒 10 个请求
    }
}

@Service
public class SecureChatService {
    private final ChatClient chatClient;
    private final RateLimiter rateLimiter;
    
    public String chat(String message) {
        // 速率限制
        rateLimiter.acquire();
        
        // 输入验证
        if (message.length() > 10000) {
            throw new IllegalArgumentException("消息过长");
        }
        
        return chatClient.prompt().user(message).call().content();
    }
}

11.5 成本控制 ​

java
@Service
public class CostAwareChatService {
    private final ChatClient chatClient;
    
    public String chat(String message) {
        return chatClient.prompt()
            .user(message)
            .options(OpenAiChatOptions.builder()
                .withModel("gpt-3.5-turbo")  // 使用更便宜的模型
                .withMaxTokens(500)          // 限制输出长度
                .build())
            .call()
            .content();
    }
    
    // 根据任务选择模型
    public String chatSmart(String message, TaskComplexity complexity) {
        String model = switch (complexity) {
            case SIMPLE -> "gpt-3.5-turbo";
            case MEDIUM -> "gpt-4";
            case COMPLEX -> "gpt-4-turbo";
        };
        
        return chatClient.prompt()
            .user(message)
            .options(OpenAiChatOptions.builder()
                .withModel(model)
                .build())
            .call()
            .content();
    }
}

enum TaskComplexity { SIMPLE, MEDIUM, COMPLEX }

十二、常见问题 ​

12.1 如何选择合适的模型? ​

场景推荐模型原因
简单问答GPT-3.5-turbo成本低,速度快
复杂推理GPT-4推理能力强
代码生成GPT-4-turbo上下文窗口大
本地部署Ollama (Llama2)数据隐私
长文本处理Claude-3-opus200K 上下文

12.2 如何选择向量数据库? ​

数据库优势适用场景
PGVector与 PostgreSQL 集成已有 PG 数据库
Pinecone托管服务,易用快速原型开发
Milvus高性能,可扩展大规模生产环境
Chroma轻量级,易部署小型项目
Redis内存存储,极快实时应用

12.3 RAG 系统的常见问题 ​

问题 1:检索到的文档不相关

  • 原因:Embedding 模型不合适,chunk size 不当
  • 解决:使用更好的 Embedding 模型,调整分割策略

问题 2:答案不准确

  • 原因:上下文不足,Prompt 设计不当
  • 解决:增加 topK,优化 Prompt,使用 Reranking

问题 3:响应速度慢

  • 原因:向量检索慢,LLM 调用慢
  • 解决:优化索引,使用缓存,选择更快的模型

12.4 成本优化建议 ​

  1. 使用缓存:缓存常见问题的答案
  2. 选择合适的模型:简单任务用 GPT-3.5
  3. 限制输出长度:设置 maxTokens
  4. 批量处理:合并多个请求
  5. 使用本地模型:Ollama 用于开发测试

十三、参考资料 ​

13.1 官方资源 ​

13.2 AI 提供商文档 ​

13.3 向量数据库文档 ​

13.4 学习路线 ​

初级(1-2 周):

  1. 理解 AI 基础概念(LLM、Embedding、RAG)
  2. 学习 Spring AI 核心 API(ChatClient、Embedding)
  3. 完成简单的聊天应用

中级(2-4 周):

  1. 掌握 Function Calling
  2. 实现 RAG 系统
  3. 学习 Advisors 和 Prompt 工程
  4. 集成向量数据库

高级(1-2 个月):

  1. 多模型支持和切换
  2. 性能优化和成本控制
  3. 可观测性和监控
  4. 生产环境部署

13.5 实战项目推荐 ​

  1. 智能客服系统:

    • ChatClient + Function Calling
    • 集成企业知识库(RAG)
    • 多轮对话记忆
  2. 文档问答系统:

    • PDF/Word 文档解析
    • 向量数据库存储
    • 语义搜索和问答
  3. 代码助手:

    • 代码生成和解释
    • 代码审查和优化建议
    • 集成 IDE
  4. 内容生成平台:

    • 文章生成
    • 图像生成
    • 多语言翻译

十四、面试技巧 ​

14.1 回答框架 ​

STAR 法则:

  • S (Situation):描述场景
  • T (Task):说明任务
  • A (Action):解释行动
  • R (Result):展示结果

示例:

"在我们的项目中(S),需要构建一个智能客服系统(T)。我使用 Spring AI 的 ChatClient 和 RAG 技术(A),实现了基于企业知识库的自动问答,将客服响应时间从 5 分钟降低到 10 秒(R)。"

14.2 加分项 ​

  1. 实战经验:

    • 展示实际项目经验
    • 分享遇到的问题和解决方案
  2. 技术深度:

    • 理解底层原理(Embedding、向量检索)
    • 掌握性能优化技巧
  3. 最佳实践:

    • 成本控制意识
    • 安全性考虑
    • 可观测性设计
  4. 持续学习:

    • 关注 AI 技术发展
    • 了解最新的模型和工具

14.3 高频问题 ​

  1. Spring AI 和 LangChain 的区别?
  2. 如何实现 RAG 系统?
  3. Function Calling 的应用场景?
  4. 如何优化 AI 应用的性能?
  5. 如何控制 AI 应用的成本?
  6. 向量数据库的选择标准?
  7. 如何处理 AI 响应的幻觉问题?
  8. 多租户 RAG 系统如何实现?

十五、总结 ​

Spring AI 是 Java 生态系统中构建 AI 应用的强大框架,它提供了:

✅ 统一的 API 抽象:轻松切换 AI 提供商 ✅ 完整的 RAG 支持:从文档处理到向量检索 ✅ 企业级特性:可观测性、安全性、事务支持 ✅ Spring 生态集成:无缝集成 Spring Boot、Spring Cloud ✅ 丰富的功能:Function Calling、Advisors、Prompt Templates

核心能力:

  • ChatClient API 用于聊天交互
  • Embedding 模型用于文本向量化
  • Vector Store 用于向量存储和检索
  • Function Calling 用于工具调用
  • Advisors 用于增强 AI 交互

最佳实践:

  • 合理选择模型和向量数据库
  • 优化 Prompt 设计
  • 实现错误处理和重试机制
  • 关注性能和成本
  • 重视安全性和可观测性

学习建议:

  1. 从简单的聊天应用开始
  2. 逐步学习 RAG 和 Function Calling
  3. 实践完整的项目
  4. 关注最新技术发展

💡 提示:本文档基于 Spring AI 1.1+ 版本编写,内容来源于官方文档和实战经验。建议结合官方文档和示例项目深入学习。

📚 相关文档:

基于 VitePress 构建