LangChain Tool 实战:让大模型“长出双手”,通过 Tool 调用连接真实世界

前言

大语言模型(LLM)通常被比作"大脑",它拥有海量的知识和强大的逻辑推理能力。但是,一个只有大脑的智者,如果无法感知当下的时间、无法查询实时的天气、无法进行精确的数学运算,它的能力就会大打折扣。

Tools(工具) 模块,就是 LangChain 赋予大模型的"双手"和"感官"。通过 Tools,我们可以让大模型不仅仅是陪聊,而是真正具备调用外部函数、执行复杂任务的能力。

本文将结合一段基于 ChatDeepSeek 的实战代码,带你从零开始,深入理解如何在 LangChain 中定义工具、使用 Zod 进行参数校验,以及如何优雅地处理模型返回的工具调用请求。


一、 为什么我们需要 Tool?

在深入代码之前,我们先思考一个问题:为什么 ChatGPT 有时候做不好简单的加减法?为什么它不知道今天北京的天气?

因为大模型的本质是概率预测,它是基于训练数据生成下一个字的,而不是基于逻辑运算或实时数据库。

  • 数学计算:大模型是"文科生",复杂的计算容易产生幻觉。
  • 实时信息:大模型的训练数据截止于过去,它无法预知"今天"发生的事情。

LangChain Tool 的出现解决了这个问题。 它的核心思想是: 当用户问"1234 + 5678 等于几?"时,大模型不再自己瞎猜,而是分析出"哦,这是一个计算任务",然后告诉程序:"请帮我调用 add 工具,参数是 a=1234, b=5678"。程序运行完代码后,将结果反馈给用户。

还有一个重要作用在于:对于简单任务,无需调用大模型自行处理,从而节省宝贵的 token ;而对于大模型本身难以完成的特定任务 (实时感知、精准执行或外部操作.....),则可以通过调用外部工具来扩展其能力,使其能够间接完成这些工作。

接下来,我们将通过代码一步步实现这个过程。


二、 基础入门:定义一个简单的"加法工具"

我们要讲的第一个知识点是:如何定义一个工具

在 LangChain 中,tool 函数是核心。我们需要引入它,并配合 zod 库来定义参数结构。

1. 引入必要的依赖

javascript 复制代码
import { ChatDeepSeek } from "@langchain/deepseek";
import 'dotenv/config';
import { tool } from "@langchain/core/tools";
import { z } from "zod"; // 关键:用于定义函数参数的类型 schema

这里重点介绍一下 zod 。 大模型输出的是文本(JSON 字符串),而我们的函数需要的是具体的变量(如数字、字符串)。Zod 的作用就是建立一个"契约":它告诉大模型,"如果你要调用这个工具,你必须给我传什么样的参数,是数字还是字符串"。这大大提高了工具调用的准确性。

2. 编写加法工具 (addTool)

我们先从最简单的加法开始,让大模型学会算术。

javascript 复制代码
// 函数 定义一个加法工具
const addTool = tool(
    // 1. 工具的具体逻辑(函数体)
    // 这里接收一个对象,解构出 a 和 b
    // 注意:所有工具函数的参数通常都会被包装成一个对象
    async ({a, b}) => String(a + b), 
    {
        // 2. 工具的元数据(Metadata)
        name: "add", // 工具名称,大模型通过这个名字识别工具
        description: "计算两个数字的和", // 工具描述,非常重要!这相当于写给大模型的 Prompt,告诉它什么时候用这个工具
        
        // 3. 参数 Schema 定义
        schema: z.object({
            a: z.number().describe("加数 a"), // 明确告诉模型,a 必须是一个数字
            b: z.number().describe("加数 b")  // b 也必须是一个数字
        })
    }
)

代码深度解析:

  • tool(function, options): 这是定义工具的标准范式。第一个参数是实际执行的 JavaScript 函数,第二个参数是配置对象。
  • async ({a, b}): LangChain 的工具调用通常是异步的。参数被设计为解构形式,这是为了配合 Zod 定义的 Object Schema。
  • description: 千万不要忽视描述!大模型正是通过这段文字来判断用户的问题是否需要使用该工具。如果描述写得含糊,模型可能就不会调用它。
  • z.number() : 这里限制了 ab 必须是数字。如果模型抽风传了字符串,Zod 会在底层进行校验或报错,保证了程序的健壮性。

三、 进阶实战:定义"天气查询工具"

学会了简单的加法,我们来模拟一个更贴近真实场景的业务------查询天气。 由于我们没有连接真实的气象局 API,这里我们使用一个"伪数据库"来模拟。

1. 模拟数据源

javascript 复制代码
const fakeWeatherDB = {
    北京: { temp: "30°C", condition: "晴", wind: "微风" },
    上海: { temp: "28°C", condition: "多云", wind: "东风 3 级" },
    广州: { temp: "32°C", condition: "阵雨", wind: "南风 2 级" }
}

2. 编写天气工具 (weatherTool)

javascript 复制代码
const weatherTool = tool(
    async ({city}) => {
        // 模拟数据库查询
        const weather = fakeWeatherDB[city];
        // 边界情况处理
        if(!weather){
            return `城市${city}的天气未找到`;
        }
        // 返回自然语言描述的结果,方便大模型理解
       return `当前${city}的天气是${weather.temp},${weather.condition}, 风力${weather.wind}`;
    },
    {
        name: "get_weather",
        description: "查询指定城市的今日天气情况", // 明确的功能描述
        schema: z.object({
            // z.string() 规定参数 city 必须是字符串
            // .describe() 进一步解释参数含义,帮助模型提取正确的实体
            city: z.string().describe("要查询天气的城市") 
        })
    }
)

知识点延伸:weatherTool 中,我们看到了工具不仅仅是计算,还可以是查表、请求 API、读取文件

  • 返回值:工具的返回值通常是字符串。为什么?因为这个返回值最终是要喂回给大模型的,大模型理解文本能力最强。
  • Error Handling :我们在代码中判断了 if(!weather)。在真实开发中,工具内部做好错误处理非常重要,否则一个异常可能导致整个对话崩塌。

四、 链接大脑:绑定工具到模型

工具定义好了,就像家里买了吸尘器和洗碗机,但如果作为"管家"的大模型不知道它们的存在,也是白搭。我们需要将工具**绑定(Bind)**到模型上。

javascript 复制代码
// 实例化 DeepSeek 模型
const model = new ChatDeepSeek({
    model: 'deepseek-chat',
    temperature: 0 // 设置为 0,让模型更理性、逻辑更严密,减少胡言乱语
}).bindTools([addTool, weatherTool]) // 核心步骤:绑定工具数组

解析:

  • .bindTools([...]) : 这一步发生了很多魔法。LangChain 会自动将我们要定义的 addToolweatherToolnamedescription 以及 zod schema 转换成 OpenAI 兼容的 JSON 格式,并发送给 DeepSeek 模型。
  • 此时,模型不仅知道"我是谁",还知道"我手边有两个工具:一个能算加法,一个能查天气"。

五、 触发调用与结果处理(重点:可选链运算符)

万事俱备,只欠东风。我们向模型提问,看看它如何反应。

javascript 复制代码
// 发起提问
const res = await model.invoke('北京今天的天气怎么样');

当这句话发出去后,DeepSeek 会进行如下思考:

  1. 用户问的是天气。
  2. 我有 get_weather 工具吗?有。
  3. 工具描述说它能"查询指定城市天气"。匹配成功!
  4. 提取参数:城市是"北京"。
  5. 决策:我不直接回答文本,而是返回一个"工具调用请求(Tool Call)"。

处理响应:可选链运算符 (?.) 的妙用

模型返回的 res 对象中,包含了很多信息。如果模型决定调用工具,res.tool_calls 属性就会有值。

javascript 复制代码
// 可选链运算符 es6 新增 代码的简洁和优雅
// 传统写法(繁琐):
// if(res.tool_calls){
//     if(res.tool_calls.length){
//         // ...
//     }
// }

// 现代写法(推荐):
if(res.tool_calls?.length){
    console.log("模型决定调用工具:", res.tool_calls);
    
    // 获取第一个工具调用的指令
    const toolCall = res.tool_calls[0];

    // 根据工具名称,分发执行逻辑
    if(toolCall.name === 'add'){
        // 执行加法工具
        // toolCall.args 包含了模型提取好的参数 {a: ..., b: ...}
        const result = await addTool.invoke(toolCall.args);
        console.log("加法工具运行结果:", result);
    }
    else if(toolCall.name === 'get_weather'){
        // 执行天气工具
        const result = await weatherTool.invoke(toolCall.args);
        console.log("天气工具运行结果:", result);
    }
}

为了直观理解函数打印一下res.tool_calls看看

你可以直观地看到调用结构:name 和工具定义里的 name 一模一样,而 args 则是大模型在理解用户问题后,根据tool要求的参数格式,自动"填好"的输入内容。

知识点详解:可选链运算符 (?.)

在 JavaScript 中,处理深层嵌套的对象属性时,经常会遇到 Cannot read property of undefined 的报错。

  • res.tool_calls 可能不存在(如果模型认为不需要调用工具,只是普通聊天)。
  • 如果不使用 ?.,我们需要写多层 if 判断来确保安全。
  • res.tool_calls?.length 的意思是:如果 res.tool_calls 存在(不为 null 或 undefined),则读取 .length;否则,直接返回 undefined,不会报错。配合 if 语句,undefined 会被视为 false,逻辑非常通顺。

这一小小的语法糖,极大地提升了代码的简洁性优雅度,是现代 JS 开发的必备技巧。


六、 完整流程总结

让我们回顾一下这段代码运行的完整生命周期:

  1. 定义阶段 :我们用 tool()zod 精确描述了 addget_weather 两个工具的功能和参数格式。
  2. 绑定阶段 :通过 .bindTools() 将这些能力的"说明书"发给了 DeepSeek 大模型。
  3. 推理阶段 :用户提问"北京天气",模型分析语义,命中 get_weather 工具,并精准提取出 city: "北京"
  4. 响应阶段 :模型返回包含 tool_calls 的响应对象。
  5. 执行阶段 :我们的代码通过 ?. 安全地检测到调用请求,并在本地运行了 weatherTool 函数,通过查阅 fakeWeatherDB 得到了结果。

运行效果预览

当你运行这段代码(node index.js),控制台会输出:

bash 复制代码
[
  {
    name: 'get_weather',
    args: { city: '北京' },
    id: 'call_xxxxxxxxx'
  }
]
最终结果: 当前北京的天气是30°C,晴, 风力微风

如果你把问题改成 model.invoke('100 加 200 等于几'),它会自动切换逻辑:

bash 复制代码
[
  {
    name: 'add',
    args: { a: 100, b: 200 },
    id: 'call_yyyyyyyyy'
  }
]
最终结果: 300

七、 结语

通过这篇文章,我们不仅学习了 LangChain 中 tool 的基础用法,还掌握了如何利用 zod 进行数据验证以及使用 ES6+ 的可选链运算符编写健壮的代码。

Tools 模块彻底改变了大模型的应用形态。它让大模型从一个"只读"的百科全书,进化成了一个能"读写"现实世界的智能代理(Agent)。

赶快打开你的编辑器,试着定义属于你自己的工具吧!不管是查询股票、发送邮件,还是控制智能家居,LangChain 都能帮你实现。


本文代码示例基于 LangChain.js 最新版本,欢迎点赞收藏!

相关推荐
小程故事多_802 小时前
开源封神!Minion Skills 重构 Claude Skills,解锁 AI Agent 无限能力
人工智能·重构·开源·aigc
mys55182 小时前
杨建允:AI搜索优化对全链路营销的影响
人工智能·aigc·geo·ai搜索优化·ai引擎优化
小北方城市网3 小时前
第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)
大数据·前端·vue.js·ai·性能优化·node.js
沛沛老爹4 小时前
Advanced-RAG原理:RAG-Fusion 检索增强生成的多查询融合实战
langchain·llm·agent·fusion·rag·advanced·web转型
NullPointer84 小时前
【剪映小助手源码精讲】第32章:日志管理系统
python·aigc
DisonTangor4 小时前
Mac Studio配备1.5 TB显存——基于雷电5的远程直接内存访问技术
人工智能·macos·开源·aigc
Cshaosun4 小时前
阿里云宝塔面板部署vue+nodejs项目并实现https访问操作流程
vue.js·阿里云·https·node.js·宝塔·文件下载
Mr_Wu20184 小时前
Corepack 实现 pnpm 版本自动管理
node.js
天远数科4 小时前
Node.js 全栈攻略:基于天远数据 API 开发即时身份核验中间件
大数据·node.js·编辑器·vim