議與Nacos實(shí)戰(zhàn):構(gòu)建多Agent協(xié)作互通層)
1. 互通層為什么單機(jī)跑通的 Agent一上線就失聯(lián)先說(shuō)個(gè)背景。上一期我們把單個(gè) Agent 的構(gòu)建、記憶管理和工具調(diào)用都盤(pán)了一遍很多朋友照著做完之后本地測(cè)試一切正常結(jié)果一放到多進(jìn)程、多服務(wù)的環(huán)境里就出問(wèn)題A 服務(wù)的 Agent 要調(diào)用 B 服務(wù)的 Agent兩邊明明都在跑卻互相找不到或者說(shuō)找到了但調(diào)不通。這個(gè)問(wèn)題幾乎出現(xiàn)在每一個(gè)從單體走向分布式的 Agent 項(xiàng)目里本質(zhì)就是互通層沒(méi)做。AgentScope Java 在這塊的設(shè)計(jì)思路很直接用 A2A 協(xié)議把 Agent 的能力暴露成標(biāo)準(zhǔn)服務(wù)再用 Nacos 做這些服務(wù)的注冊(cè)與發(fā)現(xiàn)讓 Agent 之間像調(diào)用普通微服務(wù)一樣互相協(xié)作。很多剛開(kāi)始接觸這個(gè)概念的同學(xué)會(huì)問(wèn)我已經(jīng)有 HTTP 接口了為什么還要搞 A2A直接調(diào)接口不就行了答案是如果你只是調(diào)用別人的寫(xiě)死接口那叫系統(tǒng)集成不叫 Agent 協(xié)作。A2A 解決的是動(dòng)態(tài)發(fā)現(xiàn)能力、動(dòng)態(tài)編排任務(wù)、跨實(shí)現(xiàn)框架通信這幾件事。你的 Agent 可能用的是 AgentScope Java對(duì)方的 Agent 可能跑在別的語(yǔ)言框架上你們之間沒(méi)有約定好 AgentCard、Task 狀態(tài)機(jī)、Message 結(jié)構(gòu)就只能靠人肉對(duì)齊參數(shù)改一版崩一版。這篇文章就圍繞互通層展開(kāi)重點(diǎn)講三件事A2A 協(xié)議的核心機(jī)制、Nacos 接線的完整鏈路、以及我在實(shí)測(cè)中踩過(guò)的坑。內(nèi)容是基于 AgentScope Java 當(dāng)前主線的 A2A 模塊經(jīng)驗(yàn)部分示例代碼做了簡(jiǎn)化類(lèi)名和包路徑以你實(shí)際引入的版本為準(zhǔn)但思路和排查路徑是通用的。2. 把 Agent 說(shuō)出去A2A 協(xié)議與 AgentCard 的核心機(jī)制2.1 A2A 不是消息隊(duì)列是一套卡口明確的遠(yuǎn)程調(diào)用協(xié)議很多人第一次看 A2A 文檔會(huì)誤以為它是類(lèi)似 MQ 的異步消息系統(tǒng)其實(shí)不對(duì)。A2AAgent2Agent是一個(gè)基于 JSON-RPC 的同步/異步混合協(xié)議核心特征是每個(gè) Agent 暴露一組標(biāo)準(zhǔn)端點(diǎn)如message/send、task/get、task/cancel所有請(qǐng)求響應(yīng)都走 HTTP POST JSON。一次任務(wù)Task有完整的生命周期狀態(tài)submitted、working、input-required、completed、canceled、failed。消息內(nèi)容被拆成 Part也就是多模態(tài)內(nèi)容片段可以同時(shí)包含文本、URL、結(jié)構(gòu)化數(shù)據(jù)。舉個(gè)例子。假設(shè)你有一個(gè)訂單售后處理 Agent另一個(gè)團(tuán)隊(duì)用別的框架寫(xiě)了一個(gè)倉(cāng)儲(chǔ)庫(kù)存 Agent。兩邊要協(xié)作A2A 的做法是售后 Agent 收到用戶訴求后需要查庫(kù)存于是它構(gòu)造一個(gè) Task把用戶的訴求描述、相關(guān)訂單 ID、需要查詢(xún)的商品清單作為 Message 發(fā)出去通過(guò) A2A 端點(diǎn)發(fā)給庫(kù)存 Agent。庫(kù)存 Agent 處理完成后返回一個(gè)completed狀態(tài)的任務(wù)帶上庫(kù)存結(jié)果 Part。整個(gè)過(guò)程是標(biāo)準(zhǔn)化的兩邊都不需要關(guān)心對(duì)方的內(nèi)部實(shí)現(xiàn)。這種設(shè)計(jì)的好處在于協(xié)議卡在邊界上邊界以?xún)?nèi)的實(shí)現(xiàn)隨便你折騰。這和微服務(wù)之間走 REST 是同一個(gè)邏輯只不過(guò) A2A 把Agent 之間的對(duì)話抽象成了可以編排、可恢復(fù)、帶狀態(tài)機(jī)的任務(wù)而不是簡(jiǎn)單的請(qǐng)求響應(yīng)。2.2 AgentCard 里該放什么不該放什么A2A 協(xié)議里有一個(gè)經(jīng)常被一筆帶過(guò)但實(shí)際上很關(guān)鍵的組件AgentCard。它本質(zhì)是一個(gè) JSON 描述文件通常放在服務(wù)的/.well-known/agent.json路徑下用于告訴調(diào)用方這個(gè) Agent 是誰(shuí)、能干什么、怎么調(diào)。我在項(xiàng)目里維護(hù)過(guò)三個(gè) Agent 的卡片第一版把能干什么寫(xiě)得特別詳細(xì)幾乎把每個(gè)工具函數(shù)的參數(shù)都列進(jìn)去了結(jié)果維護(hù)成本極高改一個(gè)字段就要同步更新卡片。后來(lái)我把卡片收斂成下面這個(gè)結(jié)構(gòu){ name: order-after-sale-agent, description: 處理訂單售后訴求可查詢(xún)訂單狀態(tài)、發(fā)起退款、生成工單, url: https://agent-gateway.example.com/a2b, version: 1.0.0, capabilities: { functionCalling: true, streaming: false, multiModal: false }, skills: [ { name: query_order, description: 根據(jù)訂單號(hào)查詢(xún)訂單當(dāng)前狀態(tài), parameters: { type: object, properties: { orderId: { type: string } }, required: [orderId] } } ] }一個(gè)核心教訓(xùn)是AgentCard 是發(fā)現(xiàn)用的不是文檔用的。它要讓遠(yuǎn)程 Agent 在幾毫秒內(nèi)判斷你能不能滿足我的訴求、我該不該把任務(wù)發(fā)給你所以description和skills里的摘要信息一定要寫(xiě)清楚邊界。比如你的 Agent 只能處理國(guó)內(nèi)訂單就在 description 里直接寫(xiě)prompt僅支持國(guó)內(nèi)訂單海外訂單請(qǐng)轉(zhuǎn)人工省得調(diào)用方把任務(wù)打過(guò)來(lái)再被打回去來(lái)回浪費(fèi)一次遠(yuǎn)程調(diào)用。另一個(gè)容易忽略的點(diǎn)是url字段要和實(shí)際部署環(huán)境對(duì)齊。本地聯(lián)調(diào)時(shí)可以是 localhost 地址一旦接到 Nacos 上這個(gè) url 就要改成網(wǎng)關(guān)或服務(wù)實(shí)例的對(duì)外地址。否則就會(huì)出現(xiàn)Nacos 里能看到服務(wù)Agent 也選擇了實(shí)例但發(fā)請(qǐng)求時(shí)發(fā)現(xiàn) AgentCard 里的 url 是別人舊環(huán)境的地址直接 404。3. Nacos 接線Agent 服務(wù)如何被動(dòng)態(tài)發(fā)現(xiàn)3.1 服務(wù)注冊(cè)與發(fā)現(xiàn)的完整鏈路Nacos 在整套接線里扮演的角色是服務(wù)注冊(cè)中心 配置中心。Agent 服務(wù)啟動(dòng)后會(huì)把自身的 IP、端口、健康狀態(tài)、元數(shù)據(jù)注冊(cè)到 Nacos調(diào)用方不再需要寫(xiě)死對(duì)端地址而是通過(guò)服務(wù)名去 Nacos 拉取實(shí)例列表從中選一個(gè)健康實(shí)例發(fā)起 A2A 調(diào)用。接線鏈路可以拆成五步Agent 服務(wù)啟動(dòng)讀取配置向 Nacos 注冊(cè)自身實(shí)例信息。Nacos 注冊(cè)中心維護(hù)服務(wù)名到實(shí)例列表的映射并通過(guò)心跳機(jī)制感知實(shí)例存活狀態(tài)。調(diào)用方從 Nacos 查詢(xún)目標(biāo)服務(wù)名拿到一個(gè)或多個(gè)健康實(shí)例。調(diào)用方構(gòu)造 A2A 請(qǐng)求向目標(biāo)實(shí)例的 A2A 端點(diǎn)發(fā)送 HTTP POST。目標(biāo) Agent 處理請(qǐng)求返回結(jié)果任務(wù)狀態(tài)流轉(zhuǎn)更新。這個(gè)鏈路看著簡(jiǎn)單但實(shí)際接線時(shí)容易被三個(gè)細(xì)節(jié)卡住服務(wù)名命名規(guī)范、實(shí)例元數(shù)據(jù)設(shè)計(jì)、健康檢查配置。先講服務(wù)名。我建議按照業(yè)務(wù)域-角色-用途的格式命名比如agent-order-service、agent-logistics-service不要用agent1、agent2這種沒(méi)有任何語(yǔ)義的名字。因?yàn)樵诙?Agent 協(xié)作場(chǎng)景里服務(wù)名就是 Agent 的電話號(hào)碼別人通過(guò)名字找到你名字起得清晰能省很多溝通成本。再講元數(shù)據(jù)。Nacos 注冊(cè)實(shí)例時(shí)可以攜帶自定義 metadata這是一個(gè)很容易被浪費(fèi)掉的字段。你可以把 AgentCard 的關(guān)鍵信息直接塞進(jìn) metadata比如agentName、version、capabilities。這樣調(diào)用方在拉取實(shí)例列表時(shí)不需要先發(fā)一次 HTTP 請(qǐng)求去讀 AgentCard直接通過(guò)元數(shù)據(jù)就能做簡(jiǎn)單過(guò)濾。這在高頻調(diào)用場(chǎng)景下對(duì)性能和穩(wěn)定性都有幫助。健康檢查配置同樣值得上心。默認(rèn)的心跳機(jī)制依賴(lài)服務(wù)實(shí)例主動(dòng)上報(bào)如果 Agent 的 A2A 端點(diǎn)所在端口和 Nacos 健康檢查端口配置不一致會(huì)出現(xiàn)服務(wù)注冊(cè)成功但始終不健康的詭異情況。我在調(diào)試中就遇到過(guò)這個(gè)問(wèn)題客戶端拉到的實(shí)例一直標(biāo)記為 unhealthy排查了半天才發(fā)現(xiàn)是spring.cloud.nacos.discovery.port和實(shí)際暴露 A2A 端點(diǎn)的端口沒(méi)對(duì)齊。3.2 metadata 設(shè)計(jì)讓調(diào)用方一眼看懂 Agent 能力接線的難點(diǎn)往往不是注冊(cè)上去而是讓對(duì)方快速知道該不該調(diào)用你。我推薦在 Nacos 實(shí)例元數(shù)據(jù)里固定維護(hù)以下幾項(xiàng)metadata 字段示例值作用agentNameorder-after-sale-agentAgent 的唯一標(biāo)識(shí)服務(wù)名前綴version1.0.0能力版本用于灰度兼容owneraftersale-team團(tuán)隊(duì)負(fù)責(zé)人方便排查capabilitiesfunctionCalling,streaming能力標(biāo)簽用于快速過(guò)濾heartbeatInterval5000心跳間隔幫助調(diào)用方預(yù)判故障這里有一個(gè)經(jīng)驗(yàn)metadata 里的能力和 AgentCard 里的能力必須保持同步否則調(diào)用方根據(jù) metadata 判斷你支持 streaming結(jié)果發(fā)來(lái)流式請(qǐng)求發(fā)現(xiàn)不支持任務(wù)直接失敗。我目前的處理方式是做一個(gè)啟動(dòng)時(shí)的配置校驗(yàn)把 metadata 和本地 AgentCard 做一次比對(duì)不一致就打印告警日志寧可啟動(dòng)失敗也不要帶病上線。這個(gè)設(shè)計(jì)還有一個(gè)額外收益當(dāng)你想做 Agent 分組或灰度時(shí)metadata 直接作為路由維度。比如新版本 Agent 上線只注冊(cè) 10% 的實(shí)例metadata 標(biāo)version2.0.0-canary調(diào)用方通過(guò) Nacos 的權(quán)重策略就能把部分任務(wù)灰度到新版本上不需要改動(dòng)任何 A2A 調(diào)用代碼。4. 端到端接線實(shí)操AgentScope Java Spring Boot Nacos4.1 環(huán)境準(zhǔn)備與依賴(lài)配置這一部分我直接給一份可以照抄的依賴(lài)清單。項(xiàng)目的基本框架是 Spring Boot 3.x AgentScope Java 當(dāng)前主線版本 Nacos 服務(wù)端 2.x。dependency groupIdcom.alibaba.agentscope/groupId artifactIdagentscope-agent/artifactId version${agentscope.version}/version /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId version${alibaba.cloud.version}/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependencyNacos 服務(wù)端我建議直接用 Docker 起一個(gè)做開(kāi)發(fā)驗(yàn)證docker run --name nacos-server -p 8848:8848 -p 9848:9848 \ -e MODEstandalone \ nacos/nacos-server:v2.3.0然后配置 application.yml注意namespace和group一定要提前定好。不同環(huán)境的 Nacos 命名空間必須隔離不然測(cè)試環(huán)境的 Agent 和生產(chǎn)環(huán)境的 Agent 互相串線整個(gè)協(xié)作就亂了。spring: application: name: order-after-sale-agent cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev group: AGENT_GROUP register-enabled: true4.2 實(shí)現(xiàn)一個(gè)業(yè)務(wù) Agent 并暴露為 A2A 端點(diǎn)這里用一個(gè)訂單查詢(xún) Agent做例子。在 AgentScope Java 里Agent 核心邏輯是重寫(xiě)消息處理方法把輸入 Message 解析成業(yè)務(wù)需求執(zhí)行對(duì)應(yīng)的工具函數(shù)再封裝成輸出 Message 返回。Component public class OrderQueryAgent extends AgentBase { private final OrderService orderService; public OrderQueryAgent(OrderService orderService) { this.orderService orderService; } Override protected Message invoke(Message input) { // 解析入?yún)⑦@里簡(jiǎn)化成只處理文本文本內(nèi)容 String text input.getTextContent(); if (text null || text.isBlank()) { return Message.textMessage( 參數(shù)不完整需要提供訂單號(hào), order-query-agent ); } // 調(diào)用業(yè)務(wù)服務(wù)查詢(xún)訂單 OrderInfo info orderService.queryByOrderId(text.trim()); if (info null) { return Message.textMessage(訂單不存在: text, order-query-agent); } // 把查詢(xún)結(jié)果封裝為結(jié)構(gòu)化Part返回 return Message.textMessage( 訂單狀態(tài): info.getStatus() ,金額: info.getAmount(), order-query-agent ); } }有了 AgentBean之后最核心的一步是把它暴露成 A2A 遠(yuǎn)程端點(diǎn)。AgentScope Java 默認(rèn)提供的服務(wù)端適配模塊可以幫我們省去大量協(xié)議樣板代碼你只需要把 Agent 實(shí)例掛載到路由上框架會(huì)自動(dòng)完成協(xié)議轉(zhuǎn)換。RestController RequestMapping(/a2b) public class A2AController { private final A2AServerManager a2aServerManager; public A2AController(A2AServerManager a2aServerManager) { this.a2aServerManager a2aServerManager; } PostMapping(/message/send) public Task sendMessage(RequestBody IncomingMessage message) { return a2aServerManager.sendMessage(message); } PostMapping(/task/get) public Task getTask(RequestBody TaskQuery query) { return a2aServerManager.getTask(query.getTaskId()); } GetMapping(/.well-known/agent.json) public AgentCard getAgentCard() { return a2aServerManager.generateAgentCard(); } }需要注意不同版本的 AgentScope Java 在端點(diǎn)路徑上可能略微不同但/.well-known/agent.json和message/send是協(xié)議層的約定盡量保持一致降低對(duì)接成本。如果你是自己手寫(xiě) Controller建議先別急著做復(fù)雜邏輯直接把 AgentCard 和消息收發(fā)跑通再逐步加功能。4.3 編寫(xiě)客戶端通過(guò) Nacos 找到 Agent 并發(fā)起任務(wù)服務(wù)端暴露好之后客戶端的關(guān)鍵是從 Nacos 拿實(shí)例列表然后選中一個(gè)實(shí)例發(fā)起 A2A 調(diào)用。這一步的選實(shí)例邏輯很要命。最簡(jiǎn)單可靠的策略是先過(guò)濾健康實(shí)例再按權(quán)重隨機(jī)選擇一個(gè)。Component public class AgentServiceInvoker { private final NamingService namingService; public AgentServiceInvoker(NamingService namingService) { this.namingService namingService; } public String invokeAgent(String serviceName, String messageText) throws Exception { // 1. 從 Nacos 拉取健康實(shí)例 ListInstance instances namingService.selectInstances(serviceName, true); if (instances null || instances.isEmpty()) { throw new RuntimeException(沒(méi)有可用Agent實(shí)例: serviceName); } // 2. 簡(jiǎn)單的隨機(jī)選擇生產(chǎn)環(huán)境可以換成帶權(quán)負(fù)載 Instance instance instances.get(ThreadLocalRandom.current().nextInt(instances.size())); String url http:// instance.getIp() : instance.getPort() /a2b; // 3. 構(gòu)造 A2A 請(qǐng)求體 MapString, Object body new HashMap(); body.put(taskId, UUID.randomUUID().toString()); body.put(localAgentId, user-service); body.put(remoteAgentId, serviceName); body.put(message, Map.of( role, user, content, messageText )); // 4. 發(fā)起 JSON-RPC 調(diào)用 RestTemplate restTemplate new RestTemplate(); ResponseEntityMap resp restTemplate.postForEntity(url /message/send, body, Map.class); return resp.getBody().toString(); } }這段代碼最核心的價(jià)值不在邏輯而在于它揭示了 Agent 協(xié)作和普通 RPC 調(diào)用的關(guān)系。A2A 調(diào)用本質(zhì)上就是一次 HTTP 請(qǐng)求不要把它想得過(guò)于魔幻。只是它的請(qǐng)求體和響應(yīng)體要符合協(xié)議規(guī)范任務(wù)狀態(tài)要按協(xié)議狀態(tài)機(jī)走。4.4 運(yùn)行驗(yàn)證與調(diào)用鏈分析接線完成后別急著上線先把整條鏈路的日志打全。我習(xí)慣在三個(gè)位置打日志Nacos 注冊(cè)成功、收到入站消息、發(fā)送出站消息。每個(gè)日志都要帶上taskId和serviceName這樣出問(wèn)題的時(shí)候能通過(guò)同一個(gè) taskId 把一條調(diào)用鏈串起來(lái)。一個(gè)簡(jiǎn)單的壓測(cè)驗(yàn)證# 先看 AgentCard 是否可訪問(wèn) curl http://localhost:8080/a2b/.well-known/agent.json # 再發(fā)一條消息 curl -X POST http://localhost:8080/a2b/message/send \ -H Content-Type: application/json \ -d {taskId:demo-test-001,localAgentId:caller,remoteAgentId:order-after-sale-agent,message:{role:user,content:查詢(xún)訂單 OG123456}}如果返回的內(nèi)容里包含正常的訂單狀態(tài)信息且 Nacos 控制臺(tái)上能看到order-after-sale-agent的實(shí)例處于健康狀態(tài)那么這條接線就基本跑通了。5. 踩坑實(shí)錄接線過(guò)程中最折磨人的五個(gè)問(wèn)題5.1 AgentCard 404 或地址錯(cuò)亂癥狀調(diào)用方在 Nacos 中拿到了 Agent 的實(shí)例地址但訪問(wèn)/.well-known/agent.json時(shí)返回 404或者拿到的是一個(gè)過(guò)期環(huán)境的地址。根因我把 AgentCard 的url字段和 Nacos 元數(shù)據(jù)里的uri字段都配成了內(nèi)網(wǎng)地址而調(diào)用方在外網(wǎng)環(huán)境。內(nèi)網(wǎng)地址它當(dāng)然訪問(wèn)不到。處理方式統(tǒng)一用一個(gè)網(wǎng)關(guān)域名作為 Agent 的對(duì)外地址。比如如果你的 Agent 服務(wù)在網(wǎng)關(guān)后面AgentCard 的url就配成網(wǎng)關(guān)對(duì)外的域名加路徑Nacos 注冊(cè)的 ip/port 則保留服務(wù)實(shí)例實(shí)際的內(nèi)網(wǎng)地址。兩者各司其職Nacos 里的地址用于 VPC 內(nèi)服務(wù)發(fā)現(xiàn)AgentCard 里的 url 用于跨網(wǎng)或跨域標(biāo)識(shí)。5.2 Nacos 心跳丟失服務(wù)列表時(shí)有時(shí)無(wú)癥狀服務(wù)啟動(dòng)后Nacos 控制臺(tái)能看到實(shí)例但過(guò)幾分鐘就變成了不健康再過(guò)一會(huì)兒直接消失然后又重新出現(xiàn)。根因這個(gè)坑大概率出在端口配置和網(wǎng)絡(luò)隔離上。Nacos 2.x 的 gRPC 端口是主端口加 1000 偏移8848 對(duì)應(yīng) 9848如果防火墻只開(kāi)了 8848 而沒(méi)開(kāi) 9848心跳上報(bào)會(huì)間歇性失敗。處理方式把 8848 和 9848 都放通確認(rèn)服務(wù)器安全組規(guī)則別漏。還有一個(gè)容易忽略的點(diǎn)Agent 服務(wù)所在的容器如果做了端口映射spring.cloud.nacos.discovery.ip和port要顯式配置成宿主機(jī)可達(dá)的地址否則注冊(cè)的是容器內(nèi)網(wǎng) IP別的服務(wù)根本路由不過(guò)去。5.3 Message 序列化不一致中文亂碼或結(jié)構(gòu)丟失癥狀調(diào)用方發(fā)了個(gè)結(jié)構(gòu)化的消息對(duì)端 Agent 收到的content是奇怪的亂碼或者嵌套的 Map 結(jié)構(gòu)丟失了。根因兩邊用的 HTTP 客戶端/服務(wù)端對(duì) JSON 編解碼的默認(rèn)行為不一致。比如 Jackson 的默認(rèn)配置在某些版本會(huì)對(duì)未知字段報(bào)錯(cuò)或者對(duì)MapString, Object的 value 類(lèi)型推斷錯(cuò)誤。處理方式給 HTTP 調(diào)用統(tǒng)一設(shè)置produces和consumes為application/json;charsetUTF-8同時(shí)避免在 Message 里塞過(guò)于復(fù)雜的嵌套泛型。A2A 協(xié)議的 Message 本質(zhì)是 Part 列表盡量用扁平結(jié)構(gòu)如content字符串 metadata簡(jiǎn)單鍵值對(duì)復(fù)雜對(duì)象放到artifact里用文件或內(nèi)容地址引用。5.4 Task 狀態(tài)機(jī)沒(méi)同步好癥狀調(diào)用方發(fā)出任務(wù)后一直輪詢(xún)task/get返回的狀態(tài)始終是working但 Agent 那邊其實(shí)已經(jīng)處理完了。根因?qū)崿F(xiàn) A2A 服務(wù)端時(shí)Task 狀態(tài)沒(méi)有同步更新。處理方式任務(wù)處理結(jié)束后不要只返回業(yè)務(wù)結(jié)果一定要把 Task 狀態(tài)顯式置為completed或failed。這尤其容易發(fā)生在異步處理場(chǎng)景里。如果是異步任務(wù)建議單獨(dú)維護(hù)一個(gè) Task 存儲(chǔ)處理線程完成后更新?tīng)顟B(tài)再開(kāi)放查詢(xún)接口。不要試圖用線程內(nèi)的局部變量去驅(qū)動(dòng)狀態(tài)查詢(xún)跨請(qǐng)求的臨時(shí)狀態(tài)丟失是埋雷高發(fā)區(qū)。5.5 安全與頻控癥狀A(yù)gent 端點(diǎn)暴露到公網(wǎng)后被掃描器刷了一波請(qǐng)求Nacos 服務(wù)列表里出現(xiàn)一堆陌生的服務(wù)名。處理方式A2A 端點(diǎn)不要裸奔。至少加一層簡(jiǎn)單的鑒權(quán)邏輯比如Authorization頭校驗(yàn)或agent-id白名單。同時(shí)配合 Nacos 的鑒權(quán)能力控制注冊(cè)權(quán)限。頻控方面可以引入 sentinel 結(jié)合 Nacos 做限流配置動(dòng)態(tài)下發(fā)這個(gè)我在后面的擴(kuò)展章節(jié)細(xì)說(shuō)。6. 互通層的延伸配置中心、動(dòng)態(tài)刷新與多環(huán)境路由6.1 Nacos 配置中心承載 Agent 配置的動(dòng)態(tài)更新Agent 的很多狀態(tài)性配置比如系統(tǒng)提示詞、工具啟停開(kāi)關(guān)、模型參數(shù)都適合放在 Nacos 配置中心里做動(dòng)態(tài)下發(fā)。典型場(chǎng)景某個(gè) Agent 的系統(tǒng)提示詞寫(xiě)得不夠好想調(diào)整策略不用重新發(fā)布服務(wù)直接在 Nacos 里改配置Agent 通過(guò)監(jiān)聽(tīng)配置變更自動(dòng)加載新提示詞。實(shí)現(xiàn)思路是引入 Nacos 配置監(jiān)聽(tīng)器。AgentScope Java 的 Agent 對(duì)象在運(yùn)行時(shí)讀取配置我們需要把配置讀取這一步做成可以動(dòng)態(tài)刷新的模式。Component public class DynamicPromptUpdater { private final AgentBase agent; private final ConfigService configService; public DynamicPromptUpdater(AgentBase agent, ConfigService configService) { this.agent agent; this.configService configService; } PostConstruct public void registerListener() throws NacosException { configService.addListener(agent-prompt, AGENT_GROUP, new Listener() { Override public Executor getExecutor() { return Executors.newSingleThreadExecutor(); } Override public void receiveConfigInfo(String configInfo) { // 動(dòng)態(tài)更新Agent的系統(tǒng)提示詞 agent.updateSystemPrompt(configInfo); } }); } }需要注意的是動(dòng)態(tài)更新雖然方便但對(duì) Agent 這種會(huì)話狀態(tài)敏感的組件要謹(jǐn)慎。如果一個(gè)長(zhǎng)任務(wù)正在執(zhí)行中你在中途改了系統(tǒng)提示詞可能導(dǎo)致任務(wù)行為不一致。我的建議是只對(duì)非關(guān)鍵狀態(tài)做熱更新比如模型溫度、超時(shí)時(shí)間、工具開(kāi)關(guān)而系統(tǒng)提示詞盡量走灰度發(fā)布。6.2 多環(huán)境路由與灰度發(fā)布前面提到 metadata 里的 version 字段配上 Nacos 后可以做很靈活的路由。比如你有 5 個(gè)訂單查詢(xún) Agent 實(shí)例其中 1 個(gè)是 v2.0 新版本你想讓 10% 請(qǐng)求打到新版其他打到 v1.0。Nacos 支持設(shè)置實(shí)例權(quán)重按權(quán)重分配流量。spring: cloud: nacos: discovery: metadata: version: v2.0 weight: 1另一個(gè)思路是根據(jù) Agent 卡片的 capabilities 做路由。例如某實(shí)例聲明自己支持多模態(tài)調(diào)用方拿到實(shí)例列表后先按capabilities過(guò)濾只保留帶multiModal的實(shí)例再做二次分發(fā)。這種先過(guò)濾再選擇的模式比單純隨機(jī)選擇能顯著降低無(wú)效調(diào)用。6.3 限流配置的下發(fā)與熔斷最后提一下限流。Agent 端點(diǎn)暴露后最怕的不是業(yè)務(wù)問(wèn)題而是被大量無(wú)效請(qǐng)求打掛。我在前面的項(xiàng)目里嘗試過(guò)把 sentinel 和 Nacos 結(jié)合起來(lái)限流規(guī)則統(tǒng)一存在 Nacos 配置中心sentinel 客戶端監(jiān)聽(tīng)配置變更動(dòng)態(tài)調(diào)整每個(gè) Agent 端點(diǎn)的 QPS 閾值。這個(gè)組合解決了一個(gè)很實(shí)際的痛點(diǎn)你的 Agent 是供多個(gè)業(yè)務(wù)方調(diào)用的每個(gè)業(yè)務(wù)方的優(yōu)先級(jí)不同。給內(nèi)部核心業(yè)務(wù)配額高一些給外部試用的配額低一些這個(gè)配額比不是一錘定音而是能通過(guò) Nacos 實(shí)時(shí)調(diào)整。限流規(guī)則有一個(gè)routeId的概念可以按調(diào)用方維度做精細(xì)化控制。在寫(xiě)這套配置的時(shí)候啟動(dòng)時(shí)的限流規(guī)則不要寫(xiě)在代碼里直接寫(xiě)在 Nacos 上否則改一個(gè)閾值又得重新部署。我自己遇到過(guò)的坑是限流規(guī)則沒(méi)放在 Nacos 里結(jié)果壓測(cè)時(shí) QPS 超了只能臨時(shí)改代碼重新上線耽誤了半小時(shí)。從那以后我所有的流控規(guī)則都走配置中心。7. 寫(xiě)在最后互通層的核心理念做了這么多期的實(shí)戰(zhàn)走到互通層這一期我心里最有感觸的一點(diǎn)是不要讓 Agent 之間的協(xié)作像人與人之間的私聊而要讓它們像服務(wù)與服務(wù)之間的 API 調(diào)用。私聊沒(méi)有標(biāo)準(zhǔn)、沒(méi)有契約、沒(méi)有狀態(tài)回溯而 A2A Nacos 的組合恰恰把這些補(bǔ)上了。如果你也正在做多 Agent 系統(tǒng)我的建議是先把 AgentCard 寫(xiě)好寫(xiě)清楚邊界再把 Nacos 的 metadata 設(shè)計(jì)好別浪費(fèi)這個(gè)免費(fèi)的能力透?jìng)魅肟谧詈蟀讶蝿?wù)狀態(tài)機(jī)跑順日志打全。這三件事做到位互通層的基本盤(pán)就穩(wěn)了。