从零开始掌握LangChain工具调用:让AI拥有“动手能力”

从零开始掌握LangChain工具调用:让AI拥有"动手能力"

前言

在传统的AI对话中,大模型只能依靠自己的知识回答问题。但当我们想让AI查询实时天气、计算复杂数学、访问数据库时,单纯的语言模型就显得力不从心了。

LangChain的Tools(工具)模块完美解决了这个问题------它让大模型具备了调用外部工具的能力,就像给AI装上了"双手",可以主动获取和处理信息。

本文将手把手教你实现一个完整的工具调用系统,包括天气查询和数学计算两个实战案例。

环境准备

1. 创建项目并安装依赖

首先创建项目文件夹并选择项目文件夹进行初始化:

bash 复制代码
npm init -y

安装必要的依赖包:

bash 复制代码
# 安装LangChain核心包和DeepSeek集成
npm install @langchain/core @langchain/deepseek

# 安装环境变量管理
npm install dotenv

# 安装参数验证工具
npm install zod

2. 配置环境变量

创建 .env 文件:

env 复制代码
DEEPSEEK_API_KEY=你的DeepSeek_API密钥

如果你还没有DeepSeek API密钥,可以访问 DeepSeek官网 注册获取。

基础概念理解

在开始编码前,我们需要理解几个核心概念:

什么是Tool(工具)?

在LangChain中,Tool是一个可以被AI模型调用的函数,它包含三个核心要素:

  • 执行逻辑:函数具体做什么
  • 名称:工具的唯一标识
  • 描述:告诉AI这个工具什么时候该用
  • 参数定义:规定工具需要什么参数

工具调用的工作流程

markdown 复制代码
用户提问 → AI分析 → 需要工具? → 是 → 返回工具调用信息
                           ↓ 否
                        直接回答

实战一:实现加法计算工具

让我们从最简单的数学计算开始,实现一个加法工具。

创建加法工具

新建 index.js 文件,开始编写代码:

javascript 复制代码
import { ChatDeepSeek } from '@langchain/deepseek'
import 'dotenv/config'
import { tool } from "@langchain/core/tools"
import { z } from 'zod'

// 创建加法工具
const addTool = tool(
    // 第一个参数:工具的执行函数
    async ({ a, b }) => {
        // 将结果转为字符串返回
        return String(a + b)
    },
    // 第二个参数:工具的配置
    {
        name: "add",                    // 工具名称
        description: "计算两个数字的和",  // 工具描述
        // 参数结构定义
        schema: z.object({
            a: z.number().describe("第一个加数"),
            b: z.number().describe("第二个加数")
        })
    }
)

console.log("加法工具定义完成:", addTool)

创建模型并绑定工具

javascript 复制代码
// 创建DeepSeek模型实例
const model = new ChatDeepSeek({
    model: 'deepseek-chat',  // 使用deepseek聊天模型
    temperature: 0            // 设置为0使输出更确定
}).bindTools([addTool])       // 绑定加法工具

// 测试调用
const res = await model.invoke("3+5等于多少?")

console.log( res.content)


### 处理工具调用结果

```javascript
// 检查是否需要调用工具
if (res.tool_calls?.length) {
    // 获取第一个工具调用
    const toolCall = res.tool_calls[0]
    console.log("调用的工具:", toolCall.name)
    console.log("工具参数:", toolCall.args)
    
    // 根据工具名称执行对应的工具
    if (toolCall.name === "add") {
        const result = await addTool.invoke(toolCall.args)
        console.log("计算结果:", result)
    }
}
  • 效果图

实战二:实现天气查询工具

接下来实现一个更实用的天气查询工具,模拟从数据库获取天气信息。

创建模拟天气数据库

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

创建天气查询工具

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

### 绑定多个工具

```javascript
// 同时绑定加法和天气工具
const model = new ChatDeepSeek({
    model: 'deepseek-chat',
    temperature: 0
}).bindTools([addTool, weatherTool])

// 测试天气查询
const res = await model.invoke("上海的天气怎么样?")

console.log("模型内容:", res.content)
console.log("工具调用:", res.tool_calls)

// 处理工具调用
if (res.tool_calls?.length) {
    const toolCall = res.tool_calls[0]
    
    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)
    }
}
  • 执行查询天气工具效果图

深入理解:为什么需要两轮调用?

!!! 核心知识点:为什么res.content是空的?

很多初学者会遇到一个困惑:为什么执行 console.log(res.content) 打印出来的是不是想要的答案?这其实是大模型工具调用的核心设计原理

深入理解工具调用的两阶段过程

当大模型决定调用工具时,它会经历以下过程:

javascript

arduino 复制代码
// 初学者常犯的错误:只做了第一步
const res = await model.invoke("上海的天气怎么样?")
console.log(res.content) // "" (空) - 为什么是空的?!

原因解释

大模型的响应分为两种情况:

场景 res.content res.tool_calls 含义
直接回答 有答案 undefined 模型用自己的知识回答
需要工具 为空 有工具信息 模型说:"我需要先查数据,请执行工具后再告诉我"

类比理解

这就像你和助手对话:

  • 你问:"上海的天气怎么样?"
  • 助手想:"我需要查一下天气数据"
  • 助手说:"请稍等,我查一下"(返回 tool_calls,content 为空)
  • 你去查:打开天气APP(执行工具)
  • 你告诉助手:"上海28°C,多云"(工具结果)
  • 助手回答:"上海今天28°C,多云..."(最终回答)

完整的工具调用流程

要获得最终答案,需要进行两轮调用

javascript

php 复制代码
// 第一轮:模型决定是否需要工具
const firstResponse = await model.invoke("上海的天气怎么样?")

// 此时 firstResponse.content 为空
// firstResponse.tool_calls 包含工具信息

if (firstResponse.tool_calls?.length) {
    // 执行工具
    const weatherData = await weatherTool.invoke(firstResponse.tool_calls[0].args)
    
    // 第二轮:把工具结果给模型,生成最终回答
    const finalResponse = await model.invoke([
        { role: "user", content: "上海的天气怎么样?" },
        { role: "assistant", content: "", tool_calls: firstResponse.tool_calls },
        { role: "tool", content: weatherData, tool_call_id: firstResponse.tool_calls[0].id }
    ])
    
    console.log("最终回答:", finalResponse.content) // ✅ 现在有答案了
}

对比测试:理解不同场景

javascript

javascript 复制代码
import {ChatDeepSeek} from '@langchain/deepseek'
import 'dotenv/config'
import {tool} from "@langchain/core/tools"
import {z} from 'zod'//用于定义工具的输入参数类型
//tool 就是一个函数,定义一个加法工具

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

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("要查询天气的城市")
        })
    }
)

console.log(weatherTool)

const addTool = tool(
    //两数相加
    //等下大模型来调用
    //参数 对象,以结构出来a b
  async({a,b})=>String(a+b),
  {
    name:"add",
    description:"计算两个数字的和",
    //参数schema 
    schema:z.object({
        a:z.number(),
        b:z.number()
    })
  }
)
const model=new ChatDeepSeek({
    model:'deepseek-chat',
    temperature:0
}).bindTools([addTool,weatherTool])

// const res=await model.invoke('3+5等于多少?')
const res=await model.invoke("上海的天气怎么样?")


console.log(res.content)
//使用?因为大模型可能不会调用工具
//? 可选链运算符 es6新增的,有利于代码的简洁和优雅
if(res.tool_calls?.length){
//    console.log(res.tool_calls[0])
if(res.tool_calls[0].name==="add"){
const result=await addTool.invoke(res.tool_calls[0].args)
console.log("计算结果:",result)
}else if(res.tool_calls[0].name==="get_weather"){
const result=await weatherTool.invoke(res.tool_calls[0].args)
console.log("天气结果:",result)
}
}

常见问题解答

Q1: 什么时候需要使用 bindTools

当你希望模型具备调用外部工具的能力时,就需要使用 bindTools。如果不绑定工具,模型就只能依靠自身知识回答。

Q2: 如何让工具返回更复杂的数据?

工具函数可以返回任何类型的数据,不仅仅是字符串。例如可以返回对象:

javascript 复制代码
const complexTool = tool(
    async ({ id }) => {
        const data = await fetchData(id)
        return {
            success: true,
            data: data,
            timestamp: new Date().toISOString()
        }
    },
    // ... 配置
)

Q3: 为什么有些问题不触发工具调用?

模型会根据问题和工具描述来决定是否调用工具。比如你问"你好",模型不会调用天气工具,因为描述中说明这是查天气用的。

总结

通过本文的学习,我们掌握了:

  1. Tool的基本概念:给AI添加外部能力的函数
  2. 工具的定义方法 :使用 tool() 函数创建
  3. 参数验证:使用 Zod 确保参数正确
  4. 工具绑定 :通过 bindTools() 让模型具备工具调用能力
  5. 结果处理:如何执行工具并生成最终回答

工具调用是让AI从"聊天机器人"进化为"智能助手"的关键一步。掌握了这个技能,你就可以开发出能查询数据库、调用API、处理复杂任务的智能应用。


希望这篇文章对你有帮助,欢迎在掘金点赞收藏,有问题在评论区留言讨论!

相关推荐
a1117761 小时前
波浪圆圈背景效果(html 开源)
前端·html
程序员ys1 小时前
网页白屏的原理与优化
前端·性能优化·浏览器
@PHARAOH2 小时前
WHAT - SWC Rust-based platform for the Web
开发语言·前端·rust
滕青山2 小时前
HTML编码/解码 核心JS实现
前端·javascript·vue.js
dustcell.2 小时前
企业级web应用服务器
前端
RunsenLIu2 小时前
智慧房屋租赁管理系统
前端·javascript·vue.js
凌云拓界2 小时前
TypeWell全攻略(四):AI键位分析,让数据开口说话
前端·人工智能·后端·python·ai·交互