从零手写 AI Agent:LLM + Tool + LangChain,打造能干活的大模型

从零手写 AI Agent:LLM + Tool + LangChain,打造能干活的大模型

为什么 ChatGPT 只能聊天,而 Claude Code 能写代码、改文件、执行命令?答案就是 Agent 架构。本文从 Promise 异步基础出发,用 LangChain 框架手把手构建一个能读取文件、分析代码的 AI Agent,深入理解 Agent = LLM + Memory + Tool + RAG + MCP + Skills 的完整公式。


前言

ChatGPT 很强大,但当你让它"帮我创建一个 React + Vite 的 TodoList 项目"时,它只能给你步骤说明------它不能真的帮你创建文件、运行命令

而 Claude Code、Cursor Agent、Manus 这些产品,本质上都是在 LLM 外面套了一层Agent 架构:给它装上 Memory(记忆)、Tool(工具)、RAG(知识检索)、MCP(协议连接)、Skills(技能蒸馏),让 LLM 从"只会说话"变成"能干活"。

本文将带你:

  1. 理解 Agent 的本质与组成公式
  2. 掌握 Promise 三种状态与 Promise.all 并行执行
  3. 入门 LangChain 框架的 LLM 兼容与 Tool 注册
  4. 手写一个完整的 read_file Agent

一、Agent 的本质:LLM 的"超进化"

1.1 LLM 的五大局限

局限 说明 Agent 解决方案
无状态 每次对话结束,什么都不记得 Memory 记忆模块
无法访问网页 只能告诉你思路,不能自己做 Tool Use 工具调用
无法访问私有文档 预训练数据中没有你的内部资料 RAG 检索增强生成
不知道最新消息 世界杯新闻、股价实时数据 MCP 第三方工具协议
无法执行复杂任务 做 PPT、分析股市并自动买卖 Skills 技能蒸馏

1.2 Agent 的组成公式

ini 复制代码
Agent = LLM + Memory + Tool + RAG + MCP + Skills
组件 作用 类比
LLM 大脑:思考、规划、推理 人的大脑
Memory 记忆:记住对话历史、用户偏好 人的记忆
Tool 双手:读写文件、调用 API、执行命令 人的双手
RAG 知识库:查询内部文档、私有数据 人的专业知识
MCP 连接器:标准化接入第三方服务 人的社交关系
Skills 技能:做 PPT、数据分析、写代码 人的职业技能

1.3 Agent 的工作流程

markdown 复制代码
用户提出复杂任务
    ↓
LLM Planning / Reasoning(规划/推理)
    ↓
要不要加载 Memory?(获取历史上下文)
    ↓
要不要调用 Tool?(分步骤调用多个工具)
    ↓
要不要查询 RAG?(检索内部知识库)
    ↓
生成 Response → 返回给用户
    ↓
任务完成

一个具体例子:"创建一个 React + Vite 的 TodoList"

arduino 复制代码
步骤1:调用文件写入 Tool → 创建项目结构
步骤2:调用 LLM 编程 Tool → 生成组件代码
步骤3:调用 CLI 命令 Tool → vite create + npm install
步骤4:调用运行命令 Tool → npm run dev

二、Promise 基础:Agent 异步执行的基石

Agent 的核心是异步:调用工具、查询数据库、请求 API------这些操作都不能阻塞主线程。Promise 是理解 Agent 代码的前提。

2.1 Promise 的三种状态

javascript 复制代码
const p = new Promise((resolve, reject) => {
    // pending(等待中)...
    
    if (成功) {
        resolve(结果);   // pending → fulfilled(成功)
    } else {
        reject(错误);    // pending → rejected(失败)
    }
});

// 状态只能变一次,且不可逆
// pending → fulfilled  或  pending → rejected
scss 复制代码
┌─────────┐    resolve()    ┌───────────┐
│ pending │ ──────────────→ │ fulfilled │
└─────────┘    reject()     └───────────┘
         ──────────────→  ┌───────────┐
                          │ rejected  │
                          └───────────┘

2.2 模拟异步操作

javascript 复制代码
function getWeather() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ temp: 38, conditions: 'Sunny with Clouds' });
        }, 2000);
    });
}

function getTweets() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(['I like cake', 'BBQ is good too!']);
        }, 500);
    });
}

2.3 串行 vs 并行:await 的陷阱

串行执行(慢)

javascript 复制代码
const main = async () => {
    console.time('serial');
    const weatherData = await getWeather();  // 等 2s
    const tweetsData = await getTweets();    // 等 0.5s
    console.log(weatherData, tweetsData);
    console.timeEnd('serial'); // 约 2500ms
};

问题getWeathergetTweets 之间没有依赖关系,但串行执行白白浪费了时间。

并行执行(快)

javascript 复制代码
const main = async () => {
    console.time('parallel');
    // 两个 Promise 同时启动,等待全部完成
    const [weatherData, tweetsData] = await Promise.all([
        getWeather(),   // 2s
        getTweets()     // 0.5s
    ]);
    console.log(weatherData, tweetsData);
    console.timeEnd('parallel'); // 约 2000ms(取最慢的)
};

Promise.all 的核心价值

场景 执行方式 时间
串行 await 一个接一个 总和(2s + 0.5s = 2.5s)
Promise.all 同时启动 最大值(max(2s, 0.5s) = 2s)

Agent 中经常需要同时调用多个工具(如同时查询天气和股价),Promise.all 是性能优化的关键。

2.4 Promise.all 的注意事项

javascript 复制代码
// 结果顺序与 Promise 数组顺序一致
const [a, b, c] = await Promise.all([promiseA, promiseB, promiseC]);

// 如果一个失败,全部失败
await Promise.all([
    Promise.resolve(1),
    Promise.reject('error'),  // 这里 reject
    Promise.resolve(3)
]);
// 整体抛出错误,不会返回任何结果

三、LangChain:Agent 开发的瑞士军刀

3.1 为什么需要 LangChain?

痛点 LangChain 解决
LLM 厂商众多(OpenAI、DeepSeek、Claude) 统一兼容层:一套代码切换不同模型
Tool 定义格式不统一 标准化 Tool 注册:函数 + Schema + 描述
对话历史管理复杂 Message 抽象:System / Human / AI / Tool Message
多步骤任务编排困难 Chain / Graph:可视化任务流

LangChain 比 OpenAI SDK 更早诞生,它的设计哲学是**"模型无关"**------你今天用 DeepSeek,明天可以无缝切换到 Claude,代码几乎不用改。

3.2 LangChain 的核心模块

bash 复制代码
LangChain 架构
├── @langchain/core
│   ├── messages      ← SystemMessage, HumanMessage, AIMessage, ToolMessage
│   ├── tools         ← tool() 函数注册工具
│   └── prompts       ← PromptTemplate
├── @langchain/openai ← ChatOpenAI(兼容 OpenAI 格式)
├── @langchain/anthropic
├── @langchain/deepseek
└── langgraph         ← 多智能体编排

四、实战:手写一个 read_file Agent

4.1 项目依赖

json 复制代码
{
  "type": "module",
  "dependencies": {
    "@langchain/openai": "latest",
    "@langchain/core": "latest",
    "zod": "latest",
    "dotenv": "latest"
  }
}

4.2 完整代码

tool.mjs

javascript 复制代码
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import {
    HumanMessage,    // user 角色
    SystemMessage,   // system 角色
    ToolMessage,     // tool 返回结果
    AIMessage        // AI 回复
} from '@langchain/core/messages';
import fs from 'node:fs/promises';
import { z } from 'zod';

// 1. 创建 LLM 实例(兼容 DeepSeek API)
const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,  // 确定性输出,适合工具调用
    configuration: {
        baseURL: 'https://api.deepseek.com/v1',
    }
});

// 2. 定义 read_file 工具
const readFileTool = tool(
    // 第一部分:异步处理函数
    async ({ filePath }) => {
        const content = await fs.readFile(filePath, 'utf-8');
        // 时刻反馈 Agent 执行消息
        console.log(`[工具调用] read_file(${filePath}) 成功读取 ${content.length} 字节`);
        return content;
    },
    // 第二部分:描述对象(LLM 靠这个来决定是否调用)
    {
        name: 'read_file',
        description: `用此工具来读取文件内容,当用户要求读取文件、查看代码、分析文件内容时,调用此工具。输入文件路径(可以是相对路径或绝对路径)`,
        schema: z.object({  // 注意:schema 是小写 s
            filePath: z.string().describe('要读取的文件路径')
        })
    }
);

// 3. 工具列表
const tools = [readFileTool];

// 4. 将工具绑定到模型
const modelWithTools = model.bindTools(tools);

// 5. 构建消息上下文
const messages = [
    new SystemMessage(`
        你是一个代码助手,可以使用工具读取文件并解释代码。

        工作流程:
        1. 用户要求读取文件时,立即调用 read_file 工具
        2. 等待工具返回文件内容
        3. 基于文件内容进行分析和解释

        可用工具:
        - read_file:读取文件内容(使用此工具来获取文件内容)
    `),
    new HumanMessage('请读取 tool.mjs 文件内容并解释代码')
];

// 6. 调用模型(LLM 会分析并决定调用工具)
let response = await modelWithTools.invoke(messages);
messages.push(response);

// response 中可能包含 tool_calls → 需要执行工具 → 将结果再次传入模型

4.3 代码逐层解析

第一步:LLM 实例化

javascript 复制代码
const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,
    configuration: { baseURL: 'https://api.deepseek.com/v1' }
});

ChatOpenAI 是 LangChain 的兼容层 。虽然名字叫 OpenAI,但通过配置 baseURL 可以接入任何兼容 OpenAI 格式的 API(DeepSeek、通义千问、本地模型等)。

temperature: 0 表示确定性输出------每次相同输入得到相同输出,这对工具调用场景非常重要。

第二步:Tool 的定义(双结构)

LangChain 的 tool() 函数要求传入两个参数:

参数 类型 作用
参数1 异步函数 执行逻辑:接收校验后的参数,执行操作,返回结果
参数2 描述对象 元信息:name、description、schema,供 LLM 决策使用
javascript 复制代码
tool(
    async ({ filePath }) => { ... },  // ← 执行逻辑
    {                                 // ← 元信息
        name: 'read_file',
        description: '...',
        schema: z.object({ filePath: z.string() })
    }
);

为什么需要 description?

LLM 不是靠函数名来决策的------它根本看不到你的代码。它靠的是 description 中的文字描述来判断"当前这个任务,应不应该调用这个工具"。

第三步:Schema 校验

javascript 复制代码
schema: z.object({
    filePath: z.string().describe('要读取的文件路径')
})

Zod Schema 有两个作用:

  1. 运行时校验:确保 LLM 传来的参数类型正确
  2. 自动生成 JSON Schema:LangChain 内部将 Zod Schema 转换为 JSON Schema 发给 LLM,LLM 据此生成正确的参数

⚠️ 重要提醒schema 的键名是小写 s 。如果写成 Schema(大写),LangChain 可能无法正确识别,导致参数校验失败。

第四步:绑定工具

javascript 复制代码
const modelWithTools = model.bindTools(tools);

bindTools 将工具列表注册到模型上。之后调用 modelWithTools.invoke() 时,LLM 会知道"我有这些工具可用"。

第五步:Message 类型

javascript 复制代码
const messages = [
    new SystemMessage('系统角色设定...'),   // 设定 Agent 的身份和能力
    new HumanMessage('用户的提问...')        // 用户的实际需求
];

LangChain 定义了四种核心消息类型:

消息类型 角色 用途
SystemMessage system 设定 Agent 的身份、能力、工作流程
HumanMessage user 用户的输入/提问
AIMessage assistant AI 的回复(可能包含 tool_calls)
ToolMessage tool 工具执行后的返回结果

第六步:工具调用的完整循环

javascript 复制代码
// 第一轮:LLM 分析 → 决定调用 read_file
let response = await modelWithTools.invoke(messages);
// response 包含:content(文字)+ tool_calls(工具调用请求)

messages.push(response); // 将 AI 的回复加入历史

// 如果 response 包含 tool_calls:
//   1. 提取 tool_calls[0].function.name = "read_file"
//   2. 提取 arguments = { "filePath": "tool.mjs" }
//   3. 执行 readFileTool({ filePath: "tool.mjs" })
//   4. 将结果包装为 ToolMessage,加入 messages
//   5. 再次调用 modelWithTools.invoke(messages)
//   6. LLM 基于文件内容生成最终回答

五、用户体验优化:给 Agent 加上"进度条"

Agent 执行任务可能很耗时(读取大文件、调用多个工具),用户如果太久没收到反馈,可能会认为程序卡死了。

javascript 复制代码
const readFileTool = tool(
    async ({ filePath }) => {
        const content = await fs.readFile(filePath, 'utf-8');
        // ✅ 时刻反馈 Agent 执行消息
        console.log(`[工具调用] read_file(${filePath}) 成功读取 ${content.length} 字节`);
        return content;
    },
    { name: 'read_file', description: '...', schema: z.object({...}) }
);

在工具函数中添加 console.log,让用户看到 Agent 正在执行什么操作------这是 Agent 产品化的关键细节。


六、从单工具到多工具:扩展 Agent 能力

当前 Agent 只有一个 read_file 工具。要让它更像 Claude Code,可以扩展更多工具:

javascript 复制代码
// 写文件工具
const writeFileTool = tool(
    async ({ filePath, content }) => {
        await fs.writeFile(filePath, content, 'utf-8');
        console.log(`[工具调用] write_file(${filePath}) 写入成功`);
        return `文件 ${filePath} 写入成功`;
    },
    {
        name: 'write_file',
        description: '写入文件内容',
        schema: z.object({
            filePath: z.string().describe('文件路径'),
            content: z.string().describe('文件内容')
        })
    }
);

// 执行命令工具
const execCommandTool = tool(
    async ({ command }) => {
        const { execSync } = await import('child_process');
        const result = execSync(command, { encoding: 'utf-8' });
        console.log(`[工具调用] exec(${command}) 执行成功`);
        return result;
    },
    {
        name: 'exec_command',
        description: '执行 CLI 命令(如 npm install、git commit)',
        schema: z.object({
            command: z.string().describe('要执行的命令')
        })
    }
);

const tools = [readFileTool, writeFileTool, execCommandTool];
const modelWithTools = model.bindTools(tools);

有了这三个工具,Agent 就能完成"创建项目 → 写代码 → 运行项目"的完整工作流。


知识树

javascript 复制代码
从零手写 AI Agent
├── Agent 本质
│   ├── LLM 五大局限(无状态/无手/无知识/无时效/无技能)
│   ├── Agent 公式:LLM + Memory + Tool + RAG + MCP + Skills
│   └── 工作流程:Planning → Memory → Tool → RAG → Response
├── Promise 基础
│   ├── 三种状态:pending → fulfilled / rejected
│   ├── await:异步变同步
│   └── Promise.all:并行执行(取最慢的时间)
├── LangChain 框架
│   ├── ChatOpenAI:统一兼容层(DeepSeek/Claude/OpenAI)
│   ├── tool():双结构(执行函数 + 描述对象)
│   ├── bindTools():将工具注册到模型
│   └── Message 类型:System / Human / AI / Tool
├── 实战:read_file Agent
│   ├── LLM 实例化(temperature: 0)
│   ├── Tool 定义(name + description + schema)
│   ├── SystemMessage 设定工作流程
│   └── 工具调用循环(invoke → tool_calls → ToolMessage → 再 invoke)
├── 扩展方向
│   ├── write_file(写文件)
│   ├── exec_command(执行 CLI)
│   └── Promise.all 并行调用多个工具
└── 审查纠正
    ├── 代码截断补全
    ├── schema 键名大小写
    └── 并发工具优化

结语

Agent 的本质,是给 LLM 装上"记忆"和"双手",让它从"会说话"变成"能干活"。

LangChain 的价值在于抽象和统一 :不管底层是 DeepSeek、Claude 还是 GPT,你都用同样的 ChatOpenAI 接口;不管工具是读文件、查数据库还是发邮件,你都用同样的 tool() 函数注册。

理解了这个模式,你就具备了构建真正 AI 产品的能力:

  1. 分析 LLM 的局限 → 确定需要哪些组件
  2. 用 LangChain 注册工具 → 给 LLM 装上"手"
  3. 用 Memory 管理对话 → 给 LLM 装上"记忆"
  4. 用 RAG 接入知识库 → 给 LLM 装上"专业知识"
  5. 用 MCP 连接外部服务 → 给 LLM 装上"社交关系"

Agent 开发的核心不是写更复杂的代码,而是设计更好的系统。LLM 已经会思考了,你的工作是让它的思考能够落地。


参考与拓展阅读:

  • LangChain 官方文档(langchain.com
  • Zod 官方文档(zod.dev)
  • 《MCP 协议深度解析》------ Agent 工具层的标准化协议
  • 《LLM Tool Calling 实战》------ 底层 Function Calling 原理
  • 《Harness Engineering 深度解析》------ Agent 工程化的系统架构
  • 《Promise 与异步编程》------ JS 异步机制完全指南

如果本文帮你理解了 Agent 的构建逻辑和 LangChain 的实战用法,欢迎点赞 + 收藏。动手写一个你自己的 Agent,欢迎在评论区分享你的创意 👇

#AIAgent #LangChain #Promise #ToolCalling #Node.js #掘金技术社区

相关推荐
用户2930750976691 小时前
拆解 AI Agent:用 LangChain 构建你的第一个智能工具
agent
_冷眸_1 小时前
用 OpenAI SDK 统一调用 Claude、Gemini、DeepSeek:聊聊 AI API 聚合网关的实践
人工智能·ai·llm·agent
梦想4811 小时前
三次迭代,把一个本地检索引擎的排序准确率从 77.8% 做到 100%
agent
文言一心2 小时前
业务数据对话查询智能体系统
langchain·agent
武子康2 小时前
调查研究-217 Fortress:当浏览器 Agent 开始被网站识别,开源 Chromium 反拦截引擎来了
人工智能·爬虫·agent
新知图书2 小时前
多模态智能体开发的核心挑战与解决方案
人工智能·agent·多模态·ai agent·智能体·langgraph
gis分享者2 小时前
easy-agent 轻量级 Agent 组件深度评测
agent·测评·easy-agent
he___H2 小时前
基于LCEL的联想
开发语言·python·langchain
阿拉斯攀登3 小时前
RAG 性能优化:缓存、批量与并发
人工智能·缓存·性能优化·embedding·agent·loop·rag