第3课:工具系统与通信协议
核心机制⭐⭐⭐⭐ 高频↔ Hermes 源码对照MCPA2A
一、概述:为什么工具系统是 Agent 的"手脚"
Agent 的能力边界 = 工具系统的能力边界。
LLM 知道万事万物的知识(预训练),但无法直接行动:它不能查数据库、发邮件、写硬盘、调用 API。工具系统给 LLM 装上了"手脚"。
工具系统有 3 个关键层次:
- Function Calling(协议层)— LLM 如何表达"我想调用一个工具"
- Tool Registry(实现层)— 工具如何定义、注册、分发
- MCP(标准化层)— 工具如何跨框架、跨语言被发现和调用
本节课从底向上拆解每一层。
二、Function Calling:LLM 的"工具语言"
2.1 核心流程(第2课 Agent Loop 里面的关键一步)
// 用户请求
User: "帮我查一下上海的天气"
// Step 1: 系统把工具定义传给 LLM
LLM.input = messages + [tool_definition_1, tool_definition_2, ...]
// 工具定义长什么样?
// 每个工具 = name + description + parameters(JSON Schema)
// Step 2: LLM 输出(不是自然语言,是结构化 JSON)
{
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"上海\"}"
}
}]
}
// Step 3: Agent 框架解析这个 JSON → 调用实际函数
result = get_weather(city="上海")
// Step 4: 把结果拼回对话
messages.append({
"role": "tool",
"tool_call_id": "call_abc123",
"content": '{"temp": 28, "humidity": 65}'
})
// Step 5: LLM 看到工具结果 → 生成自然语言回复
LLM: "上海现在 28°C,湿度 65%。"
2.2 工具定义:JSON Schema 是核心
每个工具都需要一个严格的 JSON Schema 描述,LLM 根据这个描述决定是否调用。
Hermes 中的真实例子(web_search 工具):
// 来自 /usr/local/lib/hermes-agent/tools/web_tools.py
// Hermes 真实代码,我亲自从源码读来的
WEB_SEARCH_SCHEMA = {
"name": "web_search",
"description": "Search the web for information. Returns up to 5 results...",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to look up on the web."
},
"limit": {
"type": "integer",
"description": "Maximum number of results.",
"minimum": 1,
"maximum": 100,
"default": 5
}
},
"required": ["query"]
}
}
关键原则:
name 唯一标识,LLM 靠这个决定调哪个工具
description 极其重要——LLM 靠它理解这个工具是干嘛的、什么时候用。写得越清楚,LLM 越不会乱调
parameters 是 JSON Schema 格式,LLM 根据 schema 的约束生成 arguments
required 告诉 LLM 哪些参数必须传
default 可以让 LLM 省略不传
2.3 OpenAPI Specification vs JSON Schema 的区别
面试常问:"工具定义和 REST API 有什么区别?"
REST API 工具描述
─────────────────
POST /weather
Headers: { "Authorization": "Bearer xxx" }
Body: { "city": "string" }
→ 需要人理解 API 文档 → 自己编码调用
LLM Function Calling
────────────────────
{
"name": "get_weather",
"description": "...",
"parameters": { ... }
}
→ LLM 直接理解描述 → LLM 自己决定参数 → 框架执行
核心区别:LLM 不是通过代码调用 API,而是通过语义理解调用。工具的 name 和 description 对 LLM 的重要性,远大于对传统程序的重要性。
2.4 Hermes 的 Tool Registry 架构
从 Hermes 源码我读到的注册机制:
// 每个工具文件在模块加载时自动注册
// 例如 tools/web_tools.py 的底部:
from tools.registry import registry
registry.register(
name="web_search",
toolset="web",
schema=WEB_SEARCH_SCHEMA, // ↑ 上面的 schema dict
handler=handle_web_search, // 实际执行函数
check_fn=check_network, // 可用性检查
requires_env=["OPENAI_API_KEY"],
)
// ToolRegistry 内部结构:
class ToolEntry:
name: str // 工具名
toolset: str // 属于哪个工具集(web, terminal, file...)
schema: dict // JSON Schema
handler: Callable // 实际处理函数
check_fn: Callable // 检查是否可用
is_async: bool
// 一个 ToolRegistry 单例持有所有工具:
ToolRegistry._tools: Dict[str, ToolEntry]
设计亮点:
- 工具集隔离:每个工具属于一个 toolset,用户可以用
hermes tools enable/disable 控制可见性
- 可用性检查:工具注册时可以附带
check_fn,动态判断当前环境是否可用(如 Docker 没装就不暴露 docker 工具)
- 动态 Schema:
dynamic_schema_overrides 支持运行时修改工具描述(比如 delegate_task 的描述根据配置动态生成)
- MCP 桥接:外部 MCP 服务器注册的工具自动注入到同一个 Registry,和内置工具统一调用
三、MCP:工具标准的"HTTP 时刻"
MCP = Model Context Protocol(Anthropic 主导,2024年底推出)
3.1 为什么需要 MCP?
在 MCP 之前,每个 Agent 框架的工具有自己的注册方式:
- LangChain 用
@tool 装饰器
- OpenAI 用
function_call API 参数
- Hermes 用
registry.register()
- Semantic Kernel 用
KernelPlugin
它们本质上都是把 name + description + schema 告诉 LLM,但格式各不相同。每次换框架,工具就要重写一次。
MCP 要解决的问题:
MCP = 工具世界的 HTTP 协议
HTTP 之前:每个应用自己实现通信协议 → 各自为政
HTTP 之后:一套标准,万维网互联
MCP 之前:每个框架自己定义工具协议 → 生态割裂
MCP 之后:一套标准,工具一次开发到处可用
3.2 MCP 架构
┌──────────────┐ MCP Protocol ┌──────────────┐
│ │ ◄══════════════════════► │ │
│ MCP Client │ JSON-RPC 2.0 over │ MCP Server │
│ (Agent框架) │ stdio / SSE / Streamable │ (工具提供方) │
│ │ HTTP │ │
└──────────────┘ └──────────────┘
│
▼
┌──────────┐
│ API │
│ Database │
│ Files │
└──────────┘
// 核心操作
// 1. tools/list → Server 返回所有可用工具的定义(name + schema)
// 2. tools/call → Client 调用指定工具,传参数
// 3. resources/list → Server 返回可读取的资源列表
// 4. resources/read → Client 读取资源内容
// 5. prompts/list → Server 返回预设的 prompt 模板
3.3 MCP 的 3 种传输方式
| 传输方式 | 场景 | 示例 |
| stdio | 本地子进程,低延迟 | 一个 Python 脚本作为 MCP Server,被 Hermes 启动 |
| SSE (Server-Sent Events) | 远程服务,单向推送 | 远程的文件系统 MCP 服务 |
| Streamable HTTP | 远程服务,双向流 | 企业内部的数据库 MCP 服务 |
3.4 Hermes 的 MCP 支持
Hermes 本身内置了 MCP Server(hermes_tools_mcp_server.py),如果你运行:
hermes mcp-serve
# 启动一个 MCP 服务器,把 Hermes 的所有工具
# 以 MCP 协议暴露给其他 Agent 框架
这意味着:你可以用 Claude Desktop 连接 Hermes 的 MCP 服务器,使用 Hermes 的工具——工具一次开发,到处可用。
四、A2A:Agent 之间的"HTTP 协议"
A2A = Agent-to-Agent Protocol(Google 主导,2025年4月推出)
4.1 和 MCP 的区别
🔧 MCP
Agent ↔ 工具
让 Agent 能调用外部工具
类比:
USB 接口
连接设备和外设
🤝 A2A
Agent ↔ Agent
让 Agent 之间直接通信
类比:
HTTP 协议
连接服务器和服务器
📞 Function Calling
LLM ↔ 框架
LLM 表达"我想调一个工具"
类比:
指令集
CPU 告诉主板要做什么
4.2 A2A 核心概念
- Agent Card:每个 Agent 发布一个"能力卡",描述它擅长什么、怎么联系
- Task:A 给 B 发一个任务,B 返回任务状态(pending/running/completed/failed)
- Streaming:B 可以边做边给 A 推送进度和中间结果
- Skill Discovery:A 可以问 B "你会什么?"
// A2A 工作流程
Agent A (编排器) Agent B (数据库Agent)
│ │
│── Agent Card (描述自己能做什么) ──► │
│ │
│◄── Agent Card (返回B的能力描述) ── │
│ │
│── Task: "查公司2024年营收数据" ──► │
│ │── SELECT ... FROM finance
│◄── Status: "working" (持续推送) ── │
│ │
│◄── Result: {data: [...], status: done}│
4.3 标准化的意义
MCP + A2A 合在一起,补全了 Agent 生态的通信标准:
┌─────────────────────────────────────────┐
│ 🧠 Agent System │
│ │
│ ┌──────────┐ A2A ┌──────────┐ │
│ │ Agent A │◄══════►│ Agent B │ │
│ │ (编排器) │ │ (专家) │ │
│ └────┬─────┘ └──────────┘ │
│ │ MCP │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ MCP Svr1 │ │ MCP Svr2 │ │
│ │ (数据库) │ │ (文件系统)│ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
MCP: Agent ↔ 工具(竖线)
A2A: Agent ↔ Agent(横线)
五、对比与适用场景
复杂度
▲
│
自研 Function Calling │ 低复杂度,实现简单
+ JSON Schema │ 适合单一框架、小规模
│
───────────────────────────
│
MCP 标准化工具接口 │ 中复杂度,一次开发到处用
│ 适合多框架共存、工具共享
│
───────────────────────────
│
A2A + MCP 全栈 │ 高复杂度,大规模
标准体系 │ 适合企业级多Agent系统
│
└────────────────────► 适用规模
六、面试问答
🎯 Q1:"请设计一个工具系统,Agent 怎么知道该调用哪个工具?"
框架:
1. 工具定义:每个工具用 JSON Schema 描述 name + description + parameters
2. 工具注册:集中式 Registry,按工具集分组(file/web/terminal...)
3. LLM 理解:工具定义随每次 LLM 调用一起传入,LLM 根据 description 决定调哪个
4. 参数填充:LLM 输出 JSON 格式的 arguments,框架解析并校验
5. 执行与反馈:框架执行工具,结果拼回对话供 LLM 继续推理
加分点:提到可用性检查(check_fn)、动态 schema(dynamic_schema_overrides)、工具集隔离。
🎯 Q2:"Function Calling 和 MCP 有什么区别?什么时候用哪个?"
框架:
Function Calling 是 LLM API 层的协议——定义 LLM 如何输出工具调用请求。精度低,但实现简单。
MCP 是工具层的协议——定义工具如何被发现和调用。精度高,但需要额外基础设施。
选型:
- 内部小工具(< 10 个)→ 直接 Function Calling
- 跨框架共享工具、或第三方工具 → MCP
- 完全云原生企业 → MCP + A2A
🎯 Q3:"工具调用失败了怎么办?怎么设计容错?"
5 层容错:
1. Retry:网络/临时错误 → 退避重试(exponential backoff + jitter)
2. Fallback:主工具挂了 → 切备用工具(如 web_search 切 bing_search)
3. Graceful Degradation:工具不可用 → 告诉 LLM"这个工具现在不可用"让 LLM 决定替代方案
4. Idempotency 幂等:工具支持幂等(如查询、读文件天然幂等;下单需要去重)
5. Timeout:每个工具有独立超时,超时后 LLM 决定继续等待还是放弃
七、课后行动
- ✅ 理解 JSON Schema 格式——这是任何 Agent 框架的基石
- ✅ 想一个你自己的工具需求(比如"查公司内部 Wiki"),用 JSON Schema 写出它的定义
- ✅ 梳理 MCP vs A2A vs Function Calling 的区别(面试高频对比题)
- ⏭ 下节课:记忆与上下文管理——Token 预算、滑动窗口、RAG
📁 0003-tools-protocols.html
📁 源码参考:/usr/local/lib/hermes-agent/tools/registry.py (589行)
📁 源码参考:/usr/local/lib/hermes-agent/tools/web_tools.py → WEB_SEARCH_SCHEMA 等
📝 有问题随时问。