从理解 Agent 核心概念到用 LangChain 写出第一个 Tool Calling 智能体,一文讲透 AI Agent 开发

8 阅读8分钟

🤖 AI Agent 从入门到实践:手把手教你打造第一个智能体

从理解 Agent 核心概念到用 LangChain 写出第一个 Tool Calling 智能体,一文讲透 AI Agent 开发。


📖 前言

2025 年是 AI Agent 的元年。从 Claude Code、Cursor 到 Manus,Agent 产品正在以前所未有的速度落地。但是——Agent 到底是什么?它是怎么工作的?我能不能自己写一个?

本文基于我在 agent_in_action 项目中的学习笔记和实战代码,从零开始梳理 Agent 的核心概念、架构设计,并用 LangChain + DeepSeek 带你写出第一个带 Tool Calling 能力的智能体。

读完这篇文章,你将获得:

  • ✅ 透彻理解 Agent 的本质和架构
  • ✅ 掌握 LLM + Tool + Memory 的核心设计模式
  • ✅ 能够用 LangChain 独立开发单智能体
  • ✅ 理解 Promise.all 并行优化 Tool 调用的实战技巧

一、为什么不能直接用大模型?

很多人以为 Agent 就是调一下大模型接口——不对,还差得远。

纯 LLM 有以下五个致命问题:

问题描述解决方案
🧠 无记忆上周聊过的信息,这周完全忘了——LLM 是 Stateless 的Memory 模块(数据库 / Redis / 前端存储)
🔧 不能做事让它帮你访问网页、操作文件?它只能说"你可以这样做",但动不了手Tool Use 模块
📚 不懂内部知识公司内部文档、私有知识库,LLM 训练数据里没有RAG 模块
📰 不知道最新信息2026 世界杯新闻?预训练数据截止日期之后的事一概不知MCP / 第三方 Tool
🎯 缺少专业技能做 PPT、分析股市并自动交易——需要专业领域能力Skills 技能模块

💡 核心洞察:Agent 本质上就是给 LLM 装上"手"和"脑"。
"手"就是 Tool——让它能真正做事情;"脑"就是 Memory——让它记住该记住的东西。


二、Agent 到底是什么?

2.1 一句话定义

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

一个知道内部知识、能思考规划、能够帮你做事情的扩展版大模型,就是一个 Agent。

2.2 Agent 工作流程

当用户提出一个复杂任务时,Agent 内部是这样运转的:

用户 Prompt(复杂任务)
    │
    ▼
┌─────────────────────────────────────┐
│         LLM 规划 / 推理              │
│     (Planning & Reasoning)         │
└──────────┬──────────────────────────┘
           │
    ┌──────┼──────┬──────────┐
    ▼      ▼      ▼          ▼
  Memory  Tool   RAG       MCP/Skills
  (记忆)  (工具) (知识检索)  (第三方能力)
    │      │      │          │
    └──────┴──────┴──────────┘
           │
           ▼
    ┌─────────────┐
    │  Response    │
    │  返回给用户   │
    └─────────────┘

整个过程的关键在于:LLM 自己会思考、会规划,当它发现自己能力不够时,会自动调用 Tool 来扩展能力,用 Memory 来管理上下文,用 RAG 来获取私有知识。


三、LangChain:Agent 开发的事实标准

3.1 为什么选 LangChain?

LangChain 是目前最成熟的 LLM 应用开发框架之一,早于 OpenAI 的官方 SDK 诞生。它的核心价值在于:

  • 🔗 统一接口:兼容各家 LLM(OpenAI / DeepSeek / 通义千问……),一套代码切换模型
  • 🛠️ Tool 抽象:通过 @langchain/core/tools 和 Zod 验证,优雅地定义和管理工具
  • 📦 按需加载:只引入你需要的模块,不会拖垮项目

3.2 技术栈

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

本教程先聚焦单智能体,多智能体我们后续再讲。


四、实战:用 LangChain 打造第一个带 Tool 的 Agent

4.1 环境搭建

package.json:

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

.env 配置(以 DeepSeek 为例):

DEEPSEEK_API_KEY=sk-your-api-key
DEEPSEEK_API_BASE_URL=https://api.deepseek.com/v1

4.2 Hello World:第一次调用 LLM

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

const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    configuration: {
        baseURL: process.env.DEEPSEEK_API_BASE_URL,
    }
});

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

🎯 这一步验证了模型能正常连通。但光这样还不够——它只能说,不能做。

4.3 核心突破:给 LLM 装上"手"——Tool Calling

下面的代码是本文的精华所在。我们来一步步拆解:

import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import {
    HumanMessage,   // user role
    SystemMessage,  // system role
    ToolMessage,    // tool role
    AIMessage,      // assistant role
} from '@langchain/core/messages';
import fs from 'node:fs/promises';
import { z } from 'zod';

// 1️⃣ 初始化模型
const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,
    configuration: {
        baseURL: process.env.DEEPSEEK_API_BASE_URL,
    }
});

// 2️⃣ 定义工具:读取文件
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];

// 4️⃣ 绑定工具到模型
const modelWithTools = model.bindTools(tools);

// 5️⃣ 构建对话
const messages = [
    new SystemMessage(`
        你是一个代码助手,可以使用工具读取文件并解释代码。

        工作流程:
        1. 用户要求读取文件时,立即调用 read_file 工具。
        2. 等待工具返回文件内容。
        3. 基于文件内容进行分析和解释。

        可用工具:
        - read_file: 读取文件内容(使用此工具来获取文件内容)
    `),
    new HumanMessage('请读取 tool.mjs 文件内容并解释代码'),
];

// 6️⃣ 第一次调用——LLM 决定调用哪个工具
let response = await modelWithTools.invoke(messages);
messages.push(response);

4.4 深入理解 Tool 的两个核心部分

每个 Tool 由两部分组成:

第一部分:处理函数(异步)
async ({ filepath }) => {
    const content = await fs.readFile(filepath, 'utf-8');
    return content;
}

这就是工具"真正做事"的地方。它必须是异步函数,因为真实场景中可能需要读写文件、调 API、查数据库——都是异步操作。

第二部分:函数描述对象
{
    name: 'read_file',                    // 工具名称
    description: '...',                   // 详细功能描述(LLM 靠这个判断何时调用)
    schema: z.object({                    // 参数约束(Zod 验证)
        filepath: z.string().describe('...'),
    }),
}

⚠️ 关键细节description 写得越好,LLM 就越清楚什么时候该调这个工具。含糊的描述 = 不确定的工具调用 = 糟糕的用户体验。


五、Tool Calling 的底层机制

5.1 LLM 如何"决定"调用工具?

当 LLM 发现用户的需求超出了自己的纯文本生成能力时,它不会强行编造,而是停下来,返回一个 tool_calls 列表:

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

关键字段解析:

字段作用
id工具调用的唯一标识——关联后续的工具执行结果
name要调用的工具名称
arguments传给工具的参数(必须符合 schema 定义)

5.2 为什么 id 这么重要?

一个复杂任务可能涉及多个工具、多次调用。用 id 来关联"哪个结果对应哪个工具调用",才能在历史会话列表中拼出完整的任务上下文。

用户: 帮我分析 tool.mjs 的代码
    │
    ▼
LLM: 我需要调用 read_file("tool.mjs")  →  id: call_001
    │
    ▼
Tool 返回: "import { ChatOpenAI } from..."  →  关联 id: call_001
    │
    ▼
LLM: 基于文件内容,我来解释这段代码...

六、性能优化:Promise.all 并行执行

6.1 问题场景

LLM 的一个复杂任务可能调用多个独立的 Tool,比如:

  • 同时读取 3 个文件
  • 同时查询天气 + 获取推文

如果串行执行,总耗时 = 每个 Tool 耗时的累加。

6.2 Promise 基础复习

Promise 是 ES6 提供的异步编程核心,有三种状态:

        ┌──────────┐
        │ Pending  │  等待中
        └────┬─────┘
        ┌────┴─────┐
        ▼          ▼
   ┌─────────┐  ┌──────────┐
   │Fulfilled│  │ Rejected │
   │  成功   │  │   失败   │
   └─────────┘  └──────────┘
  • 状态只能从 Pending → Fulfilled 或 Pending → Rejected
  • 一旦转变,不可回退(这是 Promise 的核心特征)
  • await(ES8)是最优雅的异步变同步语法

6.3 并行 vs 串行实战

// ❌ 串行写法 —— 总耗时 = 2000ms + 500ms = 2500ms
const weatherData = await getWeather();     // 等 2000ms
const tweetsData = await getTweets();        // 再等 500ms

// ✅ 并行写法 —— 总耗时 = max(2000ms, 500ms) ≈ 2000ms
const [weatherData, tweetsData] = await Promise.all([
    getWeather(),
    getTweets()
]);

💡 Promise.all 的行为:等待所有 Promise 完成,返回结果数组,结果顺序与传入的 Promise 数组顺序一致

6.4 在 Agent 中的实际应用

当 LLM 返回的 tool_calls 包含多个工具调用时:

// 并行执行所有工具调用
const toolResults = await Promise.all(
    response.tool_calls.map(toolCall =>
        executeTool(toolCall.name, toolCall.arguments)
    )
);

这就是高性能 Agent 的关键优化点——多个独立工具调用,绝不串行等待。


七、完整架构图

把上面所有知识点串起来,就是 Agent 的完整架构:

┌─────────────────────────────────────────────────────┐
│                     🧠 AI Agent                      │
│                                                      │
│   ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│   │  Memory  │  │   LLM    │  │   RAG / MCP /    │  │
│   │  记忆模块 │  │  大模型   │  │   Skills 技能    │  │
│   └────┬─────┘  └────┬─────┘  └────────┬─────────┘  │
│        │             │                 │             │
│        └─────────────┼─────────────────┘             │
│                      │                               │
│              ┌───────┴───────┐                       │
│              │  Tool System  │                       │
│              │   工具系统     │                       │
│              ├───────────────┤                       │
│              │ • read_file   │                       │
│              │ • write_file  │                       │
│              │ • web_search  │                       │
│              │ • db_query    │                       │
│              │ • code_exec   │                       │
│              │ • ...更多工具  │                       │
│              └───────────────┘                       │
│                      │                               │
│              ┌───────┴───────┐                       │
│              │ Promise.all() │  ← 并行优化           │
│              │   并行执行     │                       │
│              └───────────────┘                       │
└─────────────────────────────────────────────────────┘

八、总结与展望

8.1 核心要点回顾

要点一句话总结
🎯 Agent 本质LLM + Memory + Tool + RAG + MCP + Skills
🔧 Tool 机制description(告诉 LLM 何时用)+ schema(Zod 约束参数)+ 异步处理函数
🏷️ tool_calls{id, name, arguments} —— id 关联工具执行结果,构建完整上下文
⚡ 性能优化Promise.all() 并行执行独立工具调用,大幅减少等待时间
🏗️ 框架选型LangChain(单智能体)+ LangGraph(多智能体)+ NestJS(企业级后端)

8.2 下一步学习路线

✅ 单 Agent + Tool Calling(本文完成)
    │
    ▼
⬜ Memory 模块 —— 让 Agent 记住上下文
    │
    ▼
⬜ RAG 模块 —— 接入私有知识库
    │
    ▼
⬜ LangGraph 多智能体 —— 多个 Agent 协作
    │
    ▼
⬜ MCP 协议 —— 接入第三方工具生态
    │
    ▼
⬜ 全栈 AI Agent 产品落地 —— NestJS + LangChain 企业级实战

8.3 写在最后

Agent 其实并不复杂。LLM 本身就具备思考和规划能力——我们要做的,是用 Tool 扩展它的行动力,用 Memory 增强它的记忆力,用 RAG 赋予它私有知识。这样一个知道你的业务、能帮你干活、还能记住你说过的话的 AI——就是 Agent。

而作为开发者,我们的价值在于 Harness Engineering:把 AI 的能力用工程化的方式落地,创造真正的商业价值。