Tool:让大模型长出手脚

前言

上一篇文章我们讲了 Agent 的三要素:LLM 是大脑,Tools 是手脚,Messages 是记忆。

Tool 是 Agent 能从"聊天机器人"升级为"能干活的助理"的关键。没有 Tool,AI 只能空谈;有了 Tool,AI 能读文件、写代码、查数据库、发邮件------真正把手伸进现实世界。

但这套手脚怎么装?LLM 怎么知道什么时候用哪个 Tool?参数怎么传?这篇文章把 Tool 的一切讲清楚。

一、Tool 的本质:函数 + 说明书

Tool 就是一个普通的函数,但多了一层 JSON 格式的"说明书"。LLM 读不懂你的代码,它读的是这份说明书。

bash 复制代码
说明书(function definition)→ 给 LLM 看的,让它知道什么时候用、怎么传参
实现(function body)        → 给运行时执行的,真正干活的代码

一个完整的 Tool 定义长这样:

javascript 复制代码
{
    type: "function",              // 固定值,类型声明
    function: {
        name: "get_closing_price", // 函数名:snake_case,动词开头
        description: "获取指定股票的最近收盘价。需要股票全称,如'青岛啤酒'、'贵州茅台'。",
        parameters: {              // 参数 JSON Schema
            type: "object",
            properties: {
                name: {
                    type: "string",
                    description: "股票全称,如'青岛啤酒'"
                }
            },
            required: ["name"]
        }
    }
}

五个字段各司其职:

字段 作用 谁来用
type 类型声明,固定写 "function" API 协议
name LLM 靠这个名字决定"我要调这个" LLM
description LLM 靠这个判断"这个 Tool 能干什么、该不该用" LLM(最重要!)
parameters LLM 靠这个知道"该传什么参数、参数长什么样" LLM
函数体(代码) 真正执行操作,返回结果 你的程序

二、description:Tool 的灵魂

LLM 不理解你的代码。它只看 description。

description 的好坏,直接决定了 LLM 能不能在正确的时机选中正确的 Tool、传正确的参数。它是 LLM 和你的 Tool 之间唯一的语义接口。

javascript 复制代码
// ❌ 差 --- LLM 不知道该不该调
description: "获取股票信息"

// ❌ 也差 --- 何时用、用什么参数都不清楚
description: "获取收盘价"

// ✅ 好 --- 功能、场景、边界、输入格式全说清楚了
description: "根据完整股票名称查询最近交易日收盘价。" +
             "支持 A 股主板,不支持港股和美股。" +
             "一周内未开盘则返回最近交易日数据。" +
             "输入须为股票全称,如'青岛啤酒',不要用代码。"

description 写作黄金法则:

要包含的要素 作用 示例
功能 这个 Tool 做什么 "查询最近交易日收盘价"
适用场景 什么时候用 "当用户需要股价信息时"
限制条件 什么时候不能用 "不支持港股美股"
输入提示 LLM 怎么正确传参 "股票全称,如'青岛啤酒'"

三、parameters 与 properties:大门和格子

这是最容易被搞混的两个概念。

css 复制代码
parameters  ─────────────── 大门:声明"这个 Tool 接收什么"
  ├── type: "object"         ← 固定套路,参数总是一个 JSON 对象
  ├── properties             ← 格子们:每个参数逐个定义
  │     ├── 字段A: { type: "string", description: "..." }
  │     ├── 字段B: { type: "integer", description: "..." }
  │     └── 字段C: { type: "boolean", description: "..." }
  └── required: ["字段A"]    ← 哪些必须填

parameters 是容器,properties 是容器里的具体字段。 写 Tool 时,你把精力放在 properties 里每个字段的定义上------尤其是每个字段的 description

参数 description 同样重要

LLM 从用户消息里提取参数值时,靠的不是参数名匹配,而是参数 description 和用户原话的语义匹配

javascript 复制代码
// 参数叫 stock_code,但用户说"茅台"------
// 如果 description 写得清楚,LLM 会主动把"茅台"翻译成"600519"
properties: {
    stock_code: {
        type: "string",
        description: "上市公司股票代码,6 位数字。例如贵州茅台是 600519"
    }
}

四、parameters 里的约束全家桶

除了 typedescription,每个参数还可以加这些约束,让 LLM 更准确:

约束字段 适用类型 作用 示例
enum string 限定只能从几个值里选 ["北京", "上海", "深圳"]
default 任意 LLM 不传时用默认值 3
minimum number/integer 数值下限 1
maximum number/integer 数值上限 100
items array 数组元素的类型定义 { type: "string" }
minItems array 数组最少元素数 1
maxItems array 数组最多元素数 10
additionalProperties object false 禁止未定义的字段 ---
嵌套 properties object 定义嵌套对象的子字段 ---

一个完整示例

javascript 复制代码
const tools = [{
    type: "function",
    function: {
        name: "search_flights",
        description: "搜索航班信息。用于用户查询特定日期、城市间的航班。价格单位为人民币。",
        parameters: {
            type: "object",
            properties: {
                from: {
                    type: "string",
                    description: "出发城市中文全称,如'北京'",
                    enum: ["北京", "上海", "广州", "深圳", "成都"]
                },
                to: {
                    type: "string",
                    description: "目的城市中文全称,如'上海'",
                    enum: ["北京", "上海", "广州", "深圳", "成都"]
                },
                date: {
                    type: "string",
                    description: "出发日期,格式 YYYY-MM-DD"
                },
                passengers: {
                    type: "object",
                    description: "乘客信息",
                    properties: {
                        adults: {
                            type: "integer",
                            description: "成人数量",
                            minimum: 1,
                            maximum: 9,
                            default: 1
                        },
                        children: {
                            type: "integer",
                            description: "儿童数量(2-12 岁)",
                            minimum: 0,
                            maximum: 9,
                            default: 0
                        }
                    },
                    required: ["adults", "children"]
                }
            },
            required: ["from", "to", "date"]
        }
    }
}];

五、调用层控制:让 LLM 听话

Tool 定义好之后,还能通过调用参数控制 LLM 的行为:

javascript 复制代码
const response = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: [...],
    tools: [...],
    tool_choice: "auto",           // ← 控制调不调、怎么调
    parallel_tool_calls: true,     // ← 是否允许一次调多个
    max_tool_calls: 128,          // ← 单次调用上限
});

tool_choice 的四种模式

含义 适合场景
"auto" LLM 自己判断:文字回答 还是 调 Tool 最常用,适合大部分场景
"none" 强制纯文字,禁止调 Tool 你确定不需要 Tool 时
"required" 强制必须调 Tool,不许直接答 需要强制行动时
{ type: "function", function: { name: "xxx" } } 强制只调某个特定 Tool 多 Tool 但你知道该用哪个

parallel_tool_calls

javascript 复制代码
parallel_tool_calls: true   // LLM 可一次同时调多个互不依赖的 Tool
parallel_tool_calls: false  // 一次只能调一个,按顺序来

当用户问"北京和上海的天气各是多少",且你有 get_weather 工具时,parallel_tool_calls: true 允许 LLM 同时发出两次天气查询,而不是先查北京、等结果、再查上海。


六、一次 Tool 调用的完整生命周期

两个最容易出错的地方:

  1. tool_call_id 忘记带上 → LLM 不知道这个结果对应哪个调用,任务失败
  2. 没有循环 → 只调了一次 LLM,拿到 tool_calls 就结束了,没有执行函数也没有把结果喂回去

七、多 Tool 场景:LLM 怎么选

当你注册了多个 Tool 时,LLM 根据每个 Tool 的 namedescription 做语义匹配,选择最合适的那个:

javascript 复制代码
tools = [
    { function: { name: "get_stock_price", description: "获取股票当前价格" } },
    { function: { name: "get_weather",     description: "获取城市天气信息" } },
    { function: { name: "search_web",      description: "搜索互联网获取实时信息" } },
];

// "青岛啤酒收盘价多少?" → LLM 选 get_stock_price
// "北京今天天气如何?"   → LLM 选 get_weather
// "C 罗是谁?"           → LLM 选 search_web 或直接文字回答(看哪个更合适)

LLM 的这个选择过程本身是它推理的一部分------如果你开启了 reasoning,能从 reasoning_content 里看到它为什么选了 A 而不是 B。


八、如何让 LLM 准确用你的 Tool(工程实践)

经过上面的讲解,核心原则已经呼之欲出:

  1. description 是 LLM 唯一的信息来源 --- 细节写进 description,别指望 LLM 猜
  2. 参数的 description 同样关键 --- LLM 靠它从用户消息中提取参数值
  3. 善用约束 --- enumminimum/maximumdefault 能显著提高准确率
  4. 开启 strict --- 防止 LLM 幻觉出多余的参数
  5. 写出使用边界 --- description 里说清什么场景不该用这个 Tool,和说清该用一样重要

总结

arduino 复制代码
Tool = name(让 LLM 选中它)
     + description(让 LLM 判断该不该用)
     + parameters(让 LLM 知道怎么传参)

参数 = properties(逐个定义每个字段)
     + required(标注必填)
     + enum/default/min/max/items(精细约束)

调用 = tool_choice(调不调)
     + parallel_tool_calls(并行吗)
     + max_tool_calls(调多少)

Tool 不是"写了就行",它的每一个 description、每一个参数约束,都在直接影响 LLM 的调用准确率。

相关推荐
黄忠1 小时前
大模型之LangGraph技术体系
python·llm
用户329901675052 小时前
给 AI 返回数据加 TS 类型,别全标 any
agent
Lei活在当下12 小时前
【AI手记系列-2026/6/18】iSparto & Harness,Caveman 以及AI时代的生存指南
人工智能·llm·openai
冬奇Lab14 小时前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
冬奇Lab14 小时前
Agent 系列(22):Context Engineering 深度——三种上下文管理策略的量化对比
人工智能·agent
葫芦和十三15 小时前
渐进发现|代码库不是文档库
langchain·agent·ai编程
米小虾16 小时前
告别单打独斗:2026年多Agent协作架构实战指南
人工智能·agent
EternalRights21 小时前
skill 的终局是 agent 化:我给 SKILL.md 写了个编译器,6 个 Harness 把散文孵化成独立 Agent
agent
leeyi21 小时前
Document 组件:把文件喂给 AI 之前,必须先做这三步
aigc·agent·ai编程