从零打造 Agent 智能体:LLM + Tool + Memory,让大模型真正帮你干活

开篇:一个经常被忽略的真相

你可能已经用过大模型聊天,也感受过它在文案、翻译、代码解释上的能力。但如果把问题换成下面这些,它通常就不太行了:

  • 它还记得你们上周聊过什么吗?
  • 它能主动打开网页抓取内容吗?
  • 它能访问你公司内部的私有文档吗?
  • 它知道今天刚发生的新闻吗?
  • 它能自动分析数据、生成 PPT,甚至帮你下单买卖吗?

大多数时候,答案都是否定的。

原因并不复杂:大模型本质上只是一个无状态的文本生成器。每次你发来一条消息,它都会基于当前输入重新生成回答,而不是像人一样持续记住上下文、调用工具、查阅资料、完成动作。

这也是为什么,单独一个 LLM 很聪明,却很难真正"干活"。

这篇文章想讲清楚一件事:怎么给 LLM 装上记忆、工具、知识和执行能力,把它升级成一个真正能工作的 Agent 智能体。

为什么不能直接调大模型接口

先看几个最常见的问题:

问题 根本原因
不记得上周聊过什么 LLM 是无状态的,每轮对话都像"重新开始"
只能给建议,不能执行 它本质是文本生成器,不能直接操作网页、文件、系统
不知道公司内部文档 训练数据里没有你的私有知识
不知道最新新闻 模型知识存在时间截止
不能做 PPT、炒股、下单 缺少工具系统和执行能力

一句话总结:裸 LLM 擅长生成答案,但不擅长完成任务。

而 Agent 的出现,就是为了解决这个问题。

Agent 到底是什么

如果把 LLM 比作大脑,那一个完整的 Agent 至少还需要补上几类能力:

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

每一部分都在补 LLM 的一个硬伤:

  • LLM:负责理解任务、规划步骤、做推理决策。
  • Memory:负责保存历史对话、长期偏好和任务上下文。
  • Tool:负责把"想法"变成"动作",比如读文件、调 API、执行命令。
  • RAG:负责补充模型训练数据之外的知识,尤其是你的私有文档和业务资料。
  • MCP:负责标准化接入第三方服务,比如地图、网盘、数据库、企业系统。
  • Skill:负责沉淀某一类可复用能力,比如做 PPT、分析股票、代码审查等。

所以,Agent 并不是一个更大的模型,而是一个带外部能力系统的 LLM 运行框架

Agent 和 Chatbot 的本质区别

很多人第一次接触 Agent 时,会把它理解成"更高级的聊天机器人"。但两者其实不是一回事。

Chatbot 的典型模式是:

用户提问 → 模型直接回答

而 Agent 的典型模式是:

用户提任务 → 模型规划 → 判断是否调用工具 / 检索知识 / 读取记忆 → 执行动作 → 观察结果 → 再推理 → 输出最终结果

也就是说,Agent 不是一次性作答,而是在一个循环里持续完成任务。

Agent 的工作流程

把这个流程展开,大致是下面这样:

这个流程里有几个特别关键的判断点:

  • 当前任务要不要加载历史记忆?
  • 当前问题要不要查知识库?
  • 当前目标能不能靠工具直接执行?
  • 如果工具执行失败,要不要换一种方案?

这就是 Agent 真正有价值的地方:它不是在"回答问题",而是在"完成任务"。

一套实用的技术栈

如果你想从零开始搭一个 Agent,下面这套组合非常适合入门和工程化落地:

nestjs 后端框架,适合企业级项目和模块化架构 langchain 单 Agent 开发框架,统一模型与工具抽象 langgraph 多 Agent 协作和流程编排框架 MCP 标准化工具协议,用来接第三方服务

这里顺手说一个很容易被忽略的点:LangChain 的价值,不只是"封装大模型调用",而是它提供了一套统一抽象。

同样一段业务代码,你可以:

  • 用 OpenAI 模型跑
  • 换成 DeepSeek 跑
  • 换成阿里云兼容接口继续跑

很多时候,真的只需要换 baseURL 和 API Key。

这也是为什么在 Agent 场景里,LangChain 依然很有价值。

实战:搭建第一个 Tool Agent

接下来进入最关键的部分:让 LLM 真正学会调用工具。

项目结构可以先从一个很小的 Demo 开始:

hello-langchain/ ├── package.json ├── .env ├── index.mjs ├── tool.mjs └── 1.html

其中:

  • index.mjs:最基础的 LLM 调用
  • tool.mjs:让 LLM 能调用工具读文件
  • 1.html:演示 Promise 并行优化

第一步:最小 LLM 调用

index.mjs 的核心代码如下:

arduino 复制代码
    import { ChatOpenAI } from "@langchain/openai";
    import "dotenv/config";

    const model = new ChatOpenAI({
      modelName: "deepseek-v4-flash",
      apiKey: process.env.DEEPSEEK_API_KEY,
      configuration: {
        baseURL: "https://api.deepseek.com/v1",
      },
    });
    // client.chat.completions.create
    const res = await model.invoke("棍王杯台球比赛应该有什么奖励");
    console.log(res.content);

这段代码虽然简单,但已经体现了 LangChain 的核心抽象:

  • ChatOpenAI 是统一模型接口
  • model.invoke() 是统一调用方式
  • 不同模型厂商只需要换配置,不需要重写业务逻辑

从使用体验上看,它和直接调用模型 SDK 很像;但从可扩展性看,它更适合后面继续接 Tool、Memory、RAG。

第二步:让 LLM 学会调用 Tool

真正让 Agent "活起来"的,是工具系统。

read_file 工具的核心实现:

javascript 复制代码
const readFileTool = tool( async ({ filePath }) => { const content = await fs.readFile(filePath, "utf-8"); console.log(`[工具调用] read_file (${filePath}) 成功读取文件内容 ${content.length} 字节`); return { content }; }, { name: "read_file", description: `用此工具来读取文件内容,当用户要求读取文件、 查看代码、分析文件内容时,调用此工具。输入文件路径。`, schema: z.object({ filePath: z.string().describe("要读取的文件路径名"), }), } );

这段代码可以拆成两层理解:

1. 真正执行任务的是处理函数

第一部分是一个异步函数:

dart 复制代码
    async ({ filePath }) => {
      const content = await fs.readFile(filePath, "utf-8");
      return { content };
    }

它才是真正"干活"的部分。这里的动作非常明确:读取文件内容并返回。

2. 决定模型会不会调用它的是工具描述

第二部分是工具元信息:

  • name:工具名
  • description:告诉模型什么时候该用它
  • schema:告诉模型参数应该怎么传

这部分非常关键,因为 LLM 并不是靠"猜"来调用工具,而是靠读取工具描述做决策。

如果 description 写得太模糊,模型就很可能不会调,或者调错。

如果 schema 不清楚,模型就不知道参数该怎么构造。

所以在 Agent 开发里,一个常见经验是:

Tool 的描述质量,往往直接决定 Tool Agent 的调用效果。

3. 执行过程要有反馈

你在工具内部加的这段日志非常好:

javascript 复制代码
console.log(`[工具调用] read_file (${filePath}) ...`)

这是很多初学者会忽略的细节。

现实里的 Agent 任务通常不是一两秒结束的。它可能要:

  • 连续调用多个工具
  • 读多个文件
  • 检索知识库
  • 多轮推理

如果整个过程中没有反馈,用户很容易以为它卡死了。

所以,一个可用的 Agent,不只是"能调用工具",还要让执行过程可感知、可观察。

4. 把工具绑定给模型

接下来是这一步:

ini 复制代码
const tools = [readFileTool];
const modelWithTools = model.bindTools(tools);

这可以理解成:告诉模型,你现在不仅能聊天,还拥有了这些额外能力。

绑定之后,模型在推理时就会多一个选择:

  • 直接回答
  • 或者先请求调用某个工具

5. 用消息历史驱动完整对话

你的消息上下文写法也很标准:

go 复制代码
const messages = [
  new SystemMessage(`
    你是一个代码助手,可以使用工具读取文件并解释代码

    工作流程:
    1. 用户要求读取文件时,立即调用 read_file 工具
    2. 等待工具返回文件内容
    3. 基于文件内容进行分析和解释
  `),
  new HumanMessage("请读取文件 index.js 并解释代码"),
];

这里的重点不是"SystemMessage 要写得多长",而是要明确三件事:

  • 模型现在扮演什么角色
  • 它应该遵循什么流程
  • 它有哪些可以调用的能力

有了这些信息,模型才更像一个稳定执行任务的 Agent,而不是一个随机发挥的聊天模型。

6. Tool Agent 的运行本质

当你执行下面这句时:

ini 复制代码
    let response = await modelWithTools.invoke(messages);
    messages.push(response);

模型不会立刻产出最终答案。它通常会先返回一个 tool_calls 结构,里面包含:

  • 要调用哪个工具
  • 这个工具的参数是什么
  • 调用 ID 是多少

也就是说,它会先说:

"这个问题我需要先调用 read_file,等拿到结果再继续。"

这个过程可以用一张图看得更清楚:

这就是 Tool Agent 和普通聊天的本质区别:

  • 普通聊天:模型直接输出文本
  • Tool Agent:模型先判断要不要行动,再根据行动结果继续输出

一个很容易被低估的问题:性能

当 Agent 开始变复杂之后,性能问题会很快暴露出来。

比如一个任务可能同时需要:

  • 查天气
  • 查社交平台数据
  • 读文件
  • 调数据库

如果这些请求一个一个串行执行,体验会非常差。

你在笔记里单独整理 Promise 优化,其实抓得很准,因为 Tool 调用性能往往是 Agent 落地时第一批真正的瓶颈。

Promise 三状态,为什么重要

Promise 只有三种状态:

复制代码
pending   → fulfilled
pending   → rejected

一旦从 pending 变成成功或失败,就不会再逆转。

这个机制决定了,我们可以把多个互不依赖的异步任务一起发出去,最后统一等待结果。

串行和并行的差距

你在 1.html 里演示的例子很典型。

如果串行执行:

csharp 复制代码
const weatherData = await getWeather(); // 2000ms
const tweetsData = await getTweets();   // 再等 500ms

总耗时大约是 2500ms。

如果并行执行:

scss 复制代码
const [weatherData, tweetsData] =
  await Promise.all([getWeather(), getTweets()]);

总耗时就会降到大约 2000ms,由最慢的那个任务决定。

可视化之后非常直观:

所以这里可以提炼出一个很实用的经验:

只要多个 Tool 之间没有依赖关系,就应该尽量并发执行,而不是串行等待。

这在 Agent 场景里非常重要,因为一次复杂任务往往会触发很多独立的外部调用。

如果继续往前走,可以做到什么程度

你在笔记里提到一个很有意思的目标:

手写一个简单版的 Claude Code Agent:LLM + Tool(fs + cli)

这个方向完全成立,而且思路已经很清晰了。

比如用户说:

复制代码
创建一个 React + Vite 的 TodoList

一个编程型 Agent 完整可以拆成下面几步:

objectivec 复制代码
第一步:用 CLI Tool 创建项目
第二步:用 File Tool 写入代码
第三步:用 CLI Tool 运行项目
第四步:根据运行结果继续修复和调整

换句话说,只要你把 Tool 能力从"读文件"扩展到:

  • 读文件
  • 写文件
  • 执行命令

再加上一层循环控制,实际上就已经具备了一个简化版代码 Agent 的雏形。

最后,用一张图把 Agent 看透

回到最开始的问题:Agent 到底神秘吗?

其实并不神秘。

你完全可以把它理解成这样一个系统:

  • LLM 负责思考
  • Tool 负责行动
  • Memory 负责记住
  • RAG 负责查资料
  • MCP / Skill 负责连接外部世界和复用能力

于是,一个原本只会生成文本的大模型,就被升级成了一个能在"思考 → 行动 → 观察 → 再思考"循环里持续完成任务的智能体。

这也是我对 Agent 最喜欢的一种理解:

Agent 不是什么玄学,它只是一个装上了手脚、记忆和知识系统的 LLM。

相关推荐
正儿八经的少年1 小时前
Spring Boot
java·spring boot·后端
未秃头的程序猿1 小时前
虚拟线程用了一年,我真香了——10倍吞吐量是真实存在的
java·后端·面试
神奇的代码在哪里2 小时前
AI编程时代,PPT已成为过去,单个HTML文件创造无限可能
javascript·html·powerpoint·ai编程·ppt
Highcharts2 小时前
从 ECharts 平滑迁移 Highcharts的配置映射与踩坑手册
前端·javascript
地铁潜行者2 小时前
从单体思维到微服务:服务拆分与组件选型实践
java·后端
花生了什么事o2 小时前
线程池参数调优:corePoolSize 怎么设置
java·后端
想要成为糕糕手2 小时前
🚀 从零玩转 Agent 开发:原理、架构与框架实战完全指南
后端
卷无止境2 小时前
pytest 从零到实战:要想代码好,测试少不了
后端·python
大爱编程♡2 小时前
Vue3基础
前端·javascript·vue.js