当前位置: 首页 > news >正文

拆解 LangChain:为什么说它是“胶水框架“?

🦞 一只用 AI Agent 搭副业产线的程序员


去年我用 LangChain 搭了个代码审查 Agent。写到一半我突然意识到:我明明可以直接调 OpenAI API,3 行代码就完事,为什么非要套一层 LangChain?

这个问题我问了自己三个月,终于在看源码的时候想明白了。

这篇文章就是我的答案。


项目简介

LangChain(GitHub 95k+ Stars)是目前最流行的 LLM 应用开发框架。它的核心定位不是"实现 AI 能力",而是给所有 AI 能力提供统一接口——不管底层是 OpenAI 还是 Anthropic、Pinecone 还是 Chroma,上层代码不需要改。这就是"胶水框架"的含义:它不生产能力,它连接能力。


架构全景

把 LangChain 拆开,就三层东西:

┌──────────────────────────────────────────────────┐ │ Agent 层 │ │ "让 LLM 自己决定:下一步该调哪个 Tool?" │ │ AgentExecutor → 思考 → 选 Tool → 执行 → 再思考 │ ├──────────────────────────────────────────────────┤ │ Chain 层 │ │ "把多个步骤串成一条流水线" │ │ prompt │ llm │ output_parser │ tool │ ├──────────────────────────────────────────────────┤ │ Tool 层 │ │ "把任意函数包装成 LLM 能调用的工具" │ │ 搜索引擎 · 数据库查询 · 代码执行 · 文件读写 │ ├──────────────────────────────────────────────────┤ │ 底层能力(LLM / 向量库 / 文档加载器) │ │ OpenAI · Anthropic · Chroma · Pinecone · ... │ └──────────────────────────────────────────────────┘

每一层解决一个问题。逐层拆开看。


第一层:Tool——把函数变成 LLM 认识的工具

LLM 不认识你的函数签名,它只认识一种东西:函数描述 + 参数 Schema

Tool 层干的就是这个转化。

fromlangchain.toolsimporttool@tooldefsearch_knowledge_base(query:str)->str:"""搜索团队内部知识库,返回相关文档内容。 Args: query: 搜索关键词,支持自然语言 """# 实际调用你的搜索逻辑returnkb.search(query)

加上@tool装饰器之后,LangChain 在背后做了什么?看源码:

# langchain/tools/base.py —— 简化后的核心逻辑classBaseTool:name:str# 工具名,LLM 用它来标识description:str# 工具描述,LLM 用它来理解"什么时候该用我"args_schema:Type[BaseModel]# 参数的 JSON Schemadef_run(self,**kwargs):"""子类实现具体的执行逻辑"""raiseNotImplementedError

关键设计:Tool 不关心 LLM 是谁,LLM 不关心 Tool 怎么实现。它们之间只通过name + description + JSON Schema耦合。这意味着你随时可以把 OpenAI 换成 DeepSeek,Tool 代码一行不用改。

这层抽象对应一个设计原则:依赖倒置。高层模块(Agent)不依赖低层模块(具体函数),两者都依赖抽象(Tool 接口)。


第二层:Chain——用管道把组件串起来

有了 Tool 之后,下一个问题:怎么把 prompt、LLM 调用、结果解析这几个步骤串起来?

最笨的办法是写一堆if-else和临时变量。LangChain 的答案是 LCEL(LangChain Expression Language)——用|管道符串联组件。

fromlangchain.promptsimportChatPromptTemplatefromlangchain.chat_modelsimportChatOpenAIfromlangchain.schemaimportStrOutputParser prompt=ChatPromptTemplate.from_template("用{language}语言解释:{concept}")llm=ChatOpenAI(model="gpt-4")chain=prompt|llm|StrOutputParser()# 一行调用result=chain.invoke({"language":"Go","concept":"goroutine"})

这行prompt | llm | StrOutputParser()看起来像魔法。拆开源码看:

# langchain/runnables/base.py —— 核心就在这里classRunnable:def__or__(self,other:"Runnable")->"RunnableSequence":"""管道操作符:self | other → RunnableSequence"""returnRunnableSequence(self,other)definvoke(self,input,config=None):"""同步调用"""...asyncdefainvoke(self,input,config=None):"""异步调用"""...defstream(self,input,config=None):"""流式输出"""...

RunnableSequenceinvoke()本质上就是:

classRunnableSequence:def__init__(self,*steps):self.steps=stepsdefinvoke(self,input,config=None):forstepinself.steps:input=step.invoke(input,config)returninput

每个 step 的输出变成下一个 step 的输入。就是 Unix 管道的面向对象版本。

这层抽象的价值:把"编排逻辑"和"执行逻辑"分离。你写 chain 的时候只关心数据怎么流,不关心每个组件内部怎么实现。写法从"命令式"变成了"声明式"。


第三层:Agent——让 LLM 自己决定调用顺序

Chain 的问题是:你必须提前知道步骤。但真实的场景是——用户说"帮我查一下上周的代码审查报告",你没法提前知道需要几步:先查知识库?还是直接调 Git API?还是两个都要?

Agent 层解决的就是这个:让 LLM 成为流程的决策者

fromlangchain.agentsimportAgentExecutor,create_openai_functions_agent tools=[search_knowledge_base,query_git_log,send_dingtalk_message]agent=create_openai_functions_agent(llm,tools,prompt)executor=AgentExecutor(agent=agent,tools=tools)executor.invoke({"input":"查一下 task-api 仓库上周的代码审查报告并发到钉钉"})

Agent 的执行流程是一个循环:

用户输入 → LLM 思考 → 需要调 Tool? ├─ 是 → 调 Tool → 拿到结果 → 回到"LLM 思考" └─ 否 → 输出最终答案

看核心源码:

# langchain/agents/agent.py —— AgentExecutor 的简化核心循环classAgentExecutor:def_call(self,inputs):intermediate_steps=[]whileTrue:# 让 LLM 决定下一步output=self.agent.plan(intermediate_steps,callbacks=...,**inputs,)# 如果 LLM 说"完成了",返回最终答案ifisinstance(output,AgentFinish):returnoutput.return_values# 否则 LLM 说"调这个 Tool",执行它tool_name=output.tool tool_input=output.tool_input observation=self.tools[tool_name].run(tool_input)# 把执行结果记录到中间步骤,下一轮 LLM 能看到intermediate_steps.append((output,observation))

这个while True循环就是整个 Agent 的心脏。intermediate_steps是 LLM 的"记忆"——它看到自己刚才调了什么 Tool、拿到了什么结果,才能决定下一步。


三个关键设计决策

看完三层抽象,回头看 LangChain 做的最好的三个设计决策:

决策 1:Runnable 统一接口

问题:框架里有几十种组件——LLM、Prompt、Tool、Retriever、OutputParser。每个都不同,怎么让它们能自由组合?

决策:所有组件都实现同一个Runnable接口。invoke()stream()batch()ainvoke()四种调用方式覆盖了所有场景。不管你前面接的是 LLM 还是 Retriever,对后面的组件来说都一样——上一个Runnable的输出,就是我的输入。

决策 2:管道语法|而不是 Builder 模式

问题:怎么表达"A → B → C"这样的组件链?

决策:重载__or__操作符实现|管道。对比 Builder 模式:

# Builder 模式(Java 风格)chain=ChainBuilder().add(prompt).add(llm).add(parser).build()# LCEL 管道(Python 风格)chain=prompt|llm|parser

管道语法胜在视觉上就是数据流向。从左到右,一目了然。这是 API 设计的品味问题——好的 API 让正确的写法"看起来就是对的"。

决策 3:用 Pydantic 做 Tool 的 Schema 生成

问题:怎么把 Python 函数的参数告诉 LLM?手写 JSON Schema 又丑又容易出错。

决策:用 Pydantic 的BaseModel自动生成 JSON Schema。你定义 Python 类型,框架自动转成 LLM 能理解的参数格式。类型注解既是类型检查,也是 API 文档,还是 LLM 的 function calling schema——一次定义,三处受益


核心代码拆解:__or__是怎么工作的

管道是 LangChain 最核心的语法,值得单独拆开看:

# 当你写 chain = prompt | llm | parser# Python 实际执行的是:# Step 1: temp = prompt.__or__(llm)# 返回 RunnableSequence(prompt, llm)# Step 2: temp.__or__(parser)# 返回 RunnableSequence(prompt, llm, parser)

RunnableSequence在执行时不只是简单的 for 循环。它还要处理:

1. 输入映射——前一个输出可能是个对象,后一个需要的是 dict 的某个字段:

# 只把 LLM 输出的 "text" 字段传给下一个组件chain=prompt|llm|{"summary":itemgetter("text")}|summary_parser

2. 并行执行——管道里某个环节可以并行调多个组件:

# 同时查知识库和搜索网页,两个结果合并后给 LLMchain=prompt|{"kb_result":kb_retriever,"web_result":web_search,}|llm

3. 流式传递——stream()不是等上一个组件全部输出完才传给下一个,而是逐 token 传递。这要求每个 Runnable 的stream()返回的都是迭代器,RunnableSequencestream()本质是把多个迭代器串联起来。

这三种能力加起来,让|不只是一个语法糖——它背后藏着一整套数据流编排引擎


你可以抄的作业

LangChain 的三层抽象不只适用于 AI 框架。任何需要"编排多个外部服务"的系统,都能用同样的思路:

1. 用统一接口隔离变化

你的系统如果依赖多个外部 API(短信、邮件、推送),给它们套一层统一接口。以后换供应商只改适配器,业务代码不动。LangChain 的Runnable就是你的Notifier

2. 管道优于 Builder

Python 里做链式调用,考虑重载__or__而不是写.add().add().build()。管道语法读起来像数据流向图,代码自己就是文档。

3. 类型注解驱动元数据

Pydantic 这套"用类型定义自动生成 Schema"的思路可以用在任何需要"描述接口给外部系统"的场景。比如你要做一个插件系统——让插件作者用类型注解声明参数,你的框架自动生成配置 UI。

4. 循环 + 中间状态 = 通用 Agent 模式

while True + intermediate_steps这个结构不只是给 LLM 用的。任何"不确定步骤数、每步根据上一步结果动态决策"的场景都能用——比如 CI/CD 管道的自动回滚、智能爬虫的下一页决策。


最后

LangChain 被很多人吐槽"过度封装"、“抽象泄漏”。这些批评有道理——当你用AgentExecutor跑了一个小时发现 LLM 在死循环调同一个 Tool,你会想把电脑砸了。

但抛开使用体验,它的架构设计是这个行业最好的教材之一。Runnable 接口的统一定义、LCEL 管道的声明式编排、Agent 循环的 ReAct 模式——这三个东西是Agent 框架的设计范式,LangChain 之后的框架(LlamaIndex、Semantic Kernel、Dify)都在不同程度上复用了这些范式。

看完源码再写 Agent,你就不是"调 API"了——你知道每一层在干什么,知道为什么 prompt 要这样写、Tool 要这样定义、Chain 要从这个方向串。

下一讲拆 LlamaIndex。它是怎么把"非结构化数据 → 向量索引 → 语义查询"这个流程抽象成框架的?跟 LangChain 的设计思路有什么不同?


本文拆解的 LangChain 版本:v0.3.x。源码地址:github.com/langchain-ai/langchain

http://www.gsyq.cn/news/1416248.html

相关文章:

  • AI时代开发者如何避免思维钝化:重构人机协作的认知深度
  • 团队项目 第一阶段绩效评分
  • Python剪映自动化终极指南:用代码解放你的视频剪辑工作流
  • Go语言跨平台网络服务开发:构建跨平台Web服务器
  • TVA跨语言协同将迈向统一运行时
  • 跨越天际:从智能汽车到 eVTOL 的适航与系统级开发7——飞行器级功能危害评估(FHA)与系统安全性评估(SSA)
  • 急疯!WPS兼容腾讯元宝公式的最佳方法?AI导出鸭实测后我扔掉了Pandoc
  • 【数据分析】python-pandas速查文档(2)
  • Web 红包题第二弹
  • Dism++终极指南:免费开源的Windows系统优化神器
  • 大语言模型上下文污染:成因、诊断与四层防御策略
  • 学Simulink——风光储一体化并网逆变器的能量管理策略仿真
  • 终极指南:Dell G15散热控制中心的开源替代方案完全解析
  • 告别卡顿!实测对比:Parallels Desktop vs. VMware Fusion vs. UTM,谁才是Mac上跑Win10的最佳选择?
  • 2026农用薄膜十大品牌排行榜-农用薄膜哪个牌子好-大家比精选排行榜单 - GrowthUME
  • Arduino生日音乐盒制作:从硬件选型到代码调试全解析
  • BOM 核心对象
  • 推荐1款提升办公效率的神级软件,简真是Windows神器!
  • 2026年4月高架库定做厂家有哪些,自动化立体仓库/高架库/立体仓储/智能仓库/智能仓储/立体仓库,高架库优质厂家推荐 - 品牌推荐师
  • 如何快速完成微信聊天记录备份:面向普通用户的完整指南
  • 收藏!普通人也能抓住的AI大模型应用开发机遇,高薪就在眼前!
  • 豆包视频去水印方法实测:4款工具横评推荐
  • 基于Arduino的智能植物监测系统DIY:从传感器到低功耗设计
  • P1325 雷达安装【洛谷算法习题】
  • 基于Arduino与FFT的音频频谱可视化:从原理到实现的完整指南
  • Zabbix监控初步搭建
  • 2026年5月停车场出入口设备厂家选型攻略|智慧停车采购指南 - TOP10品牌推荐榜单
  • 2026广州白云区注册公司攻略|靠谱财税代办机构TOP5科普推荐 - GrowthUME
  • 基于DLP平台的手写数字分类——CPU到深度学习处理器的加速实践
  • 如何使用Legacy iOS Kit实现旧款iOS设备降级与越狱的完整指南