作觸達(dá)層的架構(gòu)與工程實(shí)踐)
1. Agent-Reach 是什么先把它講明白最近兩年我一直在做多智能體相關(guān)的系統(tǒng)Agent-Reach 是我越做越覺(jué)得繞不開(kāi)的一個(gè)話題。其實(shí)這個(gè)詞本身并不是什么大廠發(fā)布的框架它更像一個(gè)方向Agent 代表現(xiàn)在被大模型帶火的自主執(zhí)行單元Reach 代表“觸達(dá)”和“覆蓋”。合在一起核心就是解決一個(gè)特別現(xiàn)實(shí)的問(wèn)題——當(dāng)系統(tǒng)里有幾十上百個(gè) Agent 的時(shí)候一個(gè) Agent 怎么才能穩(wěn)定、安全、可控地觸達(dá)另一個(gè) Agent 的能力和數(shù)據(jù)。這套東西做出來(lái)之后你會(huì)發(fā)現(xiàn)它能解決三類很典型的痛點(diǎn)一是 Agent 之間互相不知道對(duì)方的存在和能力能力全部變成孤島二是通信協(xié)議五花八門HTTP、WebSocket、消息隊(duì)列全混著來(lái)協(xié)作成本極高三是沒(méi)有任何權(quán)限邊界一個(gè) Agent 一旦被異常輸入誘導(dǎo)就可能拖垮別的 Agent 甚至泄露數(shù)據(jù)。Agent-Reach 本質(zhì)上就是在這中間加一層“觸達(dá)層”把發(fā)現(xiàn)問(wèn)題、路由問(wèn)題、協(xié)議轉(zhuǎn)換、權(quán)限控制、鏈路追蹤這些橫向能力全部接管。如果你正在做單 Agent 產(chǎn)品或者正準(zhǔn)備把多個(gè) Agent 拼成一套協(xié)作系統(tǒng)這篇文章值得看完。我會(huì)把 Agent-Reach 拆成架構(gòu)、核心能力、輕量原型、踩坑實(shí)錄和擴(kuò)展方向幾個(gè)部分來(lái)講純實(shí)操視角沒(méi)有空話。1.1 為什么多 Agent 系統(tǒng)需要單獨(dú)的觸達(dá)層有人會(huì)問(wèn)Agent 之間直接用 HTTP 或者 gRPC 調(diào)用不就行了能通但只是最表層通。多 Agent 協(xié)作和普通微服務(wù)調(diào)用有一處本質(zhì)區(qū)別微服務(wù)的接口相對(duì)穩(wěn)定由團(tuán)隊(duì)統(tǒng)一定義Agent 卻有大量自主性和不確定性。同一個(gè)能力不同 Agent 的實(shí)現(xiàn)可能天差地別同一個(gè) Agent在不同上下文下返回結(jié)果也可能漂移。再加上 Agent 之間可能互相發(fā)起請(qǐng)求、失敗需要重試、負(fù)載需要均衡這些動(dòng)態(tài)邏輯一旦散落在業(yè)務(wù)代碼里最后就是一團(tuán)亂麻。獨(dú)立觸達(dá)層的好處是能把“發(fā)現(xiàn)、連接、路由、限流、審計(jì)”這些橫向能力沉淀成基礎(chǔ)設(shè)施讓 Agent 只關(guān)心自己的業(yè)務(wù)。打個(gè)生活化的比方公司里要有前臺(tái)、總機(jī)和工位指引牌員工不需要記住每個(gè)人坐哪個(gè)工位打個(gè)電話給總機(jī)就行。Agent-Reach 就是智能體世界的總機(jī)。1.2 整體架構(gòu)中心化起步再向網(wǎng)格演進(jìn)架構(gòu)選型上我建議早期走 Hub-Spoke 模型也就是一個(gè)中心注冊(cè)中心加若干接入節(jié)點(diǎn)。原因很實(shí)在中心化模型容易調(diào)試適合業(yè)務(wù)驗(yàn)證階段快速迭代等 Agent 規(guī)模上來(lái)再往 Mesh 形態(tài)演進(jìn)把路由決策下沉到各節(jié)點(diǎn)。去中心化看起來(lái)很酷但排障成本極高小團(tuán)隊(duì)貿(mào)然上馬很容易被復(fù)雜性和分布式問(wèn)題拖死。我的參考架構(gòu)大致包含五類組件注冊(cè)中心、路由網(wǎng)關(guān)、消息總線、狀態(tài)與觀測(cè)服務(wù)、策略引擎。標(biāo)題里的“Reach”其實(shí)是這五類組件協(xié)同之后呈現(xiàn)給上層的能力當(dāng)一個(gè) Agent 發(fā)出需要另一個(gè) Agent 能力的請(qǐng)求時(shí)請(qǐng)求能穩(wěn)定到達(dá)目標(biāo)而且調(diào)用方能清晰知道結(jié)果、狀態(tài)和耗時(shí)。下面每一塊我會(huì)具體拆開(kāi)講。2. 核心能力拆解連接、調(diào)度、觀測(cè)三大件2.1 注冊(cè)與發(fā)現(xiàn)讓 Agent 之間找得到先講最底層的一步Agent 怎么被找到。我的做法是讓每個(gè) Agent 啟動(dòng)后通過(guò) Agent-Reach 的接入 SDK 上報(bào)能力清單。這個(gè)清單要自描述最好類似 OpenAPI 那套思路明確說(shuō)清楚“我能做什么、接收什么參數(shù)、返回什么結(jié)構(gòu)”。注冊(cè)表里存這些元數(shù)據(jù)并給每個(gè) Agent 分配一個(gè)全局唯一 ID。下面是一個(gè)參考模型定義用 Pydantic 寫出來(lái)很直觀from pydantic import BaseModel class Capability(BaseModel): name: str input_schema: dict output_schema: dict max_concurrency: int 5 timeout_ms: int 30000 class AgentInfo(BaseModel): agent_id: str name: str endpoint: str capabilities: list[Capability] ttl: int 30Agent 下線靠心跳機(jī)制淘汰。我習(xí)慣心跳間隔設(shè) 10 秒TTL 設(shè) 30 秒連續(xù)三次沒(méi)有心跳就標(biāo)記為離線路由時(shí)自動(dòng)避開(kāi)。這里有個(gè)我踩過(guò)的坑早期用name做路由匹配結(jié)果兩個(gè) Agent 都叫 researcher行為完全不一樣任務(wù)直接發(fā)錯(cuò)。后來(lái)強(qiáng)制全局唯一 ID名字只當(dāng)作展示標(biāo)簽。這是做觸達(dá)層非常重要的一條紀(jì)律。2.2 消息路由與協(xié)議轉(zhuǎn)換別讓格式卡住協(xié)作第二個(gè)大坑是協(xié)議不統(tǒng)一。有的 Agent 只暴露 WebSocket有的只支持 HTTP 回調(diào)有的內(nèi)部是 asyncio 隊(duì)列。Agent-Reach 的做法是統(tǒng)一信封Envelope把原始請(qǐng)求包裝成帶路由頭、消息 ID、過(guò)期時(shí)間、調(diào)用鏈 ID 的標(biāo)準(zhǔn)化封裝底層用適配器連接不同傳輸協(xié)議。路由匹配策略我習(xí)慣基于“能力”而不是直接指定 agent_id。調(diào)用方只聲明“我需要什么能力”路由層自己去找當(dāng)前可用且負(fù)載較低的 Agent。這樣做的收益在容錯(cuò)上特別明顯一個(gè)節(jié)點(diǎn)掛了可以自動(dòng)找另一個(gè)新增同能力 Agent 也不用改調(diào)用方還能順便做灰度把新 Agent 的權(quán)重調(diào)低慢慢放量。路由表的查詢不能每次都打到注冊(cè)中心上否則壓力全堆在中心節(jié)點(diǎn)。我通常做本地緩存緩存時(shí)間幾秒鐘并讓注冊(cè)中心在 Agent 注冊(cè)時(shí)主動(dòng)推送路由表更新事件這樣剛注冊(cè)的 Agent 會(huì)被網(wǎng)關(guān)增量感知不用等緩存過(guò)期。2.3 權(quán)限與信任邊界Agent 之間的安全柵欄多 Agent 協(xié)作最容易被忽略的其實(shí)是權(quán)限。內(nèi)部流量也不能默認(rèn)放行?,F(xiàn)實(shí)場(chǎng)景里可能有一個(gè) Agent 能讀用戶數(shù)據(jù)另一個(gè) Agent 只是做文案潤(rùn)色。如果文案 Agent 被一段經(jīng)過(guò)精心構(gòu)造的提示詞誘導(dǎo)它有沒(méi)有能力把數(shù)據(jù) Agent 拖下水這就是信任邊界問(wèn)題。我在 Agent-Reach 里先做兩層控制。第一層注冊(cè)時(shí)聲明 scope 和角色調(diào)用時(shí)校驗(yàn)調(diào)用方的 token 是否擁有目標(biāo)能力的 scope同時(shí)所有跨 Agent 請(qǐng)求都帶調(diào)用鏈 ID審計(jì)日志完整記錄誰(shuí)在什么時(shí)候用哪個(gè)能力查了什么數(shù)據(jù)。第二層是出站白名單每個(gè) Agent 能觸達(dá)哪些其他 Agent、哪些外部工具都在策略引擎里配置默認(rèn)拒絕一切未聲明的訪問(wèn)。這一層做得好不好直接決定系統(tǒng)敢不敢接真實(shí)業(yè)務(wù)。我自己見(jiàn)過(guò)不止一次 Agent 被惡意提示詞誘導(dǎo)去調(diào)用內(nèi)部工具的場(chǎng)景沒(méi)有權(quán)限柵欄的話損失面會(huì)非常大這不是危言聳聽(tīng)。3. 從零搭一個(gè) Agent-Reach 輕量原型3.1 環(huán)境準(zhǔn)備與最小配置理論講完下面帶你把這套東西用 Python 快速實(shí)現(xiàn)一遍。不需要 Kubernetes單機(jī) Docker Compose 就能跑。技術(shù)選型我介紹一下FastAPI 做注冊(cè)中心和路由網(wǎng)關(guān)Redis Streams 做消息總線JWT 做調(diào)用憑證。選這些不是因?yàn)樗鼈冏畛倍巧鷳B(tài)成熟本地調(diào)試順手遇到問(wèn)題搜一下全都有答案。最小配置文件長(zhǎng)這樣reach: registry: heartbeat_interval: 10 ttl: 30 router: cache_ttl: 5 retry_on_failure: 2 fallback_enabled: true bus: queue_name: reach.tasks consumer_group: reach-workers max_retry: 3 security: jwt_secret: change-me-in-prod observability: trace_header: X-Reach-Trace這些參數(shù)不是拍腦袋定的。心跳 10 秒、TTL 30 秒意味著最壞情況下 30 秒后死節(jié)點(diǎn)會(huì)被剔除對(duì)多數(shù)內(nèi)部系統(tǒng)來(lái)說(shuō)可以接受。想要更快可以調(diào)到 3 秒和 10 秒但 Redis 的寫壓力會(huì)高不少屬于典型的“用資源換響應(yīng)速度”權(quán)衡看業(yè)務(wù)容忍度來(lái)選。3.2 核心模塊實(shí)現(xiàn)思路注冊(cè)接口用 FastAPI 寫核心是POST /register接收 AgentInfo 后寫入 Redis Hash用 agent_id 做 key同時(shí)設(shè)置過(guò)期時(shí)間。這樣連心跳續(xù)約都省了Agent 每次心跳就是重新提交一次 AgentInfo順手重置 TTL。路由部分的參考實(shí)現(xiàn)async def find_target(capability, excluded_ids()): candidates await reach.router.find(capabilitycapability) candidates.sort(keylambda x: (x.load_score, x.weight)) alive [c for c in candidates if c.agent_id not in excluded_ids] return alive[0] if alive else None其中l(wèi)oad_score來(lái)自 Redis 里累計(jì)的任務(wù)計(jì)數(shù)weight就是灰度系數(shù)。我建議把緩存和批次查詢都做上一次聚合查詢拿回全部候選不要在循環(huán)里挨個(gè)查 Redis否則并發(fā)一上來(lái)就是性能災(zāi)難。消息流轉(zhuǎn)用 Redis Stream 很順手。生產(chǎn)者把 Envelope 寫入 Stream消費(fèi)者組里的 worker 拉任務(wù)執(zhí)行任務(wù)完成后把結(jié)果寫回另一個(gè)結(jié)果 Stream通過(guò)message_id做對(duì)賬。有人問(wèn)為什么用 Stream 不用數(shù)據(jù)庫(kù)因?yàn)?Stream 天然有 ack 機(jī)制、消費(fèi)者組和待確認(rèn)消息列表省了自己寫消息表那一大堆事。3.3 跑通一次跨 Agent 協(xié)作的完整鏈路我們?cè)O(shè)一個(gè)具體場(chǎng)景有一個(gè) planner Agent 需要讓 researcher Agent 查資料。planner 構(gòu)造一個(gè)標(biāo)準(zhǔn)請(qǐng)求envelope { message_id: b1e4f1a2c3d04e569a7b8c9d0e1f2a3b, trace_id: trace-001, source: planner-agent-01, target_capability: web_search, payload: {query: 多智能體系統(tǒng)為什么需要觸達(dá)層}, deadline_ms: 20000, }Agent-Reach 收到請(qǐng)求后先校驗(yàn) token 的 scope再匹配路由表找到 researcher 的 endpoint通過(guò)適配器把 payload 發(fā)給它。researcher 執(zhí)行完把結(jié)果塞回結(jié)果 Stream。planner 這邊異步等待結(jié)果如果超時(shí)就按配置觸發(fā)默認(rèn)策略重試一次再不行就換另一個(gè)具備web_search能力的 Agent這就是配置里fallback_enabled在做的事。整個(gè)鏈路里最重要的字段是trace_id。它帶著整個(gè)調(diào)用關(guān)系一路往下走讓所有相關(guān)日志都能串起來(lái)。排障的時(shí)候靠一條 trace_id 就能把注冊(cè)記錄、路由記錄、執(zhí)行日志、結(jié)果回傳拼成一個(gè)完整故事。沒(méi)有這個(gè)字段多 Agent 協(xié)作出了問(wèn)題你只能兩眼一抹黑。4. 觀測(cè)與追蹤把看不見(jiàn)的協(xié)作講清楚4.1 為什么觀測(cè)要比微服務(wù)時(shí)代更重做多 Agent 系統(tǒng)之后我有一個(gè)很深的感受觀測(cè)的重要性比微服務(wù)時(shí)代還要高出一個(gè)量級(jí)。微服務(wù)的調(diào)用鏈相對(duì)是穩(wěn)定的一個(gè)請(qǐng)求從 A 到 B 再到 C路徑基本固定Agent 系統(tǒng)不一樣路由是動(dòng)態(tài)的同一個(gè)能力可能今天落在 A 上明天落在 B 上Agent 還會(huì)根據(jù)上下文自主協(xié)作。靜態(tài)鏈路圖根本畫不出來(lái)只能靠運(yùn)行時(shí)觀測(cè)來(lái)還原每一次調(diào)用的真實(shí)軌跡。所以 Agent-Reach 里觀測(cè)不是附加功能而是核心能力。SDK 會(huì)在注冊(cè)、路由、入隊(duì)、執(zhí)行開(kāi)始、執(zhí)行結(jié)束、結(jié)果返回這些關(guān)鍵節(jié)點(diǎn)自動(dòng)打點(diǎn)統(tǒng)一上報(bào)。4.2 埋點(diǎn)方案與鏈路還原埋點(diǎn)數(shù)據(jù)至少要覆蓋這些字段agent_id、capability、開(kāi)始時(shí)間、耗時(shí)、返回狀態(tài)、重試次數(shù)。更細(xì)一點(diǎn)的調(diào)用鏈模型可以這樣定義class Span: trace_id: str span_id: str parent_span_id: str service_name: str operation: str start_ms: int duration_ms: int每個(gè) Agent 在執(zhí)行任務(wù)時(shí)都生成一個(gè) Span通過(guò)parent_span_id掛到父 Span 上。這樣收集端拿到一批 Span 后就能把調(diào)用關(guān)系還原成一棵完整的調(diào)用樹(shù)。展示上直接對(duì)接 Jaeger 或者 Grafana Tempo 就行不用自研。日志規(guī)范也建議統(tǒng)一Redis 里的任務(wù)日志、路由網(wǎng)關(guān)的訪問(wèn)日志、Agent 的執(zhí)行日志全部打上 trace_id、span_id、agent_id 這三個(gè)公共字段。日志格式我推薦 keyvalue 的明文格式雖然占空間但排查起來(lái)比純 JSON 更友好grep 一下就能取出來(lái)用。4.3 采樣策略與告警指標(biāo)觀測(cè)數(shù)據(jù)量是很大的尤其當(dāng)任務(wù)量上來(lái)之后。全量上報(bào)會(huì)拖垮存儲(chǔ)我建議做兩段式采樣正常路徑按 10% 頭部采樣保存 trace慢路徑和異常路徑全量保存。理由是過(guò)去踩過(guò)的坑里問(wèn)題往往集中在慢調(diào)用和失敗調(diào)用上這些必須完整還原正常的快速調(diào)用保留少量樣本做趨勢(shì)分析就夠了。告警指標(biāo)我自己的優(yōu)先級(jí)排序是成功率、p95 耗時(shí)、隊(duì)列積壓數(shù)、路由失敗次數(shù)。隊(duì)列積壓這個(gè)指標(biāo)最容易被忽視但它往往是最早暴露問(wèn)題的信號(hào)——某個(gè) Agent 執(zhí)行變慢任務(wù)積壓先漲下游才跟著出問(wèn)題。建議把積壓數(shù)的告警閾值設(shè)置得比成功率更敏感一些。5. 踩坑實(shí)錄與調(diào)優(yōu)清單5.1 高頻問(wèn)題速查這幾類問(wèn)題是我實(shí)際跑多 Agent 系統(tǒng)時(shí)反復(fù)遇到的整理成一張表方便你直接對(duì)照現(xiàn)象可能原因解決辦法Agent 注冊(cè)后路由找不到TTL 過(guò)期或緩存未刷新調(diào)短緩存 TTL注冊(cè)時(shí)發(fā)增量更新事件任務(wù)偶發(fā)丟失消費(fèi)者組 ack 前進(jìn)程崩潰開(kāi)啟 Stream 的待確認(rèn)消息對(duì)賬重放未確認(rèn)任務(wù)同一任務(wù)重復(fù)執(zhí)行重試沒(méi)有冪等鍵Envelope 里加 message_id執(zhí)行端做去重大量無(wú)效調(diào)用沒(méi)有能力匹配網(wǎng)關(guān)兜底邏輯混亂路由前先做 capability 檢測(cè)拒絕時(shí)返回明確錯(cuò)誤碼調(diào)用鏈斷掉子 Agent 不傳 trace_id在 SDK 層強(qiáng)制透?jìng)髯侄尾辉试S關(guān)閉權(quán)限被繞過(guò)去只校驗(yàn)來(lái)源不校驗(yàn) scope改用 JWT 加 scope 校驗(yàn)出站白名單默認(rèn)拒絕5.2 性能優(yōu)化與容錯(cuò)設(shè)計(jì)性能上常見(jiàn)瓶頸有兩個(gè)。第一是路由表查詢每次請(qǐng)求都去 Redis 查候選會(huì)很快打滿 Redis 連接。我做過(guò)一次實(shí)測(cè)對(duì)比加上本地緩存和訂閱更新之后網(wǎng)關(guān) QPS 提升了一個(gè)量級(jí)。第二是結(jié)果回傳的輪詢?nèi)绻{(diào)用方用 1 秒一次的輪詢?nèi)ニ⑿陆Y(jié)果 Stream流量會(huì)被放大好幾倍。后來(lái)改成回調(diào)機(jī)制曾經(jīng)把 2000 個(gè)并發(fā)任務(wù)的內(nèi)存占用直接降了 70%。容錯(cuò)設(shè)計(jì)上最容易被忽略的就是背壓。某個(gè) Agent 執(zhí)行特別慢任務(wù)還在源源不斷進(jìn)來(lái)消息隊(duì)列就會(huì)越堆越長(zhǎng)最后整體拖垮。做法是給每個(gè) capability 配置最大并發(fā)數(shù)超過(guò)上限直接拒絕并讓調(diào)用方走降級(jí)鏈路。這個(gè)max_concurrency字段在我最開(kāi)始的Capability模型里就有它不是擺設(shè)一定要用起來(lái)。重試策略也要帶退避。固定間隔重試會(huì)引發(fā)重試風(fēng)暴所有失敗任務(wù)在同一時(shí)間點(diǎn)一起重試瞬間把 Agent 打死。我習(xí)慣用指數(shù)退避base_delay1s, factor2, max_delay30s并且每次重試前檢查 Envelope 里的deadline_ms一旦超時(shí)就丟棄不再做無(wú)意義的重試。6. 擴(kuò)展方向與個(gè)人體會(huì)6.1 觸達(dá)層長(zhǎng)出來(lái)之后能做什么Agent-Reach 這套觸達(dá)層一旦打牢后面能長(zhǎng)出來(lái)的東西非常多。最直接的是接一個(gè) Agent 能力市場(chǎng)Agent 注冊(cè)時(shí)的結(jié)構(gòu)化能力描述直接變成商品詳情加上能力搜索、對(duì)比、上下架流程玩法就很接近 App Store 了。第二個(gè)是服務(wù)編排層基于 DAG 圖把多個(gè) Agent 串起來(lái)跑審批流、研究流程這對(duì)企業(yè)級(jí)場(chǎng)景很有價(jià)值。第三個(gè)是流量回放與評(píng)估把線上請(qǐng)求錄下來(lái)用新版本 Agent 離線回放對(duì)比輸出質(zhì)量和行為差異這比盲目的模型升級(jí)安全得多。6.2 幾點(diǎn)真心話最后寫幾句實(shí)在話。Agent-Reach 聽(tīng)起來(lái)是通訊中間件但我覺(jué)得本質(zhì)是治理系統(tǒng)。通信協(xié)議本身反而不是最難的部分難的是一致性的定義、權(quán)限的邊界、排障的可觀測(cè)性這些都是在長(zhǎng)期運(yùn)維里才慢慢暴露出來(lái)的東西。如果你想做多 Agent 系統(tǒng)別等 Agent 數(shù)量超過(guò) 20 個(gè)才開(kāi)始考慮觸達(dá)層到時(shí)候重構(gòu)成本會(huì)高到讓你懷疑人生。我個(gè)人的實(shí)際體會(huì)是注冊(cè)、路由、追蹤、權(quán)限這四件事先做扎實(shí)比什么都強(qiáng)。不少花哨的編排功能都可以后期再加但這四件事做不好Agent 再多也是一盤散沙。后面我會(huì)把完整的示例代碼整理好再發(fā)出來(lái)到時(shí)候咱們?cè)偌?xì)聊。