AI 不再嘴炮:用 Tools 给大模型接上“执行力”

在大模型应用开发中,单纯的对话能力早已无法满足复杂场景需求,大模型仅凭自身训练数据很难给出准确答案。这时候,LangChain Tools 就像给大模型装上了「手脚」,让它从「只会聊天」升级为「能办实事」。今天我们就来深入聊聊 LangChain Tools 的核心知识,结合实战代码带你快速掌握这个强大功能!

一、LangChain Tools 是什么? 🤔

LangChain Tools(工具)是 LangChain 框架中扩展大模型能力的核心组件。它的本质是将「具体的业务逻辑或操作能力」封装成标准化接口,让大模型能根据用户请求自主判断:要不要调用工具?调用哪个工具?该传什么参数?最终通过工具弥补大模型自身的局限性。

简单来说:

  • 大模型是「大脑」🧠:负责分析问题、判断需求、制定方案;
  • Tools 是「手脚」👋:负责执行具体操作(比如查天气、算数据、调接口)。

二者结合,才能实现从「思考」到「行动」的完整闭环。

二、LangChain Tools 为了解决什么问题? 🚫➡️✅

大模型虽然强大,但存在不少「天然短板」,而 Tools 正是为了弥补这些短板而生:

  1. 突破知识滞后性 ⏳大模型的训练数据有时间截止线,无法获取实时信息(比如 2024 年的天气、最新股票行情)。通过自定义工具(如对接实时天气 API 的 weatherTool),就能让大模型获取「新鲜数据」。
  2. 访问私有数据 / 系统 🔒企业内部数据库、本地文件、私有 API 等信息,大模型无法直接访问。通过工具封装这些私有资源的访问逻辑(如下文示例中的 fakeWeatherDB 模拟私有天气库),既能安全调用数据,又不泄露敏感信息。
  3. 扩展操作边界 🚀除了查数据、算结果,工具还能实现更复杂的操作:发送邮件、操作数据库、执行代码、控制硬件设备等。只要能写成函数的逻辑,都能封装成工具让大模型调用。

三、LangChain Tools 的核心组成要素 🔑

一个完整的 LangChain 工具由两部分组成:「核心执行逻辑」和「工具配置对象」。我们以下面核心代码中的 weatherTooladdTool 为例,拆解每个要素的作用:

Tools核心代码:

const weatherTool = tool(
    async({city}) => {
        // 工具做什么事情 从fakeWeatherDB中获取天气信息
        const weather = fakeWeatherDB[city];
        if(!weather) {
            return `暂无${city}的天气信息`;
        }
        return `当前${city}的天气是${weather.condition},温度${weather.temp},风力${weather.wind}`;
    },
    // 配置schema 定义工具的输入参数的类型 传给大模型
    {
        name:"get_weather",
        description:"查询指定城市的今日天气情况", // 函数的描述
        schema:z.object({
            city:z.string().describe("要查询天气的城市")
        })
    }
)

// 函数 定义一个加法工具
const addTool = tool(
    // 两个参数
    // 等大模型来调用这个函数(工具)
    // 参数是一个对象,包含a和b两个属性,可以解构出来
    async({a,b}) => String(a + b),
    {
        name:"add", // 函数名字
        description:"计算两个数字的和", // 函数的描述
        // 定义参数的类型 参数schema 是一个对象 调用时也要传一个对象
        schema:z.object({
            a:z.number().describe("第一个数字"),
            b:z.number().describe("第二个数字")
        })
    }
)

1. 核心执行逻辑(异步函数) 🚀

这是工具的「灵魂」,负责接收参数、执行具体操作、返回结果。比如:

  • weatherTool 的执行逻辑是从 fakeWeatherDB 中查询城市天气并格式化结果;
  • addTool 的执行逻辑是计算两个数字的和并返回字符串。

关键要求

  • 必须是「异步函数」(带 async 关键字):因为工具常涉及网络请求(如调用 API)、IO 操作(如读文件)等耗时任务,异步执行可避免阻塞整个流程。
  • 入参格式:需与 schema 定义的结构一致,推荐用对象解构(如 async ({ city }) => {...}async ({ a, b }) => {...}),方便参数匹配。

2. 工具配置对象(给大模型的「使用说明书」) 📖

配置对象是大模型理解工具的关键,包含 3 个核心字段:

  • 工具名称(name) 🏷️工具的唯一标识(如 get_weatheradd),大模型通过这个名称确定要调用的工具。注意:多个工具绑定时名称必须唯一,否则会出现调用混乱(就像两个人重名会认错人一样)。

  • 工具描述(description) 📝大模型判断是否调用该工具的「核心依据」。描述需清晰说明工具的功能和适用场景,比如:

    • weatherTool 的描述是「查询指定城市的今日天气情况」,大模型看到用户问「北京天气」时,就知道该调用它;
    • addTool 的描述是「计算两个数字的和,仅支持纯数字加法运算」,用户问「3+4 等于几」时,大模型会匹配到这个工具。
  • 参数校验 Schema(schema) 🧩用 zod 库定义的参数规则,有两个核心作用:

    • 代码层面:校验传入工具的参数是否合法(比如 city 必须是字符串,ab 必须是数字),防止非法参数导致报错;
    • 大模型层面:告诉大模型「需要传什么参数、参数是什么意思」。比如 city: z.string().describe("要查询天气的城市,必须是中文城市名称"),大模型就知道要传中文城市名。

四、LangChain Tools 的核心工作流程 📝

先来看看完整代码:

// 1. 导入 DeepSeek 聊天模型(用于和大模型进行对话及工具调用交互)
// 从 @langchain/deepseek 包中导出,专门适配 DeepSeek 大模型的 LangChain 封装
import { ChatDeepSeek } from '@langchain/deepseek';

// 2. 导入 dotenv 配置(用于加载 .env 文件中的环境变量,如 DeepSeek API KEY)
// 执行此导入后,.env 文件中的变量会被注入到 process.env 中,无需额外配置
import "dotenv/config";

// 3. 导入 LangChain 核心工具装饰器/工具创建函数(用于封装自定义工具,供大模型调用)
// 来自 @langchain/core/tools 包,是 LangChain 工具系统的核心API
import { tool } from "@langchain/core/tools";

// 4. 导入 zod 数据校验库(用于定义工具输入参数的校验规则和描述,同时提供给大模型理解参数含义)
// zod 不仅能做参数类型校验,还能通过 describe 方法给大模型提供参数说明,是 LangChain 工具调用的标配
import { z } from 'zod';

// 5. 模拟天气数据库(无真实接口请求,用本地对象模拟城市天气数据,方便演示工具调用效果)
// 键:城市名称,值:包含温度、天气状况、风力的天气详情对象
const fakeWeatherDB = {
    北京: { temp: "30°C", condition: "晴", wind: "微风" },
    上海: { temp: "28°C", condition: "多云", wind: "东风 3 级" },
    广州: { temp: "32°C", condition: "阵雨", wind: "南风 2 级" },
};

// 6. 创建 天气查询工具(使用 tool 函数封装自定义工具,供大模型自动调用)
// tool 函数接收两个参数:① 工具执行的异步函数 ② 工具配置项(名称、描述、参数校验规则)
const weatherTool = tool(
    // 工具核心执行逻辑:异步函数,接收解构后的参数对象(city 城市名)
    async ({ city }) => {
        // 从模拟天气数据库中获取目标城市的天气信息
        const weather = fakeWeatherDB[city];

        // 容错处理:如果数据库中没有该城市的天气数据,返回提示信息
        if (!weather) {
            return `暂无${city}的天气信息`;
        }

        // 格式化返回结果,让大模型和用户都能清晰理解
        return `当前${city}的天气是${weather.condition},温度${weather.temp},风力${weather.wind}`;
    },
    // 工具配置项:给大模型提供工具元信息,用于大模型判断是否调用该工具及如何传参
    {
        name: "get_weather", // 工具唯一标识(大模型通过该名称识别工具)
        description: "查询指定城市的今日天气情况", // 工具功能描述(核心!大模型根据该描述判断何时调用此工具)
        schema: z.object({ // 参数校验规则(zod 定义),同时给大模型提供参数说明
            city: z.string().describe("要查询天气的城市,必须是中文城市名称(如:北京、上海)")
        })
    }
);



// 7. 创建 加法计算工具(同样使用 tool 函数封装,演示多参数工具的定义)
const addTool = tool(
    // 工具核心执行逻辑:异步函数,接收解构后的两个数字参数 a 和 b
    // 计算求和后转换为字符串返回(大模型更易解析字符串格式结果)
    async ({ a, b }) => String(a + b),
    // 工具配置项:定义工具元信息和多参数校验规则
    {
        name: "add", // 工具唯一标识(大模型通过该名称识别加法工具)
        description: "计算两个数字的和,仅支持纯数字加法运算", // 工具功能描述,明确工具适用场景
        // 多参数校验规则:z.object 中定义多个参数,分别指定类型和描述
        schema: z.object({
            a: z.number().describe("第一个要相加的数字,必须是数字类型(整数或小数)"),
            b: z.number().describe("第二个要相加的数字,必须是数字类型(整数或小数)")
        })
    }
);

// 8. 初始化 DeepSeek 聊天模型实例(配置模型参数,建立和 DeepSeek 大模型的连接)
const model = new ChatDeepSeek({
    model: "deepseek-chat", // 指定使用的 DeepSeek 模型版本(deepseek-chat 为对话模型)
    temperature: 0, // 温度参数(0 表示输出结果确定性强,无随机性;值越大随机性越高)
}).bindTools([addTool, weatherTool]); // 给模型绑定自定义工具(核心!让大模型知晓可用的工具列表)

// 9. 第一个请求:让模型计算 3 和 4 的和(触发加法工具调用)
const res = await model.invoke("计算3和4的和");

// 10. 处理模型返回结果,执行工具调用并获取最终结果
// 可选链操作符 ?. 说明:
// - 作用:判断 res.tool_calls 是否存在,若存在则访问 length 属性;若不存在则返回 undefined,不会报错
// - 场景:如果大模型未识别到需要调用工具(如用户问题无需工具),则 res 中无 tool_calls 属性,直接访问 res.tool_calls.length 会抛出 TypeError
// - 优势:简化容错处理代码,无需额外写 if (res.tool_calls) 判断,让代码更简洁优雅
if (res.tool_calls?.length) {
    // 此处仅处理第一个工具调用(示例中用户问题仅需调用一个工具)
    if (res.tool_calls[0].name === "add") {
        // 调用加法工具的 invoke 方法,传入模型返回的工具参数(res.tool_calls[0].args)
        const result = await addTool.invoke(res.tool_calls[0].args);
        // 打印最终计算结果
        console.log("最终结果:", result);
    }
}

// 11. 第二个请求:让模型查询北京的天气(触发天气查询工具调用)
const res2 = await model.invoke("查询北京的天气");

// 12. 同理处理天气查询的工具调用结果
if (res2.tool_calls?.length) {
    // 判断调用的工具是否为天气查询工具(get_weather)
    if (res2.tool_calls[0].name === "get_weather") {
        // 调用天气工具的 invoke 方法,传入模型返回的城市参数
        const result = await weatherTool.invoke(res2.tool_calls[0].args);
        // 打印最终天气查询结果
        console.log("最终结果:", result);
    }
}

结合上面代码,我们一步步拆解工具调用的完整流程,看看大模型是如何「思考并行动」的:

步骤 1:定义 / 封装工具 🔨

用 LangChain 提供的 tool 函数,将业务逻辑和配置对象封装成工具实例。以 weatherTool 为例:

javascript

// 天气查询工具定义
const weatherTool = tool(
  // 核心执行逻辑:从模拟数据库查询天气
  async ({ city }) => {
    const weather = fakeWeatherDB[city];
    return weather ? 
      `当前${city}的天气是${weather.condition},温度${weather.temp},风力${weather.wind}` : 
      `暂无${city}的天气信息`;
  },
  // 工具配置:给大模型的「使用说明」
  {
    name: "get_weather",
    description: "查询指定城市的今日天气情况",
    schema: z.object({
      city: z.string().describe("要查询天气的城市,必须是中文城市名称(如:北京、上海)")
    })
  }
);

同理,addTool 也按照这个结构封装,定义加法逻辑和参数规则。

步骤 2:将工具绑定到大模型 🧩

通过模型的 .bindTools() 方法,把工具「告诉」大模型,让它知道自己有哪些「技能」:

javascript

// 初始化DeepSeek模型并绑定工具
const model = new ChatDeepSeek({
  model: "deepseek-chat", // 指定模型版本
  temperature: 0, // 0表示结果更确定,无随机性
}).bindTools([addTool, weatherTool]); // 绑定工具列表

这一步是工具调用的前提 —— 不绑定工具,大模型就只能「空谈」,无法执行任何操作。

步骤 3:发送用户请求,模型生成工具调用指令 📤

通过 model.invoke(用户请求) 向大模型提问,大模型会分析需求并决定是否调用工具:

javascript

// 示例1:请求计算3和4的和
const res = await model.invoke("计算3和4的和");
// 示例2:请求查询北京天气
const res2 = await model.invoke("查询北京的天气");

大模型会返回包含 tool_calls 的结果:

  • 如果需要调用工具,tool_calls 里会包含工具名称(如 add)和参数(如 { a: 3, b: 4 });
  • 如果不需要调用工具(比如简单闲聊),则没有 tool_calls

步骤 4:解析指令,执行工具并获取结果 🛠️

解析 tool_calls 中的信息,调用对应的工具执行逻辑:

javascript

// 处理加法工具调用
if (res.tool_calls?.length) { // 可选链操作符:避免无tool_calls时报错
  if (res.tool_calls[0].name === "add") {
    const result = await addTool.invoke(res.tool_calls[0].args);
    console.log("最终结果:", result); // 输出:7
  }
}

// 处理天气工具调用
if (res2.tool_calls?.length) {
  if (res2.tool_calls[0].name === "get_weather") {
    const result = await weatherTool.invoke(res2.tool_calls[0].args);
    console.log("最终结果:", result); // 输出:当前北京的天气是晴,温度30°C,风力微风
  }
}

这里的 ?.(可选链操作符)是个小技巧:如果大模型没调用工具(无 tool_calls),直接访问 res.tool_calls.length 会报错,用 ?. 可优雅避免这种情况。

步骤 5:(可选)整理结果,生成自然语言回复 💬

复杂场景中,可将工具结果再次传给大模型,让它整理成更友好的回复。比如将天气结果传给模型,让它生成「今天北京天气晴朗,温度 30°C,适合户外活动~」这样的自然语言。示例中我们为了简化流程,直接打印了结果。

执行结果亮个相:

1

五、面试官会问这些问题 🎯

  1. Q:工具的 description 字段有什么作用?能不能省略? A:不能省略。description 是大模型判断是否调用工具的核心依据,描述越清晰,大模型调用工具的准确率越高。比如如果 addTool 的描述写成「处理数字」,大模型可能在用户问「3 乘以 4」时也错误调用它。
  2. Q:为什么工具的执行逻辑必须是异步函数? A:因为工具常涉及网络请求(如调用 API)、IO 操作(如读文件)等耗时任务,异步执行可避免阻塞整个程序流程,保证应用的响应速度。
  3. Q:zod 在工具中起到了什么作用? A:有两个作用:① 代码层面校验参数合法性(比如防止给 addTool 传字符串);② 告诉大模型参数的类型和含义,指导它正确传参(比如 city 必须是中文城市名)。
  4. Q:多个工具绑定时,需要注意什么? A:工具的 name 必须唯一,否则会出现覆盖或调用混乱;同时 description 要区分度高,避免大模型混淆工具的功能(比如一个查天气、一个查空气质量,描述要明确区分)。
  5. Q:如何处理工具调用失败的情况? A:可以在工具执行逻辑中加入错误处理(如 try/catch),返回明确的错误信息(如「查询天气失败,请检查城市名称」);也可以将错误信息反馈给大模型,让它决定重试或提示用户。

六、结语 🌟

LangChain Tools 是大模型从「对话助手」升级为「实用工具」的关键。通过封装工具,我们能让大模型突破自身局限,对接实时数据、执行具体操作、访问私有资源,真正解决实际问题。

文中我们的示例代码(加法工具、天气查询工具)已经包含了工具开发的核心逻辑,你可以在此基础上扩展:比如对接真实的天气 API、封装数据库查询工具、实现邮件发送工具等。记住:只要能写成函数的逻辑,都能成为大模型的「工具」!

快去动手试试吧,让你的大模型应用从「能说会道」变成「能干实事」~ 🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐