智能体互联网协议ACPs实战:从零搭建一个餐厅推荐协作系统

想象一下,你身处一个陌生的城市,夜幕降临,饥肠辘辘,只想找一家合口味的餐厅。过去,你可能会打开几个App,在点评、地图和预订软件间来回切换,手动比较信息。但现在,你只需对你的个人智能助理说一句:“帮我找一家晚上7点、适合商务宴请、有安静包间的本地特色餐厅,并安排好车。”剩下的,就交给一个由多个智能体无缝协作构成的网络去完成。这背后,正是智能体互联网协议(ACPs) 在发挥作用。

对于开发者而言,ACPs并非遥不可及的学术概念,而是一套可以落地的工程蓝图。它定义了智能体如何像互联网中的计算机一样,实现标准化注册、发现与交互。本文将带你从零开始,亲手搭建一个基于ACPs核心协议(ARP、ADP、AIP)的餐厅推荐协作系统。我们将绕过抽象的理论,直接进入代码和配置的实战环节,剖析每个协议在具体场景下的实现细节与设计考量。无论你是对多智能体系统感兴趣的工程师,还是希望构建下一代智能应用的架构师,这里都有你需要的“脚手架”和“设计图”。

1. 系统架构与核心组件设计

在动手写第一行代码之前,我们必须清晰地勾勒出整个系统的轮廓。一个基于ACPs的餐厅推荐协作系统,其核心在于将“找餐厅”这个复杂任务,分解为由多个专业化智能体协同完成的流程。我们的系统不依赖于任何单一的、庞大的“全能”模型,而是通过协议将多个轻量级、高专精度的智能体连接起来。

整个系统的架构可以划分为三个逻辑层:服务治理层智能体协作层工具资源层

  • 服务治理层:这是系统的“目录”和“调度中心”,对应ACPs中的ARP与ADP协议。主要包括能力注册服务器和能力发现服务器。前者负责登记每个智能体的“身份证”和“技能清单”;后者则像搜索引擎,能根据任务需求,快速找到具备相应技能的智能体。
  • 智能体协作层:这是系统的“执行大脑”,对应AIP协议。核心是一个个人助理智能体(Personal Agent),它负责接收用户原始需求,进行任务分解,并作为协调者,组织其他智能体组成临时任务小组(Task Group)进行协作。
  • 工具资源层:为智能体提供“武器”和“弹药”,对应ATP协议。包括访问第三方餐厅数据库API、地图路径规划API、在线预订接口等工具。

它们之间的协作关系,可以通过下表来清晰界定:

组件名称对应ACPs协议核心职责关键技术考量
能力注册服务器ARP接收并存储智能体的能力描述元数据;管理智能体生命周期(注册、更新、注销)。元数据Schema设计、数据一致性、高可用集群。
能力发现服务器ADP提供智能体能力查询接口;根据自然语言或结构化查询,匹配最合适的智能体列表。语义理解与匹配算法、查询性能优化、结果排序。
个人助理智能体AIP终端用户接口;任务理解与分解;协作流程编排与监控。意图识别、任务规划、状态管理与错误恢复。
专业化智能体AIP执行具体子任务,如查询、推荐、预订、规划。领域专精模型、工具调用可靠性、结果格式化。
工具网关ATP统一封装和管理外部工具与API,提供标准化调用接口。API聚合、鉴权管理、限流与熔断。

提示:在微服务架构中,每个智能体都可以被视作一个独立的、自治的服务。ACPs协议的本质,就是为这些异构的、可能由不同团队甚至不同公司开发的服务,制定一套通用的“社交礼仪”和“通信语言”,让它们能够互相理解和协作。

明确了架构,我们就可以开始搭建最基础的部分——让智能体能够“自我介绍”并被“找到”。

2. 实现智能体注册协议(ARP):让智能体“持证上岗”

ARP协议的核心是解决“你是谁?你能做什么?”的问题。我们首先需要定义一种机器可读、语义丰富的智能体能力描述语言。这里我们不发明新语言,而是基于广泛采用的JSON-LD(Linked Data)格式进行扩展,使其包含ACPs所需的特定字段。

2.1 定义智能体能力描述Schema

一个智能体在注册时,需要提交一份包含身份、能力和元信息的描述文件。下面是一个餐厅查询智能体的注册描述示例:

{
  "@context": "https://acp.example.org/context/v1",
  "@type": "Agent",
  "agentId": "urn:agent:restaurant-query:v1.0",
  "name": "本地餐厅信息查询智能体",
  "provider": "美食数据公司",
  "version": "1.0.0",
  "endpoint": "https://api.fooddata.com/acp/agent/query",
  "authentication": {
    "method": "BearerToken",
    "info": "https://auth.fooddata.com/acp"
  },
  "capabilities": [
    {
      "capabilityId": "query-restaurant-by-location",
      "name": "按地理位置查询餐厅",
      "description": "根据给定的经纬度坐标和半径,返回该区域内的餐厅基本信息列表。",
      "inputSchema": {
        "type": "object",
        "properties": {
          "latitude": { "type": "number", "description": "中心点纬度" },
          "longitude": { "type": "number", "description": "中心点经度" },
          "radius": { "type": "number", "description": "搜索半径(米)", "default": 2000 }
        },
        "required": ["latitude", "longitude"]
      },
      "outputSchema": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "id": { "type": "string" },
            "name": { "type": "string" },
            "address": { "type": "string" },
            "cuisine": { "type": "array", "items": { "type": "string" } },
            "avgPrice": { "type": "number" }
          }
        }
      }
    },
    {
      "capabilityId": "query-restaurant-details",
      "name": "查询餐厅详情",
      "description": "根据餐厅ID,获取其详细资料,包括营业时间、菜单、评价等。",
      "inputSchema": {
        "type": "object",
        "properties": {
          "restaurantId": { "type": "string" }
        },
        "required": ["restaurantId"]
      },
      "outputSchema": { ... } // 详情结构
    }
  ],
  "metadata": {
    "supportedLanguages": ["zh-CN", "en-US"],
    "responseTimeSLA": "P95<500ms",
    "rateLimit": "100 req/min"
  }
}

这份描述文件清晰地定义了该智能体的两个核心能力,以及每个能力所需的输入参数和返回的数据结构。inputSchemaoutputSchema使用JSON Schema定义,这使得调用方可以在运行时验证请求和响应,极大地提高了协作的可靠性。

2.2 构建能力注册服务器

有了描述格式,我们需要一个服务器来接收和管理这些注册信息。我们可以使用一个简单的RESTful服务来实现。以下是用Python Flask框架演示的核心注册接口:

from flask import Flask, request, jsonify
from pydantic import BaseModel, ValidationError
import json
# 假设我们使用一个字典在内存中存储注册信息(生产环境应使用数据库)
agent_registry = {}

app = Flask(__name__)

class AgentCapability(BaseModel):
    capabilityId: str
    name: str
    description: str
    inputSchema: dict
    outputSchema: dict

class AgentRegistration(BaseModel):
    agentId: str
    name: str
    endpoint: str
    capabilities: list[AgentCapability]
    # 其他字段省略...

@app.route('/acp/v1/agents', methods=['POST'])
def register_agent():
    try:
        registration_data = request.get_json()
        # 1. 验证注册数据格式
        agent_info = AgentRegistration(**registration_data)
        
        # 2. 检查agentId是否唯一
        if agent_info.agentId in agent_registry:
            return jsonify({"error": "Agent ID already exists"}), 409
        
        # 3. 存储注册信息
        agent_registry[agent_info.agentId] = agent_info.dict()
        
        # 4. 返回成功响应,包含注册时间戳
        return jsonify({
            "status": "success",
            "agentId": agent_info.agentId,
            "registeredAt": datetime.utcnow().isoformat()
        }), 201
    except ValidationError as e:
        return jsonify({"error": "Invalid registration format", "details": e.errors()}), 400
    except Exception as e:
        return jsonify({"error": "Internal server error"}), 500

@app.route('/acp/v1/agents/<agent_id>', methods=['GET'])
def get_agent(agent_id):
    agent = agent_registry.get(agent_id)
    if not agent:
        return jsonify({"error": "Agent not found"}), 404
    return jsonify(agent), 200

这个简单的服务器实现了ARP最基础的注册和查询功能。在实际生产中,你还需要考虑:

  • 分级注册:支持树状结构的注册服务器集群,根服务器同步全局信息。
  • 心跳与健康检查:智能体定期发送心跳,注册服务器标记离线节点。
  • 能力版本管理:支持智能体能力描述的版本更新和兼容性检查。

当智能体成功注册后,下一步就是如何让需要帮助的“人”(其他智能体)找到它们。

3. 实现智能体发现协议(ADP):构建智能“人才市场”

ADP协议的核心是“按需索骥”。个人助理智能体拿到“找餐厅”的任务后,它需要知道该找谁来帮忙。ADP服务器就是提供这个“人才搜索”服务的。

3.1 设计能力查询语言

查询语言需要兼顾易用性和表达能力。我们设计两种主要查询方式:

  1. 自然语言查询“我需要一个能根据用户口味和预算推荐餐厅的智能体。”
  2. 结构化查询:提供更精确的过滤和匹配条件。

ADP服务器需要将自然语言查询转换为对能力描述文本的语义搜索。这里我们可以利用嵌入向量模型。以下是一个处理结构化查询的示例端点:

# 假设我们使用Elasticsearch作为后端存储和搜索引擎
from elasticsearch import Elasticsearch
es = Elasticsearch()

@app.route('/acp/v1/discovery/query', methods=['POST'])
def discover_agents():
    query_data = request.get_json()
    query_type = query_data.get('type', 'structured')
    
    if query_type == 'structured':
        # 示例:查找所有具备“推荐”能力,且支持中文的智能体
        body = {
            "query": {
                "bool": {
                    "must": [
                        { "match": { "capabilities.description": "推荐" } },
                        { "term": { "metadata.supportedLanguages": "zh-CN" } }
                    ]
                }
            },
            "sort": [ { "_score": { "order": "desc" } } ] # 按相关度排序
        }
        results = es.search(index='agent-registry', body=body)
        # 提取并格式化结果
        agents = [hit['_source'] for hit in results['hits']['hits']]
        return jsonify({"matchedAgents": agents})
    
    elif query_type == 'natural':
        natural_query = query_data.get('query')
        # 使用文本嵌入模型将自然语言转换为向量
        query_vector = get_text_embedding(natural_query)
        # 在Elasticsearch的dense_vector字段中进行近似最近邻搜索
        body = {
            "knn": {
                "field": "capability_embedding",
                "query_vector": query_vector,
                "k": 10,
                "num_candidates": 100
            }
        }
        results = es.search(index='agent-registry', body=body)
        # ... 处理并返回结果

注意:为每个智能体的能力描述生成并存储文本向量(capability_embedding),是实现高效、准确语义搜索的关键。这需要在注册流程中增加一个向量化处理的环节。

3.2 实现查询与匹配流程

个人助理智能体的发现流程可以概括为以下几步:

  1. 任务解析:个人助理解析用户请求“找一家晚上7点、适合商务宴请的本地特色餐厅”。它识别出关键子任务:查询餐厅个性化推荐预订出行规划
  2. 生成查询:为每个子任务生成ADP查询。例如,对于“个性化推荐”,生成结构化查询:{“requiredCapability”: “restaurant-recommendation”, “filters”: {“scene”: “business-dining”}}
  3. 调用ADP:向ADP服务器发送查询请求。
  4. 评估与选择:ADP返回多个候选智能体。个人助理可能需要根据metadata中的responseTimeSLArateLimit或服务等级协议(SLA)进行二次筛选,选择最优的一个或一组。
  5. 建立连接:获得目标智能体的endpointauthentication信息,为后续交互做好准备。

至此,个人助理已经找到了所有需要的“帮手”。接下来,就是如何组织它们一起干活了。

4. 实现智能体交互协议(AIP):编排一场协同交响乐

AIP协议规定了智能体之间如何组队、通信、协商并最终完成任务。这是整个系统最复杂、也最体现智能的部分。我们将以餐厅推荐场景,详细拆解AIP的流程。

4.1 任务分发与组队建立

个人助理(PA)在通过ADP找到四个智能体(查询A、推荐A、预订A、出行A)后,启动AIP流程。

# 个人助理侧的任务分发逻辑(伪代码)
class PersonalAgent:
    def handle_user_request(self, user_request):
        # 1. 任务理解与分解
        subtasks = self.plan_task(user_request) # 输出: ['query', 'recommend', 'book', 'route']
        
        # 2. 通过ADP发现智能体
        discovered_agents = {}
        for subtask in subtasks:
            agents = self.discovery_client.query(subtask)
            discovered_agents[subtask] = agents[0] # 选择第一个匹配的
        
        # 3. 广播组队请求
        task_group_id = generate_group_id()
        join_requests = []
        for role, agent in discovered_agents.items():
            request = {
                "protocol": "AIP",
                "version": "1.0",
                "action": "TaskGroupInvite",
                "taskGroupId": task_group_id,
                "initiator": self.agent_id,
                "taskDescription": f"协助用户完成餐厅用餐规划:{user_request}",
                "expectedRole": role,
                "compensation": { ... } # 可选,定义激励或计费机制
            }
            # 异步发送请求,等待确认
            response = self.send_to_agent(agent['endpoint'], request)
            if response.get('status') == 'accepted':
                join_requests.append({'agent': agent, 'role': role})
            else:
                # 处理拒绝或超时,寻找备用智能体
                pass
        
        # 4. 确认组队,分发具体子任务
        if len(join_requests) == len(subtasks):
            self.task_groups[task_group_id] = join_requests
            for member in join_requests:
                subtask_spec = self.create_subtask_spec(user_request, member['role'])
                self.assign_subtask(task_group_id, member['agent'], subtask_spec)
        else:
            # 组队失败,回滚或通知用户
            self.notify_user("暂时无法找到所有服务,请稍后再试。")

4.2 子任务执行与双边协商

智能体们在执行子任务时,可能需要进行简单的双边协商。例如,推荐智能体可能需要向查询智能体请求更详细的餐厅评分数据。

// 推荐智能体向查询智能体发送的协商消息
{
  "protocol": "AIP",
  "version": "1.0",
  "action": "SubtaskCoordination",
  "taskGroupId": "tg_123456",
  "from": "urn:agent:restaurant-recommender:v1.0",
  "to": "urn:agent:restaurant-query:v1.0",
  "content": {
    "type": "DataRequest",
    "requestId": "req_789",
    "parameters": {
      "restaurantIds": ["res_001", "res_005", "res_008"],
      "fields": ["rating", "recentReviews"]
    }
  }
}

这种点对点的协商减轻了个人助理的负担,使得协作网络更加高效和灵活。所有关键的交互状态和最终结果,都需要汇总到个人助理。

4.3 结果汇总与交付

每个智能体完成子任务后,将结果发送给个人助理。个人助理负责整合、润色,并最终呈现给用户。

# 个人助理的结果处理逻辑
    def collect_and_present_results(self, task_group_id):
        results = {}
        for member in self.task_groups[task_group_id]:
            # 等待或异步接收每个成员的结果
            agent_result = self.receive_result(member['agent'])
            results[member['role']] = agent_result
        
        # 整合结果:将查询列表、推荐理由、预订确认号、路线规划整合成一段自然语言回复
        final_output = self.synthesize_results(results)
        
        # 交付用户
        self.user_interface.present(final_output)
        
        # 解散任务组,释放资源
        self.cleanup_task_group(task_group_id)

在整个AIP流程中,错误处理超时控制至关重要。例如,如果预订智能体一直无响应,个人助理需要启动备用流程(如通知用户手动预订),并将此情况反馈给注册/发现系统,可能影响该智能体的信誉评分。

5. 系统联调与实战优化建议

将ARP、ADP、AIP三个模块组合起来,你的餐厅推荐协作系统就具备了雏形。但在投入真实使用前,还需要进行大量的联调和优化。

首先,搭建一个最小可行系统(MVP)进行端到端测试。

  1. 部署一个ARP注册中心(可使用上述Flask示例的增强版)。
  2. 部署一个ADP发现服务(集成简单的文本匹配或向量搜索)。
  3. 开发并注册4个模拟智能体(查询、推荐、预订、出行),它们可以暂时使用硬编码数据或模拟API。
  4. 开发个人助理智能体,实现上述AIP流程。
  5. 创建一个简单的命令行或Web界面作为用户入口。

其次,关注以下几个实战中的关键问题:

  • 性能与延迟:智能体间的网络调用会引入延迟。需要为每个交互设置合理的超时时间,并考虑异步非阻塞的调用模式。对于推荐、查询这类可能耗时的操作,可以采用“快速返回候选,后台精筛”的策略。
  • 通信格式标准化:除了协议层面的动作(TaskGroupInvite),智能体间交换的业务数据(如餐厅信息结构)也需要提前定义好Schema,并使用像Protocol Buffers或JSON Schema进行约束和验证,避免解析错误。
  • 安全与鉴权:每个智能体的endpoint都需要有严格的访问控制。可以在ARP注册时交换公钥,在AIP交互中使用JWT(JSON Web Tokens)进行请求签名和验证,确保调用方的合法性和消息的完整性。
  • 可观测性:为每个智能体和协议交互环节加入详细的日志、指标(Metrics)和分布式追踪(如OpenTelemetry)。当用户反馈“推荐不准”时,你能快速定位是查询智能体返回数据不全,还是推荐智能体的算法有问题。

最后,思考扩展性。 当前的系统是中心化的个人助理协调模式。随着智能体数量增多,可以考虑更去中心化的协商机制,例如让智能体之间通过拍卖、投票等方式自主形成任务小组。或者引入信誉系统,让ADP在发现时不仅考虑能力匹配,还参考智能体的历史任务完成率、响应速度等信誉指标,实现更优的调度。

搭建这样一个系统,最深的体会是协议设计比算法实现更重要。清晰的接口约定和状态机定义,能让异构的智能体像乐高积木一样灵活组合。在调试时,我习惯先用简单的日志打印出每个AIP消息的流向,确保组队、分发、协商、汇总的每个环节都按预期运转,这比直接处理业务逻辑的Bug要高效得多。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐