建大模型觸達企業(yè)系統(tǒng)的三層架構(gòu))
Agent-Reach 這個項目名我是從一次內(nèi)部工具對接的討論里帶出來的。當(dāng)時我們團隊接了一個 OA 系統(tǒng)的自動化改造老板的要求特別簡單讓業(yè)務(wù)人員在聊天窗口里直接查訂單、催流程、拉報表。聽起來就是給大模型接幾個 API 嘛但真做起來才發(fā)現(xiàn)模型能聊天和能干活完全是兩碼事。Agent-Reach 這個名字的立意很簡單——它解決的不是模型能不能推理的問題而是模型能不能觸達真實系統(tǒng)并安全地把事辦成的問題。如果你也在做 AI Agent 相關(guān)的應(yīng)用尤其是要把大模型接進企業(yè)內(nèi)部系統(tǒng)這篇文章里的設(shè)計思路、實現(xiàn)細節(jié)和踩坑記錄應(yīng)該能幫你少走很多彎路。1. 先聊清楚一個事Agent-Reach 到底卡在哪個環(huán)節(jié)1.1 一個讓我意識到問題所在的真實場景事情的開端特別樸素。我們一開始給 OA 系統(tǒng)接大模型方式是常見的 RAG 問答模型只負責(zé)讀文檔、回答報銷流程是什么這類問題。上線一周效果還行然后業(yè)務(wù)方就提了新需求能不能在對話框里直接說幫我查一下上周提交的退款單到哪一步了然后系統(tǒng)直接給結(jié)果這一下需求性質(zhì)就變了。查退款單不是一個問答動作它涉及三個環(huán)節(jié)第一模型得理解用戶意圖把查退款單映射到某個具體接口第二系統(tǒng)得拿到用戶的身份和權(quán)限確認這個人有權(quán)利查這些數(shù)據(jù)第三調(diào)用接口拿到結(jié)果之后還要把結(jié)構(gòu)化的數(shù)據(jù)組織成自然語言回復(fù)給用戶。整個鏈路里模型只是大腦真正干活的是觸達動作。我們當(dāng)時最缺的就是一套能統(tǒng)一管理這些觸達動作的基礎(chǔ)設(shè)施。1.2 對話能力與任務(wù)執(zhí)行能力之間的差距很多剛接觸 Agent 開發(fā)的人會有一個誤區(qū)只要選一個聰明的模型它自己就會調(diào)用工具。實際上模型的本領(lǐng)邊界非常清晰——它擅長的是文本生成和推理但調(diào)用工具這件事模型本身并不天然具備。你得先告訴它有哪些工具、每個工具接收什么參數(shù)、什么時候該調(diào)用哪個工具然后它才能在你的引導(dǎo)下做出選擇。這些告訴的過程就是 Agent-Reach 這類觸達層框架要解決的事。再往深一層說對話型應(yīng)用和任務(wù)型 Agent 的核心差異在于有狀態(tài)和無狀態(tài)。純問答是無狀態(tài)的用戶問一句模型答一句上下文丟了也無所謂。但任務(wù)型 Agent 是有狀態(tài)的用戶說幫我查退款單系統(tǒng)先查詢列表用戶接著問那第一單為什么被駁回這個時候 Agent 得記得第一單指的是列表里的哪一項。狀態(tài)管理、工具調(diào)度的上下文銜接、結(jié)果回填這些都不是模型本身能搞定的而是需要一層專門的中樞來編排。Agent-Reach 最開始的項目定位就是這個中樞。所以我很建議所有準(zhǔn)備做 Agent 應(yīng)用的朋友先別急著選模型、調(diào) prompt。先把觸達層想清楚你的 Agent 要接觸哪些系統(tǒng)這些系統(tǒng)之間的調(diào)用關(guān)系是怎樣的誰來負責(zé)權(quán)限校驗如果是靠寫死代碼把幾十個接口糊在一起短期能跑長期一定崩。下面我把 Agent-Reach 的三層設(shè)計拆開講這是我落地之后覺得最值得參考的部分。2. Agent-Reach 的主體設(shè)計觸達、發(fā)現(xiàn)、執(zhí)行三層分離2.1 觸達層把不同模型的 function calling 差異抹平第一次做 Agent 的時候我天真地以為只要選一個廠家的大模型后面就只用對著它開發(fā)即可。實際上業(yè)務(wù)場景里經(jīng)常要替換模型或者同時用多個模型做不同任務(wù)。不同模型的 function calling 格式差異很大有的要求嚴(yán)格的 JSON Schema有的支持自然語言描述工具用途有的在并行調(diào)用多個工具時行為完全不同。如果把這些差異散落在業(yè)務(wù)代碼里每換一次模型就要改一遍全鏈路代價很高。觸達層的職責(zé)就是把這種差異收斂掉。我們做了一套統(tǒng)一的工具描述格式把每個工具定義成 id、名稱、描述、參數(shù) Schema、返回結(jié)構(gòu)五個字段。模型側(cè)只需要關(guān)心這五個字段底層再針對不同模型廠家的 API 做適配轉(zhuǎn)換。打個比方這就像電腦的 USB 接口不同設(shè)備有自己的協(xié)議和電壓需求但 USB 接口把所有差異都隱藏了插上就能用。觸達層就是這個 USB 接口讓業(yè)務(wù)系統(tǒng)不用關(guān)心模型是哪家的。2.2 發(fā)現(xiàn)層工具注冊表的設(shè)計邏輯發(fā)現(xiàn)層解決的是模型怎么知道有哪些工具可以用的問題。剛開始我就踩過一個坑把所有工具的描述全部塞到系統(tǒng)提示詞里結(jié)果上下文被撐爆模型的選擇準(zhǔn)確率反而下降。后來我把工具信息抽出來做成了注冊表按需注入效果立刻不一樣。這個注冊表的核心字段如下工具 ID 全局唯一用于調(diào)用記錄追蹤。名稱要短便于模型理解例如 get_refund_order 就比 queryRefundOrderDetailByIdV2 好理解得多。描述要包含觸發(fā)條件、適用場景和典型用戶指令的語義表達這直接影響模型選工具的準(zhǔn)確率。參數(shù) Schema 用標(biāo)準(zhǔn) JSON Schema 定義模型按此生成參數(shù)。返回結(jié)構(gòu)要說明返回的字段和格式方便模型組織語言。權(quán)限元數(shù)據(jù)是可選的默認空閑。注冊表真正起作用的機制是語義檢索。用戶輸入幫我查退款單時系統(tǒng)先把這句話向量化從注冊表里召回最相關(guān)的 5 到 8 個工具再把這幾個工具的 Schema 注入到模型請求中。這比把所有工具都塞給模型有兩個好處省 token同時減少干擾項。實際落地后工具選擇的準(zhǔn)確率從 82% 提升到了 95% 左右。2.3 執(zhí)行層會話編排與任務(wù)分發(fā)執(zhí)行層是整個 Agent-Reach 里最容易被低估的部分。模型選好了工具、生成好了參數(shù)接下來要做的事包括調(diào)用外部接口、處理超時和重試、把結(jié)果回傳給模型、維護會話狀態(tài)。這些事看起來瑣碎但每一個細節(jié)都可能讓整個 Agent 崩掉。我設(shè)計的執(zhí)行層核心是一個會話狀態(tài)機狀態(tài)流轉(zhuǎn)如下會話啟動后進入意圖解析階段Agent 判斷用戶想干什么如果無法判斷則主動澄清。隨后進入工具選擇階段執(zhí)行層根據(jù)注冊表召回候選工具。然后是參數(shù)填充階段模型根據(jù)用戶輸入和工具 Schema 生成參數(shù)如果是語料缺失的場景則需要追問補全參數(shù)。接著進入執(zhí)行階段調(diào)用工具觸達外部系統(tǒng)。最后是結(jié)果組織階段模型把結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)成用戶能看懂的自然語言。這個狀態(tài)機的關(guān)鍵設(shè)計在于每個階段都允許用戶干預(yù)。比如參數(shù)填充時用戶說算了不查了狀態(tài)機應(yīng)該能優(yōu)雅退出而不是繼續(xù)強行調(diào)用工具。多輪對話里用戶可能中途修改條件例如先讓 Agent 查退款單又說還是查昨天的吧狀態(tài)機要把修改后的參數(shù)重新綁定到同一個工具調(diào)用上而不是新開一個任務(wù)。這種編排邏輯寫起來不復(fù)雜但要想清楚邊界避免狀態(tài)錯亂。3. 把 Agent-Reach 跑起來的關(guān)鍵實現(xiàn)細節(jié)3.1 工具注冊表從普通函數(shù)到可發(fā)現(xiàn)資產(chǎn)的改造如果你已有現(xiàn)成的內(nèi)部 API把它接入 Agent-Reach 并不是寫一個 Python 函數(shù)那么簡單。API 的參數(shù)可能來自請求頭、查詢參數(shù)、請求體返回結(jié)果可能是嵌套 JSON還可能涉及分頁。要讓模型能順利調(diào)用每個 API 都要被重新包裝成模型友好的形式。我建議的包裝方式是寫一個適配器層agent_reach_tool( nameget_refund_order, description根據(jù)退款單號查詢退款進度。當(dāng)用戶詢問退款、退單狀態(tài)時使用。, params_schema{ type: object, properties: { refund_id: {type: string, description: 退款單號如 RF20250101}, include_detail: {type: boolean, description: 是否返回明細默認 false} }, required: [refund_id] }, return_schema{ type: object, properties: { status: {type: string}, current_node: {type: string}, history: {type: array} } } ) def get_refund_order(refund_id: str, include_detail: bool False): # 這里調(diào)用真實的內(nèi)部 API resp http_client.get(f/api/refund/{refund_id}) return normalize(resp.json())裝飾器做的事是把函數(shù)注冊進工具表同時把參數(shù) Schema 轉(zhuǎn)成模型的 prompt 片段。這里最值得注意的細節(jié)是描述字段的寫法對模型選工具的影響非常大。我試過查詢退款單狀態(tài)和根據(jù)退款單號查詢退款進度適用于用戶咨詢退款到哪一步、是否通過、被駁回原因等場景可附帶明細開關(guān)這兩種寫法后者的工具選擇準(zhǔn)確率明顯更高。模型是靠語義匹配選擇工具的描述越接近用戶的真實表達方式選得越準(zhǔn)。3.2 會話狀態(tài)管理記憶、壓縮、過期多輪對話的 Agent 很容易出現(xiàn)上下文失控的問題。一開始我把所有歷史消息都塞給模型幾個來回之后 token 就上去了而且模型會被舊信息干擾。后來我把 Agent-Reach 的會話管理分成了三個層級。工作記憶層保存最近 1 到 2 輪完整的對話原文直接注入模型保證銜接準(zhǔn)確。摘要記憶層對更早的歷史做摘要用一個專門的輕量模型遞歸壓縮把用戶要求查退款單 → 查詢結(jié)果正?!脩糇穯柕谝粏务g回原因這樣的過程提煉為一句話。關(guān)鍵事件層則記錄所有工具調(diào)用記錄包括輸入、輸出、耗時、狀態(tài)這些信息不直接給模型而是用于審計和問題排查。記憶過期策略也要講究。用戶的會話如果 10 分鐘無操作我建議直接把工作記憶清空只保留摘要記憶避免用戶回來時上下文錯亂。長期無操作的會話直接歸檔用 session_id 恢復(fù)時會提示您可以重新描述需求。這套策略執(zhí)行下來上下文 token 占用減少了約 40%同時用戶的追問體驗并沒有明顯下降。3.3 并發(fā)、超時與熔斷觸達外部系統(tǒng)不能裸奔工具調(diào)用是高風(fēng)險的。外部接口可能很慢可能直接報錯也可能返回的數(shù)據(jù)格式和預(yù)期不一致。如果 Agent 不加保護地反復(fù)調(diào)用失敗接口不僅浪費資源還會讓用戶對系統(tǒng)的信任度下降。我在執(zhí)行層里加了三道防線。第一道是超時控制。每個工具調(diào)用必須顯式聲明超時時間默認 5 秒。有些報表生成類接口可能需要 30 秒那就單獨調(diào)高但要標(biāo)記為長任務(wù)走異步流程避免阻塞整個會話。第二道是重試策略。網(wǎng)絡(luò)抖動導(dǎo)致的瞬時失敗可以重試但重試上限是 2 次間隔采用指數(shù)退避1 秒、2 秒。更重要的是要區(qū)分可重試錯誤和不可重試錯誤。超時、503、連接斷開屬于可重試權(quán)限不足、參數(shù)非法、404 這類錯誤重試多少次都沒用直接終止并把錯誤信息返回給模型讓模型向用戶解釋。第三道是熔斷器。如果某個工具在 30 秒內(nèi)失敗率超過 50%直接熔斷 2 分鐘。熔斷期間該工具不再被調(diào)度模型會收到該功能暫時不可用的提示。這個設(shè)計救了我很多次因為下游系統(tǒng)的故障往往不是單次請求的問題而是持續(xù)性的不熔斷就會產(chǎn)生雪球效應(yīng)。3.4 權(quán)限與審計最小權(quán)限觸達不是選項而是底線Agent 觸達真實系統(tǒng)權(quán)限問題躲不開。一個能讓模型自由調(diào)用內(nèi)部接口的框架本身就是極大的安全風(fēng)險。在 Agent-Reach 里我把權(quán)限控制放到了執(zhí)行層的最前面而不是讓模型去判斷。每個工具有一個最小角色權(quán)限聲明調(diào)用前執(zhí)行層會取出當(dāng)前用戶身份驗證是否具備權(quán)限。權(quán)限不足就直接拒絕不給模型任何嘗試的機會。這里有個容易踩坑的地方不要在系統(tǒng)提示詞里讓模型判斷權(quán)限模型經(jīng)常會給出模棱兩可的結(jié)果而權(quán)限是二元的必須由確定性的代碼來判斷。同時每次工具調(diào)用都要寫審計日志記錄誰在什么時間通過哪個會話調(diào)用了什么工具傳入什么參數(shù)。一旦出現(xiàn)數(shù)據(jù)泄露或越權(quán)審計日志就是追查的第一手資料。4. 一個完整的落地案例客服退款查詢 Agent4.1 從需求到觸達鏈路的拆解理論講完拿我們實際做的客服退款查詢 Agent 當(dāng)例子串一遍完整鏈路。需求來自客服部門用戶來電咨詢退款時客服希望直接在聊天輔助工具里輸入用戶提供的退款單號系統(tǒng)自動展示退款狀態(tài)、當(dāng)前處理節(jié)點、歷史流轉(zhuǎn)記錄。這個需求可以直接套進 Agent-Reach 的框架里。工具層面需要三個后端接口查詢退款單基本信息、查詢退款審批流轉(zhuǎn)記錄、查詢駁回原因詳情。會話流程設(shè)計成客服輸入退款單號 → Agent 解析意圖并凍結(jié)單號 → 調(diào)用基本信息接口 → 如果狀態(tài)是已駁回再調(diào)用駁回原因接口 → 匯總成一段結(jié)構(gòu)化的客服話術(shù)。這個案例里有一個關(guān)鍵決策是讓模型自由決定調(diào)用哪幾個接口還是用預(yù)設(shè)流程我采用了折中方案。當(dāng)退款單狀態(tài)是正常退款中時只調(diào)用基本信息和流轉(zhuǎn)記錄兩個接口當(dāng)狀態(tài)是已駁回時自動追加調(diào)用駁回原因接口。這個狀態(tài)驅(qū)動分支不是讓模型現(xiàn)場推理出來的而是由執(zhí)行層的規(guī)則引擎判斷的。原因很簡單——退款查詢這種操作路徑固化、出錯代價高的場景流程確定性比靈活性更重要。4.2 一條真實觸達鏈路的完整日志放一段脫敏后的鏈路日志你可以直觀感受 Agent-Reach 在背后做了什么[會話 8f3a] 用戶輸入: 客戶說 RF20250301 退款還沒到賬幫他查一下 [編排] 意圖解析 - 查退款進度 [發(fā)現(xiàn)] 召回工具: get_refund_order, get_refund_flow, get_refund_reject_reason [調(diào)度] 注入工具 Schema 到模型 [模型] 選擇工具: get_refund_order, 參數(shù): {refund_id: RF20250301} [執(zhí)行] 權(quán)限校驗: 客服組通過 [觸達] GET /api/refund/RF20250301 200 OK耗時 312ms [執(zhí)行] 狀態(tài)判斷: statusREJECTED觸發(fā)分支 - 追加調(diào)用 get_refund_reject_reason [觸達] GET /api/refund_reject/RF20250301 200 OK耗時 156ms [模型] 組織話術(shù): 該筆退款已于昨天被駁回原因是發(fā)票信息不一致請聯(lián)系用戶重新上傳發(fā)票。 [會話] 完整回復(fù)已返回用戶整個過程大概 1.5 秒。如果不用 Agent-Reach這段邏輯要么寫成硬編碼的 if-else要么讓模型自由調(diào)用前者沒法覆蓋新場景后者在權(quán)限控制上漏洞百出。這個案例說明Agent-Reach 這類觸達層最有價值的地方不是智能而是把智能和真實系統(tǒng)之間的縫隙填平。5. 我在 Agent-Reach 上踩過的坑和最終解法5.1 function calling 參數(shù)枚舉的坑第一次讓模型調(diào)用工具時我遇到一個讓人非常頭疼的問題模型的參數(shù)幻覺。工具定義里寫著 status 只有 PENDING、PROCESSING、REJECTED、SUCCESS 四種取值模型卻在調(diào)用時傳了一個 in progress。原因很簡單模型是根據(jù)語義生成參數(shù)的它認為 in progress 表達的就是處理中但它不知道后端接口只認枚舉值。解決思路是在兩層做防護。第一層參數(shù) Schema 里把所有枚舉值明確定義并在描述里強調(diào)只允許傳以下值第二層執(zhí)行層在調(diào)用工具前用 JSON Schema 做嚴(yán)格校驗校驗不通過就直接返回參數(shù)不合法給模型讓模型自我修正重新生成。這里要特別提醒不要指望模型一次生成完全合法一定要做校驗和重試機制否則任何一個字段的小偏差都會讓整個鏈路中斷。5.2 工具返回結(jié)果過大撐爆上下文另一個高頻問題是外部系統(tǒng)返回的數(shù)據(jù)太大了。比如退款單的完整流轉(zhuǎn)記錄有 80 條全塞給模型既浪費 token又干擾模型組織話術(shù)。剛開始我們沒做處理一個查詢?nèi)蝿?wù)能吃掉 8000 多 token成本高不說回復(fù)質(zhì)量還差。最后用了兩招解決。第一招是返回結(jié)構(gòu)裁剪工具適配器默認只返回最近 5 條流轉(zhuǎn)記錄完整數(shù)據(jù)放在 details 字段里標(biāo)注僅在用戶明確要求時展示。第二招是結(jié)果摘要化對于一些本身就很大的報表數(shù)據(jù)工具先做一次摘要再返回給模型例如該訂單共 40 條操作記錄分為 3 個階段當(dāng)前處于第二階段。這兩招把平均 token 消耗壓到了原來的三分之一而且用戶感知幾乎沒有下降。5.3 外部接口不穩(wěn)定導(dǎo)致 Agent 反復(fù)重試有一次灰度測試下游審批系統(tǒng)出現(xiàn)間歇性超時結(jié)果我們的 Agent 像瘋了一樣對同一個接口連續(xù)重試了 8 次每次重試間隔僅 1 秒直接把下游系統(tǒng)的負載又抬高了一截。問題出在我最初的重試邏輯太簡單只要超時就重試完全不考慮失敗模式。后來我下了三個硬性規(guī)定重試上限固定為 2 次一旦超過就立刻把問題拋給用戶說明系統(tǒng)繁忙請稍后再試只對冪等接口開重試查詢類接口一般冪等但提交審批這種操作類接口絕不自動重試否則會出現(xiàn)重復(fù)提交的嚴(yán)重事故任何工具連續(xù)失敗 3 次就自動熔斷熔斷期間不再調(diào)用該工具并在回復(fù)里誠實告知當(dāng)前功能暫不可用。這幾條看著簡單但能攔住大多數(shù)外部系統(tǒng)故障引起的連環(huán)問題。5.4 多個 Agent 同時觸達同一個資源時的競爭問題Agent-Reach 后期支持了多個業(yè)務(wù) Agent 共用一個工具注冊表新的問題又出現(xiàn)了兩個 Agent 幾乎同時調(diào)用同一業(yè)務(wù)接口修改同一份訂單數(shù)據(jù)造成數(shù)據(jù)沖突。比如銷售 Agent 更新了客戶聯(lián)系方式客服 Agent 緊接著又用舊信息覆蓋了一遍。解決方式是在執(zhí)行層加了資源鎖。同一資源 ID 在同一時間只允許一個寫操作后續(xù)的寫操作會排隊等待同時對寫類工具強制要求帶上操作冪等鍵同一個冪等鍵的重復(fù)請求直接返回之前的結(jié)果。這個設(shè)計對用戶體驗的影響微乎其微但對數(shù)據(jù)安全的意義極大。6. 從 Agent-Reach 繼續(xù)往前走的一點想法6.1 把觸達過程變成可觀測的數(shù)據(jù)資產(chǎn)做到后期我發(fā)現(xiàn) Agent-Reach 更大的價值不在調(diào)通而在可觀測。每個工具調(diào)用都有完整的鏈路日志從意圖識別、工具選擇、參數(shù)填充、權(quán)限校驗到最終執(zhí)行結(jié)果。這些日志不僅用于排查問題還可以反哺模型效果。舉個例子我們通過日志發(fā)現(xiàn)用戶提問退款到哪里了時模型經(jīng)常選錯工具選成了查詢退款審批流程。原因是兩個工具的描述語義邊界有重疊。后來我們根據(jù)日志反饋把描述改成了更明確的分工表述get_refund_order 負責(zé)查狀態(tài)和到賬信息get_refund_flow 負責(zé)查審批流轉(zhuǎn)節(jié)點。改完之后選對率明顯上升。這個優(yōu)化過程完全依賴可觀測數(shù)據(jù)沒有它就只能靠猜。6.2 從單點觸達到網(wǎng)狀協(xié)作的擴展方向最后聊一下擴展。目前的 Agent-Reach 是一個 Agent 觸達多個工具的單點模式再往下走就是多 Agent 協(xié)作一個主 Agent 拆解任務(wù)子 Agent 各自觸達不同系統(tǒng)最后匯總結(jié)果。這種模式對執(zhí)行層的要求又高了一個檔次子 Agent 之間的通信、任務(wù)結(jié)果的中轉(zhuǎn)、優(yōu)先級調(diào)度都會成為新的瓶頸。我的建議是無論怎么擴展盡量不要把 Agent 之間的協(xié)作做成完全自由的網(wǎng)狀結(jié)構(gòu)而是借鑒工作流引擎的思路定義好節(jié)點和邊。完全自由的多 Agent 協(xié)作看起來很美但排錯會讓人崩潰。Agent-Reach 目前保持的設(shè)計原則是流程確定性優(yōu)先智能靈活性補充。這也是我在多次踩坑之后沉淀下來的核心體會。最后分享一個實際經(jīng)驗做 Agent 項目第一版能用就行但觸達層的工具注冊、權(quán)限校驗、審計日志這些骨架一定要在一開始就搭好。這些東西不依賴具體模型也不依賴具體業(yè)務(wù)后面的所有迭代都是在這個骨架上長肉。如果你現(xiàn)在正準(zhǔn)備做 Agent 應(yīng)用我真心建議先把觸達層想明白再談模型調(diào)優(yōu)和 Prompt 工程。模型總有更聰明的版本但觸達層的基礎(chǔ)設(shè)計決定了你換多少個模型都不會推翻重來。