1. 面试官追问 Spring AI 时,你总得有个能跑通的统一接入方案
Java 后端这两年面试有个明显变化:Spring Boot、Redis、Kafka 这些老几样问完,面试官十有八九会拐到 AI 上。尤其是做 AIGC 业务线的团队,Spring AI 和 RAG 架构几乎是必问项。问题不在于你知不知道 RAG 是什么,而在于你能不能把 ChatClient、EmbeddingModel、VectorStore 这条链路真正串起来,并且说清楚每一步的取舍。
我试过在本地把 Spring AI 接到不同厂商的模型上,最烦的就是每换一个模型就要改一套配置:OpenAI 一套 Key、另一个厂商一套 Key、Embedding 又是另一套地址。面试时如果被追问“你们怎么管理多模型接入”,答不上来就很尴尬。所以这篇用一个统一 Key/API 通道把 Spring AI 的 ChatClient 和 EmbeddingModel 都指到同一个端点,配置可复制,链路可验证,面试时也能拿这套架构去讲取舍。
核心检索词先摆出来:Spring AI 是 Spring 生态里做 AI 应用开发的框架,它把对话、嵌入、向量存储这些能力抽象成统一的 API;RAG 架构是检索增强生成,用外部知识库给大模型补充事实依据。这套东西适合谁?适合已经有 Spring Boot 基础、想在企业文档问答、智能客服这类场景里落地 AI 的 Java 后端。下面从面试场景切入,一步步把配置和验证做出来。
2. 为什么面试里 RAG 架构总被追问,以及统一端点的前置准备
面试官问 RAG,通常不是让你背流程,而是想看你对“检索质量决定生成质量”这件事有没有体感。RAG 的核心链路是:文档加载 → 文本切片 → 向量化 → 存入向量库 → 查询向量化 → 语义检索 → 上下文拼装 → 大模型生成。这里面 EmbeddingModel 和 ChatClient 是两个必须打通的组件,前者负责把文本变成向量,后者负责最终生成。
问题来了:很多团队 Embedding 用一个厂商,Chat 用另一个厂商,配置散落在各处,面试时被问“你们 Embedding 和 Chat 是同一个模型吗,维度怎么对齐的”就容易卡壳。统一端点的价值就在这里——Base URL 一个、Key 一个,Chat 和 Embedding 都走同一条通道,配置集中管理,切换模型只改 Model ID。
前置准备很简单:一个 Spring Boot 3.x 项目,JDK 17 以上,Maven 或 Gradle 都行。依赖上引入 Spring AI 的 starter。我用的版本是 Spring AI 1.0.x 系列,Spring Boot 3.3.x。如果你还在用 Spring Boot 2.x,建议先升级,Spring AI 对 3.x 支持更完整。
需要提前拿到的东西:一个统一 API Key 和对应的 Base URL。TaoToken 的 API 地址是 https://taotoken.net/api ,Key 在控制台的 API Keys 页面创建。注意 Base URL 不要带多余路径,Spring AI 的 OpenAI 兼容实现会自动拼接 /v1/chat/completions 这类后缀。模型对话可以在模型对话页面试,Coding Plan 适合长期编码和 Agent 场景,接入文档在 doc 页面。
这里有个面试常被追问的点:为什么要用 OpenAI 兼容协议?因为 Spring AI 的 OpenAiChatModel 和 OpenAiEmbeddingModel 都基于 OpenAI 的接口规范,只要端点兼容这套规范,就能用同一套客户端代码对接,不用为每个厂商写适配层。这就是统一端点的架构意义,面试时可以直接讲。
3. 可复制的 application.yml 与 Spring AI 配置片段
这一节是重点,配置直接给全。先看 application.yml,路径放在 src/main/resources/application.yml。注意 Base URL 写 https://taotoken.net/api ,Key 用环境变量注入,别硬编码进仓库。
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 embedding: options: model: text-embedding-3-small这里 chat 和 embedding 共用同一个 base-url 和 api-key,这就是统一端点的直接体现。Model ID 按你实际可用的填,chat 用对话模型,embedding 用嵌入模型。temperature 控制生成随机性,做企业文档问答建议调低到 0.2 到 0.3,减少胡编。
如果你用 Gradle,依赖这样写:
dependencies { implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0' implementation 'org.springframework.boot:spring-boot-starter-web' }Maven 对应:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>接下来是 Java 配置类,把 ChatClient 和 EmbeddingModel 注入进来。Spring AI 的 starter 会自动装配 OpenAiChatModel 和 OpenAiEmbeddingModel,你只需要拿 Bean。
@Configuration public class AiConfig { @Bean public ChatClient chatClient(OpenAiChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是一个企业文档问答助手,只基于提供的上下文回答。") .build(); } }EmbeddingModel 直接注入即可,不用额外包装。如果你要接向量库,比如 SimpleVectorStore 做本地验证:
@Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return SimpleVectorStore.builder(embeddingModel).build(); }面试时如果被问“ChatClient 和 ChatModel 什么关系”,可以答:ChatModel 是底层模型调用接口,ChatClient 是更高层的门面,封装了 Prompt 模板、系统消息、流式输出这些常用能力,类似 RestTemplate 和 WebClient 的关系。这个类比面试官一般能秒懂。
还有一个容易踩的坑:Spring AI 的配置前缀在不同版本有变化,1.0.x 用 spring.ai.openai,早期 milestone 版本可能不同。如果你启动报“找不到 base-url 配置”,先确认版本和前缀是否匹配。另外 base-url 结尾不要加 /v1,加了会变成 /v1/v1/chat/completions,直接 404。
4. 验证请求:从 ChatClient 调用到 RAG 检索链路最小闭环
配置写完,先验证 Chat 能不能通。写一个简单的 Controller 或者 CommandLineRunner:
@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/chat") public String chat(@RequestParam String q) { return chatClient.prompt() .user(q) .call() .content(); } }启动后访问 http://localhost:8080/chat?q=你好 ,能返回内容就说明 Chat 链路通了。如果返回 401,检查 Key 是否正确注入;如果返回连接超时,检查 base-url 是否可达。
接着验证 Embedding。写一个测试方法,把一段文本转成向量,看维度:
@Autowired private EmbeddingModel embeddingModel; @Test void testEmbedding() { float[] vector = embeddingModel.embed("Spring AI 统一接入验证"); System.out.println("维度: " + vector.length); }text-embedding-3-small 一般是 1536 维。维度能打印出来,说明 Embedding 也走通了同一条通道。
然后是 RAG 检索链路的最小验证。用 SimpleVectorStore 存几段文档,做一次相似度检索:
@Autowired private VectorStore vectorStore; @Test void testRagRetrieval() { List<Document> docs = List.of( new Document("公司年假制度:入职满一年享5天年假。"), new Document("报销流程:发票需在30天内提交财务系统。") ); vectorStore.add(docs); List<Document> results = vectorStore.similaritySearch( SearchRequest.builder().query("年假有几天").topK(1).build() ); results.forEach(d -> System.out.println(d.getText())); }跑出来应该命中“年假制度”那条。这一步验证的是:Embedding 把文档和查询都转成向量,向量库做语义匹配。面试时你可以说,RAG 的检索质量取决于切片粒度和 topK 设置,切片太大噪声多,太小上下文不完整,一般按语义段落切,topK 取 3 到 5。
把检索结果拼进 Prompt 就是完整的 RAG:
String context = results.stream() .map(Document::getText) .collect(Collectors.joining("\n")); String answer = chatClient.prompt() .system("只基于以下上下文回答:\n" + context) .user("年假有几天") .call() .content();到这里,Chat、Embedding、检索、生成四个环节全部跑通,而且共用同一个 Base URL 和 Key。面试时这套链路能讲清楚,基本就稳了。
5. 本篇常见报错排查:401、local proxy failed、reading choices 与 OAuth
配置和验证过程中,报错集中在几个地方,逐个说。
401 Unauthorized。最常见的原因是 Key 没注入成功。检查环境变量 TAOTOKEN_API_KEY 是否设置,或者 application.yml 里是否写成了 ${TAOTOKEN_API_KEY} 但启动时没传。另一个原因是 Key 前后有空格,复制时容易带上。还有一种是 base-url 写错,请求打到了别的地址,认证自然失败。排查方法:在启动日志里打印 base-url 和 Key 的前几位,确认无误。
local proxy failed 或 connection refused。这类报错通常是网络层问题,不是配置问题。先确认 base-url 是 https://taotoken.net/api ,然后用 curl 直接测:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'curl 能通说明网络没问题,问题在 Spring 配置;curl 不通就检查本机网络环境。注意不要在任何地方配置系统级代理指向不明地址,企业内网环境建议走公司统一的出口。
reading choices 或 JSON 解析异常。这个报错说明请求发出去了,但返回体不是预期的 JSON 结构。常见原因是 base-url 多写了 /v1,导致路径变成 /v1/v1/chat/completions,服务端返回了 HTML 错误页。把 base-url 改成 https://taotoken.net/api 即可。另一个原因是 Model ID 写错,服务端返回错误对象,Spring AI 解析 choices 字段时失败。检查 model 字段是否拼写正确。
OAuth 相关报错。如果你用的是需要 OAuth 的客户端工具,比如某些 CLI 工具,报 OAuth 失败通常是认证方式没选对。Spring AI 走的是 API Key 的 Bearer 认证,不需要 OAuth 流程。如果你在 Codex 的 auth.json 或 Claude Code 的配置里混用了 OAuth 和 API Key,建议统一用 API Key 方式。Codex 的 auth.json 里填 Base URL、Key、Model ID 三件套;Claude Code 在 settings 里配 ANTHROPIC_BASE_URL 和 ANTHROPIC_API_KEY,指向统一端点。Cline 的 MCP 配置同理,Base URL、Key、Model ID 三个字段缺一不可。
还有一个隐蔽的坑:Spring AI 的 EmbeddingModel 和 ChatModel 如果配了不同的 base-url,但只配了一个 api-key,会出现其中一个 401。统一端点方案下两者共用同一组配置,这个问题自然消失。这也是面试时可以讲的架构优势:配置收敛,减少出错面。
6. 面试追问架构取舍时,这套统一接入怎么答
面试官如果追问“为什么不让 Chat 和 Embedding 用不同厂商”,你可以从三个角度答。第一,运维成本:一套 Key、一个 Base URL,配置和轮换都简单。第二,一致性:Embedding 和 Chat 如果跨厂商,向量维度和语义空间可能不匹配,检索质量下降。第三,可替换性:统一端点下换模型只改 Model ID,业务代码不动,符合开闭原则。
如果追问“RAG 里向量库怎么选”,可以答:本地验证用 SimpleVectorStore,生产环境按数据量和检索性能选 Milvus、PgVector 或 Redis 向量检索。关键是 Embedding 模型和向量库的维度要对齐,换 Embedding 模型要重建索引。
如果追问“怎么降低幻觉”,答:RAG 提供事实依据是基础,Prompt 里明确要求“只基于上下文回答”,再配合低 temperature。更进一步的方案是让模型输出引用来源,或者用 Agent 工具执行做交叉验证。MCP 协议在这里的作用是标准化模型和外部数据源的连接,让工具调用更规范。
最后给一个实操建议:把 Base URL、Key、Model ID 这三件套写进你的项目模板,下次面试前跑一遍 /chat 和 Embedding 测试,确保链路是通的。需要长期做编码和 Agent 场景的,可以看 Coding Plan;只是验证模型效果的,用模型对话页面就够;接入文档在 doc 页面,API Key 在 console 的 api-keys 页面创建。把这套配置跑通,面试时被问到 Spring AI 和 RAG,你就有实打实的东西可以讲,而不是背概念。