Spring Boot 3 + LangChain4j 构建AI应用生成平台:微服务全栈实战

发布时间:2026/10/2 17:42:43
Spring Boot 3 + LangChain4j 构建AI应用生成平台:微服务全栈实战 简介面向企业级 AI 应用开发者与微服务学习者的全栈项目源码基于 Spring Boot 3 与 LangChain4j 构建大厂级 AI 应用生成平台覆盖 Agent 智能体编排、RAG 检索增强、多模型接入等核心能力。压缩包共 387 个文件、约 618KB包含 287 个 Java 服务端微服务源码、21 个 TypeScript 与 17 个 Vue 构成的前端界面、15 个 XML 配置、15 个 TXT 说明文档以及 YAML/JSON 等环境配置前后端工程结构完整清晰。平台内置 ReAct、Plan-and-Execute 等多种 Agent 运行时支持工具插件动态加载、知识库文档解析、向量检索与流式输出并整合 Nacos、Seata、Gateway 等微服务组件完整呈现生产级 AI 平台的落地路径。代码遵循 Clean Architecture 分层规范配套架构设计、接口契约、Agent 开发指南、模型接入、安全加固与性能压测等文档。已有 25 人学习适合中高级开发者从中获取模块划分、接口契约、部署配置与模型适配等一手可复用内容沉淀大模型应用平台的设计与编码经验。1. 编程导航 AI 微服务全栈新项目Spring Boot 3 LangChain4j 到底要做成什么样2025 年很多做 Java 的人都被一个朴素的焦虑困住学会 Redis、MySQL、RabbitMQ能写 CRUD但投履历时总觉得缺一个「别人没做过、但你做过的」项目。而这条标题给的方向恰好把三类稀缺元素叠在一起——微服务架构、Spring Boot 3 新特性、以及 Java 接大模型时的正统姿势 LangChain4j。所谓「大厂 AI 应用生成平台」不是让你从零写一个大模型而是做一个「让业务方用表单拖拽就能生成一个 AI 应用」的配置化平台输入提示词、选模型、配知识库、设流程平台自动生成一个能对话、能检索、能调工具的 Web 应用。适合正在补 Java 全栈学习路线、想拿微服务项目做主力作品、以及想搞懂 Spring Boot 3 和 LangChain4j 到底怎么配合的人这篇文章会把我实际落地这个方向时的拆分思路、核心代码和最关键的那几个坑完整铺开。2. 微服务拆分的实战思考这个 AI 应用生成平台到底该拆成几块2.1 为什么这种项目天然适合微服务而不是单体做 AI 应用生成平台和做普通后台管理系统一个最大的差别一个普通系统里所有模块的负载是均匀的、长尾的而这里「生成应用」这个动作是瞬时高 CPU 高内存 高外部 API 延迟的。一个用户的生成操作可能包含组装 Prompt → 调大模型 → 解析返回的结构化结果 → 生成配置 → 写入数据库 → 调用部署模块把产物发布出去。整条链路里大模型调用可能是 3~10 秒而其他模块只需要几毫秒。如果把生成和大模型流量都塞进同一个单体进程一次慢调用就可能拖垮整个应用的管理页面。所以按实际的负载特征来拆比「按菜单拆微服务」靠谱得多。我通常把这种项目拆成 5 个服务服务名核心职责关键技术点gateway统一入口、路由、Token 鉴权Spring Cloud Gateway JWTauth登录、用户体系、权限Spring Security OAuth2app-manager应用模板管理、生成记录、发布流程MyBatis-Plus 状态机ai-enginePrompt 组装、模型调用、流式输出、RAGSpring Boot 3 LangChain4jfile-service文件上传、镜像管理、产物存储MinIO 异步任务spring boot、spring、spring cloud 之间很多人一直搞混按我的理解Spring Boot 3 是基础框架Spring Cloud 负责解决微服务里的服务发现和配置管理而 LangChain4j 是在 Spring Boot 3 之上接大模型的组件三者是叠加关系不是替代关系。2.2 服务间调用怎么设计同步还是异步服务拆完之后的第一个问题是ai-engine 调用大模型很慢app-manager 要不要同步等它很多人第一次做微服务会直接把 Feign 调用放在链路上结果一个生成操作让网关线程挂半分钟。可落地做法是同步调用只保留在小链路里比如校验 Token、查模板详情真正的生成操作走异步任务app-manager 收到请求后立刻返回「生成中」同时把任务 ID 推给 ai-engineai-engine 完成后再回调结果回调失败要有补偿我一般用一张生成任务表存状态由定时任务去捞超时未完成的记录。// 生成任务状态表的关键结构 public class GenerateTask { private String taskId; private String templateId; private String userId; private Integer status; // 0: 待执行 1: 执行中 2: 成功 3: 失败 private String extraParams; private LocalDateTime createTime; private LocalDateTime finishTime; }这里要注意把状态放在独立表里而不是靠消息队列的重试来保证推进。消息队列适合通知「该干活了」但最终状态必须落库因为数据库是恢复现场的唯一依据。我见过有人把任务状态只存在 Redis 里服务一重启全部任务丢失只能人工后台改数据这就是典型的「把微服务做重、把一致性做没了」。2.3 Spring Boot 3 在这套架构里真正用到了什么Spring Boot 3 相比 2.x 最大的变化是 Java 17 基线 Jakarta EE。除此之外在这个项目里我最常用的是Spring Boot 3 的 Native Image 配置虽然实际很少人直接把整个微服务打成 native因为 LangChain4j 的反射类太多spring-boot-starter-webflux与 WebMVC 的选择。ai-engine 这侧为了流式输出我会单独用 WebFlux因为 SSE 场景下 WebFlux 天然支持异步非阻塞其他服务继续用 WebMVC避免团队心智负担过大。LangChain4j 在 Spring Boot 3 上同时支持阻塞和流式两个分支选 WebFlux 时要记得引入 reactor 的依赖。dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-spring-boot-starter/artifactId version1.0.0-beta1/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version1.0.0-beta1/version /dependency具体的版本号建议以 Spring Boot 3.2 以上为基础来搭。LangChain4j 的启动器会自动注册ChatLanguageModel、EmbeddingModel和ChatMemory这类 Bean不用你自己手写工厂但后面我会讲为什么自动配置恰恰是坑的来源。version 参数别乱升到最新如果你用的模型接口是 OpenAI 兼容格式那么langchain4j-open-ai就够如果你要接国内模型记得确认它是不是走/v1/chat/completions标准协议不是的话要换langchain4j-http或自定义请求。3. 用 LangChain4j 把 AI 应用生成平台的推理引擎搭出来3.1 LangChain4j 是什么以及和 Spring AI 怎么选LangChain4j 是 Java 版的 LangChain核心目标就是「让 LLM 接入 Java 应用变成和操作数据库一样自然」。它不是 Spring 官方的但依赖层面做得非常好可以把 OpenAI、通义千问、Ollama 都抽象成统一的ChatLanguageModel接口这样业务代码里不用写具体厂商 SDK。Spring AI 是 Spring 官方的方案起步晚一点生态不够 LangChain4j 稳尤其在 RAG 组件和回调钩子上LangChain4j 明显更成熟。在 AI 应用生成平台里LangChain4j 主要承担四个能力用模板生成对话应用的 Prompt支持流式响应把大模型返回逐字推给前端做 RAG给 AI 应用挂知识库让模型基于用户导入的文档回答调工具比如让模型根据用户输入查询天气预报、查库存。最关键的是第一点。你写生成器核心逻辑就是把用户配置的表单数据组装成一个完整的 AI 应用定义这个组装过程本身就是「Prompt Engineering」。3.2 组装一个可运行的对话应用把表单变成可编程的 AI 服务通常用户在平台上创建一个 AI 应用只需要填应用名称、系统提示词、选择模型、温度、是否开启知识库、是否开启联网。这些字段存进 template 表生成时再由 ai-engine 组装出真正的运行时配置。下面是核心的 Prompt 模板组装代码public ChatRequest buildGenerateRequest(AiAppConfig config) { String systemPrompt 你是一个 AI 应用生成器。 用户想创建一个名为 %s 的应用定位是%s。 请根据以下要求生成该应用的系统提示词 1. 不要出现你是AI助手这类空话直接以业务角色进入 2. 输出必须包含角色、能力边界、输出格式、禁止事项 3. 控制在 200 字以内。 .formatted(config.getAppName(), config.getDescription()); return ChatRequest.builder() .messages(singletonList(SystemMessage.from(systemPrompt))) .temperature(config.getTemperature()) .maxTokens(800) .build(); }这段代码的意图不是直接生成业务答案而是生成「能让另一个模型高质量工作的系统提示词」——这是生成平台的本质。temperature 在这里要保守一点如果填了 0.8 以上生成出的 Prompt 会发散导致业务方拿到的应用「每次都换一个风格」。所以平台侧我会做一层限制生成器内部用的温度固定在 0.2~0.4业务方配置的温度只作用于最终生成的运行时应用。maxTokens 也不是越大越好。生成 Prompt 到 800 token 足够如果设到 2000响应变慢、成本变高而且很多模型对超长输出的尾部质量会急剧下降。一般我会在测试里压出三个档位600、800、1200比较生成结果的稳定性和耗时再决定默认值。3.3 流式输出怎么做让生成过程肉眼可见AI 应用生成平台和一个传统后台最大的体验差异在「生成过程要有响应」。用户点完生成按钮界面如果白屏 8 秒基本会被直接判定为系统卡死。LangChain4j 的StreamingChatLanguageModel就是干这个的。PostMapping(value /generate/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamGenerate(RequestBody GenerateRequest req) { SseEmitter emitter new SseEmitter(60_000L); executor.execute(() - { try { streamingModel.chat(messages) .onPartialResponse(token - { emitter.send(SseEmitter.event().name(message).data(token)); }) .onCompleteResponse(resp - { emitter.send(SseEmitter.event().name(done).data(resp.aiMessage().text())); emitter.complete(); }) .onError(error - { emitter.completeWithError(error); }) .start(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; }用 SseEmitter 时尤其注意超时参数的设置。默认的SseEmitter()超时是 30 秒如果你的模型经常跑 40 秒前端会在 30 秒就收到一个超时中断所以初始化时显式传 60_000L。这里的 onPartialResponse 是 LangChain4j 的流式回调后端每收到一段 token 就推给前端前端用 EventSource 接收并拼到界面上。拼接顺序、断线重连、以及「用户中途取消生成」这三个问题要单独处理用户取消时调用emitter.complete()还不够还要在内存里维护一个 taskId - CancellationToken 的 Map才能真正打断模型那边的请求否则内存里的响应还在继续浪费外部 API 额度。3.4 给一个 AI 应用挂上知识库RAG 链路的最小实现一个完整的 AI 应用通常要能回答业务方私有文档里的问题这就要 RAG。LangChain4j 的 RAG 链路由三块组成Embedding 模型、向量存储、以及检索器的参数调优。首先往项目里引入文本嵌入模型我用的是一套兼容 OpenAI 协议的 embedding 接口langchain4j: open-ai: chat-model: base-url: ${MODEL_BASE_URL} api-key: ${MODEL_API_KEY} model-name: gpt-4o-mini embedding-model: base-url: ${MODEL_BASE_URL} api-key: ${MODEL_API_KEY} model-name: text-embedding-3-small导入知识库的文档后需要把文本切成块再向量化LangChain4j 提供了DocumentSplitterpublic void importDocument(String content, String knowledgeBaseId) { Document document Document.from(content); ListTextSegment segments DocumentSplitter.recursive(500, 100) .split(document); ListEmbedding embeddings embeddingModel.embedAll(segments).content(); ListString segmentIds embeddingStore.addAll(embeddings, segments); knowledgeBaseRelService.batchAdd(knowledgeBaseId, segmentIds); }这里recursive(500, 100)是参数调整的第一站数表示每个片段最多 500 字符、重叠部分 100 字符。片段切的太小检索到的上下文不完整切得太大一次塞给模型会稀释关键信息。我跑过很长一段时间的业务问答最后发现 500 字左右、重叠 100 字是最稳的组合。如果一个文档本身是高度结构化的表格我会换成DocumentSplitter.recursive(200, 20)让模型聚焦在更小粒度的上下文里。检索的时候默认的EmbeddingStoreRetriever参数是 topK3。实际效果是业务方提出的问题往往包含多个条件topK3 容易漏掉关键文档。比较稳的做法是 topK 取 5~7然后让模型对检索结果做一次相关性过滤再回答而不是只量一次相似度就下结论。4. LangChain4j Spring Boot 3 避坑实录现象、原因与解决办法4.1 流式响应一直报「Async request timed out」前端收到 504现象SSE 接口在 Postman 里能通一走网关就超时控制台打Async request timed out。原因Spring Cloud Gateway 默认的响应超时是 30 秒而大模型流式输出通常十几秒才返回第一帧客户端早断了。解决在 gateway 的配置里单独放宽这条路由的读超时。spring: cloud: gateway: httpclient: response-timeout: 120000注意response-timeout是整个 httpclient 的全局配置不是某条路由单独设。如果你不想全局放宽可以在路由 predicate 里加Metadata或者在过滤器里为生成流接口单独创建新的 WebClient 实例把连接和响应超时都拉长。流式输出属于长连接不是每条请求都要 120 秒所以我对普通接口仍然保留默认 10 秒只对/api/ai-engine/generate/**做长超时。4.2 LangChain4j 自动配置的 ChatModel 和你配置的 API Key 对不上现象本地跑得好好的部署上去之后模型返回 401 或模型名不存在。原因langchain4j-spring-boot-starter会自动从application.yml读langchain4j.open-ai.chat-model.api-key但如果你同时在代码里手动 new 了一个OpenAiChatModel代码里的实例会覆盖自动装配的 Bean于是你改 yml 永远不生效。解决统一入口只在 yml 里配模型代码里始终用Autowired注入ChatLanguageModel不要手动 new如果一定要多模型并存把每个模型定义成单独的Bean并给名字带上业务前缀注入时用Qualifier(codeReviewModel)区分。4.3 大模型返回 JSON 时偶尔多一个「json」前缀或丢失后括号现象让模型返回结构化 JSON 交给前端渲染十个请求里有三个解析失败日志里全是JSONDecodeException查看原始返回值发现开头是json或者结尾少了一个}。原因LLM 本身对输出格式的控制不稳定尤其是中文系统提示词很长时模型有可能把「输出 JSON 格式」误解成「输出一个 Markdown 代码块」。解决三层兜底。第一层提示词末尾强制加「不要输出任何解释不要包含代码块标记只输出 JSON 本身」。第二层解析前清洗字符串public String sanitizeModelOutput(String raw) { String cleaned raw.trim(); // 去掉 json 这种围栏 if (cleaned.startsWith()) { cleaned cleaned.replaceAll(^[a-zA-Z]*\\n, ) .replaceAll(\\n$, ); } // 去掉模型偶尔画蛇添足的前缀 int firstBrace cleaned.indexOf({); int lastBrace cleaned.lastIndexOf(}); if (firstBrace 0 lastBrace firstBrace) { return cleaned.substring(firstBrace, lastBrace 1); } return cleaned; }第三层最重要提示词里用「用 Markdown JSON 代码块包裹」这种说法反而会诱导模型输出围栏。更可靠的做法是直接告诉模型「你的输出会被程序解析非法 JSON 将导致用户损失务必只输出原始 JSON」。加一句责任描述比任何格式强调都有效。4.4 微服务里 RabbitMQ 消费 AI 生成结果时消息体太大直接被丢弃现象生成任务详情里明明有 PDF 转出的长文本但 ai-engine 收到消息后内容被截断或者消费端抛java.lang.IllegalArgumentException: body exceeds 1MB。原因RabbitMQ 默认 max frame 是 1MB很多团队把文档转出来的 Markdown 全塞进消息体它当然会超。解决消息只传任务 ID文档内容让 ai-engine 回查 app-manager 的文件服务接口同时把 yml 里的spring.rabbitmq.connection-timeout调大一点如果必须传大 body就在 RabbitMQ 管理后台把 frame_max 调大但这会增大集群内存压力不推荐。我一般会在上传文档时就把内容落到 MinIO消息里只存fileId。4.5 热更新 Prompt 模版不生效感觉黑匣子一样现象运营同学后台改了「生成应用的系统提示词」保存成功但重新生成还是原来的效果。原因ai-engine 服务本地 JVM 缓存了模板类且没有做版本号变更通知。解决模板表里加version字段每次修改version1ai-engine 启动时加载模板到 ConcurrentHashMap再提供一个刷新接口由 RabbitMQ 广播模板变更事件ai-engine 收到消息后重新查库、更新本地缓存。Component public class PromptTemplateHolder { private volatile MapString, PromptTemplate cache new ConcurrentHashMap(); public void reload(String templateCode) { PromptTemplate fresh mapper.findByCode(templateCode); this.cache.put(templateCode, fresh); } public PromptTemplate get(String templateCode) { return cache.get(templateCode); } }这个坑的根源是把「模板」当静态资源而不是当一份随时可修改的业务数据。只要模板需要被运营后台变更就必须有版本管理和刷新机制。很多团队在线下拿同一个模板测两三次没问题一到线上发现改不动就是这个细节没做。5. 全栈工程化从 AI 应用生成到用户能直接用的完整链路5.1 前端页面怎么和流式接口对接整个项目跑通之前前后端最容易出现「各说各话」。前端用 EventSource 接收 SSE但 EventSource 不支持 POST浏览器原生 API 只能发 GET而生成接口需要带 body这是第一个不匹配点。两个可落地的解一个是把生成参数编码到 GET 的 query string但 body 里可能夹带长 PromptURL 长度容易炸另一个是用 fetch ReadableStream 读取 SSE 格式的响应前端手动按行解析。第二种是我的默认做法const response await fetch(/api/ai-engine/generate/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(payload) }); const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { value, done } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop(); lines.forEach(line { if (line.startsWith(data:)) { const eventData JSON.parse(line.slice(5).trim()); renderStreamToken(eventData.token); } }); }这段代码的关键是对每行以data:开头的 SSE 报文做 JSON 解析而读取流必须分块拼接因为网络包不一定正好在换行符处断开。Buffer 拼接这里我吃过亏最开始直接用value变量去判断发现中文经常被截成半个字符乱码后面改成保留本 chunk 尾部剩余字节再拼接才稳定。5.2 「无代码/低代码」生成应用的核心结构化配置的本质AI 应用生成平台的「生成」其实不是运行时才动态判断业务逻辑而是把用户配置翻译成 AI 应用的「定义文件」。这个文件建议设计成 JSON Schema 而不是直接生成代码因为 JSON 可持久化、可版本比对、可做权限控制如果直接生成 Java 代码再编译部署整个平台的交付链路会变得极其重而且越到后面越难维护。常见的做法是定义 runtime-config 表每条记录对应一个应用 ID存一个 JSON含 model、temperature、systemPrompt、knowledgeBaseId、tools 列表。ai-engine 在应用创建时把这个 JSON 落库应用详情页直接读 JSON 渲染表单。这样做的好处是所有 AI 应用本质上都是同一套 ChatModel Prompt Retriever 的组装只不过参数不同平台不需要为每个应用单独部署一个服务。5.3 AI 应用生成平台里的 Agent 和 Tool 调用怎么嵌入如果标题里的「AI 应用」只支持对话和知识库问答竞争力明显不够。一线团队做到后面都会加上「让应用调用外部工具」的能力比如让 AI 应用查今日天气、查快递、或在内部系统里建工单。LangChain4j 的工具调用需要声明参数结构然后注册给模型。Tool(根据城市名查询当前天气) public String getWeather(ToolParam(城市名称) String city) { WeatherClient client new WeatherClient(); return client.query(city); }把这段代码所在的 Bean 注入给AiServices模型就会在需要时触发这个工具。这里最容易踩的坑是 Tool 方法不要抛出受检异常模型在 Tool 执行失败时只能得到一个异常文本它很容易开始编造答案正确做法是方法内部 catch 所有异常返回一个「接口暂不可用」这样的字符串让模型如实告诉用户而不是替用户编一个假天气。5.4 部署与联调整套微服务怎么在本地先跑起来哪怕只在自己电脑上要跑通整个平台我一般也会配docker-compose把 MySQL、Redis、RabbitMQ、MinIO 四件套拉起来然后逐个启动服务的main方法。顺序有讲究先启 auth再启 file、app-manager、ai-engine最后是 gateway。因为 gateway 依赖服务发现而 ai-engine 的启动又依赖 RabbitMQ 存在如果顺序颠倒控制台会出现一堆连接拒绝的噪音虽然不影响最终启动但会干扰排查真正的问题。services: mysql: image: mysql:8.4 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ai_platform ports: - 3306:3306 redis: image: redis:7-alpine ports: - 6379:6379 rabbitmq: image: rabbitmq:3.13-management ports: - 5672:5672 - 15672:15672 minio: image: minio/minio command: server /data --console-address :9001 ports: - 9000:9000 - 9001:9001联调时最实用的排查技巧是「从前端到后端逐层关防火墙」先在浏览器 DevTools 里确认请求确实发到了 gateway再在 gateway 日志里看路由有没有匹配再到服务日志里看 Feign 调用有没有到下游。这个顺序能过滤掉大部分「明明代码 OK 但网络不通」的玄学。另外记得所有服务统一时区Spring Boot 3 启动类里加一行TimeZone.setDefault(TimeZone.getTimeZone(Asia/Shanghai))否则生成任务的时间戳会差 8 小时排查起来非常痛苦。6. 进阶验证方法与可复用的调优习惯项目做到能跑只是第一关真正能成为「简历亮点」的是你对细节的掌控力。我把最后这一章的篇幅留给三个我自己验证过、能显著提升项目完成度的动作。第一个是给 ai-engine 服务写一个「准生产压测」脚本。不需要引入复杂工具就用 jmeter 或 go-wrk 打流式接口把并发从 1 升到 20观察两个指标首 token 延迟和生成完整响应时间。你会发现大模型接口在 10 并发以后首 token 延迟变化不大但完整响应时间会大幅上升因为服务端输出带宽被占满。这个数据可以用来向面试官解释为什么要在网关层做限流而不是无限加大容器数。第二个是做一个可录屏的「平台自举」演示在 AI 应用生成平台上创建一个专门用来写 Prompt 的 AI 应用让它在 3 分钟内生成一套促销话术再把这套话术配到另一个应用里做客服。录一段 30 秒短视频放在作品 README 里。这个演示能让读者或面试官直接理解「生成平台」不是概念是真实可用的产品闭环。任何项目说明文字都比不上一段真实的屏幕录制有说服力。第三个是我个人会做、但很多人忽略的细节把生成任务表做成一个可观测面板统计每个模板的「生成成功率」「平均生成时长」「Prompt 平均 token 数」。上线两周后调一次模板内容这比任何代码评审都能提升系统质量。因为模型和模板的变化是不可解释的数据是唯一能说服自己「这次改动真的变好了」的后悔药。如果你要沿这个方向自己搭一套我的建议是别把摊子铺得太大。第一次版本只保留 app-manager ai-engine gateway 三个服务知识库用 JSON 文件临时存向量跑通整个生成闭环再逐步引入 RabbitMQ 和 MinIO。微服务架构的复杂度是必要成本但没必要在最开始一口气全上把 AI 应用生成、流式输出、动态模板这三件事做到位这个项目就有足够的区分度了。我希望这篇基于 Spring Boot 3 LangChain4j 的大厂方向 AI 应用生成平台拆解能帮到你也希望你在搭的时候减少一些我当年踩过的时间损耗。本文还有配套的精品资源点击获取