🤖 Agent开发完全指南:从LLM到智能体的蜕变之旅

🤖 Agent开发完全指南:从LLM到智能体的蜕变之旅

你的大模型不只能聊天,它还能成为你的"数字员工"!让AI从"纸上谈兵"的顾问,变成"动手实干"的得力助手。


📌 目录


🧐 为什么需要Agent?

一个扎心的场景

想象一下:你上周跟ChatGPT讨论了一个重要的项目方案,今天想继续深入,结果发现它完全失忆了------仿佛你们从未相识。😅

或者你让它帮你读取一个本地文件,它却礼貌地回复:"我可以告诉你操作步骤"------这就像请了一个"纸上谈兵"的顾问,什么都懂,但什么都做不了。

LLM的四大痛点

痛点 问题描述 影响
🧠 无记忆 Stateless设计,每次对话都是"初次见面" 无法进行多轮深度协作
🔧 无行动力 只能"动嘴",不能"动手" 无法操作实际系统
📚 知识受限 不知道你的私有文档和数据 无法解决具体业务问题
🌐 信息滞后 训练数据截止,无法获取实时信息 无法处理时效性任务

Agent = 解决方案

ini 复制代码
Agent = LLM + Memory + Tool + RAG + MCP + Skills

就像给一个聪明但"四肢不勤"的博士配上了:

  • 📓 记事本(Memory)------ 记住所有对话
  • 🔧 工具包(Tool)------ 能干实事
  • 📚 图书馆(RAG)------ 查阅私有知识
  • 🔌 万能插头(MCP)------ 连接外部服务
  • 🎯 专业技能(Skills)------ 胜任特定领域

🔄 Agent的工作流程揭秘

关键设计原则: 规划→执行→反思,形成一个闭环的智能体行为模式。


🛠️ 实战:打造你的第一个高性能Agent

技术栈选择

组件 技术选型 理由
后端框架 NestJS 企业级架构,依赖注入友好
单智能体 LangChain 生态丰富,上手快
多智能体 LangGraph 支持复杂流程编排
LLM服务 DeepSeek API 高性价比,性能优秀

完整代码实现

让我们逐步构建一个代码阅读分析Agent

typescript 复制代码
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️⃣ 环境配置与模型初始化

typescript 复制代码
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:参数验证的艺术

typescript 复制代码
import { z } from 'zod';

Zod = TypeScript运行时类型验证库 🛡️

  • TypeScript只在编译时检查,运行时就像"脱了盔甲的战士"
  • Zod在运行时也进行检查,双重保障!
🚫 没有Zod:危险!
typescript 复制代码
async function readFile(filePath) {
    // 如果传入的是数字?null?undefined?
    // 程序可能崩溃!💥
    const content = await fs.readFile(filePath, 'utf-8');
    return content;
}
✅ 有Zod:安全!
typescript 复制代码
const schema = z.object({
    filePath: z.string().describe('要读取的文件路径')
});

// Zod会验证:
// 1. filePath是否存在
// 2. filePath是否为string类型
// 3. 验证失败时抛出清晰的错误信息
Zod在Agent中的工作流程
rust 复制代码
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装上"双手"

typescript 复制代码
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的执行流程
markdown 复制代码
1. LLM决定调用工具
   ↓
2. LangChain解析 tool_calls
   ↓
3. Zod验证参数
   ↓
4. 执行函数(异步)
   ↓
5. 返回结果
   ↓
6. 结果作为 ToolMessage 传给LLM
💡 工具设计最佳实践
typescript 复制代码
// ✅ 好工具:职责单一,描述清晰
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的"记忆宫殿"

typescript 复制代码
import { 
    HumanMessage,   // 用户消息
    SystemMessage,  // 系统指令
    ToolMessage,    // 工具返回结果
    AIMessage       // AI的回复
} from '@langchain/core/messages';
🎭 消息角色与作用
消息类型 角色 作用 示例
SystemMessage 系统 设定AI的人设和规则 "你是代码助手"
HumanMessage 用户 用户的问题/指令 "读取文件"
AIMessage AI AI的思考和回复 "我即将调用工具"
ToolMessage 工具 工具执行的结果 "文件内容是..."
🧠 对话历史管理
typescript 复制代码
// 完整的对话历史
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 的关键作用
typescript 复制代码
// ⚠️ 关键: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识别需求
css 复制代码
用户: "读取 tool.mjs 并解释"
LLM思考: "需要调用read_file工具获取文件内容"
LLM输出: {
  tool_calls: [{
    id: "call_123",
    name: "read_file",
    arguments: { filePath: "tool.mjs" }
  }]
}
🎬 第二幕:执行工具
typescript 复制代码
// 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整合结果
typescript 复制代码
// 将工具结果添加到对话历史
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个数据库

顺序执行 :总耗时 = 所有任务耗时之和 😱 并行执行:总耗时 = 最慢任务耗时 ⚡

📊 性能对比
typescript 复制代码
// ⏰ 顺序执行: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 深度解析
typescript 复制代码
// 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 详解
typescript 复制代码
// 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);
}
⚠️ 并行执行注意事项
typescript 复制代码
// ✅ 适合并行:互不依赖
const tasks = [
    readFile('a.txt'),   // 独立
    readFile('b.txt'),   // 独立
    fetchWeather(),      // 独立
];

// ❌ 不适合并行:有依赖关系
const filePath = await getConfigPath();   // 必须先执行
const content = await readFile(filePath); // 依赖上一步结果
// 这种情况必须顺序执行!🧵

🧩 核心模块详解

1️⃣ Memory记忆模块

typescript 复制代码
// 🧠 让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检索增强生成

typescript 复制代码
// 📚 让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协议支持

typescript 复制代码
// 🔌 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代码特征
typescript 复制代码
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代码特征
typescript 复制代码
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能自主完成从"读取文件"到"分析代码"再到"生成报告"的全流程时,你就真正掌握了这门技术!💪

相关推荐
Token炼金师1 小时前
推理部署层:吞吐上不去的显存账本与调度博弈
人工智能·深度学习·llm
槑有老呆2 小时前
从零打造你的第一个 AI Agent:LLM 不够用?给它装上"外挂"
agent
倾颜2 小时前
AI Mind 的多会话短期记忆容器:如何隔离多个聊天上下文
langchain·agent·next.js
阿拉斯攀登2 小时前
Prompt 工程与答案生成优化
prompt·agent·memory·知识库·向量数据库·rag
Briwisdom2 小时前
Agent 帮我写 CUDA(续):当 Self-Attention 遇上 Causal Mask 和 Cross-Attention
agent·算子开发·flash attention
CaffeinePro2 小时前
Langchain环境搭建与LCEL核心语法
人工智能·agent
人工智能培训2 小时前
大模型驱动下传统大数据架构的变革方向
大数据·人工智能·重构·架构·agent·agi
倾颜3 小时前
AI Mind 的单线程短期记忆设计:如何压缩、整理并控制当前会话上下文
前端·langchain·llm
掉鱼的猫4 小时前
把 OpenAPI 接入 Agent Harness:零代码让 Agent 听懂你的 REST API
java·llm·agent