摘要:LLM天然无状态不会调工具不懂内部文档。Agent给它加上Memory、Tool、RAG、MCP和Skills五层外挂,让它从聊天机器人进化为能自主执行任务的智能体。本文用LangChain手写readFileTool并注册到模型,再以Promise.all并行优化多工具调用性能,走通Agent开发全流程。
目录
一、Agent = LLM + 五层外挂
大语言模型本身只是一个"预测下一个 Token"的概率引擎——它没有记忆、不会打开网页、读不到内部文档、不知道今天的新闻、也不能操作文件。直接用 LLM 接口开发的 Chatbot,本质是一个无状态的对话接口。
Agent(智能体)要做的事情,就是给 LLM 装上五层外挂:
| 问题 | 外挂 | 作用 |
|---|---|---|
| LLM 不记得上周的对话 | Memory 模块 | 数据库/前端存储/Redis 管理记忆 |
| LLM 不能操作网页和文件 | Tool Use 模块 | 让 LLM 调用外部函数和 API |
| LLM 不懂公司内部文档 | RAG 模块 | 检索内部知识库,注入 Prompt |
| LLM 不知道最新资讯 | MCP / 第三方 Tool | 接入外部工具协议获取实时数据 |
| LLM 不能自动化复杂任务 | Skills 技能 | 将多步流程封装为可复用的能力 |
Agent = LLM + Memory + Tool + RAG + MCP + Skills
Claude Code、Codex 是 Coding Agent;Manus 等产品是自动化任务 Agent。它们的共同架构是:用户提一个复杂任务 → LLM 规划/推理 → 决定调用哪些外挂 → 执行 → 返回结果。
二、LangChain:统一的大模型开发框架
LangChain 是一个比 OpenAI SDK 诞生更早的 LLM 开发框架,核心优势是统一且兼容各家大模型。无论底层是 DeepSeek、GPT 还是其他模型,上层代码几乎不用改。
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',
},
});
const response = await model.invoke('棍王杯台球比赛应该设什么奖励?');
console.log(response.content);
LangChain 的技术栈通常搭配:
| 层级 | 技术 | 职责 |
|---|---|---|
| 后端 | NestJS | 业务逻辑、路由、认证 |
| 单 Agent | LangChain | LLM + Tool + Memory 的组合编排 |
| 多 Agent | LangGraph | 多个 Agent 之间的协作与状态流转 |
三、实战:手写一个 Agent 工具并注册到模型
在 Agent 体系中,工具就是函数 + 描述 + Schema 约束。LangChain 通过 @langchain/core/tools 的 tool() 方法来定义工具,用 zod 库做参数校验。
定义读文件工具
import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import { z } from 'zod';
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('要读取的文件路径')
})
}
);
一个工具由两部分组成:
| 部分 | 内容 | 说明 |
|---|---|---|
| 处理函数 | async ({ filePath }) => { ... } | 异步执行真实任务,返回结果 |
| 描述对象 | name + description + schema | LLM 判断何时调用、参数该怎么传的依据 |
description 的质量直接决定 LLM 能否在正确时机选中这个工具——模糊的描述会导致漏调用或错调用。
注册工具并发送任务
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
temperature: 0,
configuration: { baseURL: 'https://api.deepseek.com/v1' },
});
const tools = [readFileTool];
const modelWithTools = model.bindTools(tools); // 关键:工具绑定到模型
const messages = [
new SystemMessage(`
你是一个代码助手,可以使用工具读取文件并解释代码。
工作流程:
1. 用户要求读取文件时,立即调用 read_file 工具。
2. 等待工具返回文件内容。
3. 基于文件内容进行分析和解释。
`),
new HumanMessage('请读取代码文件内容并解释'),
];
let response = await modelWithTools.invoke(messages);
messages.push(response); // 将 LLM 的 tool_calls 响应存入历史
工具调用流程
HumanMessage → LLM 推理 → 识别到需调 read_file → 生成 tool_calls →
→ Runtime 执行 readFileTool → 结果注入消息历史 → 再次调用 LLM → 最终回复
LangChain 的消息类型体系清晰地区分了不同角色的消息:
| 消息类型 | 角色 | 用途 |
|---|---|---|
HumanMessage | user | 用户的原始提问 |
SystemMessage | system | 设定 Agent 行为规则和工具使用指引 |
AIMessage | assistant | LLM 的回复(可能包含 tool_calls) |
ToolMessage | tool | 工具执行后的返回结果 |
Agent 任务可能复杂且耗时——如果用户太久没看到反馈就可能退出。工具函数中
console.log打印执行进度是一个好习惯。
四、Promise.all:并行执行多工具的秘籍
真实场景中,LLM 可能同时需要调用多个独立工具——查天气和拉推文不需要互相等待。串行调用会让总耗时等于各工具耗时之和,而并行调用才是高性能 Agent 的关键。
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);
});
}
串行写法 — 总耗时 ≈ 2000 + 500 = 2500ms:
const weatherData = await getWeather(); // 等 2000ms
const tweetsData = await getTweets(); // 再等 500ms
并行写法 — 总耗时 ≈ max(2000, 500) = 2000ms:
const [weatherData, tweetsData] = await Promise.all([
getWeather(),
getTweets()
]);
Promise 三态回顾
Promise 是 ES6 提供的异步语法,有三种互斥的状态:
Pending ──→ Fulfilled (resolve 调用)
│
└──→ Rejected (reject 调用)
一旦从 Pending 变为 Fulfilled 或 Rejected,状态就锁死了——不可二次变更。
Promise.all([promise数组]) —— 并行执行数组内所有 Promise,等待全部 resolve 后返回结果数组,结果顺序与 Promise 数组顺序一致。如果有任意一个 reject,整个 Promise.all 立即 reject。
在 LangChain 的 Agent 中,当 LLM 的 tool_calls 返回了多个无关工具调用时,应该用
Promise.all并发执行它们,而不是用for...of串行await。这是 Agent 性能优化的第一原则。
五、总结
- Agent = LLM + 外挂:Memory 记东西,Tool 做事情,RAG 查文档,MCP 接入外部工具,Skills 封装能力。Claude Code 就是 LLM + Tool(fs + cli) 的产物。
- LangChain 统一模型层:
ChatOpenAI兼容各家 LLM,tool()+zod定义工具,model.bindTools()完成注册。 - 工具两部分:处理函数(执行任务)+ 描述对象(name / description / schema),
description决定 LLM 能否选中它。 - 消息类型体系:
HumanMessage/SystemMessage/AIMessage/ToolMessage各司其职,构造完整调用上下文。 - Promise.all 并行:多工具互不依赖时并发执行,总耗时等于最慢那个而非所有之和。这是 Agent 性能的底层优化。
理解 Agent 的核心一句话:它不是一个更聪明的 LLM,而是一个被 Memory、Tool、RAG 武装起来的 LLM。
—— 一个好 Agent,是能记住、会动手、懂查找的 LLM。