建AI智能體與工作流平臺)
簡介面向全棧開發(fā)者這份資源是一套基于Spring Boot 3與LangChain4j的AI應用平臺源碼能幫助快速搭建具備智能代碼生成、智能體編排、工作流管理與工具調(diào)用能力的完整系統(tǒng)。前端使用Vue 3構(gòu)建交互界面后端提供可視化編輯、一鍵部署、應用管理和智能路由等核心能力并整合多級存儲與Nginx通過ARMS、Prometheus與Grafana實現(xiàn)應用監(jiān)控也兼容Cursor Vibe Coding開發(fā)模式。壓縮包內(nèi)共216個文件以143個Java文件為主要組成部分承載后端業(yè)務邏輯與集成配置21個TypeScript文件和19個Vue文件對應前端頁面與交互邏輯其余包括JSON配置、SQL初始化腳本、YAML部署文件以及說明文檔等輔助材料。整體壓縮包大小約1.14MB結(jié)構(gòu)清晰便于按需閱讀。截至目前已有250人瀏覽學習適合想要深入實踐LangChain4j、LangGraph4j工作流以及AI應用工程化的開發(fā)者參考學習。1. 這個“AI應用平臺”到底在解決什么問題如果你所在團隊已經(jīng)試過把大模型接進業(yè)務系統(tǒng)大概率會遇到這三件事第一模型只會“聊天”讓它去查數(shù)據(jù)庫、改工單、調(diào)接口就得寫一堆膠水代碼第二一段固定的提示詞應付不了多步任務業(yè)務要求“先查庫存再算報價最后生成合同”每一步都有嚴格順序第三做出來的東西只活在開發(fā)者的 IDEA 里業(yè)務方想要在頁面上自己拖一拖、改一改、點一下就能上線。這個標題把這件事說全了——基于 SpringBoot3 LangChain4j Vue3 搭一個 AI 應用平臺用 AI 智能體和 ToolCalling 讓模型有手有腳用 LangGraph4j 工作流把不可控的對話變成可控的流程再用 Vue3 做一套可視化編輯、應用管理和一鍵部署的殼子。它適合兩類人一類是 Java 后端為主、想在公司內(nèi)部落地 AI 功能但不想引一堆 Python 微服務的團隊另一類是正在做 AI 應用低代碼平臺、需要參考一條端到端技術(shù)路徑的開發(fā)者。這篇筆記按后端底座、智能體、工作流、前端落地和常見坑的順序展開照著能做出一版可演示、可評審、可繼續(xù)投入的骨架。2. 后端底座SpringBoot3 LangChain4j 的模型接入與多路召回2.1 為什么選 SpringBoot3 LangChain4j而不是自己封裝 HTTP 客戶端很多 Java 團隊接到 AI 需求后的第一反應是直接調(diào)模型廠商的 HTTP 接口寫一個 RestTemplate 封裝再自己管理上下文和歷史消息。短期看沒毛病一旦要支持多模型切換、流式輸出、多輪記憶、工具調(diào)用這套手寫代碼會迅速膨脹成沒人敢動的“黑匣子”。LangChain4j 在 Java 生態(tài)里的定位相當于把 LangChain 那套抽象用 Java 重寫了一遍但它更收斂核心就幾個概念——ChatLanguageModel 管模型對話EmbeddingModel 管向量化AiService 管聲明式接口Tool 管工具調(diào)用Memory 管對話歷史。對 SpringBoot3 團隊來說集成成本比引入 Python 服務低得多而且可以跟現(xiàn)有的 Spring 容器、配置體系、事務、監(jiān)控直接融合。SpringBoot3 本身的價值在 AI 場景里會被放大一是原生支持虛擬線程處理 SSE 流式響應和工具并行調(diào)用時線程開銷明顯下降二是 SpringBoot3 的配置綁定和自動裝配讓 LangChain4j 的模型參數(shù)可以全部放進 application.yml換模型時不需要改 Java 代碼三是 SpringBoot3 對 GraalVM 的支持雖然還不是萬能靈藥但做平臺類項目時預留了后續(xù)優(yōu)化啟動內(nèi)存的余地。這一層選型的核心訴求不是“誰的生態(tài)更熱鬧”而是“這個團隊能不能低成本把 AI 能力接進現(xiàn)有的 Java 服務里”。2.2 模型接入層用 OpenAI 兼容協(xié)議適配多模型平臺類應用最忌諱把模型廠商寫死在代碼里。常見的做法是底層統(tǒng)一走 OpenAI 兼容的 Chat 接口協(xié)議上層通過配置決定連哪家服務。這樣無論是公有云模型、私有化部署的模型還是開源模型網(wǎng)關(guān)只要對方暴露了兼容接口就能一根配置切過去。LangChain4j 內(nèi)置的 OpenAiChatModel 支持自定義 baseUrl官方模型和兼容協(xié)議模型都可以掛到同一個入口下。spring: application: name: ai-platform langchain4j: open-ai: base-url: ${LLM_BASE_URL:https://your-llm-gateway.example.com/v1} api-key: ${LLM_API_KEY:} chat-model: model-name: ${LLM_MODEL_NAME:qwen2.5-72b-instruct} temperature: 0.7 timeout: 60s max-tokens: 4096 log-requests: false這段配置的關(guān)鍵在于base-url和model-name都做成了環(huán)境變量占位。平臺內(nèi)部測試用一套模型生產(chǎn)切另一套前端的工作流定義、智能體配置完全不用改。log-requests在聯(lián)調(diào)階段建議開成 true能看到實際發(fā)給模型的 payload排查“模型為什么沒按預期調(diào)用工具”時這是第一手證據(jù)上線前一定關(guān)掉否則每次對話的完整內(nèi)容都會落日志時間長了下游日志系統(tǒng)先扛不住。然后是聲明式接口。LangChain4j 的 AiService 用注解定義服務方法框架在運行時生成實現(xiàn)類AiService public interface ChatAssistant { String chat(MemoryId String memoryId, UserMessage String userMessage); Streaming FluxString streamChat(MemoryId String memoryId, UserMessage String userMessage); }這個接口直接被 Spring 代理方法上的UserMessage表示哪個人傳參數(shù)拼進用戶消息MemoryId表示按業(yè)務維度隔離對話歷史——比如一個表單一個記憶一個工單一個記憶而不是全局共享上下文。Streaming返回 Reactor 的 Flux配合 SpringBoot3 的 WebFlux 或 MVC 異步支持把流式 token 通過 SSE 推給前端。需要注意AiService 的實現(xiàn)機制對“自定義上下文拼接”不夠靈活。常見做法是平臺的應用配置里允許用戶填系統(tǒng)提示詞這部分不適合寫死在注解上而是用 ChatMemory 和 MessageWindow 在會話維度動態(tài)構(gòu)造。2.3 多路召回向量檢索 關(guān)鍵詞檢索 融合排序熱詞里那個“l(fā)angchain4j 多路召回”本質(zhì)是 RAG 里提升召回質(zhì)量的關(guān)鍵手段。單靠向量檢索遇到專有名詞、型號、工單編號這類沒有語義但字符高度精確的查詢效果會很差單靠關(guān)鍵詞檢索又接不住“幫我把最近一周未結(jié)算的訂單按金額排個序”這種口語化表達。平臺里我給知識庫場景設計的召回鏈路是一路走向量相似度一路走關(guān)鍵詞 BM25兩邊分別取 topK再做歸一化融合。Component public class HybridRetriever { private final EmbeddingStoreTextSegment embeddingStore; private final EmbeddingModel embeddingModel; private final KeywordSearchService keywordSearchService; public ListScoredDocument retrieve(String query, int topK) { // 第一路向量召回 var queryEmbedding embeddingModel.embed(query).content(); var vectorResults embeddingStore.search(queryEmbedding, topK 5) .stream() .map(hit - new ScoredDocument(hit.scoredText().text(), hit.score(), vector)) .toList(); // 第二路關(guān)鍵詞召回走 BM25 或者數(shù)據(jù)庫全文索引 ListScoredDocument keywordResults keywordSearchService.search(query, topK 5); // 第三路融合用 RRF 公式而非直接加權(quán) return fuse(vectorResults, keywordResults, topK); } }融合邏輯先刷一出分數(shù)歸一化再用倒數(shù)排名融合RRFprivate List fuse(List vectorDocs, List keywordDocs, int topK) { MapString, Double scoreMap new HashMap(); addWithRrf(scoreMap, vectorDocs, 60); addWithRrf(scoreMap, keywordDocs, 60); return scoreMap.entrySet().stream() .sorted(Map.Entry.String, DoublecomparingByValue().reversed()) .limit(topK) .map(e - new ScoredDocument(e.getKey(), e.getValue(), fused)) .toList(); }private void addWithRrf(MapString, Double map, List docs, int k) { for (int rank 0; rank docs.size(); rank) { ScoredDocument doc docs.get(rank); map.merge(doc.content(), 1.0 / (k rank 1), Double::sum); } }參數(shù)說明里最值得調(diào)的是兩個值向量召回和關(guān)鍵詞召回的數(shù)量、RRF 的常數(shù) k。常見做法是多召回一部分比如 topK5讓融合階段有得選k 一般取 60 左右調(diào)小會讓高排名文檔權(quán)重更突出調(diào)大則更平均。實際跑下來經(jīng)驗是向量召回 top 50 里如果沒有答案融合也救不回來問題通常出在文檔切分粒度或 Embedding 模型本身。 ### 2.4 代碼生成器場景模型輸出到工程落地之間還有一道閘 標題里專門點出智能代碼生成這是 AI 應用平臺最接地氣的一個能力。實現(xiàn)上不是扔一個“給我生成 UserController”的提示詞就完事而是把代碼生成拆成三步需求理解、工程結(jié)構(gòu)約束、代碼后處理。工程結(jié)構(gòu)約束是最容易被忽略的——直接讓模型自由輸出生成的代碼大概率跟項目現(xiàn)有框架不一致。 常見做法是把項目的技術(shù)棧約束、目錄規(guī)范、接口風格寫成 system prompt 的一部分再把代碼生成的產(chǎn)物限定為填充式代碼塊。例如生成一個 SpringBoot 的 Service 實現(xiàn)時平臺先注入項目模板模型只需要補全方法體。生成之后的工作站不住腳代碼格式化、編譯校驗、導入補全這三步必須自動化否則用戶拿到一坨縮進混亂、缺 import 文件根本沒法看。這部分后續(xù)會展開談但在后端底座這里要明確一條邊界——LangChain4j 負責和模型打交道代碼生成的工程部分必須由平臺自己的服務接管。 ## 3. 讓模型有手有腳ToolCalling 與 AI 智能體的實現(xiàn)細節(jié) ### 3.1 ToolCalling 到底是什么為什么智能體離不開它 讓模型直接生成“查數(shù)據(jù)庫、發(fā)郵件、創(chuàng)建工單”這些操作是不現(xiàn)實的模型本質(zhì)上是概率生成文本它不知道自己能不能執(zhí)行、有沒有權(quán)限、操作結(jié)果是什么。ToolCalling 解決的是這個“能力邊界”問題模型在對話中輸出一個結(jié)構(gòu)化請求比如“調(diào)用 searchContract 工具參數(shù) keyword采購合同”應用層負責真正執(zhí)行再把執(zhí)行結(jié)果作為新的上下文繼續(xù)讓模型推理。智能體之所以叫“智能體”就是因為它具備感知收到用戶輸入、決策決定調(diào)哪個工具、行動執(zhí)行工具、觀察讀取執(zhí)行結(jié)果的循環(huán)。 ### 3.2 用 LangChain4j 注冊一個工具參數(shù)描述決定成敗 LangChain4j 的 Tool 注解會把方法暴露給模型方法名、參數(shù)名、參數(shù)描述會拼到模型的工具定義里。這部分是對模型效果影響最大、也是最容易敷衍的地方。 java Component public class ContractTool { private final ContractRepository contractRepository; Tool(根據(jù)合同編號或關(guān)鍵字搜索合同返回合同名稱、金額、簽訂日期和當前狀態(tài)) public ListContractSearchResult searchContract( ToolParameter(搜索關(guān)鍵字可以是合同名稱片段或完整合同編號) String keyword, ToolParameter(value 是否只查有效合同默認 true) boolean activeOnly) { return contractRepository.search(keyword, activeOnly); } Tool(查詢指定合同金額是否已超過預算上限) public BudgetCheckResult checkBudget( ToolParameter(合同編號) String contractNo, ToolParameter(預算金額單位元) double budgetAmount) { // 參數(shù)進 LLM 時是字符串必須做類型校驗和范圍校驗 if (budgetAmount 0 || budgetAmount 1_000_000_000) { throw new IllegalArgumentException(預算金額超出合理范圍); } return contractRepository.checkBudget(contractNo, budgetAmount); } }兩個細節(jié)值得說。第一Tool 的工具描述要寫清楚“這個工具能做什么、返回什么”模型依賴這段描述決定何時調(diào)用描述寫得太短模型容易在其他工具上誤選寫得太長又占用上下文。第二參數(shù)描述必須包含“取值范圍、單位、主鍵格式”等信息——你寫“預算金額單位元”模型就知道把用戶嘴里的“五百萬”轉(zhuǎn)成 5000000 而不是 500 萬次調(diào)用。這個環(huán)節(jié)做不好后面所有容錯邏輯都是在給提示詞背鍋。3.3 工具執(zhí)行的循環(huán)控制超時、并發(fā)、死循環(huán)模型輸出工具調(diào)用請求后應用層要執(zhí)行工具并回填結(jié)果。LangChain4j 在 AiServices 內(nèi)建了工具執(zhí)行循環(huán)但我一般會自己控制這個循環(huán)因為平臺需要統(tǒng)一記錄每一次工具調(diào)用的入?yún)?、出參、耗時和錯誤。public ChatResponse runAgentLoop(String userMessage, ListObject tools) { ChatRequest request ChatRequest.builder() .messages(List.of(UserMessage.from(userMessage))) .toolSpecs(Specs.from(tools)) .build(); ToolContext toolContext new ToolContext(); for (int step 0; step 5; step) { ChatResponse response model.chat(request); AiMessage aiMessage response.aiMessage(); if (!aiMessage.hasToolExecutionRequests()) { return response; // 模型不再請求工具正常結(jié)束 } ListToolExecutionRequest requests aiMessage.toolExecutionRequests(); ListToolExecutionResultMessage results new ArrayList(); for (ToolExecutionRequest req : requests) { try (var ignored TimeLimiter.timeout(30, SECONDS)) { String result executeTool(req, tools, toolContext); results.add(new ToolExecutionResultMessage(req, result)); } catch (Exception e) { // 工具失敗必須回填給模型而不是中斷整個循環(huán) results.add(new ToolExecutionResultMessage(req, 工具執(zhí)行失敗: e.getMessage())); } } request appendResults(request, aiMessage, results); } throw new AgentLoopExceededException(工具調(diào)用超過5輪已終止); }這個循環(huán)里三個參數(shù)按場景調(diào)循環(huán)上限建議 3~5超過就是業(yè)務設計有問題而不是模型能力問題單工具超時 30 秒已經(jīng)偏寬松一般查詢類接口 5 秒就該返回工具失敗信息回填給模型是非常反直覺但極重要的點——模型看到“工具執(zhí)行失敗合同編號不存在”會自己修正參數(shù)再試一次而不是生硬地報錯給用戶。這里有個口語經(jīng)驗寧可讓模型多問一輪也別讓它亂猜答案。3.4 生產(chǎn)環(huán)境給工具加三道鎖工具一旦面向業(yè)務方開放就不能只考慮“能不能跑通”。第一道鎖是冪等控制尤其是寫操作工具——創(chuàng)建訂單、發(fā)送通知這類工具要支持冪等鍵否則模型在一次循環(huán)里重復調(diào)用兩次業(yè)務數(shù)據(jù)就臟了。第二道鎖是范圍校驗工具里的參數(shù)不能用默認值糊弄數(shù)字范圍、枚舉值、超長文本都要在 Java 側(cè)做校驗不能把校驗壓力留給模型。第三道鎖是審計日志每次工具調(diào)用的入?yún)?、出參、耗時、由哪次會話觸發(fā)都要落庫或者打到獨立的日志通道里——這不是為了排查問題是為了出問題時能向業(yè)務方交代。4. LangGraph4j 工作流從“自由對話”到“可控流程”4.1 為什么有了智能體還需要工作流智能體自由發(fā)揮適合“幫我寫一段代碼”這類開放任務但業(yè)務場景里更多是“先查余額再走審批最后發(fā)通知”這種固定流程。自由決策意味著同樣的輸入每次可能走不同的路徑這在 ToB 場景里是災難。LangGraph4j 把工作流建模成一張狀態(tài)圖StateGraph節(jié)點是處理單元邊是流轉(zhuǎn)規(guī)則條件邊根據(jù)當前狀態(tài)決定下一步走哪個分支。這樣既保留了 AI 節(jié)點的靈活性又把整體流程定義成了業(yè)務方可預期、可審計的確定性結(jié)構(gòu)。平臺里 LangGraph4j 的角色很明確作為后端執(zhí)行引擎承接前端可視化畫布生成的 JSON 工作流定義編譯后執(zhí)行并實時上報節(jié)點狀態(tài)。它和 Reactor 的契合度也不錯——每個節(jié)點返回的狀態(tài)變更天然適合用不可變對象傳遞。4.2 定義狀態(tài)和節(jié)點最小可跑的 LangGraph4j 流程StateGraphAgentState workflow new StateGraph(AgentState::new); workflow.addNode(planner, new PlannerNode()); workflow.addNode(coder, new CodeGenNode()); workflow.addNode(reviewer, new ReviewNode()); workflow.addNode(finish, new FinishNode()); workflow.setEntryPoint(planner); workflow.addEdge(planner, coder); workflow.addEdge(coder, reviewer); workflow.addConditionalEdge(reviewer, state - state.isApproved() ? finish : coder, Set.of(finish, coder)); CompiledGraphAgentState app workflow.compile();這段代碼里的核心概念就四個狀態(tài)AgentState、節(jié)點Node、普通邊addEdge和條件邊addConditionalEdge。AgentState 是這個流程的“黑板上寫的東西”——用戶需求、生成的代碼、評審意見、循環(huán)次數(shù)都在這個狀態(tài)對象里傳遞。常見坑是新手把狀態(tài)設計成可變對象一邊跑一邊改并發(fā)場景下一改就串號。public class AgentState { private final String userRequirement; private final String generatedCode; private final String reviewComment; private final boolean approved; private final int iterationCount; public AgentState copyWith(String newCode, String newComment, boolean newApproved) { return new AgentState(userRequirement, newCode, newComment, newApproved, iterationCount 1); } }節(jié)點實現(xiàn)只需要接收當前狀態(tài)、返回新狀態(tài)。例如 CodeGenNode 拿到 userRequirement 后調(diào)用模型生成代碼生成結(jié)果放進 copyWith 返回的新狀態(tài)里。iterationCount是防止“代碼永遠評審不通過”的保險絲——ReviewNode 里如果發(fā)現(xiàn) iterationCount 超過 3直接強制 approved 為 true留一個明確的人工介入標記。4.3 條件邊背后的“意圖識別”怎么做條件邊是工作流真正復雜的部分。它會根據(jù)當前狀態(tài)判斷下一步走向這里最常見的設計是兩類一類是規(guī)則判定比如“評審結(jié)果是否通過”“金額是否超過閾值”代碼寫死可解釋性強另一類是需要模型裁決的比如讓模型判斷“用戶這個問題是否需要查知識庫”這時候條件邊內(nèi)部就內(nèi)嵌了一次模型調(diào)用。這里的實現(xiàn)細節(jié)是模型打分結(jié)果要映射成離散的邊名不要直接用自由文本。常見做法是給模型一個枚舉讓它輸出一個 JSON 字段String rawVerdict model.generate( 根據(jù)用戶問題判斷是否需要檢索知識庫只返回 JSON: {\needSearch\: true/false}); boolean needSearch JsonPath.read(rawVerdict, $.needSearch); return needSearch ? search_kb : direct_answer;4.4 工作流如何被前端可視化編輯JSON 就是橋梁LangGraph4j 的圖定義本質(zhì)上是一張有向圖而前端可視化畫布編輯的也正是這張圖。平臺里把工作流定義統(tǒng)一成一個 JSON 結(jié)構(gòu)節(jié)點列表、邊列表、每個節(jié)點的類型和參數(shù)。前端拖拽生成這個 JSON后端解析后動態(tài)構(gòu)建 LangGraph4j 實例。節(jié)點類型做三層收斂普通 LLM 節(jié)點填提示詞和模型參數(shù)、工具節(jié)點綁定已注冊的 Tool、邏輯節(jié)點條件判斷/循環(huán)/聚合。這就帶來一個版本問題工作流 JSON 結(jié)構(gòu)會演化必須加 schemaVersion 字段后端做版本遷移。否則線上跑著 20 個應用改了字段格式老的直接編譯失敗。這塊在避坑章節(jié)再展開。5. 避坑從本地 Demo 到可用平臺最容易翻車的 5 個問題5.1 模型根本不支持 ToolCalling但代碼沒做降級現(xiàn)象功能調(diào)試時工具調(diào)用一直不觸發(fā)或者模型回答里出現(xiàn)一大段 JSON 工具請求文本而不是結(jié)構(gòu)化請求。 原因接入的模型網(wǎng)關(guān)不支持該協(xié)議或者模型名配置到了不帶工具能力的小參數(shù)版本上。 解決在模型接入層做一個能力探測請求啟動時用一條固定消息發(fā)起一次 tool call如果返回里沒有結(jié)構(gòu)化 request就把該模型標記為“不支持工具”平臺側(cè)在智能體配置頁直接禁用或提示換模型。這比運行時反復重試靠譜得多。5.2 工作流狀態(tài)對象設計成可變 Map并發(fā)時狀態(tài)互相覆蓋現(xiàn)象兩個用戶同時觸發(fā)同一個工作流A 用戶的評審意見出現(xiàn)在 B 用戶的生成代碼里。 原因狀態(tài)類里用了共享的 HashMap 存放中間數(shù)據(jù)沒有做不可變拷貝。 解決強制使用不可變對象每次節(jié)點返回新狀態(tài)實例如果有大對象需要傳遞在節(jié)點側(cè)只傳引用 ID具體內(nèi)容存外部存儲避免整個對象在每一步都被復制一次導致內(nèi)存膨脹。5.3 SSE 流式響應老是斷流或者首字遲遲不出現(xiàn)象前端 EventSource 收到幾個字就斷開重連后更亂。 原因中間代理層默認緩沖了響應模型輸出攢到一定量才刷給瀏覽器另外代理超時時間太短模型思考時間長一點就掐斷了。 解決服務端設置Cache-Control: no-cache和X-Accel-Buffering: no響應頭從架構(gòu)上繞開緩沖同時定期發(fā)一個注釋行或空格作為心跳防止空閑超時斷開。5.4 多路召回的結(jié)果反而比單路更差現(xiàn)象做了向量關(guān)鍵詞融合之后檢索質(zhì)量明顯下降最相關(guān)的文檔排在了第五第六位。 原因兩路分數(shù)沒做歸一化就線性加權(quán)向量相似度分數(shù)總體偏高把關(guān)鍵詞那路的優(yōu)勢項全壓下去了。 解決放棄線性加權(quán)改成 RRF 倒數(shù)排名融合關(guān)鍵詞召回和向量召回各自保證 top 范圍里有真相關(guān)文檔再靠 RRF 將其抬上來。5.5 LangGraph4j 版本 API 變動導致升級翻車現(xiàn)象升級小版本后addEdge 方法簽名變了編譯直接報錯。 原因LangGraph4j 還在快速演進API 穩(wěn)定性遠不如 SpringBoot。如果你搜資料跟著老版本示例寫半年后很可能跑不起來。 解決鎖定版本并記錄遷移步驟升級后先用固定的測試工作流批量回歸把工作流定義 JSON 和引擎解耦無論引擎怎么改業(yè)務方畫布里的 JSON 不變只需要適配層改解析代碼。6. Vue3 可視化編輯與一鍵部署最小實現(xiàn)與驗收技巧可視化編輯的本質(zhì)是把工作流 JSON 變成看得見的節(jié)點和連線。Vue3 做這件事的核心優(yōu)勢是 Composition API 配合 reactive/ref 管理畫布狀態(tài)比 Vue2 時期用 data 和大對象操作清晰得多。最小實現(xiàn)只需要三塊一個節(jié)點面板可拖出不同類型的節(jié)點、一個畫布放置和連線、一個屬性面板編輯選中節(jié)點的參數(shù)const nodes: RefFlowNode[] ref([]) const edges: RefFlowEdge[] ref([]) function addNode(type: string, position: { x: number; y: number }) { nodes.value.push({ id: crypto.randomUUID(), type, // llm | tool | condition position, config: initConfigByType(type), }) } function toWorkflowJson(): WorkflowDefinition { return { schemaVersion: 1, nodes: nodes.value, edges: edges.value, } } function loadWorkflow(json: WorkflowDefinition) { nodes.value reactive(json.nodes) edges.value reactive(json.edges) }這里要提醒一句不要讓畫布組件直接持有業(yè)務數(shù)據(jù)。nodes 里存的是“圖的結(jié)構(gòu)”具體的提示詞、模型參數(shù)、工具名都放在每個節(jié)點的 config 里。一鍵部署的做法是把 toWorkflowJson() 的產(chǎn)物交給后端接口后端將其持久化并構(gòu)建 LangGraph4j 實例運行時的模型 API Key、知識庫連接串全部通過環(huán)境變量注入工作流 JSON 里不出現(xiàn)任何敏感信息。驗收時我會固定用 20 個典型場景跑一遍離線回放對比每個節(jié)點的入?yún)⒊鰠⑹欠穹项A期再放開給業(yè)務方試用。這是我踩出來的習慣——以前總覺得線上能跑就是好后來一次升級把評審節(jié)點跑丟了只能連夜回滾。從那以后每次改工作流引擎我都先過一遍回放再上線。希望這套路徑和這些坑能幫你少走一段彎路。本文還有配套的精品資源點擊獲取