第二章:AI Agent的“手脚”——Tool

第二章:AI Agent的“手脚”——Tool AI Agent的核心架构本质上模拟了人类处理问题的逻辑先通过感官获取信息再通过大脑分析决策最后通过手脚落地行动。对应到AI Agent中就是三大核心模块感知模块、决策模块、执行模块。这三个模块相互配合、无缝联动构成了AI Agent自主工作的完整闭环。本章将围绕执行模块讲起也就是它的核心Tool感知和决策先用自己脑子代替。执行模块是AI Agent的“行动载体”相当于人类的手脚负责将决策模块制定的方案落地执行完成具体任务同时将执行过程中的情况、最终结果反馈给决策模块形成闭环。执行模块的核心能力的是“工具调用”和“动作执行”这也是它与传统AI工具的核心区别之一——传统AI工具只能被动响应而AI Agent的执行模块可以主动调用各类工具完成复杂任务。比如调用邮件工具发送邮件、调用文档工具整理纪要等。传统的AI工具我们以豆包客户端为例就算我明确要求它写入本地也不行它只能告诉你执行什么命令创建那些文件代码是什么最后让你手动复制到本地运行。但是 Cursor、Trae 或者 Claude Code CLI 就可以一键到底demodemo执行模块的核心是Tool工作逻辑很简单分为“执行”和“反馈”两个环节。我们也像cursor一样开发一些 tool 给 agent 调用就可以了。比如读文件、写文件、执行命令。一、环境搭建PS进入开发前还需找个可用的大模型国产的就可以比如千问或智谱都是新用户免费送token能用很久。接下来进入正题# 创建目录 mkdir tool-test # 进入目录 cd tool-test # 初始化环境 npm init -y安装依赖# langchain 框架 pnpm i langchain/openai langchain/core # 工具类 pnpm i zod dotenvzod运行时数据校验 TS 类型生成dotenv加载 .env 环境变量的工具创建一个.env文件将你的大模型服务相关配置写入# OpenAI 配置 OPENAI_MODEL_NAME你的模型名称 OPENAI_API_KEY你的 API 密钥 OPENAI_BASE_URL你的 API 服务 URL接下来写一个hello-world程序在 src 目录下创建一个 hello-langchain.mjs二、开发一个读文件的 tool创建一个 tool-file-read.mjs// 从 .env 加载环境变量到 process.env import dotenv/config; import { ChatOpenAI } from langchain/openai; import { tool } from langchain/core/tools; import { SystemMessage, HumanMessage, AIMessage } from langchain/core/messages; // Zod为工具参数定义 schema 并生成描述 import { z } from zod; // Node 内置文件系统模块同步读写 import fs from node:fs; // 实例化聊天模型从环境变量读取端点与密钥 const model new ChatOpenAI({ model: process.env.OPENAI_MODEL_NAME, // 模型名称如 gpt-4o-mini apiKey: process.env.OPENAI_API_KEY, // API 密钥 temperature: 0, // 温度为 0输出更确定 configuration: { baseURL: process.env.OPENAI_BASE_URL, // 自定义 API 基地址兼容代理或第三方 } }); // 定义「读文件」工具异步函数体 元数据名称、描述、参数 schema const readFileTool tool(async (filePath) { const content fs.readFileSync(filePath, utf8); return ${filePath} 的内容是${content}; }, { name: read_file, // 工具在 API 中的标识名 description: 用来读取文件内容的工具。当用户要求读取文件、查看文件内容时可以使用这个工具需要输入一个文件路径。, schema: z.object({ filePath: z.string().describe(要读取的文件路径), }), }); // 工具列表可扩展多个 tool const tools [readFileTool]; // 将工具绑定到模型invoke 时模型可返回 tool_calls const modelWithTools model.bindTools(tools); // 对话消息数组先系统提示再用户任务 const messages [ new SystemMessage( 你是一个助手用来读取文件内容。当用户要求读取文件、查看文件内容时可以使用这个工具需要输入一个文件路径。 工作流程 1. 根据用户的需求选择合适的工具 2. 使用工具读取文件内容等待返回文件内容 3. 基于文件内容进行分析和解释 可用工具 - read_file读取文件内容的工具 ), new HumanMessage(读取文件路径为 src/tool-file-read.mjs 的文件内容并解释代码), ] // 发起一次带工具的推理可能只返回要调用的工具不执行工具本身 const response await modelWithTools.invoke(messages); // 打印模型返回含 content、tool_calls 等 console.log(response);整个代码的执行大致流程就是创建模型实例创建 tool把 tool 绑定到模型实例上设定系统提示词SystemMessage输入用户提示词HumanMessage执行并返回AIMessage具体的消息有四种SystemMessage、HumanMessage、AIMessage、ToolMessageSystemMessage设置 AI 是谁可以干什么有什么能力以及一些回答、行为的规范等HumanMessage用户输入的信息AIMessageAI 的回复信息ToolMessage调用工具的结果返回运行结果接下来我们将基于 tool_calls 这个参数进行工具调用。let response await modelWithTools.invoke(messages); // 打印模型返回含 content、tool_calls 等 // console.log(response); // 将模型返回添加到消息列表中 messages.push(response); while (response.tool_calls?.length 0) { console.log(\n[检测到 ${response.tool_calls.length} 个工具调用]); // 按工具调用顺序依次执行 const toolResults await Promise.all(response.tool_calls.map(async (toolCall) { const toolName toolCall.name; const toolArgs toolCall.args; const tool await tools.find((t) t.name toolName); if (!tool) { throw new Error(未找到工具: ${toolName}); } console.log(\n开始执行工具调用: ${toolName}(${JSON.stringify(toolArgs)})); try { const toolResult await tool.invoke(toolArgs); // 返回 ToolMessage 对象包含 content 和 tool_call_id // 使用 id 关联执行结果 return new ToolMessage({ content: toolResult, tool_call_id: toolCall.id, }); } catch (error) { console.error(工具调用失败: ${toolName}, 错误信息: ${error.message}); return new ToolMessage(工具调用失败: ${toolName}错误信息: ${error.message}, toolCall.id); } })); // 更新消息历史添加工具调用结果 messages.push(...toolResults); // 再次调用模型传入工具结果 console.log(再次调用模型共: ${messages.length} 条消息); response await modelWithTools.invoke(messages); messages.push(response); } console.log(\n[最终响应]); console.log(response.content);执行大致流程就是1. 将模型返回消息AIMessage添加到对话列表中2. 根据 tool_calls 的数组分别从 tools 数组里找到对应的工具3. 取出来 invoke传入大模型解析出的参数4. 最后把工具调用结果作为 ToolMessage 传给大模型让它继续回答跑起来从图中可以看出模型成功检测并调用了 read_file 这个工具读取文件让大模型分析并给出了代码解释。到此就给大模型扩展了“读”的能力。最后我们加上 git执行git init添加一个.gitignore把 node_modules 和敏感文件.env排除掉。