🤖 Agent开发完全指南:从LLM到智能体的蜕变之旅
你的大模型不只能聊天,它还能成为你的"数字员工"!让AI从"纸上谈兵"的顾问,变成"动手实干"的得力助手。
📌 目录
🧐 为什么需要Agent?
一个扎心的场景
想象一下:你上周跟ChatGPT讨论了一个重要的项目方案,今天想继续深入,结果发现它完全失忆了——仿佛你们从未相识。😅
或者你让它帮你读取一个本地文件,它却礼貌地回复:"我可以告诉你操作步骤"——这就像请了一个"纸上谈兵"的顾问,什么都懂,但什么都做不了。
LLM的四大痛点
| 痛点 | 问题描述 | 影响 |
|---|---|---|
| 🧠 无记忆 | Stateless设计,每次对话都是"初次见面" | 无法进行多轮深度协作 |
| 🔧 无行动力 | 只能"动嘴",不能"动手" | 无法操作实际系统 |
| 📚 知识受限 | 不知道你的私有文档和数据 | 无法解决具体业务问题 |
| 🌐 信息滞后 | 训练数据截止,无法获取实时信息 | 无法处理时效性任务 |
Agent = 解决方案
Agent = LLM + Memory + Tool + RAG + MCP + Skills
就像给一个聪明但"四肢不勤"的博士配上了:
- 📓 记事本(Memory)—— 记住所有对话
- 🔧 工具包(Tool)—— 能干实事
- 📚 图书馆(RAG)—— 查阅私有知识
- 🔌 万能插头(MCP)—— 连接外部服务
- 🎯 专业技能(Skills)—— 胜任特定领域
🔄 Agent的工作流程揭秘
关键设计原则: 规划→执行→反思,形成一个闭环的智能体行为模式。
🛠️ 实战:打造你的第一个高性能Agent
技术栈选择
| 组件 | 技术选型 | 理由 |
|---|---|---|
| 后端框架 | NestJS | 企业级架构,依赖注入友好 |
| 单智能体 | LangChain | 生态丰富,上手快 |
| 多智能体 | LangGraph | 支持复杂流程编排 |
| LLM服务 | DeepSeek API | 高性价比,性能优秀 |
完整代码实现
让我们逐步构建一个代码阅读分析Agent:
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import {
HumanMessage,
SystemMessage,
ToolMessage,
AIMessage
} from '@langchain/core/messages';
import fs from 'node:fs/promises';
import { z } from 'zod';
// ============================================================
// 1️⃣ 初始化LLM
// ============================================================
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
temperature: 0,
configuration: {
baseURL: 'https://api.deepseek.com/v1',
},
});
// ============================================================
// 2️⃣ 定义工具 - 让Agent拥有"双手"
// ============================================================
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('文件路径(支持相对路径或绝对路径)'),
}),
}
);
// ============================================================
// 3️⃣ 工具注册 & 绑定
// ============================================================
const tools = [readFileTool];
const modelWithTools = model.bindTools(tools);
// ============================================================
// 4️⃣ 系统提示词 - 设定"人设"
// ============================================================
const systemPrompt = `
你是一个代码分析助手,擅长阅读和解释代码。
🎯 工作流程:
1. 用户要求读取文件时,**立即调用** read_file 工具
2. 等待工具返回文件内容
3. 基于文件内容进行分析和解释
🔧 可用工具:
- read_file: 读取文件内容(优先使用此工具获取信息)
⚠️ 注意事项:
- 不要猜测文件内容,必须通过工具获取
- 分析要结构化、清晰、易懂
`;
// ============================================================
// 5️⃣ 执行Agent(完整流程)
// ============================================================
async function runAgent(userMessage: string) {
// 构建消息历史
const messages = [
new SystemMessage(systemPrompt),
new HumanMessage(userMessage),
];
// 第一次调用:LLM决定是否需要调用工具
let response = await modelWithTools.invoke(messages);
messages.push(response);
// 检测是否有工具调用
if (response.tool_calls && response.tool_calls.length > 0) {
console.log(`🔧 检测到 ${response.tool_calls.length} 个工具调用`);
// 并行执行所有工具
const toolResults = await Promise.all(
response.tool_calls.map(async (call) => {
const result = await readFileTool.invoke(call.args);
return {
tool_call_id: call.id,
result,
};
})
);
// 将工具结果添加到消息历史
for (const tr of toolResults) {
messages.push(new ToolMessage({
content: tr.result,
tool_call_id: tr.tool_call_id,
}));
}
// 第二次调用:基于工具结果生成最终回复
const finalResponse = await modelWithTools.invoke(messages);
messages.push(finalResponse);
return finalResponse.content;
}
return response.content;
}
// ============================================================
// 6️⃣ 执行示例
// ============================================================
const result = await runAgent('请读取 tool.mjs 文件内容并解释代码');
console.log('📝 Agent回复:\n', result);
🔍 核心模块深度解析
1️⃣ 环境配置与模型初始化
import 'dotenv/config'; // 📦 环境变量管家
import { ChatOpenAI } from '@langchain/openai';
dotenv/config 的作用:
- 自动读取项目根目录的
.env文件 - 将配置注入到
process.env中 - 敏感信息(API Key)不写死在代码里
ChatOpenAI 的妙用:
- 统一的LLM调用接口,支持OpenAI、DeepSeek、通义千问等
- 就像手机的Type-C接口,一统天下
🌡️ Temperature参数详解
| Temperature | 效果 | 适用场景 |
|---|---|---|
| 0 | 确定性输出,每次一样 | 工具调用、代码生成 ✅ |
| 0.3-0.5 | 轻微随机性 | 数据提取、分类任务 |
| 0.7 | 平衡创造性和稳定性 | 日常对话、内容创作 |
| 1.0 | 高创造性,天马行空 | 头脑风暴、创意写作 |
💡 为什么工具调用用0? 工具调用需要精确的参数格式,随机性会让模型"胡思乱想"导致调用失败。
temperature: 0= 每次都选概率最高的token,稳如老狗!🐕
2️⃣ Zod:参数验证的艺术
import { z } from 'zod';
Zod = TypeScript运行时类型验证库 🛡️
- TypeScript只在编译时检查,运行时就像"脱了盔甲的战士"
- Zod在运行时也进行检查,双重保障!
🚫 没有Zod:危险!
async function readFile(filePath) {
// 如果传入的是数字?null?undefined?
// 程序可能崩溃!💥
const content = await fs.readFile(filePath, 'utf-8');
return content;
}
✅ 有Zod:安全!
const schema = z.object({
filePath: z.string().describe('要读取的文件路径')
});
// Zod会验证:
// 1. filePath是否存在
// 2. filePath是否为string类型
// 3. 验证失败时抛出清晰的错误信息
Zod在Agent中的工作流程
LLM生成 tool_calls
↓
{
name: 'read_file',
arguments: '{"filePath": "src/index.js"}' // ✅ 符合Schema
}
↓
Zod验证通过 → 执行工具
↓
{
name: 'read_file',
arguments: '{"filePath": 123}' // ❌ 应该是string!
}
↓
Zod抛出错误 → Agent捕获 → LLM重新生成正确参数
3️⃣ Tool:给LLM装上"双手"
const readFileTool = tool(
// 第一个参数:执行函数
async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
return content;
},
// 第二个参数:工具配置
{
name: 'read_file', // 唯一标识
description: '读取文件内容...', // LLM阅读
schema: z.object({ // 参数约束
filePath: z.string().describe('文件路径')
})
}
);
📖 Tool的"三要素"
| 要素 | 作用 | 谁来用 |
|---|---|---|
| 执行函数 | 实际做事的代码 | 系统执行 |
| 描述 | 告诉LLM工具的功能和使用场景 | LLM阅读 |
| Schema | 约束参数格式和类型 | 系统验证 + LLM参考 |
🎭 Tool的执行流程
1. LLM决定调用工具
↓
2. LangChain解析 tool_calls
↓
3. Zod验证参数
↓
4. 执行函数(异步)
↓
5. 返回结果
↓
6. 结果作为 ToolMessage 传给LLM
💡 工具设计最佳实践
// ✅ 好工具:职责单一,描述清晰
const readFileTool = tool(
async ({ filePath }) => {
return await fs.readFile(filePath, 'utf-8');
},
{
name: 'read_file',
description: '读取指定路径的文件内容。适用场景:查看代码、分析配置文件、读取日志。',
schema: z.object({
filePath: z.string().describe('文件路径,支持相对路径和绝对路径')
})
}
);
// ❌ 坏工具:职责过多,描述模糊
const doEverythingTool = tool(
async ({ input }) => {
// 又读文件又写文件又发请求...
// LLM会困惑:到底什么时候用?
},
{
name: 'do_everything',
description: '做很多事情', // 太模糊!
schema: z.object({
input: z.any() // 太宽泛!
})
}
);
4️⃣ 消息系统:Agent的"记忆宫殿"
import {
HumanMessage, // 用户消息
SystemMessage, // 系统指令
ToolMessage, // 工具返回结果
AIMessage // AI的回复
} from '@langchain/core/messages';
🎭 消息角色与作用
| 消息类型 | 角色 | 作用 | 示例 |
|---|---|---|---|
| SystemMessage | 系统 | 设定AI的人设和规则 | "你是代码助手" |
| HumanMessage | 用户 | 用户的问题/指令 | "读取文件" |
| AIMessage | AI | AI的思考和回复 | "我即将调用工具" |
| ToolMessage | 工具 | 工具执行的结果 | "文件内容是..." |
🧠 对话历史管理
// 完整的对话历史
const messages = [
// 1. 系统提示词(永久记忆)
new SystemMessage('你是代码助手...'),
// 2. 用户问题
new HumanMessage('请读取 tool.mjs'),
// 3. AI的响应(包含tool_calls)
new AIMessage({
content: '',
tool_calls: [{
id: 'call_123',
name: 'read_file',
args: { filePath: 'tool.mjs' }
}]
}),
// 4. 工具执行结果
new ToolMessage({
content: '文件内容...',
tool_call_id: 'call_123' // 关联到对应的工具调用
}),
// 5. AI的最终回复
new AIMessage('文件内容分析如下...')
];
🔗 tool_call_id 的关键作用
// ⚠️ 关键:tool_call_id 建立关联
// 调用发起 → 工具执行 → 结果返回,三者通过 ID 串联!
// 第一步:LLM发起调用
AIMessage {
tool_calls: [{
id: 'call_abc123', // 唯一ID
name: 'read_file',
args: { filePath: 'test.txt' }
}]
}
// 第二步:工具执行,返回结果
ToolMessage {
content: '文件内容...',
tool_call_id: 'call_abc123' // 关联到第一步
}
// 第三步:LLM看到结果,继续生成
// 通过 tool_call_id,LLM知道这个结果对应哪个调用
// 即使有多个工具并发执行,也不会混淆!🎯
5️⃣ 工具调用的"三幕剧"
🎬 第一幕:LLM识别需求
用户: "读取 tool.mjs 并解释"
LLM思考: "需要调用read_file工具获取文件内容"
LLM输出: {
tool_calls: [{
id: "call_123",
name: "read_file",
arguments: { filePath: "tool.mjs" }
}]
}
🎬 第二幕:执行工具
// LangChain检测到 tool_calls 后自动执行
const toolCalls = response.tool_calls;
// 并行执行所有工具调用
const toolResults = await Promise.all(
toolCalls.map(async (call) => {
const toolFn = toolMap[call.name];
const result = await toolFn(call.args);
return {
tool_call_id: call.id,
result: result
};
})
);
🎬 第三幕:LLM整合结果
// 将工具结果添加到对话历史
for (const result of toolResults) {
messages.push(new ToolMessage({
content: result.result,
tool_call_id: result.tool_call_id
}));
}
// 再次调用LLM,基于工具结果生成最终回复
const finalResponse = await modelWithTools.invoke(messages);
console.log(finalResponse.content);
6️⃣ 并行执行:性能优化的秘密武器
🔄 为什么需要并行?
Agent处理复杂任务时,往往需要调用多个工具:
- 📖 读取3个不同的文件
- 🌐 调用2个不同的API
- 📊 查询2个数据库
顺序执行:总耗时 = 所有任务耗时之和 😱 并行执行:总耗时 = 最慢任务耗时 ⚡
📊 性能对比
// ⏰ 顺序执行:2.5秒
async function sequential() {
console.time('sequential');
const a = await taskA(); // 2秒
const b = await taskB(); // 0.5秒
console.timeEnd('sequential'); // 2500ms
}
// ⚡ 并行执行:2秒
async function parallel() {
console.time('parallel');
const [a, b] = await Promise.all([taskA(), taskB()]);
console.timeEnd('parallel'); // 2000ms
}
💡 Promise 深度解析
// Promise 三种状态
// ┌─────────┐
// │ Pending │ 初始状态(既没成功也没失败)
// └────┬────┘
// │
// ├──→ ┌──────────┐
// │ │ Fulfilled │ 操作成功 ✅
// │ └──────────┘
// │
// └──→ ┌──────────┐
// │ Rejected │ 操作失败 ❌
// └──────────┘
// 创建Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('成功啦!🎉');
} else {
reject('出错了!💥');
}
}, 1000);
});
// 使用Promise
myPromise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('完成'));
🚀 Promise.all 详解
// Promise.all 的特点:
// 1. 接收一个Promise数组
// 2. 所有Promise并行执行
// 3. 等待所有Promise完成
// 4. 全部成功 → 返回结果数组(顺序与输入一致)
// 5. 任何一个失败 → 整体失败
const promises = [
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
];
try {
const [user, posts, comments] = await Promise.all(promises);
console.log('所有数据都拿到了!', { user, posts, comments });
} catch (error) {
console.error('至少一个请求失败了', error);
}
⚠️ 并行执行注意事项
// ✅ 适合并行:互不依赖
const tasks = [
readFile('a.txt'), // 独立
readFile('b.txt'), // 独立
fetchWeather(), // 独立
];
// ❌ 不适合并行:有依赖关系
const filePath = await getConfigPath(); // 必须先执行
const content = await readFile(filePath); // 依赖上一步结果
// 这种情况必须顺序执行!🧵
🧩 核心模块详解
1️⃣ Memory记忆模块
// 🧠 让Agent拥有长期记忆
class MemoryManager {
constructor(redisClient) {
this.redis = redisClient;
this.maxHistory = 50;
}
async saveMessage(userId, message) {
const key = `chat:${userId}`;
await this.redis.lpush(key, JSON.stringify(message));
await this.redis.ltrim(key, 0, this.maxHistory - 1);
}
async loadHistory(userId, limit = 10) {
const key = `chat:${userId}`;
const history = await this.redis.lrange(key, 0, limit - 1);
return history.map(msg => JSON.parse(msg));
}
async clearMemory(userId) {
await this.redis.del(`chat:${userId}`);
}
}
2️⃣ RAG检索增强生成
// 📚 让Agent读取私有文档
const ragTool = tool(
async ({ query }) => {
console.log(`🔍 搜索知识库: ${query}`);
// 1. 将查询转换为向量
const embedding = await getEmbedding(query);
// 2. 在向量数据库中搜索相似文档
const results = await vectorStore.similaritySearch(embedding, 3);
// 3. 提取相关文本并返回
return {
query,
sources: results.map(doc => doc.metadata.source),
content: results.map(doc => doc.pageContent).join('\n---\n')
};
},
{
name: 'rag_search',
description: '搜索内部知识库获取相关信息。适用场景:查询公司文档、技术手册、历史记录。',
schema: z.object({
query: z.string().describe('搜索关键词或问题')
})
}
);
3️⃣ MCP协议支持
// 🔌 MCP (Model Context Protocol) - 让Agent调用第三方工具
const mcpTool = tool(
async ({ server, toolName, params }) => {
const client = new MCPClient(server);
await client.connect();
const result = await client.callTool(toolName, params);
await client.disconnect();
return result;
},
{
name: 'mcp_call',
description: '通过MCP协议调用外部工具和服务',
schema: z.object({
server: z.string().describe('MCP服务器地址'),
toolName: z.string().describe('要调用的工具名称'),
params: z.record(z.any()).describe('工具参数')
})
}
);
🎯 总结
核心原则
| 原则 | 说明 |
|---|---|
| Think in Tools | 把一切能力封装成工具,Agent就能学会使用 |
| 并行优先 | 独立任务用Promise.all并行执行,性能提升立竿见影 |
| 反馈为王 | 耗时操作必须给实时反馈,避免用户焦虑退出 |
| 提示词是关键 | 好的系统提示词 = 好的Agent表现 |
| 日志要详细 | 工具调用的每个环节都要可追踪 |
| 类型要安全 | 用Zod做运行时验证,避免隐藏Bug |
| 错误要友好 | 给用户可理解、可操作的建议 |
代码优化清单
✅ 好的Agent代码特征
const goodAgent = {
// 1. 清晰的工具描述
description: '具体说明什么场景使用',
// 2. 严格的参数验证
schema: z.object({ param: z.string().min(1) }),
// 3. 实时反馈
console.log('🔄 正在执行...'),
// 4. 错误处理
try { /* 执行 */ } catch (error) {
return `❌ ${error.message}`;
},
// 5. 并行优化
await Promise.all([task1(), task2()]),
// 6. 日志记录
console.log(`✅ 完成 (${duration}ms)`)
};
❌ 差的Agent代码特征
const badAgent = {
// 1. 模糊的描述
description: '做一些事情',
// 2. 无参数验证
schema: z.object({ input: z.any() }),
// 3. 无反馈(用户不知道发生了什么)
// 4. 无错误处理(可能崩溃)
await riskyOperation(),
// 5. 顺序执行(总耗时 = 所有任务之和)
await task1(); // 等2秒
await task2(); // 再等2秒
// 6. 无日志(出错了不知道哪里有问题)
};
💡 一句话总结
Agent = 会思考的 LLM + 会行动的 Tool + 会记忆的 Storage + 会学习的 RAG
Agent开发不是简单的API调用,而是将AI能力通过工程化手段落地的艺术。当你的Agent能自主完成从"读取文件"到"分析代码"再到"生成报告"的全流程时,你就真正掌握了这门技术!💪