本轮 Spring AI 面试题库(带答案)
【面试题】1. Spring AI 的 Prompt 模板引擎如何使用?变量注入有哪些语法规则?
【标准答案】Spring AI 的 Prompt 模板基于 StringTemplate 引擎,支持两种变量语法:
{{变量名}}:标准占位符,自动从 Map 或对象属性注入{变量名}:简化语法,适合快速原型
核心用法分三步:
// 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 切换只需要改配置:
spring:
ai:
openai:
embedding:
model: text-embedding-3-small
# 或
ollama:
embedding:
model: nomic-embed-text【面试题】3. Spring AI 的 VectorStore.similaritySearch() 支持哪些过滤条件?如何结合元数据做精准检索?
【标准答案】similaritySearch 方法除了向量相似度,还支持元数据过滤,这是实际项目中非常关键的能力。
核心用法:
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 协议。
核心代码:
@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 的关键点:
- 使用
EventSource或fetch+ReadableStream - 正确处理
data:前缀和\n\n分隔符 - 处理
[DONE]结束标记 - 网络断线要有重连机制
生产环境的坑:
- Nginx 默认会缓冲 SSE 响应,需要配置
proxy_buffering off - 流式响应的超时时间要设置长一些,避免中途断开
- Token 计数在流式场景下要累加,不能只算最后一个 chunk
面试加分点:提到 Spring AI 的 stream() 和同步 call() 可以共享同一个 ChatClient 实例,切换成本低。还可以提一下 Flux 的背压(backpressure)机制,在高并发场景下避免客户端消费不过来。
【面试题】5. Spring AI 如何实现对话上下文的 Token 限制?避免历史消息撑爆上下文窗口?
【标准答案】这是 ChatMemory 的核心痛点——历史消息越多,Token 消耗越大,成本越高,还可能超出模型上下文窗口限制。
Spring AI 的解决方案有两种:
方案一:MessageChatMemoryAdvisor 的滑动窗口
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, 10)) // 只保留最近10条
.build();- 优点:简单,框架自动裁剪
- 缺点:粗暴截断,可能丢失重要上下
方案二:自定义摘要策略
- 将历史对话定期摘要压缩
- 摘要结果作为 System Message 注入
- 保留关键实体和决策信息
实际项目的推荐做法:
- 根据模型窗口设置合理上限(GPT-4 可以上浮到 20-30 条)
- 重要信息(如用户明确的指令)提取后单独存储
- 使用 TokenTextSplitter 计算 Token 数,动态调整保留条数
- 摘要策略作为备选,长期对话场景必上
面试加分点:提到 countTokens() 方法可以精确计算 Token 数,不同模型的 Tokenizer 不同,Spring AI 会自动选择对应模型的计数器。
【面试题】6. Spring AI 如何处理模型调用的失败重试和降级?
【标准答案】生产环境模型调用不稳定是常态,Spring AI 需要配合 Spring Retry 或自定义重试逻辑。
方案一:Spring Retry 注解
@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();
}方案二:多模型降级
@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 系统中文档会持续更新,增量同步是核心能力。
增量更新的标准流程:
// 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));注意事项:
- ID 设计:要用业务语义 ID,不要用随机 UUID,否则无法精准删除
- 删除再添加:VectorStore 一般不支持原地更新,先删后加是最稳的做法
- 批量操作:大量文档更新时分批处理,避免一次操作超时
- 元数据同步:更新内容时同步更新元数据(如 updateTime、version)
分布式场景的坑:
- 多实例并发更新同一文档要用分布式锁
- 向量库主从同步有延迟,更新后立即查询可能查不到旧数据
面试加分点:提到文档变更检测——可以用文件的 lastModified 或内容 hash 判断是否需要更新,避免全量同步浪费资源。
【面试题】8. Spring AI 如何与 Spring Security 集成?用户身份如何传递到 AI 调用链?
【标准答案】Spring AI 作为 AI 中间件,需要和业务系统的安全体系打通。
核心集成点:
- API Key 管理
@Configuration
public class AiSecurityConfig {
@Bean
public ChatClient securedChatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new ApiKeyAuditAdvisor()) // 记录 API Key 使用
.build();
}
}- 用户身份传递
- 通过 Advisor 在 Prompt 中注入用户信息
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();
}
}- 权限校验
- 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 实现步骤:
// 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 简化后:
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
@MockBean
private ChatModel chatModel;
@Test
void testChatFlow() {
// Mock 固定返回
when(chatModel.call(any(Prompt.class)))
.thenReturn(new ChatResponse("这是测试响应"));
String result = chatService.chat("测试问题");
assertThat(result).contains("测试");
}方案二:使用本地模型测试
@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 调用,避免真实调用模型。