直接调 OpenAI API 就够了,为什么还要 LangChain?

简单场景确实不需要。但当应用从「一个 Prompt」长成「可用的 AI 系统」,复杂度会集中在组件协作上:Prompt 管理、结构化输出、Tool 接入、RAG、多步编排与调试。

LangChain 不替代 LLM,而是把这些组件串成可组合、可维护的流水线


一、没有 LangChain 的世界

最简单的 LLM 调用

以 Node.js 为例:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const response = await client.chat.completions.create({
  model: "gpt-4.1",
  messages: [
    {
      role: "user",
      content: "给登录页加一个忘记密码入口,帮我写个 React 组件",
    },
  ],
});

console.log(response.choices[0].message.content);

非常简单:一次请求,一次响应,模型直接吐出代码片段,没有 JSON 解析、没有查仓库、也没有对照内部规范。

什么时候开始变复杂

产品提出完整需求:

做一个「智能代码助手」:用户用自然语言描述功能(比如「给登录页加一个忘记密码入口」),系统自动生成代码,并结合该项目的 GitHub 仓库现状和内部开发规范,输出一份结构化的技术方案报告。

拆解下来,后端要完成五件事:

步骤 做什么 为什么复杂
1. 生成代码 根据用户描述调用 LLM 产出代码 需要维护 Prompt 模板、上下文注入
2. 输出 JSON 把结果解析成 { files, summary, risks } 等固定字段 LLM 输出不稳定,需要 Parser
3. 查 GitHub 调用 API 获取仓库结构、已有文件、PR 规范 需要 Tool 封装和鉴权
4. 检索知识库 从向量库拉取内部编码规范、架构文档 需要 RAG 流水线
5. 生成报告 把代码、仓库信息、规范文档合并,输出最终报告 多源数据合并 + 二次 LLM 调用

用户输入功能描述

LLM 生成代码

解析为 JSON 结构

GitHub API 查仓库信息

RAG 检索内部规范文档

合并数据,生成最终报告

原本只是一次 chat.completions.create,现在要串起五条链路。你的代码很快会变成:

const requirement = "给登录页加一个忘记密码入口";

const prompt = buildCodeGenPrompt(requirement);
const llmResult = await callLLM(prompt);
const codeDraft = parseCodeResult(llmResult);
const repoInfo = await fetchGitHubRepo("my-org/web-app");
const standards = await vectorSearch("前端表单组件开发规范");
const report = await generateReport({ requirement, codeDraft, repoInfo, standards });

真正复杂的,已经不是调用模型,而是组织这些组件——入参出参各不相同,改一处 Prompt 可能影响 Parser 和下游 Tool。

这正是 LangChain 要解决的问题。第二至五节将沿上表五个环节依次展开,说明 LangChain 在每一层提供的抽象与实现方式。


二、Runnable 与 Chain —— 步骤 1:生成代码

对应环节:步骤 1 · 生成代码 | 核心抽象:Runnable、pipe、Chain

LangChain 最核心的思想只有一句话:Everything is Runnable——所有组件都可以执行,都可以组合。

统一接口:invoke()

Prompt

Model

Parser

Tool

Retriever

Runnable:统一插头

回到第一节的胶水代码,五个步骤其实是五种不同的调用方式——入参、出参、错误处理各写各的。Runnable 把它们统一成同一种插头:

方法 用途
invoke(input) 单次调用
batch(inputs) 批量调用
stream(input) 流式输出
pipe(next) 串联下一个 Runnable

以步骤 1 为例,三个 Runnable 职责不同,调用方式相同:

① Prompt——把 { requirement } 格式化成 Message[]

const prompt = ChatPromptTemplate.fromTemplate(
  `根据以下需求生成代码:\n{requirement}`
);
const messages = await prompt.invoke({
  requirement: "给登录页加一个忘记密码入口",
});

② Model——把 Message[] 发给 LLM,拿回 AIMessage

const model = new ChatOpenAI({ model: "gpt-4.1" });
const aiMessage = await model.invoke(messages);

③ Parser——把 AIMessage 转成 string

const parser = new StringOutputParser();
const codeText = await parser.invoke(aiMessage);

三步串起来,数据形态的变化是:

{ requirement }

Prompt.invoke()

Message

Model.invoke()

AIMessage

Parser.invoke()

string

Chain:pipe 把三步合成一次调用

手写三步需要手动传参;用 pipe 等价于:

const codeGenChain = prompt.pipe(model).pipe(parser);

const codeText = await codeGenChain.invoke({
  requirement: "给登录页加一个忘记密码入口",
});

一次 invoke,框架自动完成 Prompt → Model → Parser 的流转——即 Chain,也是 LangChain 对步骤 1 的实现方式。

input: { requirement }

codeGenChain.invoke

output: 代码文本

步骤 1 可由 Chain 完整覆盖。步骤 2 则引入新的约束:输出须为 { files, summary, risks } 结构,而非纯文本——Model 与 Parser 的用法均需调整。


三、结构化输出 —— 步骤 2:结构化 JSON

对应环节:步骤 2 · 输出 JSON | 核心抽象:Schema、withStructuredOutput

步骤 1 的 Chain 输出的是字符串,下游步骤 3~5 需要的是结构化对象。如果只靠 Prompt 让模型返回 JSON:

const result = await model.invoke(
  "给登录页加忘记密码入口,返回 files、summary、risks 的 JSON"
);

同一条 Prompt,模型可能返回 Markdown 代码块、字段名对不上的 JSON、或把 risks 写进 summary——fetchGitHubRepogenerateReport 无法稳定消费。

LangChain 用 Schema 约束输出(底层依赖 JSON Mode),目的是固定 LLM 的回复格式

import { z } from "zod";

const codeDraftSchema = z.object({
  files: z.array(z.object({ path: z.string(), content: z.string() })),
  summary: z.string(),
  risks: z.array(z.string()),
});

const structuredModel = model.withStructuredOutput(codeDraftSchema);

const codeDraft = await structuredModel.invoke(
  "给登录页加一个忘记密码入口"
);
{
  "files": [
    { "path": "src/pages/Login.tsx", "content": "..." },
    { "path": "src/api/auth.ts", "content": "..." }
  ],
  "summary": "新增忘记密码入口,调用 /auth/reset-password 接口",
  "risks": ["需确认现有 auth 中间件是否支持", "移动端样式需单独适配"]
}

LLM 输出

Schema 校验

codeDraft 对象

→ 步骤 3 / 4 / 5

codeDraft.files 可以交给 GitHub diff 对比,codeDraft.risks 可以注入最终报告——解析逻辑从业务代码挪到了框架层

步骤 2 通过 Schema 约束得以实现。步骤 3 的需求本质不同:须由 LLM 决策是否、何时调用外部 API,而非仅格式化输出。


四、Tool 与 Tool Calling —— 步骤 3:GitHub 集成

对应环节:步骤 3 · 查 GitHub | 核心抽象tool()bindTools、执行循环

第一节的写法是硬编码:

const repoInfo = await fetchGitHubRepo("my-org/web-app"); // 写死仓库,写死调用时机

Tool Calling 换了一种方式——何时查、查哪个仓库,由模型根据用户问题决定

// 用户问 "分析 web-app" → 模型返回 tool_call
// 用户问 "这段代码什么意思" → 模型直接回答,不调 Tool
const modelWithTools = model.bindTools([githubTool]);

Tool Calling 是 OpenAI / LangChain 的常用叫法;Anthropic 称 Tool Use,早期也称 Function Calling——指的都是同一件事。

做法 B · LangChain Tool Calling

tool_calls

用户问题

LLM 决策

运行时执行

GitHub API

LLM 继续推理

做法 A · 第一节写法

固定调用

业务代码

GitHub API

定义 Tool(对 GitHub API 的 Runnable 封装):

import { tool } from "@langchain/core/tools";

const githubTool = tool(
  async ({ owner, repo }) => {
    const tree = await fetchRepoTree(owner, repo);
    return JSON.stringify(tree);
  },
  {
    name: "fetch_github_repo",
    description: "查询 GitHub 仓库的目录结构和协作信息",
    schema: z.object({ owner: z.string(), repo: z.string() }),
  }
);

用户问:「分析 my-org/web-app,为忘记密码功能生成改造方案」——模型返回 Tool Call,运行时执行后再推理:

GitHub Tool 运行时 LLM 用户 GitHub Tool 运行时 LLM 用户 分析 web-app,生成忘记密码方案 tool_calls(owner=my-org, repo=web-app) 执行 Tool 仓库 tree、auth 相关文件 结果塞回上下文 结合仓库现状输出方案

模型负责决策,Tool 负责执行,运行时负责闭环。当 Tool 不止一个、调用顺序无法预先写死时,就进入 Agent 模式——LangChain 的 createReactAgent 会把上述循环自动迭代。

步骤 3 由 Tool Calling 机制承接。步骤 4 须检索模型训练数据之外的私有知识库;步骤 5 则将前述环节的产物合并为最终报告。


五、RAG 与报告组装 —— 步骤 4 & 5:知识检索与报告生成

对应环节:步骤 4 · 检索知识库 / 步骤 5 · 生成报告 | 核心抽象:Retriever、RAG Chain、reportChain

模型知道 React 怎么写,但不知道你们的组件命名规范、PR 模板、安全红线。步骤 4 通过 RAG 把私有文档注入上下文:

查询:前端表单组件规范

Embedding

向量检索

Top-K 规范文档

与 codeDraft 拼接 Prompt

LLM 生成

合规性说明

Retriever 本身也是 Runnable:

const retriever = vectorStore.asRetriever();

const docs = await retriever.invoke("前端表单组件命名与样式规范");
// 命中:《前端编码规范 v3》《Auth 模块安全 checklist》

RAG Chain 把检索和生成 pipe 在一起:

const ragChain = RunnableSequence.from([
  { context: retriever, question: new RunnablePassthrough() },
  ragPrompt,
  model,
  parser,
]);

const complianceNotes = await ragChain.invoke(
  "给登录页加忘记密码入口,需遵循哪些内部规范?"
);

步骤 5:多源数据合并与报告生成

至此,各环节的中间产物均已就绪——codeDraft(第三节)、repoInfo(第四节)、complianceNotes(第五节)。步骤 5 通过 reportChain 将多源数据合并,经二次 LLM 调用生成最终报告:

const report = await reportChain.invoke({
  requirement,
  codeDraft,       // 步骤 2
  repoInfo,        // 步骤 3
  complianceNotes, // 步骤 4
});

步骤 2 · codeDraft

reportChain

步骤 3 · repoInfo

步骤 4 · complianceNotes

步骤 5 · 最终报告

LangChain 还提供了 Document Loader、Text Splitter、VectorStore 等配套抽象——RAG 流水线同样纳入 Runnable 组合体系。

至此,「智能代码助手」五步链路的组件层已全部可由 LangChain 覆盖。 当流程规则超出线性 Pipeline 的能力边界时,则需要引入编排层。


六、Chain 的边界:何时需要编排层

对应环节:五步链路之上 · 流程编排 | 能力边界:分支、循环、共享状态

前五步如果固定顺序、从不回退,Chain 完全够用:

生成代码

解析 JSON

查 GitHub

RAG 检索

生成报告

但产品很快会加规则:

循环——Review 不通过则重新生成:

Pass

Fail

Generate

Review

生成报告

END

条件分支——RAG 命中「禁止修改 Auth 核心模块」则直接终止:

无红线

命中红线

RAG 检索

Generate

END

生成报告

并行——GitHub 查询与 RAG 检索同时进行:

Generate codeDraft

查 GitHub

RAG 检索

Merge

生成报告

Chain 本质是 Pipeline,而非 State Machine——缺少共享状态、条件路由、循环回退与 Checkpoint。这属于编排层的能力范畴,需由 LangGraph 承接。


七、LangGraph:LangChain 的编排层

对应环节:Review 回退、红线拦截、断点恢复 | 架构关系:LangGraph 负责编排,LangChain 提供组件

LangChain 覆盖 Prompt / Model / Parser / Tool / RAG 等组件层能力;LangGraph 负责流程编排与状态管理

能力 在「智能代码助手」中
状态管理 requirementcodeDraftrepoInfo 等共享于全流程
条件路由 RAG 命中红线 → 终止;Review 通过 → 生成报告
循环执行 Review 失败 → 带审查意见回到 Generate
Checkpoint 进程崩溃后从断点恢复
Human-in-the-Loop 修改 Auth 核心模块前等待 Tech Lead 确认

把 Review 回退落到 LangGraph:

Pass

Fail

用户提交需求

Generate codeDraft

Review 规范审查

Generate 最终报告

END

每个 Node 内部仍使用 LangChain 的 Chain、Model、Tool 实现具体逻辑——LangGraph 管流程,LangChain 管组件,二者同属一个生态,呈上下层协作关系。

  • LangChain:一个组件库,提供大量现成的“零件”(如模型接口、提示词模板、检索器等)。它通过 pipe (LangChain 表达式语言) 将这些零件高效地组装成线性流程,适合快速构建和简单任务。
  • LangGraph:一个负责状态管理和复杂编排的“运行时”。它不取代 LangChain,而是利用其组件,提供更强大的图结构(Graph)模型来构建应用
维度 LangChain (上层建筑) LangGraph (底层引擎)
核心抽象 链 (Chain) / 线性流程 图 (Graph) / 状态机
控制流 主要由静态边定义的线性流水线。即便有工具调用,其逻辑也基本是顺序性的。 灵活的图结构,原生支持条件分支、循环、并行,并能处理重试机制。这对于实现复杂决策和递归任务至关重要。
状态管理 相对简单,通过Memory组件在步骤间传递上下文,但较难支持复杂的状态回溯。 内置强大的持久化状态。状态在一个共享的数据结构中流转,并且系统支持保存状态快照(Checkpoint)。
适用场景 简单、线性的一次性任务。例如基础的 RAG(检索增强生成)、文档摘要等。 复杂、有状态的系统。例如需要多轮交互的智能体(Agent)、长流程业务自动化,以及必须支持人工介入的复杂流程

八、LangChain 在 Agent 生态中的位置

基础设施

组件层 · LangChain 的核心定位

编排层

LangGraph

LangChain

Model API

Tool / MCP

RAG / VectorStore

框架 / 协议 职责 与本文的关系
LangChain Prompt、Model、Parser、Tool、RAG §二~§五 的主体
LangGraph 状态图、条件路由、循环、Checkpoint §六~§七 的延伸
OpenAI Agents SDK OpenAI 生态的 Agent 运行时 与 LangGraph 定位类似
MCP Tool 的标准化接入协议 解决 Tool 跨服务复用

九、什么时候该用

你的「智能代码助手」走到哪一步 建议
单次问答、一个 Prompt 搞定 直接调 API
固定顺序的多步 Prompt(分析 → 设计 → 生成) LangChain Chain
需要 JSON 输出 + GitHub Tool + 内部规范 RAG(前五步) LangChain 组件层
Review 回退、红线拦截、断点恢复 加 LangGraph
已有成熟编排,只需 LLM 封装 只用 LangChain 的 Model / Parser

判断标准:如果系统只有 1-2 个固定步骤,直接调 API;如果步骤超过 3 个且需要频繁切换模型/Tool/RAG,用 LangChain 组件;如果出现分支、回退、人工审批,上 LangGraph。但每一步引入都要评估:框架省下的胶水代码,是否 worth 它带来的抽象复杂度。


总结

LangChain 不是模型框架,而是 AI 应用开发框架。

它解决的不是「怎么调用 LLM」,而是第一节五条链路里的组件协作问题:

Prompt 管理

Chain 组合

结构化输出

Tool Calling

RAG

报告组装

LangGraph 编排

回到开篇的问题——为什么不直接调 API?

因为「智能代码助手」这样的系统,难点不在 LLM 调用,而在 Prompt、Parser、Tool、RAG 如何统一接口、自由组合。LangChain 用 Runnable 和 Chain 提供了这套组件层;流程变复杂时,LangGraph 在同一生态内接手编排。

LangChain 不是终点,但几乎是所有 Agent 系统绕不开的起点。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐