Day 01 / 共 20 天 · 阶段1 全景与 LCEL
项目全景与分包:一个仓库里住着五户人家
读任何大项目源码,第一天都不该急着钻代码,而是先搞清"门牌号":这个 monorepo 里有哪几个包、pip 装的 langchain 对应仓库里哪个目录、langchain_core 和 langchain_classic 又是什么关系。今天用 ls + pyproject.toml + README 三样最朴素的证据,把整张地图钉死。以后 19 天不管读到哪个文件,你都能一秒说出它属于哪户人家。
📍 你在 20 天里的位置(阶段1:全景与 LCEL · D01-04)
D01 项目全景→
D02 第一个链→
D03 Runnable/LCEL→
D04 invoke 旅程→
S2 模型/消息/Prompt→
S3 数据与 RAG→
S4 工具/Agent→
S5 进阶收官
💡 先用两个类比兜住今天
类比一:LangChain 这个仓库像一个小区(monorepo),
libs/ 下的每个目录是一栋楼,每栋楼有自己的门牌(pip 包名)和户口本(pyproject.toml)——楼名(目录名)和门牌(包名)经常不一样,比如 libs/langchain_v1 这栋楼的门牌恰恰是 langchain。类比二:五户人家的关系像一个家族——langchain-core 是家里的老宅地基(谁都建在它上面,它不依赖任何兄弟);langchain(v1)是现在住人的新房;langchain-classic 是老房子(旧家具都还在,留给没搬家的老住户);text-splitters 是工具间;partners/ 是一排客房,openai、anthropic 这些外来厂商各住一间。L01
痛点:pip install langchain,装的到底是哪坨代码?
🤔 痛点你打开 LangChain 仓库想读源码,结果懵了:根目录下没有
langchain/ 文件夹,只有一个 libs/;点进去发现 core、langchain、langchain_v1、text-splitters、partners……一堆目录。更迷惑的是:网上教程有的写 from langchain.chains import LLMChain,有的写 from langchain_core.runnables import ...,有的写 from langchain.agents import create_agent——同一个词 "langchain",指的居然不是同一坨代码。不先分清门牌,读源码就像在陌生小区里乱敲门。💡 本质:一次大重构留下的"新房 + 老房"格局LangChain 1.0 做过一次大手术:把最核心的抽象沉淀进
langchain-core(地基),把新版精简主包放进 libs/langchain_v1(但 pip 包名还叫 langchain,对用户无感),把 0.x 时代堆积的几百个模块整体搬进 langchain-classic(老房),厂商代码全部拆到独立的 partners/ 小包。所以"看目录名"会骗你,"看 pyproject.toml 里的 name"才是真门牌——这就是今天 L04 要做的事。L02
README:官方一句话怎么介绍自己(真源码第 1 段)
先看仓库根目录的 README.md,官方定位 + 五行代码的 Quickstart(README.md:24 与 README.md:32-38):
# README.md:24(原文,翻译在下面)
# "LangChain is a framework for building agents and LLM-powered applications.
# It helps you chain together interoperable components and third-party
# integrations to simplify AI application development ..."
# README.md:32 安装就一行
uv add langchain
# README.md:36-38 官方 Quickstart:三行跑起来
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-5.5")
result = model.invoke("Hello, world!")
framework for agents官方定位第一个词已经是 agents(智能体),不再只是"链"。它强调"把可互换的组件串(chain)起来"——这个"串"字就是 D03 要读的 LCEL。uv add langchain用户只装一个叫 langchain 的包。但下面 L04 你会看到,这个包会自动把 langchain-core、langchain-text-splitters 等兄弟包一起拖下来。from langchain.chat_models import init_chat_model注意 import 的是 langchain——它对应仓库里的 libs/langchain_v1/langchain/ 目录(不是 libs/langchain/!L03 讲这个坑)。init_chat_model(...)一行拿到一个"能 invoke 的模型"。字符串 "openai:gpt-5.5" 里冒号前是厂商、后是型号——D02 我们会钻进这个函数看它怎么按厂商分发。大白话官方自我介绍浓缩成一句:"我是做 Agent 和 LLM 应用的框架,我的绝活是把积木串起来。"而 Quickstart 三行代码里已经藏了两大主角:
init_chat_model(模型统一入口,D02/D05 的主角)和 .invoke()(Runnable 统一接口,D03/D04 的主角)。今天只需混个脸熟。L03
libs/ 五大包逐个认门牌(真源码第 2 段)
在仓库根目录跑 ls libs/,看到的就是这几栋楼(真实目录列表):
$ ls /Users/bitmart/work/codes/github/AI_WORK/langchain/libs/
core # → langchain_core:核心抽象(地基)
langchain # → langchain_classic:旧版 0.x 兼容包(老房)
langchain_v1 # → langchain:新版 v1 主包(新房,pip 装的就是它)
text-splitters # → langchain_text_splitters:文本切分(工具间)
partners # → 各厂商适配小包(客房)
standard-tests # 给厂商包用的统一测试套件
model-profiles # 各模型能力档案(如是否支持工具调用)
$ ls libs/partners/
anthropic chroma deepseek exa fireworks groq huggingface
mistralai nomic ollama openai openrouter perplexity qdrant xai
| 仓库目录(楼) | Python 包(进门后的姓) | pip 包名(门牌) | 干什么 |
|---|---|---|---|
libs/core/ | langchain_core | langchain-core | 一切抽象的地基:Runnable、消息、Prompt、输出解析、工具、回调…… |
libs/langchain_v1/ | langchain | langchain | 新版 v1 主包:agents/chat_models/messages/tools,很薄,主要是"好用的入口" |
libs/langchain/ | langchain_classic | langchain-classic | 0.x 时代的全部家当:chains/memory/一堆 loader……只为老代码兼容 |
libs/text-splitters/ | langchain_text_splitters | langchain-text-splitters | 文本切分(D10 主角) |
libs/partners/* | langchain_openai 等 | langchain-openai 等 | 每家厂商一个独立小包,实现 core 定义的接口 |
⚠️ 最大的坑:libs/langchain ≠ pip 的 langchain目录
libs/langchain/ 里住的是 langchain_classic(老房);pip 装的 langchain 新包住在 libs/langchain_v1/。目录名是历史遗留,门牌才作数。以后你 grep 源码时:想找新版 create_agent 去 libs/langchain_v1/langchain/agents/;想找古董 LLMChain 去 libs/langchain/langchain_classic/chains/。大白话记住"进门先看户口本(pyproject),别信楼名(目录名)"。而
langchain_v1/langchain/__init__.py 的开头也自报家门(libs/langchain_v1/langchain/__init__.py:1-3):"""Main entrypoint into LangChain.""" + __version__ = "1.3.12"——白纸黑字,"LangChain 的正门入口"就是这栋楼。L04
pyproject 里的依赖真相:谁建在谁上面(真源码第 3 段)
每栋楼的户口本 pyproject.toml 里,name 是门牌、dependencies 是"我建在谁上面"。四本户口本的关键行:
# libs/core/pyproject.toml:6,24 —— 地基自己不依赖任何兄弟包
name = "langchain-core"
version = "1.4.9"
# 它的 dependencies(core/pyproject.toml:26-32)只有外部通用库:
# langsmith / tenacity / jsonpatch / PyYAML / typing-extensions / packaging
# libs/langchain_v1/pyproject.toml:6,24,27,91 —— 新房建在地基上
name = "langchain"
version = "1.3.12"
dependencies = [
"langchain-core>=1.4.9,<2.0.0", # ★依赖地基
...
"langchain-text-splitters>=1.0.0,<2.0.0", # ★还带上工具间
]
# libs/langchain/pyproject.toml:6,23,26 —— 老房也建在地基上
name = "langchain-classic"
version = "1.0.8"
# "langchain-core>=1.4.7,<2.0.0"
# libs/text-splitters/pyproject.toml:6,25
name = "langchain-text-splitters"
version = "1.1.2"
core 不依赖兄弟★这是整个架构最重要的一条:langchain-core 的依赖表里没有任何 langchain 家族的包,只有 langsmith(追踪)、tenacity(重试)这类外部工具。地基不能压在楼上——否则就循环依赖了。langchain → core新主包声明 langchain-core>=1.4.9:你 pip install langchain 时,pip 顺着这行自动把 core 拖下来。这就是"装一个包、来一家人"的原理。classic → core老房同样踩在地基上(>=1.4.7)。所以新旧两代可以共存于一个环境:它们共享同一个 core。版本号各自独立core 1.4.9、langchain 1.3.12、classic 1.0.8、text-splitters 1.1.2——每栋楼独立发版。这是 monorepo + 多包的标准玩法:代码住一起(方便联调),发布各管各(互不拖累)。💡 设计取舍:为什么厂商代码要拆成 partners 小包?如果 ChatOpenAI 直接写在主包里,主包就得依赖 openai SDK——装 langchain 的人哪怕只用 Claude 也被迫装 openai。拆成
langchain-openai、langchain-anthropic 独立小包后:用哪家装哪家,主包保持干净;厂商 SDK 破坏性升级也只炸自己的小包。代价是"要多装一个包"的一点点麻烦——用一次 pip install 换依赖清爽,划算。D02 你装环境时就会亲身体会。L05
langchain_core 模块地图:以后 19 天的寻宝图(真源码第 4 段)
地基楼里有什么?ls libs/core/langchain_core/(真实列表,挑重点标注):
$ ls libs/core/langchain_core/
runnables/ # ★★★ Runnable/LCEL:全框架地基中的地基(D03/D04/D16/D18)
language_models/ # ★★ BaseChatModel 等模型抽象(D05)
messages/ # ★★ Human/AI/Tool 消息体系(D06)
prompts/ # ★★ Prompt 模板(D07)
output_parsers/ # 输出解析(D08)
documents/ document_loaders/ # Document 与加载(D09)
embeddings/ vectorstores/ # 向量化与向量库(D11)
retrievers.py # 检索器抽象(D12)
tools/ # ★ @tool/BaseTool(D13)
callbacks/ # ★ 回调系统(D17)
tracers/ # LangSmith 追踪(D19)
caches.py rate_limiters.py globals.py # 缓存/限流/全局开关(D05 会碰到)
chat_history.py # 对话历史(D16)
agents.py exceptions.py load/ utils/ ...
图注:所有楼都踩在
langchain-core 上;绿色虚线是 langchain(v1) 顺带依赖 text-splitters。地基自己零家族依赖。💡 本质:这张目录就是 20 天课表注意看代码块里的 D 编号——
langchain_core 的一级目录几乎和我们的课表一一对应。这不是巧合:好的包结构本身就是知识地图。其中 runnables/ 是重中之重,单单 runnables/base.py 一个文件就有 6713 行——D03、D04 两整天都花在它身上。L06
串起来 + 今日小结
📝 真实值:一行 import 各回各家
假设你写下这三行,它们分别敲开哪扇门?
①
②
③
同一个环境里三行可以同时成立——因为它们是三个独立安装的包,共享同一个 core 地基。
①
from langchain.chat_models import init_chat_model → 新房 libs/langchain_v1/langchain/chat_models/base.py(init_chat_model 定义在该文件 :210);②
from langchain_core.runnables import RunnableSequence → 地基 libs/core/langchain_core/runnables/base.py:3063(D03 主角);③
from langchain_classic.chains import LLMChain → 老房 libs/langchain/langchain_classic/chains/(0.x 遗产)。同一个环境里三行可以同时成立——因为它们是三个独立安装的包,共享同一个 core 地基。
👶 小白:那我学的时候到底该 import langchain 还是 langchain_core?会不会学了个"过时的"?
👨🏫 老师:记一个简单法则——写应用用 langchain(新房入口,如 init_chat_model、create_agent),读原理看 langchain_core(地基,所有概念的定义处)。新房里的东西大多是对地基的薄封装,比如 init_chat_model 返回的就是 core 里定义的 BaseChatModel 子类。至于 langchain_classic,除非维护老项目,否则不用学——本教程也只在 D20 讲生态时提它。学地基永远不会过时。
🧠 今天你应该能回答
- pip 装的
langchain对应仓库哪个目录?(libs/langchain_v1/,不是libs/langchain/!) libs/langchain/里住的是谁?(langchain_classic,0.x 兼容包)- 五大包依赖方向?(v1 / classic / text-splitters / partners 全部依赖 core;core 零家族依赖)
- 为什么厂商代码拆成 partners 小包?(用哪家装哪家,主包不背厂商 SDK)
- 读源码找 Runnable 去哪?(
libs/core/langchain_core/runnables/base.py,6713 行) - 怎么确认一个目录的真实 pip 包名?(看它的
pyproject.toml里的name)
✋ 10 分钟动手
cd /Users/bitmart/work/codes/github/AI_WORK/langchain
# 1. 认楼
ls libs/ && ls libs/partners/
# 2. 查户口:目录名 vs 真门牌
grep -n "^name\|^version" libs/core/pyproject.toml \
libs/langchain_v1/pyproject.toml libs/langchain/pyproject.toml \
libs/text-splitters/pyproject.toml
# 3. 验证"地基零家族依赖"
sed -n '26,34p' libs/core/pyproject.toml # 只有 langsmith/tenacity 等
grep -n "langchain-core" libs/langchain_v1/pyproject.toml # 新房踩地基
# 4. 摸一眼明天的两个主角
sed -n '1,5p' libs/langchain_v1/langchain/__init__.py
grep -n "def init_chat_model" libs/langchain_v1/langchain/chat_models/base.py
明日预告 · Day 02:地图有了,明天真正跑起来——装环境、
init_chat_model 一行拿模型,然后写下你的第一根管道 prompt | model | parser,并钻进源码看 init_chat_model 是怎么按"厂商前缀"分发到 partners 包的。