Skip to content

Spring AI 面试题库 ​

生成时间:2026-05-21 21:27


【面试题】1. Spring AI 的 Advisor 机制是什么?和传统拦截器有什么区别? ​

【标准答案】Advisor 是 Spring AI 1.0 引入的核心扩展机制,本质是对 ChatClient 请求/响应流的拦截与增强。它和传统拦截器的区别在于三点:第一,Advisor 感知 AI 语义,能操作 Prompt、ChatResponse 等 AI 领域对象,而不是通用 HTTP 请求;第二,Advisor 有明确的顺序链路,通过 Ordered 接口控制执行先后,比如 QuestionAnswerAdvisor 要在日志 Advisor 之后执行;第三,Advisor 支持 streaming 和非 streaming 两种模式,分别实现不同方法。典型用法:我会在 ChatClient.builder() 上 .defaultAdvisors() 注册全局 Advisor,或者在单次 .advisors() 调用中临时追加。实际项目中,我用 Advisor 统一处理对话记忆注入、RAG 上下文增强、日志追踪,避免了在每个业务方法里重复写这些逻辑。


【面试题】2. TokenTextSplitter 的切分策略有哪些参数可调?生产环境怎么设置才能兼顾召回率和性能? ​

【标准答案】TokenTextSplitter 核心参数有三个:chunkSize(每个分块的 token 数)、overlapSize(相邻分块重叠 token 数)、separator(切分分隔符)。生产环境设置的关键思路是平衡:chunkSize 太小(比如 200),语义碎片化严重,召回率低;太大(比如 2000),冗余信息多,浪费上下文窗口且降低回答精度。我的经验值是 500-800 token,配合 overlapSize 设为 chunkSize 的 10%-15%(50-120 token)。separator 默认按段落和换行切,但如果是代码文档或 Markdown,建议自定义分隔符比如 "##" 或 "\n\n",让分块边界落在语义断点处。另外有个坑:默认 token 计算是按空格分词估算的,中文场景下实际 token 数可能偏大,建议用 EmbeddingModel 对应的 tokenizer 来精确计算,否则会出现分块不均匀的问题。


【面试题】3. Spring AI 的 Function Calling 中,@Bean 注册函数的 JSON Schema 是怎么自动生成的?有什么限制? ​

【标准答案】Spring AI 通过扫描 @Bean 注册的函数方法签名,自动推导生成 JSON Schema:方法参数的类字段名变成 Schema 的 properties,字段类型映射为 JSON 类型,再通过 Jackson 的注解(@Description、@JsonProperty)补充描述和示例。生成的 Schema 会作为 tool 的 input_schema 传给大模型,大模型据此构造参数 JSON 回调。限制方面:第一,不支持复杂的泛型嵌套,比如 Map<String, List<CustomObj>> 这种,Schema 生成会丢失类型信息,需要手动用 @Schema 注解补;第二,枚举类型默认只生成 string,不会列出枚举值,需要加 @Schema(allowableValues=...) 显式声明;第三,同名 @Bean 会冲突,必须用 spring.ai.openai.function.xxx.name 显式指定工具名。我的做法是给每个函数参数类单独定义一个 Request DTO,加上完整的 @Description 注解,保证 Schema 信息量足够,大模型才能准确调用。


【面试题】4. ChatMemory 接口和 MessageChatMemoryAdvisor 是什么关系?生产环境怎么做持久化? ​

【标准答案】ChatMemory 是存储接口,负责对话历史的 CRUD;MessageChatMemoryAdvisor 是 Advisor 实现,负责在每次请求时自动从 ChatMemory 读取历史消息注入 Prompt,并在响应后自动追加新的用户消息和助手回复到 ChatMemory。两者是存储层和业务层的解耦关系。生产环境持久化方案:InMemoryChatMemory 只能开发测试用,重启全丢。我实际用的是自定义实现 ChatMemory 接口,底层接 Redis(用 conversationId 做 key,存 JSON 数组),或者接 PGVector 的表结构做持久化。关键注意点:对话历史不能无限增长,要设置窗口大小(比如保留最近 10 轮),MessageChatMemoryAdvisor 构造时传 maxHistorySize 参数控制。另一个坑是并发写入:如果同一会话并发请求,Redis 方案需要加分布式锁,否则消息顺序会乱。


【面试题】5. Spring AI 对接多个大模型时,如何做动态路由和故障切换?给出架构设计思路。 ​

【标准答案】我的设计思路是三层架构。第一层:路由层,自定义 RoutingChatModel 实现 ChatModel 接口,内部维护一个 Map<String, ChatModel>,根据请求中的参数(比如 modelHint、业务标签、用户等级)选择目标 ChatModel 实现。第二层:健康检查层,对每个 ChatModel 实例维护熔断状态,用 Resilience4j 或自定义 CircuitBreaker 包装,连续失败 N 次自动降级到备选模型。第三层:配置层,通过 spring.ai.openai/anthropic/ollama.* 分别配置多个模型实例,用 @ConditionalOnProperty 控制启用。故障切换流程:主模型调用超时 → 熔断器打开 → 自动 fallback 到备模型 → 异步探测主模型恢复 → 熔断器半开 → 成功后切回。另外要在路由层打标签,记录每次请求使用的模型和耗时,方便后续分析成本和质量差异。


【面试题】6. BeanOutputConverter 在 Structured Output 中解决了什么问题?和 entity() 方法有什么区别? ​

【标准答案】BeanOutputConverter 解决的核心问题是:把大模型返回的自由文本转成 Java 对象。它的工作流程是:根据目标类的 Class 信息生成 JSON Schema 约束 → 把 Schema 塞进 Prompt 指令要求大模型按格式输出 → 拿到大模型文本后用 ObjectMapper 反序列化成目标对象。entity() 方法是 ChatClient 提供的语法糖,底层调的就是 BeanOutputConverter,但帮我们省去了手动构造 Prompt 模板和解析响应的代码。关键区别:BeanOutputConverter 可以单独使用,更灵活,比如我要在自定义 Prompt 模板里嵌入 Schema 指令,用 BeanOutputConverter 的 getFormat() 拿到格式指令拼进去;entity() 是端到端封装,一行代码搞定,但灵活性低,只能在 ChatClient 链式调用中使用。踩坑点:大模型有时会在 JSON 前后加 markdown 代码块标记(```json),BeanOutputConverter 内部做了 strip 处理,但如果模型输出格式严重跑偏,还是会解析失败,建议加 retry 机制。


【面试题】7. Spring AI 的 VectorStore 接口统一了哪些操作?切换向量库需要改多少代码? ​

【标准答案】VectorStore 接口统一了五个核心操作:add(写入文档向量)、delete(按 ID 删除)、similaritySearch(相似度检索)、accept(文档过滤回调)、getName(获取存储名称)。切换向量库的代码改动极小,这是我选 Spring AI 的关键理由。具体来说:业务代码层零改动,因为都是面向 VectorStore 接口编程;配置层改依赖和 YAML 即可,比如从 PGVector 切到 Pinecone,只需要把 spring-ai-pgvector-store-starter 换成 spring-ai-pinecone-store-starter,再把 spring.ai.vectorstore.pgx.* 配置换成 spring.ai.vectorstore.pinecone.* 配置。唯一需要注意的是不同向量库的 EmbeddingModel 维度必须匹配,比如 PGVector 我用了 1536 维的 OpenAI embedding,切到 Pinecone 时 index 的维度也得是 1536,否则写入会报错。另外 similaritySearch 的 Filter 表达式语法在不同实现中有细微差异,复杂过滤场景建议提前验证。


【面试题】8. Spring AI 的 Streaming 响应怎么用?和同步调用在架构上有什么区别? ​

【标准答案】Streaming 用法很简单,ChatClient 调用 .stream() 替代 .call(),返回的是 Flux<ChatResponse> 流式响应。架构上的核心区别是:同步调用是请求-阻塞-返回完整结果,适合短文本、批量处理;Streaming 是服务端逐 token 推送,客户端增量消费,适合对话场景,用户能看到逐字生成,体感好很多。技术实现上,Spring AI 底层用 WebClient 接 SSE(Server-Sent Events),Flux 流中每个 ChatResponse 包含一个 token 片段,最后一条的 metadata 里带 usage 统计。注意点:第一,Streaming 模式下 Advisor 的处理不同,MessageChatMemoryAdvisor 需要在流结束时(doOnComplete)才写入记忆,不能逐 token 写;第二,Function Calling 在 Streaming 中也可以用,但 tool_call 的参数是流式拼接的,需要攒够完整的 JSON 再执行函数调用;第三,前端对接时需要 EventSource 或 fetch + ReadableStream 消费,不能用普通 HTTP 请求。


【面试题】9. Spring AI 1.0 正式版和之前的 Milestone/SNAPSHOT 版本相比,有哪些破坏性变更?迁移要注意什么? ​

【标准答案】最大的破坏性变更有四个。第一,ChatClient 成为统一入口,之前直接用 ChatModel.call() 的方式虽然还能用,但官方推荐全部迁移到 ChatClient.builder().build() 风格,因为 Advisor、Memory、RAG 都挂在 ChatClient 上。第二,配置前缀统一为 spring.ai.*,之前的 spring.ai.openai.chat.options.model 现在缩简或调整了层级,需要对照官方迁移指南逐项检查 YAML。第三,VectorStore 接口方法签名变了,比如 add 方法的返回值、similaritySearch 的参数类型,老代码直接编译不过。第四,Advisor 架构是新增的,之前用 ChatMemory 和 RAG 是手动拼 Prompt,现在必须改成 Advisor 方式,否则拿不到框架的自动增强能力。迁移建议:先升级依赖到 1.0,然后全量编译,逐个修复编译错误;功能层面重点测试 RAG 和 Memory 的 Advisor 行为是否和之前手动实现的一致。


【面试题】10. Spring AI 的 Observability 监控怎么做?能观测哪些指标? ​

【标准答案】Spring AI 1.0 原生集成了 Micrometer,开箱即用。只要项目里引入了 spring-boot-starter-actuator 和 micrometer-registry-prometheus(或其他 Registry),Spring AI 就会自动注册指标。能观测的核心指标有:ai.chat.model.calls(调用次数)、ai.chat.model.tokens(token 用量,分 input/output)、ai.chat.model.latency(响应延迟)、ai.vectorstore.operations(向量库操作次数和耗时)。另外还有 Span 级别的分布式追踪,接入 Zipkin 或 Jaeger 后能看到一次完整 AI 调用的链路:从 ChatClient 入口 → Advisor 链 → ChatModel 调用 → Function Calling 执行 → 响应返回。我的实践经验:重点监控 token 用量(成本控制)和 P99 延迟(用户体验),配合 Grafana 做告警,token 消耗突增可能意味着 prompt 注入攻击,延迟飙升可能是模型服务降级。还有一个小技巧:给每次调用打自定义 Tag(比如 businessType、modelVersion),方便按业务维度分析成本。