AI 速读
从 LLM 调用链路到命名与描述技巧,理解 Agent 眼中的工具长什么样。
上一篇聊了「Agent 工具是用户界面」这个核心理念。这一篇把镜头切到模型侧:**LLM 究竟是如何「看到」并调用你的工具的?**理解了这条链路,设计约束就一目了然。
一个关键认知:LLM 不会「执行」任何函数#
所谓的 function calling / tool calling,本质上是 LLM 与应用程序之间的一个多轮对话协议:
┌─────────────┐ ┌─────────────┐
│ │ ① 发送请求 + 工具定义 │ │
│ │ ─────────────────────────────▶│ │
│ │ ③ 返回「工具调用请求」 │ │
│ 应用程序 │ ◀─────────────────────────────│ LLM │
│ │ ⑤ 返回工具执行结果 │ │
│ │ ─────────────────────────────▶│ │
│ │ ⑥ 生成最终回复(或继续调用) │ │
│ │ ◀─────────────────────────────│ │
└──────┬──────┘ └─────────────┘
│ ④ 执行实际的函数调用
▼
┌─────────────┐
│ 外部工具 │
│ (API/DB/..) │
└─────────────┘textLLM 只做一件事:生成文本。执行永远发生在应用程序侧。
完整流程五步:定义工具 → LLM 决策并返回调用请求 → 应用程序执行函数 → 结果回传 → LLM 生成最终回复。
以 OpenAI API 为例,工具通过 tools 参数传入,每个定义包含三个部分:
tools = [
{
"type": "function",
"name": "get_weather", # 工具名称
"description": "获取指定城市的当前天气", # LLM 理解用途的关键
"parameters": { # 参数的 JSON Schema
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如:深圳、北京"
}
},
"required": ["location"]
}
}]pythonname、description、parameters,就是 LLM「看到」的工具的全部信息。
工具定义如何被模型消费#
JSON 只是中间格式#
JSON 对 LLM 并不友好:边界模糊({}[]" 在普通文本中频繁出现)、语法严格(少个逗号就解析失败)、转义噩梦(参数含代码片段时极易出错)、远距离依赖(嵌套括号匹配)、缺乏显式结束标记。
因此许多提供商内部使用类 XML 格式表示工具调用:标签边界明确、自描述、预训练语料丰富、容错更好。一些提供商(如 OpenAI 的 Strict Mode)还用 Constrained Decoding 在解码时动态限制候选 Token,保证输出合法——代价是可能影响速度和表达能力。
工具定义是 System Prompt 的一部分#
工具定义会被注入 system prompt,占用上下文窗口,带来两个直接影响:
- 上下文占用:工具定义的 Token 越多,留给对话的空间越少;
- Prompt Caching:动态修改工具列表会导致 KV cache 失效,显著增加延迟和成本。
这也是要用各提供商原生 function calling 机制的原因——模型在训练时对这些格式做过专门优化。
工具数量的红线#
| 工具数量 | Token 消耗 |
|---|---|
| 10 个 | 2,500-3,000 tokens |
| 20 个 | 5,000-6,000 tokens |
| 50 个 | 12,500-15,000 tokens |
| 100 个 | 25,000-30,000 tokens |
工具多了,模型面临选择困难、注意力稀释、Prompt 拥挤。OpenAI 官方建议控制在 20 个以内;实践中 1-5 个精心设计的工具,胜过 20 个随意堆砌的。
MCP 的定位与命名陷阱#
MCP 没有改变 tool calling 机制本身,它解决的是标准化定义与暴露工具的问题:在 MCP 出现之前,你要为每家 LLM 单独适配工具 Schema(N×M 问题);MCP 引入标准化中间层后,Server 按协议暴露工具,Client 负责转换成各家模型能理解的格式。
但 MCP 工具通常带 mcp_<server-name>_<tool-name> 前缀,会带来:
- 与自带工具的冲突或歧义;
- 工具名过长(如 TRAE 有 60 字符限制);
- 工具数量爆炸(多个 Server 轻易累计超过 20 个的建议上限);
- 命名空间污染(多个 search 工具描述相似);
- Schema 兼容性差异(不同 LLM 对 JSON Schema 支持不一);
- 参数传递的不确定性(类型错误、必填缺失等)。
应对:尽量用简单、扁平的 Schema,做好防御性编程。
设计技巧:让工具「自解释」#
Agent 眼中,每个工具就是一个三元组:工具 = (名称, 描述, 参数 Schema)。没有代码实现,没有注释,没有文档链接。
所以核心原则是防呆式语义化——假设 Agent 会完全按字面意义理解你的工具,不做任何「显然」的推断:
# 依赖隐含上下文,Agent 猜不出 id 是什么
def get_user(id):
"""获取用户信息"""
# 显式语义化,Agent 一次就能传对
def get_user_by_uuid(user_uuid: str):
"""
根据 UUID 获取用户信息。
参数:user_uuid: 用户的唯一标识符,格式为 'usr_xxxxxxxx'
返回:用户信息的 JSON 对象,包含 name、email、created_at 等字段
"""python命名三原则:
- 动词优先:
create_github_issue、send_slack_message; - 命名即分类:一致的前缀帮 Agent 快速筛选;
- 长度适中:工具名保持在 30-50 字符以内。
描述要回答四个问题:做什么?什么时候用?有什么限制?返回什么?参数描述善用示例、标注必填/可选、说明默认值与失败情况。
关键洞察#
设计工具的本质,是在设计 Agent 的认知体验。好的工具设计,就是不断减少 Agent 的认知负担。
出处
本文参考小夏《如何让你的 Agent 更准确:MCP 工具设计技巧(上)》(TRAE 公众号文章 ↗),对调用链路与设计要点部分做了梳理与自己的理解。示例与数据归原作者。