第 14 章 工具增强 Tool Augmentation
本章要解决的问题
Agent 的能力边界取决于工具库------工具该设计哪些、怎么让模型正确选择和调用?
章节大纲
- 14.1 工具库设计原则
- 14.2 搜索、计算、代码执行等工具实战
- 14.3 工具选择策略
- 14.4 主流框架对照与变体
- 🛠 解决方案:工具幻觉参数识别与校验
14.1 模式原理:工具是 Agent 的手脚
14.1.1 一句话定义
工具增强(Tool Augmentation)是通过让模型调用外部函数/API,把它的能力从"纯文本推理"扩展到"可计算、可检索、可执行、可写系统"。模型负责"想",工具负责"做"。
这是全书第 5 章(工具调用 Tool Calling)的"模式化延伸":第 5 章讲的是单次调用的机制与排错,本章讲的是工具库的整体设计哲学------工具库怎么组织、暴露多少、怎么让模型选对。
14.1.2 为什么工具是关键分水岭
一个残酷的事实:没有工具的模型是"嘴上巨人"。让它做数学题,它可能一本正经算错;让它查最新股价,它只能胡编。工具的引入让模型发生了质变:

图 1:无工具 vs 有工具
| 能力 | 无工具 | 有工具 |
|---|---|---|
| 数学计算 | 易算错、不可复现 | 调计算器,精确 |
| 实时信息 | 知识截止日前的旧数据 | 调搜索/API,最新 |
| 代码执行 | 只能"读"代码 | 能跑、能验证 |
| 数据库操作 | 只能描述 | 能查询真实数据 |
| 系统动作 | 只能建议 | 能下单/发邮件/写文件 |
工具是第 13 章结论的"最优解":知识型错误反思修不了,工具能------模型不知道就调用工具去查,这是"能力外挂"。
14.1.3 工具库设计的黄金比例
工具库不是越多越好。每多一个工具定义,就多一份上下文占用,也增加模型选错的概率。设计原则:
markdown
工具数量与质量:
- 3~5 个核心工具:覆盖 80% 高频需求(最佳状态)
- 6~10 个:中大型系统,按域分组
- 10+ 个:必须分组 + 显式路由(见 14.3),否则工具选择错误率可能显著上升
宁可少而精,不可多而滥。 每加一个工具前问自己:这个工具是覆盖一个真实的高频需求,还是"可能有用"?后者不加。
14.2 工具库设计原则
14.2.1 工具定义五要素
一个高质量的工具定义(Function Schema)必须包含:

图 2:工具定义五要素
| 要素 | 说明 | 例子 |
|---|---|---|
| 名称 | 动词开头、语义清晰 | query_order_status 而非 get |
| 描述 | 说明用途 + 何时用 + 何时不用 | "查询订单状态,当用户询问物流/发货进度时使用" |
| 参数 Schema | 类型、必填、枚举、默认值 | order_id: string, required |
| 边界 | 声明不能做的事 | "仅支持近 90 天订单" |
| 返回说明 | 返回结构说明(帮助模型理解结果) | "返回 JSON:{status, eta}" |
描述是工具的灵魂。模型"选工具"主要靠读描述,描述写得好,选择准确率直接提升。一个反例 vs 正例:
text
❌ 描述:"处理订单相关请求"
✅ 描述:"查询订单物流状态。当用户询问'货到哪了/什么时候发货/快递进度'时使用;
不支持退款申请(用 apply_refund)。"
14.2.2 工具名称与描述的对齐测试
用 20~30 条真实用户问题测"模型能否为每个问题选对工具"。如果某工具的选中率低于 60%,不是模型笨,是描述不清楚------回去改描述。工具的选型准确率是可以用评测集量化的(呼应第 20 章)。
14.2.3 工具封装层:别让模型直接碰原始系统
生产环境的铁律:给模型暴露"安全封装"而非"原始系统"。

图 3:安全封装层
| 原始系统 | 模型不应直接访问 | 应暴露的封装 |
|---|---|---|
| 生产数据库 | 直接 SQL | query_reports(start, end) 只读 + 限字段 |
| 订单系统 | 直接改库 | create_order(items) 带权限校验 |
| 文件系统 | 任意路径读写 | read_workspace(path) 白名单路径 |
| 邮件 | 直接发送 | send_email(to, subject, body) 审核后发 |
封装层的三个作用:安全(权限/白名单)、稳定性(异常兜底)、可观测(统一日志)(呼应第 22 章安全、第 21 章可观测)。
14.3 完整示例:搜索、计算、代码执行三件套
实现一个"分析师助手"的工具库------三个经典工具,覆盖"查资料、算数字、验证代码"三大高频需求:

图 4:工具增强闭环
python
import json, subprocess, re
from openai import OpenAI
client = OpenAI(base_url="https://api.deepseek.com", api_key="<你的Key>")
# ── 工具 1:计算器(防模型算错数学)──
def calculator(expr: str) -> str:
"""安全表达式求值:仅允许数字/运算符/括号"""
if not re.fullmatch(r"[0-9+\-*/().\s]+", expr):
return json.dumps({"error": "非法表达式"})
try:
result = eval(expr, {"__builtins__": {}}, {})
return json.dumps({"result": result})
except Exception as e:
return json.dumps({"error": str(e)})
# ── 工具 2:代码执行沙箱(验证代码正确性)──
def run_python(code: str) -> str:
"""在隔离沙箱执行 Python 代码,返回 stdout/stderr。
注意:本示例仅做超时和输出截断,不具备真正的沙箱隔离。
生产环境应使用容器(Docker)、Firejail、gVisor 或云端沙箱服务隔离执行。"""
try:
proc = subprocess.run(
["python3", "-c", code],
capture_output=True, text=True, timeout=10,
)
return json.dumps({
"stdout": proc.stdout[:2000],
"stderr": proc.stderr[:2000],
"exit_code": proc.returncode,
})
except subprocess.TimeoutExpired:
return json.dumps({"error": "执行超时"})
# ── 工具 3:知识检索(查最新/外部信息)──
def search(query: str) -> str:
"""调用搜索 API 返回前 3 条结果摘要(真实实现接 SerpAPI/必应等)"""
# 伪实现:真实场景接入搜索服务
return json.dumps({"results": [f"关于{query}的结果1", "结果2", "结果3"]})
# ── 工具注册表 + 让模型选择并调用 ──
TOOLS = [
{"type": "function", "function": {
"name": "calculator",
"description": "精确数学计算,当问题涉及加减乘除/百分比/利息等计算时使用",
"parameters": {"type": "object", "properties": {
"expr": {"type": "string", "description": "数学表达式,如 (1200*0.08)+35"}},
"required": ["expr"]}}},
{"type": "function", "function": {
"name": "run_python",
"description": "在沙箱执行 Python 代码验证算法/逻辑,当需要验证代码正确性时使用",
"parameters": {"type": "object", "properties": {
"code": {"type": "string", "description": "完整的 Python 代码"}},
"required": ["code"]}}},
{"type": "function", "function": {
"name": "search",
"description": "检索最新外部信息,当需要实时数据/新闻/事实核查时使用",
"parameters": {"type": "object", "properties": {
"query": {"type": "string", "description": "搜索关键词"}},
"required": ["query"]}}},
]
HANDLERS = {"calculator": calculator, "run_python": run_python, "search": search}
def tool_augmented_chat(user_msg: str, max_tool_rounds: int = 3):
messages = [{"role": "user", "content": user_msg}]
for _ in range(max_tool_rounds):
resp = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=TOOLS,
tool_choice="auto",
temperature=0.1, # 工具选择/参数生成要低温度(第24章表二)
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content # 模型认为无需工具,直接回答
messages.append(msg)
for call in msg.tool_calls:
# 执行工具 → 把结果塞回上下文 → 继续循环
try:
args = json.loads(call.function.arguments)
except json.JSONDecodeError:
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"error": "参数 JSON 非法,请重新生成参数"}),
})
continue
result = HANDLERS[call.function.name](**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
return "已达最大工具轮数,返回当前结果"
# 使用:模型会自动决定调哪个工具
print(tool_augmented_chat("帮我算一下 1200 元打 8 折再减 35 是多少?"))
print(tool_augmented_chat("写段代码验证冒泡排序是否正确?"))
这个示例展示工具增强的完整闭环:工具定义(Schema)→ 模型选择(tool_choice=auto)→ 执行 → 结果回填 → 循环 → 无工具调用即结束。
14.3.2 工具结果的"回填艺术"
工具执行完,结果怎么喂回模型是有讲究的:
- 结果要简洁:只回填模型需要的字段,别把 2000 行 API 响应全塞回去(上下文污染)。
- 结果要标注 :
role: "tool"+tool_call_id,模型能对应到是哪次调用。 - 失败要说明:工具报错时,回填"错误信息 + 可能原因",让模型有机会换参数重试。
14.4 主流框架对照与变体
14.4.1 框架对照
| 实现方式 | 特点 | 适用 |
|---|---|---|
OpenAI 兼容 tools 参数(本章主线) |
标准函数调用协议,各家模型兼容 | 通用 |
| MCP(Model Context Protocol) | 标准化工具接入,即插即用(第 7 章) | 跨系统工具集成 |
LangChain @tool 装饰器 |
装饰器定义工具,框架自动生成 Schema | 已用 LangChain |
| ReAct 提示式工具调用 | 不用原生 tools 协议,靠提示词引导"工具:xxx"格式 | 老模型/特殊场景 |
重要提醒:如果工具数量多或跨系统,优先 MCP(第 7 章)------它是工具生态的"USB 接口",避免每个工具写一套适配代码。
14.4.2 变体一:工具选择策略(auto vs 显式)
| 策略 | 行为 | 适用 |
|---|---|---|
tool_choice="auto" |
模型自己判断是否需要工具 | 通用默认 |
tool_choice="required" |
强制必须调工具 | 流水线场景(每步都该调) |
tool_choice="none" |
禁用工具 | 纯问答降本 |
| 显式指定某工具 | 固定用某一个 | 明确场景,防选错 |
策略建议:入口用 auto,但配合 14.1.3 的"少而精"工具库;工具多的系统,用第 11 章路由先分域,再让每个域内 auto 选择("先路由、后选择"降低选择空间)。
14.4.3 变体二:多工具并行调用
parallel_tool_calls=true 时,模型可以在一次响应里同时发起多个工具调用(比如同时查订单 + 查物流)。收益是省一轮往返,代价是模型可能并发调用有依赖的工具------工具间有依赖时必须关掉并行 (呼应第 12 章并行化)。注意:并非所有 OpenAI 兼容模型/服务端都支持此参数,使用前需确认目标模型的兼容性。
14.4.4 变体三:工具就是"可验证的反思"(Tool-based Verification)
接第 13 章 13.4.4:生成代码后用 run_python 验证,生成 SQL 后真跑一次,生成数据后对账。把"让模型反思"升级为"让工具验证",是最可靠的质量保障(呼应第 13、17 章)。
🛠 解决方案:工具幻觉参数识别与校验
常见问题
- "模型生成了不存在的工具名":罕见但会发生。对策:调用前用注册表校验工具名,非法直接返回"工具不存在,请重新选择"。
- "工具参数瞎编"(幻觉参数) :比如订单号编造
"order_id": "ABC123"。对策:① temperature 降到 0~0.1;② Schema 写严格 description + 枚举约束;③ 工具内部做参数真实性校验(查无此单 → 返回明确错误);④ 对关键参数让模型先"从原文引用"再填。 - "工具一直失败重试,烧钱":无限重试是成本黑洞。对策:单工具重试 2~3 次 + 熔断(连续失败 5 次停用该工具并转人工/换路径,呼应第 24 章 B1)。
- "模型不用工具,全靠编" :描述写得不够清楚,模型不知道何时该调。对策:重写描述(14.2.1 正例格式)+ 给 few-shot 示例 + 必要时
tool_choice="required"。 - "工具被恶意调用":用户提示注入诱导模型调用危险工具。对策:工具封装层白名单 + 敏感操作二次确认 + 注入检测(呼应第 22 章 E2)。
解决方案速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 工具名幻觉 | 罕见错误 | 注册表校验 + 非法返回 |
| 参数幻觉 | 温度高/描述弱 | 低温度 + 严格 Schema + 引用原文 |
| 无限重试 | 无熔断 | 重试上限 + 连续失败熔断 |
| 不用工具 | 描述不清 | 重写描述 + few-shot |
| 恶意调用 | 注入攻击 | 白名单 + 二次确认 |
实战提示
- 工具 Schema 当"合同"写:description 写清"何时用、何时不用、边界、返回结构",这是投入产出比最高的优化。
- 先测选型准确率:用 20~30 条真实问题验证"模型选对工具的比例",<60% 就改描述(14.2.2)。
- 工具执行结果必须回填干净:简洁 + 标注 + 失败说明,别污染上下文(14.3.2)。
- 封装层不可省略:模型不应直接访问生产系统,中间必须有安全封装层(14.2.3)------除非有明确的安全评估和授权豁免。