Skip to content

Spring AI 面试题库 ​

生成时间:2026-05-24 19:34 (Asia/Shanghai)


【面试题】1. Spring AI 中 ChatClient 和 ChatModel 的区别是什么?为什么1.0版本推荐用 ChatClient? ​

【标准答案】ChatModel 是底层模型调用接口,负责把 Prompt 发给大模型拿回 Response,它只管"调模型"这一件事。ChatClient 是 1.0 引入的统一高层入口,基于 Fluent API 链式调用,内部把 Advisor 链、Memory、Function Calling、Structured Output 这些横切关注点全编排好了。简单说:ChatModel 是引擎,ChatClient 是方向盘+仪表盘。实际开发中直接用 ChatClient,因为它帮你省掉了大量手动拼 Prompt 和管理 Advisor 链的代码,不用 ChatClient 你就得自己处理 Advisor 调用顺序、上下文注入这些脏活。

【面试题】2. RAG 流程中 TokenTextSplitter 的 chunkSize 和 overlap 怎么调?调不好会出什么问题? ​

【标准答案】chunkSize 决定每段文本多长,overlap 决定相邻段重叠多少。经验值:chunkSize 500-1000 token,overlap 50-100 token。chunkSize 太大,检索不精准,一段里混了多个主题,语义模糊;太小则上下文断裂,检索到的片段信息不完整,回答缺斤少两。overlap 太小会丢失边界信息——一段话从中间切断,关键信息在边界处丢失;太大则存储膨胀、检索结果重复冗余。实际调法:先看文档类型,技术文档 chunkSize 偏小(500),长文叙述偏大(800-1000),然后根据检索 recall 指标微调。

【面试题】3. Spring AI 的 Advisor 机制执行顺序是怎样的?如果我想在请求前加日志、响应后做脱敏,Advisor 怎么排? ​

【标准答案】Advisor 是链式执行,类似拦截器模式。请求方向从外到内依次执行 Advisor 的 beforeAdvise,响应方向从内到外依次执行 afterAdvise——就是洋葱模型。如果你要加日志和脱敏,日志 Advisor 排在最外层(先记录请求、最后记录响应),脱敏 Advisor 排在日志内层(在脱敏后的响应返回时,日志已经记录完了原始数据,或者你把日志也放内层让脱敏先执行再记日志,看你需求)。代码里通过 ChatClient.builder().defaultAdvisors(logAdvisor, desensitizeAdvisor) 按顺序添加,顺序就是执行顺序。

【面试题】4. Function Calling 中 @Bean 注册函数的 JSON Schema 是怎么自动生成的?自定义 Schema 有哪些坑? ​

【标准答案】Spring AI 通过反射扫描 @Bean 函数的参数类型和泛型信息,自动生成 JSON Schema。比如你的函数参数是个 Record,它会提取每个字段的名称、类型、description 注解来生成 schema。坑主要有三个:一是参数类型必须是简单POJO或Record,用 Map、Object 这种泛化类型会生成不精确的 schema,模型调用时参数对不上;二是嵌套太深(超过3层)模型容易迷失,生成的参数结构不准;三是中文 description 偶尔会导致编码问题,建议 description 用英文,业务描述放函数名。如果自动生成不满意,可以实现 FunctionCallback 自定义 schema,完全控制输入输出结构。

【面试题】5. Spring AI 对接多个大模型时,如何实现运行时动态切换?配置上怎么管理? ​

【标准答案】Spring AI 用 spring.ai.* 配置前缀区分不同模型实现,比如 spring.ai.openai.api-key、spring.ai.ollama.base-url、spring.ai.zhipuai.api-key。同时注入多个 ChatModel Bean 后,通过 @Qualifier 指定使用哪个。动态切换有两种方式:一是用策略模式封装一个 ChatModelRouter,内部维护 Map<String, ChatModel>,按场景或用户选择路由;二是结合 Spring Profile,不同环境激活不同模型配置。1.0 版本还支持同一个供应商下多模型切换,比如 spring.ai.openai.chat.options.model 可以在运行时通过 ChatOptions 动态覆盖。

【面试题】6. BeanOutputConverter 做 Structured Output 时,实体类设计有哪些常见踩坑点? ​

【标准答案】最常见三个坑:第一,实体类必须有无参构造器,否则反序列化报错,Record 类型要注意这点;第二,字段命名不规范,大模型返回的 JSON key 和 Java 字段对不上,建议用 @JsonProperty 显式映射;第三,嵌套对象或集合类型(List<xxx>)经常反序列化失败,因为 schema 生成时嵌套结构描述不清,模型返回的 JSON 结构和预期不一致。另外还有个隐性坑:枚举类型如果不加 @Schema(description="可选值: A,B,C"),模型可能返回枚举之外的值导致解析失败。entity() 方法内部就是 BeanOutputConverter + 提示词注入 schema 约束,所以实体类设计直接决定了成功率。

【面试题】7. ChatMemory 的 InMemoryChatMemory 和持久化方案各适用什么场景?持久化自己实现要注意什么? ​

【标准答案】InMemoryChatMemory 就是内存里的 ConcurrentHashMap,重启就丢,适合单机演示、测试、短对话场景。生产环境必须持久化,否则一重启对话上下文全没了。自己实现 ChatMemory 接口要注意三点:一是按 conversationId 隔离存取,别串对话;二是消息量控制,对话太长要裁剪老消息,否则 token 爆炸,建议保留最近 N 轮或用滑动窗口;三是并发安全,多个请求同时写同一对话要加锁或用乐观锁。持久化载体可以选 Redis(高性能)、PGVector(和向量库共用)、或者普通关系库。Spring AI 没有内置持久化实现,1.0 只提供了 InMemory 版本,需要自己实现或用社区扩展。

【面试题】8. Spring AI 在微服务架构下,API Key 管理和限流熔断怎么做? ​

【标准答案】API Key 绝对不能硬编码在配置文件里,必须走外部化:要么用 Spring Cloud Config/Vault 统一管理,要么用环境变量注入。推荐方式是 spring.ai.openai.api-key=${OPENAI_API_KEY},由运维在部署时注入。限流方面,大模型 API 本身有 RPM/TPM 限制,应用层要用 Resilience4j 或 Sentinel 做限流,防止突发请求打爆配额。熔断也是 Resilience4j,当模型 API 持续超时或返回 429,触发熔断走降级逻辑,比如切换到备用模型或返回缓存结果。超时配置在 spring.ai.openai.chat.options 里设置,也可以在 ChatClient 层面用 Advisor 做统一超时拦截。关键思路:把大模型当外部服务对待,所有微服务的稳定性手段都要用上。

【面试题】9. VectorStore 接口统一后,PGVector 和 Redis 作为向量库各自的优劣和选型依据是什么? ​

【标准答案】PGVector 优势是和现有 PostgreSQL 生态无缝集成,事务、备份、运维工具现成的,适合已有 PG 基础设施的团队;劣势是大规模向量检索性能一般,亿级数据量下明显不如专业向量库。Redis(Redis Stack)优势是纯内存检索速度极快,适合低延迟场景;劣势是内存成本高,数据量受内存限制,持久化配置复杂。选型依据:数据量百万级以下、对延迟不极端敏感、已有 PG → 选 PGVector,省运维成本;数据量百万级以上、延迟要求 <10ms → 考虑 Milvus 或 Pinecone 这种专业向量库;需要极低延迟且数据量可控 → Redis。Spring AI 的 VectorStore 统一接口让切换成本低,可以先用 PGVector 验证,后期再迁移。

【面试题】10. Spring AI 1.0 的 Streaming 响应怎么用?和同步调用在 Advisor 链上的行为有什么区别? ​

【标准答案】同步调用用 chatClient.prompt().call(),返回完整 ChatResponse;流式用 chatClient.prompt().stream(),返回 Flux<ChatResponse>,逐 token 推送。关键区别在 Advisor 链:同步模式下 Advisor 的 beforeAdvise 和 afterAdvise 顺序执行,afterAdvise 拿到完整响应再处理;流式模式下 afterAdvise 拿到的是 Flux,需要在响应流上做操作,比如用 doOnNext 逐片段处理、用 reduce 聚合完整响应后再脱敏。所以脱敏、日志这类 Advisor 在流式场景下需要特殊处理——不能等完整响应再脱敏(那就失去流式意义),通常用逐片段检测+延迟输出策略。另外流式场景下 ChatMemory 的写入时机也要注意,建议在 Flux 完成时(doOnComplete)统一写入,而不是每个片段都写。