打通智能体孤岛:用 AgentRun 构建生产级 A2A 多 Agent 管理协作系统
作者丛霄当我们把一个复杂业务拆解成多个专职 Agent 时随之而来的问题是这些 Agent 怎么知道彼此的存在怎么找到对方、理解对方的能力、发起调用A2AAgent-to-Agent协议给出了标准答案。它由 Google 主导提出通过AgentCard让每个智能体对外自描述能力与接入方式通过服务发现让调用方动态感知可用 Agent 的全貌。然而协议只定义了规范生产落地还需要一套完整的管理体系——注册、隔离、权限控制、多环境管理缺一不可。AgentRun 在 A2A 协议之上构建了这套体系。本文从原理出发以「希希咖啡厅」多 Agent 系统为例完整演示如何在 AgentRun 中完成从工作空间搭建、发现端点配置到用 Go SDK 拉取 AgentCard、与 Agent 完成一轮对话的全链路流程。A2A 协议原理AgentCard智能体的自我介绍A2A 协议中每个智能体都通过一份AgentCard对外声明自己的身份和能力。AgentCard 是一份标准 JSON 文档描述了是谁Agent 的名称、描述、版本、提供方能做什么技能列表Skills每个技能有 ID、名称、描述和示例问法怎么访问服务地址URL、支持的传输协议JSON-RPC / gRPC有什么限制认证方式OAuth2、API Key 等、是否支持流式响应。按照 A2A 标准AgentCard 默认托管在/.well-known/agent-card.json路径下客户端只需知道 Agent 的 Base URL就能拿到这份自描述文档进而决定如何与它通信。一个典型的 AgentCard 结构如下{ name: coffee_agent, description: 希希咖啡店智能服务可以帮助点咖啡和查询订单, url: https://agent.example.com/agent, version: 0.0.1, capabilities: { streaming: true, pushNotifications: true, stateTransitions: [submitted, working, completed, failed] }, skills: [ { id: coffee_agent-get_products, name: 获取商品列表, description: 获取当前可点的咖啡饮品列表, examples: [有什么咖啡推荐, 看看菜单] }, { id: coffee_agent-create_order, name: 创建订单, description: 帮用户下单点咖啡, examples: [我要一杯拿铁, 下单两杯美式] } ] }服务发现谁在这个网络里有了 AgentCard 规范还缺一个关键问题的答案我怎么知道有哪些 Agent 可以调用A2A 协议本身没有定义中心化的注册表实际项目中通常需要一个「发现层」来管理 Agent 的注册和查询。发现层的职责是接受一个查询如「给我这个环境里所有可用的 Agent」返回每个 Agent 的 AgentCard URL调用方再逐一拉取 AgentCard 完成能力感知。通信机制JSON-RPC 2.0 Task 模型A2A 协议的核心是JSON-RPC 2.0消息格式配合Task任务模型来实现 Agent 间的通信。消息格式A2A 采用标准的 JSON-RPC 2.0 请求/响应模式// 请求 { jsonrpc: 2.0, id: 1, method: tasks/sendMessage, params: { message: { role: user, parts: [{ type: text, text: 我要一杯拿铁 }] }, taskId: null } } // 响应 { jsonrpc: 2.0, id: 1, result: { id: task-123, status: { state: completed }, messages: [...], artifacts: [...] } }核心方法包括tasks/sendMessage发送消息创建新 Task 或继续已有 Tasktasks/get查询 Task 状态tasks/cancel取消执行中的 Task。Task 生命周期每个与 Agent 的交互都建模为一个TaskTask 有明确的状态流转submitted → working → completed ↓ ↓ failed canceledsubmitted任务已提交等待处理workingAgent 正在处理可能通过messageDelta事件推送中间结果completed任务完成返回最终结果failed执行失败canceled任务被取消。流式响应Server-Sent EventsA2A 支持通过 Server-Sent EventsSSE实现流式输出。当 AgentCard 中capabilities.streaming: true时客户端可以订阅任务的事件流// SSE 事件流 event: messageDelta data: {messageId: msg-1, parts: [{type: text, text: 正在为您}]} event: messageDelta data: {messageId: msg-1, parts: [{type: text, text: 下单...}]} event: taskStatusUpdate data: {taskId: task-123, status: {state: completed}}这种模式特别适合长文本生成、多步骤推理等耗时操作用户无需等待完整响应即可看到中间进展。推拉模式主动通知除了客户端主动拉取pollingA2A 还支持服务端主动推送。当capabilities.pushNotifications: true时Agent 可以在任务状态变化时主动通知客户端// 客户端在初始请求中携带 pushNotification URL { params: { message: {...}, pushNotification: { url: https://client.example.com/callback, token: optional-auth-token } } }这样Agent 处理完任务后可以直接回调客户端无需客户端轮询。技能调用Skills 机制AgentCard 中的skills数组定义了 Agent 可以执行的原子能力。每个 Skill 包含id唯一标识符name人类可读名称description功能描述examples示例问法帮助 LLM 理解何时应该调用这个 Skill。调用方在拿到 AgentCard 后可以将 Skills 告诉主控 AgentOrchestrator让它根据用户意图选择合适的 Skill在tasks/sendMessage时通过skillId参数直接指定调用某个 Skill简单场景下让 Agent 自行判断使用哪个 Skill依赖 LLM 的推理能力。AgentRun 的多 Agent 管理注册、发现与隔离AgentRun 在 A2A 协议基础上提供了一套生产级的多 Agent 管理体系核心围绕三个概念工作空间Workspace逻辑隔离的 Agent 集合工作空间是 AgentRun 中组织 Agent 的基本单位类似于一个「项目空间」或「命名空间」。不同业务域、不同团队的 Agent 可以分属不同工作空间互相隔离权限独立管理。一个 Agent Runtime 归属于一个工作空间后工作空间就成为它对外可被发现的范围边界。发现端点Discovery Endpoint按环境隔离的发现入口一个工作空间内可以配置多个发现端点典型用法是按部署环境区分每个发现端点维护了一张映射表记录「哪个 Agent」对应「哪个访问地址」。同一个 Agent 在不同端点中可以配置不同的地址例如开发地址 vs 自定义域名。平台托管 vs 外部 Agent统一的发现体验AgentRun 支持两类 Agent 共存于同一工作空间类型部署方式注册方式状态流转平台托管 AgentAgentRun 负责部署到运行时通过创建注册CREATING → READY外部 Agent自行部署在任意位置手动注册到指定空间直接 READY两类 Agent 在发现端点中的表现完全一致——调用方拿到的都是标准a2aAgentCardUrl无需关心 Agent 实际部署在哪里。凭证安全保护谁可以发现这些 Agent服务发现本身就是敏感信息——暴露工作空间内有哪些 Agent、它们在哪里可能为攻击者提供侦察入口。AgentRun 在发现端点上内置了完整的凭证验证体系支持多种行业标准的认证方式可按场景灵活选择。支持的凭证类型API Key最轻量的接入方式适合服务端对服务端的调用场景。平台颁发一个 Key调用方在请求头中携带即可。# 使用自定义 Header推荐更语义化 curl -H X-API-Key: your-api-key \ https://.../workspaces/workspace-name/discovery/agents?discoveryEndpointNamedefault # 或使用标准 Authorization Bearer 方式 curl -H Authorization: Bearer your-api-key \ https://.../workspaces/workspace-name/discovery/agents?discoveryEndpointNamedefaultHeader 名称如X-API-Key和 prefix如Bearer均可在凭证配置中自定义。此外Key 也可以通过 URL 查询参数?Authorizationkey传递方便在不便设置请求头的环境中使用。HTTP Basic Auth兼容标准 HTTP Basic 认证适合对接遗留系统或工具链中不便使用 Token 的场景。curl -u username:password \ https://.../workspaces/my-workspace/discovery/agents?discoveryEndpointNamedefault凭证与工作空间的绑定关系凭证配置与工作空间解耦——你可以在平台统一管理凭证然后将某个凭证绑定到指定工作空间的服务发现配置上。绑定之后未携带有效凭证的请求会被拒绝401 / 403更换凭证时只需在平台重新绑定无需修改任何 Agent 的代码不同工作空间可以绑定不同凭证实现精细化的访问控制。实战体验准备工作创建工作空间并注册 Agent第一步部署模版多 Agent 系统本文以「希希咖啡厅」多 Agent 系统作为演示对象。在 AgentRun 控制台的Agent 模版页面找到该模版点击一键部署平台会自动创建并启动以下两个 Agentcoffee_agent希希咖啡店智能服务负责点单、查看菜单、查询订单delivery_agent送了么配送助手负责安排配送和查询配送状态。等待两个 Agent 状态变为Ready后进行下一步。第二步创建工作空间在 AgentRun 控制台新建一个工作空间Workspace。工作空间是组织和隔离 Agent 的基本单元后续的服务发现都以工作空间为范围。第三步将 Agent 注册到工作空间有两种方式将 Agent 纳入工作空间方式一创建新 Agent 时指定工作空间在创建 Agent Runtime 时填写workspaceId字段Agent 会直接归属到该工作空间。方式二将已有 Agent 移入工作空间在 Agent 详情页或工作空间管理页面将已有的 Agent Runtime 迁移到目标工作空间。外部 Agent 也可接入如果你有运行在 AgentRun 平台外部的 A2A 兼容服务自建服务、第三方 Agent同样可以注册进来。注册时勾选「外部 Agent」选项并填写对外服务地址即可平台不会尝试部署状态立即变为 Ready后续发现和调用方式与平台托管 Agent 完全一致。配置发现端点Agent 注册好之后还需要在工作空间中配置「发现端点」才能让外部调用方通过统一入口发现这些 Agent。在工作空间管理页面进入「服务发现」或「Discovery」配置添加一个发现端点例如命名为default然后将工作空间内的 Agent 逐一配置进来填写每个 Agent 的访问地址。配置访问凭证为了防止发现端点被未授权访问可以为工作空间绑定一个 API Key 凭证。配置后所有对该工作空间发现端点的请求都需要在请求头中携带 X-API-Key。在工作空间设置中选择或创建一个凭证将其关联到该工作空间的服务发现配置上。调用发现端点配置完成后可以用下面的方式验证发现端点是否正常工作curl -s \ -H X-API-Key: your-api-key \ https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/workspaces/workspace-name/discovery/agents?discoveryEndpointNamedefault响应示例{ code: SUCCESS, data: { items: [ { agentRuntimeName: coffee-agent, protocolConfiguration: { protocolSettings: [ { name: A2A, a2aAgentCardUrl: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/coffee-agent/endpoints/Default/invocations/.well-known/agent-card.json } ] } } ], pageNumber: 1, pageSize: 1, total: 1 } }每个 Agent 的a2aAgentCardUrl就是接下来用 A2A SDK 发起连接的入口。基于 a2a-go SDK 的发现 Demo以下是一个完整的 Go Demo展示如何从发现端点解析出 AgentCard URL再使用 a2a-go SDK https://github.com/a2aproject/a2a-go 获取 AgentCard 并与 Agent 通信。完整代码位于examples/a2a-discovery-demo/。package main import ( context encoding/json flag fmt io log net/http os github.com/a2aproject/a2a-go/a2a github.com/a2aproject/a2a-go/a2aclient github.com/a2aproject/a2a-go/a2aclient/agentcard ) // DiscoveryResponse 是 AgentRun 发现端点的响应结构 // 实际响应格式{code:SUCCESS,data:{items:[...]}} type DiscoveryResponse struct { Code string json:code Data *DiscoveryData json:data,omitempty AgentRuntimes []AgentRuntime json:agentRuntimes,omitempty // 兼容旧版格式 } type DiscoveryData struct { Items []AgentRuntime json:items PageNumber int json:pageNumber PageSize int json:pageSize Total int json:total } type AgentRuntime struct { AgentRuntimeName string json:agentRuntimeName ProtocolConfiguration *ProtocolConfiguration json:protocolConfiguration,omitempty } type ProtocolConfiguration struct { ProtocolSettings []ProtocolSetting json:protocolSettings } type ProtocolSetting struct { Name string json:name A2AAgentCardURL string json:a2aAgentCardUrl,omitempty } // Config 保存运行时的所有端点配置 type Config struct { DiscoveryBaseURL string // AgentRun 平台基础地址格式https://uid.agentrun-data.cn-hangzhou.aliyuncs.com WorkspaceID string // 工作空间名称 DiscoveryEndpointName string // 发现端点名称通常为 default APIKey string // API Key 凭证 } func (c *Config) discoveryURL() string { return fmt.Sprintf( %s/workspaces/%s/discovery/agents?discoveryEndpointName%s, c.DiscoveryBaseURL, c.WorkspaceID, c.DiscoveryEndpointName, ) } func main() { var cfg Config flag.StringVar(cfg.DiscoveryBaseURL, base-url, envOrDefault(AGENTRUN_BASE_URL, ), AgentRun 平台基础地址) flag.StringVar(cfg.WorkspaceID, workspace-id, envOrDefault(AGENTRUN_WORKSPACE_ID, ), 工作空间名称) flag.StringVar(cfg.DiscoveryEndpointName, endpoint-name, envOrDefault(AGENTRUN_ENDPOINT_NAME, default), 发现端点名称) flag.StringVar(cfg.APIKey, api-key, envOrDefault(AGENTRUN_API_KEY, ), API Key 凭证) flag.Parse() ctx : context.Background() // ── 第一步请求发现端点获取 Agent 列表 ───────────────────────────── req, _ : http.NewRequestWithContext(ctx, http.MethodGet, cfg.discoveryURL(), nil) req.Header.Set(X-API-Key, cfg.APIKey) resp, err : http.DefaultClient.Do(req) if err ! nil { log.Fatalf(请求发现端点失败: %v, err) } defer resp.Body.Close() body, _ : io.ReadAll(resp.Body) var discovery DiscoveryResponse if err : json.Unmarshal(body, discovery); err ! nil { log.Fatalf(解析发现响应失败: %v, err) } // ── 第二步从响应中提取 A2AAgentCardURL ───────────────────────────── // 响应嵌套在 data.items 中旧版在顶层 agentRuntimes runtimes : discovery.AgentRuntimes if discovery.Data ! nil len(discovery.Data.Items) 0 { runtimes discovery.Data.Items } var firstCardURL string for _, runtime : range runtimes { if runtime.ProtocolConfiguration nil { continue } for _, ps : range runtime.ProtocolConfiguration.ProtocolSettings { if ps.Name A2A ps.A2AAgentCardURL ! { fmt.Printf(发现 Agent: %s\n AgentCard URL: %s\n\n, runtime.AgentRuntimeName, ps.A2AAgentCardURL) if firstCardURL { firstCardURL ps.A2AAgentCardURL } } } } if firstCardURL { log.Fatal(未发现任何 A2A Agent) } // ── 第三步用 agentcard.Resolver 拉取 AgentCard ────────────────────── // A2AAgentCardURL 已是完整 URLWithPath() 跳过默认路径拼接 card, err : agentcard.DefaultResolver.Resolve(ctx, firstCardURL, agentcard.WithPath()) if err ! nil { log.Fatalf(获取 AgentCard 失败: %v, err) } fmt.Printf(AgentCard 信息:\n) fmt.Printf( 名称: %s\n, card.Name) fmt.Printf( 描述: %s\n, card.Description) fmt.Printf( 版本: %s\n, card.Version) fmt.Printf( 服务地址: %s\n, card.URL) fmt.Printf( 支持流式: %v\n, card.Capabilities.Streaming) for _, skill : range card.Skills { fmt.Printf( - [%s] %s: %s\n, skill.ID, skill.Name, skill.Description) } // ── 第四步建立 A2A 客户端向 Agent 发送消息 ─────────────────────── // NewFromCard 根据 AgentCard 声明的 preferredTransport 自动选择传输协议 client, err : a2aclient.NewFromCard(ctx, card) if err ! nil { log.Fatalf(创建 A2A 客户端失败: %v, err) } defer client.Destroy() msg : a2a.NewMessage(a2a.MessageRoleUser, a2a.TextPart{Text: 你好请介绍一下你能做什么}) result, err : client.SendMessage(ctx, a2a.MessageSendParams{Message: msg}) if err ! nil { log.Fatalf(发送消息失败: %v, err) } // SendMessageResult 是 interface实际类型为 *a2a.Task 或 *a2a.Message switch r : result.(type) { case *a2a.Task: fmt.Printf(\nAgent 回复 (Task, 状态: %s):\n, r.Status.State) for _, artifact : range r.Artifacts { for _, part : range artifact.Parts { if tp, ok : part.(a2a.TextPart); ok { fmt.Printf( %s\n, tp.Text) } } } case *a2a.Message: fmt.Printf(\nAgent 回复 (Message):\n) for _, part : range r.Parts { if tp, ok : part.(a2a.TextPart); ok { fmt.Printf( %s\n, tp.Text) } } } } func envOrDefault(key, defaultVal string) string { if v : os.Getenv(key); v ! { return v } return defaultVal }运行方式通过 run_demo.sh 脚本运行所有端点参数通过环境变量传入# run_demo.sh #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR$(cd $(dirname ${BASH_SOURCE[0]}) pwd) # ── 端点配置可通过环境变量覆盖 ───────────────────────────────────────── BASE_URL${AGENTRUN_BASE_URL:?请设置 AGENTRUN_BASE_URL例如 https://uid.agentrun-data.cn-hangzhou.aliyuncs.com} WORKSPACE_ID${AGENTRUN_WORKSPACE_ID:?请设置 AGENTRUN_WORKSPACE_ID即工作空间名称} ENDPOINT_NAME${AGENTRUN_ENDPOINT_NAME:-default} API_KEY${AGENTRUN_API_KEY:?请设置 AGENTRUN_API_KEY} cd $SCRIPT_DIR echo ▶ 运行 A2A Discovery Demo echo base-url: $BASE_URL echo workspace-id: $WORKSPACE_ID echo endpoint-name: $ENDPOINT_NAME echo go run main.go \ --base-url $BASE_URL \ --workspace-id $WORKSPACE_ID \ --endpoint-name $ENDPOINT_NAME \ --api-key $API_KEY执行命令exportAGENTRUN_BASE_URLhttps://uid.agentrun-data.cn-hangzhou.aliyuncs.com exportAGENTRUN_WORKSPACE_IDworkspace-name exportAGENTRUN_API_KEYyour-api-key ./examples/a2a-discovery-demo/run_demo.sh执行结果▶ 运行 A2A Discovery Demo base-url: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com workspace-id: workspace-name endpoint-name: default A2A Discovery Demo 发现端点: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/workspaces/workspace-name/discovery/agents?discoveryEndpointNamedefault 发现 2 个 A2A Agent: - buy-me-a-coffeev-workspace-name_coffee_agent AgentCard URL: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-workspace-name_coffee_agent/endpoints/Default/invocations/.well-known/agent-card.json - buy-me-a-coffeev-workspace-name_delivery_agent AgentCard URL: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-workspace-name_delivery_agent/endpoints/Default/invocations/.well-known/agent-card.json 正在获取 AgentCard: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-workspace-name_coffee_agent/endpoints/Default/invocations/.well-known/agent-card.json AgentCard 信息: 名称: coffee_agent 描述: 希希咖啡店智能服务可以帮助点咖啡和查询订单 版本: 0.0.1 服务地址: https://uid.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-workspace-name_coffee_agent/endpoints/Default/invocations 支持流式: false 技能列表: - [coffee_agent] model: 希希咖啡店智能服务可以帮助点咖啡和查询订单 - [coffee_agent-get_products_api_coffee_products_get] get_products_api_coffee_products_get: 获取商品列表 - [coffee_agent-create_order_api_coffee_orders_post] create_order_api_coffee_orders_post: 创建订单 - [coffee_agent-get_order_api_coffee_orders__order_id__get] get_order_api_coffee_orders__order_id__get: 获取订单详情 ...共 9 个技能 发送消息: 你好请介绍一下你能做什么 Agent 回复 (Task, 状态: completed): 你好我是希希咖啡的智能服务助手可以帮你做以下几件事情哦 - 点咖啡为你推荐饮品、帮助下单。 - 查询订单查看你的订单状态或获取最近的订单信息。 - 提供门店基础信息比如地址、营业时间等。 有什么我可以帮到你的吗☕️总结本文完整走通了 AgentRun A2A 通信链路的多个环节。A2A 的价值不止于此次演示的单轮对话。一旦你的 Agent 网络通过服务发现互相可见Orchestrator Agent 就可以在运行时动态拼装调用链将复杂任务分发给最合适的专职 Agent——这正是多智能体协作的核心模式。AgentRun 的工作空间与发现端点机制让这套模式在生产环境中真正可管理、可扩展、可审计。本功能目前邀测阶段欢迎加入“函数计算 AgentRun 客户群”钉群号134570017218联系试用。立即体验函数计算 AgentRun函数计算 AgentRun 的无代码到高代码演进能力现已开放体验查看更多产品详情https://www.aliyun.com/product/fc/agentrun快速创建访问控制台https://functionai.console.aliyun.com/cn-hangzhou/agent/explore60 秒创建你的第一个 Agent深度定制当需要更复杂功能时一键转换为高代码持续演进利用函数计算 AgentRun 的基础设施能力持续优化你的 Agent快速了解函数计算 AgentRun一句话介绍函数计算 AgentRun 是一个以高代码为核心的一站式 Agentic AI 基础设施平台。秉持生态开放和灵活组装的理念为企业级 Agent 应用提供从开发、部署到运维的全生命周期管理。函数计算 AgentRun 架构图AgentRun 运行时基于阿里云函数计算 FC 构建继承了 Serverless 计算极致弹性、按量付费、零运维的核心优势。通过深度集成 AgentScope、LangChain、RAGFlow、Mem0 等主流开源生态。函数计算 AgentRun 将 Serverless 的极致弹性、零运维和按量付费的特性与 AI 原生应用场景深度融合助力企业实现成本与效率的极致优化平均 TCO 降低 60%。让开发者只需专注于 Agent 的业务逻辑创新无需关心底层基础设施让 Agentic AI 真正进入企业生产环境。参考链接[1] A2A Protocol Specificationhttps://browser.alibaba-inc.com/?Urlhttps%3A%2F%2Fa2a-protocol.org%2Flatest%2Fspecification%2F[2] a2a-go SDKhttps://github.com/a2aproject/a2a-go
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2420087.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!