ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringAI整合DeepSeek构建AI聊天对话的完整实践指南

基于SpringAI整合DeepSeek构建AI聊天对话的完整实践指南 如果只是想在项目里加一个“能聊天的AI”大多数人第一反应就是拿 DeepSeek 的 API 文档写个 HTTP 调用拼 JSON、发请求、解析返回。可一旦你开始认真做要处理的就不止“发一条消息”这么简单了——历史消息怎么存、上下文怎么传、流式输出怎么推给前端、超时重试怎么办、用户并发怎么隔离这些琐碎问题很快会把代码撑成一坨难以维护的“腊肠”。我后来在 Spring Boot 项目里换成了 SpringAI 这套方案用官方 OpenAI 客户端去对接 DeepSeek 的兼容接口整体思路一下子清爽很多。这篇文章不是抄官方文档是我实际把“基于 SpringAI 整合 DeepSeek 模型实现 AI 聊天对话”从头搭到尾的经验记录包含依赖版本、最小配置、多轮会话记忆、上下文窗口控制、流式输出对接前端、以及生产环境常见的坑。适合正在用 Java 做后端、想在现有系统里快速集成大模型对话能力的开发者参考。1. 为什么我不直接裸调 DeepSeek API而要套一层 SpringAI1.1 DeepSeek 的接口兼容性决定了 SpringAI 能轻松对接先理清一个关键事实DeepSeek 官方提供的 API 是 OpenAI 兼容协议。什么意思就是它对外暴露的请求体结构、鉴权方式、返回格式跟 OpenAI 的 Chat Completions 接口基本一致只是 base-url 换成了 DeepSeek 的地址模型名换成了deepseek-chat或deepseek-reasoner。这个兼容性太重要了。因为 SpringAI 内置了针对 OpenAI 协议的客户端实现我们不需要自己写 provider、不需要实现一套新的模型适配器只要把 SpringAI 的 OpenAI Starter 引进来再把 base-url 指向 DeepSeek 就行。SpringAI 是 Spring 官方推出的 AI 应用开发框架它做的事跟 JDBC 在数据库领域的角色有点像给你一套统一的抽象ChatClient、ChatModel、ChatMemory屏蔽掉不同模型厂商的协议差异。今天你接 DeepSeek明天想换别的兼容 OpenAI 协议的模型改几行配置就能切过去业务代码不用动。1.2 裸调 HTTP 时我真正烦的是什么可能有人觉得DeepSeek 接口那么简单用RestTemplate调不就行了我在最初的原型阶段确实这么干过但一旦需求从“单轮问答”变成“完整的聊天对话”麻烦就来了。第一是历史消息管理。DeepSeek 的大模型本身没有记忆每次请求都得把之前的对话记录按messages数组传进去。这意味着你要自己维护会话历史还要考虑历史太长时怎么裁剪——全传进去早晚撞上上下文上限传太少又会影响模型对上下文的理解。第二是流式输出。聊天场景如果等模型把整段内容生成完再一次性返回用户可能盯着空白页面十几秒。要做打字机效果就得处理 SSE 流式响应裸调时这块解析代码写起来非常啰嗦。第三是异常处理和重试。DeepSeek 接口在并发高时会返回 429 限流网络抖动会超时裸调时这些逻辑全部要自己写而且每个调用点都要处理代码重复率极高。第四是会话隔离。一套聊天服务通常要服务多个用户每个用户有独立的会话 ID历史消息不能串。自己做的话要设计存储结构、并发控制又是一个不小的工程。SpringAI 的价值就是把上面这些通用能力封装好了ChatClient负责发起对话ChatMemory负责存历史消息MessageChatMemoryAdvisor负责把历史消息自动注入到请求里。我需要写的业务代码量直接下降一个数量级。1.3 三种常见接入方式的选型对比我把实际接触过的几种方案放在一起做了个对比给大家选型时参考方案多轮会话流式输出异常重试切换模型成本学习成本适合场景裸调 HTTPRestTemplate/HttpClient自己实现自己解析 SSE自己实现高要改代码低只是临时验证 API 可用性使用 DeepSeek 官方 SDK自己实现官方 SDK 支持部分支持中中需要快速写 Python 或 Node 脚本SpringAI OpenAI Starter 指向 DeepSeek框架内置 Advisor 自动处理原生 Flux 流式统一 Retry 配置低改配置即可中低Spring Boot 开发者上手快Java/Spring 项目集成首选我的结论很直接如果你在维护 Spring Boot 项目不用犹豫选 SpringAI。它不是什么“重框架”本质上就是给 Spring 生态补上了大模型接入的标准姿势。2. 环境准备与依赖配置这里有一半的人会卡住2.1 版本搭配是第一个最容易翻车的点SpringAI 这个项目迭代非常快API 在不同版本之间变过不少次。我用 0.8.x 跑通过一版后来升到 1.0.0 正式版发现ChatClient的构建方式完全变了。如果你网上去搜教程搜到 0.8.x 的写法拿到 1.0.x 项目里跑连编译都过不去。我实际使用的组合是Spring Boot 3.4.xSpring AI 1.0.0JDK 17Maven 里通过 BOM 方式统一管理版本避免各个 SpringAI 组件版本不一致导致冲突dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement然后引入核心依赖。这里要特别注意因为 DeepSeek 兼容 OpenAI 协议我们引入的是 SpringAI 的 OpenAI 模块而不是什么“DeepSeek Starter”dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency引入这个 Starter 后ChatClient、ChatModel、ChatMemory这些核心类就都在了。2.2 application.yml 最小配置SpringAI 对接 OpenAI 协议客户端的配置入口是spring.ai.openai。对接 DeepSeek 时三样东西必须配对API Key、base-url、模型名。spring: application: name: ai-chat-service ai: openai: api-key: ${DEEPSEEK_API_KEY} base-url: https://api.deepseek.com chat: options: model: deepseek-chat temperature: 0.7DEEPSEEK_API_KEY放在环境变量里别写到配置文件提交到 Git 仓库这是底线。关于 base-url我实测过两个地址都能用https://api.deepseek.com和https://api.deepseek.com/v1。DeepSeek 官方对这两个路径都做了兼容SpringAI 的 OpenAI 客户端会在 base-url 后面自动拼接/chat/completions所以上面这种写法拼出来就是https://api.deepseek.com/chat/completions正好是 DeepSeek 支持的标准路径。2.3 先用 curl 验证你的 API Key 是否可用很多人配了半天发现 401问题不一定出在 Spring 代码里而是 API Key 本身就有问题。建议先在命令行验证一把curl https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -d { model: deepseek-chat, messages: [ {role: user, content: 你好请用一句话介绍你自己} ] }能正常返回带content的 JSON说明 Key 和网络都没问题再回过来排查 Spring 配置。这个习惯能帮你把“配置问题”和“代码问题”快速切分开。2.4 401/404 的常见排查顺序如果启动后调用直接报错按这个顺序排查确认 API Key 有没有被正确注入可以在配置里临时logging.level.org.springframework.aiDEBUG打开请求日志看实际发出的 Authorization 头是否正常。确认 base-url 有没有写错。常见错误是写成https://api.deepseek.com/chat/completions再把完整路径也配进去结果 SpringAI 拼出双份/chat/completions。确认模型名对不对。DeepSeek 目前主流是deepseek-chat和deepseek-reasoner拼错模型名会返回模型不存在的报错。按这个顺序走下来绝大部分配置层面的问题都能定位到。3. 从单轮到多轮写一个能记住上下文的聊天服务3.1 为什么说“多轮对话”才是真正的分水岭单轮对话的代码非常短Service public class ChatService { private final ChatClient chatClient; public ChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }几行就完事。但仔细想想这种服务基本没法直接拿来当“聊天机器人”用用户上一句问“我老婆是程序员”下一句问“你觉得她适合学什么”模型根本没有上一句的上下文回答就是无本之木。DeepSeek 的 API 是无状态的。你要让对话“连续”就得把之前的消息打包进每次请求。这就是热搜里经常有人问“deepseek 怎么继承上一个对话”的根源——不是模型不支持是你没在应用层做记忆。3.2 ChatMemory把会话历史从业务代码里解放出来SpringAI 提供了一套会话记忆抽象核心是两个接口/类ChatMemory负责按会话 ID 存取消息列表。框架自带InMemoryChatMemory用 ConcurrentHashMap 存单机原型完全够用。MessageChatMemoryAdvisor一个 Advisor拦截器在每次发起请求前自动从ChatMemory里取出历史消息拼到 API 请求中。它们之间通过一个conversationId会话 ID关联。相当于每个用户一个“聊天档案袋”谁说的话都记在自己的档案里。InMemoryChatMemory的问题也很明显服务重启就丢多实例部署时会话不共享。生产环境我建议自己实现一个基于 Redis 的ChatMemory接口本身很简单实现add、get、clear几个方法就行。下面先用框架自带的把逻辑跑通。3.3 结合 MessageChatMemoryAdvisor 的完整多轮聊天实现在 SpringAI 1.0 中需要先有一个ChatClient.Builder然后在构造时注册默认 AdvisorService public class ChatSessionService { private final ChatMemory chatMemory new InMemoryChatMemory(); private final ChatClient chatClient; public ChatSessionService(ChatClient.Builder builder) { this.chatClient builder .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .defaultSystem(你是一个友善、严谨的AI助手。) .build(); } public String chat(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chat_memory_conversation_id, sessionId)) .call() .content(); } }注意几个关键点sessionId每次请求都要带上MessageChatMemoryAdvisor 根据这个 ID 找到对应的历史消息。一般前端在用户进入聊天页时生成一个 UUID后端保存下来之后所有请求都用同一个 ID。defaultSystem()设置的是系统提示词相当于给模型定人设。这块后续细说。.advisors(a - a.param(chat_memory_conversation_id, sessionId))是 1.0 版本的固定写法参数名chat_memory_conversation_id不能写错它是框架内置常量。这样改完之后同一个sessionId下的多次提问就自动带上了历史上下文。用户上一句问“我老婆是程序员”下一句问“你对她职业发展有什么建议”模型是能接住话茬的。3.4 解决“达到对话长度上限请开启新对话”的核心手段聊到一定程度你会发现 DeepSeek 返回一段固定文案“达到对话长度上限请开启新对话”。很多人在热搜里问这个问题以为是 DeepSeek 故意拦截其实本质是上下文 Token 超限。DeepSeek 官方公布的单次上下文长度是 64K Token 量级具体以官方最新文档为准。也就是说你把历史上所有消息都塞进一次请求很快就到上限。尤其是中文场景一个汉字差不多占 1~2 个 Token几十轮长对话之后轻松触顶。解决思路不是让用户“手动开新对话”而是应用层主动控制记忆窗口。MessageChatMemoryAdvisor提供了一个基于 builder 的窗口参数private ChatClient buildChatClient(ChatClient.Builder builder) { MessageChatMemoryAdvisor advisor MessageChatMemoryAdvisor.builder(chatMemory) .windowSize(20) .build(); return builder .defaultAdvisors(advisor) .defaultSystem(你是一个友善、严谨的AI助手。) .build(); }windowSize(20)的意思是每次请求只把最近的 20 条消息传给模型。更早的对话虽然还在ChatMemory里存着但不会进入本次请求从根源上避免历史无限膨胀。这个数值怎么定看你业务的对话长度和 token 预算窗口大小优势劣势适合场景10极难超限响应快模型“记性”太短可能忘记开头信息简单问答、客服机器人20平衡较好普通聊天够用长上下文场景仍可能超限大多数聊天对话场景50记忆强能处理复杂多轮任务容易触发上下文超限单次请求更慢需要长上下文推理的任务注意windowSize统计的是“消息条数”而不是 Token 数。如果单条消息很长20 条也可能顶爆上下文。真要精细化控制我通常会在业务层额外限制单条消息长度双保险。另外如果还是遇到超限SpringAI 不会自动帮你清会话。最稳妥的兜底方案是捕获异常并做降级处理public String chatWithFallback(String sessionId, String userMessage) { try { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chat_memory_conversation_id, sessionId)) .call() .content(); } catch (RuntimeException e) { // 判断是否需要清理会话 chatMemory.clear(sessionId); return 当前对话历史太长了我已经帮你开启了新对话咱们从头聊吧。; } }这样即使用户聊到超限也不至于完全不可用体验上至少有个平滑过渡。3.5 给每个用户一套独立会话sessionId 的正确用法最后强调一下会话隔离。多个用户同时在线时绝对不能共用一个 sessionId否则 A 用户的历史消息会暴露给 B 用户出的是安全事故。正确的设计是会话创建时后端生成sessionIdUUID存储会话与用户的绑定关系。每次聊天请求带着它Redis 里以chat:{sessionId}为 key 存储消息列表。这样用户之间天然隔离。如果你直接用了InMemoryChatMemory只要 sessionId 是独立的数据也不会串。只是服务重启数据会丢生产环境务必换成 Redis 实现。4. 流式输出把“打字机”效果接到前端页面4.1 为什么聊天场景一定要用流式我一开始偷懒用一次性返回接的前端。结果就是用户点击发送后要等 5~15 秒才看到整段回答。期间没有任何反馈用户会以为服务挂了连续点三次发送对话就乱了。DeepSeek 生成长文本是需要时间的但用户阅读这些文本同样需要时间。流式输出的核心价值是模型每生成一小段文字就立刻推给前端显示。用户看到的是文字在“逐字蹦出来”等待焦虑完全消失。从后端稳定性角度讲流式也更好。一次性返回时如果响应几十秒还没结束中间任何一次网络抖动或网关超时整个请求就废了。流式是边生成边推送单次网络包小很多容错性更好。4.2 SpringAI 流式调用的核心 APISpringAI 1.0 的流式方法跟普通调用几乎一一对应只是把call()换成stream()返回值变成响应式FluxStringpublic FluxString streamChat(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chat_memory_conversation_id, sessionId)) .stream() .content(); }建议在ChatSessionService里同时提供chat一次性和streamChat流式两个方法兼容不同前端场景。4.3 Controller 层返回 SSE前端用 EventSource 消费流式接口在 HTTP 层使用的是 SSEServer-Sent Events。它是纯文本协议服务端把数据按data: xxx\n\n格式一帧一帧推给客户端浏览器原生支持解析。Spring WebFlux 环境下的 Controller 可以这样写RestController RequestMapping(/api/chat) public class ChatController { private final ChatSessionService chatSessionService; public ChatController(ChatSessionService chatSessionService) { this.chatSessionService chatSessionService; } GetMapping(path /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String sessionId, RequestParam String message) { return chatSessionService.streamChat(sessionId, message); } }前端最基础的做法是用EventSourceconst sessionId crypto.randomUUID(); const es new EventSource(/api/chat/stream?sessionId${sessionId}message${encodeURIComponent(你好)}); es.onmessage (event) { // event.data 就是模型吐出来的一小段文本 appendToChatBox(event.data); }; es.onerror () { es.close(); };但有个大坑必须在实战里提醒EventSource只支持 GET 请求。如果你想用 POST 发送更复杂的消息体比如长文本、附带参数就不能用 EventSource要换 fetch ReadableStreamconst response await fetch(/api/chat/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ sessionId, message }), }); const reader response.body.getReader(); const decoder new TextDecoder(utf-8); while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value, { stream: true }); // 按 SSE 格式解析 data: 开头的行 const lines chunk.split(\n); for (const line of lines) { if (line.startsWith(data:)) { const text line.slice(5).trim(); if (text) appendToChatBox(text); } } }4.4 实测中碰到的中文乱码和 Nginx 缓冲问题流式接入前端后我遇到过两个非常典型的问题。第一个是中文乱码。表现为页面上一堆乱码或者半个汉字。原因是 SSE 流的编码没有明确声明为 UTF-8。解决办法是确保 Controller 返回的 Content-Type 带有 charsetGetMapping(path /stream, produces text/event-stream;charsetUTF-8)Spring 里如果直接写MediaType.TEXT_EVENT_STREAM_VALUE默认可能不带 charset这种情况建议手动指定。同时前端TextDecoder要始终用utf-8。第二个问题是代理层缓冲导致前端“什么都不显示”。本地直连后端一切正常一旦放到 Nginx 后面流式内容全被缓冲起来直到模型生成完才一次性推给前端打字机效果名存实亡。这是 Nginx 默认开启了proxy_buffering导致的需要在 Nginx 配置里显式关闭location /api/chat/ { proxy_pass http://backend; proxy_buffering off; proxy_cache off; proxy_set_header X-Accel-Buffering no; proxy_read_timeout 300s; }proxy_read_timeout也要调大一点。长对话时模型思考时间可能比较长默认 60 秒超时会导致前端连接断开。4.5 流式连接的断开与异常处理流式接口比普通接口更容易出现“连接断到一半”的问题。前端用户可能随时关闭页面代理层可能超时后端模型生成也可能中途报错。实际处理上我有几条经验前端在onerror或reader.read()抛异常时不要直接弹错误弹窗。把已经收到的那部分文本保留提示“回答已中断”即可。后端Flux不要在业务代码里原地try-catch吞掉异常让响应式流的错误信号正常传下去前端统一收尾。如果是 WebFlux Nginx 部署记得给 SSE 接口单独设置较长的 read timeout普通接口保持短超时。5. 更贴近生产的参数调优与疑难杂症清单5.1 System Prompt让模型知道“它是谁”ChatClient的defaultSystem()相当于给整个会话设定一个系统级指令所有用户对话都会带上这段提示。这类内容对回答质量的影响非常直接。我举一个实际例子。不加 System Prompt 时问“帮我写个 Spring Boot 定时任务”模型会从最基础的知识开始讲啰嗦且抓不住重点。加了下面这段后回答质量立刻提升一个档次.defaultSystem( 你是资深Java后端工程师拥有20年Spring生态开发经验。 回答问题时 1. 直接给可落地的代码和配置 2. 先给结论再解释原因 3. 涉及安全问题时明确警告风险 4. 不要重复已给过的建议 )System Prompt 的合理度比调 temperature 更能影响回答质量。建议在业务上线前多花点时间打磨这一段。5.2 温度、超时、重试策略的推荐配置temperature 控制模型输出的随机性。DeepSeek 的接口对这个参数支持得不错我的经验值智能客服、代码生成0.3~0.5追求稳定准确标准聊天助手0.7平衡创造力和稳定性头脑风暴、文案创意0.9~1.0允许发散超时配置值得单独说。DeepSeek 在推理题这类复杂问题上响应速度可能慢超时设太短会频繁失败。我在 SpringAI 里配了单独的重试策略spring: ai: retry: max-attempts: 3 backoff: initial-interval: 1000 multiplier: 2 max-interval: 10000这个配置的意思是请求失败时最多重试 3 次每次重试间隔按 1 秒、2 秒、4 秒……指数退避上限 10 秒。配合 OpenAI Starter 内部的超时默认值普通对话场景足够用了。5.3 deepseek-chat 与 deepseek-reasoner 怎么选DeepSeek 有两个主流模型名用途差异很大模型名适用场景特点对话场景推荐度deepseek-chat通用对话、文本生成、知识问答响应快成本低日常聊天首选非常推荐deepseek-reasoner数学推理、逻辑分析、复杂代码调试会先输出思维链响应更慢token 消耗更大不推荐日常聊天使用需要深度推理的场景比如让模型解一道数学题、分析一段复杂代码逻辑切到deepseek-reasoner效果更好。但要注意reasoner 模型会先产出大量的推理过程账单涨得飞快不适合对每个普通问答都用它。实际项目中我一般这样设计普通会话默认用deepseek-chat用户点击“深度分析”按钮时前端带着特殊标记请求另一个接口后端使用 reasoner 模型处理。5.4 日志与 Token 消耗监控上线后不看 Token 消耗月底账单会给你“惊喜”。SpringAI 调用后的响应对象里带了 Token 用量信息。如果你用响应式流式接口可以这样拿到累计用量public FluxString streamChatWithUsage(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chat_memory_conversation_id, sessionId)) .stream() .content(); }在 Controller 层直接消费FluxString时如果还想记录 API 的 tokenUsage可以把Prompt的.stream()换成拿到ChatResponse的操作或者用 custom 的 advisor。最简单的方案是先从call()的返回类型ChatResponse里读ChatResponse response chatClient.prompt() .user(userMessage) .call(); ChatResponseMetadata metadata response.getMetadata(); TokenUsage usage metadata.getUsage();从usage里可以拿到getPromptTokens()、getCompletionTokens()、getTotalTokens()。把这些量异步写入日志或数据库每天跑个统计基本能做到成本心里有数。同时建议把 SpringAI 的日志级别打开一部分排查问题时能看到模型调用细节logging: level: org.springframework.ai: WARN org.springframework.ai.chat.client: DEBUG5.5 典型异常对照表我把这个项目从开发到上线遇到过的报错整理成一张表基本都是高频问题异常现象根本原因解决办法401 UnauthorizedAPI Key 错误或环境变量未生效用 curl 验证 Key检查配置读取方式404 Not Foundbase-url 拼接错误或模型名错误确认 base-url 是https://api.deepseek.com不带/chat/completions429 Too Many Requests触发限流通常并发过高或账户余额不足开启 SpringAI 重试降低单用户请求频率检查账户余额请求超时模型响应慢或代理层超时设置过短调大 HTTP 客户端超时Nginx 调大 proxy_read_timeout达到对话长度上限上下文 Token 超过模型窗口设置 MessageChatMemoryAdvisor 窗口清空会话历史中文乱码SSE 响应未声明 UTF-8 编码设置 produces 为text/event-stream;charsetUTF-8前端迟迟收不到流式内容Nginx 缓冲未关闭配置 proxy_buffering off服务重启后历史丢失用了 InMemoryChatMemory 存储生产环境换成基于 Redis 的 ChatMemory 实现5.6 关于“SpringAI 能不能替代 Python 写代码”的思考这个热搜问题挺有意思。很多人觉得 AI 开发是 Python 的专利Java 生态玩不转。真实情况是如果你要做的是训练模型、调模型结构、做深度学习实验那 Python 不可替代。但如果只是“调用大模型 API把 AI 能力集成到业务系统里”SpringAI 已经非常成熟了。我见过不少团队为了一个简单的 AI 聊天功能额外起一个 Python 服务放那儿就为了调一次大模型 API。维护成本、部署成本、团队学习成本全是双层负担。对 Java 技术栈为主的团队来说直接用 SpringAI 在现有项目里加功能省掉的不只是一个 Python 服务而是整个技术栈的割裂感。当然这也不是说学了 SpringAI 就能把 Python 团队干掉。它替代的是“为了调 AI API 而引入 Python”这件事而不是 Python 本身在 AI 领域的研究和工程价值。作为 Java 开发者把 SpringAI 玩熟你的技术栈里就多了一把能直接落地的 AI 钥匙。最后说几个我踩过之后觉得最值的经验这个项目做下来如果让我提炼几条最想告诉后来者的话大概是这几点第一先跑通最小 demo 再谈架构。不要一开始就设计一堆抽象类、策略模式、适配器。SpringAI 本身就帮你挡掉了大部分复杂性先用官方的 OpenAI Starter 调通一次单轮对话再逐步加记忆、流式、超限处理每一步都能验证出问题了也容易定位。第二base-url 和模型名这种配置务必从第一天就走环境变量不要硬编码。我因为图省事在 yml 里直接写了 API Key结果不小心提交到了 Git 历史后面改 Key、重置、清理历史折腾了整整一天。第三流式接口的“用户体验收益”远超预期。把一次性返回改成 SSE 流式后用户反馈里再也没出现过“AI 没反应”“是不是卡死了”这类话。如果你的对话服务还没做流式这是优先级最高的一件事。最后想说的是技术选型这件事别被“最流行”带着走。SpringAI 也许不是功能最全的 AI 框架但它是 Spring 生态里跟现有 Java 项目集成成本最低的。对一个业务系统来说“能快速落地、能维护、不引入额外复杂度”比什么都重要。
返回列表