定觸達真實系統(tǒng)的中間層設(shè)計)
這兩年做大模型應(yīng)用的人應(yīng)該都有同一種感受模型本身的能力已經(jīng)不太是瓶頸了真正卡住項目的是智能體到底能不能穩(wěn)定地“夠到”它該夠的東西——內(nèi)部系統(tǒng)、外部API、數(shù)據(jù)庫、審批流、甚至另一個AI。這個夠不到的問題就是觸達問題。我剛接手一個客服智能體項目的時候被折磨了整整三周。智能體本身答得挺好但一讓它去查訂單狀態(tài)、發(fā)退款申請、調(diào)CRM數(shù)據(jù)就開始各種“假動作”要么說自己沒權(quán)限要么去調(diào)了一個早該廢棄的舊接口更離譜的是有一次它同時給三個下游系統(tǒng)發(fā)了同一筆退款指令。后來我把所有跟外部交互的部分全部抽出來統(tǒng)一走了一層專門做觸達的中間層問題才真正解決。這套方案后來被我們內(nèi)部叫做Agent-Reach。這篇文章不聊模型選型也不聊Prompt工程只聚焦一件事怎么讓Agent穩(wěn)定、可靠、有權(quán)限地把動作執(zhí)行到真實系統(tǒng)里。我會把Agent-Reach的架構(gòu)思路、接入步驟、還有我們實際踩過的坑全部攤開講適合正在做Agent落地、被“智能體只會聊天不會干活”折磨的團隊參考。1. 先拆解“觸達”這件事Agent-Reach到底解決什么問題很多人以為Agent接個API就是觸達。真不是。API只是拿到了門鑰匙門后面還有走廊、電梯、門禁和一堆你需要確認的東西。1.1 觸達的第一層工具觸達這是最基礎(chǔ)的一層。Agent需要調(diào)用某個工具、某個函數(shù)、某個接口來完成一個具體的動作。比如查詢庫存、創(chuàng)建工單、發(fā)送短信。大多數(shù)Agent框架都能做這件事只要你把工具描述寫得足夠清楚。但這里有個很隱蔽的問題工具的可用性和真實狀態(tài)。一個接口今天能調(diào)通明天可能因為參數(shù)變更、權(quán)限調(diào)整、限流策略修改就掛了。普通框架會直接報錯Agent就會把這個錯誤當(dāng)成最終結(jié)果告訴用戶。Agent-Reach的做法是在中間層做一次能力探活定期檢查所有注冊工具的健康狀態(tài)把失聯(lián)的工具從可選列表里摘掉而不是讓Agent去碰運氣。我見過太多項目把工具列表一股腦塞給Agent結(jié)果Agent自己也不知道哪個能用哪個不能用全靠瞎試。這在低風(fēng)險場景還好一旦涉及支付、退款、數(shù)據(jù)刪除這種“盲試”成本太高了。1.2 觸達的第二層渠道觸達渠道觸達是指Agent能從哪個口子到達目標(biāo)系統(tǒng)。同一個業(yè)務(wù)動作可能有多個渠道比如查詢訂單狀態(tài)可以走CRM接口也可以走數(shù)據(jù)倉庫甚至可以走后端微服務(wù)網(wǎng)關(guān)。不同渠道的實時性、數(shù)據(jù)口徑、成本都不一樣。Agent-Reach在設(shè)計上強制你給每個工具標(biāo)注來源渠道并定義渠道的優(yōu)先級和互斥規(guī)則。為什么要這么做因為我在實際項目中遇到過“臟數(shù)據(jù)”的問題。Agent同時從小程序端和大數(shù)據(jù)平臺拿到同一個訂單的狀態(tài)兩邊數(shù)據(jù)不一致Agent就直接蒙了。加上渠道優(yōu)先級之后它就知道以哪個數(shù)據(jù)為準(zhǔn)不會東一榔頭西一棒子。1.3 觸達的第三層上下文觸達這是最容易忽略、也最容易出問題的一層。上下文觸達指的是Agent在跨系統(tǒng)執(zhí)行任務(wù)時怎么保持業(yè)務(wù)上下文的連續(xù)性和一致性。舉個真實例子用戶在對話框里說“我要退掉昨天買的那個藍色T恤的訂單”Agent查到訂單號之后去執(zhí)行退款操作。這個“訂單號”就是一個上下文。問題在于如果Agent需要先查訂單系統(tǒng)再到退款系統(tǒng)執(zhí)行退款這兩個系統(tǒng)之間的調(diào)用是獨立的中間一旦斷了退款系統(tǒng)只拿到一個訂單號并不知道這個退款動作來自哪個用戶、什么會話、什么渠道。Agent-Reach的核心做法是給每一次外部調(diào)用都附加一個貫通全局的執(zhí)行上下文包含traceId、userId、會話ID、業(yè)務(wù)標(biāo)識。當(dāng)下游出現(xiàn)異常時起碼能快速定位“哪個用戶、哪個會話、哪一步出了問題”而不是翻日志翻到眼瞎。用一句話總結(jié)Agent-Reach解決的不是“Agent能干什么”而是“Agent在復(fù)雜的真實系統(tǒng)中怎么每一次都穩(wěn)定干成”。如果只跑Demo和POC這幾個問題基本不會暴露出來但只要上到生產(chǎn)環(huán)境每一條都會變成事故。2. 核心架構(gòu)把Agent-Reach當(dāng)成一個智能接線板我不想把架構(gòu)講得太玄用生活里最普通的東西來類比就是接線板。你的Agent是手機手機需要充電但墻上的插座分散在各個角落還分不同的電壓標(biāo)準(zhǔn)。Agent-Reach就是一個智能接線板它把各種接口統(tǒng)一成標(biāo)準(zhǔn)插孔然后把線接到背后正確的插座上同時它還帶一個智能開關(guān)——當(dāng)某個插座不穩(wěn)定的時候它會把電切換到另一個可用插座上而不是讓你的手機干瞪眼。2.1 統(tǒng)一執(zhí)行協(xié)議讓一切工具都長一個樣子Agent-Reach里最核心的抽象是統(tǒng)一執(zhí)行協(xié)議。不管你的下游是REST API、gRPC服務(wù)、消息隊列、數(shù)據(jù)庫、還是另一個Agent接入Agent-Reach時全部都會被包裝成同一種結(jié)構(gòu)動作名 入?yún)⒔Y(jié)構(gòu) 鑒權(quán)信息 預(yù)期返回結(jié)構(gòu)。一個工具的能力描述大概長這樣{ action: order.refund, description: 對指定訂單發(fā)起退款需要提供訂單號和退款原因, input_schema: { order_id: {type: string, required: true}, reason: {type: string, required: true}, amount: {type: number, required: false} }, output_schema: { refund_id: {type: string}, status: {type: string} }, channel: oms_api, timeout_ms: 5000, execution_mode: sync }所有工具用同一個協(xié)議描述Agent側(cè)就不用去記幾十種參數(shù)格式。它只要知道“有一個動作叫order.refund需要order_id和reason”然后就可以生成調(diào)用意圖。這個統(tǒng)一動作名的好處是當(dāng)?shù)讓酉到y(tǒng)替換的時候Agent的Prompt完全不用動只需要在Agent-Reach的配置里把動作映射到新接口就行。我后來重構(gòu)過一次訂單系統(tǒng)把舊接口全部下線Agent的行為零改動全部在中間層處理掉了。2.2 工具注冊中心與動態(tài)可見性Agent-Reach里有一個工具注冊中心所有接入的外部系統(tǒng)都在這里登記。注冊中心不只是存?zhèn)€名字和URL它還會持久化每個工具的版本信息、調(diào)用統(tǒng)計、失敗率、平均延遲、最近探活時間。這個設(shè)計和Agent的行為控制有直接關(guān)聯(lián)。Agent在決定調(diào)用哪個工具之前會先從Agent-Reach拿一次最新的工具列表而這個列表只包含“狀態(tài)正?!钡墓ぞ?。有一次我們某個報表接口因為后端SQL慢查詢超時了Agent-Reach在探活失敗后自動把該工具標(biāo)記為癱瘓Agent立刻就不再嘗試調(diào)用它而是直接告訴用戶“當(dāng)前功能暫時不可用請稍后再試”。聽起來好像很簡單但如果沒這一層Agent會一遍遍嘗試調(diào)用一個已經(jīng)超時的接口每次還得等滿超時時間用戶體驗糟糕到不行。注冊中心還要支持工具的版本管理。線上接口在迭代舊版本不能馬上刪新版本也要灰度。Agent-Reach允許同一個動作名同時映射到多個版本然后按比例灰度。比如order.refund動作10%的請求走新接口90%的請求走舊接口跑一段時間沒問題再全量切過去。2.3 從請求到執(zhí)行的完整生命周期一次完整的Agent觸達在Agent-Reach里是這樣流動的Agent根據(jù)用戶意圖產(chǎn)出一個結(jié)構(gòu)化調(diào)用意圖指定動作名和參數(shù)。Agent-Reach收到意圖后先做參數(shù)校驗必需參數(shù)缺失就直接打回不會帶著半截參數(shù)去請求下游。校驗通過后進入鑒權(quán)與授權(quán)模塊。Agent-Reach根據(jù)調(diào)用來源的用戶身份、會話上下文、動作敏感級別判斷這次調(diào)用是否有權(quán)限。敏感動作會要求二次確認或升級授權(quán)。授權(quán)通過后進入路由模塊。根據(jù)動作名追蹤到具體的下游地址再由路由模塊執(zhí)行調(diào)用。調(diào)用過程包含超時控制、重試策略、冪等控制。拿到下游結(jié)果后Agent-Reach做一次返回結(jié)構(gòu)標(biāo)準(zhǔn)化把不同系統(tǒng)的返回格式統(tǒng)一成Agent容易消費的JSON結(jié)構(gòu)。最后整條調(diào)用鏈路的日志被記錄歸檔包括入?yún)?、出參、耗時、錯誤碼、重試次數(shù)等全部留下方便事后復(fù)盤。這套流程跑順之后我們在排查問題時省了太多精力。以前出問題要翻Agent的推理日志、應(yīng)用服務(wù)器的調(diào)用日志、下游系統(tǒng)的接口日志三者對齊有多痛苦做過的人都懂?,F(xiàn)在直接在Agent-Reach的后臺按traceId查一條鏈路就完事。3. 實際接入讓Agent真正“伸出手”的具體步驟理論說完了聊點能直接落地的東西。下面是我們把一個內(nèi)部CRM系統(tǒng)接入Agent-Reach的完整過程不算復(fù)雜但細節(jié)不少。3.1 第一步定義能力邊界而不是一股腦全接很多團隊上來就把所有API全部接入結(jié)果Agent的選擇空間太大反而經(jīng)常挑錯工具。我們做過一次統(tǒng)計當(dāng)工具數(shù)量超過25個之后Agent選擇工具的準(zhǔn)確率會明顯下降幻覺工具名的情況也變多了。所以第一步是定義能力邊界。把業(yè)務(wù)需求拿出來逐條問自己三句話Agent是不是真的需要直接調(diào)用這個接口有沒有可能通過更上層的聚合接口實現(xiàn)這個接口的動作能不能用一個動詞名詞說清楚如果說不太清楚說明邊界太模糊Agent很容易用錯。調(diào)用失敗之后有沒有兜底方案如果沒有寧可先不接。我們CRM系統(tǒng)第一次梳理下來50多個接口最終只接入了12個。剩下的不是不好而是現(xiàn)階段Agent用它們的機會很少反而徒增干擾。3.2 第二步對下游接口做適配包裝市面上幾乎不可能有哪個系統(tǒng)天生遵循Agent-Reach的統(tǒng)一協(xié)議所以要寫適配層。適配層做的事情不算復(fù)雜說白了就是翻譯把系統(tǒng)A的路徑參數(shù)轉(zhuǎn)換成Agent-Reach的標(biāo)準(zhǔn)參數(shù)把系統(tǒng)B的鑒權(quán)方式包裝成統(tǒng)一鑒權(quán)調(diào)用把系統(tǒng)C的返回字段名映射為標(biāo)準(zhǔn)輸出字段適配層代碼不用寫得多高級但維護文檔一定要跟上。每接入一個下游都要記錄原接口文檔、字段映射表、變更聯(lián)系人。別問我為什么強調(diào)這個我們公司有個系統(tǒng)三年換了五個負責(zé)人每次對接都要重新考古。3.3 第三步給動作配置權(quán)限模型Agent-Reach的權(quán)限模型我建議按“動作級”來做。不要按系統(tǒng)級或接口級那不是太粗就是太細。動作級的意思是某個用戶或者某個機器人身份只對特定的動作名稱有執(zhí)行權(quán)限。我們的配置大概長這樣角色客服機器人 允許動作order.query, order.refund, customer.query, ticket.create, ticket.update 禁止動作customer.delete, order.modify_price, finance.settlement 角色運營分析機器人 允許動作report.query, data.export 禁止動作order.refund, ticket.update這個配置帶來的直接好處是就算Agent抽風(fēng)了最多也只能在允許的權(quán)限范圍內(nèi)做動作出不了大亂子??头C器人就算被注入了一堆惡意指令它也沒有權(quán)限去刪除客戶資料系統(tǒng)層面就把這條路堵死了。3.4 第四步Agent側(cè)接入把工具描述寫得像人話最后一步才是改Agent本體。在這個階段只需要把Agent-Reach暴露的可用動作列表以工具描述的形式注入給Agent就行。描述要寫得“像人話”讓模型一看就懂在什么場景下使用。我以前見到過一個反面教材use_crm_api_request_processor_v2: { params: { u: 用戶id, tp: 用戶類型, d: 部門 } }這種工具描述給Agent一百次它都不愛用。后來我們改成查詢客戶信息: { description: 當(dāng)用戶詢問客戶詳情、客戶等級、歷史消費記錄時使用此工具。需要提供用戶ID。, arguments: { user_id: 用戶ID } }即便是同一個動作描述得清晰之后Agent使用工具的準(zhǔn)確率能差出一大截。這里的經(jīng)驗是工具描述寧可啰嗦也不要抽象。模型不是程序員它理解不了“用戶實體n1關(guān)聯(lián)查詢”這種縮寫。4. 踩過的坑觸達鏈路上的四個真實問題與排查過程理論和步驟講完這部分全部是收費內(nèi)容。每一個坑都是我們真金白銀熬出來的不能說多慘痛但每一個都值得拿出來提醒大家。4.1 超時設(shè)置不合理導(dǎo)致Agent“假死”第一次上線的時候我們把所有下游調(diào)用的超時時間統(tǒng)一設(shè)成3秒。聽起來挺合理對吧結(jié)果有個報表接口平時就要2.5秒遇到數(shù)據(jù)量大一點的時候能到4秒。Agent等了4秒沒等到結(jié)果就開始重新推理推理完又調(diào)了一次接口還是慢Agent就這么卡在同一個工具上來回折騰。這個問題的根因是超時時間必須跟著下游的真實性能分布走不能用一刀切的方式。排查過程很簡單翻Agent-Reach的調(diào)用記錄找到那個報表工具的耗時分布P50是2.4秒P95是4.1秒。于是調(diào)整策略該工具的超時時間設(shè)為6秒重試次數(shù)設(shè)為0同時加了一個超時后的兜底回復(fù)。改完之后Agent再遇到這個慢接口時會直接告訴用戶“查詢比較慢請稍等”而不是內(nèi)部死循環(huán)。另外還有一個通用建議默認重試次數(shù)設(shè)為1就行。Agent調(diào)用一個動作失敗后會自己換一種說法再次發(fā)起調(diào)用。中間層如果再瘋狂重試等于幫它把錯誤放大了三倍。4.2 重試導(dǎo)致的重復(fù)執(zhí)行這個問題我印象最深。某一次我們接退款系統(tǒng)Agent調(diào)用order.refund動作時下游返回超時但實際退款已經(jīng)成功了。Agent-Reach自動重試了一次結(jié)果退了兩筆款。雖然后來財務(wù)發(fā)現(xiàn)對賬異常把錢追回來了但這個問題處理得極其狼狽。從那之后對涉及資金、狀態(tài)變更、數(shù)據(jù)刪除的敏感動作強制要求下游接口支持冪等鍵。冪等鍵用請求ID來生成同一個請求ID即使Agent再調(diào)三次五次下游也只會執(zhí)行一次。配置里長這樣actions: - name: order.refund execution_mode: sync idempotent: true idempotent_key_source: request_id如果下游系統(tǒng)不支持冪等Agent-Reach這邊會自動生成一條pending記錄在調(diào)用前先落庫結(jié)果返回后再更新狀態(tài)。這樣哪怕下游重復(fù)執(zhí)行了Agent-Reach也能靠本地狀態(tài)判斷“這筆動作其實已經(jīng)在執(zhí)行了不要再發(fā)”。這個方案在接老系統(tǒng)時尤其好用不需要改造對方只需要在自己的狀態(tài)存儲里做一層防護。4.3 憑證管理混亂導(dǎo)致Agent“越權(quán)”早先我們圖省事把多個系統(tǒng)的API Key都寫在一個配置里啟動的時候一次性加載。結(jié)果有一次測試環(huán)境的Key和生產(chǎn)環(huán)境的Key混了Agent在測試環(huán)境呼叫生產(chǎn)系統(tǒng)的退款接口對方?jīng)]攔還真執(zhí)行了。原因是兩個環(huán)境的憑證都配在同一個文件里Agent-Reach啟動時讀到了一個Key就全局生效。后來憑證管理改成三件事環(huán)境隔離、最小授權(quán)、輪換機制。每個下游系統(tǒng)的憑證在Agent-Reach里都綁定到具體環(huán)境標(biāo)簽測試環(huán)境和生產(chǎn)環(huán)境物理隔離不能共用一套配置。每個憑證的權(quán)限范圍也要收斂到該服務(wù)實際需要的最小集。另外定時輪換不要指望一個Key用三年不出事。這里再補一個小細節(jié)對Agent調(diào)用動作時的身份一定要注入userId不能只靠系統(tǒng)身份的公共憑證。如果你只用一個公共憑證那所有Agent調(diào)用都長一個樣出了問題根本查不到是誰在什么場景下發(fā)起的。4.4 上下文在跨系統(tǒng)時“斷線”這個坑最隱蔽也最考驗架構(gòu)能力。有一次客服智能體在幫用戶處理換貨流程先查了訂單然后創(chuàng)建退貨單再通知倉庫系統(tǒng)。前兩步都正常第三步倉庫系統(tǒng)報錯了。報錯信息里沒有訂單號、沒有用戶ID只有倉庫自己的一套外部單號。我們當(dāng)時排查了很久才發(fā)現(xiàn)Agent-Reach在調(diào)用倉庫系統(tǒng)時沒有把前幾步生成的內(nèi)部業(yè)務(wù)單號透傳過去導(dǎo)致倉庫系統(tǒng)無法把三件事關(guān)聯(lián)起來。這個問題在分布式系統(tǒng)里有個耳熟能詳?shù)拿宙溌纷粉?。但在Agent場景里不僅要追蹤調(diào)用鏈路還要帶上業(yè)務(wù)單據(jù)的關(guān)聯(lián)信息。Agent-Reach在設(shè)計上要求所有下游調(diào)用的header里都攜帶X-Agent-Reach-TraceId本次會話的唯一追蹤ID X-Agent-Reach-UserId發(fā)起用戶ID X-Agent-Reach-Business-Ref業(yè)務(wù)單據(jù)引用編號比如在換貨場景里引擎會把前一步創(chuàng)建的退貨單號寫入Business-Ref這樣倉庫系統(tǒng)報錯時我們就能順著這條鏈找到是哪個退貨單引發(fā)的問題。這個改完之后客服團隊反饋處理異常工單的時間從原來的幾小時縮短到十幾分鐘。5. 關(guān)鍵參數(shù)與調(diào)優(yōu)思路讓觸達更順滑的幾組經(jīng)驗值這節(jié)不講架構(gòu)就講我們摸索了大半年之后沉淀下來的參數(shù)經(jīng)驗和調(diào)優(yōu)思路。每一項都有明確背景抄作業(yè)時可以結(jié)合自己的業(yè)務(wù)再微調(diào)。5.1 一組可以直接當(dāng)起點的參數(shù)表參數(shù)項推薦初始值調(diào)整依據(jù)我們踩過的教訓(xùn)工具超時時間默認3000ms按下游P95耗時30%余量設(shè)定統(tǒng)一3秒導(dǎo)致慢接口假死重試次數(shù)默認1次敏感動作直接設(shè)為0自動重試造成重復(fù)退款工具列表可見數(shù)量不超過25個按Agent選準(zhǔn)率統(tǒng)計工具太多導(dǎo)致選錯工具探活頻率每30秒一次按系統(tǒng)重要程度調(diào)整探活太頻繁會拖慢注冊中心上下文保留時長24小時按會話場景調(diào)整太短跨天會話斷鏈鑒權(quán)緩存時間5分鐘權(quán)限變更頻率越高越短緩存過長導(dǎo)致刪除用戶后仍可調(diào)用注意表格里“上下文保留時長”。它對客服類Agent特別重要因為很多用戶今天聊一半明天又來接著聊。Agent-Reach會把會話的上下文狀態(tài)持久化包含必要的業(yè)務(wù)引用關(guān)系。24小時是一個比較折中的值能覆蓋大多數(shù)跨天會話又不會讓狀態(tài)存儲膨脹得太離譜。5.2 并發(fā)與限流的平衡該排隊排隊該拒絕拒絕Agent一旦接入多個高頻業(yè)務(wù)動作并發(fā)問題就會冒出來。某次活動期間客服智能體會話量漲了十倍Agent-Reach往訂單系統(tǒng)發(fā)的查詢請求也漲了十倍導(dǎo)致訂單系統(tǒng)直接被拖垮連正常的OA工作流都跑不動了。后來又加了一層令牌桶限流。Agent-Reach對每個下游系統(tǒng)配置了上游限流值比如“訂單查詢動作每秒最多放行20個請求”。多出來的請求進入排隊隊列超過一定長度之后直接拒絕并給Agent返回一個“當(dāng)前熱度較高”的提示讓Agent換個時間再問。這里有個容易被忽略的細節(jié)給不同敏感度的動作配不同的限流優(yōu)先級。比如業(yè)務(wù)高峰時查詢類的請求可以擠占掉語音外呼的配額但退款這類敏感動作吃的永遠是最高的隊列優(yōu)先級。限流這個東西本質(zhì)上是取舍只有明確了哪些動作不能丟削峰才有意義。6. 一些還在驗證中的方向Agent-Reach可以怎么演進這個項目做到現(xiàn)在基本盤穩(wěn)定了但也暴露了不少新的方向我個人還在摸索。第一個方向是讓Agent-Reach具備簡單的自我恢復(fù)能力。目前探活發(fā)現(xiàn)某個工具異常后只是摘掉它。下一步想做到自動檢查該工具的上一個可用版本如果老版本還健康自動平滑切流回去。這個思路源于一次實例崩潰當(dāng)時我們手動切回了上一個版本整個切換花了十五分鐘如果能自動化平均故障恢復(fù)時間能壓縮到一分鐘內(nèi)。第二個方向是把觸達能力往多智能體協(xié)作上去延伸?,F(xiàn)在的Agent-Reach主要還是服務(wù)單個Agent調(diào)用外部系統(tǒng)。但多個Agent之間互相調(diào)用時前面提到的鑒權(quán)模型、冪等控制、上下文傳遞會更復(fù)雜。比如一個數(shù)據(jù)分析Agent請求一個客服Agent幫忙查客戶投訴記錄這個請求中間要過幾道身份校驗和授權(quán)判定Agent-Reach需要變成一個更通用的智能體間通信網(wǎng)關(guān)。第三個方向偏工程效率讓工具接入過程更“自助化”?,F(xiàn)在每接一個下游系統(tǒng)還要手寫適配層工作量不小。如果能把OpenAPI文檔導(dǎo)入之后自動生成適配層代碼和工具描述描述接入時間能大幅壓縮。我在小范圍實驗過效果還可以但生成的描述描述質(zhì)量還不太穩(wěn)定需要人工過一遍。這些方向嚴格說還沒完全驗證穩(wěn)定我就不拍胸脯保證了。做Agent基礎(chǔ)設(shè)施這塊久了都會有一個感覺方案永遠都有更好的但真正能扛住線上流量和突發(fā)狀況的往往是那個把細節(jié)摳到極致的版本。Agent-Reach在架構(gòu)上沒什么天才設(shè)計全是靠一個個問題喂出來的。如果你也在做類似的Agent項目別急著堆功能先把觸達做扎實Agent才能真正從“能聊”變成“能干”。