当前位置: 首页 > 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.jsqmd.com/news/904952/

相关文章:

  • 基于Snowflake与AI的向量化检索系统:实现知识产权语义相似度检测
  • 市面上有哪些是真正性价比高的降AIGC网站(轻松压低AI生成疑似率)
  • Unity手游实战:用TrailRenderer和LineRenderer分别实现切水果刀痕,哪个更适合你的项目?
  • 权威发布!家居收纳品牌哪家专业? - 17329971652
  • Java协同Python与C++在TVA中的实践
  • 日照外贸网站定制开发,WaiMaoYa 外贸鸭实景展示产能与实力,精准打动海外大客户 - 外贸营销驿站
  • 2026年5月南通黄金回收哪家好?5家实测+避坑全攻略 - 天天生活分享日志
  • 从繁琐搜索到智能获取:baidupankey如何将百度网盘资源获取时间缩短95%
  • gitlab运维技巧-提取部分文件夹目录
  • 避坑指南:从Built-in管线迁移到URP后,ShaderGraph老报错怎么办?
  • DeepSeek编码能力到底行不行?用数据说话
  • AI时代开发者如何避免思维钝化:重构人机协作的认知深度
  • 团队项目 第一阶段绩效评分
  • Ets1:巨噬细胞Mek-Erk通路的“信号分选器”——介导抗炎极化并改善胰岛素抵抗
  • 河池外贸网站建设公司,WaiMaoYa 外贸鸭一对一专属运维,售后全程保驾护航 - 外贸营销驿站
  • 终极指南:如何用YOLOv8构建高性能实时视觉辅助系统
  • Python剪映自动化终极指南:用代码解放你的视频剪辑工作流
  • 导师认可的AI论文写作工具星级排名(2026 权威发布)
  • WarcraftHelper:让经典魔兽争霸3在现代电脑上重获新生的终极解决方案
  • 别再让远处贴图糊成马赛克了!Unity/UE4中Mipmap的保姆级设置与性能调优指南
  • Go语言跨平台图形编程:使用OpenGL绑定库
  • Go语言跨平台网络服务开发:构建跨平台Web服务器
  • 2026年4月市场上比较好的绕线机公司推荐,嵌线扩张一体机/线嵌一体机/下线机/大型最终整型机,绕线机品牌哪家好 - 品牌推荐师
  • 通过 curl 命令直接测试 Taotoken 接口连通性与模型响应
  • TVA跨语言协同将迈向统一运行时
  • 在职考中医执助备考推荐,我为什么选择阿虎医考 - 医考机构品牌测评专家
  • `ConversationRuntime::run_turn` 函数解析
  • 别再只盯着Delaunay了!Townscaper网格生成的‘松弛’(Relax)与‘整形’(Reshape)才是灵魂,附Unity可视化调试技巧
  • 为什么你的DeepSeek集群总在凌晨降级?揭秘GPU节点亲和性错配、NVLink带宽瓶颈与Prometheus指标盲区(附Grafana看板JSON)
  • 跨越天际:从智能汽车到 eVTOL 的适航与系统级开发7——飞行器级功能危害评估(FHA)与系统安全性评估(SSA)