大模型只会聊天?手把手教你用 LangChain 打造一个能读文件、写代码的 AI Agent

0 阅读8分钟

从零理解 Agent 架构,到用 Node.js + LangChain 写出你的第一个智能体


前言

最近大家都在聊 Agent,Claude Code 能帮你写代码,Manus 能帮你自动完成复杂任务。你可能会想:这不就是调大模型接口吗,有什么神奇的?

说实话,我一开始也这么想。直到上周的课上跟着老师走了一遍,才发现——纯大模型和 Agent,根本是两个物种。

这篇文章是我整理的课堂笔记和代码实践,从 Agent 的核心概念讲起,到用 LangChain 亲手写一个能读文件、能调用工具的简易 Agent。希望能帮你少走弯路。


一、纯大模型到底差在哪?

你可能会问:GPT、DeepSeek 这么强了,为什么还要 Agent?

我们来看看纯 LLM 在实际场景中有哪些硬伤:

场景纯 LLM 的表现你真正需要的
"帮我记住上周聊过的需求"😅 我是无状态的,记不住有 Memory,跨会话持久记忆
"帮我把这个网页的内容爬下来"🤔 你应该用 requests + BeautifulSoup...直接调用工具,自己动手
"公司内部的接口文档怎么用?"🫤 我不知道你们内部的东西RAG 检索内部知识库
"昨晚世界杯谁赢了?"😬 我的训练数据截止到...接入搜索工具,获取实时信息
"帮我做一份 Q2 复盘 PPT"📝 我给你大纲,你手动做Skills 编排多步骤自动完成

发现了吗?大模型就像一个知识渊博但困在聊天框里的顾问——它能告诉你思路,但不能替你干活。

Agent 要做的,就是打破这个聊天框


二、Agent 到底是什么?

一句话公式:

Agent = LLM + Memory + Tool + RAG + MCP + Skills

每个模块解决一个具体问题:

  • Memory(记忆):LLM 是无状态的,每次对话都是"初次见面"。Memory 用数据库/Redis/前端存储把历史对话持久化,让 Agent 能记住你说过的话。
  • Tool(工具):让 Agent 能读写文件、发 HTTP 请求、操作数据库,从"动嘴"变成"动手"。
  • RAG(检索增强生成):把企业内部文档、PDF、代码库喂给 Agent,让它具备私有领域知识。
  • MCP(模型上下文协议):标准化的第三方工具接入协议,像给 Agent 装"App Store"。
  • Skills(技能):把多个工具调用编排成可复用的技能,比如"做 PPT"就是一个组合技能。

Agent 的工作流程

用户提出复杂任务(Prompt)
  → Agent 接收
    → LLM 规划与推理(Planning/Reasoning)
      → 需要查记忆吗?(Memory)
      → 需要动手操作吗?(Tool,可能分多步、多工具)
      → 需要查内部知识吗?(RAG → 拼入 Prompt)
    → 整合结果 → 返回用户

关键点在于:LLM 不只是生成答案,它在做规划、决策、调度。 这就是 Agent 和"调接口"的本质区别。


三、LangChain:Agent 开发的脚手架

LangChain 是目前最主流的 Agent 开发框架之一,Python 和 JS 生态都有。它的核心价值就两个:

  1. 统一 LLM 接口:不管底层是 OpenAI、DeepSeek 还是其他模型,一套代码全兼容。
  2. 标准化 Tool 机制:定义工具 → 注册到模型 → LLM 自动决策调用,全流程封装。

技术栈推荐:

Node.js (NestJS) + LangChain(单智能体) + LangGraph(多智能体编排)

3.1 三步接入大模型

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

const model = new ChatOpenAI({
    modelName: process.env.DEEPSEEK_MODEL,
    apiKey: process.env.DEEPSEEK_API_KEY,
    configuration: {
        baseURL: process.env.DEEPSEEK_BASE_URL, // 兼容任意 OpenAI 格式接口
    }
});

const response = await model.invoke('棍王杯台球比赛应该设置什么奖励?');
console.log(response);

@langchain/openai 不止支持 OpenAI,只要接口兼容 OpenAI 格式,换个 baseURL 就能切模型。这意味着你的代码和模型提供商解耦了——今天用 DeepSeek,明天换别的,一行配置的事。

3.2 Tool:让 LLM 长出手来

这是 Agent 最核心的能力。Tool 由两部分组成:

① 执行函数(真正干活的代码)

const readFileTool = tool(
    async ({ filePath }) => {
        const content = await fs.readFile(filePath, 'utf-8');
        console.log(`[工具调用] read_file(${filePath}) 成功读取 ${content.length} 字节`);
        return content;
    },
    // ...描述对象见下方
);

② 描述对象(给 LLM 看的"使用说明书")

{
    name: 'read_file',
    description: `用此工具来读取文件内容。当用户要求读取文件、
        查看代码、分析文件内容时,调用此工具。`,
    schema: z.object({
        filePath: z.string().describe('要读取的文件路径')
    })
}

注意这里的设计:description 不是写给人看的注释,是写给 LLM 看的 Prompt。LLM 根据这段描述来判断"现在该不该用这个工具"。描述越清晰,LLM 的工具选择越准确。

schemaZod 做参数校验——LLM 如果要调用此工具,必须按照 schema 定义的格式提供参数,否则直接报错。

3.3 注册工具并发起调用

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

const modelWithTools = model.bindTools(tools);

const messages = [
    new SystemMessage(`
        你是一个代码助手,可以使用工具读取文件并解释代码。
        工作流程:
        1. 用户要求读取文件时,立即调用 read_file 工具
        2. 等待工具返回文件内容
        3. 基于文件内容进行分析和解释
    `),
    new HumanMessage('请读取 tool.mjs 文件内容并解释代码'),
];

let response = await modelWithTools.invoke(messages);
messages.push(response); // 把 AI 回复追加到对话历史

四种消息类型构成了完整的对话上下文:

类型角色何时使用
SystemMessagesystem设定 Agent 人设和行为规范
HumanMessageuser用户每次的输入
AIMessageassistantLLM 生成回复或返回 tool_calls
ToolMessagetool工具函数执行后的返回值

3.4 Tool 调用的内部机制

当 LLM 判断"这事我得用工具"时,它会停止生成文字,转而返回 tool_calls

[
  {
    "id": "call_abc123",
    "name": "read_file",
    "arguments": { "filePath": "tool.mjs" }
  }
]

然后你的代码执行工具,拿到结果后带着 ToolMessage 再次请求 LLM,LLM 才能基于工具结果继续生成最终回复。注意这里:一次完整的 Agent 调用,背后可能是多轮 LLM 请求。每轮都可能产生新的 tool_calls,循环直到 LLM 觉得任务完成。


四、性能优化:别让工具排队浪费时间

真实场景中,Agent 可能一次调用 3-5 个工具。如果串行执行(一个等一个),3 个 2 秒的工具总耗时就是 6 秒——用户早就关页面了。

Promise 三种状态(基础但必须懂)

Pending(等待中)
  → Fulfilled(成功)
  → Rejected(失败)

状态不可逆:一旦从 Pending 变为 Fulfilled/Rejected,就定型了。

串行 vs 并行,差距有多大?

// ❌ 串行(2500ms)
const weather = await getWeather();  // 等 2000ms
const tweets = await getTweets();    // 再等 500ms
// 总计 2500ms

// ✅ 并行(2000ms)
const [weather, tweets] = await Promise.all([
    getWeather(),   // ┐
    getTweets()     // ┤ 同时起飞
]);                 // ┘ 等最慢的那个
// 总计 2000ms

Promise.all 的核心规则

  • 并行启动所有 Promise
  • 等全部完成才返回
  • 结果顺序 = 入参数组顺序
  • 适用于互不依赖的独立操作

在 Agent 场景中,如果你需要同时读 3 个文件、调 2 个 API,用 Promise.all 总耗时 = 最慢那个,而不是全部加起来。这对用户体验是质的提升。


五、实战思维:手写一个简易 Claude Code

课堂上的核心思路:Claude Code 的本质 = LLM + 文件工具 + 命令行工具。

以"创建一个 React+Vite 的 TodoList"为例,Agent 的 Planning 会拆成三步:

步骤操作使用的 Tool
1创建项目骨架CLI Tool(npm create vite
2写出 TodoList 代码文件写入 Tool(生成 .jsx/.css)
3跑起来看看效果CLI Tool(npm run dev

这三步的核心依赖就两个工具:文件系统 Tool + CLI Tool。加上 LLM 的编程能力和 Planning 能力,就是一个小型的 Coding Agent。

理解了这一点,你就知道 Agent 开发并不是什么黑魔法——本质上是把 LLM 的推理能力和工程系统的执行能力连接起来


六、你的第一个 Agent 项目需要哪些依赖?

{
  "dependencies": {
    "@langchain/core": "^1.2.1",
    "@langchain/openai": "^1.5.3",
    "dotenv": "^17.4.2",
    "zod": "^4.4.3"
  }
}

四个包,各司其职:

  • @langchain/core:工具定义、消息类型、链式调用等核心抽象
  • @langchain/openai:统一的 LLM 调用入口(兼容所有 OpenAI 格式接口)
  • dotenv:API Key 放 .env,代码和配置分离
  • zod:Tool 入参校验,LLM 参数不规范直接拒掉

七、学习路线图

如果你也想入门 Agent 开发,建议按这个顺序来:

  1. 理解 Agent 概念:搞懂 LLM 的局限性,以及 Memory/Tool/RAG/MCP/Skills 各自解决什么问题
  2. 掌握 LangChain 基础:LLM 调用 → Tool 定义 → 消息类型 → bindTools 注册
  3. 吃透异步编程:Promise → async/await → Promise.all 并行优化(这决定了你 Agent 的性能)
  4. 动手写一个简易 Agent:先做一个只有 1-2 个 Tool 的小 Agent,跑起来
  5. 逐步加模块:加 Memory 让它记住对话 → 加 RAG 让它会查文档 → 加更多 Tool
  6. 进阶:LangGraph 多智能体编排、MCP 协议接入第三方工具

写在最后

Agent 没那么神秘。回顾一下公式:

Agent = LLM + Memory + Tool + RAG + MCP + Skills

它就是一个会思考、有记忆、能动手、懂内部知识、可扩展技能的大模型。每一项能力都是一个独立的模块,你不需要一次全加上——从最简单的 "LLM + 一个 Tool" 开始,逐步迭代。

那些让你觉得惊艳的 Agent 产品(Claude Code、Manus),底层逻辑逃不出这个框架。关键在于工程落地的细节:Tool 的粒度怎么切、Memory 的策略怎么选、Planning 失败怎么兜底……

希望这篇文章能帮你迈出第一步。共勉 💪