【Spring AI 实战】二、 5 分钟上手 ChatClient,对话模型调用就这么简单

张开发
2026/4/16 13:55:18 15 分钟阅读

分享文章

【Spring AI 实战】二、 5 分钟上手 ChatClient,对话模型调用就这么简单
【Spring AI 实战】二、5 分钟上手 ChatClient对话模型调用就这么简单作者Spring AI 系列专题 | 更新时间2025-04所属阶段第一阶段·核心基础前置知识建议先阅读第一篇了解 Spring AI 的整体定位与模型抽象。适用版本本文保留 Spring AI 1.0.0-M4 的入门写法如果你使用 1.0 正式版优先采用spring-ai-openai-spring-boot-starter等 starter 依赖。本文导航1. 前置条件2. ChatClient 核心 API 详解3. 四种调用方式实战4. 多轮对话实现5. 系统提示词与参数控制6. 异常处理7. 完整示例从配置到 Controller8. 常见问题排查9. 小结1. 前置条件1.1 环境要求JDK 17Spring AI 要求Spring Boot 3.2可用的 OpenAI API Key或任何支持的模型 API Key1.2 项目创建推荐使用 Spring Initializr 创建项目勾选✅ Spring Web ✅ Spring Boot DevTools然后在pom.xml中加入dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion1.0.0-M4/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-openai-spring-boot-starter/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency/dependencies1.3 配置 API Key方式一环境变量推荐exportOPENAI_API_KEYsk-xxxxxxxxxxxxxxxxxxxxxxxx方式二application.ymlspring:ai:openai:api-key:${OPENAI_API_KEY:sk-your-key-here}base-url:https://api.openai.comchat:options:model:gpt-4o-minitemperature:0.7⚠️生产环境务必使用环境变量或 Vault不要将 API Key 硬编码在配置文件中。2. ChatClient 核心 API 详解2.1 ChatClient 是什么ChatClient是 Spring AI 1.0 推出的全新对话 API设计灵感来自RestClient和JdbcTemplate特点是流式 API Builder 模式 类型安全。// 使用方式极其简洁chatClient.prompt().user(你好).call().content();2.2 核心组件![ChatClient API 四种调用方式](…/blog/images/02-chatclient-invocation-modes.png null)| 组件 | 说明 ||------|------||ChatClient| 主入口通过prompt().user().call()链式调用 ||Prompt| 对话提示词包含 user message / system message ||UserPrompt| 用户消息构建.user()||SystemPrompt| 系统提示词构建.system()||ChatResponse| 响应对象包含 content、metadata 等 |2.3 工作流程Application Code │ ▼ chatClient.prompt() │ ▼ Prompt(user message, system message, ...) │ ▼ [OpenAiChatModel / AnthropicChatModel / ...] ← 自动注入的模型实现 │ ▼ HTTP POST to AI Provider API │ ▼ ChatResponse │ ▼ .content() / .entity() / .document()3. 四种调用方式实战3.1 同步调用最常用ServicepublicclassAiService{privatefinalChatClientchatClient;publicAiService(ChatClient.Builderbuilder){this.chatClientbuilder.build();}/** * 最简单的同步调用 */publicStringask(Stringquestion){returnchatClient.prompt().user(question).call().content();}/** * 带系统提示词的同步调用 */publicStringaskWithContext(Stringquestion){returnchatClient.prompt().system(你是一位资深的 Java 后端工程师用简洁专业的语言回答问题。).user(question).call().content();}}3.2 流式调用适合实时输出流式调用的核心优势首 token 延迟低用户体验好适合 AI 助手类应用。/** * 流式调用 - 返回 Flux分段内容 */publicFluxStringaskStream(Stringquestion){returnchatClient.prompt().user(question).stream().content();}/** * 在 Controller 中返回流式响应SSE */GetMapping(value/chat/stream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatStream(RequestParamStringquestion){returnaiService.askStream(question);}实际效果AI 是一边思考一边输出字符而不是等全部生成完再一次性返回。// 后端推送到前端的 SSE 格式/* data: 你好 data: data: 我 data: 是 data: Spring data: AI data: 。 data: [DONE] */3.3 带结构化参数调用/** * 控制模型的温度创造性和最大 token 数 */publicStringaskWithOptions(Stringquestion){returnchatClient.prompt().user(question).options(ChatOptionsBuilder.builder().withTemperature(0.3)// 降低随机性更确定性.withMaxTokens(500)// 限制输出长度.withTopP(0.9)// 控制采样范围.build()).call().content();}常见参数说明| 参数 | 范围 | 说明 ||------|------|------||temperature| 0.0~2.0 | 随机性越低越确定性默认 0.7 ||maxTokens| 1~4096 | 最大生成 token 数限制响应长度 ||topP| 0.0~1.0 | 核采样控制输出多样性 ||frequencyPenalty| -2.0~2.0 | 频率惩罚减少重复 ||presencePenalty| -2.0~2.0 | 在场惩罚鼓励话题扩展 |3.4 返回结构化对象强类型响应Spring AI 支持让模型直接返回 Java 对象而不只是字符串// 定义响应结构publicrecordWeatherInfo(Stringcity,Stringcondition,doubletemperature){}// 服务层publicWeatherInfogetWeather(Stringcity){returnchatClient.prompt().user(请查询city今天的天气用 JSON 格式返回包含 city、condition、temperature 字段).call().entity(WeatherInfo.class);// 自动解析为 Java 对象}// 输出// WeatherInfo[city北京, condition晴, temperature23.5]配合JsonProperty可以映射更复杂的结构publicrecordApiResponseT(intcode,Stringmessage,Tdata){}publicApiResponseUserProfilegetUserProfile(LonguserId){returnchatClient.prompt().user(查询用户 IDuserId 的个人信息).call().entity(newParameterizedTypeReferenceApiResponseUserProfile(){});}4. 多轮对话实现4.1 简单的内存级对话ServicepublicclassChatSessionService{privatefinalMapString,ListMessagesessionsnewConcurrentHashMap();privatefinalChatClientchatClient;publicChatSessionService(ChatClient.Builderbuilder){this.chatClientbuilder.build();}/** * 多轮对话每次携带历史消息 */publicStringchat(StringsessionId,StringuserMessage){// 获取或创建会话历史ListMessagehistorysessions.computeIfAbsent(sessionId,k-newArrayList());// 构建 Prompt携带历史PromptpromptnewPrompt(MessageBuilder.createMessage(MessageType.USER,userMessage,newMediaContent(MediaType.TEXT_PLAIN,userMessage)));// 如果有系统提示词先加进去if(history.isEmpty()){prompt.getOptions().setSystem(你是智能助手小 Spring。);}// 实际使用 Advisors 更优雅见下节这里演示手动方式returnchatClient.prompt(prompt).call().content();}}4.2 使用 Advisors 实现对话记忆推荐Advisors是 Spring AI 的拦截器链用法类似 Spring AOP是实现对话记忆的标准方式ServicepublicclassAdvisorChatService{privatefinalChatClientchatClient;// 默认开启 20 轮对话记忆publicAdvisorChatService(ChatClient.Builderbuilder){this.chatClientbuilder.defaultSystem(你是一位乐于助人的 Java 技术顾问).build();}/** * 基于 Advisors 的多轮对话自动管理历史 */publicStringchatWithMemory(StringsessionId,Stringquestion){returnchatClient.prompt().user(question).advisors(// 对话记忆 Advisors自动保存并注入历史消息MessageChatAdvisor.builder().chatMemory(newInMemoryChatMemory()// 内存存储生产环境换 Redis).sessionId(sessionId)// 按会话 ID 隔离历史.build()).call().content();}}MessageChatAdvisor的工作原理用户: 第一个问题 ↓ [MessageChatAdvisor] ← InMemoryChatMemory.getHistory(sessionId) → [] ↓ 调用模型携带历史 [] ↓ 模型回复: 第一个回答 ↓ [MessageChatAdvisor] ← InMemoryChatMemory.add(sessionId, userassistant) ↓ 用户: 第二个问题上下文相关 ↓ [MessageChatAdvisor] → InMemoryChatMemory.getHistory(sessionId) → [Q1, A1] ↓ 调用模型携带历史 [Q1, A1, Q2]5. 系统提示词与参数控制![ChatClient 参数控制一览](…/blog/images/02-chatclient-parameter-control.png null)5.1 全局系统提示词// 在构建 ChatClient 时设置全局系统提示词BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultSystem(你是一位专业的 Java 后端开发专家擅长 Spring Boot、Spring Cloud、Spring AI回答问题时优先给出代码示例用【技术要点】【代码示例】【运行说明】三段式结构回答。).build();}5.2 每次调用的局部系统提示词// 覆盖默认系统提示词StringreplychatClient.prompt().system(SystemPromptTemplate.from(你是{role}用{style}风格回答用户问题。用户所在地{location}).render(Map.of(role,宠物医生,style,专业但通俗易懂,location,北京))).user(我的猫最近食欲不振怎么办).call().content();5.3 PromptTemplate 模板// 定义一个 Prompt 模板PromptTemplatepromptTemplatePromptTemplate.from(请为以下{language}代码添加中文注释并说明每段逻辑的作用\n\n{code}\n);// 渲染模板PromptpromptpromptTemplate.render(Map.of(language,Java,code,public class Demo { public static void main(String[] args) { System.out.println(\Hello\); } }));StringresultchatClient.prompt(prompt).call().content();6. 异常处理6.1 Spring AI 异常体系// 异常继承结构SpringAIException(Root)├──RateLimitException// 速率限制├──BadRequestException// 参数错误400├──AuthenticationException// 认证失败401├──AuthorizationException// 权限不足403├──ResourceNotFoundException// 资源不存在404└──AIException// AI 模型通用错误6.2 全局异常处理RestControllerAdvicepublicclassGlobalExceptionHandler{ExceptionHandler(RateLimitException.class)publicResponseEntityMapString,ObjecthandleRateLimit(RateLimitExceptionex){returnResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(Map.of(error,请求过于频繁请稍后再试,retryAfter,30));}ExceptionHandler(AuthenticationException.class)publicResponseEntityMapString,StringhandleAuth(AuthenticationExceptionex){returnResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(error,API Key 无效或已过期));}ExceptionHandler(AIException.class)publicResponseEntityMapString,StringhandleAI(AIExceptionex){log.error(AI 调用异常,ex);returnResponseEntity.status(HttpStatus.BAD_GATEWAY).body(Map.of(error,AI 服务暂时不可用请稍后重试));}}7. 完整示例从配置到 Controller7.1 配置类ConfigurationpublicclassChatConfig{Value(${spring.ai.openai.api-key})privateStringapiKey;BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultSystem(你是 AI 助手请专业、简洁地回答用户问题。).build();}}7.2 服务类ServiceRequiredArgsConstructorpublicclassAiChatService{privatefinalChatClientchatClient;publicStringchat(Stringmessage){returnchatClient.prompt().user(message).call().content();}publicFluxStringchatStream(Stringmessage){returnchatClient.prompt().user(message).stream().content();}}7.3 ControllerRestControllerRequestMapping(/api/ai)RequiredArgsConstructorpublicclassAiController{privatefinalAiChatServiceaiChatService;/** * 同步对话 */GetMapping(/chat)publicResponseEntityMapString,Stringchat(RequestParamStringmessage){if(messagenull||message.isBlank()){returnResponseEntity.badRequest().body(Map.of(error,消息不能为空));}StringreplyaiChatService.chat(message);returnResponseEntity.ok(Map.of(reply,reply,timestamp,String.valueOf(System.currentTimeMillis())));}/** * 流式对话SSE */GetMapping(value/chat/stream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatStream(RequestParamStringmessage){returnaiChatService.chatStream(message);}}7.4 application.ymlspring:application:name:spring-ai-demoai:openai:api-key:${OPENAI_API_KEY}base-url:https://api.openai.comchat:options:model:gpt-4o-minitemperature:0.7server:port:80807.5 启动测试# 测试同步接口curlhttp://localhost:8080/api/ai/chat?messageSpringAI是什么# 测试流式接口curl-Nhttp://localhost:8080/api/ai/chat/stream?message用三句话介绍Java8. 常见问题排查| 问题 | 原因 | 解决方案 ||------|------|---------||401 Unauthorized| API Key 错误或过期 | 检查OPENAI_API_KEY环境变量 ||429 Too Many Requests| 请求频率超限 | 添加限流或等待重试 ||SocketTimeoutException| 网络超时 | 增大超时时间或检查网络 || 返回空字符串 | 模型未正确配置 | 检查 base-url 和 model || 流式无响应 | 前端未正确解析 SSE | 检查Accept: text/event-stream|| 响应乱码 | 编码问题 | 确保 UTF-8 配置正确 |9. 小结本文围绕 ChatClient 做了全面深入的实战讲解四种调用方式同步调用、流式调用、带参数调用、结构化对象返回多轮对话手动管理历史 vs Advisors 自动管理推荐系统提示词全局默认 局部覆盖 模板渲染异常处理Spring AI 完整异常体系与全局处理完整示例从配置 → 服务类 → Controller → 测试下一篇预告【Spring AI 实战】三、Prompt 工程模板化、结构化输出与 Advisors 顾问模式——我们将深入 Prompt 工程的核心技巧包括 Few-Shot 提示、Chain of Thought 推理、Advisors 拦截器链的高级用法以及如何引导模型输出严格符合预期的 JSON 结构。 系列导航← 上一篇【Spring AI 实战】一、Spring AI 是什么→ 下一篇【Spring AI 实战】三、Prompt 工程与 Advisors→ 完整目录 示例说明本文以入门链路为主若你准备做生产级接入建议继续阅读第三篇和第四篇。

更多文章