在之前的学习中,我们眼中的大模型(LLM)更像是一个博学但封闭的“大脑”。它能陪你聊天、写诗、解释复杂的概念,但它有一个致命的弱点:它无法触及外部世界

它不知道此时此刻北京的天气(因为它只有历史数据),它算不好复杂的加减乘除(因为它是基于概率预测文字,而不是计算器),它更无法帮你查询数据库。

今天这一课,我们将通过 LangChain 的 Tools(工具) 模块,打破这层次元壁。我们将学习如何将自定义函数变成大模型可以调用的“工具”,让 AI 从“只会聊天”进化为“能干实事”。

为什么需要 Tools?

大模型的核心能力是理解意图和生成文本。当我们把“工具”引入系统时,我们实际上是在通过 Prompt 告诉大模型:

“嘿,我这里有一个查天气的函数和一个做加法的函数。如果用户问的问题涉及到这两个领域,不要自己瞎编,请告诉我你需要用哪个函数,以及参数是什么。”

这就是 Function Calling(函数调用) 的核心逻辑。

在本文中,我们将构建一个基于 DeepSeek 模型的应用,通过两个具体的工具——天气查询器加法计算器,来演示这一过程。

第一步:定义工具(The “Hands”)

在 LangChain 中,定义一个工具不仅仅是写一个函数,我们还需要告诉大模型这个工具是干什么用的(Description),以及它需要什么参数(Schema)。

我们需要引入 zod 库来进行参数的类型验证,这是连接自然语言与程序代码的桥梁。

1. 模拟一个天气数据库

首先,因为我们没有真实的 API key,我们用一个对象来模拟数据库:

// 模拟的天气数据库
const fakeWeatherDB = {
    北京: { temp: "30摄氏度", condition: "晴", wind: "微风"},
    上海: { temp: "28摄氏度", condition: "多云", wind: "强"},
    广州: { temp: "26摄氏度", condition: "阴", wind: "中"},
}

2. 创建天气工具

我们使用 @langchain/core/tools 中的 tool 方法来封装逻辑。

请注意 schema 部分。我们使用 z.object 定义了输入必须包含 city 字段,并且必须是字符串。这非常关键,因为大模型会根据这个定义,从用户的自然语言(如“北京今天咋样”)中提取出 { city: "北京" }

import { tool } from '@langchain/core/tools';
import { z } from 'zod';

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({
            city: z.string().describe("要查询天气的城市") // 参数描述
        })
    }
)

3. 创建加法工具

同理,大模型通常不擅长精确数学计算。我们可以给它一个计算器:

const addTool = tool(
    async ({a, b}) => String(a + b), // 将结果转为字符串返回给模型
    {
        name: 'add',
        description: '计算两个数字的和',
        schema: z.object({
            a: z.number(),
            b: z.number()
        })
    }
)

第二步:绑定模型(The “Brain”)

有了工具,我们还需要让“大脑”知道它们的存在。我们将使用 ChatDeepSeek 模型,并通过 .bindTools() 方法将刚才定义的工具箱挂载到模型上。

import { ChatDeepSeek } from '@langchain/deepseek';
import 'dotenv/config';

const model = new ChatDeepSeek({
    model: 'deepseek-chat',
    temperature: 0 // 设置为0,让模型更理性,专注于任务执行
}).bindTools([addTool, weatherTool]);

.bindTools() 是整个流程的点睛之笔。它并不会改变模型的内部权重,而是将工具的描述和 Schema 转换成特定格式的 System Prompt 发送给大模型,让模型进入“待命状态”。

第三步:调用与执行(The Action)

现在,让我们看看当我们向模型提问时,究竟发生了什么。

场景一:用户询问天气

const res = await model.invoke("北京今天的天气怎么样?");

如果是普通的聊天模型,它可能会回答:“作为一个 AI 我无法联网…”。

但因为绑定了工具,模型会分析语意,发现这匹配了 get_weather 的描述。

此时,模型并没有直接执行代码(切记,大模型只是文本生成器,它运行不了 JS 代码)。它返回的 res 对象中包含了一个特殊的属性:tool_calls

让我们打印看看 res.tool_calls[0] 长什么样:

{
  name: 'get_weather',
  args: { city: '北京' },
  type: 'tool_call',
  id: 'call_00_xyz...'
}

模型准确地提取了函数名和参数!

JavaScript 小贴士:优雅的可选链(Optional Chaining)

在处理模型返回结果时,我们需要非常小心。因为模型不一定总是调用工具。如果用户问“你好”,模型可能只返回文本,此时 tool_calls 是 undefined。

在传统代码中,我们可能需要这样写防御性代码:

// 繁琐的写法
if (res.tool_calls && res.tool_calls.length > 0) {
    // 执行逻辑
}

但在 ES6+ 中,我们可以使用 可选链操作符 (?.) 来让代码更优雅:

// 优雅的写法
if (res.tool_calls?.length) {
    // 只有当 tool_calls 存在且长度不为 0 时,才执行
}

它的作用是安全地访问嵌套对象的属性,一旦中间某个值为 nullundefined,表达式会立即短路返回 undefined,而不会报错导致程序崩溃。

第四步:闭环(执行工具逻辑)

最后一步,我们需要在代码中捕获模型的“意图”,并在本地执行真正的函数,然后获取结果。

if(res.tool_calls?.length) {
    const toolCall = res.tool_calls[0]; // 获取第一个工具调用请求
    
    console.log("模型请求调用工具:", toolCall.name);

    if (toolCall.name === 'add') {
        // 调用加法工具
        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);
    }
}

当我们运行这段代码时,控制台将输出:

最终结果: 城市北京的天气是30摄氏度, 晴, 风力微风

如果用户问数学题呢?

如果我们把输入改为:model.invoke(“3 + 5等于多少?”)。

模型会返回 name: ‘add’ 和 args: { a: 3, b: 5 }。

我们的代码会命中 add 分支,最终输出 8。

总结与思考

通过今天的课程,我们揭开了 AI Agent(智能体)最基础的雏形:

  1. 定义:利用 zodtool 定义清晰的函数边界。
  2. 绑定:利用 bindTools 赋予大模型选择权。
  3. 解析:利用 tool_calls 获取模型的决策。
  4. 执行:在本地运行代码并获取结果。

为什么这很重要?

这就好比给大脑(LLM)接上了手(Tools)。虽然在这个简单的例子中,我们需要手动写 if/else 来判断执行哪个工具,但在更高级的 LangChain Agent 模块中,这一过程是自动化的——模型会自己“观察”结果,甚至根据结果进行下一轮的思考(ReAct 模式)。

Logo

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

更多推荐