nestjs+langchain:Tools

6 阅读1分钟

书接上文

一、Tools

Tools 本质上是封装了特定功能的可调用模块,是Agent、Chain或LLM可以用来与世界互动的接口。

具体实现

使用 tool() 函数

tools.ts

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

/**
 * 定义加法工具
 */
export const addTwoNumberTool = tool(
  async (input: { a: number; b: number }) => {
    // 工具的具体执行逻辑
    return input.a + input.b;
  },
  {
    name: 'add_two_number',
    description: 'two number add',
    schema: z.object({
      a: z.number().describe('第一个整数'),
      b: z.number().describe('第二个整数'),
    }),
    // 注意:LangChain.js 的 tool 函数配置中通常没有 returnDirect 选项,
    // 这种行为通常在 Agent 层面控制,或者通过返回特定格式字符串实现。
  },
);

agents.service.ts

import { addTwoNumberTool } from 'src/tools/tools';

async useAddTool() {
    console.log(`name = ${addTwoNumberTool.name}`);
    console.log(`description = ${addTwoNumberTool.description}`);
    console.log(`schema = ${JSON.stringify(addTwoNumberTool.schema, null, 2)}`);
    const result = await addTwoNumberTool.invoke({ a: 5, b: 10 });
    console.log(result); 
}

输出结果:

name = add_two_number
description = two number add
schema = {
  "def": {
    "type": "object",
    "shape": {
      "a": {
        "def": {
          "type": "number",
          "checks": []
        },
        "type": "number",
        "minValue": null,
        "maxValue": null,
        "isInt": false,
        "isFinite": true,
        "format": null
      },
      "b": {
        "def": {
          "type": "number",
          "checks": []
        },
        "type": "number",
        "minValue": null,
        "maxValue": null,
        "isInt": false,
        "isFinite": true,
        "format": null
      }
    }
  },
  "type": "object"
}
15

继承 StructuredTool 类

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

export class AddTwoNumberTool extends StructuredTool {
  name = 'add_two_number';
  description = '两数相加';
  schema = z.object({
    a: z.number().describe('第一个整数'),
    b: z.number().describe('第二个整数'),
  });

  constructor() {
    super();
  }

  async _call(input: { a: number; b: number }): Promise<string> {
    // 注意:StructuredTool 通常返回字符串,以便 LLM 处理
    return (input.a + input.b).toString();
  }
}

// 实例化
export const addTwoNumberTool = new AddTwoNumberTool();

工具调用举例

大模型会决定是否调用工具,并分析出调用哪个工具,但不会自动调用该工具 tools.ts

import { StructuredTool } from '@langchain/core/tools';
import * as fs from 'fs';
import * as path from 'path';
// 自定义 MoveFileTool
export class MoveFileTool extends StructuredTool {
  name = 'move_file';
  description = '移动文件从源路径到目标路径';
  schema = z.object({
    sourcePath: z.string().describe('源文件的绝对路径或相对路径'),
    destinationPath: z.string().describe('目标文件夹的绝对路径或相对路径'),
  });

  async _call(input: {
    sourcePath: string;
    destinationPath: string;
  }): Promise<string> {
    const source = input.sourcePath;
    const destDir = input.destinationPath;

    // 确保目标目录存在
    if (!fs.existsSync(destDir)) {
      fs.mkdirSync(destDir, { recursive: true });
    }

    const fileName = path.basename(source);
    const destPath = path.join(destDir, fileName);

    fs.renameSync(source, destPath);
    return `文件已成功从 ${source} 移动到 ${destPath}`;
  }
}

agents.service.ts

  async runMoveFileToolExample() {
    // 使用自定义工具
    const tools = [new MoveFileTool()];
    
    // 绑定工具到 LLM 
    // bindTools 是 LangChain.js 中处理函数调用的标准方式 
    // 它会自动处理 schema 转换和提示词注入
    const modelWithTools = this.llm.bindTools(tools);

    const messages = [new HumanMessage('将文件 a.txt 移动到桌面文件夹')];

    console.log('--- Invoking LLM with Tools ---');

    try {
      // 调用模型 
      // 模型会决定是返回文本还是调用工具
      const response = await modelWithTools.invoke(messages);

      console.log('LLM Response:', response);
      // 检查是否有工具调用
      if (response.tool_calls && response.tool_calls.length > 0) {
        console.log('--- Tool Calls Detected ---');
        response.tool_calls.forEach((tc) => {
          console.log(`Tool Name: ${tc.name}`);
          console.log(`Arguments: ${JSON.stringify(tc.args)}`);
        });
        // 执行工具并获取结果 
        // 通常 Agent 框架会自动处理这一步。如果手动处理,需要根据 name 找到对应的工具实例并执行
        const toolCall = response.tool_calls[0];
        const selectedTool = tools.find((t) => t.name === toolCall.name);

        if (selectedTool) {
          console.log('--- Executing Tool ---');
          const result = await selectedTool.invoke(toolCall.args);
          console.log('Tool Execution Result:', result);
        }
      } else {
        console.log('No tool calls, direct response:', response.content);
      }
    } catch (error) {
      console.error('Error during tool invocation:', error);
    }
  }