作中能力發(fā)現(xiàn)與路由的觸達層設(shè)計)
上個月我把一個壓了很久的需求徹底重做了讓一個AI Agent獨立完成“監(jiān)控指標→生成日報→推送到企業(yè)群”整條流水線。第一版我圖省事把查數(shù)據(jù)庫、算聚合、做圖表、組織文案四件事全塞進同一個Agent的System Prompt里結(jié)果最直觀的表現(xiàn)是——任務(wù)越長越容易在某一個中間步驟上崩盤。后來改成多Agent分工新問題立刻冒出來這幾個Agent彼此根本不認識更不知道誰擅長什么讓A去找BB反而把請求推回給A。我折騰了一周最終沉淀下來的就是一套叫Agent-Reach的觸達層。Agent-Reach這個名字里的“Reach”說的從來不是Agent有多聰明而是它能不能在整個工作流里“夠得著”正確的協(xié)作對象和正確的上下文。它的核心價值可以拆成三件事把Agent的能力變成可見、可查、可校驗的列表讓請求按照語義和約束路由到正確的接收者在Agent之間做顯式的上下文對齊而不是靠模型臨場發(fā)揮。這篇文章我打算把整套思路和代碼級實現(xiàn)完整寫出來包括我是怎么設(shè)計注冊中心、消息總線和能力描述協(xié)議的也把跑起來之后踩過的四個深坑原原本本交代一遍。無論你是正在糾結(jié)“單Agent還是多Agent”還是已經(jīng)拆了多個Agent但發(fā)現(xiàn)它們根本沒法穩(wěn)定協(xié)作這篇都應(yīng)該能給你一個可以直接落地的參考框架。1. 為什么說“Agent-Reach”解決的是連接而不是智商1.1 單體Agent跑全流程本質(zhì)上是在賭上下文不失控先說說我為什么要把單體Agent拆掉。用單Agent執(zhí)行完整業(yè)務(wù)鏈路時最容易踩的坑就是System Prompt越來越長。你把查詢引擎怎么用、分析規(guī)則是什么、報告模板長什么樣全部塞進去隨著任務(wù)推進上下文里塞滿了各階段的中間結(jié)果。每次工具調(diào)用返回的數(shù)據(jù)都完整拼在對話尾部上下文總量就不再由信息價值決定而是由中間步驟的數(shù)量決定。我實際項目里有一個指標單Agent處理一份包含12個SKU的日報生成任務(wù)到了第八個SKU數(shù)字開始張冠李戴把A倉的銷售額填到了B倉的報告里。當(dāng)時我根本分不清是提示詞問題還是數(shù)據(jù)問題因為整個上下文已經(jīng)膨脹到快5萬token模型注意力的分配早就是一團漿糊了。這里要澄清一下我并不是否定單體Agent的價值。對于“總結(jié)會議記錄”這類上下文封閉的任務(wù)單Agent依然是最好的選擇因為它的輸入輸出邊界非常清晰。但跨環(huán)節(jié)業(yè)務(wù)是狀態(tài)開放的你需要把狀態(tài)經(jīng)過多個工具、多個職責(zé)節(jié)點傳遞這時候把所有狀態(tài)都堆在一個上下文里本身就是瓶頸。拆分責(zé)任是工程選擇而不是模型能力不夠的妥協(xié)。1.2 協(xié)作的瓶頸發(fā)現(xiàn)、路由、上下文對齊多Agent化之后第一個真實沖突點就是“我不知道該找誰”。我在代碼里創(chuàng)建了一堆Agent實例每個都很能干但它們在運行時完全不知道彼此的存在。你可以在Coordinator的提示詞里寫“你有以下子Agent可用”但這就把協(xié)作完全押在了模型的理解力上。后來我把“觸達”這件事拆成三個層面能力發(fā)現(xiàn)每個Agent提供一份結(jié)構(gòu)化的“自我介紹”包括name、description、input_schema、output_schema注冊到一個統(tǒng)一列表里。這個過程和搜索引擎爬蟲提交站點地圖沒什么區(qū)別核心是讓能力可以被檢索。路由調(diào)度收到一個請求后決策模塊需要判斷這個請求該交給哪個Agent處理。這里既可以用規(guī)則匹配也可以用LLM做語義判斷但關(guān)鍵在于必須有一個確定性的兜底不能純靠模型猜。上下文對齊不同Agent對字段名、時間格式、單位、分頁規(guī)則的預(yù)期往往不一樣。比如一個Agent輸出的date是“2024-08-01”另一個Agent接收時要求的是時間戳。對齊這件事必須顯式做不能指望Agent自己猜出來。打個比方這套東西跟企業(yè)總機的分機表加前臺轉(zhuǎn)接沒什么區(qū)別能力發(fā)現(xiàn)就是電話簿路由調(diào)度就是前臺上下文對齊就是接線翻譯。Agent-Reach做的不是訓(xùn)練Agent本身而是在協(xié)作體系里把這張電話網(wǎng)搭好讓每個分機都夠得著它該夠得著的人。1.3 為什么要做成顯式協(xié)議而不是靠提示詞約定有人可能會問我直接在Coordinator的提示詞里寫“你有以下Agent可用”不也能實現(xiàn)觸達嗎表面看能但這里有個致命問題提示詞約定是不可觀測的。Agent什么時候選錯了路由、選了之后有沒有調(diào)用成功、調(diào)用失敗之后有沒有按預(yù)期降級這些過程全都藏在黑盒里出了問題只能靠猜。顯式協(xié)議的價值在于每個協(xié)作動作都對應(yīng)一條可查的日志誰提交了注冊、路由是按什么規(guī)則匹配的、消息在哪個入站隊列等了多久、上下文在哪一步做了字段轉(zhuǎn)換。故障定位從“猜模型在想什么”變成了“查記錄”。這也是我把Agent-Reach定位成基礎(chǔ)設(shè)施而不是業(yè)務(wù)代碼的原因它不需要任何Agent變聰明只需要讓每個協(xié)作動作變成可觀察、可追蹤、可控制的操作。2. 一版就跑通的MVP注冊中心、消息總線與能力描述協(xié)議技術(shù)選型上我用了Python加asyncio沒有引入任何重量級框架。選它的原因很樸素asyncio的隊列天然適合做Agent間的消息傳遞每個Agent的輸入輸出就是JSON消息調(diào)試起來特別直觀。什么分布式消息中間件、服務(wù)網(wǎng)格在MVP階段都不需要。2.1 能力描述文件讓每個Agent先寫“自我介紹”在Agent-Reach里注冊的是能力不是Agent實例本身。每個Agent在啟動時必須先向注冊中心提交一份能力描述我用的格式大概長這樣name: report_agent description: 根據(jù)指標數(shù)據(jù)生成日報/周報支持Markdown和HTML輸出 version: 0.3.1 owner:>import asyncio import time from dataclasses import dataclass, field from typing import Optional, List dataclass class AgentInfo: name: str description: str input_schema: dict output_schema: dict tags: List[str] timeout_hint: int 60 last_heartbeat: float field(default_factorytime.time) healthy: bool True class Registry: def __init__(self): self._agents {} async def register(self, info: AgentInfo): self._agents[info.name] info async def unregister(self, name: str): self._agents.pop(name, None) async def heartbeats(self, name: str): if name in self._agents: self._agents[name].last_heartbeat time.time() self._agents[name].healthy True async def find_by_tag(self, tag: str) - Optional[str]: for info in self._agents.values(): if tag in info.tags: return info.name return None async def find_by_description(self, text: str) - Optional[str]: score_max, name_selected 0, None for info in self._agents.values(): hit sum(word in info.description for word in text.split()) if hit score_max: score_max, name_selected hit, info.name return name_selected if score_max 0 else None async def healthcheck(self, max_stale: int 30): now time.time() for name, info in self._agents.items(): info.healthy (now - info.last_heartbeat) max_stalefind_by_description的實現(xiàn)方式是把請求文本和每個Agent的description按詞匹配計算命中數(shù)取最高分。有人看到這種簡單做法可能會覺得太原始但我要強調(diào)它不依賴LLM結(jié)果完全可復(fù)現(xiàn)速度快到可以忽略不計。我在實際配置里讓規(guī)則路由先跑如果規(guī)則找不到結(jié)果再讓Coordinator用LLM做一次模糊匹配并且對LLM選出的結(jié)果做schema校驗。規(guī)則兜底永遠比直接推理更可靠這是多Agent架構(gòu)里最值得堅持的原則。2.3 消息總線其實只需要一個能超時和重試的緩沖為了不讓Agent之間直接互相調(diào)用搞出網(wǎng)狀耦合我在中間加了一條簡單的消息總線。每一條消息統(tǒng)一包成envelope格式如下{ msg_id: 2f9a4f00-1234-abcd-1234, from: coordinator, to: report_agent, trace_id: a1b2c3, type: request, payload: { ... }, credentials: { scope: [read:metrics, write:report] } }失敗時type變?yōu)閑rror在payload里帶code和message。總線入口邏輯很單一接收消息、檢查目標Agent是否注冊且健康、放入對應(yīng)隊列、等待Agent完成或超時、寫日志。超時判斷不復(fù)雜用asyncio.wait_for包在響應(yīng)隊列的get()上超時后自動重試一次。但重試必須小心一個陷阱不是所有操作都冪等。比如report_agent生成報告第一次可能已經(jīng)生成了半份結(jié)果你無腦重試對方就要面對兩份半成品。我的做法是在payload里帶msg_id目標Agent會先檢查msg_id是否處理過處理過就直接返回上一次結(jié)果這比用各種分布式事務(wù)方案簡單得多。3. 打通一條完整鏈路數(shù)據(jù)查詢Agent與日報生成Agent的真實協(xié)作理論說得再多不如直接看一個真實鏈路。我拿業(yè)務(wù)方的一個典型需求舉例每天想看到“銷售額低于閾值10%的分倉SKU”然后基于這些數(shù)據(jù)生成一份日報并推送出去。3.1 場景拆解與角色分配以前在單體Agent里處理這個需求一個Prompt從頭寫到尾中間橫跨查詢、計算、報告生成、發(fā)送四個環(huán)節(jié)?,F(xiàn)在我拆成兩個Agentdata_query_agent只負責(zé)查詢數(shù)倉返回固定字段的原始指標不做判斷不組織文案。report_agent接收指標和日期字段負責(zé)寫報告、生成圖表、組裝最終輸出。拆分的理由很明確數(shù)據(jù)查詢是強約束操作最好用規(guī)則或API完成結(jié)果必須可復(fù)驗而報告生成是一次模型調(diào)用允許失敗重試。兩者混在一起模型在中間步驟稍微失一下真整個數(shù)據(jù)鏈路的可信度就完蛋了。3.2 路由鏈路與上下文轉(zhuǎn)換用戶從統(tǒng)一入口說了一句“生成昨天的日報”。Coordinator的處理流程是先用tags里的daily精確找到report_agent。report_agent發(fā)現(xiàn)自己需要原始數(shù)據(jù)但它不知道數(shù)據(jù)API在哪兒于是通過注冊中心找到data_query_agent。report_agent發(fā)出一條request并在payload里帶上明確的上下文約束timezone8、interval[昨天開始, 昨天結(jié)束)、granularitydaily、min_sales_threshold0.1。上下文轉(zhuǎn)換的核心動作發(fā)生在這條request里把自然語言里的“昨天”解析成精確的start_date/end_date把“低于閾值10%”翻譯成API能識別的min_sales_threshold0.1把返回值統(tǒng)一成雙方約定的字段名sku, warehouse, sales_amount, threshold_ratio。轉(zhuǎn)換必須是顯式步驟不能依賴Agent自己發(fā)揮。我在實際操作中發(fā)現(xiàn)如果這里不顯式傳時區(qū)一個在北京早上九點跑的任務(wù)和一個在UTC時間跑的任務(wù)算出來的“昨天”可能差出整整一天。3.3 超時、降級與部分失敗的處理數(shù)據(jù)查詢Agent返回之后report_agent開始寫報告寫完要推送到企業(yè)群于是它再請求notifier_agent。這一步看起來很順但真正考驗系統(tǒng)的是失敗場景。我在第2版里設(shè)置過查詢Agent返回超時并重試仍失敗時report_agent不再無限等下去而是直接生成一份降級報告明確標注“XX時段的銷售數(shù)據(jù)缺失”流程繼續(xù)向前走。這個設(shè)計在最初遭到了質(zhì)疑有人說數(shù)據(jù)都不全了還生成什么報告我的看法是多Agent編排里的每一個環(huán)節(jié)都是可以獨立降級的你追求的不應(yīng)該是“所有環(huán)節(jié)都成功”而是“鏈路走到最后一定會有一個結(jié)論哪怕這個結(jié)論是部分失敗”。當(dāng)業(yè)務(wù)方半夜三點等著看報告時一份帶缺失標注的報告比一個卡死到天亮的任務(wù)有價值得多。另外一個經(jīng)驗是編排鏈路高度盡量控制在三層以內(nèi)——協(xié)調(diào)層、能力Agent層、數(shù)據(jù)服務(wù)層。層數(shù)一旦到四層以上trace信息形態(tài)就很容易失控每次排查問題都像在一團亂麻里找線頭。4. 踩坑實錄我在這套架構(gòu)里翻過的四個大坑寫這塊之前我先聲明Agent-Reach這版設(shè)計不是一次就穩(wěn)的。我前后跑了兩個月翻了四個讓我印象非常深的坑每一個都值得單獨拿出來說。4.1 死循環(huán)調(diào)用A做不了就推給BB又推回A第一個版本的System Prompt里我給每個Agent都加了一句“如果你不確定可以請求其他Agent幫助”。這句看似無害的話實際運行起來差點把系統(tǒng)拖垮。某天日志里出現(xiàn)了一條消息在兩個Agent之間來回傳播trace_id完全一樣from和to交替出現(xiàn)直到我手動把它終止。跑了一會兒定位到根因report_agent要做一個翻譯功能它不會于是調(diào)用assistant_agentassistant_agent也不知道數(shù)據(jù)準確抓取又推回給report_agent。兩個Agent都沒有處理能力但提示詞給了它們“可以求助”的權(quán)限于是形成了死循環(huán)。修改方案是兩個約束一起上第一注冊中心路由時禁止“環(huán)形轉(zhuǎn)包”即一個Agent只能響應(yīng)tags和能力描述匹配的請求不能把屬于自己職責(zé)的請求再推給其他同類Agent第二消息里強制帶max_hop3超過跳數(shù)直接返回錯誤。從那以后我把系統(tǒng)里所有Prompt中的“盡量”“你可以嘗試”這類模糊措辭全部清理了一遍——在多Agent架構(gòu)里模糊的授權(quán)就是故障的溫床。4.2 假死比崩潰更麻煩隊列積壓與重試風(fēng)暴第二個大坑是假死。某天鏈路突然停下來所有消息都卡在隊列里不報錯、不返回、也沒崩潰。一開始很難判斷是LLM API在“思考”還是真的卡住了直到我查了API調(diào)用日志發(fā)現(xiàn)一個Agent在調(diào)用LLM時超時了拋了一個普通異??蚣芏挷徽f按“失敗重試”處理。結(jié)果多個Agent同時交互都沒有退避機制重試請求被放大成了三倍流量每個Agent都在反復(fù)撞同一個失敗點。解決辦法分兩層第一層是給每個Agent的重試都加上指數(shù)退避和隨機抖動比如第一次等3秒、第二次等9秒、第三次等27秒第二層是全局限制重試次數(shù)不能每個節(jié)點各自無限重試。假死的另一個表現(xiàn)是Agent內(nèi)部在跑一個很長的工具調(diào)用隊列遲遲等不到響應(yīng)。后來我在Agent里增加了心跳機制它每隔30秒?yún)R報一次當(dāng)前階段狀態(tài)如果心跳停了才判定真死這個設(shè)計類似排隊時服務(wù)員喊一句“還在辦理請稍等”至少讓你知道系統(tǒng)不是無響應(yīng)。4.3 上下文在鏈路里無限膨脹這是讓我最頭疼的坑。第一版的時候每條消息都保存完整payload數(shù)據(jù)查詢Agent返回了幾千行結(jié)果report_agent拿到結(jié)果后把整份表格又寫進了自己的chain日志然后再原封不動地傳給notifier_agent。最后notifier的上下文里躺著三份重復(fù)的原始數(shù)據(jù)表模型已經(jīng)完全分不清哪一份是最新的了。解決方法是做截斷型規(guī)則核心就一句話每個Agent收到中間結(jié)果后只保留“結(jié)論摘要加結(jié)果引用”原始數(shù)據(jù)統(tǒng)一存在對象存儲里消息里只放一個指向原文的鏈接。換句話說鏈路中的上下文像一個接力棒傳出去的同時處理串就要縮短。我還在消息封包里加了token預(yù)算字段超過預(yù)算時原始payload替換為摘要再做一次語義壓縮。舉個具體例子原來傳給notifier的payload是5000行原始銷售數(shù)據(jù)現(xiàn)在變成{ summary: 銷售額低于閾值10%的分倉共3個主要集中華南區(qū)最大缺口為華南-01倉, data_ref: s3://report-data/20240801/summary.json }這一改整條鏈路的token開銷直接下降了70%以上而且每個環(huán)節(jié)要處理的噪音少了很多上下文灌入不正確數(shù)據(jù)的概率也降下來了。4.4 權(quán)限邊界被忽略任何Agent都能調(diào)用所有能力Agent-Reach有一個隱含假設(shè)“只要Agent能發(fā)現(xiàn)就能調(diào)用”。這個假設(shè)在內(nèi)網(wǎng)單租戶環(huán)境跑著沒毛病一旦放到多租戶環(huán)境或者涉及用戶數(shù)據(jù)風(fēng)險立刻冒頭。我遇到的實際案例是報表Agent用read:report的scope請求了write:user的能力結(jié)果真的調(diào)用成功了因為注冊中心只校驗了Agent名稱沒有校驗scope。后來我改成每個能力描述里聲明所需scope注冊中心在路由階段同時檢查調(diào)用方的scope集合是否覆蓋目標能力的scope要求不滿足直接返回403。這里有個細節(jié)權(quán)限校驗一定不要放在Agent內(nèi)部代碼里。Agent的代碼邏輯可以被提示詞影響你沒法保證模型在運行過程中不會自主“繞過”一段看似多余的校驗。放在注冊中心和消息總線入口的強制過濾才是最可靠的因為那一段是純規(guī)則判斷不涉及推理。5. 什么時候真正適合引入Agent-Reach寫到最后我必須說一句常被忽略的話并不是所有場景都該上多Agent架構(gòu)Agent-Reach也不例外。如果你根本不需要多個Agent分工這個框架只會白白增加復(fù)雜度。5.1 用這把尺子決定任務(wù)能否被切分中間狀態(tài)能否被顯式傳遞如果出現(xiàn)下面兩種情況之一你多半不需要Agent-Reach任務(wù)高度連貫、無法拆分。比如一段需要全局上下文才能做好的創(chuàng)意寫作拆給多個Agent反而會丟失整體風(fēng)格。中間狀態(tài)很難用JSON表達。比如一個依賴實時交互和視覺反饋的復(fù)雜任務(wù)硬拆開會讓進度和狀態(tài)都無法傳遞。真正適合的場景是任務(wù)流相對固定中間狀態(tài)可以結(jié)構(gòu)化且每個環(huán)節(jié)允許獨立重試和降級。拿數(shù)據(jù)分析加報告推送來說數(shù)據(jù)查詢的結(jié)果是標準JSON報告生成的輸出是字符串推送動作就一個回調(diào)——這種結(jié)構(gòu)天然適合多Agent協(xié)作。5.2 三檔接入方案不是所有團隊第一步就要上完整的注冊中心加LLM路由我建議按下面三檔逐步演進方案接入改動適合團隊路由確定性排查成本人肉路由只做分工腳本請求人工轉(zhuǎn)發(fā)剛接觸多Agent想驗證分工收益100%人判斷低因為步驟少規(guī)則路由MVP注冊中心tags匹配規(guī)則兜底分工已明確希望減少人工介入中等依賴描述質(zhì)量中日志可查LLM路由權(quán)限控制完整注冊中心LLM模糊匹配scope校驗請求語義多樣需要靈活分發(fā)高但引入不確定性高需要品牌級追蹤我個人的建議是先從人肉路由開始大概跑兩周看清楚每個Agent的真實分工邊界再升級到規(guī)則路由。直接跳到LLM路由是非常容易踩坑的你可能連路由錯了都不知道。5.3 效果度量與回滾方法引入框架后我主要盯這幾個指標端到端任務(wù)成功率。平均鏈路跳數(shù)與p95時延。失敗定位時間也就是從故障發(fā)生到定位到具體環(huán)節(jié)需要多久。上下文字均值對比拆分前降了多少。重試率與降級率。這幾項指標里失敗定位時間是最能體現(xiàn)Agent-Reach價值的指標。以前單Agent出了問題我要靠反復(fù)調(diào)提示詞去猜現(xiàn)在多Agent鏈路上出了問題我打開trace日志就能看到是哪一環(huán)返回了錯誤碼。排查時間從小時級降到了分鐘級這本身就是拆分最大的回報。另外架構(gòu)上要保留回滾能力。一旦發(fā)現(xiàn)拆分后的端到端成功率反而不如原來的單體Agent我會一鍵把路由規(guī)則改成直連單個Agent。Agent-Reach存在的意義是讓系統(tǒng)具備“按需協(xié)作”的能力而不是強迫你永遠處在一個復(fù)雜的協(xié)作模式里。我現(xiàn)在的習(xí)慣是先在固定結(jié)構(gòu)任務(wù)里用Agent-Reach比如周報、數(shù)據(jù)校驗、定時巡檢這類邊界清晰、狀態(tài)可傳遞的場景暫時不讓它碰開放性特別強、錯誤代價特別高的動作。連接能力落地之后如果模型業(yè)務(wù)確實處理得不好至少我們能在幾秒鐘之內(nèi)定位到問題出在哪一環(huán)而不是在漆黑一片里到處亂猜。這種“可定位”的價值說真的比多Agent本身還要值錢。