上下文解决「看见什么」,工具解决「能对世界做什么」。本篇对应书的第 4 章:在工具从十几个涨到成百上千、任务从同步问答拉到跨邮件与电话的异步世界时,动作空间该如何设计。

两个核心压力:

  1. 选择:工具说明本身就能撑爆窗口,模型如何找到「那一个」?
  2. 时间:长任务、用户打断、外部回调并存时,如何避免同步死等?

五类工具:谁发起、作用在哪

类型调用方向作用对象例子
感知Agent 主动获取信息web_searchread_filegrep
执行Agent 主动改变世界write_fileshell_execsend_email
协作Agent 主动其他 Agent / 人类spawn_subagent、请求审批
事件触发Agent 注册,外部触发唤醒 Agentset_timermonitor_shellconnect_channel
用户沟通Agent 主动触达用户reply_to_user、卡片消息、推送

前三类在同步 ReAct 里最常见;后两类在「秘书型」常驻 Agent 里变成标配——没有事件入口,系统只能等用户下一句。

能力形态:专用工具还是 Skill + 通用执行器

形态是什么适合
专用工具结构化 function + 严格 schema嵌套参数、强权限、需审计的稳定 API
Skill + 通用执行器自然语言流程 + bash / 代码解释器变更频繁、步骤组合多、模型足够强

例:「部署应用」写成 Skill(build → docker → kubectl),用终端逐步执行,不必为每步挂一个 MCP 工具。

三维决策:

  1. 参数复杂度——联合校验多 → 专用 schema 更稳
  2. 变更频率——周周变的流程 → 改 Skill 文本比改代码发布便宜
  3. 模型能力——弱模型更吃结构化引导;强模型更能消化 Skill

默认倾向:通用执行器优先,除非安全、权限粒度或性能有明确理由。计算器十个不如沙箱 Python 一个;但生产库写操作仍应封装成可审计的专用接口。

粒度:该合就合,该分就分

过细:工具数膨胀,选择错误率升(经验上过百个后,即使强模型也明显吃力)。
过粗:单工具语义糊,参数变成大杂烩。

整合标准看功能相似性 + 场景重叠extract_pdf / extract_docx / extract_pptx 都是「路径进、文本出」→ 合并为 read_document(file_type=...) 更清晰。OCR 与视频关键帧提取虽都叫「提取」,参数形态与延迟差太大,硬合并会让模型更懵。

高频且参数集独特的能力,可保持独立,避免被通用接口拖慢。

描述质量:告诉模型「何时用 / 不能做什么」

工具描述决定调用准确率。调试原则:频繁选错时,先改描述,再怀疑模型。

写描述时优先这几项:

  1. 触发条件——「当需要实时信息或未知事实时使用」,优于只写「搜索相关内容」。
  2. 边界与反例——「仅匹配文件名,不搜文件内容」;大多数失败来自模型不知道工具不能做什么。
  3. 参数用例子——timestamp: RFC3339,如 2024-03-15T14:30:00Zphone: E.164,如 +8613888888888
  4. 返回形状——JSON 字段列表,减少后续解析幻觉。
  5. 代价提示——「完整下载网页可能 5~10 秒;只要元信息请用 get_page_metadata」。
  6. 1~5 个真实调用示例——schema 说不清的隐式约定(秒 vs 毫秒、过滤嵌套)靠例子传达。

对比两段描述,差别一目了然:

# 弱
search_files: 搜索文件

# 强
search_files:
  何时用:只知道文件名片段、要在工作区内定位路径时。
  不要用:需要按文件内容关键字查找时(请用 grep_file)。
  参数:
    pattern: glob 模式,例如 "**/*.ts"、"src/**/Button.tsx"
    不含正则;匹配的是路径而非文件内容。
  返回:字符串数组,每项为相对工作区的路径;最多 200 条,
        超出时 truncated=true 并提示收窄 pattern。
  示例:{"pattern": "**/test_*.py"}

参数保真:禁止静默改写模型意图

比缺功能更隐蔽的故障:参数传递层静默转换输入

案例形态:读文件工具原样返回中文弯引号;编辑工具却把弯引号改成直引号 → old_string 永远匹配失败 → 模型反复重试却无法自诊断。另一类是静默注入:所有 git commit 被附加额外 flag,旧版 Git 直接报错,模型只会改 commit message 措辞。

原则:模型感知的世界与工具操作的世界不得有系统性偏差。 必须规范化时,要在描述与返回值里显式告知,而不是「好心」改写。

感知工具:控制信息量

感知只读,可缓存、可并行。设计要点:

  • 搜索返回候选列表(标题、位置、摘要),不要一次倾倒全文;支持分页 / cursor。
  • read 支持 offset/limit;截断必须显式(「已显示 1–200 / 共 5000 行」),静默截断会导致基于残缺信息的错误决策。
  • 输出超阈值时做上下文感知压缩或落盘 + 引导 read_file
  • 多模态:纯文字可 OCR/提取;布局敏感(复杂表、UI)保留图像给视觉模型。

执行工具:安全与验证闭环

执行会改变世界,错误代价不对称。分层防护:

  1. 输入验证——路径穿越、命令注入、类型范围;异常则快速失败,不「智能修正」。
  2. 权限——工作目录白名单、危险命令策略、API 配额;黑名单只能作一层,Shell 组合爆炸要语义层理解(第 5 章)。
  3. 提议者-审核者——高风险操作前,独立模型(最好不同家族、能力相近)审批;拒绝理由以 tool 结果回写轨迹。
  4. Sidecar——与主流式输出并行的轻量分类,只看结构化 {tool, args},不看主模型自由文本,降低话术操纵;连续拒绝则熔断到人工。
  5. 自动验证——write_file 后跑 linter,错误结构化返回,形成执行-验证-反馈环。
  6. 长输出——头尾保留 + 中间省略 + 完整路径。
  7. 幂等——可重试操作带 idempotency key;不可幂等(发邮件、转账)用预检-确认两段式。

沙盒不是 venv:venv 只隔离依赖。隔离强度从 OS 机制 → 容器 → microVM 递增,按威胁模型选型。

协作工具:子 Agent 与人在回路

子 Agent 价值是专业化与并行,不是为拆而拆。上下文传递四档:

策略传什么适合
最小化仅调用参数查天气等高频简单
手动筛选显式字段可控但费设计
自动裁剪规则:近 N 轮 + 用户画像默认中等任务
LLM 生成按隐私与任务动态摘要复杂,多一次调用

提示词里标注来源([FROM_MAIN_AGENT] / [FROM_USER] / [TOOL_RESULT]),降低注入与角色混淆。HITL(Human-in-the-Loop,人在回路)要配超时与默认保守策略,并把批准/拒绝记入可检索案例或训练数据。

MCP:互操作标准与三个工程坑

MCP(Model Context Protocol) 是工具与数据源的开放协议:MCP Server 暴露工具/资源/提示模板,Client(IDE / Agent 框架)用统一方式发现与调用。价值是一次开发、多客户端复用。

实践中的三个坑:

  1. 同步请求-响应为主——会话内可有 progress / notification,但跨会话唤醒、多事件源离线触发,仍要在协议之上自建事件队列与运行时。
  2. 上下文开销——少量 MCP 服务器即可引入数万 token 的工具定义。缓解:默认只注入名称索引,按需拉 schema(与 Skills / 工具搜索同构);有实测可显著降低 MCP 相关 token。
  3. 信任模型——接入第三方 server ≈ 把不可信文本注入上下文,并可能交出凭证。风险包括:描述投毒、供应链篡改、同名工具遮蔽(shadowing)、凭证滥用。缓解:审计 description、锁定版本、最小权限凭证、运行时 Sidecar。

事件驱动的异步 Agent

同步 ReAct 假设:工具调用的下一条必须是结果。真实助理场景则是:用户随时插话、邮件随时到、电话中途要 OTP。

把输入统一建模为结构化事件(来源、渠道、内容、优先级、关联上下文),经队列进入同一条轨迹。处理策略按紧急度分三档:

策略何时行为
取消式用户「停止」、高优告警中止当前推理/同步工具,清空队列,合并事件后重推 LLM
队列式常规补充、工具结果不打断,完成当前步后批量追加
并行式与主任务无关的轻量查询旁路会话快答,结果标记并行写回主轨迹
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Any


class Urgency(Enum):
    CANCEL = "cancel"   # 立即中止当前步
    QUEUE = "queue"     # 当前步结束后批量并入
    PARALLEL = "parallel"


@dataclass
class Event:
    """统一事件:来源 + 载荷 + 紧急度,进入轨迹前先结构化。"""
    source: str
    channel: str
    payload: dict[str, Any]
    urgency: Urgency


def classify_urgency(event: Event) -> Urgency:
    """规则优先;语义模糊时再交给小模型分类(此处仅示意规则层)。"""
    if event.source in {"user.interrupt", "supervisor.instruction"}:
        return Urgency.CANCEL
    if event.payload.get("priority") == "high" and event.channel == "pager":
        return Urgency.CANCEL
    if event.payload.get("independent") is True:
        return Urgency.PARALLEL
    return Urgency.QUEUE


def merge_into_trajectory(trajectory: list[dict], events: list[Event]) -> list[dict]:
    """把一批事件追加为 user/tool 风格消息,供下一轮 LLM 一并看见。"""
    for e in events:
        trajectory.append({
            "role": "user",
            "content": f"[event source={e.source} channel={e.channel}] {e.payload}",
        })
    return trajectory


if __name__ == "__main__":
    e = Event("user.interrupt", "im", {"text": "停止,我说错了"}, Urgency.QUEUE)
    print(classify_urgency(e))
    # 典型输出:Urgency.CANCEL

事件触发工具三类:

  • 定时器——一次性或 cron 循环;无推送的外部系统用轮询补洞
  • 后台监控——盯 shell 新输出或关键词,避免「干等到底」或「空转刷屏」
  • 外部 Channel——邮件/Webhook/电话中途事件即时推送(对比纯 Heartbeat 的分钟级延迟)

用户沟通工具:当渠道不再是「单一 session 回显 assistant 文本」时,reply_to_user / 卡片 / 推送成为显式动作。通知对象是审批者 → 归协作;是终端用户 → 归用户沟通。

常驻异步 Agent 往往还要 虚拟身份 + 隔离环境(独立邮箱/浏览器档案/工作区),避免直接持有用户全部账号;与用户真实账号交互时用 HITL 登录,会话令牌限时复用。

主动工具发现

工具上百时,平铺 schema 既贵又干扰决策。主动工具发现:上下文默认只有索引(名称、短描述、分类),模型通过 tool_search 类工具按需加载完整定义。

层次化目录帮助定位,例如:搜索 / 读取 / 解析 / 结构化查询。业界公开数字量级:按需检索可将工具使用准确率从约五成提升到七成以上(视基准与模型而异)。这与第 2 章「前缀只留简述、完整 schema 追加轨迹」一致,并要求模型训练中见过「对话中途出现工具定义」的模式。

本篇收束

  1. 五类工具覆盖感知、改变世界、协作、被世界唤醒、主动触达用户。
  2. 形态上 Skill+执行器与专用工具按复杂度/变更/模型能力选型;粒度按语义合并。
  3. 描述写触发与边界,参数保真禁止静默改写;执行侧多层约束与验证。
  4. MCP 解决互操作,不自动解决异步、token 税与供应链信任。
  5. 异步靠事件统一建模 + 紧急度策略;规模化靠主动发现而非无限加长 tools 字段。

下一篇进入第 5 章:Coding Agent 与代码元能力——为什么通用 Agent 常以编码运行时为内核,以及代码如何超出「写程序」本身。

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:04. 工具设计:手脚的粒度、描述与异步

本文链接:https://www.sshipanoo.com/blog/ai/ai-agent-indepth/04-工具设计/

本文最后一次更新为 天前,文章中的某些内容可能已过时!