面试高频:Spring AI 统一聊天入口怎么设计,这次把路由和降级讲具体
Spring AI 面试题:ChatClient 怎么落地?把统一聊天接口、多模型路由、超时降级讲透
很多人以为 Spring AI 的 ChatClient 只是一个更顺手的 SDK 包装层,但真正上线后,难点其实在统一入口、模型路由和治理能力。
这篇我直接按 Java 项目怎么落地来拆,既讲 ChatClient 怎么用,也讲什么时候该回到底层 ChatModel 去接管路由。
🦅个人主页
🐼GitHub主页
文章目录
- Spring AI 面试题:ChatClient 怎么落地?把统一聊天接口、多模型路由、超时降级讲透
- 先看真实问题:为什么很多团队接了 Spring AI,最后还是把模型调用写散了
- 一张表先看懂:一个能上线的统一聊天入口最少要管住哪些事
- 举个具体例子:客服中心同时接 FAQ、工单总结、内部知识问答三类能力
- 代码示例:统一聊天接口 + 模型路由 + 超时降级
- Maven 依赖
- application.yml
- 统一聊天服务(ChatClient 做标准调用,ChatModel 做精细路由)
- 系统设计时我会优先拆哪几层
- 统一入口层
- 路由与 Prompt 层
- 治理层
- 真正上线时最容易卡住的点
- 监控和指标建议盯哪些
- 如果面试官问我这块怎么设计,我会这样答
- 结语
先看真实问题:为什么很多团队接了 Spring AI,最后还是把模型调用写散了
很多 Java 团队一开始都是某个 service 直接注入一个模型 Bean,然后哪里要问答就在哪里 call。一开始当然快,但业务场景一多,很快就会出现 Prompt 风格不一致、模型切换成本高、超时不好治理的问题。
真正难的不是把回答跑出来,而是把模型接入做成统一能力:统一请求体、统一会话标识、统一模型路由、统一成本统计,以及统一的超时降级策略。
- 客服 FAQ、工单总结、内部知识问答需要的模型能力完全不一样
- 有的场景更看效果,有的场景更看成本,不能所有流量都打到同一个模型
- 如果没有统一入口,审计、限流、日志和 token 归集最后都会散在业务代码里
一张表先看懂:一个能上线的统一聊天入口最少要管住哪些事
| 维度 | 怎么做 | 为什么 |
|---|---|---|
| 统一请求体 | Controller 只收 scene、sessionId、userMessage、attachments | 避免每个业务自己定义模型参数 |
| 模型路由 | 根据场景、预算、延迟、合规要求选模型 | 把“用哪个模型”从业务代码里拿出来 |
| Prompt 策略 | 系统提示词和输出规范做模板化 | 同一类问题输出风格更稳定 |
| 降级治理 | 超时切备用模型,再差切规则结果 | 线上要先保证稳定,再谈最优答案 |
举个具体例子:客服中心同时接 FAQ、工单总结、内部知识问答三类能力
- 业务侧统一调用
/ai/chat,只告诉网关这是 FAQ、工单总结还是知识问答。 - FAQ 走便宜一点的模型,工单总结走结构化输出更稳定的模型,内部知识问答加上检索增强。
- 如果主模型 RT 超过 3 秒,就自动切备用模型;如果备用模型也失败,再降级到固定话术。
- 每次调用都把 token、耗时、场景、用户、traceId 打到调用日志表里,后面才能看成本和稳定性。
代码示例:统一聊天接口 + 模型路由 + 超时降级
Maven 依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>application.yml
spring:ai:openai:api-key:${OPENAI_API_KEY}base-url:https://api.openai.comchat:options:model:gpt-4o-minitemperature:0.2management:endpoints:web:exposure:include:health,metrics,prometheus统一聊天服务(ChatClient 做标准调用,ChatModel 做精细路由)
@Service@RequiredArgsConstructorpublicclassUnifiedChatService{privatefinalChatClient.BuilderchatClientBuilder;privatefinalMap<String,ChatModel>chatModelRegistry;privatefinalModelCallLogServicemodelCallLogService;publicStringchat(ChatRequestrequest){longstart=System.currentTimeMillis();ChatModelchatModel=route(request.scene());Promptprompt=newPrompt(List.of(newSystemMessage(""" 你是企业级AI助手。 当前场景: %s 回答要求: 1. 先给结论,再给理由 2. 没有把握不要编造 3. 控制在 200 字内 """.formatted(request.scene())),newUserMessage(request.question())));try{ChatResponseresponse=chatModel.call(prompt);Stringanswer=response.getResult().getOutput().getText();modelCallLogService.success(request,chatModel.getClass().getSimpleName(),System.currentTimeMillis()-start,answer);returnanswer;}catch(Exceptionex){returnfallback(request,ex,start);}}publicStringrewriteForRetrieval(Stringquestion){returnchatClientBuilder.build().prompt().system("你是查询改写助手,只输出适合知识库检索的一句话").user(question).call().content();}privateChatModelroute(Stringscene){returnswitch(scene){case"FAQ"->chatModelRegistry.get("cheapChatModel");case"TICKET_SUMMARY"->chatModelRegistry.get("stableStructuredModel");case"RAG_QA"->chatModelRegistry.get("knowledgeChatModel");default->chatModelRegistry.get("defaultChatModel");};}privateStringfallback(ChatRequestrequest,Exceptionex,longstart){modelCallLogService.fail(request,ex.getMessage(),System.currentTimeMillis()-start);if("FAQ".equals(request.scene())){return"当前智能助手繁忙,请稍后重试,或者转人工处理。";}thrownewIllegalStateException("模型调用失败",ex);}publicrecordChatRequest(Stringscene,StringsessionId,Stringquestion){}}系统设计时我会优先拆哪几层
统一入口层
- 对外统一 REST / SSE 接口,业务方不要直接碰底层模型 SDK
- 请求里至少要有 scene、sessionId、traceId,后面做日志和限流才有抓手
路由与 Prompt 层
- scene 决定模型类型,模板决定回答风格,不要把两件事混在一起
- 复杂场景可以先用 ChatClient 做 Prompt 组装,再把最终 Prompt 下发给指定 ChatModel
治理层
- 统一做超时、限流、错误码映射、成本归集
- 每次调用都要能查到是谁发起、走了哪个模型、为什么降级
真正上线时最容易卡住的点
- 把 ChatClient 当成唯一入口,结果一碰到多模型路由就写不动了。真到复杂项目里,ChatClient 和底层 ChatModel 往往要配合使用。
- 没有按业务场景拆 Prompt 模板,最后一个系统提示词想兼容所有场景,效果一定很飘。
- 没有统一调用日志,月底只看到总账单,却不知道哪个业务线把 token 烧掉了。
监控和指标建议盯哪些
- 模型调用成功率、P95/P99 RT
- 各场景 token 消耗、平均每问成本
- 超时降级触发率、熔断触发率
- 主模型和备用模型的命中比例
如果面试官问我这块怎么设计,我会这样答
如果面试官问我 Spring AI 的 ChatClient 怎么落地,我不会只说“它比 SDK 好用”,而是会先讲统一聊天入口,再讲模型路由和治理。ChatClient 更适合做标准调用链路和 Prompt 组装,但一旦涉及多模型路由、预算策略、降级兜底,我会把 ChatModel 抽出来单独控制。也就是说,ChatClient 解决的是易用性,真正上线还要补齐治理能力。
结语
Spring AI 不是接上就结束了,真正拉开差距的是:你有没有把模型接入沉淀成统一入口,而不是散落在几十个 service 里。
你们项目里如果已经接了大模型,更麻烦的是模型路由,还是日志和成本治理?这一点很适合继续往下聊。
