ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LangChain4j+LangGraph4j:Java AI应用平台实战

LangChain4j+LangGraph4j:Java AI应用平台实战 简介面向全栈 AI 应用开发者的工程示例与平台源码包聚焦 Spring Boot 3 LangChain4j Vue 3 技术栈覆盖智能代码生成、AI 智能体、LangGraph4j 工作流与 Tool Calling 等核心能力并展示可视化编辑、一键部署、应用管理及智能路由的实现思路既有完整的前后端交互链路也适合作为 AI 应用平台脚手架进一步扩展。压缩包共 216 个文件、约 1.14MB其中 Java 后端逻辑约占 143 个文件Vue/TS 前端页面与交互约 40 个文件另有 JSON、XML、YML、SQL 等配置脚本及 Markdown 说明目录分层明显便于按模块阅读与二次开发智能体编排、工具调用注册、工作流节点配置等关键示例均包含在内。资源还结合多级存储与 Nginx 部署给出 Prometheus、Grafana、ARMS 监控方案并预留 Cursor Vibe Coding 协作开发入口可帮助读者快速复现一套可观测、可扩展的 AI 应用平台骨架。已有 250 人学习下载适合正在搭建智能体、工具调用或工作流平台的同学对照源码梳理完整实现链路。1. 从智能体到代码生成这个 AI 应用平台到底在解决什么问题一个 Java 技术栈为主的团队想上 AI 应用往往卡在同一个地方Python 生态的 LangChain 很成熟但团队没人愿意跨界维护两套技术栈。这个平台标题给出的答案很直接——用 SpringBoot3 做后端底座用 LangChain4j 接大模型用 LangGraph4j 做工作流编排再配一个 Vue3 的可视化画布把 AI 能力变成能拖拽、能部署、能管理的产品。它解决的不是怎么调一次大模型接口而是怎么把智能体、代码生成、工具调用这些能力沉淀成企业内部的标准化应用平台。适合两类人一是想从零构建 AI 应用平台的架构师二是接了类似需求但不知道从哪落地的后端和前端工程师。2. SpringBoot3 LangChain4j 的工程底座选型理由与最小可运行配置2.1 为什么是 LangChain4j 而不是自研封装或 Python LangChain先回答一个很多人纠结的问题我直接写 OpenAI SDK 或者 Spring AI 不行吗自己封装 OpenAI SDK 当然能跑通对话但一旦涉及多轮对话的记忆管理、文档的 Embedding 切分、工具的自动发现和调用代码量会迅速膨胀。LangChain4j 在 Java 生态里的定位和 Python 版 LangChain 一样把这些通用能力抽象成 ChatModel、EmbeddingModel、AiServices、Tool 这些接口你只需要替换模型厂商的依赖业务代码基本不动。Spring AI 和 LangChain4j 之间我选了后者原因是它对 ToolCalling 和函数回调的支持更直接Tool 注解的体验和 Java 开发者的直觉一致。LangGraph4j 的出现补齐了 LangChain4j 最缺的工作流编排能力——之前做复杂 Agent 只能自己手写状态机现在有官方的图执行引擎节点、条件边、状态传递都是声明式的。这里要认清一个现实LangChain4j 的迭代速度很快API 在不同小版本之间有过调整所以工程落地时要先锁版本不要一路上最新。2.2 SpringBoot3 工程骨架与模型接入的最小配置SpringBoot3 强制要求 JDK17 起步实际项目我建议直接用 JDK21LTS 版本虚拟线程对 AI 场景的流式输出有帮助。创建一个标准的 Maven 工程关键依赖就三个langchain4j-spring-boot-starter、langchain4j-open-ai兼容 OpenAI 协议的服务都走这个、langgraph4j-core。下面这个 pom 片段是经过验证的最小集合properties java.version21/java.version spring-boot.version3.3.5/spring-boot.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-spring-boot-starter/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId /dependency !-- LangGraph4j 建议挂在 langchain4j 同一版本族下避免接口不匹配 -- dependency groupIdcom.langchain4j/groupId artifactIdlanggraph4j-core/artifactId /dependency /dependencies注意 langchain4j 和 langgraph4j 的 groupId 不一样前者是 dev.langchain4j后者是 com.langchain4j。版本号我刻意没写死因为这两个库的版本更新频繁直接查 Maven 仓库选最新稳定版即可但一定要检查 langgraph4j 依赖的 langchain4j-core 版本和你项目里的保持一致否则运行时会出现 NoSuchMethodError。模型接入配置走 SpringBoot 的配置文件这里以 OpenAI 兼容接口为例国内云厂商的模型网关、Ollama、vLLM 部署的本地模型都能通过这个方式接入langchain4j: open-ai: chat-model: base-url: ${LLM_BASE_URL:http://localhost:8000/v1} api-key: ${LLM_API_KEY:sk-local} model-name: ${LLM_MODEL:qwen2.5-coder:32b} temperature: 0.2 max-tokens: 4096 log-requests: true log-responses: true embedding-model: base-url: ${LLM_BASE_URL:http://localhost:8000/v1} api-key: ${LLM_API_KEY:sk-local} model-name: ${EMBEDDING_MODEL:bge-m3}base-url 通过环境变量注入这样同一个 jar 包在开发、测试、生产环境不用改配置。temperature 设成 0.2 是代码生成场景的推荐值温度太高模型会自由发挥生成的东西看着像代码但编译不过。log-requests 和 log-responses 在联调阶段一定要开LangChain4j 会把完整的请求体和响应体打出来排查 prompt 问题和 token 统计都靠它。2.3 模型接入层的抽象OpenAI 兼容接口与本地模型共存实际项目里不太可能只接一家模型。我一般会在业务代码之上加一层 ModelRouter按场景路由到不同模型意图识别用便宜的小模型代码生成用能力强的 32B 以上模型Embedding 固定用一个。LangChain4j 的 ChatModel 接口天然支持这种抽象你只需要在配置类里声明多个 BeanConfiguration public class ModelConfig { Bean Primary public ChatModel mainChatModel(Value(${llm.main.model}) String model) { return OpenAiChatModel.builder() .baseUrl(http://localhost:8000/v1) .apiKey(sk-local) .modelName(model) .temperature(0.2) .build(); } Bean public ChatModel fastChatModel(Value(${llm.fast.model}) String model) { return OpenAiChatModel.builder() .baseUrl(http://localhost:8000/v1) .apiKey(sk-local) .modelName(model) .temperature(0.1) .maxTokens(1024) .build(); } }Primary 注解保证默认注入的是能力最强的那个模型需要快模型的地方用 Qualifier(fastChatModel) 显式指定。这个做法的好处是后续接 Anthropic 或 Gemini 时只要再实现一个 ChatModel Bean业务代码零改动。到了这个阶段你已经有一个能对话、能 Embedding 的 SpringBoot3 后端了下一步就是把它升级成能自主完成任务的智能体。3. 用 LangGraph4j 编排代码生成 Agent节点、条件边与 ToolCalling3.1 LangGraph4j 的核心概念State、节点与条件边LangGraph4j 解决的核心问题是一个智能体不只是一次模型调用而是感知-规划-行动-观察的循环。你需要在代码里显式表达这个循环包括循环什么时候结束、状态怎么在节点之间传递。它有三个基础概念State状态、Node节点、Edge边。State 是一个在节点间传递的数据载体Node 是处理状态的一个函数Edge 定义节点的连接关系其中一种特殊的边叫条件边根据当前状态决定下一步走哪个节点。这样设计的好处是人和模型的分工变清楚了节点里的逻辑是你写死的确定性代码模型只负责在节点里产出内容或决定走哪条条件边。相比纯提示词驱动的 AgentLangGraph4j 让整个流程可观测、可 debug、可回放——这正是生产环境最需要的东西。下面定义一个代码生成工作流的状态类型public interface CodeGenState { GraphStateDTOString requirement GraphStateDTO.string(requirement); GraphStateDTOString plan GraphStateDTO.string(plan); GraphStateDTOString sourceCode GraphStateDTO.string(sourceCode); GraphStateDTOString execResult GraphStateDTO.string(execResult); GraphStateDTOInteger retryCount GraphStateDTO.int32(retryCount); GraphStateDTOBoolean passed GraphStateDTO.bool(passed); }每个节点只关心它需要的字段LangGraph4j 会在节点完成后自动合并新状态。retryCount 在这里很关键它控制整个工作流的终止条件避免模型陷入生成-执行失败-再生成的死循环。3.2 定义代码生成工具Tool 的参数描述决定调用准确率ToolCalling 是大模型连接外部系统的通道。LangChain4j 的做法是用 Tool 注解标记一个 Java 方法模型根据方法名、描述和参数描述来决定什么时候调用、传什么参数。很多人第一次写工具方法时只写方法名结果模型死活不调用原因就是描述信息太少模型不知道这个方法能干什么、参数应该填什么。Component public class CodeExecutionTools { Tool(执行传入的 Java 源码返回编译和运行的标准输出如果编译失败则返回错误信息) public String runJavaCode( ToolParam(完整的 Java 源码字符串必须包含 public class Main 和 main 方法) String sourceCode) { // 将 sourceCode 写入临时文件调用 javac 编译java 执行 // 捕获 stdout 和 stderr超时时间设为 10 秒防止模型生成死循环代码。 return output; } Tool(读取项目内指定相对路径的文本文件内容) public String readFile( ToolParam(相对项目根目录的文件路径) String filePath) { return FileUtil.readUtf8String(filePath); } }ToolParam 里的描述会作为参数说明拼进模型请求的 tools 定义里描述越具体模型传参的准确率越高。我踩过的坑是参数描述太模糊模型把文件路径传成 test.java而实际要传 src/test/java/Test.java。这个环节没捷径每个工具都要站在模型的角度写清楚这个参数应该是怎样的格式。3.3 组装可执行的工作流plan → 生成 → 执行 → 评审有了状态和工具就可以组装工作流了。这个代码生成 Agent 的流程是先让模型读需求做计划再生成源码然后调用工具执行最后让模型评审执行结果。评审不通过且重试次数没超限就带着错误信息回到生成节点重新生成。Service public class CodeGenWorkflow { private final ChatModel chatModel; private final ToolExecutor toolExecutor; public CodeGenWorkflow(ChatModel chatModel, ToolExecutor toolExecutor) { this.chatModel chatModel; this.toolExecutor toolExecutor; } public StateGraphCodeGenState buildGraph() { StateGraphCodeGenState graph new StateGraph(CodeGenState.SPEC); graph.addNode(planner, this::planNode); graph.addNode(codegen, this::codeGenNode); graph.addNode(executor, this::executeNode); graph.addNode(reviewer, this::reviewNode); graph.setEntryPoint(planner); graph.addEdge(planner, codegen); graph.addConditionalEdge(executor, this::needFix, Map.of(true, codegen, false, reviewer)); graph.addEdge(reviewer, StateGraph.END); return graph; } private MapString, Object planNode(MapString, Object state) { String requirement (String) state.get(requirement); String prompt 你是资深后端架构师。根据需求输出实现计划要求 1. 列出需要创建的类及其职责 2. 标注每个类之间的依赖关系 3. 计划不超过 200 字 需求%s .formatted(requirement); String plan chatModel.generate(prompt); return Map.of(plan, plan); } }condition 边是 LangGraph4j 的核心needFix 方法返回 true 时回到 codegen 节点false 时进入 reviewer 节点。这样模型生成错了也不怕executor 节点拿到的编译错误会被拼进新一轮生成的 Prompt形成错误反馈-重新生成的闭环。注意节点函数的入参和返回值都是 Map变量名要和状态定义时的 key 保持一致。组装工具调用要注意 langgraph4j 的机制和 LangChain4j 的 AiServices 不太一样——工作流节点里需要你自己把 ChatModel 和工具的执行结果串联起来。常见的做法是复用 LangChain4j 的 AiServices把它绑定工具类后作为一个整体节点调用private MapString, Object codeGenNode(MapString, Object state) { CodeGenerator agent AiServices.builder(CodeGenerator.class) .chatModel(chatModel) .tools(new CodeExecutionTools()) .build(); String requirement (String) state.get(requirement); String plan (String) state.get(plan); String lastError state.get(execResult) null ? : (String) state.get(execResult); String code agent.generate(requirement, plan, lastError); return Map.of(sourceCode, code); }这里 AiServices 内部已经把 ToolCalling 的消息循环封装好了模型请求调用工具框架自动执行工具方法再把结果回传给模型直到模型给出最终答案。你只需要在界面接口里定义 generate 方法并标注 SystemMessage 和 UserMessage 模板即可这就是 LangChain4j 把 Java 接口变成 Agent 的标准方式。3.4 流式输出与进度推送SSE 把过程反馈给前端工作流跑起来后如果整个操作要等 30 秒才返回前端的体验是灾难。我一般用 SSEServer-Sent Events把每个节点的执行状态实时推给前端。后端在节点入口往 Spring 的 SseEmitter 里写一条进度事件前端在画布对应节点上亮灯。LangGraph4j 本身没有内置 SSE 支持但你可以用一个全局的 WorkflowProgressPublisher 组件节点里调用它发事件Controller 层暴露 SSE 接口订阅。Go 的实现不复杂SseEmitter 存进 ConcurrentHashMap节点状态变化时遍历发送前端用 EventSource 接收。注意 SseEmitter 默认超时时间是 30 秒工作流超过这个时间要记得在初始化时显式设置更长超时。4. Vue3 可视化编排从拖拽画布到应用管理的一体化前端4.1 画布的数据模型一个 JSON 就是一个工作流可视化编排的本质是所见即所得地编辑一个 JSON后端拿着这个 JSON 再把它翻译成 LangGraph4j 的 StateGraph。所以前端画布的数据模型一定要工作流对齐而不是只顾画得好看。我维护的数据结构长这样{ nodes: [ { id: n1, type: agent, label: 代码生成, config: { model: qwen2.5-coder:32b, temperature: 0.2 } }, { id: n2, type: tool, label: 执行器, config: { toolName: runJavaCode } }, { id: n3, type: condition, label: 评审通过, config: { condition: review.passed true } } ], edges: [ { source: n1, target: n2, label: normal }, { source: n2, target: n3, label: normal }, { source: n3, target: n1, label: false }, { source: n3, target: end, label: true } ], global: { maxRetry: 3, timeoutSeconds: 120 } }node 的 config 字段是给属性面板用的每个节点的类型不同配置项也不同。agent 节点要选模型和调参tool 节点要选工具名和入参。后端解析这个 JSON 时按 type 分发到不同的节点工厂这样就实现了一次编排、到处执行。4.2 节点面板、连线和属性表单的 Vue3 实现Vue3 实现拖拽画布选 vue-flow 最省力它对 Vue3 Composition API 支持得很好节点拖拽、连线、缩放的交互开箱即用。核心组件结构是左侧一个节点物料面板中间是画布右侧是选中节点的属性表单三个区域的数据流都汇到一个 reactive 对象上script setup import { ref, reactive, computed } from vue import { VueFlow, useVueFlow } from vue-flow/core import vue-flow/core/dist/style.css import AgentNode from ./nodes/AgentNode.vue import ToolNode from ./nodes/ToolNode.vue const nodes ref([]) const edges ref([]) const selectedNode ref(null) // 左侧物料面板的拖拽把节点类型写进 dataTransfer // 画布 drop 事件里根据类型创建节点对象。 function onDragStart(event, type) { event.dataTransfer.setData(application/flow-type, type) event.dataTransfer.effectAllowed move } function onDrop(event) { const type event.dataTransfer.getData(application/flow-type) const position project({ x: event.clientX, y: event.clientY }) nodes.value.push({ id: node-${Date.now()}, type, position, config: defaultConfig(type) }) } // 选中节点后它的 config 直接绑定到右侧表单组件 // 表单的每一项都是动态渲染的节点类型决定渲染哪些字段。 const selectedConfig computed(() selectedNode.value?.config || {}) /script核心思路就两条节点类型决定默认配置和属性表单的渲染字段选中节点和属性表单之间用 selectedNode 单例绑定。属性表单这里有一个 Vue3 动态增删表单项的经典需求——agent 节点的环境变量是不定长的我直接用 v-for 渲染一组 key-value 输入框添加和删除按钮只操作数组的 push 和 splice配 deep 监听同步到画布节点的 configdiv v-for(env, index) in selectedConfig.envs :keyindex classenv-row input v-modelenv.key placeholder环境变量名 / input v-modelenv.value placeholder值 / el-button typedanger clickselectedConfig.envs.splice(index, 1)删除/el-button /div el-button clickselectedConfig.envs.push({ key: , value: })添加环境变量/el-button这个表单项的增删改查全在 reactive 对象上完成Vue3 的代理机制保证画布节点同步刷新。整体画布在工作中就是一个后台管理系统里的复杂表单只是这个表单的结构不是人定的是用户自己拖出来的。4.3 应用管理与一键部署的前后端衔接可视化编辑只是平台的前半段后半段是应用管理和一键部署。后端需要提供应用维度的 CRUD 接口创建应用、保存画布 JSON、发布版本、查看部署状态。我习惯把保存和发布拆成两个动作——保存只写数据库草稿发布才真正触发构建和部署。这样用户频繁调整画布不会产生一堆垃圾部署记录发布记录表里每一行都可回滚。一键部署的后端实现是异步任务加日志流推送。收到发布请求后后端把应用 ID、画布 JSON、模型配置打包成一个部署任务丢给线程池执行。部署过程分四步生成后端工程骨架、写入工作流配置、Maven 打包、Docker 构建并启动。每一步的日志都通过 SSE 实时推给前端用户在浏览器里能看到构建进度条一行一行滚动这是一键部署体验的关键。PostMapping(/api/apps/{appId}/deploy) public SseEmitter deploy(PathVariable Long appId, RequestBody DeployRequest request) { SseEmitter emitter new SseEmitter(300_000L); deployService.submit(appId, request, emitter); return emitter; }SseEmitter 的超时时间我显式设成了 300 秒因为首次构建要拉 Maven 依赖和 Docker 基础镜像慢的时候能跑到 3 分钟以上。前端 EventSource 创建后要监听 readyState 变化部署完成后主动 close 连接避免无效连接占满 Tomcat 线程。5. 从开发到上线的常见问题排查5 个高频踩坑点5.1 SpringBoot 版本与 JDK 版本不匹配导致注解扫描失败现象项目启动后报 ClassNotFoundException: jakarta.servlet.http.HttpServlet或者 swagger 页面打不开接口全 404。原因SpringBoot3 用的是 Jakarta EE 规范包名从 javax.* 改成了 jakarta.*。如果你的本机 JDK 是 8 或 11Maven 编译时用了低版本 source/target生成的字节码指向老的 javax 包运行时必然找不到类。还有一种情况是依赖里混入了 javax.servlet-api 的老传递依赖把包名污染了。解决JDK 锁到 17 或 21在 pom 里显式声明 spring-boot-maven-plugin 的 jvmArguments 为 --add-opens java.base/java.langALL-UNNAMED防止反射告警。排除所有 javax.servlet 开头的依赖改用 spring-boot-starter-web 统一管理。排查命令是 mvn dependency:tree看是否有老 servlet 传递进来。5.2 WebFlux 与 WebMVC 冲突SSE 流式输出起不来的元凶现象后端加了 SSE 接口后项目启动直接报 SpringApplicationApplicationContextException: Invalid web application: spring.main.web-application-typenone或者 SSE 接口永远挂起不返回。原因LangChain4j 的流式响应在部分版本里默认依赖 WebFlux 的 Flux 类型引入 spring-boot-starter-webflux 后和原来的 spring-boot-starter-web 打架。Spring 容器检测到两个 WebApplicationFactory直接不知道用哪个只能报错。解决确定自己用的是 Servlet 栈还是响应式栈。我用 Servlet 栈做 SSE就别引 webfluxLangChain4j 流式输出用 StreamingChatModel 接口阻塞式订阅 Flux 再转 SseEmitter。如果非得混用就把 web-application-type 显式设为 servlet并保证 webflux 的依赖 scope 是 provided 或完全不引入。这个坑排查起来很费时间启动日志里看到 invalid web application 直接去查依赖树别去改配置瞎试。5.3 反代缓冲让流式输出变成等半天一次性吐出来现象本地 flow 输出一个字一个字蹦部署到服务器之后前端 EventSource 要等几十秒才收到第一批内容。原因Nginx 默认开了 proxy_buffering会等后端响应全部写完再一次性转发给客户端SSE 的 chunked 流式效果被彻底吞掉。这不是后端代码的问题是网关层配置问题典型的上线才踩坑。解决在 Nginx 的 location 配置里加上 proxy_buffering off; 和 proxy_cache off;同时把 proxy_read_timeout 调到 300 秒以上。注意 SSE 用的是长连接心跳设置要同时照顾 Nginx 和浏览器两侧的超时。代码里 SseEmitter 注释里写清楚必须配 Nginx 关缓冲防止部署的人不懂这个关联关系。5.4 ToolCalling 工具参数缺失描述模型宁可拒绝也不调用现象日志里模型明确说需要调用工具但响应里的 tool_calls 是空的或者传过来一个空的 JSON 参数业务侧解析直接抛异常。原因大模型在不确定工具参数格式时会选择不调用或传入空值。而我早期写的 ToolParam 只有参数名没有描述模型不知道这个字符串应该填相对路径还是绝对路径。更隐蔽的是参数类型不匹配——工具方法声明的是 Map模型传过来的却是 JSON 字符串。解决每个 ToolParam 都按格式 示例写描述比如相对项目根目录的文件路径例如 src/main/java/Application.java。复杂参数不要用 Map定义成 POJO 配合 ToolParam 逐字段描述。另外要确认工具返回值转成 String 后没有截断有些模型服务对 tool message 有长度限制超长返回会被丢弃表现为工具调用后模型没有反应。5.5 Vue3 属性继承与表单校验失效的连带问题现象属性面板里填的表单值保存后又变回原样或者画布的全屏按钮在 Edge 浏览器里偶尔点不动鼠标点上去没反应。原因这个现象和 Vue3 的组件属性透传机制有关。自定义组件如果根元素恰好是另一个组件外部传入的 class、style、事件监听器会透传到根组件上如果根组件内部也用 v-bind$attrs容易产生事件覆盖。比较典型的是 Element Plus 的 el-form-item 在动态渲染时label 属性被透传走了导致校验触发条件丢失表单值不更新另一种情况是画布全屏按钮的外层容器把 click 事件捕获了。解决在 Vue3 组件里显式声明 inheritAttrs: false并手动在需要的位置绑定 $attrs避免事件和样式透传到意外的 DOM 节点上。动态表单项的 prop 属性要用 index 拼接区分不要都用同一个固定字符串否则校验状态互相覆盖。Edge 里点不动按钮先怀疑是否被某个透明遮罩层挡住给按钮加 z-index 并确保外层组件有明确的定位上下文别裸奔。注意以上五条是高频但不是全部AI 应用平台涉及的新依赖多、版本节奏快上线前把它当体检列表过一遍能省出至少一个周末的排错时间。6. 进阶多模型路由、链路追踪与压测验证平台能跑通以后下一个问题就是成本和质量。大模型 API 按 token 计费代码生成场景一次调用动辄几千 token全公司都用最强的 32B 模型扛不住。我一般会在网关层做模型路由分发而这正是多路召回思路的工程化落地——把用户请求先做意图分类简单任务翻译、格式化、解释报错直接落到一个轻量模型上复杂任务生成完整项目、多文件改造才转发到强模型。public interface ModelRouter { String route(String intent, int estimatedInputTokens); } Component public class DefaultModelRouter implements ModelRouter { Override public String route(String intent, int estimatedInputTokens) { if (estimatedInputTokens 3000 || intent.startsWith(codegen:)) { return strong; } return fast; } }估 token 的办法不精确但够用中文字符按 1.5 倍估算代码字符按 0.4 倍估算取整加余量。另外链路追踪要趁早接工作流的每个节点执行耗时、模型调用的 token 消耗、工具调用的成败全都要落日志traceId 从请求进来就生成并贯穿 SSE 和异步任务。我用的是最朴素的方案——MDC put traceIdlogback 输出到控制台和文件排障时按 traceId grep 一次拿到全链路。微服务规模大了再上 SkyWalking 也不迟单机阶段别过度设计。压测验证重点关注两个指标工作流单次完成时间 P95 和部署任务的并发上限。前者决定用户体验后者决定平台的稳定性。用压测工具模拟 50 个并发用户同时触发代码生成工作流观察数据库连接池和线程池是否成为瓶颈。LangGraph4j 的节点执行是阻塞式的但节点之间没有共享状态可以放心加 Async 把无依赖的节点并行化。我自己踩过的教训是刚开始为图省事把所有工具方法都加上 synchronized结果多路召回一上线就串行排队P95 从 8 秒暴涨到 40 秒。后来改成工具内部用无状态设计只在写文件时用临时文件隔离压测直接达标。这个平台的完整落地路径是SpringBoot3 接模型能力LangGraph4j 编排复杂工作流ToolCalling 打通系统边界Vue3 把这一切变成可视化操作。到了后期你会意识到平台最大的价值不是某一个 AI 能力而是把散落在各种脚本里的 AI 调用沉淀成了可编排、可观测、可回滚的标准化应用。希望这些思路和踩坑记录能帮到你少走一段我走过的弯路。本文还有配套的精品资源点击获取
返回列表