1. 先搞清楚把 AI 塞进若依到底在“整合”什么1.1 这不只是“调接口”这么简单很多朋友看到别人发了篇文章标题写“若依整合AI”配图是打开浏览器点点鼠标就能跟机器人聊天第一反应就是调一个 HTTP 接口呗把大模型返回的内容塞到聊天框里不就行了我一开始也是这么想的直到我被生产环境教育了一轮才明白问题远没有那么简单。真正上过线的同学一定经历过这样的场景接口文档写得清清楚楚参数照着填Postman 里测试也一切正常结果发布到测试环境前端页面打开以后文字每隔几秒才蹦出来几个偶尔还直接卡住不动了。打开浏览器控制台一看请求一直处于 pending后端日志里却查不到任何异常。再过一会儿用户刷新页面对话记录没了多开几个对话上下文串了后台管理用户点击同一个 AI 功能发现所有人的额度全部走一个账号成本根本没法控制。这些问题的根源并不是你“不会调接口”而是你把大模型当成一个普通的第三方接口来对待了。大模型的调用方式和传统的业务接口有几个关键差异输出方式是流式的、上下文窗口是有限的、单次调用是有成本的、模型是不断迭代的。如果我们不把“接入 AI”当作一次架构设计而只是“调用接口”那后面不管你换多少次模型问题还是会反复出现。所以“若依整合 AI”真正的核心不在于你怎么发请求而在于你把大模型的能力改造成一件适合若依业务体系使用的、可控的、可观测的内部工具。这就是“拔高原理篇”想聊清楚的事。1.2 若依的“框架身份”决定了整合方式在讲具体方案之前必须先明确若依到底是个什么定位的东西。若依是一个后台管理框架它天生解决的问题是权限、菜单、用户、部门、日志、代码生成、定时任务。也就是说它是一套“面向企业内部系统”的基础设施。而 AI 大模型是一个“面向对话/生成任务”的外部能力。它不像数据库那样存放在自己的机房不像 Redis 那样可以由你随意控制它是一个通过网络方式提供的、有延迟的、有上下文限制的、按 token 计费的服务。这两者天然是两种不同气质的东西。若依强调“管理”大模型强调“生成”。你不可能把若依的主体逻辑改造得像一个聊天机器人那样实时、流式、高并发那违背了这个框架的核心定位也不可能让大模型去做权限审批、菜单管理那是拿大炮打蚊子。合理的做法是把 AI 能力作为一个“旁挂模块”挂在若依体系旁边若依负责权限、认证、数据持久化AI 模块负责对话、理解、生成、编排。我见过不少团队把 AI 功能直接写在若依的业务 Controller 里和保存订单、审批流程混在一起。短期看着方便过了两个月模型升级、换了供应商你就要在一堆业务代码里找“调用大模型的那几行”改完还要发版、测试、灰度。这个成本在长期看非常高。正确的姿势是若依还是那个若依AI 是“另一个服务”两边只通过接口或消息队列通信。这样业务代码是干净的AI 模型怎么迭代不影响核心管理功能权限体系又能复用。这也是为什么后面很多企业级方案都选择“若依 独立 AI 服务”的结构。1.3 为什么“若依 独立 Python 服务”的组合在实战里很常见我看后台搜索热词里有一条非常贴合实战的场景“基于若依管理系统和独立 python 处理服务构建的企业级识别系统”。这个关键词背后其实代表了国内很多团队的成熟选型。为什么不是纯 Java因为当前大模型、图像识别、语音识别这些 AI 生态主力 SDK 和社区方案集中在 Python 那边。你到 Hugging Face 上拉一个模型官方示例基本都是 Python你想用 LangChain、LlamaIndex 这些工具链也是 Python 最舒服。可企业的核心业务系统、用户体系、数据权限、审批流往往又都在 Java 体系里。若依作为 Java 后台管理框架正好解决了纪检这些。所以实操中比较顺的方案是若依负责“业务编排”Python 服务负责“模型推理”。两边通过 REST API 或者消息队列来通信。识别任务耗时较长就由若依发一个任务到消息队列Python 服务消费并处理处理完回调更新数据库。用户在前端看到的是一套完整的业务系统底层跑的是 Python 的模型逻辑。这个思路我再展开一点这套拆法本质上就是把“算力密集”的部分和“业务密集”的部分分开。AI 推理需要 GPU、需要大规模批处理、需要跑的模型镜像这些很容易拖垮业务服务器。而若依这样的小型管理框架最希望的是稳定运行、不折腾环境。两个进程分开以后AI 服务挂了不会连带若依崩溃模型升级的时候可以独立发布不会影响业务主链路。2. 拔高第一层会话、上下文与大模型调用原理2.1 无状态接口和有状态对话之间的矛盾聊完了总体思路我们把镜头拉近一点看看最底层的“对话原理”。HTTP 协议本身是无状态的。每一次请求都是独立的服务端不会默认记录你是谁来、你之前说过什么。大模型接口也一样你发过去一段 prompt它返回一段回答下次你再发它是不知道你上次问过什么的。但人类的对话天然是有状态的。用户说“刚才那个方案再细化一下”你必须知道“刚才那个方案”指的是什么。这就是无状态接口和有状态对话之间的矛盾。解决方案有很多但最经典的就是“由你来保存状态”。每次用户发消息你先从存储里把该用户最近的聊天记录取出来拼成一个消息列表连同用户当前这句话一起发给大模型。大模型看到完整的历史上下文才能给出连贯的回答。在若依项目里这个存储我一般建议用 Redis。原因有三个第一若依本身就整合了 Redis开箱即用第二对话数据的读取频率极高MySQL 撑不住高频读写而 Redis 的读写性能好很多第三对话数据有很强的时效性很容易给每条会话设置过期时间比如 7 天自动清理省得库表无限膨胀。我见过一个直接的做法把聊天记录存在 MySQL 表里每次对话都 SELECT 一次最近 20 条消息再用 limit 翻页。用户量小的时候没问题用户量一大这个表会变成访问最频繁的表最后只能加索引、加缓存绕了一大圈回到 Redis。2.2 Token 和上下文窗口为什么用“最近 N 轮”而不是全部解决了“存哪里”的问题下一个问题是“存多少”。很多第一次接入大模型的人踩过最深的坑就是把所有聊天记录一股脑全发给模型结果模型直接报错。因为大模型的输入和输出加起来有一个上限叫上下文窗口。以常见的中文模型为例有的是 8K token有的是 128K token。你一个会话聊了两个月所有消息加上去早就超了。Token 是个什么东西你可以把 Token 理解成大模型处理文字时的“基本单位”。中文里一个字通常对应 1 到 2 个 token英文里一个单词大约 1.3 个 token代码里一个普通字符也可能算 0.25 个 token。它跟“字”不一样所以没办法简单地用字数去算只能估算或者用 SDK 的计数器去数。整理消息列表时我更建议做“滑动窗口”。意思就是我只取这个会话最近 N 轮对话比如最近 10 轮或者最近 3000 个 token 的内容更早的历史不直接发给模型。这个方案简单粗暴但非常稳不会因为对话太长而报错成本也可控。但这样做有个问题如果用户很早之前提过一个重要背景滑动窗口后面的内容就丢失了。解决办法是“摘要压缩”。当历史消息超过某个长度阈值时先调用一次大模型把已有内容总结成一段简短摘要再把摘要和新消息一起发给模型。这本质上是牺牲一点信息密度换来有限的上下文窗口能覆盖更长的会话周期。如果你做的是文档问答那就不是简单的摘要能解决的了。你需要引入向量数据库把文档切成小段用 Embedding 模型转成向量存起来用户提问时把问题也转成向量然后做相似度搜索找到相关片段再把片段塞进 prompt 里交给大模型生成回答。这个流程叫 RAG检索增强生成。用若依做外壳、用向量数据库做知识库、用大模型做回答这就是目前企业知识库问答的标准架构了。2.3 普通请求和流式请求SSE底层发生了什么如果说“上下文管理”是意识层面的原理那“流式输出”就是用户能直接感知到的原理。普通请求是这样的前端发起一个 HTTP 请求后端收到以后去调用大模型接口大模型经过几秒甚至几十秒的推理生成完整回答然后一次性返回给前端。这个过程中用户只能看到“加载中”转圈。如果模型生成的时间长界面就好像死了一样体验非常差。流式请求不一样。大模型在生成第一个字的时候就立刻往回传后端收到一块数据就往客户端推一块客户端拿到一个 token 就渲染一个 token。用户看到的视觉效果就是“文字一个一个蹦出来”虽然实际等待总时长差不了太多但用户会觉得系统“活着”体验好得多。在协议层面流式输出通常有两种方式一种是 SSEServer-Sent Events服务端推送事件适合纯文本单向流式输出另一种是 WebSocket适合双向实时交互。聊天场景用 SSE 就够了因为它方向明确实现也简单。SSE 的响应头是Content-Type: text/event-stream数据格式大致是这样的data: {delta: 你} data: {delta: 好} data: [DONE]前端通过EventSource或者fetch的ReadableStream来一段一段读取。这里我想特别提醒一句如果把后端放在 Nginx 后面一定要关掉 Nginx 对 SSE 的缓冲。否则你的流式数据会被 Nginx 攒到一大块才吐给前端效果就变成等待一段时间突然蹦一堆字流式就白做了。这个坑在第四章“常见问题”里还会专门讲。3. 拔高第二层架构设计的去耦合与扩展性3.1 把 AI 封装成独立服务而不是写在业务代码里如果把 AI 接入这件事做一个简单的层次划分你会发现它并不是“一个 Controller 一个 Service”就能写完的。它至少包含几个层次接口接收与参数校验、会话历史组装、模型供应商选择、请求调用与流式接收、结果解析与持久化、成本统计与审计日志。这些层次如果全部堆在一个业务 Service 里代码会在第三个月变成一团浆糊。更合理的做法是在若依项目里把 AI 相关模块单独建一个包再进一步拆成独立的“AI 服务端项目”。哪怕初期规模不大也至少要在模块边界上划清楚。举个例子我接过一个项目用户要求“对接多个大模型以后可能还要换”最开始是直接在 Service 里写了一个if (modelType.equals(qwen))的方法。后来要接入第二个模型他复制了一大段把整个 Service 改得越来越大。最后我要重构时发现改一个公共参数有二十多处隐性依赖。我的建议是哪怕是单体项目也按微服务的思想来划边界。若依只负责对外暴露 HTTP 接口AI 服务内部独立处理会话和模型调度。这样做的好处很多AI 服务可以横向扩容比如对话服务扛不住的时候单独加机器AI 服务可以独立部署新模型版本不影响业务AI 服务的压力隔离不会把业务数据库连接池拖垮。3.2 用策略模式 工厂统一管理多模型接入模型接入这块我已经“踩过不止一遍”了。很多团队的模型接入代码长这样if (qwen.equals(model)) { // 调用通义千问 } else if (gpt.equals(model)) { // 调用 OpenAI 兼容接口 } else if (local.equals(model)) { // 调用本地 Ollama }这个写法在刚接入一个模型的时候没问题但只要你接第二个就该难受了。因为每个模型的 API 地址不同、鉴权方式不同、请求体不同、返回格式不同。如果你用 if-else 写每加一个新模型就要把所有涉及调用的大方法都打开改一遍很容易把别的模型的逻辑改崩。正确的做法是定义一个统一的接口叫ChatProvider每个模型对应一个实现类然后用一个工厂根据配置返回对应的实现。public interface ChatProvider { String getProviderName(); void chat(ChatRequest request, StreamEmitter emitter); } Component public class QwenProvider implements ChatProvider { Override public String getProviderName() { return qwen; } Override public void chat(ChatRequest request, StreamEmitter emitter) { // 具体调用通义千问的逻辑 } } Component public class OllamaProvider implements ChatProvider { Override public String getProviderName() { return ollama; } Override public void chat(ChatRequest request, StreamEmitter emitter) { // 具体调用本地 Ollama 的逻辑 } }工厂类负责把容器里所有的ChatProvider收集起来按名称返回Component public class ChatProviderFactory { private final MapString, ChatProvider providerMap; public ChatProviderFactory(ListChatProvider providers) { this.providerMap providers.stream() .collect(Collectors.toMap(ChatProvider::getProviderName, p - p)); } public ChatProvider getProvider(String name) { return Optional.ofNullable(providerMap.get(name)) .orElseThrow(() - new RuntimeException(未找到模型供应商: name)); } }这样做的好处一眼就能看出来以后要接入新模型只需要加一个实现类加上Component注解工厂会自动把它收进来业务代码一行都不用改。这里面的核心思想叫“开闭原则”翻译成人话就是对扩展开放对修改关闭。加功能不依赖改老代码而是加新代码。为什么我这么强调这个因为大模型的迭代速度实在太快了。你今年接的 A 模型半年后可能就被 B 模型替代了。如果你用了策略模式加工厂替换模型只需要改配置文件或数据库字段线上切换 10 分钟搞定。否则你就要重新走一遍开发的完整流程。3.3 从单模型到 AI Agent多 AI 协作与工作流搜索热词里出现的“AI Agent”、“多AI协作”、“AI工作流”、“教别人用AI赚翻了”这些词看起来离若依很远但实际它们之间有一条清晰的逻辑主线当你把单次“一问一答”做顺了接下来一定会遇到“让 AI 自动完成一个任务”的需求。普通问答模型你问它“帮我写一段营销文案”它会把文案写出来。但 Agent 不是这样Agent 的核心是“模型会调用工具”。比如你说“帮我把最近一周的销售数据整理成报表然后发给部门群”Agent 需要这样执行先识别出意图然后调用“查询销售数据”这个工具接着调用“生成报表”这个工具再调用“发送消息”这个工具最后把完整结果告诉你。在若依体系里落地 Agent有一个天然的切入点若依有权限体系、有业务 API、有定时任务、甚至有 workflow 流程引擎。你完全可以把 Agent 的工具调用映射为若依的业务接口。例如你可以给模型提供一份工具描述清单告诉它有哪些工具、每个工具需要什么参数模型在生成回答时不是直接输出文字而是输出一个工具调用指令后端解析以后去执行对应的若依接口然后把结果返回给模型让模型继续生成。这里涉及到一个进阶概念叫“Function Calling”目前主流大模型基本都支持。原理不复杂但落地到若依时有个关键点你必须在服务端把工具的权限校验做完整。因为若依本来就是一个带权限的系统一个普通用户调用 AIAI 内部再去调若依的接口查数据必须判断这个用户到底有没有查这些数据的权限而不能让 AI“代提权”。否则 AI Agent 就会变成一个巨大的越权漏洞。我的建议是不要一上来就追求复杂的 Agent先把“单轮问答”和“多轮上下文”做扎实再逐步加“单工具调用”最后再上“多工具编排”。否则你会被模型的不确定性和错误工具调用搞得焦头烂额。4. 拔高第三层若依里的落地细节和踩坑4.1 Spring Boot 异步与线程池并发一高就超时在真正的若依整合代码里有一个非常容易出现“诡异现象”的地方就是同步调用导致的线程池耗尽。想象一下这个场景前端请求后端后端在请求线程里同步去调用大模型接口。大模型少则两三秒多则十几秒才返回。Tomcat 默认的工作线程池并不大如果你有 200 个请求进来很快就占满了。接下来的请求全部排队用户看到的就是“转圈圈转很久”然后网关超时。解决的思路是把 AI 调用改成异步的后端接口收到请求后立刻返回一个SseEmitter给前端然后把真正的大模型调用丢到一个独立的线程池里执行。这样接收请求的线程马上释放可以继续处理其他请求而大模型推理在业务线程池里慢慢跑跑完一个代码块就推一个给前端。线程池的参数不能拍脑袋。我分享一个我自己常用的配置思路核心线程数设置为 CPU 核数 1最大线程数设置为 CPU 核数 * 2队列容量根据你的并发量和模型响应时间计算。比如平均每个请求耗时 5 秒你希望同时能容忍 50 个并发那队列容量至少要有 50 * 5 250。这里还要考虑模型的并发限流如果大模型供应商限制你每分钟只能调用 10 次那线程池开得再大也没用反而会把你的调用打到限流报错。还有一个特别容易被忽略的坑如果你在 Spring 事务里调用大模型那整个事务期间数据库连接一直被占用非常伤数据库连接池。所以凡是涉及 AI 调用的服务一定要先提交事务再做模型调用或者干脆在事务外面做。你可以在业务方法上只对“更新数据那一步”加事务不要在包含模型调用的整个方法上直接加Transactional。4.2 缓存、限流、用户体系打通保证安全和成本讲完了异步再讲讲成本和安全。先把话说透大模型是按 token 计费的不像普通接口只要不崩随便调。如果不做限流一个用户恶意刷对话可能一晚上就让你烧掉一个不小的预算。若依本身自带 Redis我建议在 AI 接口上直接用 Redis 做一个简单的滑动窗口限流。思路很简单每个用户有一个 Redis 键比如ai:rate:{userId}每次调用时记录当前时间戳然后统计最近一分钟调用次数超过阈值就拒绝。这个实现不复杂你可以封装成一个注解写在 Controller 方法上比如AiRateLimit(limit 10, period 60)每次进入方法前先检查。还有更重要的是用户体系打通。若依的登录认证是基于 Spring Security 和 JWT 的AI 接口不要做成一个完全开放的匿名接口。我之前见过一个项目为了前端调试方便接口路径直接设置了anon放行后果是任何人都可以直接 POST 这个地址消费模型连登录都不用。这就是成本和安全双重失控。正确做法是AI 相关的 Controller 统一放在需要登录的路径下用若依现有的PreAuthorize注解控制权限。至少做到“必须登录才能对话管理员可以选择给哪些角色开放”。另外建议对每个用户每天的 token 消耗做记录。若依有现成的操作日志功能你可以仿照它的日志注解写一个AiAuditLog把每次调用的用户、模型、输入 token、输出 token、消耗金额、耗时都存进一张表。这张表既能为成本分析提供数据也能在出现纠纷时帮你查出具体调用记录。4.3 前端 Vue3 TS 的常见雷区搜热词里有个词条“若依vue3 ts报错”非常真实。若依 Vue3 版本是 Vue3 TypeScript 的组合很多从 JavaScript 转过来的开发者在整合流式输出时会遇到一堆 TS 类型问题。第一个高频问题是自定义的axios封装返回类型和实际返回类型不一致。若依自带了一个request.ts封装统一对返回结构做了处理。但当你对接 AI 流式接口时不能直接用axios的普通返回因为流式要求的响应类型是text/event-stream。你要么用fetch要么用axios的responseType: stream。许多人在封装层卡了半天就是因为没意识到流式接口和普通接口的响应格式根本不一样。第二个高频问题是TypeScript 的严格模式报错。Vue3 版本的若依默认开了严格模式所以你在写onDownloadProgress或者ReadableStream解析时会碰到一堆类型不匹配。比如reader.read()返回的done是布尔值你写成if (reader.read().done)就错了你得先取出结果再判断。这些小细节对老手来说不算事对新入门的人来说很劝退。第三个高频问题是路径别名解析。Vue3 若依工程用指向src目录如果你新装了一些依赖没配置对应类型会遇到 “Cannot find module” 的报错。通常处理方式是检查tsconfig.json里的paths配置和vite.config.ts里的 alias 是否一致。前端这块我建议把 AI 对话封装成一个独立的useChatStream组合式函数把建立连接、接收数据、更新状态、错误处理全部收敛到里面。业务组件只关心消息列表和输入框不要让组件的逻辑被流式解析细节淹没。下面是我常用的一个 fetch 流式解析核心片段大家在 base 上扩展即可async function chatStream(message: string, sessionId: string) { const res await fetch(/api/ai/chat/${sessionId}, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ message }), }); if (!res.ok) throw new Error(请求失败); const reader res.body!.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); // 按行解析 SSE 数据 const lines buffer.split(\n); buffer lines.pop()!; for (const line of lines) { if (line.startsWith(data:)) { const data line.replace(data:, ).trim(); if (data [DONE]) return; const json JSON.parse(data); // 把 json.delta 追加到你的消息列表里 } } } }4.4 代码示例一个可落地的流式聊天接口聊完原理咱们看一段能直接用的后端示例。假设我们已在若依工程里新建了一个AiChatController前端通过 POST 请求发送用户消息后端通过 SSE 逐步返回大模型生成结果。RestController RequestMapping(/ai/chat) public class AiChatController { Resource private AiChatService aiChatService; PostMapping(/{sessionId}) public SseEmitter chat(PathVariable String sessionId, RequestBody ChatCreateDTO dto) { SseEmitter emitter new SseEmitter(180000L); aiChatService.streamChat(sessionId, dto.getMessage(), emitter); return emitter; } }注意几点第一SseEmitter的超时时间要设置大一些至少要留足大模型生成时间我一般设 3 分钟第二这个方法不能直接同步调用大模型而是要把任务丢到线程池里所以 Service 层应该用Async或者主动创建一个线程池。Service 层的核心逻辑大致是这样的先根据sessionId从 Redis 取出最近的历史消息把当前用户消息追加进去然后组装大模型请求调用ChatProviderFactory获取对应模型的 Provider最后将流式输出写进SseEmitter。Service public class AiChatServiceImpl implements AiChatService { Resource private ChatProviderFactory chatProviderFactory; Resource private StringRedisTemplate stringRedisTemplate; Override public void streamChat(String sessionId, String message, SseEmitter emitter) { // 从配置或数据库获取当前用户的模型名这里简化处理 String providerName qwen; ListChatMessage history getHistory(sessionId); history.add(new ChatMessage(user, message)); ChatRequest request new ChatRequest(history, providerName); chatProviderFactory.getProvider(providerName) .chat(request, new SseStreamEmitter(emitter)); // 把本次用户消息和最终回答保存到 Redis saveHistory(sessionId, history); } }这里有一个很重要的小细节SseEmitter必须支持超时完成和错误回调。如果模型调用过程中突然断了你至少要给前端返回一个error事件否则前端会一直以为还在生成中。写法如下emitter.onCompletion(() - emitter.complete()); emitter.onTimeout(() - emitter.completeWithError(new RuntimeException(timeout))); emitter.onError((e) - emitter.completeWithError(e));另外历史消息在保存时不要把大模型返回的超长完整结果全存成 Redis 的 String建议做压缩或者截断处理。因为会话一旦多了Redis 内存会被聊天记录吃光。更聪明的做法是只保留最近 N 轮超过 N 轮的按摘要替换。5. 常见问题与排查技巧实录我把自己和团队在落地的过程中踩过的坑整理成一张速查表如果你在生产环境遇到了类似的现象可以直接对照着排查。现象可能原因排查与解决前端一直转圈后端过很久才收到响应同步调大模型请求线程被占满改成异步 独立线程池用 SseEmitter 返回SSE 流式变成“卡一会蹦一段”Nginx 缓冲了 SSE 响应在 Nginx 配置里关闭对接口的 proxy_buffering对话记录串了不同用户看到别人的聊天sessionId 只用了一个固定值会话 ID 必须绑定用户 ID每次对话前校验归属发一大段历史记录给模型就报错上下文超出模型 token 上限改成滑动窗口 必要时做摘要压缩用户多调用几次就被限流没有成本控制加 Redis 限流 按用户 token 消耗统计Vue3 TS 报错找不到响应类型axios 封装返回 type 不匹配使用 fetch ReadableStream 解析接口在 Postman 能用前端不能用跨域或者 JWT 没有带过去确认若依的跨域配置和请求头携带 Token大模型回答乱码中文字符变问号编码不是 UTF-8确认后端和前端都用 UTF-8响应头指定 charset并发高时数据库连接池被耗尽AI 调用放在事务里连接被长占把 AI 调用移出事务事务只包必要的数据更新这里额外补充几个细节排查经验。第一“前端收到一半就断流”的问题别急着改代码先看网关。若依本身带网关微服务版或者你可能部署了 Nginx。在 Nginx 配置里针对/ai/chat这个路径加入下面这几行location /ai/chat { proxy_pass http://你的后端地址; proxy_buffering off; proxy_cache off; proxy_read_timeout 300s; proxy_set_header Connection ; chunked_transfer_encoding on; }proxy_buffering off是关键不关掉的话Nginx 会把流式内容攒起来再一次性发出去用户体验直接退化。第二排查 SSE 断流时建议在浏览器控制台把 Network 面板打开盯着响应时间线。正常情况应该看到一条持续的输出线而不是一堆间隔的小段。如果看到的是间歇性的大块数据基本可以断定是缓冲问题。第三JWT 过期导致 AI 接口返回 401这个现象很容易被忽略。因为流式输出一开始是正常的但你可能为了性能把 Token 从数据库里拿出来每天加载一次过期时间到了以后新请求带的是旧 Token后端校验失败了。这个问题处理起来很简单把 Token 校验时间缩短或者每次请求都从若依当前登录用户那里获取 Token不缓存。第四也是我自己吃过大亏的一点本地测试模型响应正常部署到服务器以后响应特别慢。排查了半天发现是 AI 服务出网方式的问题。国内访问海外模型接口延迟很高如果你部署在国内服务器建议优先选择国内云厂商的模型或者走合规的代理方式否则用户体验就是“一个字一个字蹦但一句等很久”。6. 我最后一次实际操作中的体会如果你问我若依整合 AI 最难的部分是什么我可能会回答不是代码而是思维方式的切换。传统的开发输入一个确定性的参数返回一个确定性的结果你几乎能预测所有边界情况。但大模型是概率性的你给它同一段 prompt它可能每次返回不同你让它调用工具它偶尔会犯蠢调错参数甚至伪造结果。所以接入 AI 之后你的系统设计必须额外考虑“模型不靠谱”这个前提。所有关键业务操作不能被模型直接决定模型只能给出建议最终执行必须落在确定性的代码里。我的第二个体会是不要贪多求快。我见过太多人一上来就想做一个全能的 AI Agent最后卡在工具调用不稳定的泥潭里。更务实的路径是第一阶段只做“流式问答 会话历史”把对话交互打磨顺第二阶段做“知识库检索”用 RAG 解决文档问答第三阶段再加“工具调用”选择两三个稳定的若依业务接口作为 Agent 工具。每一阶段都上线验证再往前走。还有一个小技巧我想在这里分享给所有正在做类似项目的人提前把 AI 调用的日志结构设计好。很多团队前期为了赶进度只打了简单 log上线后模型出了问题拿着日志根本还原不出当时的 prompt 和响应排查效率极低。我建议从第一天就记录完整的请求和响应摘要至少包括用户 ID、会话 ID、模型名、输入 token、输出 token、耗时、状态。这些数据不仅能帮你排查问题还能用来做成本和效果分析好处会在后期逐步显现。最后一个建议算是给团队协作的如果你负责的若依项目将来要交到别人手里维护代码里的 AI 调用部分一定不要写一堆 if-else。用策略模式 工厂收口维护的人只需要关注新增一个模型实现类而不用理解整个业务链路。这个价值会在你离开那个项目三个月后接到咨询电话时感受得特别深。这个系列我没有急着往前写那些“十分钟接入 ChatGPT 接口”的速通内容是因为我觉得若依整合 AI 这件事真正值钱的不是那几行 HTTP 调用代码而是背后这些架构取舍和运行原理。把原理搞清楚了后面不管你接哪个模型、做什么级别的 AI 应用心里都有底。