Skip to content

Tool:让大模型调用工具

大模型只能告诉你怎么做,不能帮你做。Cursor 能直接读写文件、跑命令,靠的就是 Tool。

环境准备

用阿里的千问模型(免费额度),通过 .env 管理 API Key:

bash
MODEL_NAME=qwen-plus
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
bash
pnpm install @langchain/openai @langchain/core zod dotenv

调用大模型

js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';

const model = new ChatOpenAI({
  modelName: process.env.MODEL_NAME || 'qwen-plus',
  apiKey: process.env.OPENAI_API_KEY,
  temperature: 0,  // 0 = 严格按指令,不要自由发挥
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL,
  },
});

const response = await model.invoke("介绍下自己");
console.log(response.content);

创建 Tool

核心就三样:函数实现名称和描述参数格式(用 zod 定义)。

js
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import fs from 'node:fs/promises';

const readFileTool = tool(
  async ({ filePath }) => {
    const content = await fs.readFile(filePath, 'utf-8');
    return `文件内容:\n${content}`;
  },
  {
    name: 'read_file',
    description: '读取文件内容,输入文件路径',
    schema: z.object({
      filePath: z.string().describe('文件路径'),
    }),
  }
);

绑定 Tool 到模型

js
const modelWithTools = model.bindTools([readFileTool]);

完整调用流程

js
import { HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages';

const messages = [
  new SystemMessage('你是一个代码助手,可以使用 read_file 工具读取文件'),
  new HumanMessage('请读取 src/index.js 文件并解释'),
];

let response = await modelWithTools.invoke(messages);
messages.push(response); // 把 AI 的回复也放入历史

// 循环处理 tool calls,直到没有新的工具调用为止
while (response.tool_calls && response.tool_calls.length > 0) {
  // 执行所有工具调用
  const toolResults = await Promise.all(
    response.tool_calls.map(async (toolCall) => {
      const tool = tools.find(t => t.name === toolCall.name);
      return await tool.invoke(toolCall.args);
    })
  );

  // 把工具结果封装成 ToolMessage,用 tool_call_id 关联
  response.tool_calls.forEach((toolCall, index) => {
    messages.push(
      new ToolMessage({
        content: toolResults[index],
        tool_call_id: toolCall.id,  // 告诉模型这是哪个工具调用的结果
      })
    );
  });

  // 再次调用模型,让它基于工具结果继续思考
  response = await modelWithTools.invoke(messages);
  messages.push(response);
}

console.log(response.content);

流程总结

用户提问 → 大模型思考 → 返回 tool_calls

执行工具,拿到结果

结果作为 ToolMessage 传回大模型

大模型基于结果继续回答(可能再次调用工具)

直到没有新的 tool_calls,输出最终回复

关键点

  • messages 是完整的对话历史,SystemMessage → HumanMessage → AIMessage → ToolMessage,顺序不能乱
  • tool_call_id 用来关联工具调用和结果,模型靠它匹配
  • temperature: 0 让模型严格按指令执行,不自由发挥
  • 没有 LangChain 也能实现 Tool Calling,本质就是通过 prompt 告诉模型有哪些工具、参数格式,让它按规范返回调用信息,你自己执行后再传回去。LangChain 只是做了封装。