Skip to content

本轮 Spring AI 面试题库(带答案)

【面试题】1. Spring AI 的 Prompt 模板引擎如何使用?变量注入有哪些语法规则? ​

【标准答案】Spring AI 的 Prompt 模板基于 StringTemplate 引擎,支持两种变量语法:

  • {{变量名}}:标准占位符,自动从 Map 或对象属性注入
  • {变量名}:简化语法,适合快速原型

核心用法分三步:

java
// 1. 创建模板
PromptTemplate template = new PromptTemplate("你好{{name}},今天是{{date}}");

// 2. 准备数据
Map<String, Object> params = Map.of("name", "张三", "date", "周一");

// 3. 渲染生成 Prompt
Prompt prompt = template.render(params);

实际项目中的最佳实践:

  • 模板内容建议放在外部文件(如 prompts/xxx.st),便于运维调整
  • 复杂模板支持条件判断、循环等语法,避免在代码里拼接
  • 变量名要有业务含义,团队统一规范

面试加分点:提到 Prompt 模板和传统模板引擎(如 Thymeleaf)的区别——Prompt 模板专注于 LLM 场景,支持 Token 计数优化,避免无意义的格式字符浪费 Token。

【面试题】2. Spring AI 的 EmbeddingModel 如何选择?OpenAI 和本地模型各有什么优劣势? ​

【标准答案】EmbeddingModel 选型核心看三个维度:质量、成本、延迟。

OpenAI text-embedding-3 系列优势:

  • 质量稳定,在 MTEB 榜单上排名靠前
  • 支持维度缩减(从 1536 压缩到 256/512),节省向量库存储
  • 文档丰富,社区成熟

本地模型(如 Ollama + nomic-embed-text)优势:

  • 零成本调用,不依赖外网
  • 数据不出域,适合敏感场景
  • 延迟可控,适合实时性要求高的场景

劣势对比:

  • OpenAI:调用有成本(虽然 Embedding 比 Chat 便宜),国内网络不稳定需要代理
  • 本地模型:质量参差不齐,需要自己测评,部分模型中文效果一般

实际项目中的建议:

  • POC 阶段:直接用 OpenAI,快速验证
  • 生产环境:敏感数据走本地,非敏感数据可以 OpenAI
  • 成本敏感场景:先小规模测试本地模型质量,达标就切

Spring AI 切换只需要改配置:

yaml
spring:
  ai:
    openai:
      embedding:
        model: text-embedding-3-small
# 或
    ollama:
      embedding:
        model: nomic-embed-text

【面试题】3. Spring AI 的 VectorStore.similaritySearch() 支持哪些过滤条件?如何结合元数据做精准检索? ​

【标准答案】similaritySearch 方法除了向量相似度,还支持元数据过滤,这是实际项目中非常关键的能力。

核心用法:

java
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.query("用户问题")
        .withTopK(5)
        .withSimilarityThreshold(0.7)
        .withFilterExpression("category == 'tech' && year >= 2024")
);

过滤表达式支持的语法:

  • 比较操作:==, !=, >, <, >=, <=
  • 逻辑操作:&&, ||, !
  • 集合操作:in ['a', 'b'], not in ['c']
  • 字符串:contains 'keyword', startsWith 'prefix'

典型应用场景:

  • 权限控制:department == 'sales' && level <= 3
  • 时效性:publishDate >= '2024-01-01'
  • 多租户:tenantId == 'xxx'

面试加分点:强调元数据设计的重要性——索引文档时要规划好元数据字段,检索时才能精准过滤,避免海量向量库全表扫描影响性能。

【面试题】4. Spring AI 的流式响应(Streaming)如何实现?SSE 接口对接有哪些注意事项? ​

【标准答案】Spring AI 的 Streaming 响应基于 Flux 响应式流,底层对接各模型厂商的 SSE 协议。

核心代码:

java
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String question) {
    return chatClient.prompt()
        .user(question)
        .stream()
        .content();
}

前端对接 SSE 的关键点:

  1. 使用 EventSource 或 fetch + ReadableStream
  2. 正确处理 data: 前缀和 \n\n 分隔符
  3. 处理 [DONE] 结束标记
  4. 网络断线要有重连机制

生产环境的坑:

  • Nginx 默认会缓冲 SSE 响应,需要配置 proxy_buffering off
  • 流式响应的超时时间要设置长一些,避免中途断开
  • Token 计数在流式场景下要累加,不能只算最后一个 chunk

面试加分点:提到 Spring AI 的 stream() 和同步 call() 可以共享同一个 ChatClient 实例,切换成本低。还可以提一下 Flux 的背压(backpressure)机制,在高并发场景下避免客户端消费不过来。

【面试题】5. Spring AI 如何实现对话上下文的 Token 限制?避免历史消息撑爆上下文窗口? ​

【标准答案】这是 ChatMemory 的核心痛点——历史消息越多,Token 消耗越大,成本越高,还可能超出模型上下文窗口限制。

Spring AI 的解决方案有两种:

方案一:MessageChatMemoryAdvisor 的滑动窗口

java
ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, 10)) // 只保留最近10条
    .build();
  • 优点:简单,框架自动裁剪
  • 缺点:粗暴截断,可能丢失重要上下

方案二:自定义摘要策略

  • 将历史对话定期摘要压缩
  • 摘要结果作为 System Message 注入
  • 保留关键实体和决策信息

实际项目的推荐做法:

  1. 根据模型窗口设置合理上限(GPT-4 可以上浮到 20-30 条)
  2. 重要信息(如用户明确的指令)提取后单独存储
  3. 使用 TokenTextSplitter 计算 Token 数,动态调整保留条数
  4. 摘要策略作为备选,长期对话场景必上

面试加分点:提到 countTokens() 方法可以精确计算 Token 数,不同模型的 Tokenizer 不同,Spring AI 会自动选择对应模型的计数器。

【面试题】6. Spring AI 如何处理模型调用的失败重试和降级? ​

【标准答案】生产环境模型调用不稳定是常态,Spring AI 需要配合 Spring Retry 或自定义重试逻辑。

方案一:Spring Retry 注解

java
@Retryable(
    value = {ApiException.class, TimeoutException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String callModel(String prompt) {
    return chatClient.prompt().user(prompt).call().content();
}

方案二:多模型降级

java
@Recover
public String fallback(Exception e, String prompt) {
    // 主模型失败,切换到备用模型
    return fallbackChatClient.prompt().user(prompt).call().content();
}

实际项目的最佳实践:

  • 超时设置:spring.ai.openai.chat.options.timeout=30s
  • 降级策略:OpenAI 主 -> 通义/智谱备 -> 本地 Ollama 兜底
  • 熔断:配合 Resilience4j,连续失败超过阈值后直接返回兜底答案
  • 监控告警:失败率超过 5% 触发告警

面试加分点:提到函数调用(Function Calling)失败的重试策略——需要幂等设计,避免重复执行产生副作用。还可以提一下流式响应的重试更复杂,需要记录已返回的 Token 位置。

【面试题】7. Spring AI 如何实现文档的增量更新?VectorStore 的 add/delete 操作有什么注意事项? ​

【标准答案】RAG 系统中文档会持续更新,增量同步是核心能力。

增量更新的标准流程:

java
// 1. 生成文档唯一 ID(业务语义)
String docId = "doc_" + documentHash; // 基于 URL 或 内容 hash

// 2. 先删除旧版本
vectorStore.delete(List.of(docId));

// 3. 添加新版本
Document newDoc = new Document(docId, content, metadata);
vectorStore.add(List.of(newDoc));

注意事项:

  1. ID 设计:要用业务语义 ID,不要用随机 UUID,否则无法精准删除
  2. 删除再添加:VectorStore 一般不支持原地更新,先删后加是最稳的做法
  3. 批量操作:大量文档更新时分批处理,避免一次操作超时
  4. 元数据同步:更新内容时同步更新元数据(如 updateTime、version)

分布式场景的坑:

  • 多实例并发更新同一文档要用分布式锁
  • 向量库主从同步有延迟,更新后立即查询可能查不到旧数据

面试加分点:提到文档变更检测——可以用文件的 lastModified 或内容 hash 判断是否需要更新,避免全量同步浪费资源。

【面试题】8. Spring AI 如何与 Spring Security 集成?用户身份如何传递到 AI 调用链? ​

【标准答案】Spring AI 作为 AI 中间件,需要和业务系统的安全体系打通。

核心集成点:

  1. API Key 管理
java
@Configuration
public class AiSecurityConfig {
    @Bean
    public ChatClient securedChatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultAdvisors(new ApiKeyAuditAdvisor()) // 记录 API Key 使用
            .build();
    }
}
  1. 用户身份传递
  • 通过 Advisor 在 Prompt 中注入用户信息
java
public class UserContextAdvisor implements RequestResponseAdvisor {
    @Override
    public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
        String userId = SecurityContextHolder.getContext().getAuthentication().getName();
        String enhancedPrompt = request.userText() + "\n\n[当前用户: " + userId + "]";
        return AdvisedRequest.from(request).withUserText(enhancedPrompt).build();
    }
}
  1. 权限校验
  • Function Calling 的函数执行前校验权限
  • 向量库检索时按用户 ID 过滤

生产环境的最佳实践:

  • API Key 不要硬编码,用 Spring Cloud Config 或 Vault 管理
  • 敏感操作(如调用外部 API)记录审计日志
  • 多租户场景下,租户隔离要在 Advisor 层面强制注入

面试加分点:提到 Function Calling 的安全风险——LLM 可能被诱导调用恶意函数,需要在函数层做权限校验,不能只依赖 Prompt 约束。

【面试题】9. Spring AI 的 QuestionAnswerAdvisor 和传统 RAG 实现有什么区别?什么场景用哪个? ​

【标准答案】这是 Spring AI 1.0 引入的高级封装,简化了 RAG 的样板代码。

传统 RAG 实现步骤:

java
// 1. 切分文档
List<Document> chunks = textSplitter.split(document);

// 2. 生成 Embedding
List<float[]> embeddings = embeddingModel.embed(chunks);

// 3. 存入向量库
vectorStore.add(chunks);

// 4. 检索
List<Document> retrieved = vectorStore.similaritySearch(query);

// 5. 拼接 Prompt
String prompt = "根据以下文档回答问题:\n" + 
    retrieved.stream().map(Document::getContent).collect(Collectors.joining("\n")) +
    "\n\n问题:" + query;

// 6. 调用模型
String answer = chatClient.prompt().user(prompt).call().content();

QuestionAnswerAdvisor 简化后:

java
ChatClient client = ChatClient.builder(chatModel)
    .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
    .build();

String answer = client.prompt()
    .user(question)
    .call()
    .content();

适用场景:

  • QuestionAnswerAdvisor:标准 RAG 场景,快速落地,代码简洁
  • 传统实现:需要自定义检索逻辑、复杂过滤、多向量库融合等场景

面试加分点:提到 QuestionAnswerAdvisor 内部也是调用 similaritySearch,可以传入自定义参数覆盖默认行为。还可以提一下它的 Prompt 模板可以自定义,适应不同业务场景。

【面试题】10. Spring AI 项目如何做单元测试和集成测试?Mock 大模型调用有哪些策略? ​

【标准答案】AI 项目测试的核心挑战:大模型输出不确定、调用有成本。

方案一:Mock ChatModel

java
@MockBean
private ChatModel chatModel;

@Test
void testChatFlow() {
    // Mock 固定返回
    when(chatModel.call(any(Prompt.class)))
        .thenReturn(new ChatResponse("这是测试响应"));
    
    String result = chatService.chat("测试问题");
    assertThat(result).contains("测试");
}

方案二:使用本地模型测试

java
@SpringBootTest
@TestPropertySource(properties = {
    "spring.ai.ollama.base-url=http://localhost:11434",
    "spring.ai.ollama.chat.model=llama3"
})
class IntegrationTest {
    // 使用 Ollama 本地模型做集成测试
}

方案三:快照测试(Snapshot Testing)

  • 记录真实模型输出作为 golden master
  • 后续改动对比快照,检测意外变化
  • 适合检测 Prompt 调整的影响

测试分层的最佳实践:

  • 单元测试:Mock 所有外部依赖,测业务逻辑
  • 集成测试:用本地模型或 Mock,测流程串联
  • E2E 测试:用真实模型,测关键路径,频率控制在每日/每周

面试加分点:提到 Spring AI 的 TestChatModel 工具类,可以快速构造测试用的 ChatModel 实例。还可以提一下 Function Calling 的测试策略——单独测试函数逻辑,Mock LLM 调用,避免真实调用模型。