LangChain 到底帮你干了什么活?——Agent 开发框架完全解读

LangChain 到底帮你干了什么活?------Agent 开发框架完全解读

摘要:Agent 开发很火,但手写一个 Agent 需要处理循环调度、记忆管理、输出解析等大量底层逻辑。LangChain 把这些脏活累活全包了。本文从 Agent 的核心组件出发,拆解 LangChain 如何标准化 Tool 编写、封装 ReAct 循环,并深入异步并发优化多 Tool 调用性能。

📑 目录

  • 为什么需要 Agent?LLM 的五大缺陷
  • Agent 是什么?一个"能干活"的 LLM
  • 手写 Agent 有多麻烦?
  • LangChain:Agent 开发的"标准化引擎"
  • Tool 编写实战:用 LangChain 封装文件读取工具
  • Tool 的返回格式与 ID 关联机制
  • 异步并发:Agent 高性能的关键
  • Promise 状态与并发控制
  • 一点总结
  • 互动讨论

为什么需要 Agent?LLM 的五大缺陷

大模型很强,但它有几个天生的缺陷,如果不解决,它永远只能当"聊天机器人",干不了正事:

LLM 缺陷 问题描述
无状态 每次对话结束就忘光,记不住项目规范、用户偏好
无法操作外部世界 只能生成文字,不能读文件、不能执行命令、不能操作浏览器
预训练知识不含私有数据 公司的内部文档、产品的技术架构,LLM 一概不知
不知道最新信息 训练数据有截止日期,最新发生的事情它不知道
复杂任务无法自动完成 只能一问一答,不能自主规划多步骤任务

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

Agent 就是围绕以上问题,给 LLM 加上记忆模块、工具调用能力、RAG、MCP、Skills 等组件,让 LLM 从一个"只会说话"的模型,进化成一个"能干活"的智能体。

Agent 是什么?一个"能干活"的 LLM

Agent(智能体)的工作流程是这样的:

text

markdown 复制代码
用户以 Prompt 形式提出一个任务(复杂的)
    ↓
LLM 进行规划/推理(Planning/Reasoning)
    ↓
要不要加载 Memory?
    ↓
需不需要调用工具?(可分步骤调用多个工具)
    ↓
要不要查询 RAG?
    ↓
生成 Response 返回给用户

在这个过程中,LLM 需要:

  1. 理解用户的复杂任务
  2. 拆解成可执行的步骤
  3. 决定调用哪些工具、按什么顺序调用
  4. 根据工具返回的结果继续推理
  5. 最终生成答案

看起来很简单,但手写一个 Agent 非常麻烦

手写 Agent 有多麻烦?

如果不用任何框架,自己实现一个 Agent,你需要处理:

需要手写的内容 复杂度
手写 while 循环管理"思考→行动→观察"迭代 容易出 bug,难以控制迭代次数
手动拼接 System Prompt,包含工具描述 Prompt 模板需要反复调优
用正则从 LLM 文本中提取工具调用指令 脆弱,LLM 输出格式稍有偏差就解析失败
手动管理对话历史,防止上下文溢出 记忆管理复杂
处理多个 Tool 的并发调用 需要手动管理 Promise 和错误处理

这就是 LangChain 的用武之地。

LangChain:Agent 开发的"标准化引擎"

LangChain 是一个 LLM 应用开发框架,比 OpenAI 的 SDK 更早诞生。它的核心价值不是"简化代码",而是标准化 Agent 的开发流程

LangChain 封装的复杂内容

原生实现(自己造轮子) LangChain 提供的标准化组件
手写循环管理迭代 AgentExecutor 内置标准循环,自动处理最大迭代次数、提前终止
手动拼接 System Prompt 自动生成经过验证的 Prompt 模板
正则提取工具调用 自带 OutputParser,精准解析成结构化对象
手动管理对话历史 内置多种记忆机制(BufferMemory / SummaryMemory
手动处理多 Tool 并发 配合 Promise.all 轻松实现并行调用

LangChain 的兼容性

LangChain 提供了兼容各家大模型的抽象层。无论你用 OpenAI、DeepSeek、Claude 还是其他模型,都可以用同样的 API:

javascript

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

const model = new ChatOpenAI({
    modelName: process.env.DEEPSEEK_MODEL_FLASH,
    apiKey: process.env.DEEPSEEK_API_KEY,
    configuration: {
        baseURL: process.env.DEEPSEEK_API_BASE_URL,
    }
});

const response = await model.invoke('简单介绍一下足球比赛的规则');
console.log(response.content);

你只需要换一个 modelNamebaseURL,其他代码完全不用改。

Tool 编写实战:用 LangChain 封装文件读取工具

LangChain 把 Tool 的编写抽象为两个紧密相连的部分,代码可读性更强,编写效率也更高。

javascript

javascript 复制代码
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
import { z } from 'zod';
import fs from 'node:fs/promises';

// 1. 实例化模型
const model = new ChatOpenAI({
    modelName: process.env.DEEPSEEK_MODEL_FLASH,
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,
    configuration: {
        baseURL: process.env.DEEPSEEK_API_BASE_URL,
    }
});

// 2. 用 LangChain 的 tool() 函数封装
const readFileTool = tool(
    async ({ FilePath }) => {   // ① 处理函数:实际干活的
        const content = await fs.readFile(FilePath, 'utf-8');
        console.log(`[工具调用] read_file(${FilePath}) 成功读取 ${content.length}字节`);
        return content;
    },
    {                           // ② 描述对象:告诉 LLM 这个工具是干嘛的
        name: 'read_file',
        description: '此工具用来读取文件内容,当用户要求读取文件、查看代码、分析文件内容时,调用此工具。',
        schema: z.object({
            FilePath: z.string().describe('需要读取的文件的文件地址')
        })
    }
);

// 3. 多个工具打包给 LLM
const tools = [readFileTool];

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

两个部分缺一不可

  • 处理函数:实际执行业务逻辑,读取文件、查询数据库、调用 API。
  • 描述对象 :包含 namedescriptionschema,让 LLM 知道这个工具能做什么、需要什么参数。

LangChain 内部会自动将 Zod Schema 转换成 LLM 能理解的 JSON Schema,你不需要手写冗长的 JSON 格式。

Tool 的返回格式与 ID 关联机制

当 LLM 决定调用工具时,它会返回一个 tool_calls 数组:

json

bash 复制代码
{
  "tool_calls": [
    {
      "id": "call_abc123",
      "function": {
        "name": "read_file",
        "arguments": "{"FilePath": "./tool.mjs"}"
      }
    }
  ]
}

多个工具调用时,每个调用都有一个唯一的 id。由于工具函数是异步的,执行顺序和返回顺序可能不一致,必须通过 id 来关联请求和响应,才能组成完整、正确的上下文。

javascript

php 复制代码
// 将 LLM 的响应加入对话历史
messages.push(response);

// 执行工具调用
for (const toolCall of response.tool_calls) {
    const result = await executeTool(toolCall.function.name, toolCall.function.arguments);
    // 用 tool_call_id 关联结果
    messages.push(new ToolMessage({
        content: result,
        tool_call_id: toolCall.id
    }));
}

只有这样,LLM 才能在后续对话中知道"哪个结果对应哪个请求",从而正确地将返回结果通过对话告知用户。

异步并发:Agent 高性能的关键

Agent 任务复杂,可能需要调用多个 Tool,或同一个 Tool 多次。如果串行执行,总耗时是所有 Tool 耗时之和。

顺序执行(串行)

javascript

csharp 复制代码
const weather = await getWeather(); // 等待 2s
const tweets = await getTweets();   // 再等待 0.5s
// 总耗时:2.5s

并发执行(并行)

javascript

scss 复制代码
const [weather, tweets] = await Promise.all([
    getWeather(), // 发起请求,不等待
    getTweets()   // 立即发起另一个请求
]);
// 总耗时:约 2s(最慢的那个)

在 Agent 中的实际应用

当 Agent 需要调用多个互不影响、无依赖关系 的 Tool 时,使用 Promise.all 能显著提升响应速度:

javascript

javascript 复制代码
// Agent 需要同时读取多个文件(互不依赖)
async function readMultipleFiles(paths) {
    const contents = await Promise.all(
        paths.map(path => readFileTool({ FilePath: path }))
    );
    return contents; // 顺序与 paths 一致
}

💡 注意Promise.all 是"一荣俱荣,一损俱损"------任何一个 Promise 失败,整体都会失败。如果某个 Tool 偶尔会挂掉,应使用 Promise.allSettled,它会等待所有 Tool 完成(无论成功或失败)。

Promise 状态与并发控制

理解 Promise 的三个状态,是理解异步并发的关键:

状态 含义 触发时机
pending(待定) 操作尚未完成 Promise 刚创建,请求已发出但未回复
fulfilled(已成功) 操作成功完成 触发 .then()await 后的下一行
rejected(已失败) 操作失败 网络断开、404、代码报错,触发 .catch()

状态不可逆 :一旦从 pending 变为 fulfilledrejected,就永久固定。这就是"承诺"(Promise)的含义------一旦兑现或失败,不能再改变。

并发执行的底层原理

JavaScript 是单线程,但 Node.js 底层通过 libuv 实现"宏观并发":

  • 网络 I/Ofetchhttp.request):利用操作系统内核事件轮询(Linux: epoll / Windows: IOCP
  • 文件 I/Ofs.readFile):利用 C++ 线程池(默认 4 个线程)在后台读取

主线程发起 I/O 后立即交给 libuv,自己继续执行。libuv 拿到结果后通过事件循环推回主线程。这就是 Promise.all 能实现并行等待的根本原因。

async/await 只是写法上的语法糖,让异步代码看起来像同步,并没有改变其非阻塞本质。

一点总结

通过这次学习,我理解了:

  1. Agent 解决了 LLM 的五大缺陷:无状态、无法操作外部世界、无私有知识、无最新信息、无法自动完成复杂任务。
  2. Agent 的工作流程:规划/推理 → 加载 Memory → 调用 Tool → 查询 RAG → 生成 Response。
  3. LangChain 是 Agent 开发的标准化引擎:封装了循环调度、记忆管理、输出解析等复杂逻辑,并提供兼容各家大模型的抽象层。
  4. LangChain 的 Tool 抽象:处理函数 + 描述对象(name、description、schema),代码可读性强,编写效率高。
  5. tool_calls 的 ID 关联机制 :异步调用多 Tool 时,通过 id 关联请求和响应,保证上下文完整。
  6. Promise.all 是 Agent 性能优化的利器:互不依赖的 Tool 调用可以并行执行,大幅缩短响应时间。

互动讨论

  1. 手写 Agent 时,最让你头疼的是哪个部分? 循环控制、Prompt 拼接还是输出解析?
  2. LangChain 的 bindTools() 和直接在 ChatOpenAI 构造器中传入 tools 有什么区别?
  3. Promise.all 的"一荣俱荣"特性在 Agent 开发中有什么风险? 如何规避?
  4. 如果你要设计一个"自动写代码并运行测试"的 Agent,需要哪些 Tool? 它们的调用顺序应该如何编排?
  5. tool_callsid 为什么能保证异步调用的顺序关联? 如果不用 id 会有什么问题?

📌 一点心得:LangChain 的核心价值不是"简化代码",而是"标准化流程"。它把 Agent 开发中重复、繁琐的底层逻辑封装好,让你专注于业务逻辑和 Tool 的实现。

相关推荐
用户2930750976691 小时前
拆解 AI Agent:用 LangChain 构建你的第一个智能工具
agent
Darling噜啦啦1 小时前
从零手写 AI Agent:LLM + Tool + LangChain,打造能干活的大模型
langchain·llm·agent
_冷眸_1 小时前
用 OpenAI SDK 统一调用 Claude、Gemini、DeepSeek:聊聊 AI API 聚合网关的实践
人工智能·ai·llm·agent
梦想4811 小时前
三次迭代,把一个本地检索引擎的排序准确率从 77.8% 做到 100%
agent
文言一心2 小时前
业务数据对话查询智能体系统
langchain·agent
武子康2 小时前
调查研究-217 Fortress:当浏览器 Agent 开始被网站识别,开源 Chromium 反拦截引擎来了
人工智能·爬虫·agent
新知图书2 小时前
多模态智能体开发的核心挑战与解决方案
人工智能·agent·多模态·ai agent·智能体·langgraph
gis分享者2 小时前
easy-agent 轻量级 Agent 组件深度评测
agent·测评·easy-agent
he___H2 小时前
基于LCEL的联想
开发语言·python·langchain