LangChain:到底在工程里解决了什么问题?——模块拆解与使用边界
LangChain 这个名字已经被提了很多次,但真正落到工程里,容易出现两个极端:
一种是「把所有东西都往 LangChain 上堆」;另一种是「觉得它只是套 API 壳,不如手写」。
这一篇先不讲「十分钟上手」,而是想搞清楚几件事:LangChain 在工程里具体解决了哪些问题、它的核心模块各自负责什么,以及在哪些场景下用它合适、在哪些场景下自己撸一套更干净。
1. 如果不用 LangChain,工程里会长成什么样?
可以先从「不用 LangChain」的理想化场景想起:
- 你直接调用大模型 API(OpenAI、Anthropic、本地模型等);
- 手写 prompt 字符串,手写参数(temperature、max_tokens 等);
- 需要检索/工具调用时,再自己拼接:
- 去向量库查文档,取出 top-k 片段拼到 prompt 里;
- 按约定格式让模型输出 JSON,再自己解析。
在 Demo 或非常简单的应用里,这样完全可以。但一旦应用复杂一点,很快会堆出几类问题:
- 可组合性差
- RAG → 工具调用 → 再问一次模型 → 再写回数据库,这种多步流程全部手写,文件之间互相调用,很快变成「逻辑散落在各处」;
- 不可观测
- 每一步 prompt/输出/中间结果都需要手动打日志,否则排查问题时只能猜模型在想什么;
- 难以替换与重用
- 想把一个 prompt + post-process 流程在多个地方复用,需要不断复制粘贴;
- 换模型/换向量库/换检索策略时,调用代码和业务逻辑搅在一起,很难分层。
LangChain 做的事情,可以理解为:在「调用模型」这一件事之上,给你一套「可组合、可观测、可替换」的工程抽象。
2. 核心模块一览:LangChain 把问题拆成了哪些块?
用一个简化版的模块图来感受 LangChain 的设计重心:
- Model 层
LLM/ChatModel:统一不同厂商的大模型调用接口;
- Prompt & 输出层
PromptTemplate:把 prompt 写成模板 + 变量插值;OutputParser:把模型输出解析成结构化结果(JSON、Pydantic 模型等);
- 知识与检索层
DocumentLoader/TextSplitter:加载与切分文本;VectorStore/Retriever:向量存储与检索抽象;- 组合成各种 RAG 链;
- 工具与 Agent 层
Tool:把外部能力(HTTP 请求、DB、代码执行等)封装成「工具」;Agent:让模型根据指令选择工具、多步决策;
- 链式编排层
Runnable/ LCEL(LangChain Expression Language):- 用“管道表达式”的方式,把 model/prompt/parser/retriever/tool 这些模块拼成一条或多条链;
- 观测与运行时层
- callbacks / tracing / logging:
- 在每一步前后插入钩子,用于调试、监控、采集样本。
- callbacks / tracing / logging:
这些模块本身并不是「高深算法」,而是偏工程化的拆分:
把所有「本该分层的东西」拆开,并给它们做好组合接口。
3. PromptTemplate 与 OutputParser:从「堆字符串」到「结构化 IO」
在最基础的用法上,LangChain 先解决了两个常见小痛点:
- PromptTemplate
- 让 prompt 不再是到处散落的硬编码字符串;
- 把「不变的框架」和「每次注入的变量」分开。
- OutputParser
- 再也不用每个地方都手写
JSON.parse+ 各种防御性 if/try; - 可以定义「期望的结构」,让解析逻辑集中管理。
- 再也不用每个地方都手写
这一层带来的好处是:
- Prompt 可以独立被 review / 版本管理;
- 输出结构有统一约束,更方便后续链路(例如再调一个模型、存数据库、进监控)。
可以把这一步类比为前端里从「手写字符串 DOM」到「有组件抽象」:
LangChain 把「prompt + parse」也变成了一种「组件」,可以固定形状、在不同流程之间传递。
4. 知识与检索:RAG 管线从临时脚本变成可复用模块
在实际工程里,跟 LangChain 关系最紧密的就是 RAG(Retrieval-Augmented Generation):
- 文档加载、切块、向量化、入库这几步;
- 请求时的检索 top-k、重排序、过滤;
- 把检索结果塞进 prompt 构造上下文。
不用 LangChain 时,工程里往往是到处散落着:
- N 种 Loader(本地文件、S3、数据库……);
- N 种切分规则(按字符、按段落、按语义块);
- N 个「自己写的向量库小封装」。
LangChain 的做法是:
- 把 Loader / Splitter / VectorStore / Retriever 都抽象成统一接口;
- 再用 chain 把「检索 → 组装上下文 → 调用模型 → 解析结果」串成一个可复用的 RAG 流程。
工程上的几个直接收益是:
- 可替换:换向量库、换切分策略,改一处配置/构造就行,不需要重写业务逻辑;
- 可观测:每一步都可以插 callback,记录检索命中、最终上下文长度、模型回复等,用于排查「为什么这次答得很差」。
- 可重用:同一套 RAG 流程可以在 API 服务、批处理任务、后台工具里复用。
可以把 LangChain 理解为:把常见的 RAG 设计思路「产品化」成一组 Python/TS 模块,而不是替代设计本身。
5. 工具与 Agent:把「调用外部能力」变成一等公民
当应用不再只是「问答」而是需要真正做事时(改代码、查数据库、调 API),问题就变成:
- 怎么描述工具?
- 怎么把工具安全地暴露给模型?
- 怎么控制模型不要乱用工具或用错场景?
LangChain 在这里提供的是:
- Tool 抽象
- 每个工具都描述为:名字、描述、参数结构、执行函数;
- 底层可以是 HTTP 请求、本地函数、脚本、DB 操作等。
- Agent 抽象
- 基于 ReAct/Plan-and-Execute 等思想,让模型可以「读描述 → 选工具 → 构造参数 → 执行 → 观察结果 → 再决定下一步」。
工程上的价值不在于「LangChain 让模型更聪明」,而是:
- 把「工具列表」这件事标准化,便于审计和权限控制;
- 把「使用工具的策略」抽象成可配置/可替换的 Agent,而不是散落在业务脚本里的 if/else。
在 IDE、运维平台、自动化运行业务脚本这类场景里,这一点尤其重要:
一旦模型调用的“工具”越界,后果可能是「一键删库跑路」级别,所以必须有一个清晰的工具描述与安全层。LangChain 提供的是一个可以直接拿来用的实现模板。
6. Runnable / LCEL:把「链」变成可读、可插桩的表达式
随着模块增多,「如何组合它们」本身就成了一个问题。
如果一切都写成嵌套回调/函数调用,逻辑很快就变成难以维护的「胶水代码泥石流」。
LangChain 推出的 Runnable / LCEL(LangChain Expression Language)本质上做了两件事:
- 把「一步步调用」写成一个链式表达式
- 类似 Unix 管道 / RxJS 那种风格:
prompt | model | parser; - 复杂时可以分支、合流、map over list 等。
- 类似 Unix 管道 / RxJS 那种风格:
- 为链提供统一的「观察钩子」
- 在任意一个 Runnable 前后插 callback:
- 记录输入输出;
- 统计时延、token 消耗;
- 采样请求,用于后续评估和重放。
- 在任意一个 Runnable 前后插 callback:
这在工程上的意义是:
- 业务逻辑从「一堆散乱的函数拼接」变成「一条(或多条)可以读的流程图」;
- 调试时可以很清楚地看到:
- 当前请求在链上的哪一步;
- 每一步的 prompt 长什么样、检索结果是什么、模型回复如何。
可以把它类比为「在微服务世界里引入一个统一的 service mesh」,只不过这里 mesh 的对象是「模型调用链路」。
7. 什么时候用 LangChain,什么时候自己撸?
最后一个重要问题是边界:不是所有用大模型的地方都该上 LangChain。
可以用一个简单的判断矩阵:
- 更适合用 LangChain 的场景
- 有明显的「多步流程」:RAG + 工具调用 + 多轮 agent;
- 需要集成多种后端能力:多个向量库、多个模型供应商、多个数据源;
- 需要观测与演进:需要系统性地记录 prompt/输出、对失败样例做重放与评估。
- 适合自己撸轻量封装的场景
- 非常简单的一步问答/补全,逻辑不复杂;
- 只对接一种模型、一个向量库,未来没有复杂组合需求;
- 系统主体是现有架构,只需要在其中嵌一个「小模型调用点」。
一个比较务实的经验是:
- 把 「业务架构」和「模型调用层」 分开是更重要的原则;
- 至于是用 LangChain 实现模型调用层,还是自己写一套更轻量的 pipeline,可以按上面这几个维度来权衡。
8. 小结:把 LangChain 放回「工程工具箱」的正确位置
可以用几句话给这篇收个尾:
- LangChain 的价值不在于「比直接调用 API 更酷」,而在于:它把 prompt、解析、检索、工具调用、多步链路这些分散的工程问题,统一抽象成一批可组合、可观测的模块。
- 它很适合用在:需要多步 RAG、复杂工具调用、跨模型/向量库组合、以及需要系统性调优与监控的大模型应用里。
- 它不强迫你在所有地方都用它:对于简单、局部的模型调用点,一个干净的手写函数往往更简单。
后面如果要继续展开,可以在这篇的基础上拆开几条线:
比如专门写一篇「LangChain 在代码仓库 RAG 里的玩法与坑点」、一篇「工具调用与 Agent 的安全边界设计」,再结合 IDE 场景中的 AI 助手设计,把 LangChain 当成「落地这些架构想法的一种实现方案」来讲。