現(xiàn) AI 編排引擎)
1. 為什么要在 RuoYi-Vue-Plus 里塞進(jìn)一個(gè) AI 編排引擎RuoYi-Vue-Plus 這套腳手架在國(guó)內(nèi)中小型管理系統(tǒng)開(kāi)發(fā)里出鏡率極高權(quán)限、數(shù)據(jù)權(quán)限、多租戶、代碼生成、工作流這些企業(yè)級(jí)能力基本開(kāi)箱即用很多團(tuán)隊(duì)拿它當(dāng)后臺(tái)底座來(lái)搭業(yè)務(wù)系統(tǒng)。但這兩年有個(gè)明顯變化業(yè)務(wù)方開(kāi)始不滿足于表單列表審批這套老三樣越來(lái)越多的需求變成能不能讓系統(tǒng)自己判斷一下能不能根據(jù)這段描述自動(dòng)生成工單能不能把這幾份文檔自動(dòng)歸類。說(shuō)白了就是要在傳統(tǒng) CRUD 系統(tǒng)里長(zhǎng)出一點(diǎn)智能。問(wèn)題在于絕大多數(shù) RuoYi 系項(xiàng)目是純 Java 技術(shù)棧團(tuán)隊(duì)里沒(méi)人愿意為了一個(gè) AI 功能再單獨(dú)維護(hù)一套 Python 服務(wù)跨語(yǔ)言調(diào)用、部署、監(jiān)控、鑒權(quán)全是額外成本。這時(shí)候把 AI 編排能力直接以 Java 庫(kù)的形式嵌進(jìn)現(xiàn)有工程就成了最省事的路子。AgentScope 的 Harness 模塊正好提供了這樣一套 Java 側(cè)的 Agent 編排能力它把模型調(diào)用、工具注冊(cè)、多輪對(duì)話、執(zhí)行循環(huán)這些原本散落在各處的邏輯收斂成一個(gè)可編程的運(yùn)行時(shí)。這篇內(nèi)容面向的是已經(jīng)熟悉 RuoYi-Vue-Plus 基本結(jié)構(gòu)、想在不引入第二套語(yǔ)言棧的前提下接入 AI 能力的后端開(kāi)發(fā)者。我會(huì)把整個(gè)集成過(guò)程拆開(kāi)講從依賴怎么引、模塊怎么放到 Agent 怎么注冊(cè)、工具怎么暴露、會(huì)話怎么和系統(tǒng)用戶綁定再到實(shí)際跑起來(lái)之后那些文檔里不會(huì)寫(xiě)的坑。核心關(guān)鍵詞就三個(gè)AgentScope Harness 集成、純 Java AI 平臺(tái)、RuoYi-Vue-Plus 擴(kuò)展。讀完你應(yīng)該能自己動(dòng)手把一套可用的 AI 編排能力掛到現(xiàn)有后臺(tái)里而不是停留在知道有這么個(gè)東西。2. 集成前的架構(gòu)判斷Harness 到底該放在哪一層2.1 RuoYi-Vue-Plus 的模塊劃分與 AI 能力的歸屬RuoYi-Vue-Plus 默認(rèn)是多模塊 Maven 結(jié)構(gòu)常見(jiàn)的是ruoyi-admin啟動(dòng)入口、ruoyi-system系統(tǒng)業(yè)務(wù)、ruoyi-common通用工具、ruoyi-framework框架配置這幾塊。AI 編排能力放哪里直接決定了后面依賴會(huì)不會(huì)打架、循環(huán)依賴會(huì)不會(huì)出現(xiàn)。我的建議是單獨(dú)開(kāi)一個(gè)ruoyi-ai模塊而不是塞進(jìn)ruoyi-system或者ruoyi-common。理由很實(shí)際Harness 會(huì)引入自己的 HTTP 客戶端、JSON 處理、可能的日志門面這些和ruoyi-common里已有的工具類存在版本沖突風(fēng)險(xiǎn)。單獨(dú)成模塊依賴隔離干凈ruoyi-admin只依賴ruoyi-ai的接口層業(yè)務(wù)模塊通過(guò)接口調(diào)用不會(huì)把 AI 的傳遞依賴污染到整個(gè)工程。具體依賴方向是這樣的ruoyi-ai依賴ruoyi-common復(fù)用它的統(tǒng)一返回、異常、工具類ruoyi-system按需依賴ruoyi-airuoyi-admin負(fù)責(zé)把ruoyi-ai的 Bean 掃描進(jìn)來(lái)。這樣 AI 模塊既能用上框架的通用能力又不會(huì)反向被業(yè)務(wù)模塊依賴保持單向。2.2 為什么不做成獨(dú)立微服務(wù)有人會(huì)問(wèn)既然 AI 調(diào)用可能耗時(shí)、可能不穩(wěn)定為什么不干脆拆成獨(dú)立服務(wù)我的實(shí)測(cè)結(jié)論是在中小規(guī)模場(chǎng)景下獨(dú)立服務(wù)的運(yùn)維成本遠(yuǎn)大于它帶來(lái)的隔離收益。拆出去意味著你要多維護(hù)一套注冊(cè)發(fā)現(xiàn)、多一套鑒權(quán)透?jìng)鳌⒍嘁惶祖溌纷粉櫠?AI 調(diào)用本身在 RuoYi 這種后臺(tái)系統(tǒng)里往往是低頻、非核心路徑的操作比如生成一段摘要、做一次分類完全可以用線程池 異步任務(wù)的方式在單體里消化掉。真正需要拆服務(wù)的臨界點(diǎn)是當(dāng) AI 調(diào)用變成高頻核心鏈路或者需要獨(dú)立擴(kuò)縮容的時(shí)候。在那之前單體里嵌一個(gè)模塊部署簡(jiǎn)單、調(diào)試方便、事務(wù)和權(quán)限都能復(fù)用現(xiàn)成的這才是純 Java AI 平臺(tái)這個(gè)思路的價(jià)值所在。2.3 Harness 在運(yùn)行時(shí)的角色定位把 Harness 理解成一個(gè)Agent 運(yùn)行時(shí)容器比較準(zhǔn)確。它本身不負(fù)責(zé)具體業(yè)務(wù)而是提供Agent 的生命周期管理、消息在 Agent 之間的流轉(zhuǎn)、工具的注冊(cè)與調(diào)用分發(fā)、以及一輪對(duì)話的執(zhí)行循環(huán)。你在 RuoYi 里要做的事情本質(zhì)上是把系統(tǒng)的業(yè)務(wù)能力包裝成 Harness 能識(shí)別的工具再把 Harness 的執(zhí)行結(jié)果接回系統(tǒng)的業(yè)務(wù)流。這個(gè)定位決定了集成的兩個(gè)關(guān)鍵接口一個(gè)是工具注冊(cè)把 RuoYi 的 Service 方法暴露給 Agent另一個(gè)是會(huì)話管理把 Harness 的會(huì)話和 RuoYi 的登錄用戶、租戶綁定起來(lái)。這兩塊做扎實(shí)了剩下的就是配置和調(diào)優(yōu)。3. 依賴引入與模塊骨架搭建的實(shí)操細(xì)節(jié)3.1 Maven 依賴的引入方式與版本對(duì)齊Harness 作為 Java 庫(kù)通常以 Maven 坐標(biāo)形式提供。在ruoyi-ai模塊的pom.xml里引入時(shí)第一件要確認(rèn)的事是它依賴的 JSON 庫(kù)和 HTTP 客戶端版本。RuoYi-Vue-Plus 較新版本用的是 Jackson 或者 Hutool 的 JSON 工具如果 Harness 內(nèi)部用的是別的 JSON 實(shí)現(xiàn)就可能在序列化時(shí)出現(xiàn)字段丟失、日期格式不一致這類隱蔽問(wèn)題。我的做法是在ruoyi-ai的pom.xml里顯式聲明 Harness 依賴然后用mvn dependency:tree把整棵依賴樹(shù)打出來(lái)重點(diǎn)看三類包JSON 處理、HTTP 客戶端、日志門面。如果發(fā)現(xiàn)和父工程已有版本沖突用exclusions把 Harness 傳遞進(jìn)來(lái)的舊版本排掉統(tǒng)一走父工程的版本管理。這一步看著瑣碎但不做的話后面會(huì)出現(xiàn)本地跑得好好的打包上線就報(bào) NoSuchMethodError這種經(jīng)典問(wèn)題。dependency groupIdcom.example.agentscope/groupId artifactIdharness-core/artifactId version${harness.version}/version exclusions exclusion groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /exclusion /exclusions /dependency提示版本號(hào)統(tǒng)一放到父工程的properties里管理別在子模塊里寫(xiě)死否則多模塊升級(jí)時(shí)會(huì)漏改。3.2 模塊目錄結(jié)構(gòu)與包命名約定ruoyi-ai模塊內(nèi)部我習(xí)慣按職責(zé)分四個(gè)包c(diǎn)onfig自動(dòng)配置和 Bean 聲明、agentAgent 定義與注冊(cè)、tool業(yè)務(wù)工具封裝、service對(duì)外暴露的服務(wù)接口。這樣分的好處是當(dāng)你要排查某個(gè)工具為什么沒(méi)被 Agent 調(diào)用到時(shí)直接去tool包看注冊(cè)邏輯不用在幾百個(gè)類里翻。包名沿用 RuoYi 的約定比如com.ruoyi.ai.config、com.ruoyi.ai.agent這樣和框架其他模塊風(fēng)格一致團(tuán)隊(duì)新人接手時(shí)不會(huì)有認(rèn)知負(fù)擔(dān)。配置類上用Configuration加ConditionalOnProperty讓 AI 能力可以通過(guò)配置開(kāi)關(guān)控制是否啟用——這點(diǎn)很重要因?yàn)椴皇敲總€(gè)部署環(huán)境都需要 AI 功能能關(guān)掉就意味著出問(wèn)題時(shí)可以快速降級(jí)。3.3 自動(dòng)配置類的編寫(xiě)要點(diǎn)自動(dòng)配置類是整個(gè)模塊的入口它要做三件事初始化 Harness 運(yùn)行時(shí)、注冊(cè)工具、暴露服務(wù) Bean。初始化運(yùn)行時(shí)的時(shí)候模型相關(guān)的配置比如接口地址、密鑰、超時(shí)時(shí)間應(yīng)該從 RuoYi 的配置體系里讀而不是硬編碼。Configuration ConditionalOnProperty(prefix ruoyi.ai, name enabled, havingValue true) public class AiAutoConfiguration { Bean ConditionalOnMissingBean public HarnessRuntime harnessRuntime(AiProperties properties) { HarnessConfig config HarnessConfig.builder() .endpoint(properties.getEndpoint()) .apiKey(properties.getApiKey()) .connectTimeout(properties.getConnectTimeout()) .readTimeout(properties.getReadTimeout()) .build(); return HarnessRuntime.create(config); } }這里有個(gè)細(xì)節(jié)ConditionalOnMissingBean要加上方便測(cè)試時(shí)用 Mock 替換真實(shí)運(yùn)行時(shí)。超時(shí)時(shí)間一定要顯式配置Harness 默認(rèn)的超時(shí)往往偏長(zhǎng)在 Web 請(qǐng)求線程里同步調(diào)用會(huì)拖垮整個(gè)接口響應(yīng)后面第 6 節(jié)會(huì)專門講這個(gè)。4. 把 RuoYi 的業(yè)務(wù)能力包裝成 Agent 可調(diào)用的工具4.1 工具注冊(cè)的核心機(jī)制Harness 調(diào)用業(yè)務(wù)能力的方式是工具調(diào)用——Agent 在推理過(guò)程中決定要調(diào)用哪個(gè)工具、傳什么參數(shù)Harness 負(fù)責(zé)把調(diào)用分發(fā)到對(duì)應(yīng)的實(shí)現(xiàn)上。所以集成的核心工作就是把 RuoYi 的 Service 方法包裝成工具。工具注冊(cè)一般有兩種模式注解掃描和手動(dòng)注冊(cè)。注解掃描寫(xiě)起來(lái)簡(jiǎn)潔在方法上加個(gè)AgentTool之類的注解啟動(dòng)時(shí)自動(dòng)收集手動(dòng)注冊(cè)則是在配置類里一個(gè)個(gè)register。我傾向于混合使用通用的、穩(wěn)定的工具用注解掃描需要?jiǎng)討B(tài)參數(shù)或者依賴運(yùn)行時(shí)上下文的用手動(dòng)注冊(cè)。原因是注解掃描在遇到重載方法、泛型參數(shù)時(shí)容易出歧義而手動(dòng)注冊(cè)雖然啰嗦但每個(gè)工具的入?yún)⒊鰠⒍记迩宄?.2 工具描述的質(zhì)量決定 Agent 的可用性這是最容易被低估的一點(diǎn)。很多人把工具注冊(cè)完就完事了結(jié)果 Agent 要么不調(diào)用工具要么亂調(diào)用。問(wèn)題幾乎都出在工具描述上。Harness 是靠描述來(lái)判斷這個(gè)工具是干什么的、什么時(shí)候該用的描述寫(xiě)得含糊Agent 的決策就一塌糊涂。舉個(gè)例子你有一個(gè)查詢訂單的工具描述寫(xiě)成查詢訂單信息Agent 根本不知道它和查詢物流有什么區(qū)別。正確的寫(xiě)法是把適用場(chǎng)景、參數(shù)含義、返回內(nèi)容都寫(xiě)清楚AgentTool( name queryOrderDetail, description 根據(jù)訂單編號(hào)查詢訂單的詳細(xì)信息包括下單時(shí)間、金額、狀態(tài)。 適用于用戶明確提供了訂單編號(hào)、需要了解訂單具體內(nèi)容的場(chǎng)景。 如果用戶只提供了手機(jī)號(hào)而沒(méi)有訂單號(hào)請(qǐng)先調(diào)用 queryOrderByPhone。 ) public OrderDetail queryOrderDetail(ToolParam(訂單編號(hào)純數(shù)字字符串) String orderNo) { return orderService.getByOrderNo(orderNo); }描述里明確寫(xiě)了什么時(shí)候用什么時(shí)候不用前置工具是什么Agent 的調(diào)用準(zhǔn)確率會(huì)有肉眼可見(jiàn)的提升。這是我在實(shí)際項(xiàng)目里反復(fù)驗(yàn)證過(guò)的經(jīng)驗(yàn)比調(diào)模型參數(shù)管用得多。4.3 參數(shù)校驗(yàn)與異常兜底工具被 Agent 調(diào)用時(shí)傳進(jìn)來(lái)的參數(shù)是模型生成的不能假設(shè)它一定合法。訂單號(hào)可能傳成中文日期可能格式不對(duì)枚舉值可能超出范圍。所以每個(gè)工具方法的第一件事應(yīng)該是參數(shù)校驗(yàn)校驗(yàn)失敗要返回一個(gè)結(jié)構(gòu)化的錯(cuò)誤信息而不是直接拋異常。為什么強(qiáng)調(diào)結(jié)構(gòu)化錯(cuò)誤因?yàn)?Agent 拿到錯(cuò)誤信息后可能會(huì)自我修正、重新調(diào)用。如果你直接拋IllegalArgumentExceptionHarness 可能把它當(dāng)成系統(tǒng)故障終止整個(gè)流程如果你返回{error: 訂單號(hào)格式不正確應(yīng)為純數(shù)字},Agent 就有機(jī)會(huì)換個(gè)參數(shù)再試一次。這個(gè)差別在復(fù)雜任務(wù)里非常關(guān)鍵。public ToolResult queryOrderDetail(String orderNo) { if (orderNo null || !orderNo.matches(\\d)) { return ToolResult.error(訂單號(hào)格式不正確應(yīng)為純數(shù)字字符串); } OrderDetail detail orderService.getByOrderNo(orderNo); if (detail null) { return ToolResult.error(未找到該訂單請(qǐng)確認(rèn)訂單號(hào)是否正確); } return ToolResult.success(detail); }4.4 工具與數(shù)據(jù)權(quán)限的綁定RuoYi-Vue-Plus 有完善的數(shù)據(jù)權(quán)限體系工具調(diào)用不能繞過(guò)它。也就是說(shuō)Agent 通過(guò)工具查數(shù)據(jù)時(shí)必須帶上當(dāng)前登錄用戶的身份走和普通接口一樣的數(shù)據(jù)權(quán)限過(guò)濾。實(shí)現(xiàn)方式是在工具方法里通過(guò)SecurityUtils拿到當(dāng)前用戶把用戶上下文傳給 Service 層。這里有個(gè)坑如果 AI 調(diào)用是異步執(zhí)行的比如放到線程池里跑SecurityUtils依賴的ThreadLocal在新線程里是空的會(huì)直接報(bào)獲取用戶失敗。解決辦法是在提交異步任務(wù)前把用戶上下文取出來(lái)作為參數(shù)傳進(jìn)去在異步線程里手動(dòng)設(shè)置。這個(gè)細(xì)節(jié)不處理異步 AI 功能一上線就會(huì)大面積報(bào)錯(cuò)。5. 會(huì)話管理讓 Agent 記住誰(shuí)在問(wèn)、問(wèn)到哪了5.1 會(huì)話與系統(tǒng)用戶的映射關(guān)系Harness 的會(huì)話Session是它內(nèi)部維護(hù)的對(duì)話上下文而 RuoYi 的用戶是系統(tǒng)級(jí)的身份。兩者需要建立映射一個(gè)系統(tǒng)用戶可能有多個(gè)會(huì)話比如開(kāi)了多個(gè)對(duì)話窗口一個(gè)會(huì)話必須歸屬于某個(gè)用戶和某個(gè)租戶。我的做法是在數(shù)據(jù)庫(kù)里建一張ai_session表字段包括會(huì)話 ID、用戶 ID、租戶 ID、創(chuàng)建時(shí)間、最后活躍時(shí)間、會(huì)話狀態(tài)。Harness 側(cè)生成會(huì)話 ID 后往這張表里插一條記錄。這樣做的價(jià)值在于會(huì)話可以持久化、可以審計(jì)、可以按用戶清理。純內(nèi)存的會(huì)話在服務(wù)重啟后就丟了用戶體驗(yàn)很差。5.2 多輪對(duì)話上下文的存儲(chǔ)策略多輪對(duì)話的上下文如果全放內(nèi)存服務(wù)一重啟就斷片如果每輪都全量存數(shù)據(jù)庫(kù)數(shù)據(jù)量會(huì)爆炸。折中方案是內(nèi)存 持久化雙層活躍會(huì)話的上下文放內(nèi)存用帶過(guò)期時(shí)間的緩存同時(shí)定期把關(guān)鍵節(jié)點(diǎn)快照到數(shù)據(jù)庫(kù)。用戶下次進(jìn)來(lái)時(shí)先從內(nèi)存找找不到再?gòu)臄?shù)據(jù)庫(kù)恢復(fù)最近 N 輪。N 取多少合適我的經(jīng)驗(yàn)是保留最近 10 到 20 輪足夠覆蓋絕大多數(shù)場(chǎng)景。再往前的上下文對(duì)當(dāng)前對(duì)話的貢獻(xiàn)很小反而會(huì)占用大量 token、拖慢響應(yīng)、增加成本。超過(guò)這個(gè)輪數(shù)的歷史可以只保留摘要而不是原文。5.3 會(huì)話并發(fā)與線程安全同一個(gè)會(huì)話如果被并發(fā)訪問(wèn)比如用戶快速點(diǎn)了兩次發(fā)送Harness 的內(nèi)部狀態(tài)可能被破壞。解決辦法是對(duì)會(huì)話加鎖同一會(huì)話的請(qǐng)求串行執(zhí)行??梢杂肅oncurrentHashMap存會(huì)話鎖對(duì)象或者用 Redis 的分布式鎖多實(shí)例部署時(shí)必須用后者。public String chat(String sessionId, String userInput) { Lock lock sessionLocks.computeIfAbsent(sessionId, k - new ReentrantLock()); lock.lock(); try { return harnessRuntime.chat(sessionId, userInput); } finally { lock.unlock(); } }單機(jī)用ReentrantLock夠了多實(shí)例部署就得換成 Redis 鎖否則兩個(gè)實(shí)例同時(shí)操作一個(gè)會(huì)話照樣出問(wèn)題。這個(gè)點(diǎn)在做集群部署時(shí)特別容易漏。6. 實(shí)測(cè)踩坑那些文檔里不會(huì)寫(xiě)的集成問(wèn)題6.1 同步調(diào)用拖垮 Web 線程池最開(kāi)始我把 AI 調(diào)用直接放在 Controller 里同步執(zhí)行測(cè)試環(huán)境沒(méi)問(wèn)題一上生產(chǎn)就發(fā)現(xiàn)接口響應(yīng)時(shí)間從幾百毫秒飆到十幾秒并發(fā)一高整個(gè)線程池被打滿連不相關(guān)的接口都開(kāi)始超時(shí)。原因很簡(jiǎn)單AI 調(diào)用是秒級(jí)甚至十秒級(jí)的操作而 Tomcat 的請(qǐng)求線程是寶貴資源被這種長(zhǎng)耗時(shí)操作占住其他請(qǐng)求就排隊(duì)了。解決方案是異步化。接口收到請(qǐng)求后立即返回一個(gè)任務(wù) IDAI 調(diào)用丟到獨(dú)立線程池里執(zhí)行前端通過(guò)輪詢或者長(zhǎng)連接拿結(jié)果。線程池要單獨(dú)配置核心線程數(shù)、隊(duì)列長(zhǎng)度、拒絕策略都要根據(jù)實(shí)際并發(fā)量調(diào)別用默認(rèn)的。Bean(aiExecutor) public ThreadPoolTaskExecutor aiExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(16); executor.setQueueCapacity(100); executor.setThreadNamePrefix(ai-task-); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }6.2 工具調(diào)用超時(shí)導(dǎo)致的半截任務(wù)Agent 執(zhí)行一個(gè)任務(wù)可能連續(xù)調(diào)用多個(gè)工具如果某個(gè)工具卡住比如查了個(gè)慢 SQL整個(gè)任務(wù)就掛在那里。Harness 一般有整體超時(shí)但工具級(jí)別的超時(shí)往往需要自己控制。我的做法是給每個(gè)工具方法加超時(shí)保護(hù)用Future.get(timeout)或者信號(hào)量控制超時(shí)后返回一個(gè)明確的錯(cuò)誤讓 Agent 決定是重試還是放棄。這里有個(gè)反直覺(jué)的點(diǎn)工具超時(shí)后不要直接拋異常終止而是返回該操作超時(shí)請(qǐng)稍后重試或換一種方式。因?yàn)?Agent 可能會(huì)根據(jù)這個(gè)信息調(diào)整策略比如換個(gè)查詢條件、或者告訴用戶稍后再試。直接拋異常會(huì)讓整個(gè)對(duì)話中斷體驗(yàn)很差。6.3 模型返回格式不穩(wěn)定引發(fā)的解析失敗模型輸出是自然語(yǔ)言即使你要求它返回 JSON它也可能給你包一層 markdown 代碼塊或者在 JSON 前后加一句好的結(jié)果如下。如果代碼里直接JSON.parse就會(huì)頻繁解析失敗。處理方式是容錯(cuò)解析先嘗試直接解析失敗后剝離可能的代碼塊標(biāo)記再解析再失敗就用正則提取第一個(gè)完整的 JSON 對(duì)象。同時(shí)在提示詞里明確要求只返回 JSON不要任何額外文字能減少大部分問(wèn)題。但容錯(cuò)邏輯必須留著因?yàn)槟P偷男袨椴皇?100% 可控的。6.4 日志與可觀測(cè)性缺失AI 調(diào)用出問(wèn)題時(shí)如果沒(méi)有詳細(xì)的日志你根本不知道是模型沒(méi)返回、工具沒(méi)調(diào)用、還是參數(shù)傳錯(cuò)了。所以從第一天起就要把日志打全每次模型請(qǐng)求的輸入輸出、每次工具調(diào)用的入?yún)⒊鰠?、每輪?duì)話的耗時(shí)。這些日志在排查問(wèn)題時(shí)是救命的。但要注意脫敏。用戶輸入里可能有手機(jī)號(hào)、身份證號(hào)工具返回里可能有敏感業(yè)務(wù)數(shù)據(jù)日志里不能明文記錄。RuoYi 有現(xiàn)成的脫敏工具直接復(fù)用。排查場(chǎng)景關(guān)鍵日志常見(jiàn)原因Agent 不調(diào)用工具模型原始輸出工具描述不清、提示詞未引導(dǎo)工具調(diào)用報(bào)錯(cuò)工具入?yún)? 異常棧參數(shù)格式不符、權(quán)限上下文丟失響應(yīng)特別慢各階段耗時(shí)打點(diǎn)模型響應(yīng)慢、工具慢 SQL、線程池排隊(duì)會(huì)話串話會(huì)話 ID 用戶 ID會(huì)話映射錯(cuò)誤、并發(fā)未加鎖7. 上線前的配置調(diào)優(yōu)與降級(jí)預(yù)案7.1 超時(shí)與重試參數(shù)的取值邏輯超時(shí)和重試這兩個(gè)參數(shù)取值不能拍腦袋。連接超時(shí)一般設(shè) 3 到 5 秒因?yàn)榫W(wǎng)絡(luò)握手正常都在毫秒級(jí)超過(guò)這個(gè)數(shù)基本是網(wǎng)絡(luò)問(wèn)題重試也沒(méi)用。讀取超時(shí)要看模型響應(yīng)速度一般設(shè) 30 到 60 秒復(fù)雜推理任務(wù)可能更久。重試次數(shù)建議最多 1 次因?yàn)?AI 調(diào)用不是冪等友好的場(chǎng)景重試可能產(chǎn)生重復(fù)的工具調(diào)用副作用。重試還要區(qū)分錯(cuò)誤類型網(wǎng)絡(luò)超時(shí)可以重試參數(shù)錯(cuò)誤重試沒(méi)意義限流錯(cuò)誤要退避后再試。一刀切的重試策略只會(huì)放大問(wèn)題。7.2 降級(jí)開(kāi)關(guān)的設(shè)計(jì)AI 功能必須能一鍵關(guān)掉。設(shè)計(jì)上分兩級(jí)全局開(kāi)關(guān)配置中心控制關(guān)掉后所有 AI 接口返回功能維護(hù)中和單功能開(kāi)關(guān)某個(gè)具體 Agent 出問(wèn)題只關(guān)它。全局開(kāi)關(guān)用于模型服務(wù)整體不可用時(shí)的緊急降級(jí)單功能開(kāi)關(guān)用于局部問(wèn)題隔離。開(kāi)關(guān)狀態(tài)要能動(dòng)態(tài)刷新不能改配置還得重啟。RuoYi 集成了配置刷新能力的話直接用沒(méi)有的話用定時(shí)任務(wù)輪詢配置中心也行。7.3 成本控制的實(shí)際手段AI 調(diào)用是花錢的尤其是 token 消耗。控制成本有幾個(gè)立竿見(jiàn)影的手段限制單次對(duì)話的最大輪數(shù)防止用戶無(wú)限聊下去、限制單次請(qǐng)求的最大 token防止超長(zhǎng)輸入、對(duì)高頻用戶做限流防止刷接口、緩存常見(jiàn)問(wèn)題的回答相同問(wèn)題直接返回緩存。其中緩存最容易被忽略。很多后臺(tái)系統(tǒng)的 AI 問(wèn)答其實(shí)是重復(fù)的比如這個(gè)功能怎么用緩存命中率能到 30% 以上省下來(lái)的成本很可觀。緩存 key 用問(wèn)題文本的哈希注意設(shè)置合理的過(guò)期時(shí)間。8. 我個(gè)人在集成過(guò)程中的幾點(diǎn)體會(huì)整套東西跑通之后回頭看最耗時(shí)間的不是寫(xiě)代碼而是調(diào)工具描述和排查上下文丟失這兩件事。工具描述我前后改了七八版每改一版 Agent 的調(diào)用準(zhǔn)確率就上一個(gè)臺(tái)階這個(gè)過(guò)程沒(méi)有捷徑只能拿真實(shí)場(chǎng)景反復(fù)測(cè)。上下文丟失則集中在異步和權(quán)限兩個(gè)環(huán)節(jié)凡是跨線程的地方都要檢查一遍用戶上下文有沒(méi)有正確傳遞。另外一個(gè)體會(huì)是別指望 Agent 一次就把復(fù)雜任務(wù)做對(duì)。真實(shí)場(chǎng)景里一個(gè)需要調(diào)用三四個(gè)工具的任務(wù)第一次成功率可能只有六七成。所以產(chǎn)品設(shè)計(jì)上要允許重試和人工兜底把 AI 定位成輔助而不是全自動(dòng)這樣即使它偶爾出錯(cuò)業(yè)務(wù)也不會(huì)崩。等準(zhǔn)確率通過(guò)持續(xù)調(diào)優(yōu)穩(wěn)定到九成以上再考慮擴(kuò)大它的自主權(quán)。最后純 Java 集成這條路是走得通的而且對(duì) RuoYi 系團(tuán)隊(duì)來(lái)說(shuō)是最平滑的選擇。它不需要你引入新語(yǔ)言、新部署單元復(fù)用現(xiàn)有的權(quán)限、日志、監(jiān)控體系出問(wèn)題也在熟悉的排查路徑里。代價(jià)是你要接受 Java 生態(tài)在 AI 編排這塊相對(duì)年輕一些高級(jí)特性可能不如其他語(yǔ)言棧豐富但對(duì)絕大多數(shù)后臺(tái)系統(tǒng)的智能化需求來(lái)說(shuō)夠用了。