项目三 智能体描述与发现
摘要:本文是《智能体互联网(IoA)技术实践教程》的第三个项目,基于 GB/Z 185.4—2026 和 GB/Z 185.5—2026 国家标准,带你用 12 学时完成三件事:为智能体编写标准化的能力名片(Agent Card)→ 搭建智能体发现服务实现"智能体黄页" → 实现基于自然语言的自动发现与匹配。全文涵盖 GB/Z 185.4 规定的 15 项描述属性与 8 项技能属性、两种发现方式(发现服务/预置信息)、完整的发现-交互闭环,含完整可运行代码与评价量规,适合应用本科与职业院校教学,也适合开发者掌握智能体标准化描述与发现技术。
关键词:智能体描述、Agent Card、智能体发现、GB/Z 185.4、GB/Z 185.5、A2A 协议、能力名片、FastAPI、JSON-RPC
适合人群:已完成项目一、项目二的学习者、物联网/AI 课程教师、应用本科与职业院校学生、希望掌握智能体标准化描述与发现技术的开发者
配套专栏:《智能体互联网(IoA)技术实践教程》项目三
📖 前言
项目二中,"社团通知助手"和"校园生活助手"都已经拿到了身份码和凭证,完成了身份注册。但现在出现了一个新问题——
小李对校园生活助手说:“帮我查一下下周哪些教室是空的。”
校园生活助手犯了难:它自己只会发通知,不会查教室。它知道学校里应该有个"教室查询助手",但它不知道这个助手在哪里、能做什么、怎么联系。
这就像一个人有了身份证,但通讯录是空的——他想找人帮忙,却不知道谁能帮、怎么找。
GB/Z 185.4 和 185.5 就是为解决这个"找人"问题而制定的:
- 💡 GB/Z 185.4(智能体描述):规定智能体如何"自我介绍"——用一份结构化的"能力名片"(Agent Description)说明自己是谁、能干什么、怎么联系。
- 💡 GB/Z 185.5(智能体发现):规定智能体如何"找到对方"——通过发现服务或预置信息,按名称、标签、自然语言等条件搜索到合适的智能体。
本项目将带领你为校园智能体编写能力描述、搭建发现服务、实现自动匹配,让智能体从"各自为战"走向"互相发现、按需协作"。
建议学时:12 学时(理论 4 + 实践 8)
项目目标:
- 能够编写符合 GB/Z 185.4 规范的智能体描述 JSON 文件,掌握 15 项描述属性与 8 项技能属性;
- 能够用 FastAPI 搭建智能体发现服务,支持按名称、标签、关键词检索;
- 能够编写客户端程序,根据自然语言需求自动发现并调用合适智能体;
- 理解 GB/Z 185.4 与 A2A Agent Card 的映射关系,建立"能力可见、按需发现"的协作意识。
前置项目:项目一(认识智能体互联网)、项目二(智能体身份码与身份管理)
素养目标:树立"标准化描述对智能体规模化互联的支撑作用"的工程意识,理解国标制定的意义。
🗂️ 文章目录
CSDN 会根据二级、三级标题自动生成右侧目录,可直接跳转阅读。全文按"项目导读 → 任务 3.1 → 任务 3.2 → 任务 3.3 → 项目总结"组织,建议按顺序学习。
项目导读
学习目标
| 目标维度 | 具体要求 |
|---|---|
| 知识 | ① 掌握 GB/Z 185.4 规定的 15 项描述属性与 8 项技能属性;② 理解 GB/Z 185.5 的两种发现方式(发现服务 / 预置信息);③ 理解 A2A Agent Card 与国标描述属性的映射关系 |
| 技能 | ① 能编写符合规范的智能体描述 JSON 文件;② 能用 FastAPI 搭建智能体发现服务,支持按名称、标签、关键词检索;③ 能编写客户端程序,根据自然语言需求自动发现并调用合适智能体 |
| 素养 | ① 建立"能力可见、按需发现"的协作意识;② 理解标准化描述对智能体规模化互联的支撑作用 |
项目任务总览
| 任务 | 名称 | 核心标准 | 产出物 |
|---|---|---|---|
| 任务 3.1 | 智能体能力名片编写 | GB/Z 185.4 | agent_card_builder.py——描述文件生成与校验工具 |
| 任务 3.2 | 智能体发现服务搭建 | GB/Z 185.5 | discovery_server.py——FastAPI 发现服务 |
| 任务 3.3 | 智能体能力匹配与自动发现 | 185.4 + 185.5 | auto_discover_client.py——自动发现并调用客户端 |
项目与前序项目的关系
项目一:搭建智能体 → 体验协作 → 画架构图
↓
项目二:身份码 → 注册中心 → 凭证验证(解决"谁在互联、是否可信")
↓
项目三:能力名片 → 发现服务 → 自动匹配(解决"具备什么能力、如何被找到") ← 你在这里
↓
项目四:智能体交互……(解决"如何协同完成任务")
任务 3.1 智能体能力名片编写
对应标准:GB/Z 185.4—2026
建议学时:4 学时
① 任务情境
校园里陆续出现了好几个智能体:社团通知助手、教室查询助手、食堂菜单助手、校车查询助手……但每个智能体都"沉默"地运行着,其他智能体不知道它们的存在和能力。
学校信息中心决定:为每个智能体编写一份标准化的"能力名片",让任何智能体都能通过这份名片了解对方是谁、能做什么、怎么联系。这份名片就是 GB/Z 185.4 规定的智能体描述,在 A2A 协议中对应 Agent Card。
② 知识准备
一、为什么需要智能体描述
💡 想象一个没有通讯录、没有搜索引擎的世界:你要找人帮忙,只能挨家挨户敲门问"你会修水管吗?"——效率极低。
智能体互联网面临同样的问题。如果没有标准化的能力描述:
- ❌ 调用方不知道谁能完成任务;
- ❌ 被调用方的能力无法被机器自动理解;
- ❌ 发现服务没有数据可检索。
✅ GB/Z 185.4 通过规定15 项描述属性和8 项技能属性,让每个智能体都能用统一格式"自我介绍",使机器可读、可检索、可匹配。
二、GB/Z 185.4 的 15 项描述属性
GB/Z 185.4 将智能体描述属性分为四大类:
| 类别 | 属性名 | 含义 | 是否必填 |
|---|---|---|---|
| 身份标识 | agentId | 身份码(来自 GB/Z 185.2) | 是 |
name | 名称 | 是 | |
alias | 别名 | 否 | |
version | 版本 | 是 | |
| 基础信息 | description | 描述 | 是 |
iconAddress | 图标地址 | 否 | |
provider | 提供方 | 否 | |
| 访问信息 | accessAddress | 访问地址 | 是 |
accessMethod | 访问方法(如 JSON-RPC、HTTP) | 是 | |
servingArea | 服务区域 | 否 | |
authentication | 认证方式 | 否 | |
| 能力声明 | capabilities | 辅助功能(如流式、推送) | 否 |
defaultInputTypes | 默认输入类型 | 否 | |
defaultOutputTypes | 默认输出类型 | 否 | |
| 技能引用 | skills | 技能列表 | 是 |
记忆口诀:描述属性 15 项——身份 4 项(码名别版)、基础 3 项(述图供)、访问 4 项(址法域证)、能力 3 项(功入出)、技能引用
skills1 项;其中每项技能又含 8 个技能属性。口径说明:正文所称"15 项描述属性"是包含技能引用字段
skills在内的全部字段;而"8 项技能属性"指每一项技能(skill)内部所含的属性数。两者口径不同,使用时注意区分。
三、GB/Z 185.4 的 8 项技能属性
每个智能体可声明多项技能,每项技能包含 8 个属性:
| 属性名 | 含义 | 是否必填 |
|---|---|---|
skillId | 技能标识 | 是 |
skillName | 技能名字 | 是 |
skillDescription | 技能描述 | 是 |
tags | 标签 | 否 |
examples | 样例 | 否 |
inputTypes | 输入类型 | 否 |
outputTypes | 输出类型 | 否 |
dependencies | 运行依赖 | 否 |
四、GB/Z 185.4 与 A2A Agent Card 的映射
💡 A2A 协议的 Agent Card 与国标描述属性高度一致,但字段名有差异。下表是两者的映射关系:
| GB/Z 185.4 属性 | A2A Agent Card 字段 | 说明 |
|---|---|---|
agentId | (无直接对应,A2A 用 URL 标识) | 国标要求身份码,A2A 靠 URL |
name | name | 完全一致 |
alias | (无) | A2A 未定义别名 |
version | version | 完全一致 |
description | description | 完全一致 |
iconAddress | (无) | A2A 未定义图标 |
provider | provider | 一致,均为对象 |
accessAddress | url(在 interface 中) | A2A 用 url 字段 |
accessMethod | supported_interfaces 中的 protocol_binding | A2A 用接口对象 |
servingArea | (无) | A2A 未定义服务区域 |
authentication | security_schemes / security | A2A 遵循 OpenAPI 规范 |
capabilities | capabilities | 一致,均为对象 |
defaultInputTypes | default_input_modes | 字段名不同,含义一致 |
defaultOutputTypes | default_output_modes | 字段名不同,含义一致 |
skills | skills | 一致,均为数组 |
skillId | id | 字段名不同 |
skillName | name | 字段名不同 |
skillDescription | description | 字段名不同 |
tags | tags | 完全一致 |
examples | examples | 完全一致 |
inputTypes | input_modes | 字段名不同 |
outputTypes | output_modes | 字段名不同 |
dependencies | (无) | A2A 未定义依赖 |
💡 关键结论:国标描述属性比 A2A Agent Card 多出
agentId、alias、iconAddress、servingArea、dependencies五项。在实际工程中,可以把这些额外属性放在 Agent Card 的扩展字段中。
五、智能体描述的三大流程
GB/Z 185.4 规定了描述的注册、发布、变更三大流程:
注意:变更流程的最后一步"同步发现服务"非常关键——描述变更后必须通知发现服务更新索引,否则调用方会按旧描述匹配,导致任务失败。
③ 任务实施
步骤 1:准备项目目录
在项目一、项目二的基础上,新建项目三的工作目录:
# 确保在 ioa-lab 目录下(已有 .venv 虚拟环境)
cd d:\潘志宏工作空间\IoA-Agent\ioa-lab
# 激活虚拟环境
.venv\Scripts\activate
# 创建项目三工作目录
mkdir project3
cd project3
✅ 验证要点:执行
python --version应显示 Python 3.10+,执行pip show a2a-sdk应显示已安装。
步骤 2:编写社团通知助手的 Agent Card
新建文件 agent_card_builder.py,编写一个生成和校验 Agent Card 的工具:
# agent_card_builder.py
# 智能体能力名片生成与校验工具
# 对应标准:GB/Z 185.4—2026
import json
from pathlib import Path
# ============================================================
# 一、定义三个校园智能体的能力名片
# 对照 GB/Z 185.4 的 15 项描述属性 + 8 项技能属性
# ============================================================
def build_notify_agent_card(aid: str = '') -> dict:
"""构建社团通知助手的 Agent Card。
参数:
aid: 身份码(来自项目二注册中心,可选)
"""
return {
# ---- 身份标识 4 项 ----
'agentId': aid or '1.2.156.3088.1.SCH001.TEAM01.A00001.I00001',
'name': '社团通知助手',
'alias': 'ClubNotify',
'version': '1.0.0',
# ---- 基础信息 3 项 ----
'description': '接收活动信息,生成并发送社团通知,支持自定义成员列表',
'iconAddress': '',
'provider': {
'organization': '校园信息中心',
'url': 'https://campus.example.edu.cn',
'contactEmail': 'agent@campus.example.edu.cn',
},
# ---- 访问信息 4 项 ----
'accessAddress': 'http://127.0.0.1:9999',
'accessMethod': 'JSONRPC',
'servingArea': '校园内网',
'authentication': {
'scheme': 'custom',
'description': '通过 X-Agent-AID 和 X-Agent-Credential 请求头携带身份凭证',
},
# ---- 能力声明 3 项 ----
'capabilities': {
'streaming': False,
'pushNotifications': False,
},
'defaultInputTypes': ['text/plain'],
'defaultOutputTypes': ['text/plain'],
# ---- 技能引用 1 项(含 8 项技能属性)----
'skills': [
{
'skillId': 'send_club_notice',
'skillName': '社团通知发送',
'skillDescription': '根据活动信息,生成通知文本并模拟发送给指定成员列表',
'tags': ['通知', '社团', '消息'],
'examples': ['通知成员下周二活动', '帮我发社团通知', '社团活动提醒'],
'inputTypes': ['text/plain'],
'outputTypes': ['text/plain'],
'dependencies': [],
}
],
}
def build_classroom_agent_card(aid: str = '') -> dict:
"""构建教室查询助手的 Agent Card。"""
return {
'agentId': aid or '1.2.156.3088.1.SCH001.TEAM01.A00003.I00001',
'name': '教室查询助手',
'alias': 'RoomFinder',
'version': '1.0.0',
'description': '查询校园空教室信息,支持按时间段、人数、设备条件筛选',
'iconAddress': '',
'provider': {
'organization': '校园信息中心',
'url': 'https://campus.example.edu.cn',
'contactEmail': 'agent@campus.example.edu.cn',
},
'accessAddress': 'http://127.0.0.1:9998',
'accessMethod': 'JSONRPC',
'servingArea': '校园内网',
'authentication': {
'scheme': 'custom',
'description': '通过 X-Agent-AID 和 X-Agent-Credential 请求头携带身份凭证',
},
'capabilities': {
'streaming': False,
'pushNotifications': False,
},
'defaultInputTypes': ['text/plain'],
'defaultOutputTypes': ['text/plain'],
'skills': [
{
'skillId': 'find_empty_classroom',
'skillName': '空教室查询',
'skillDescription': '根据时间段、人数、设备需求,查询可用的空教室',
'tags': ['教室', '查询', '预约'],
'examples': ['查下周四下午能容纳30人的多媒体教室', '有没有空教室'],
'inputTypes': ['text/plain'],
'outputTypes': ['text/plain'],
'dependencies': [],
}
],
}
def build_canteen_agent_card(aid: str = '') -> dict:
"""构建食堂菜单助手的 Agent Card。"""
return {
'agentId': aid or '1.2.156.3088.1.SCH001.TEAM01.A00004.I00001',
'name': '食堂菜单助手',
'alias': 'CanteenMenu',
'version': '1.0.0',
'description': '查询食堂每日菜单,支持按食堂、菜品类型、价格区间筛选',
'iconAddress': '',
'provider': {
'organization': '校园信息中心',
'url': 'https://campus.example.edu.cn',
'contactEmail': 'agent@campus.example.edu.cn',
},
'accessAddress': 'http://127.0.0.1:9997',
'accessMethod': 'JSONRPC',
'servingArea': '校园内网',
'authentication': {
'scheme': 'custom',
'description': '通过 X-Agent-AID 和 X-Agent-Credential 请求头携带身份凭证',
},
'capabilities': {
'streaming': False,
'pushNotifications': False,
},
'defaultInputTypes': ['text/plain'],
'defaultOutputTypes': ['text/plain'],
'skills': [
{
'skillId': 'query_menu',
'skillName': '菜单查询',
'skillDescription': '查询指定食堂的当日菜单,支持按菜品类型和价格筛选',
'tags': ['食堂', '菜单', '餐饮'],
'examples': ['今天一食堂有什么菜', '查一下便宜的素菜'],
'inputTypes': ['text/plain'],
'outputTypes': ['text/plain'],
'dependencies': [],
}
],
}
# ============================================================
# 二、Agent Card 校验器
# 对照 GB/Z 185.4 检查必填属性是否齐全
# ============================================================
# GB/Z 185.4 规定的必填描述属性
REQUIRED_FIELDS = ['agentId', 'name', 'version', 'description',
'accessAddress', 'accessMethod', 'skills']
# GB/Z 185.4 规定的必填技能属性
REQUIRED_SKILL_FIELDS = ['skillId', 'skillName', 'skillDescription']
def validate_card(card: dict) -> list[str]:
"""校验 Agent Card 是否符合 GB/Z 185.4 要求。
返回:
错误信息列表(空列表表示通过)
"""
errors = []
# 1. 检查必填描述属性
for field in REQUIRED_FIELDS:
if field not in card or not card[field]:
errors.append(f'缺少必填属性:{field}')
# 2. 检查技能列表
skills = card.get('skills', [])
if not skills:
errors.append('技能列表不能为空(至少声明 1 项技能)')
else:
for i, skill in enumerate(skills):
for field in REQUIRED_SKILL_FIELDS:
if field not in skill or not skill[field]:
errors.append(f'技能 {i} 缺少必填属性:{field}')
# 3. 检查身份码格式(简单校验:以 OID 前缀开头)
aid = card.get('agentId', '')
if aid and not aid.startswith('1.2.156.3088.'):
errors.append(f'身份码格式不正确(应以 1.2.156.3088. 开头):{aid}')
# 4. 检查访问地址
addr = card.get('accessAddress', '')
if addr and not (addr.startswith('http://') or addr.startswith('https://')):
errors.append(f'访问地址必须以 http:// 或 https:// 开头:{addr}')
return errors
# ============================================================
# 三、主程序:生成、校验并保存 Agent Card
# ============================================================
def main():
print('=' * 60)
print('智能体能力名片生成与校验工具')
print('对应标准:GB/Z 185.4—2026')
print('=' * 60)
# 构建三张名片
cards = {
'notify_agent_card.json': build_notify_agent_card(),
'classroom_agent_card.json': build_classroom_agent_card(),
'canteen_agent_card.json': build_canteen_agent_card(),
}
for filename, card in cards.items():
print(f'\n--- 正在处理:{card["name"]} ---')
# 校验
errors = validate_card(card)
if errors:
print(f' × 校验失败,发现 {len(errors)} 个问题:')
for e in errors:
print(f' - {e}')
continue
print(f' √ 校验通过')
print(f' 身份码:{card["agentId"]}')
print(f' 名称:{card["name"]}({card.get("alias", "无别名")})')
print(f' 版本:{card["version"]}')
print(f' 地址:{card["accessAddress"]}')
print(f' 技能数:{len(card["skills"])}')
for skill in card['skills']:
print(f' · {skill["skillName"]}({skill["skillId"]})')
print(f' 标签:{skill.get("tags", [])}')
# 保存为 JSON 文件
filepath = Path(filename)
filepath.write_text(
json.dumps(card, ensure_ascii=False, indent=2),
encoding='utf-8',
)
print(f' √ 已保存到:{filepath.absolute()}')
print(f'\n{"=" * 60}')
print(f'全部完成!共生成 {len(cards)} 张能力名片。')
print(f'这些 JSON 文件将在任务 3.2 中注册到发现服务。')
print(f'{"=" * 60}')
if __name__ == '__main__':
main()
步骤 3:运行名片生成工具
python agent_card_builder.py
预期输出:
============================================================
智能体能力名片生成与校验工具
对应标准:GB/Z 185.4—2026
============================================================
--- 正在处理:社团通知助手 ---
√ 校验通过
身份码:1.2.156.3088.1.SCH001.TEAM01.A00001.I00001
名称:社团通知助手(ClubNotify)
版本:1.0.0
地址:http://127.0.0.1:9999
技能数:1
· 社团通知发送(send_club_notice)
标签:['通知', '社团', '消息']
√ 已保存到:d:\潘志宏工作空间\IoA-Agent\ioa-lab\project3\notify_agent_card.json
--- 正在处理:教室查询助手 ---
√ 校验通过
身份码:1.2.156.3088.1.SCH001.TEAM01.A00003.I00001
名称:教室查询助手(RoomFinder)
版本:1.0.0
地址:http://127.0.0.1:9998
技能数:1
· 空教室查询(find_empty_classroom)
标签:['教室', '查询', '预约']
√ 已保存到:d:\潘志宏工作空间\IoA-Agent\ioa-lab\project3\classroom_agent_card.json
--- 正在处理:食堂菜单助手 ---
√ 校验通过
身份码:1.2.156.3088.1.SCH001.TEAM01.A00004.I00001
名称:食堂菜单助手(CanteenMenu)
版本:1.0.0
地址:http://127.0.0.1:9997
技能数:1
· 菜单查询(query_menu)
标签:['食堂', '菜单', '餐饮']
√ 已保存到:d:\潘志宏工作空间\IoA-Agent\ioa-lab\project3\canteen_agent_card.json
============================================================
全部完成!共生成 3 张能力名片。
这些 JSON 文件将在任务 3.2 中注册到发现服务。
============================================================
✅ 看到 “校验通过” 且生成 3 个 JSON 文件算成功。
步骤 4:查看生成的名片文件
用文本编辑器打开 notify_agent_card.json,应看到如下结构化 JSON:
{
"agentId": "1.2.156.3088.1.SCH001.TEAM01.A00001.I00001",
"name": "社团通知助手",
"alias": "ClubNotify",
"version": "1.0.0",
"description": "接收活动信息,生成并发送社团通知,支持自定义成员列表",
"iconAddress": "",
"provider": {
"organization": "校园信息中心",
"url": "https://campus.example.edu.cn",
"contactEmail": "agent@campus.example.edu.cn"
},
"accessAddress": "http://127.0.0.1:9999",
"accessMethod": "JSONRPC",
"servingArea": "校园内网",
"authentication": {
"scheme": "custom",
"description": "通过 X-Agent-AID 和 X-Agent-Credential 请求头携带身份凭证"
},
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"defaultInputTypes": ["text/plain"],
"defaultOutputTypes": ["text/plain"],
"skills": [
{
"skillId": "send_club_notice",
"skillName": "社团通知发送",
"skillDescription": "根据活动信息,生成通知文本并模拟发送给指定成员列表",
"tags": ["通知", "社团", "消息"],
"examples": ["通知成员下周二活动", "帮我发社团通知", "社团活动提醒"],
"inputTypes": ["text/plain"],
"outputTypes": ["text/plain"],
"dependencies": []
}
]
}
对照检查:这份 JSON 包含了 GB/Z 185.4 规定的全部 15 项描述属性(身份标识 4 项 + 基础信息 3 项 + 访问信息 4 项 + 能力声明 3 项 + 技能引用 1 项)和每项技能的 8 项属性。
④ 任务评价
| 评价维度 | 优秀(9-10分) | 良好(7-8分) | 合格(6分) | 需改进(<6分) |
|---|---|---|---|---|
| 描述完整性 | 15 项属性全部填写,技能含全部 8 项 | 缺 1-2 项可选属性 | 必填属性齐全 | 缺必填属性 |
| 格式规范性 | JSON 格式完全正确,缩进规范 | 格式正确,缩进略有差异 | 格式基本正确 | JSON 格式错误 |
| 校验功能 | 校验器覆盖所有必填项 + 格式检查 | 校验器覆盖必填项 | 基本能校验 | 校验器无效 |
| 标准对照 | 能准确说出每个属性对应的国标条款 | 能说出大部分属性归属 | 能说出类别 | 无法对应 |
任务 3.2 智能体发现服务搭建
对应标准:GB/Z 185.5—2026
建议学时:4 学时
① 任务情境
三个智能体的能力名片已经写好了,但它们还是"各自为战"——校园生活助手想找教室查询助手,却不知道去哪里找。
学校信息中心决定搭建一个智能体发现服务(Discovery Service),就像一个"智能体黄页":
- 每个智能体把自己的能力名片注册到发现服务;
- 需要找人帮忙时,向发现服务发起查询;
- 发现服务返回匹配的智能体列表。
这正是 GB/Z 185.5 规定的路径一:基于发现服务的发现流程。
② 知识准备
一、GB/Z 185.5 的两种发现方式
💡 GB/Z 185.5 规定了两种发现方式:
| 发现方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 路径一:发现服务 | 智能体数量多、动态变化 | 实时性强、可检索 | 依赖中心化服务 |
| 路径二:预置信息 | 智能体数量少、相对固定 | 无需中心服务、响应快 | 信息可能过时 |
💡 实际工程中,两种方式常组合使用:先查预置缓存,未命中再查发现服务。
二、发现服务的三种查询接口
GB/Z 185.5 规定发现服务宜提供三种查询接口:
| 接口类型 | 全称 | 适用场景 |
|---|---|---|
| API | Application Programming Interface | 程序间自动调用 |
| GUI | Graphical User Interface | 人工浏览检索 |
| LUI | Language User Interface | 自然语言查询 |
本项目重点实现 API 接口,因为智能体间的发现是自动完成的。
三、发现服务的发现约束
💡 GB/Z 185.5 规定了两类约束:
- ✅ 可被发现配置:智能体可声明是否允许被发现,发现服务只返回允许被发现的智能体。
- ✅ 可用性要求:智能体可附加可用性条件,如付费、特定用户群等。
💡 这就像手机通讯录里的"隐私设置"——你可以选择让自己的号码被搜索到,也可以设为不可见。
四、发现服务的 API 设计
基于 GB/Z 185.5 的要求,本项目设计以下 API:
| 接口 | 方法 | 功能 |
|---|---|---|
/discover/register | POST | 注册智能体描述(提交能力名片) |
/discover/search | GET | 按名称、标签、关键词搜索智能体 |
/discover/list | GET | 列出所有已注册智能体 |
/discover/{agent_id} | GET | 按身份码获取单个智能体描述 |
/.well-known/agents | GET | 预置信息发现(路径二) |
③ 任务实施
步骤 1:编写发现服务
新建文件 discovery_server.py:
# discovery_server.py
# 智能体发现服务
# 对应标准:GB/Z 185.5—2026
# 提供路径一(发现服务)和路径二(预置信息)两种发现方式
import json
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# ============================================================
# 生命周期:启动时自动加载本地 JSON 名片
# ============================================================
@asynccontextmanager
async def lifespan(_: FastAPI):
"""服务启动时自动加载当前目录下的 *_agent_card.json 文件。"""
card_files = Path('.').glob('*_agent_card.json')
loaded = 0
for card_file in card_files:
try:
data = json.loads(card_file.read_text(encoding='utf-8'))
desc = AgentDescription(**data)
agent_store[desc.agentId] = desc
loaded += 1
print(f' √ 已加载:{desc.name}({desc.agentId})')
except Exception as e:
print(f' × 加载失败:{card_file.name} —— {e}')
if loaded:
print(f'共加载 {loaded} 张能力名片。')
else:
print('未找到本地名片文件,请先运行 agent_card_builder.py。')
yield
app = FastAPI(
title='智能体发现服务',
description='对应标准:GB/Z 185.5—2026',
version='1.0.0',
lifespan=lifespan,
)
# 允许跨域(方便浏览器端 GUI 查询)
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_methods=['*'],
allow_headers=['*'],
)
# ============================================================
# 数据模型
# ============================================================
class AgentDescription(BaseModel):
"""智能体描述(对应 GB/Z 185.4)。"""
agentId: str
name: str
alias: Optional[str] = ''
iconAddress: Optional[str] = ''
version: str
description: str
provider: Optional[dict] = None
accessAddress: str
accessMethod: str
servingArea: Optional[str] = ''
authentication: Optional[dict] = None
capabilities: Optional[dict] = None
defaultInputTypes: Optional[list[str]] = None
defaultOutputTypes: Optional[list[str]] = None
skills: list[dict] = []
# GB/Z 185.5 发现约束
discoverable: bool = True
# 发现服务的数据存储(内存字典,key 为 agentId)
agent_store: dict[str, AgentDescription] = {}
# ============================================================
# 接口 1:注册智能体描述
# 对应 GB/Z 185.4 注册流程 + GB/Z 185.5 发现服务
# ============================================================
@app.post('/discover/register')
async def register_agent(desc: AgentDescription):
"""注册智能体描述到发现服务。
对应 GB/Z 185.4 注册流程:提交描述 → 信息检查 → 返回结果。
"""
# 信息检查:必填字段
if not desc.agentId:
raise HTTPException(status_code=422, detail='身份码不能为空')
if not desc.skills:
raise HTTPException(status_code=422, detail='技能列表不能为空')
# 检查是否已注册
is_update = desc.agentId in agent_store
# 存储
agent_store[desc.agentId] = desc
action = '更新' if is_update else '注册'
return {
'success': True,
'message': f'智能体{action}成功',
'agentId': desc.agentId,
'name': desc.name,
'discoverable': desc.discoverable,
}
# ============================================================
# 接口 2:搜索智能体(路径一核心接口)
# 对应 GB/Z 185.5 路径一:发送发现请求 → 查询匹配 → 返回结果集
# 支持 API 查询接口(GB/Z 185.5 第 6.1 条)
# ============================================================
@app.get('/discover/search')
async def search_agents(
name: Optional[str] = Query(None, description='按名称模糊搜索'),
tag: Optional[str] = Query(None, description='按标签搜索'),
keyword: Optional[str] = Query(None, description='按关键词搜索(名称+描述+标签+技能描述)'),
):
"""搜索已注册的智能体。
只返回 discoverable=True 的智能体(GB/Z 185.5 可被发现配置)。
"""
results = []
for agent in agent_store.values():
# 1. 可被发现配置检查
if not agent.discoverable:
continue
matched = False
# 2. 按名称搜索(模糊匹配)
if name:
if (name in agent.name or
(agent.alias and name in agent.alias)):
matched = True
# 3. 按标签搜索
if tag:
for skill in agent.skills:
if tag in skill.get('tags', []):
matched = True
break
# 4. 按关键词匹配
# 用户可能直接输入整句(如"帮我查一下下周四下午有没有空教室"),
# 若只做"整句作为子串匹配",几乎无法命中。因此这里采用"信号词包含匹配":
# ① 抽取该智能体的"信号词"(名称、别名、技能名、标签、样例),
# 判断其中是否有词出现在用户需求里;
# ② 同时保留"关键词是否完整出现在可搜索文本中"的原匹配。
if keyword:
query = keyword.lower()
# ① 信号词中包含匹配:某个信号词出现在用户需求中
signal_terms = [agent.name, agent.alias or '', agent.description or '']
for skill in agent.skills:
signal_terms.append(skill.get('skillName', ''))
signal_terms.extend(skill.get('tags', []))
signal_terms.extend(skill.get('examples', []))
# ② 整句包含匹配:用户关键词完整出现在可搜索文本中
searchable = ' '.join([agent.name, agent.alias or '', agent.description or ''])
for skill in agent.skills:
searchable += ' ' + skill.get('skillName', '')
searchable += ' ' + skill.get('skillDescription', '')
for t in skill.get('tags', []):
searchable += ' ' + t
if any(t and (t in query) for t in signal_terms) or \
query in searchable.lower():
matched = True
# 5. 无条件时返回全部
if not name and not tag and not keyword:
matched = True
if matched:
results.append(agent.model_dump())
return {
'count': len(results),
'agents': results,
}
# ============================================================
# 接口 3:列出所有智能体
# ============================================================
@app.get('/discover/list')
async def list_agents():
"""列出所有可被发现的智能体。"""
agents = [
a.model_dump() for a in agent_store.values()
if a.discoverable
]
return {'count': len(agents), 'agents': agents}
# ============================================================
# 接口 4:按身份码获取单个智能体
# ============================================================
@app.get('/discover/{agent_id}')
async def get_agent(agent_id: str):
"""按身份码获取单个智能体描述。"""
# 将 URL 中的点号还原(FastAPI 路径参数中点号是合法的)
if agent_id not in agent_store:
raise HTTPException(status_code=404, detail=f'未找到身份码为 {agent_id} 的智能体')
return agent_store[agent_id]
# ============================================================
# 接口 5:预置信息发现(路径二)
# 对应 GB/Z 185.5 路径二:基于预置信息源
# 使用 .well-known 地址(GB/Z 185.5 第 6.2 条 well-known 地址)
# ============================================================
@app.get('/.well-known/agents')
async def well_known_agents(
skill: Optional[str] = Query(None, description='按技能标签筛选'),
):
"""预置信息发现接口(路径二)。
返回所有可被发现的智能体描述,支持按技能标签筛选。
对应 GB/Z 185.5 第 6.2 条:基于预置信息的发现流程。
"""
agents = []
for agent in agent_store.values():
if not agent.discoverable:
continue
if skill:
# 检查是否包含指定标签
has_tag = False
for s in agent.skills:
if skill in s.get('tags', []):
has_tag = True
break
if not has_tag:
continue
agents.append(agent.model_dump())
return {'count': len(agents), 'agents': agents}
# ============================================================
# 入口
# ============================================================
if __name__ == '__main__':
import uvicorn
print('=' * 60)
print('智能体发现服务')
print('对应标准:GB/Z 185.5—2026')
print('服务地址:http://127.0.0.1:8000')
print('API 文档:http://127.0.0.1:8000/docs')
print('=' * 60)
uvicorn.run(app, host='127.0.0.1', port=8000)
步骤 2:启动发现服务
确保 agent_card_builder.py 已运行并生成了 3 个 JSON 文件,然后启动发现服务:
python discovery_server.py
预期输出:
============================================================
智能体发现服务
对应标准:GB/Z 185.5—2026
服务地址:http://127.0.0.1:8000
API 文档:http://127.0.0.1:8000/docs
============================================================
√ 已加载:社团通知助手(1.2.156.3088.1.SCH001.TEAM01.A00001.I00001)
√ 已加载:教室查询助手(1.2.156.3088.1.SCH001.TEAM01.A00003.I00001)
√ 已加载:食堂菜单助手(1.2.156.3088.1.SCH001.TEAM01.A00004.I00001)
共加载 3 张能力名片。
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
✅ 看到 3 张名片加载成功且 Uvicorn 运行算成功。
步骤 3:测试搜索接口(路径一)
保持发现服务运行,新开一个 PowerShell 窗口,用 curl 测试各种查询:
测试 1:列出所有智能体
curl http://127.0.0.1:8000/discover/list
预期返回包含 3 个智能体的列表。
测试 2:按名称搜索
curl "http://127.0.0.1:8000/discover/search?name=教室"
应只返回"教室查询助手"。
测试 3:按标签搜索
curl "http://127.0.0.1:8000/discover/search?tag=查询"
应返回"教室查询助手"(当前三个智能体中,只有它的技能标签中含"查询";食堂菜单助手的标签是 “食堂、菜单、餐饮”,不含"查询")。
若想观察"多命中"的效果,可改用食堂菜单助手的标签查询:
curl "http://127.0.0.1:8000/discover/search?tag=食堂"
应返回"食堂菜单助手"。
测试 4:按关键词搜索(模拟自然语言发现)
curl "http://127.0.0.1:8000/discover/search?keyword=空教室"
应返回"教室查询助手"(其技能描述中含"空教室")。
curl "http://127.0.0.1:8000/discover/search?keyword=通知"
应返回"社团通知助手"(其名称和技能标签中含"通知")。
步骤 4:测试预置信息发现(路径二)
curl http://127.0.0.1:8000/.well-known/agents
应返回所有可被发现的智能体列表。
按技能标签筛选:
curl "http://127.0.0.1:8000/.well-known/agents?skill=食堂"
应只返回"食堂菜单助手"。
步骤 5:测试可被发现配置
向发现服务注册一个"不可发现"的智能体:
curl -X POST http://127.0.0.1:8000/discover/register -H "Content-Type: application/json" -d "{\"agentId\":\"1.2.156.3088.1.SCH001.TEAM01.A00099.I00001\",\"name\":\"测试隐藏助手\",\"version\":\"1.0.0\",\"description\":\"这个助手不应被发现\",\"accessAddress\":\"http://127.0.0.1:9990\",\"accessMethod\":\"JSONRPC\",\"skills\":[{\"skillId\":\"test\",\"skillName\":\"测试\",\"skillDescription\":\"测试用\"}],\"discoverable\":false}"
然后再查询:
curl http://127.0.0.1:8000/discover/list
应仍然只返回 3 个智能体("测试隐藏助手"因 discoverable=false 被过滤)。
✅ 验证要点:
discoverable=false的智能体不会出现在任何查询结果中,符合 GB/Z 185.5 的"可被发现配置"要求。
④ 任务评价
| 评价维度 | 优秀(9-10分) | 良好(7-8分) | 合格(6分) | 需改进(<6分) |
|---|---|---|---|---|
| 路径一实现 | 支持名称、标签、关键词三种搜索 | 支持两种搜索方式 | 支持基本列表 | 无法搜索 |
| 路径二实现 | well-known 接口支持标签筛选 | well-known 接口可返回列表 | 接口存在但无筛选 | 未实现 |
| 发现约束 | 可被发现配置 + 可用性要求 | 可被发现配置 | 仅基本过滤 | 无约束 |
| 自动加载 | 启动时自动加载本地名片 | 手动注册可用 | 需修改代码 | 无法注册 |
任务 3.3 智能体能力匹配与自动发现
对应标准:GB/Z 185.4 + GB/Z 185.5
建议学时:4 学时
① 任务情境
发现服务已经建好了,三个智能体的能力名片也注册了。现在,校园生活助手要实现一个"智能调度"能力——
小李说:“帮我查一下下周四下午有没有能容纳 30 人的多媒体教室。”
校园生活助手自己不会查教室,但它可以:
- 向发现服务发起查询:“谁能查教室?”
- 发现服务返回"教室查询助手"的名片;
- 校园生活助手读取名片,获取访问地址;
- 自动向教室查询助手发起 A2A 调用,拿到结果。
这就是 GB/Z 185.5 规定的完整发现流程:发送发现请求 → 查询匹配 → 返回结果集 → 检查符合性 → 发起交互。
② 知识准备
一、完整的发现-交互闭环
二、能力匹配算法
💡 本项目使用基于信号词的关键词匹配算法,核心思路:
- ✅ 为每个智能体抽取一组"信号词"(名称、别名、技能名、标签、技能示例等);
- ✅ 判断用户需求的文本中,是否出现某个智能体的信号词(“信号词包含匹配”);
- ✅ 同时保留"用户关键词完整出现在该智能体可搜索文本中"的常规匹配;
- ✅ 命中任一信号词的智能体进入结果集,客户端按返回顺序取第一个进行处理。
💡 进阶思考:这里的「包含匹配」是最朴素的关键词拼接,不依赖分词工具,适合入门。实际工程中可引入 jieba 分词、BM25 评分,或用向量嵌入(Embedding)+ 余弦相似度实现语义匹配,效果更好但复杂度更高。本项目先用朴素匹配打基础。
③ 任务实施
步骤 1:编写自动发现客户端
新建文件 auto_discover_client.py:
# auto_discover_client.py
# 智能体自动发现与调用客户端
# 对应标准:GB/Z 185.4 + GB/Z 185.5
# 完整闭环:发现请求 → 匹配 → 读取名片 → 发起交互
import asyncio
import httpx
# ============================================================
# 配置
# ============================================================
DISCOVERY_URL = 'http://127.0.0.1:8000' # 发现服务地址(任务 3.2)
# ============================================================
# 第一阶段:发现——向发现服务查询合适的智能体
# 对应 GB/Z 185.5 路径一
# ============================================================
async def discover_agent(keyword: str) -> dict | None:
"""根据关键词向发现服务查询合适的智能体。
对应 GB/Z 185.5 路径一:发送发现请求 → 查询匹配 → 返回结果集。
参数:
keyword: 自然语言关键词(如"空教室""通知""菜单")
返回:
匹配度最高的智能体描述,无匹配时返回 None
"""
print(f'\n〔发现阶段〕正在向发现服务查询:「{keyword}」')
async with httpx.AsyncClient(timeout=10.0) as client:
# 1. 向发现服务发送查询请求
resp = await client.get(
f'{DISCOVERY_URL}/discover/search',
params={'keyword': keyword},
)
result = resp.json()
agents = result.get('agents', [])
print(f' 发现服务返回 {result.get("count", 0)} 个候选智能体:')
for a in agents:
print(f' · {a["name"]} —— {a["description"][:30]}...')
if not agents:
print(' × 未找到匹配的智能体')
return None
# 2. 检查符合性:选择第一个匹配结果
# (这里发现服务已按"是否命中信号词"过滤,若多个命中则按注册顺序返回;
# 实际工程中可再加相关性评分排序后取最优)
best_match = agents[0]
print(f'\n √ 选择最佳匹配:{best_match["name"]}')
print(f' 身份码:{best_match["agentId"]}')
print(f' 访问地址:{best_match["accessAddress"]}')
# 3. 打印其能力信息(对应 GB/Z 185.4 检查符合性)
print(f' 能力检查:')
for skill in best_match.get('skills', []):
print(f' · {skill.get("skillName", "")}:{skill.get("skillDescription", "")}')
return best_match
# ============================================================
# 第二阶段:调用——读取 Agent Card 并发起 A2A 交互
# 对应 GB/Z 185.5 第 6.1 条"发起交互"
# ============================================================
async def call_agent(agent_desc: dict, task_message: str) -> str:
"""根据智能体描述发起 A2A 交互。
参数:
agent_desc: 智能体描述(来自发现服务)
task_message: 要发送的任务消息
返回:
智能体的响应文本
"""
base_url = agent_desc['accessAddress']
agent_name = agent_desc['name']
print(f'\n〔调用阶段〕正在向「{agent_name}」发起交互...')
print(f' 任务内容:{task_message}')
# 延迟导入 a2a-sdk(避免未安装时整个模块无法加载)
from a2a.client import A2ACardResolver, ClientConfig, create_client
from a2a.helpers import new_text_message
from a2a.types import Role, SendMessageRequest
async with httpx.AsyncClient(timeout=60.0) as httpx_client:
try:
# 1. 读取 Agent Card(预置信息发现,路径二)
print(f' → 正在读取 Agent Card:{base_url}/.well-known/agent-card.json')
resolver = A2ACardResolver(
httpx_client=httpx_client,
base_url=base_url,
)
card = await resolver.get_agent_card()
print(f' √ 已读取名片:{card.name}')
# 2. 创建 A2A 客户端
config = ClientConfig(streaming=False, httpx_client=httpx_client)
client = await create_client(agent=card, client_config=config)
# 3. 构造并发送任务消息
message = new_text_message(task_message, role=Role.ROLE_USER)
request = SendMessageRequest(message=message)
print(f' → 正在发送任务...')
result_parts = []
async for event in client.send_message(request):
result_parts.append(str(event))
await client.close()
result_text = '\n'.join(result_parts)
print(f' √ 收到响应')
return result_text
except (httpx.ConnectError, httpx.ReadTimeout):
# 直接连接失败(目标端口无服务)
print(f' × 连接失败:{agent_name} 可能未启动')
print(f' 请确认 {base_url} 上的智能体正在运行')
return f'〔连接失败〕{agent_name} 未启动,请先运行对应服务'
except Exception as e:
# a2a-sdk 在拉取名片或交互时,若目标未启动,也会把连接异常包装成
# A2AError 抛出——这里按关键字识别,统一给友好提示。
msg = str(e).lower()
if ('connection' in msg or
'failed to fetch agent card' in msg or
'all connection attempts failed' in msg):
print(f' × 连接失败:{agent_name} 可能未启动')
print(f' 请确认 {base_url} 上的智能体正在运行')
return f'〔连接失败〕{agent_name} 未启动,请先运行对应服务'
print(f' × 调用出错:{e}')
return f'〔调用出错〕{e}'
# ============================================================
# 第三阶段:编排——完整的发现→调用闭环
# ============================================================
async def smart_dispatch(user_request: str):
"""智能调度:根据用户需求自动发现并调用合适的智能体。
这是完整的 GB/Z 185.5 发现流程:
发送发现请求 → 查询匹配 → 返回结果集 → 检查符合性 → 发起交互
"""
print('=' * 60)
print('智能调度引擎启动')
print(f'用户需求:{user_request}')
print('=' * 60)
# 第一阶段:发现
agent_desc = await discover_agent(user_request)
if not agent_desc:
print('\n× 无法处理:未找到能完成此任务的智能体')
return
# 第二阶段:调用
result = await call_agent(agent_desc, user_request)
# 第三阶段:返回结果
print(f'\n{"=" * 60}')
print('最终结果:')
print('-' * 60)
print(result)
print('-' * 60)
# ============================================================
# 主程序
# ============================================================
async def main():
"""模拟多个用户需求,体验自动发现与调度。"""
# 场景 1:查教室
await smart_dispatch('帮我查一下下周四下午有没有空教室')
# 场景 2:发通知(需要项目一的社团通知助手在运行)
# await smart_dispatch('通知社团成员下周二活动')
# 场景 3:查菜单
# await smart_dispatch('今天食堂有什么菜')
if __name__ == '__main__':
asyncio.run(main())
步骤 2:启动发现服务
确保任务 3.2 的发现服务正在运行(窗口 1):
# 窗口 1
python discovery_server.py
如果发现服务未运行,会报
ConnectionRefusedError。
步骤 3:运行自动发现客户端
# 窗口 2
python auto_discover_client.py
预期输出(无需启动教室查询助手也能看到发现阶段的效果):
============================================================
智能调度引擎启动
用户需求:帮我查一下下周四下午有没有空教室
============================================================
〔发现阶段〕正在向发现服务查询:「帮我查一下下周四下午有没有空教室」
发现服务返回 1 个候选智能体:
· 教室查询助手 —— 查询校园空教室信息,支持按时间段、人...
√ 选择最佳匹配:教室查询助手
身份码:1.2.156.3088.1.SCH001.TEAM01.A00003.I00001
访问地址:http://127.0.0.1:9998
能力检查:
· 空教室查询:根据时间段、人数、设备需求,查询可用的空教室
〔调用阶段〕正在向「教室查询助手」发起交互...
任务内容:帮我查一下下周四下午有没有空教室
→ 正在读取 Agent Card:http://127.0.0.1:9998/.well-known/agent-card.json
× 连接失败:教室查询助手 可能未启动
请确认 http://127.0.0.1:9998 上的智能体正在运行
============================================================
最终结果:
------------------------------------------------------------
〔连接失败〕教室查询助手 未启动,请先运行对应服务
------------------------------------------------------------
✅ 验证要点:
- 发现阶段成功:能从发现服务查到"教室查询助手"——说明 GB/Z 185.5 路径一生效;
- 调用阶段报连接失败:因为教室查询助手未启动(端口 9998 无服务)——这是正常的,说明客户端确实尝试了 A2A 调用。
步骤 4:(可选)体验完整闭环
如果你已完成项目一,可以启动社团通知助手(端口 9999),然后修改 auto_discover_client.py 的 main() 函数,取消注释场景 2:
async def main():
# 场景 2:发通知(需要项目一的社团通知助手在运行)
await smart_dispatch('通知社团成员下周二活动')
重新运行,即可体验完整的"发现 → 读取名片 → A2A 调用 → 获取结果"闭环。
预期输出(需要窗口 1 发现服务 + 窗口 3 社团通知助手都运行):
============================================================
智能调度引擎启动
用户需求:通知社团成员下周二活动
============================================================
〔发现阶段〕正在向发现服务查询:「通知社团成员下周二活动」
发现服务返回 1 个候选智能体:
· 社团通知助手 —— 接收活动信息,生成并发送社团通知,支持自定...
√ 选择最佳匹配:社团通知助手
身份码:1.2.156.3088.1.SCH001.TEAM01.A00001.I00001
访问地址:http://127.0.0.1:9999
能力检查:
· 社团通知发送:根据活动信息,生成通知文本并模拟发送给指定成员列表
〔调用阶段〕正在向「社团通知助手」发起交互...
→ 正在读取 Agent Card:http://127.0.0.1:9999/.well-known/agent-card.json
√ 已读取名片:社团通知助手
→ 正在发送任务...
√ 收到响应
============================================================
最终结果:
------------------------------------------------------------
(此处显示社团通知助手返回的 Task 对象,包含生成的通知文本)
------------------------------------------------------------
✅ 完整闭环验证成功:发现服务找到智能体 → 读取 Agent Card → A2A 调用成功 → 返回结果。
④ 知识链接:国标与实现的对照
| 实现要素 | 对应国标条款 | 说明 |
|---|---|---|
| Agent Card JSON 格式 | GB/Z 185.4 第 5 章 | 15 项描述属性 + 8 项技能属性 |
validate_card() 校验 | GB/Z 185.4 第 6 章注册流程 | 提交描述 → 信息检查 → 返回结果 |
/discover/register 接口 | GB/Z 185.4 第 6 章 + 185.5 第 5 章 | 描述注册到发现服务 |
/discover/search?keyword= | GB/Z 185.5 第 6.1 条 | 路径一:基于发现服务的发现 |
/.well-known/agents | GB/Z 185.5 第 6.2 条 | 路径二:基于预置信息的发现 |
discoverable 字段 | GB/Z 185.5 第 6.3 条 | 可被发现配置 |
A2ACardResolver 读取名片 | GB/Z 185.5 第 6.1 条"发起交互" | 读取名片后发起 A2A 调用 |
⑤ 常见问题排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
ConnectionRefusedError | 发现服务未启动 | 先运行 python discovery_server.py |
| 发现服务返回 0 个智能体 | 名片未加载 | 先运行 python agent_card_builder.py 生成 JSON |
| 搜索结果不匹配 | 关键词未出现在可搜索文本中 | 检查技能的 tags 和 skillDescription 是否包含相关词 |
A2ACardResolver 报错 | 目标智能体未启动或端口不对 | 检查 accessAddress 是否正确,对应智能体是否在运行 |
ModuleNotFoundError: a2a | 未安装 a2a-sdk | 在虚拟环境中执行 pip install "a2a-sdk[http-server]" |
⑥ 任务评价
| 评价维度 | 优秀(9-10分) | 良好(7-8分) | 合格(6分) | 需改进(<6分) |
|---|---|---|---|---|
| 发现功能 | 关键词搜索准确,能处理自然语言 | 关键词搜索可用 | 基本能查到 | 搜索失败 |
| 调用功能 | 完整闭环:发现→读名片→A2A调用 | 能读取名片并调用 | 能读取名片 | 无法调用 |
| 容错处理 | 连接失败有友好提示 | 基本错误处理 | 有 try-catch | 无错误处理 |
| 标准对照 | 能准确对应 GB/Z 185.5 各条款 | 能对应主要条款 | 能说出大致流程 | 无法对应 |
项目总结
知识体系回顾
项目三:智能体描述与发现
├── 任务 3.1:智能体能力名片编写(GB/Z 185.4)
│ ├── 15 项描述属性(身份4 + 基础3 + 访问4 + 能力3 + 技能1)
│ ├── 8 项技能属性
│ ├── GB/Z 185.4 与 A2A Agent Card 字段映射
│ └── 三大流程:注册 → 发布 → 变更
├── 任务 3.2:智能体发现服务搭建(GB/Z 185.5)
│ ├── 路径一:基于发现服务(API/GUI/LUI 查询)
│ ├── 路径二:基于预置信息(.well-known 地址)
│ ├── 发现约束:可被发现配置 + 可用性要求
│ └── 五个 API 接口实现
└── 任务 3.3:能力匹配与自动发现
├── 发现-交互完整闭环
├── 关键词匹配算法
└── A2ACardResolver + create_client 自动调用
能力进阶路径
| 层级 | 能力描述 | 本项目对应 |
|---|---|---|
| L1 认知 | 知道智能体描述有哪些属性 | 任务 3.1 知识准备 |
| L2 模仿 | 能照模板写 Agent Card | 任务 3.1 步骤 2 |
| L3 应用 | 能搭建发现服务 | 任务 3.2 |
| L4 分析 | 能根据需求匹配智能体 | 任务 3.3 |
| L5 创造 | 能设计新的匹配算法 | 课后拓展 |
课后拓展
- (基础) 为你身边的某个服务(如快递查询、天气查询)编写一份 Agent Card JSON,并通过
agent_card_builder.py的校验。 - (进阶) 在发现服务中增加"按自然语言语义匹配"功能:使用 sentence-transformers 计算用户需求与技能描述的余弦相似度,返回 Top-3 匹配结果。
- (实操) 尝试用 GUI 接口(网页)替代 API 接口,实现一个可视化的智能体发现页面。
- (探索) 查阅 GB/Z 185.4 附录中的描述变更流程,实现"描述变更后自动同步发现服务"的功能。
与下一项目的衔接
本项目解决了"智能体如何描述自己、如何被发现"的问题。但发现只是第一步——找到对方后,两个智能体之间如何高效地交互?是点对点还是群组?消息格式是什么?任务状态怎么管理?
这些问题将由 GB/Z 185.6(智能体交互) 和 项目四 来回答。
本文为原创教程,转载请注明出处。
如果本文对你有帮助,欢迎 点赞👍 收藏⭐ 关注➕ 一键三连,这是我持续输出的最大动力!
有任何疑问或建议,欢迎在评论区留言交流。下一篇【项目四:智能体交互——从点对点到群组协作】将带你深入 GB/Z 185.6,敬请关注专栏 👉 《智能体互联网(IoA)技术实践教程》

3021

被折叠的 条评论
为什么被折叠?



