)
一、前言从本章开始连续五章深入 Spring AI 的模型层。前面六章一直用ChatClient和ChatModel本章以OpenAI 协议客户端OpenAiChatModel 为例拆解如何配置和连接含 2.x 配置路径迁移、DeepSeek 兼容聊天模型核心参数同步 vs 流式调用与 MediaType 选型运行时选项覆盖与自定义默认模型解析自动配置开关spring.ai.model.chat的语义常见坑DeepSeek 兼容网关、seed 不生效、手动 Bean 不合并默认 model 等注意OpenAiChatModel只是OpenAI 协议客户端base-url指向 DeepSeek / 通义 / Ollama 时也能用但 model 名必须后端认得。二、前置准备2.1 依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency2.2 配置按版本分栏Spring AI 1.x对应 Boot 3.4/3.5spring: ai: openai: base-url: https://api.openai.com/v1 api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 max-tokens: 5001.x 官方属性表明确spring.ai.openai.chat.options.model默认gpt-4o-mini。Spring AI 2.0.0 GA对应 Boot 4.x2.0 官方博客Decoupling of options and configuration properties allowed theremoval of the artificial .options segment in application property keys。spring: ai: openai: base-url: https://api.openai.com/v1 api-key: ${OPENAI_API_KEY} chat: model: gpt-4o-mini # 平铺不带 options 段 temperature: 0.7 max-tokens: 5002.0 源码OpenAiChatProperties绑定前缀spring.ai.openai.chat顶层有getModel()getOptions()标Deprecated(for removal)。DeepSeek 兼容配置2.0 推荐spring: ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: model: deepseek-chat temperature: 0.7 max-tokens: 2048DeepSeek 官方兼容 OpenAI 协议可直接用OpenAiChatModel客户端。模型名填deepseek-chat老版或deepseek-v4-proV4 网关。三、自动配置3.1 自动配置做了什么引入 starter 配api-key后Spring Boot 自动创建底层 HTTP 客户端用base-url/api-key把spring.ai.openai.chat.model等转成OpenAiChatOptions作为defaultOptions注册OpenAiChatModelBean实现ChatModel注册默认ChatClientBean前提是没被禁3.2 注入使用RestController public class ChatController { private final ChatModel chatModel; // 或 OpenAiChatModel public ChatController(ChatModel chatModel) { this.chatModel chatModel; } GetMapping(/ai/generate) public MapString, String generate(RequestParam(defaultValue 讲个笑话) String message) { return Map.of(generation, chatModel.call(message)); } }3.3 启用/禁用总开关spring: ai: model: chat: openai # 只启 OpenAI 自动配置 # chat: none # 全禁聊天模型自动配置spring.ai.model.chat是单值等于某厂商名 → 该厂启用等于none或任何不匹配的值 → 所有厂 ChatModel 自动配置都不建 Bean。3.4 与第一章ChatClient的区别维度第一章ChatClient本章OpenAiChatModel类型通用门面OpenAI 协议具体实现调用.prompt().user().call().content().call(String)或.call(Prompt)耦合低换厂不改代码高绑定 OpenAI 协议专属参数需借OpenAiChatOptions向下转型直接用OpenAiChatOptions学模型特性用具体类业务开发用ChatClient。3.5spring.ai.model.chat取值语义值效果openai只启 OpenAI ChatModel 自动配置Anthropic 等被关anthropic只启 Anthropicnone全部 ChatModel 自动配置关闭不写各 starter 按自身条件走openai starter 默认启坑点联动加spring.ai.model.chatopenai后启动报AnthropicChatModel找不到——因为 Anthropic 自动配置条件不满足但代码硬注入AnthropicChatModel→ 失败。解决删 Anthropic 依赖/代码或不写该开关让两厂共存。四、核心配置参数4.1 连接参数属性2.x 平铺 / 1.x 在 chat 下说明默认spring.ai.openai.base-urlAPI 地址https://api.openai.comspring.ai.openai.api-key密钥-spring.ai.openai.timeout超时60sspring.ai.openai.max-retries重试34.2 模型参数按版本两栏1.xspring.ai.openai.chat.options.model/.temperature/.max-tokens/.top-p/.frequency-penalty/.presence-penalty/.stop/.seed/.n默认 modelgpt-4o-mini。2.x去掉options段 →spring.ai.openai.chat.model/.temperature/.max-tokens…配置路径易错点2.x 写spring.ai.openai.chat.options.model→ artificial segment 已移除不绑定1.x 写spring.ai.openai.chat.model带 chat 无 options→ 1.x 文档无此键不绑定回退默认gpt-4o-mini早期 0.xspring.ai.openai.modelgpt-3.5-turbo→ 已废弃优先级官方定义Prompt 级 OpenAiChatOptions ChatClient defaultOptions ChatModel defaultOptions spring.ai.openai.chat.model OpenAiChatOptions 内置常量默认⚠ 2.0 手动OpenAiChatModel下Prompt 不写 model 时不回退到 ChatModel 的 defaultOptions而是直接落 Options 内置常量默认gpt-4o-mini或gpt-5-mini。详见坑 8。4.3 重试spring.ai.retry.max-attempts10initial-interval2smultiplier5max-interval3min。五、同步调用 vs 流式调用5.1 同步GetMapping(/sync) public String syncCall(RequestParam(defaultValue 讲个关于程序员的笑话) String message) { return chatModel.call(message); }5.2 流式SSEGetMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxChatResponse streamCall(RequestParam(defaultValue 讲一个关于春天的故事) String message) { return chatModel.stream(new Prompt(new UserMessage(message))); }5.3 原理对比同步请求 → 计算 → 完整响应 → 返回 流式请求 → 计算 → 块1 → 块2 → ... → 块N逐块推5.4 流式接口的 MediaType 选型常量值用途TEXT_EVENT_STREAM_VALUEtext/event-streamSSE 流式5.2 用法APPLICATION_JSON_VALUEapplication/json同步 DTOTEXT_PLAIN_VALUEtext/plain同步纯文本APPLICATION_NDJSON_VALUEapplication/x-ndjson非 SSE 流式fetchReadableStreamMULTIPART_FORM_DATA_VALUEmultipart/form-data多模态上传流式接口必须显式producesTEXT_EVENT_STREAM_VALUE否则 MVC 按 json 协商前端EventSource解析乱码坑 5 根因。六、运行时选项覆盖6.1 为什么需要全局默认是gpt-4o-minitemperature0.7但数学题要temperature0复杂题要gpt-4o。6.2 代码示例GetMapping(/override) public String overrideOptions(RequestParam(defaultValue 解方程2x 3 7) String question) { ChatResponse response chatModel.call( new Prompt( question, OpenAiChatOptions.builder() .model(gpt-4o) .temperature(0.0) .maxTokens(300) .build() ) ); return response.getResult().getOutput().getText(); }官方文档At run-time you can override the default options by adding new, request specific, options to the Prompt call。6.3 覆盖规则运行时 Prompt 级OpenAiChatOptions覆盖 ChatModel 级 defaultOptions覆盖 yml 配置但不改全局默认值。⚠ 如果base-url指向 DeepSeek 网关这里写gpt-4o会 400supported models are deepseek-v4-*, but you passed gpt-4o。必须填后端白名单名如deepseek-chat或deepseek-v4-pro。七、手动配置7.1 何时手动不依赖 Boot 自动配置、要自定义 HTTP 客户端、要特殊 Key 管理、要多供应商路由。7.2 基础手动2.0 正确写法Bean Primary public ChatModel deepseekChatModel() { return OpenAiChatModel.builder() .options(OpenAiChatOptions.builder() .apiKey(System.getenv(DEEPSEEK_API_KEY)) .baseUrl(https://api.deepseek.com) .model(deepseek-chat) .temperature(0.7) .maxTokens(2048) .build()) .build(); }⚠ 2.0 已移除openAiApi(...)方法所有凭据通过OpenAiChatOptions传入。7.3 自定义 API Key从 Vault 取OpenAiChatOptions.builder() .apiKey(vaultService.getSecret(deepseek-api-key)) .baseUrl(https://api.deepseek.com) .model(deepseek-chat) .build()7.4 自定义默认模型解析Spring AI 2.x没有ModelSelectionStrategySPI。正确做法三选一(1) 启动期按 env 选手动 BeanBean Primary public ChatModel openAiChatModel() { String tier System.getenv(APP_LLM_TIER); String model pro.equals(tier) ? gpt-4o : gpt-4o-mini; return OpenAiChatModel.builder() .options(OpenAiChatOptions.builder() .apiKey(System.getenv(OPENAI_API_KEY)) .model(model) .temperature(0.7) .build()) .build(); }(2) 运行时按业务选Service 包装Service public class DynamicChatService { private final ChatModel chatModel; public DynamicChatService(ChatModel chatModel) { this.chatModel chatModel; } public String ask(String user, String scene) { String model switch (scene) { case math - gpt-4o; case casual - gpt-4o-mini; default - gpt-4o-mini; }; return chatModel.call(new Prompt(user, OpenAiChatOptions.builder() .model(model) .temperature(scene.equals(math) ? 0.0 : 0.7) .build())) .getResult().getOutput().getText(); } }(3) 多供应商路由base-url 不同Configuration public class MultiProviderConfig { Bean(openAiChatModel) public ChatModel openAiChatModel() { return OpenAiChatModel.builder() .options(OpenAiChatOptions.builder() .apiKey(System.getenv(OPENAI_API_KEY)) .model(gpt-4o) .build()) .build(); } Bean(deepseekChatModel) public ChatModel deepseekChatModel() { return OpenAiChatModel.builder() .options(OpenAiChatOptions.builder() .apiKey(System.getenv(DEEPSEEK_API_KEY)) .baseUrl(https://api.deepseek.com) .model(deepseek-chat) .build()) .build(); } Bean public MapString, ChatModel modelMap( Qualifier(openAiChatModel) ChatModel openAi, Qualifier(deepseekChatModel) ChatModel deepseek) { return Map.of( gpt-4o, openAi, gpt-4o-mini, openAi, deepseek-chat, deepseek ); } }合并优先级钉死Prompt Options ChatClient defaultOptions ChatModel defaultOptions yml chat.model 内置常量默认⚠ 2.0 手动OpenAiChatModel下call(new Prompt(text, opts))中 opts 不写 model 时不回退到 ChatModel 的 defaultOptions而是直接落OpenAiChatOptions内置常量gpt-4o-mini或gpt-5-mini。要在调用时不写 model 且不 400要么用自动配置要么调用时显式.model(defaults.getModel())要么用ChatClient.defaultOptions封装。八、模型参数详解8.1 Temperature0 最确定0.7 创意2 很随机。事实题 0~0.3代码 0.3闲聊 0.7脑暴 0.9。OpenAI 官方不建议同时改 temperature 和 top_p。8.2 Max Tokens中文 1 token ≈ 1.5~2 字。短答 100~200问答 300~500长文 1000。8.3 Top-P核采样通常只调 temperature 其一。8.4 Seed随机种子OpenAI 官方原文If specified, our system will make abest effort to sample deterministically… ——不保证 100% 确定。实测对比DeepSeek 后端后端seed123 两次调用OpenAI 官方 temperature0 seed123相同best effortDeepSeek V4deepseek-v4-pro seed123第一次「风把月光折成信笺悄悄塞进半掩的窗。」第二次「行到水穷处坐看云起时。」→不同结论seed仅 OpenAI 官方后端有效且需搭配temperature0DeepSeek / 通义 / Ollama 兼容层忽略 seed非 OpenAI 后端要确定性只能试temperature0并在目标后端实测// OpenAI 官方可靠写法 OpenAiChatOptions.builder().model(gpt-4o).temperature(0.0).seed(42).build(); // DeepSeek 后端seed 写了也没用只留 temperature(0) OpenAiChatOptions.builder().model(deepseek-chat).temperature(0.0).build();九、踩坑记录坑 1API Key 未配 → 401现象启动报错AuthenticationError: 401 Incorrect API key provided。原因spring.ai.openai.api-key未设置或设置错误。解决检查环境变量和配置文件。坑 2model 名后端不认 → 400现象调用时报400 The supported API model names are deepseek-v4-*, but you passed gpt-4o。原因base-url指向 DeepSeek 网关但 model 填了 OpenAI 的gpt-4o或默认gpt-4o-mini。解决model 填后端白名单名deepseek-chat/deepseek-v4-pro/deepseek-v4-flash。坑 3maxTokens 超上下文 → 400现象调用时报400 context_length_exceeded。原因请求的 token 总数超过了模型的上下文窗口。解决减少maxTokens或缩短输入文本。坑 4temperature 和 top_p 同时设置现象模型行为不可预测。原因OpenAI 官方建议不要同时修改这两个参数。解决只调整其中一个。坑 5流式响应乱码现象流式返回的中文出现乱码。原因未正确设置 Content-Type。解决GetMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE)坑 6spring.ai.model.chatopenai导致其他厂商报错现象加了spring.ai.model.chatopenai后启动报错提示找不到AnthropicChatModel。原因spring.ai.model.chatopenai只启用了 OpenAI 的 ChatModel 自动配置同时关掉了其他厂商如 Anthropic的自动配置。但代码中还在注入AnthropicChatModel。解决方案一只保留 OpenAI删掉 Anthropic 的依赖和相关代码方案二不写spring.ai.model.chat让各厂商各管各的方案三用Autowired(required false)让注入变为可选坑 7seed在 DeepSeek 下不生效现象相同的seed值两次调用返回不同结果。原因seed是 OpenAI 特有参数DeepSeek 兼容网关忽略该字段。解决DeepSeek 下用temperature0代替seed并在目标后端实测。坑 82.0 手动OpenAiChatModel不写 model 兜底 SDK 默认常量现象手动OpenAiChatModel.builder().options(...)创建 Bean调用时new Prompt(text, OpenAiChatOptions.builder().temperature(x).build())不写.model()报 400but you passed gpt-4o-mini。原因Spring AI 2.0 手动OpenAiChatModel下Prompt 级 Options model 为 null 时不回退到 ChatModel 的 defaultOptions而是直接落OpenAiChatOptions内置常量gpt-4o-mini或gpt-5-mini。解决方案一删掉手动 Bean用自动配置yml 配好即可方案二调用时显式.model(defaults.getModel())合并 defaultOptions方案三用ChatClient.defaultOptions封装让 ChatClient 做合并坑 92.0 手动 Bean 必须显式传 apiKey现象启动报At least one credential source must be specified: credential (apiKey), workloadIdentity, or adminApiKey。原因手动OpenAiChatModel.builder().options(...)时OpenAiChatOptions里没有apiKey且 yml 的spring.ai.openai.api-key不会被手动 Bean 继承。解决在OpenAiChatOptions里显式传.apiKey(System.getenv(DEEPSEEK_API_KEY))。十、速查表要做的事写法2.x 配默认模型spring.ai.openai.chat.modeldeepseek-chat1.x 配默认模型spring.ai.openai.chat.options.modelgpt-4o-mini禁用自动配置spring.ai.model.chatnone同步调用chatModel.call(msg)流式chatModel.stream(prompt)TEXT_EVENT_STREAM_VALUE运行时换模型OpenAiChatOptions.builder().model(gpt-4o).build()自定义默认手动Bean ChatModeldefaultOptions确定性输出(OpenAI)temperature(0.0).seed(N)确定性输出(DeepSeek)temperature(0.0)seed 无效十一、总结本章以OpenAiChatModel为抓手讲清了Spring AI 2.x 配置扁平化chat.model去.options、spring.ai.model.chat单值开关语义、Prompt 级 Options 覆盖链、手动 Bean 在 2.0 下必须显式传 apiKey 且不写 model 会兜底 SDK 常量的陷阱、SSE 必须显式 MediaType、seed 仅 OpenAI best-effort 有效。核心一句OpenAiChatModel是协议客户端model 名永远要和后端的白名单对齐2.0 手动 Bean 不继承 yml必须自己把凭据和默认 model 写进 Options。巩固练习练习一蓝兔性格深化⭐要求修改 System Prompt为蓝兔增加以下性格特征出身玉蟾宫气质高雅冰魄剑法讲究以静制动所以她遇事从容不迫虽然外表温柔但关键时刻非常果断验证这些性格特征能在对话中体现出来验收标准输入你是什么来历回复提到玉蟾宫输入遇到紧急情况怎么办回复体现从容冷静输入有人欺负我回复既有温柔安慰又有果断态度练习二七侠角色切换⭐⭐要求创建七个 ChatClient Bean分别对应七侠通过参数切换不同角色每个角色有自己的性格、特长和语气提示在 Controller 中接收hero参数取值lanTu、hongMao、shaLi、douDou、daBen、tiaoTiao、daDa根据 hero 参数选择对应的 ChatClient Bean每个角色的 system prompt 体现其性格和特长验收标准?herolanTumessage帮我制定学习计划→ 蓝兔给出学习方法建议?herohongMaomessage帮我制定学习计划→ 虹猫给出计划安排?herodouDoumessage我最近睡不好→ 逗逗给出健康建议?herodaDamessage我想写一首诗→ 达达帮忙创作练习三可选七侠合璧对话⭐⭐⭐要求创建一个特殊接口一次请求同时调用多个七侠角色针对同一个问题让不同角色从各自擅长的角度给出回答最后汇总成一个完整的回复提示使用RequestParam ListString heroes接收多个角色并行调用多个 ChatClient用CompletableFuture或Flux.merge汇总格式示例[蓝兔的建议] ... [虹猫的计划] ... [逗逗的健康提醒] ...验收标准输入?heroeslanTu,hongMao,douDoumessage我要考研回复包含蓝兔的学习方法 虹猫的复习计划 逗逗的作息建议三个部分清晰分隔各有署名十二、参考链接Spring AI 2.0 GA 博客移除 artificial .options segmentSpring AI 2.0.0 GA Available NowSpring AI 1.1 OpenAI Chat 属性表OpenAI Chat :: Spring AI ReferenceSpring AI 2.0 OpenAiChatProperties JavadocOpenAiChatProperties (Spring AI Parent 2.0.1 API)OpenAI seed best-effort 说明Docker Model Runner Chat :: Spring AI ReferenceDeepSeek 官方 API 文档DeepSeek