别让你的大模型只会“吹牛”!带你实战 LangChain Tool Calling,让 AI 真正接管你的磁盘!

0 阅读5分钟

一、 序言:大模型是“缸中之脑”吗?

你是否也曾有过这种无力感:对着 GPT-4 或 Claude 3.5 聊得热火朝天,它能帮你写出完美的 React 组件,但当你问它:“帮我看看当前目录下那个 index.js 里的逻辑有没有 Bug”时,它只能无奈地回答你:“对不起,我无法直接访问您的本地文件。”

这一刻,你会发现,再聪明的大模型,如果没有工具(Tools) ,也只是一个被困在对话框里的“缸中之脑”。它懂天文地理,却没法为你开一下灯,也没法为你读一行代码。

Agent(智能体)的诞生,就是为了给这个大脑安上手脚。 今天,我就带大家拆解一段基于 LangChain 的核心代码,看看我们是如何通过 Tool Calling(工具调用) 机制,让 AI 跨越对话框,直接读取并分析本地文件的。


二、 什么是 Agent?(不仅仅是 Prompt)

在掘金,我们不谈玄学,只谈架构。一个成熟的 AI Agent 遵循以下公式:

Agent = LLM(大脑) + Planning(规划) + Tools(手脚) + Memory(记忆)

  • 大脑:负责理解你的意图。
  • 规划:把复杂任务拆解成第一步做什么,第二步做什么。
  • 工具:去执行那些大模型本身做不到的事(如:读写文件、发邮件、跑 Bash 命令、搜 Google)。
  • 记忆:记住之前的操作结果,避免反复横跳。

三、 实战拆解:手把手教你给 AI 装上“手”

我们要实现的功能很简单:让大模型通过调用 Node.js 的异步 I/O,读取本地的 .mjs 文件并进行代码审计。

1. 定义工具:给 AI 一份说明书

在 LangChain 中,定义工具不仅仅是写个函数,更关键的是写好 Description。因为大模型是靠你的描述来判断“什么时候该用这个工具”的。

JavaScript

const readFileTool = tool(
  async ({ path }) => {
    try {
      // 真正的磁盘操作在这里
      const content = await fs.readFile(path, 'utf-8');
      console.log(`[工具调用] read_file("${path}") 成功`);
      return content; 
    } catch (error) {
      return `读取文件失败: ${error.message}`;
    }
  },
  {
    name: "read_file",
    description: "当用户需要查看代码、分析本地文件内容时,必须调用此工具。",
    schema: z.object({
        path: z.string().describe("要读取的文件相对路径或绝对路径"),
    })
  }
);

技术要点: 我们使用了 zod 进行数据校验。这非常重要!AI 有时候会胡乱传参,zod 就像安检员,确保传进来的 path 一定是个字符串,否则直接拦截。

2. 绑定工具:赋予 AI 决策权

如果你只是写了函数,大模型并不知道它的存在。我们需要通过 model.bindTools([readFileTool]) 告诉大模型:“嘿,现在你多了个技能,需要读文件时尽管开口。”


四、 核心逻辑:Tool Calling 的“反复横跳”艺术

这是整篇文章最值钱的地方。很多人不理解:为什么代码里要写一个 while 循环?

真相是:大模型并不会直接运行你的代码。 它只是一个决策者。整个过程像是一场精彩的拉锯战:

  1. 用户提问:“解释一下 tool-file-read.mjs 这个文件。”
  2. LLM 决策:它发现自己没内容,于是返回一个 tool_calls 指令,意思是:“我请求执行 read_file 工具,参数是 path: 'tool-file-read.mjs'”。
  3. 本地执行:我们的代码检测到这个请求,在本地环境运行 fs.readFile,拿到了源码。
  4. 结果反馈:我们将源码封装成一个 ToolMessage注意! 必须带上 tool_call_id,否则大模型不知道这个结果对应哪个请求。
  5. LLM 二次分析:我们将包含“原始问题 + 它的请求 + 工具执行结果”的完整对话历史再次发给大模型。
  6. 最终输出:大模型看完了代码,终于给出了详细的解释。

代码逻辑如下:

JavaScript

while (response.tool_calls && response.tool_calls.length > 0) {
    // 1. 遍历 LLM 想调用的所有工具(可能同时调用多个)
    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); // 在你的服务器上跑逻辑
        })
    );

    // 2. 将结果包装成 ToolMessage,喂回给大模型
    response.tool_calls.forEach((toolCall, index) => {
        messages.push(new ToolMessage({
            content: toolResults[index],
            tool_call_id: toolCall.id, // 关键:建立关联
        }));
    });

    // 3. 再次询问,LLM 会根据拿到的 Tool 内容进行思考
    response = await modelWithTools.invoke(messages);
    messages.push(response);
}

五、 为什么这改变了编程的游戏规则?

你最近一定被 Cursor 刷屏了。为什么 Cursor 这么强?

它的核心就是这套逻辑。当你让 Cursor 修复一个 Bug 时,它会:

  1. 调用 list_dir 看看你有多少文件。
  2. 调用 read_file 读入相关的几个组件。
  3. 在脑子里模拟运行,找到 Bug。
  4. 调用 write_to_file 直接帮你把代码改了。

这就是 Agentic Workflow(智能体工作流) 。大模型不再仅仅是一个聊天机器人,它变成了一个能够通过工具与现实世界交互的数字员工


六、 总结与进阶思考

通过今天的学习,我们掌握了让 AI 动起来的核心:Tool Calling

但这也引出了更深层次的问题:

  • 安全性:如果 AI 调用的工具是 rm -rf / 怎么办?(提示:永远不要给 Agent 赋予不受限的写权限)。
  • 准确性:如何写出让 AI 永远不误判的工具描述?
  • 多工具协作:当 AI 需要同时调用搜索工具和绘图工具时,如何保证逻辑不乱?

Agent 的世界才刚刚开启,从简单的文件读取,到自动化的 DevOps 流水线,想象空间巨大。


如果你觉得这篇文章对你有启发,别忘了点赞、收藏加关注!

评论区互动: 你最想给你的 AI 助理加上什么“变态”功能?是自动帮你点外卖,还是自动帮你写日报?欢迎留言,点赞最高的我出一期实战教程。