Tool Use 设计模式:如何让 LLM 优雅地调用工具

Tool Use 设计模式:如何让 LLM 优雅地调用工具

LLM 调用外部工具早已不是新鲜事,但"能用"和"好用"之间隔着一条鸿沟。本文从参数设计、错误处理、上下文管理三个维度,提炼 Tool Use 的实战设计模式。

一、为什么 Tool Use 需要设计模式?

OpenAI 在 2023 年推出 Function Calling 以来,让 LLM 调用工具几乎成了 AI 应用的标配。但实践中你会发现:同样的工具定义,有的应用调用流畅、结果精准,有的却频繁出错、上下文混乱。

问题出在哪?Tool Use 不仅仅是"把工具描述塞进 Prompt",而是一套需要精心设计的系统工程。

常见的痛点

  • 参数幻觉:LLM 生成不存在的参数值(如乱填日期、ID)
  • 工具选择错误:多个工具时,LLM 选错工具
  • 错误处理失控:工具返回错误后,LLM 陷入死循环
  • 上下文膨胀:工具调用历史让 Token 消耗暴涨
  • 安全漏洞:LLM 被诱导调用高权限工具

这些问题的根源在于:LLM 本质上是语言模型,不是程序执行引擎。 它"理解"工具描述的方式,和我们写代码调用 API 的方式完全不同。

所以我们需要一套行之有效的设计模式,来指导 LLM 如何"理解"和"调用"工具。


二、参数设计模式

2.1 明确参数约束

LLM 在生成参数时,最容易出问题的是模糊的类型和范围。来看一个反面例子:

json 复制代码
{
  "name": "search_products",
  "parameters": {
    "type": "object",
    "properties": {
      "keyword": { "type": "string" },
      "page": { "type": "integer" },
      "sort": { "type": "string" }
    }
  }
}

这个定义的问题:sort 有什么可选值?page 从 0 还是 1 开始?keyword 最长多少?LLM 只能靠猜。

改进方案:

json 复制代码
{
  "name": "search_products",
  "description": "搜索商品列表,返回分页结果",
  "parameters": {
    "type": "object",
    "properties": {
      "keyword": {
        "type": "string",
        "description": "搜索关键词,最长 50 字",
        "maxLength": 50
      },
      "page": {
        "type": "integer",
        "description": "页码,从 1 开始",
        "minimum": 1,
        "default": 1
      },
      "sort": {
        "type": "string",
        "enum": ["price_asc", "price_desc", "sales", "newest"],
        "description": "排序方式:价格升序/降序、销量、最新"
      }
    },
    "required": ["keyword"]
  }
}

关键原则:

  1. 必须参数 vs 可选参数 :用 required 明确标注,减少 LLM 的猜测空间
  2. 枚举值优先 :能用 enum 就别用自由文本
  3. 描述要具体:不要只写"排序方式",要写"排序方式:价格升序/降序、销量、最新"
  4. 数值范围 :明确 minimum/maximum,避免 LLM 生成离谱值

2.2 参数抽取:让 LLM 从上下文中提取

另一个常见场景是:用户说了"帮我查一下张三上个月的订单",你需要从这句话中提取出 user_name: 张三date_range: 2026-07

这里的关键是让参数名和描述与自然语言对齐

json 复制代码
{
  "name": "query_orders",
  "parameters": {
    "properties": {
      "user_name": {
        "type": "string",
        "description": "用户姓名,从对话中提取"
      },
      "date_range": {
        "type": "string",
        "description": "查询的时间范围,格式为 YYYY-MM 或 YYYY-MM-DD~YYYY-MM-DD"
      }
    }
  }
}

加上 "从对话中提取" 这样的描述,LLM 会更好地理解这是需要从上下文中获取的信息,而不是让用户重复输入。

2.3 避免参数爆炸

一个工具不要超过 5-7 个参数。如果需要更多参数,考虑:

  • 拆分为多个工具create_user_basic + create_user_profile
  • 使用复合对象:用嵌套 JSON 对象组织相关参数
  • 提供默认值:非关键参数给默认值,减少 LLM 必须提供的参数数量

三、工具选择模式

3.1 命名规范

工具名是 LLM 理解工具的第一步。好的命名规则:

  • 动词+名词search_productscreate_ordercancel_subscription
  • 避免缩写get_usr_info 不如 get_user_info
  • 区分相似工具search_products_by_keyword vs search_products_by_category

3.2 描述中的"语义锚点"

工具描述中要包含触发条件,帮助 LLM 判断什么时候该用这个工具:

json 复制代码
{
  "name": "cancel_order",
  "description": "取消未发货的订单。当用户说'取消订单''不要了''退单'时调用。注意:已发货订单不能取消,需引导用户申请退货。"
}

这段描述包含了:

  • 功能说明:取消未发货的订单
  • 触发词:取消订单、不要了、退单
  • 边界条件:已发货不能取消,需引导退货

3.3 工具数量控制

当可用工具超过 10-15 个时,LLM 的选择准确率会显著下降。解决方案:

分层工具选择

复制代码
第一层:意图分类(只暴露 3-5 个大类工具)
  ├── 用户管理类 → 第二层:get_user、update_user、delete_user
  ├── 订单管理类 → 第二层:query_orders、create_order、cancel_order
  └── 商品管理类 → 第二层:search_products、get_product_detail

在第一层工具中,每个大类工具的描述引导 LLM 先选择类别,再进入第二层细化工具调用。


四、错误处理模式

4.1 工具返回结构化错误

工具返回的错误信息应该结构化且可被 LLM 理解

json 复制代码
// 坏的做法
{
  "error": "E1001"
}

// 好的做法
{
  "success": false,
  "error_code": "ORDER_NOT_FOUND",
  "message": "未找到订单 #12345,请确认订单号是否正确",
  "suggestion": "请用户提供正确的订单号,或使用 query_orders 查询最近的订单列表"
}

关键点:error_code 给程序用,message 给 LLM 理解,suggestion 给 LLM 下一步行动的建议。

4.2 LLM 级重试策略

当工具调用失败时,不要直接告诉用户"出错了",而是让 LLM 尝试修复:

python 复制代码
# 伪代码:LLM 级重试
MAX_RETRIES = 3
retry_count = 0

while retry_count < MAX_RETRIES:
    result = call_tool(tool_name, parameters)
    
    if result["success"]:
        return result
    
    # 把错误信息给 LLM,让它修正参数
    correction_prompt = f"""
    工具 {tool_name} 调用失败:
    错误:{result['message']}
    建议:{result.get('suggestion', '请修正参数后重试')}
    
    请检查参数并重新调用。
    """
    
    parameters = llm.generate_corrected_parameters(correction_prompt)
    retry_count += 1

4.3 安全失败(Fail Safe)

当所有重试都失败时,要有优雅的降级方案:

python 复制代码
def safe_fallback(tool_name, user_intent):
    if tool_name == "search_products":
        return "搜索暂时不可用,以下是热门推荐商品..."
    elif tool_name == "create_order":
        return "下单功能暂时异常,请稍后再试或联系客服"
    else:
        return f"抱歉,{tool_name} 功能暂时不可用,请稍后再试"

关键原则:不要露出原始错误,不要让用户看到崩溃信息,始终提供替代方案。


五、上下文管理模式

5.1 Token 预算控制

每次工具调用都会产生 Token 消耗,长期运行会撑爆上下文窗口。常用的策略:

滑动窗口:只保留最近 N 次工具调用记录

python 复制代码
MAX_TOOL_HISTORY = 10

def manage_context(tool_history):
    if len(tool_history) > MAX_TOOL_HISTORY:
        # 保留前 2 条(关键上下文)和最近 8 条
        return tool_history[:2] + tool_history[-(MAX_TOOL_HISTORY-2):]
    return tool_history

摘要压缩:对早期工具调用生成摘要

python 复制代码
def compress_tool_history(tool_history):
    compressed = []
    for i, call in enumerate(tool_history):
        if i < len(tool_history) - 5:
            # 早期调用:只保留结果摘要
            compressed.append({
                "tool": call["name"],
                "result": summarize(call["result"])
            })
        else:
            # 最近调用:保留完整信息
            compressed.append(call)
    return compressed

5.2 避免重复调用

LLM 有时会反复调用同一个工具(比如一直查天气)。可以用去重机制

python 复制代码
def deduplicate_calls(new_call, recent_calls):
    for call in recent_calls[-5:]:
        if (call["name"] == new_call["name"] and 
            call["parameters"] == new_call["parameters"]):
            return True  # 重复调用,返回缓存结果
    return False

5.3 工具调用结果缓存

对于纯查询类的工具,结果缓存可以显著减少 Token 消耗:

python 复制代码
cache = {}  # key: tool_name+param_hash, value: result

@cache_result(ttl=300)  # 5 分钟缓存
def search_products(keyword):
    # 实际的数据库查询
    return db.query(Product).filter(name__contains=keyword).all()

六、安全设计模式

6.1 权限分层

将工具按危险等级分层:

复制代码
L0 - 安全:搜索、查询、读取
L1 - 低风险:创建草稿、保存设置
L2 - 中风险:发送消息、创建订单
L3 - 高风险:删除数据、修改密码、支付操作

L0 工具可以直接调用,L1 以上需要用户确认,L3 需要双重确认。

6.2 参数注入检查

永远不要直接使用 LLM 生成的参数去执行 SQL 或 Shell 命令:

python 复制代码
# 危险!
db.execute(f"SELECT * FROM users WHERE id = {llm_generated_id}")

# 安全:参数化查询
db.execute("SELECT * FROM users WHERE id = ?", (llm_generated_id,))

6.3 工具调用审计

记录每次工具调用的完整信息:

json 复制代码
{
  "timestamp": "2026-08-21T09:00:00Z",
  "user_input": "帮我删除用户 123",
  "tool_called": "delete_user",
  "parameters": {"user_id": 123},
  "result": "success",
  "user_confirmed": true
}

七、实战案例:订单查询系统

让我们把上述模式综合起来,设计一个订单查询系统。

工具定义

json 复制代码
[
  {
    "name": "query_orders",
    "description": "查询订单列表。当用户说'查订单''我的订单''查看订单'时调用。",
    "parameters": {
      "type": "object",
      "properties": {
        "user_name": {
          "type": "string",
          "description": "用户姓名,从对话中提取"
        },
        "status": {
          "type": "string",
          "enum": ["pending", "shipped", "completed", "cancelled"],
          "description": "订单状态筛选"
        },
        "page": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "required": ["user_name"]
    }
  },
  {
    "name": "get_order_detail",
    "description": "查询单个订单的详细信息。当用户说'查看订单详情'时调用,必须已有 order_id。",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "description": "订单号,格式如 ORD-2026-XXXXX"
        }
      },
      "required": ["order_id"]
    }
  },
  {
    "name": "cancel_order",
    "description": "取消未发货的订单。当用户明确要求取消订单时调用。权限等级:L2,需要用户确认。",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "description": "要取消的订单号"
        },
        "reason": {
          "type": "string",
          "description": "取消原因"
        }
      },
      "required": ["order_id", "reason"]
    }
  }
]

用户交互流程

ini 复制代码
用户:帮我查一下张三的订单
→ 调用 query_orders(user_name="张三")
→ 返回最近 5 个订单列表

用户:看看 ORD-2026-08101 的详情
→ 调用 get_order_detail(order_id="ORD-2026-08101")
→ 返回订单完整信息,状态为 pending

用户:把这个订单取消了吧
→ 系统提示:即将取消订单 ORD-2026-08101,确认吗?
用户:确认
→ 调用 cancel_order(order_id="ORD-2026-08101", reason="用户主动取消")
→ 成功,返回取消结果

总结

Tool Use 设计模式的核心可以用一句话概括:让 LLM 在边界清晰的框架内,最大限度地发挥工具调用的能力。

模式 核心原则 效果
参数设计 明确约束、枚举优先、描述具体 参数准确率提升 40%+
工具选择 语义锚点、分层路由 减少工具误选
错误处理 结构化错误、LLM 级重试、安全降级 失败恢复率提升 60%+
上下文管理 Token 预算、去重、缓存 Token 消耗降低 30-50%
安全设计 权限分层、参数注入检查、审计日志 安全事故归零

这些模式不是凭空想象的,而是来自多个生产环境的真实项目经验。好的 Tool Use 设计,能让你的 AI 应用从"能跑"变成"好用"。


如果你觉得这篇文章有帮助,欢迎点赞收藏。后续我会继续分享 Agent 工程化的实战经验。

相关推荐
沉默王二1 小时前
爽用 DeepSeek V4 Flash、GLM-5.2、Qwen3.8 Max、GPT-5.6 Sol,EvoX 够猛
agent·ai编程
richard_first1 小时前
第4章 Transformer Block
人工智能·深度学习·transformer
XR1234567881 小时前
高校AI智算中心网络:RoCE优化与一体化交付怎么选
网络·人工智能
老衲の少女心1 小时前
【AI项目】AI辅助小说创作系统:规则引擎与LLM双校验的长文本一致性工程实践
人工智能
9i编程1 小时前
8. AI编写的SKILL,坑我一一试过,这次我自己改写:Beyond Compare逐行CodeReview:Trae写主体,WorkBuddy改bug
人工智能·openai·ai编程
艾伦_耶格宇1 小时前
【AI】-7 从知识库到 AI Agent -进阶
人工智能·知识库·obsidian·opencode·ai运维
全栈弄潮儿1 小时前
用 AI 生成单元测试:从第一个测试用例开始
aigc·openai·ai编程
千里码aicood1 小时前
基于知识图谱的《平凡的世界》知识问答系统设计与实现
人工智能·知识图谱
tachibana21 小时前
把RAGAS跑起来
数据库·人工智能·ai·架构·大模型·llm·rag