Spring AI 实战:ChatClient 链式调用与多轮对话上下文管理

发布时间:2026/10/14 3:41:13
Spring AI 实战:ChatClient 链式调用与多轮对话上下文管理 1. 从配置文件到对话窗口Spring AI 到底解决了什么问题第一次接触 Spring AI 是在一个内部工具项目里当时团队想给已有的后台管理系统加一个智能问答入口需求很明确用户在页面上输入问题后端调用大模型返回答案最好还能带上上下文记忆。摆在面前的选择其实不少可以用 HTTP 客户端直接调接口也可以引入某些专门的 SDK。但真正动手之后才发现裸调接口要处理的东西远比想象中多——请求体拼装、鉴权头、超时重试、流式响应解析、多轮对话的消息列表维护每一项单独看都不难凑在一起就是一堆重复的胶水代码。Spring AI 的价值就在这里。它把大模型调用抽象成了 Spring 生态里熟悉的模式配置写在application.yml里客户端通过依赖注入拿到调用方式是一套链式 API。你不需要关心底层用的是哪家的模型服务只要配置项对得上代码基本不用改。这对已经在用 Spring Boot 的团队来说迁移成本几乎为零。这篇文章面向的是有 Java 和 Spring Boot 基础、想快速把大模型能力接进自己项目的开发者。我会从配置讲起把ChatClient的链式调用拆开揉碎重点说清楚每一步在做什么、为什么这么设计以及我在实际调试中踩过的那些坑。读完你应该能独立完成一个可用的对话接口并且知道出问题时该往哪个方向排查。需要提前说明的是Spring AI 目前仍在快速迭代不同版本之间 API 有差异。我下面基于的是较为稳定的一套用法如果你用的版本对不上优先以官方文档为准但思路是相通的。2. 环境准备与依赖配置把地基打牢2.1 版本选择与依赖引入Spring AI 对 Spring Boot 版本有要求早期版本需要 Boot 3.2 以上较新的版本基本要求 3.3 或更高。我建议直接用当前项目能接受的最高稳定版因为低版本经常出现某个 API 还没开放、或者配置项名称对不上的情况。依赖引入有两种常见方式。如果你用的是 Spring Boot 的依赖管理可以在pom.xml里先声明 BOM再引入具体的 starterdependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency /dependencies这里有个细节值得说starter 的命名通常对应模型服务提供方比如openai、ollama、azure-openai等。选哪个 starter 决定了你后面 yml 里配置项的前缀。我见过有人引入了 openai 的 starter却在配置里写 ollama 的字段结果启动直接报绑定失败排查半天才发现是依赖和配置不匹配。注意如果你的项目用了 Maven 的镜像仓库确认该仓库已经同步了 Spring AI 的构件。部分内部镜像同步有延迟会出现依赖下载不到的情况这时候临时切到中央仓库拉一次即可。2.2 yml 配置项逐条拆解配置是整个体验里最省心的部分但也是最容易出错的环节。一个典型的配置长这样spring: ai: openai: api-key: ${AI_API_KEY} base-url: https://api.example.com chat: options: model: gpt-4o-mini temperature: 0.7 max-tokens: 2048逐条说。api-key我强烈建议用环境变量注入不要硬编码在文件里。原因很直接配置文件大概率会进版本库密钥一旦提交清理起来非常麻烦就算后面删掉历史记录里依然存在。用${AI_API_KEY}这种占位符本地开发时在 IDE 的运行配置里设环境变量部署时由运维注入是最稳妥的做法。base-url这一项很多人会忽略。它的作用是覆盖默认的服务地址适用于你走的是自建网关或者代理层的情况。如果你的调用链路中间有一层统一鉴权和计费的网关这一项就必须配否则请求会直接打到默认地址上。model决定用哪个模型。这里的选择直接影响响应速度、成本和回答质量。小模型快且便宜适合做分类、抽取这类任务大模型慢但理解能力强适合开放式问答。我的经验是先用小模型跑通链路确认流程没问题后再按需切换。temperature控制输出的随机性取值一般在 0 到 2 之间。做事实性问答时调低比如 0.2 到 0.3让回答更稳定做创意类生成时调高0.8 以上会有更多变化。max-tokens限制单次响应的最大长度设太小会导致回答被截断设太大则可能浪费额度一般 1024 到 4096 之间比较合理。2.3 启动自检怎么确认配置真的生效了配置写完不代表就能用。我习惯在项目里加一个简单的启动自检注入ChatClient后发一句固定的话看能不能拿到响应。这一步能提前暴露大部分配置问题比等到业务代码里再发现要高效得多。自检时重点观察三类报错一是鉴权类通常是密钥无效或过期二是地址类表现为连接超时或 404三是模型类提示模型不存在或无权限。这三类的排查方向完全不同先定位到类别再往下查会快很多。3. ChatClient 链式调用四步拆解3.1 第一步构建 ChatClient 实例ChatClient不是直接 new 出来的而是通过构建器创建。典型写法是注入一个ChatClient.Builder然后调用build()Service public class ChatService { private final ChatClient chatClient; public ChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } }为什么用构建器而不是直接注入成品因为构建器允许你在创建时预设一些默认行为比如默认的系统提示词、默认的模型选项。这样每个业务方法调用时就不用重复设置这些公共部分。我一般会把系统提示词放在构建阶段比如设定“你是一个严谨的技术助手回答要简洁准确”这样所有走这个客户端的请求都自带这个角色设定。如果你需要多个不同角色设定的客户端可以构建多个实例分别注入使用。比如一个用于客服问答一个用于代码解释各自的系统提示词不同互不干扰。3.2 第二步设置提示词内容链式调用的起点是prompt()它返回一个可以继续链式操作的对象。设置用户输入用user()chatClient.prompt() .user(帮我解释一下什么是幂等性) .call() .content();user()接收的是用户侧的消息。除了它还有system()用来设置系统提示词可以在单次调用时覆盖构建阶段的默认值。两者的区别在于角色定位系统提示词是给模型的全局指令用户消息是具体的任务输入。这里有个容易混淆的点prompt()之后可以接多个消息设置方法它们的顺序会影响最终发给模型的消息列表顺序。一般来说系统消息在前用户消息在后这个顺序符合大多数模型服务的预期。如果你把顺序搞反了某些模型可能会给出奇怪的响应。3.3 第三步发起调用call()是真正触发网络请求的方法。调用它之前的所有操作都只是在组装请求参数只有执行到call()才会把请求发出去。这个设计的好处是组装过程可以灵活拼接比如根据条件动态添加消息、动态调整参数最后统一提交。call()返回的是一个响应对象你可以从中取出不同形式的结果。最常用的是content()直接拿字符串内容。如果需要更详细的信息比如 token 消耗、结束原因可以取完整的响应对象再逐项读取。提示call()是阻塞式的会一直等到模型返回完整结果。如果你的场景需要边生成边展示比如打字机效果那要用流式接口这个后面单独说。3.4 第四步提取响应内容content()是最直接的提取方式返回模型生成的文本。但实际项目里直接返回原始文本往往不够你可能需要做后处理去掉多余的空格、解析成 JSON、或者按特定格式拆分。我遇到过一个典型问题模型返回的内容里带了 Markdown 代码块标记前端直接渲染会显示成纯文本。解决办法是在提取后做一次清洗把代码块标记去掉或者在前端按 Markdown 渲染。这个处理放在哪一层要看你的架构我倾向于在后端做基础清洗把格式统一了再返回给前端。如果模型返回的是结构化数据比如你要求它输出 JSON那提取后还要做反序列化。这里要加异常处理因为模型偶尔会输出不符合 JSON 规范的内容直接解析会抛异常。稳妥的做法是先尝试解析失败时走降级逻辑比如返回一个默认结构或者提示用户重试。4. 多轮对话与上下文管理4.1 为什么需要手动维护上下文大模型本身是无状态的每次调用都是独立的。它不会记得你上一句说了什么除非你把历史消息一起发过去。这就是多轮对话需要额外处理的原因。Spring AI 提供了消息列表的抽象你可以把历史消息按顺序存起来每次调用时全部带上。消息类型主要有系统消息、用户消息、助手消息三种分别对应不同的角色。助手消息就是模型之前的回复把它加进列表模型才能“看到”自己说过什么。4.2 上下文窗口的取舍把全部历史都带上是最简单的做法但有两个问题。一是 token 消耗会随对话轮次线性增长成本上升很快二是模型有上下文长度上限超过之后要么报错要么被截断导致早期信息丢失。我的做法是设置一个轮次上限比如只保留最近十轮对话。超过之后从最早的开始丢弃。这样既能维持基本的连贯性又控制了成本。如果某些早期信息很重要可以在系统提示词里做摘要把关键结论固化进去而不是依赖原始对话记录。还有一种更精细的做法是做摘要压缩当历史消息超过一定长度时调用模型把前面的对话总结成一段简短描述用这段描述替代原始消息。这样能保留语义但大幅减少 token。实现起来稍复杂适合对成本敏感且对话轮次很长的场景。4.3 会话隔离的实现多个用户同时使用时必须保证各自的对话历史互不干扰。常见的做法是用会话 ID 做键把消息列表存在内存或外部存储里。内存存储适合单机部署简单但重启会丢外部存储比如 Redis 适合多实例部署需要处理序列化和过期策略。这里有个坑如果会话 ID 生成得不够唯一比如用时间戳高并发下可能撞车导致两个用户的对话串在一起。用 UUID 或者用户 ID 加时间戳的组合会更稳妥。另外记得给会话数据设过期时间不然长期运行会积累大量无用数据。5. 流式响应与参数调优5.1 流式输出的实现方式前面说的call()是等全部生成完再返回。如果回答很长用户会盯着空白页面等好几秒体验不好。流式输出可以让内容边生成边推送用户很快就能看到开头。Spring AI 的流式接口返回的是一个流对象你可以订阅它每收到一段就推给前端。前端用 SSE 或者 WebSocket 接收逐段追加显示。实现上要注意两点一是流的关闭客户端断开时要及时释放资源二是异常处理流中途出错要有兜底不能让连接一直挂着。5.2 关键参数的调整经验除了前面提到的temperature和max-tokens还有几个参数值得关注。top-p控制采样范围和 temperature 配合使用一般调一个就行两个都调容易顾此失彼。frequency-penalty和presence-penalty用来抑制重复内容当发现模型老是重复同一句话时可以适当调高。调参没有万能公式我的习惯是先固定其他参数只调一个观察输出变化找到合适的值再调下一个。每次调整后拿同一组测试问题跑一遍对比结果这样比凭感觉调要靠谱。5.3 超时与重试策略网络请求总有失败的可能超时设置很关键。设太短正常的长回答会被中断设太长出问题时用户要等很久。我的经验是连接超时设短一点比如 5 秒读取超时根据业务设长一些比如 60 秒。重试要谨慎因为大模型调用通常是有成本的无脑重试可能造成重复计费。只对明确的网络类错误重试且限制重试次数。6. 常见问题排查速查6.1 启动阶段问题现象可能原因排查方向启动报配置绑定失败依赖与配置前缀不匹配检查 starter 与 yml 前缀是否对应提示找不到 Bean未引入对应 starter确认依赖已正确引入并刷新密钥读取为 null环境变量未设置检查运行配置中的环境变量6.2 运行阶段问题现象可能原因排查方向401 或鉴权失败密钥无效或过期重新生成密钥并更新配置连接超时地址不通或网络受限确认 base-url 可达响应被截断max-tokens 过小适当调大该参数回答重复啰嗦惩罚参数未设置调整 frequency-penalty多轮对话失忆历史消息未带上检查消息列表组装逻辑6.3 几个容易忽略的细节第一个是编码问题。如果用户输入包含特殊字符传输过程中可能出现乱码。确保请求和响应都按 UTF-8 处理这个在大多数框架里是默认的但跨系统调用时要留意。第二个是日志脱敏。调试时打印请求内容很方便但生产环境要注意别把用户敏感信息打到日志里。我一般只在 debug 级别打印完整内容info 级别只记录长度和耗时。第三个是并发控制。大模型接口通常有速率限制并发太高会被限流。可以在客户端做一层信号量控制或者用队列削峰。具体阈值要看服务方的限制压测一下心里就有数了。7. 我在实际项目中的几点体会接入过程整体比预想的顺利配置加代码不到半天就跑通了。真正花时间的是调优和异常处理这部分没有捷径只能在实际使用中慢慢磨。有一点想提醒不要一上来就追求完美。先把主链路跑通能问答、能多轮、能流式然后再逐步加缓存、加限流、加监控。我见过有团队在初期就设计了一套复杂的抽象层结果模型服务一换抽象层全要重写反而拖慢了进度。另外模型输出天然带有不确定性业务代码里要做好容错。凡是依赖模型返回格式的地方都要有解析失败的兜底方案。把它当成一个可能出错的远程服务来对待而不是一个确定性的函数心态上会从容很多。最后分享一个小技巧把常用的提示词模板抽出来做成配置不要硬编码在代码里。这样调整措辞时不用改代码重新部署改配置重启即可迭代效率会高不少。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询