LangChain前端学习笔记:Tools模块详解

4 阅读15分钟

LangChain前端学习笔记:Tools模块详解

摘要

本文将深入讲解LangChain框架中的Tools模块,展示如何在前端JavaScript/TypeScript环境中让大语言模型(LLM)具备调用外部工具的能力。通过Tools模块,前端开发者可以扩展LLM的功能边界,使其能够执行计算、查询实时数据等超出纯文本生成的复杂任务。本文将从Tools模块的基本概念入手,详细讲解如何在前端声明工具、绑定工具到LLM模型、处理工具调用结果,并提供天气查询和计算工具等实际应用案例,帮助前端开发者快速掌握这一强大功能。

一、LangChain框架与Tools模块概述

LangChain是一个基于大语言模型的端到端应用程序开发框架,它通过模块化设计简化了LLM应用的开发流程 。LangChain的核心理念是"数据感知"、"主动性和链性",它允许开发者将LLM与其他数据源和工具连接起来,创建更智能、更实用的应用

Tools模块是LangChain框架中的重要组成部分,它使得LLM能够调用外部工具(函数),从而突破纯文本生成的限制 。与Memory模块专注于对话历史记忆不同,Tools模块的核心价值在于扩展LLM的能力边界 ,使其能够执行计算、查询实时数据、访问外部API等复杂操作。

在前端应用中,Tools模块特别有价值,因为它允许开发者在浏览器环境中直接扩展LLM的功能,无需依赖后端服务。前端开发者可以利用Tools模块构建具有实用价值的应用,如天气查询助手、计算器、网页爬虫等,为用户提供更丰富的交互体验。

二、Tools模块的工作原理

Tools模块的工作原理可以分为三个关键步骤:

  1. 工具声明与定义:首先需要声明和定义外部工具,包括工具名称、功能描述和参数模式 。这些信息将被传递给LLM,帮助模型理解可用工具及其功能。

  2. 工具绑定到模型:将定义好的工具绑定到LLM模型上,使模型能够根据输入决定是否调用这些工具 。绑定方式可以是显式的(如bind_tools())或通过Agent(如create_agent())实现的 。

  3. 工具调用与结果处理:当用户输入需要工具辅助时,LLM会生成工具调用指令,前端应用需要解析这些指令,执行相应工具,并将结果反馈给LLM生成最终响应 。

LLM调用工具的过程本质上是一个"思考-行动-观察"的循环 。模型首先理解用户意图,然后决定是否需要调用工具获取额外信息或执行特定操作,接着执行工具调用并观察结果,最后将工具结果整合到响应中。这种机制使LLM能够像人类一样利用工具增强其能力 。

在JavaScript环境中,Tools模块的工作流程如下:

// 1. 定义工具
const weatherTool = tool(
  async ({ city }) => {
    // 工具实现逻辑
  },
  {
    name: 'get_weather',
    description: '查询指定城市的今日天气情况',
    schema: z.object({
      city: z.string().describe('要查询天气的城市名称')
    })
  }
);

// 2. 绑定工具到模型
const modelWithTools = model.bindTools([weatherTool]);

// 3. 处理工具调用
const response = await modelWithTools.invoke('广州的天气如何?');
if (response.tool_calls) {
  // 执行工具并整合结果
}

三、前端环境中的Tools声明与配置

在前端JavaScript/TypeScript环境中,Tools模块的声明和配置有其独特之处。前端开发者可以使用tool()函数或StructuredTool.from_function方法创建工具 ,这些工具可以是异步函数(如调用API)或同步函数(如执行计算)。

1. 工具声明的基本语法

使用tool()函数声明工具的基本语法如下:

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

const addTool = tool(
  async ({ a, b }) => String(a + b), // 工具函数
  {
    name: 'add', // 工具名称
    description: '用于计算两个数字的和', // 工具描述
    schema: z.object({ // 参数模式
      a: z.number().describe('第一个数字'),
      b: z.number().describe('第二个数字')
    })
  }
);

这里的关键点包括:

  • 工具函数:可以是同步或异步函数,接受一个对象参数并返回结果。
  • name:工具的名称,LLM会根据这个名称调用工具。
  • description:工具的功能描述,LLM会根据这个描述决定是否调用工具。
  • schema:使用zod库定义的参数模式,确保输入参数的有效性 。

2. 参数验证与类型安全

在前端JavaScript环境中,参数验证是确保工具安全调用的重要环节。LangChain.js推荐使用zod库进行参数验证,这可以确保传递给工具的参数符合预期格式和类型。

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

const calculatorTool = tool(
  async ({ operation, operand1, operand2 }) => {
    switch (operation) {
      case 'add':
        return operand1 + operand2;
      case 'subtract':
        return operand1 - operand2;
      // 其他运算...
      default:
        return '不支持的操作';
    }
  },
  {
    name: 'calculator',
    description: '执行基本数学运算',
    schema: z.object({
      operation: z.union([
        z.literal('add').describe('加法操作'),
        z.literal('subtract').describe('减法操作')
      ]),
      operand1: z.number().describe('第一个操作数'),
      operand2: z.number().describe('第二个操作数')
    })
  }
);

使用zod进行参数验证的优势在于:

  • 类型安全:确保参数类型正确,避免运行时错误。
  • 文档生成:自动生成参数描述,便于LLM理解工具功能。
  • 错误处理:当参数无效时,工具可以返回明确的错误信息。

3. 工具描述的编写技巧

编写清晰、准确的工具描述对于LangChain正确调用工具至关重要。工具描述应该明确说明工具的功能、适用场景和限制条件,帮助LLM理解何时以及如何调用该工具。

好的工具描述应包含以下要素:

  • 明确的功能说明:如"获取指定城市实时天气信息"。
  • 使用场景示例:如"当用户询问'北京今天天气如何?'时调用"。
  • 参数说明:明确每个参数的含义和格式。
  • 返回值说明:描述工具返回的数据格式和内容。
const weatherTool = tool(
  async ({ city }) => {
    // 实现逻辑...
  },
  {
    name: 'get_weather',
    description: `获取指定城市的实时天气信息。
当用户询问天气相关问题时(如"广州今天天气如何?"),应该调用这个工具。
参数:
  city (string):要查询天气的城市名称
返回值:
  包含温度、天气状况和风力的字符串,格式为"当前{城市}的天气是{温度},{天气状况},风力{风力}"`,

    schema: z.object({
      city: z.string().describe('要查询天气的城市名称')
    })
  }
);

四、将Tools绑定到LLM模型

在前端环境中,将Tools绑定到LLM模型是实现工具调用的关键步骤 。LangChain.js提供了多种方式将工具与模型关联,最常用的是bindTools()方法和Agent模式。

1. 使用bindTools()方法

bindTools()方法是最直接的工具绑定方式,它将工具列表绑定到LLM模型上,返回一个支持工具调用的新模型实例。

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

const model = new ChatDeepSeek({
  model: 'deepseek-chat',
  temperature: 0,
});

// 绑定工具到模型
const modelWithTools = model.bindTools([weatherTool, addTool]);

// 调用模型
const response = await modelWithTools.invoke('广州的天气如何?');

bindTools()方法的参数是一个工具列表,每个工具都实现了Tool接口。绑定工具后,模型在生成响应时会考虑是否调用这些工具 ,这取决于模型对用户意图的理解和工具描述的清晰度。

2. Agent模式的工具绑定

对于更复杂的场景,可以使用Agent模式来绑定工具。Agent是一种更高级的工具使用方式,它允许模型自主决定是否调用工具以及调用哪个工具。

import { createAgent } from 'langchain';
import { ChatDeepSeek } from '@langchain/deepseek';

const agent = createAgent({
  model: new ChatDeepSeek({ model: 'deepseek-chat' }),
  tools: [weatherTool, addTool],
  systemPrompt: '你是一个能够调用外部工具的智能助手。'
});

const response = await agent.invoke({ messages: [{ role: 'user', content: '广州的天气如何?' }] });

Agent模式的优势在于:

  • 自动化工具调用:模型自主决定是否调用工具,无需前端开发者手动干预 。
  • 更复杂的决策流程:模型可以执行"思考-行动-观察"的循环,处理更复杂的任务。
  • 更好的错误处理:Agent模式通常包含内置的错误处理机制,提高应用的健壮性。

五、处理工具调用结果

当LLM决定调用工具时,它会在响应中返回tool_calls数组,包含需要调用的工具名称和参数。前端开发者需要解析这些工具调用指令,执行相应工具,并将结果反馈给LLM生成最终响应

1. 解析工具调用指令

工具调用指令的解析是处理工具调用的第一步。LLM返回的响应中,tool_calls数组包含了需要调用的工具名称和参数 ,这些信息需要被前端应用正确解析。

const response = await modelWithTools.invoke('广州的天气如何?');

if (response.tool_calls?.length) {
  // 解析工具调用指令
  const toolCall = response.tool_calls[0];
  console.log(`需要调用工具:${toolCall.name},参数:`, toolCall.args);
}

工具调用指令的结构通常包括:

  • name:工具名称,对应绑定时定义的工具名称。
  • args:工具参数,是一个对象,包含工具需要的输入参数。
  • id:工具调用的唯一标识,用于跟踪和管理工具调用。

2. 执行工具并整合结果

解析工具调用指令后,前端开发者需要执行相应的工具函数,并将结果反馈给LLM,以便模型生成最终响应。

if (response.tool_calls?.length) {
  const toolCall = response.tool_calls[0];
  let toolResult;

  // 根据工具名称执行相应工具
  switch (toolCall.name) {
    case 'get_weather':
      toolResult = await weatherTool.invoke(toolCall.args);
      break;
    case 'add':
      toolResult = await addTool.invoke(toolCall.args);
      break;
    default:
      toolResult = '未知工具';
  }

  // 将工具结果反馈给LLM
  const finalResponse = await modelWithTools.invoke(
    `工具调用结果:${toolResult}\n请基于此结果回答用户的问题。`
  );

  console.log('最终回答:', finalResponse.content);
}

工具结果的整合方式取决于应用需求,常见的做法包括:

  • 直接返回结果:将工具结果直接作为最终响应返回给用户。
  • 结合LLM生成最终响应:将工具结果作为LLM的输入,让模型生成更自然、更完整的回答。
  • 多轮工具调用:处理需要多个工具调用的复杂任务,模型可以决定是否需要进一步调用工具。

3. 错误处理与异常捕获

在工具调用过程中,错误处理是确保应用健壮性的重要环节。前端开发者需要捕获工具调用过程中的异常,并返回有意义的错误信息。

try {
  // 执行工具调用
  const toolResult = await weatherTool.invoke(toolCall.args);
  // 处理工具结果...
} catch (error) {
  // 捕获并处理错误
  console.error('工具调用失败:', error);
  let errorMessage;

  if (error instanceof zod.ZodError) {
    errorMessage = '参数格式错误,请检查输入。';
  } else if (error.message?.includes('网络错误')) {
    errorMessage = '网络连接问题,请稍后再试。';
  } else {
    errorMessage = '工具调用失败,请稍后再试。';
  }

  // 将错误信息反馈给LLM
  const finalResponse = await modelWithTools.invoke(
    `工具调用失败:${errorMessage}\n请基于此信息回答用户的问题。`
  );

  console.log('最终回答:', finalResponse.content);
}

常见的工具调用错误包括:

  • 参数验证错误:用户输入的参数不符合工具定义的模式。
  • 网络连接错误:工具需要访问外部API时可能发生的网络问题。
  • 工具实现错误:工具函数内部逻辑错误导致的异常。
  • 模型决策错误:LLM错误地调用了不适用的工具。

六、实际应用案例分析

1. 天气查询工具

天气查询工具是一个典型的前端应用场景,它展示了如何让LLM调用外部API获取实时数据。

工具声明

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

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: `获取指定城市的天气信息。
当用户询问天气相关问题时(如"北京今天天气如何?"),应该调用这个工具。
参数:
  city (string):要查询天气的城市名称
返回值:
  包含温度、天气状况和风力的字符串,格式为"当前{城市}的天气是{温度},{天气状况},风力{风力}"`,

    schema: z.object({
      city: z
        .string()
        .describe('要查询天气的城市名称')
        .min(1)
        .max(20)
    })
  }
);

前端集成

import React, { useState } from 'react';

function WeatherQueryComponent() {
  const [input, setInput] = useState('');
  const [weather, setWeather] = useState(null);
  const [error,设立Error] =设立useState(null);

  const handleQuery = async () => {
    try {
      // 调用模型
      const response = await modelWithTools.invoke(`查询${input}的天气情况。请使用get_weather工具。如果无法获取信息,请提供合理建议。`);

      // 解析工具调用
      if (response.tool_calls?.length) {
        const toolCall = response.tool_calls[0];
        const result = await weatherTool.invoke(toolCall.args);
        setWeather(result);
      } else {
        setWeather('模型没有调用天气查询工具。');
      }
    } catch (err) {
     设立Error('查询失败,请稍后再试。');
      console.error('天气查询失败:', err);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="请输入城市名称"
      />
      <button onClick={handleQuery}>查询天气</button>
      {weather && <div className="weather Result"><p>{weather}</p></div>}
      {error && <div className="error的消息"><p>{error}</p></div>}
    </div>
  );
}

天气查询工具的优势

  • 实时数据获取:可以查询最新天气信息,而非仅依赖模型训练数据。
  • 自然语言交互:用户可以用自然语言提问,如"广州今天适合出行吗?"
  • 错误处理能力:当城市名称不存在或格式错误时,可以提供友好的错误提示。

2. 计算工具

计算工具展示了如何让LLM执行数学运算等超出纯文本生成的复杂任务。

工具声明

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

const addTool = tool(
  async ({ a, b }) => {
    // 参数验证
    if (typeof a !== 'number' || typeof b !== 'number') {
      return '参数必须是数字。';
    }

    // 执行计算
    return String(a + b);
  },
  {
    name: 'add',
    description: `执行两个数字的加法运算。
当用户询问数学问题时(如"3加5等于多少?"),应该调用这个工具。
参数:
  a (number):第一个数字
  b (number):第二个数字
返回值:
  两个数字的和,格式为字符串`,

    schema: z.object({
      a: z
        .number()
        .describe('第一个数字')
        .min(-1000)
        .max(1000),
      b: z
        .number()
        .describe('第二个数字')
        .min(-1000)
        .max(1000)
    })
  }
);

const calculatorTool = tool(
  async ({ expression }) => {
    try {
      // 安全执行表达式
      const result = new Function(`return ${expression}`)();

      // 检查结果是否为数字
      if (typeof result !== 'number') {
        return '表达式结果不是数字。';
      }

      return String(result);
    } catch (error) {
      return '表达式格式错误。';
    }
  },
  {
    name: 'calculator',
    description: `执行数学表达式。
当用户询问数学问题时(如"3乘5加2等于多少?"),应该调用这个工具。
参数:
  expression (string):要计算的数学表达式
返回值:
  数学表达式的结果,格式为字符串`,

    schema: z.object({
      expression: z
        .string()
        .describe('要计算的数学表达式')
        .min(1)
        .max(100)
    })
  }
);

前端集成

import React, { useState } from 'react';

function CalculatorComponent() {
  const [input, setInput] = useState('');
  const [result,设立Result] =设立useState(null);
  const [error,设立Error] =设立useState(null);

  const handleCalculate = async () => {
    try {
      // 调用模型
      const response = await modelWithTools.invoke(`计算表达式:${input}。请使用calculator工具。如果表达式格式错误,请提供合理建议。`);

      // 解析工具调用
      if (response tool_calls?.length) {
        const toolCall = response.tool_calls[0];
        const result = await calculatorTool.invoke(toolCall.args);
       设立Result(result);
      } else {
       设立Result('模型没有调用计算器工具。');
      }
    } catch (err) {
     设立Error('计算失败,请稍后再试。');
      console.error('计算器工具失败:', err);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="请输入数学表达式"
      />
      <button onClick={handleCalculate}>计算</button>
      {result && <div className="result消息"><p>{result}</p></div>}
      {error && <div className="error消息"><p>{error}</p></div>}
    </div>
  );
}

计算工具的优势

  • 精确计算能力:可以执行复杂的数学运算,确保结果的准确性。
  • 自然语言交互:用户可以用自然语言提问,如"3乘5加2等于多少?"
  • 错误处理能力:当表达式格式错误或包含无效字符时,可以提供友好的错误提示。

七、高级应用场景与最佳实践

1. 组合工具使用

组合工具使用展示了如何让LLM调用多个工具协同工作,处理更复杂的任务。

工具声明

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

const currencyConversionTool = tool(
  async ({ from, to, amount }) => {
    // 汇率转换实现逻辑...
  },
  {
    name: 'currencyConversion',
    description: `执行货币兑换计算。
当用户询问货币兑换问题时(如"100美元等于多少人民币?"),应该调用这个工具。
参数:
  from (string):原货币代码(如USD、CNY)
  to (string):目标货币代码(如USD、CNY)
  amount (number):要兑换的金额
返回值:
  转换后的金额和货币代码,格式为"转换后的金额:{amount} {to}"`,

    schema: z.object({
      from: z
        .string()
        .describe('原货币代码')
        .min(3)
        .max(3)
        .regex(/^[A-Z]{3}$/),
      to: z
        .string()
        .describe('目标货币代码')
        .min(3)
        .max(3)
        .regex(/^[A-Z]{3}$/),
      amount: z
        .number()
        .describe('要兑换的金额')
        .min(0.01)
        .max(1000000)
    })
  }
);

const calculatorTool = tool(
  // 前面定义的计算器工具...
);

前端集成

import React, { useState } from 'react';

function CurrencyConverterComponent() {
  const [from,设立From] =设立useState('USD');
  const [to,设立To] =设立useState('CNY');
  const [amount,设立Amount] =设立useState('');
  const [result,设立Result] =设立useState(null);
  const [error,设立Error] =设立useState(null);

  const handleConvert = async () => {
    try {
      // 调用模型
      const response = await modelWithTools.invoke(
        `将${amount} ${from}转换为${to}。请使用currencyConversion工具。如果汇率未知,请使用calculator工具估算。`
      );

      // 解析工具调用
      if (response.tool_calls?.length) {
        const toolCall = response工具调用[0];
        const result = await工具调用工具.invoke工具调用参数;
       设立Result(result);
      } else {
       设立Result('模型没有调用货币转换工具。');
      }
    } catch (err) {
     设立Error('转换失败,请稍后再试。');
      console.error('货币转换失败:', err);
    }
  };

  return (
    <div>
      <div>
        <select
          value={from}
          onChange={(e) =>设立From(e.target.value)}
        >
          <option value="USD">美元</option>
          <option value="CNY">人民币</option>
          <option value="EUR">欧元</option>
        </select>
        <input
          type="number"
          value={amount}
          onChange={(e) =>设立Amount(e.target.value)}
          placeholder="请输入金额"
        />
        <select
          value={to}
          onChange={(e) =>设立To(e.target.value)}
        >
          <option value="USD">美元</option>
          <option value="CNY">人民币</option>
          <option value="EUR">欧元</option>
        </select>
      </div>
      <button onClick={handleConvert}>转换</button>
      {result && <div className="result消息"><p>{result}</p></div>}
      {error && <div className="error消息"><p>{error}</p></div>}
    </div>
  );
}

组合工具使用的最佳实践

  • 明确工具优先级:当多个工具可能被调用时,明确它们的优先级和适用场景。
  • 提供清晰的指导:在提示词中明确指导模型如何选择和使用工具。
  • 处理工具调用链:当需要多个工具调用时,确保能够正确处理工具调用链。

2. 与前端API集成

与前端API集成展示了如何让LLM调用浏览器环境中的API,如地理位置、本地存储等。

工具声明

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

const getGeoLocationTool = tool(
  async () => {
    return new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          resolve({
            latitude: position.coords.latitude,
            longitude: position coords.longitude
          });
        },
        (error) => {
          reject(`获取地理位置失败:${error.message}`);
        },
        {
          enableHighAccuracy: true,
          maximumAge: 0,
          timeout: 10000
        }
      );
    });
  },
  {
    name: 'get GeoLocation',
    description: `获取用户当前的地理位置。
当用户询问"我在哪里?"或"附近有什么?"时,应该调用这个工具。
参数:
  无
返回值:
  包含纬度和经度的JSON对象,格式为{"latitude": number, "longitude": number}`,

    schema: z.object({}) // 无参数
  }
);

const searchWebTool = tool(
  async ({ query }) => {
    // 使用浏览器API执行网页搜索...
  },
  {
    name: 'search Web',
    description: `在互联网上搜索信息。
当用户询问需要实时信息的问题时(如"最新的新闻"),应该调用这个工具。
参数:
  query (string):要搜索的关键词
返回值:
  搜索结果的摘要或链接列表`,

    schema: z.object({
      query: z
        .string()
        .describe('要搜索的关键词')
        .min(1)
        .max(100)
    })
  }
);

前端集成

import React, { useState, useEffect } from 'react';

function GeoLocationComponent() {
  const [input,设立Input] =设立useState('');
  const [position,设立Position] =设立useState(null);
  const [error,设立Error] =设立useState(null);

  const handleQuery = async () => {
    try {
      // 调用模型
      const response = await modelWithTools.invoke(`查询${input}。请使用get GeoLocation和search Web工具。如果无法获取信息,请提供合理建议。`);

      // 解析工具调用
      if (response.tool_calls?.length) {
        const toolCall = response工具调用[0];
        let result;

        switch (toolCall.name) {
          case 'get GeoLocation':
            result = await getGeoLocationTool.invoke(toolCall.args);
           设立Position(result);
            break;
          case 'search Web':
            result = await searchWebTool.invoke(toolCall.args);
           设立Result(result);
            break;
          default:
            result = '未知工具';
        }

        // 可能需要进一步调用模型处理结果
        const finalResponse = await modelWithTools.invoke(
          `获取到地理位置:${JSON.stringify(position)}。请基于此回答用户的问题。`
        );

       设立Result(finalResponse.content);
      } else {
       设立Result('模型没有调用地理位置工具。');
      }
    } catch (err) {
     设立Error('查询失败,请稍后再试。');
      console.error('地理位置查询失败:', err);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) =>设立Input(e.target.value)}
        placeholder="请输入查询内容"
      />
      <button onClick={handleQuery}>查询</button>
      {position && <div className="position消息"><p>地理位置:{position latitude}, {position longitude}</p></div>}
      {result && <div className="result消息"><p>{result}</p></div>}
      {error && <div className="error消息"><p>{error}</p></div>}
    </div>
  );
}

与前端API集成的最佳实践

  • 处理浏览器API限制:如地理位置API需要用户授权,工具调用可能失败。
  • 提供明确的指导:在提示词中明确指导模型如何调用浏览器API。
  • 处理异步操作:浏览器API通常是异步的,确保工具函数正确处理异步操作。

3. 与后端API集成

与后端API集成展示了如何让LLM调用后端服务,如数据库查询、业务逻辑处理等。

工具声明

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

const searchProductsTool = tool(
  async ({ query }) => {
    try {
      const response = await fetch('/api/products?query=${query}', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env BackEND_API_KEY}`
        }
      });

      if (!response.ok) {
        throw new Error(`HTTP错误!状态码:${response.status}`);
      }

      const data = await response.json();
      return JSON.stringify(data);
    } catch (error) {
      return `查询失败:${error.message}`;
    }
  },
  {
    name: 'search Products',
    description: `搜索产品信息。
当用户询问产品相关问题时(如"有哪些笔记本电脑?"),应该调用这个工具。
参数:
  query (string):要搜索的关键词
返回值:
  符合条件的产品列表,格式为JSON数组`,

    schema: z.object({
      query: z
        .string()
        .describe('要搜索的关键词')
        .min(1)
        .max(100)
    })
  }
);

前端集成

import React, { useState } from 'react';

function ProductSearchComponent() {
  const [input,设立Input] =设立useState('');
  const [products,设立Products] =设立useState([]);
  const [error,设立Error] =设立useState(null);

  const handleSearch = async () => {
    try {
      // 调用模型
      const response = await modelWithTools.invoke(`搜索产品:${input}。请使用search Products工具。如果结果为空,请提供合理建议。`);

      // 解析工具调用
      if (response.tool_calls?.length) {
        const toolCall = response工具调用[0];
        const result = await searchProductsTool.invoke(toolCall.args);

        // 解析结果
        try {
          const parsedResult = JSON.parse(result);
         设立Products(parsedResult);
        } catch (error) {
         设立Error('结果解析失败,请稍后再试。');
        }
      } else {
       设立Error('模型没有调用产品搜索工具。');
      }
    } catch (err) {
     设立Error('搜索失败,请稍后再试。');
      console.error('产品搜索失败:', err);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) =>设立Input(e.target.value)}
        placeholder="请输入产品关键词"
      />
      <button onClick={handleSearch}>搜索</button>
      {products.length > 0 && (
        <div className="products列表">
          {products.map((product, index) => (
            <div key={index} className="product消息">
              <h3>{product.name}</h3>
              <p>价格:${product.price}</p>
              <p>库存:{product stock}</p>
            </div>
          ))}
        </div>
      )}
      {error && <div className="error消息"><p>{error}</p></div>}
    </div>
  );
}

与后端API集成的最佳实践

  • 处理网络请求:使用fetchaxios处理HTTP请求,确保正确处理响应和错误。
  • 安全考虑:确保工具调用不会暴露敏感信息,如API密钥。
  • 性能优化:处理可能的网络延迟,提供加载状态和超时处理。

八、常见问题与解决方案

1. 工具未被调用

问题:LLM没有调用绑定的工具,直接返回了答案。

原因:可能是工具描述不够清晰,或模型无法理解何时需要调用工具。

解决方案

  • 在提示词中明确指导模型调用工具。
  • 提供更清晰、准确的工具描述。
  • 使用更明确的指令格式,如"请使用{工具名称}工具回答这个问题"。

示例提示词

const prompt = ChatPromptTemplate.fromMessages([
  ['system', `你是一个能够调用外部工具的智能助手。
当用户询问天气相关问题时,必须使用get_weather工具。
当用户询问数学问题时,必须使用calculator工具。
请严格按照工具定义的参数调用工具,并将结果整合到最终回答中。`],
  ['human', '{input}']
]);

2. 工具参数错误

问题:工具调用返回了参数错误,如"城市名称无效"或"数字格式错误"。

原因:可能是用户输入的参数不符合工具定义的模式,或模型生成的参数格式错误。

解决方案

  • 在工具函数中添加更详细的参数验证。
  • 提供更明确的错误提示,帮助用户理解问题所在。
  • 在提示词中强调参数格式要求,指导模型生成正确的参数。

增强参数验证的示例

const calculatorTool = tool(
  async ({ expression }) => {
    // 更严格的参数验证
    if (typeof expression !== 'string') {
      return '表达式必须是字符串。';
    }

    // 检查表达式是否只包含数字和运算符
    if (!/^[0-9+\-*/(). ]+$/.test(expression)) {
      return '表达式包含无效字符。';
    }

    try {
      const result = new Function(`return ${expression}`)();

      if (typeof result !== 'number') {
        return '表达式结果不是数字。';
      }

      return String(result);
    } catch (error) {
      return '表达式格式错误。';
    }
  },
  // ...其他配置...
);

3. 工具调用延迟

问题:工具调用(如API请求)导致应用响应延迟,用户体验下降。

原因:可能是工具调用需要访问外部服务,存在网络延迟。

解决方案

  • 使用加载状态提示用户等待。
  • 实现工具调用的超时机制。
  • 考虑缓存常用工具调用结果,减少重复请求。

处理延迟的示例

const handleQuery = async () => {
 设立Loading(true);
 设立Result(null);
 设立Error(null);

  try {
    // 调用模型
    const response = await modelWithTools.invoke(`查询${input}的天气情况。请使用get_weather工具。如果无法获取信息,请提供合理建议。`, {
      timeout: 10000 // 设置10秒超时
    });

    // ...处理工具调用...

  } catch (err) {
   设立Error('查询失败,请稍后再试。');
    console.error('天气查询失败:', err);
  } finally {
   设立Loading(false);
  }
};

九、未来发展趋势与展望

随着大语言模型和LangChain框架的不断发展,Tools模块在前端应用中的价值将进一步提升。未来,我们可以期待以下发展趋势

  1. 更丰富的前端工具库:LangChain.js将提供更多的预置工具,如浏览器API集成、前端框架特定工具等。

  2. 更智能的工具选择:LLM将能够更准确地理解用户意图,选择合适的工具进行调用,减少错误和不必要的工具调用。

  3. 工具调用的自动化:Agent模式将成为主流,前端开发者只需声明工具和模型,无需手动处理工具调用和结果整合。

  4. 与WebAssembly的结合:未来的Tools模块可能支持通过WebAssembly调用更复杂的计算工具,如机器学习模型、图像处理算法等。

  5. 工具调用的安全增强:随着工具调用的普及,安全机制将不断完善,确保工具调用不会导致安全漏洞或隐私泄露。

在前端应用中,Tools模块将使LLM能够更深入地融入用户界面和交互流程,为用户提供更智能、更实用的体验。例如,未来的前端应用可能包含:

  • 智能表单助手:能够自动填写表单、验证输入、提供建议。
  • 实时数据分析工具:能够执行数据查询、计算和可视化。
  • 交互式学习助手:能够根据用户学习进度和需求,调用合适的资源和工具。

十、总结与最佳实践

通过LangChain的Tools模块,前端开发者可以轻松实现LLM的工具调用能力,扩展模型的功能边界 。在实际应用中,应遵循以下最佳实践:

  1. 清晰定义工具:确保工具名称、描述和参数模式清晰准确,帮助LLM正确理解工具功能。

  2. 合理绑定工具:根据应用需求选择合适的工具绑定方式,简单场景使用bindTools(),复杂场景使用Agent模式。

  3. 妥善处理结果:解析LLM返回的工具调用指令,执行相应工具,并将结果反馈给LLM生成最终响应。

  4. 加强错误处理:实现完善的错误捕获和处理机制,确保应用在工具调用失败时仍能提供有意义的反馈。

  5. 优化用户体验:处理工具调用延迟,提供加载状态提示,确保应用响应流畅。

Tools模块是LangChain框架中连接LLM与外部世界的关键桥梁 ,它使前端应用能够突破纯文本生成的限制,执行计算、查询实时数据、访问外部API等复杂操作。通过合理使用Tools模块,前端开发者可以构建更智能、更实用的AI应用,为用户提供更丰富的交互体验。

在实际开发中,建议从简单的工具开始,逐步扩展到更复杂的场景,同时密切关注LangChain.js的更新和最佳实践,确保应用能够充分利用框架的新功能和改进。