概述
前几章讲了项目实战,有同学可能对源码的实现感兴趣,接下来我们将进入LangChain 源码学习。在学习源码容易犯一个错误:直接搜索某个类,然后顺着调用一路跳。
比如想看create_agent(),就直接打开langchain.agents。
想看prompt | model | parser,就去找__or__。
想看 OpenAI 模型调用,就直接点进ChatOpenAI。
这样当然能读到代码,但很快会迷路。因为 LangChain 不是一个单包项目,而是一个由多个 Python 包、多个抽象层、多个集成包共同组成的生态:
langchain-core负责核心接口和基础抽象。langchain负责高层入口、Agent harness、模型初始化和中间件。langchain-community承载大量社区集成和历史兼容内容。langchain-<provider>这类独立供应商包负责 OpenAI、Anthropic、Google、Ollama、DeepSeek 等具体实现。langgraph负责更底层的状态图、持久化、可恢复执行和复杂 Agent 编排。langsmith负责 tracing、debug、evaluation 和观测。
如果没有这张地图,你会把“接口定义”和“具体实现”混在一起,把“高层快捷入口”和“底层执行引擎”混在一起,也会误以为某个功能都应该在langchain主包里。
本文的目标不是逐行拆源码,而是先回答一个更关键的问题:
当你想看某个 LangChain 功能源码时,应该先去哪一个包、哪一个模块、哪一类接口找?
LangChain 源码阅读的第一步,不是找某个函数,而是先分清核心抽象、高层封装、供应商实现、图执行引擎和观测平台分别在哪一层。
总体架构
先看一张简化版架构图:
应用代码 | | invoke / stream / batch v langchain |-- agents.create_agent |-- chat_models.init_chat_model |-- middleware |-- structured output | | 依赖核心抽象 v langchain-core |-- runnables |-- prompts |-- messages |-- output_parsers |-- language_models |-- tools |-- retrievers |-- documents |-- embeddings |-- vectorstores | +---------------------+ | v 供应商 / 集成包 |-- langchain-openai |-- langchain-anthropic |-- langchain-google-genai |-- langchain-ollama |-- langchain-deepseek |-- langchain-chroma |-- langchain-qdrant |-- langchain-community 复杂 Agent 编排 | v langgraph |-- StateGraph |-- nodes / edges |-- checkpointer |-- interrupts |-- durable execution 观测与评估 | v langsmith |-- tracing |-- debugging |-- evaluation |-- dataset / experiment这张图里最重要的是方向:
高层 API 依赖核心抽象 具体供应商实现核心抽象 LangGraph 承接复杂执行流 LangSmith 观察所有运行过程不要反过来理解。
langchain-core不应该依赖 OpenAI,也不应该依赖某个向量数据库。否则核心包会变得非常重,任何一个供应商 SDK 的变化都会影响整个框架。
所以 LangChain 的源码设计很大一部分都围绕这件事展开:
稳定的抽象放核心包,变化快的实现放独立集成包,高层易用 API 放主包,复杂流程控制交给 LangGraph。
LangChain 的核心设计是“抽象稳定、实现外置、编排下沉、观测独立”。
包结构总览:每个包到底负责什么?
先用一张表建立导航能力:
| 包名 | 主要职责 | 典型源码目标 |
|---|---|---|
langchain-core | 核心接口、协议、数据结构、Runnable 抽象 | Runnable、BaseChatModel、BaseMessage、ChatPromptTemplate、BaseTool |
langchain | 面向用户的高层入口和 Agent harness | create_agent()、init_chat_model()、middleware、structured output |
langchain-community | 社区集成、历史集成、非核心实现 | 第三方 loader、tool、retriever、vector store 的社区实现 |
langchain-openai | OpenAI 相关具体实现 | ChatOpenAI、OpenAIEmbeddings |
langchain-anthropic | Anthropic Claude 相关具体实现 | ChatAnthropic |
langchain-google-genai | Google Gemini 相关具体实现 | Gemini chat model / embeddings |
langchain-ollama | 本地 Ollama 模型接入 | ChatOllama、Ollama embeddings |
langchain-text-splitters | 文本切分器 | RecursiveCharacterTextSplitter |
langgraph | 状态图执行、Agent 编排、持久化、中断恢复 | StateGraph、ToolNode、checkpointer |
langsmith | 追踪、调试、评估、实验管理 | trace client、evaluation、dataset |
langchain-classic | 旧版 chains、memory、classic API 兼容 | 迁移旧项目时常见 |
这张表可以直接当源码导航手册。
比如:
- 想看
prompt | model | parser为什么能跑:先看langchain-core的runnables。 - 想看 Prompt 模板如何生成 messages:看
langchain-core的prompts和messages。 - 想看模型统一接口:看
langchain-core的language_models,再跳到具体供应商包。 - 想看
ChatOpenAI怎么调 OpenAI SDK:看langchain-openai。 - 想看
create_agent()如何组装 Agent:看langchain的agents。 - 想看工具节点如何执行:看
langgraph里的 prebuilt tool node,以及langchain-core的 tool 抽象。 - 想看多轮状态如何保存:看
langgraph的 checkpointer。 - 想看 trace 数据怎么上报:看
langsmith。
读源码时先判断“这是接口问题、实现问题、编排问题,还是观测问题”,再决定进入哪个包。
langchain-core:整个生态的接口层
langchain-core是最值得先读的包。
它不是“功能最多”的包,但它定义了 LangChain 生态里最稳定的概念。后面的模型、Prompt、Parser、Tool、Retriever、VectorStore、Agent,很多都要遵守这里的接口。
可以把它理解成 LangChain 世界的协议层:
langchain_core | |-- runnables |-- prompts |-- messages |-- output_parsers |-- language_models |-- tools |-- documents |-- retrievers |-- embeddings |-- vectorstores |-- callbacks |-- exceptionsRunnable:LCEL 的根抽象
前面第 5 篇讲 LCEL 时,我们反复提到一句话:
chain=prompt|model|parser这行代码的背后就是Runnable抽象。
源码阅读重点:
Runnable.invoke(): 单次调用。Runnable.ainvoke(): 异步单次调用。Runnable.batch(): 批处理。Runnable.stream(): 流式输出。Runnable.__or__(): 管道组合。RunnableSequence: 多个 Runnable 串联之后形成的新 Runnable。RunnableParallel: 并行分支。RunnableLambda: 普通函数包装成 Runnable。
它的设计目标是让不同组件具备统一执行协议:
Prompt 是 Runnable Model 是 Runnable Parser 是 Runnable Retriever 可以被组合进 Runnable 流 普通函数也可以变成 Runnable 组合出来的 Chain 仍然是 Runnable这就是为什么 LangChain 的很多能力能自然支持invoke、stream、batch和 tracing。
如果只能先读一个模块,先读langchain-core的runnables。它是理解 LCEL、Chain、Agent 执行流的入口。
Messages:对话数据结构的统一表示
LLM 应用里最基础的数据结构不是字符串,而是 message。
常见类型包括:
SystemMessageHumanMessageAIMessageToolMessageBaseMessage
这些类解决的是“不同模型供应商如何统一表达对话上下文”的问题。
OpenAI、Anthropic、Gemini、Ollama 的底层 message 格式并不完全一样,但在 LangChain 里,高层代码通常只需要处理统一的 message 对象。
fromlangchain_core.messagesimportHumanMessage,SystemMessage messages=[SystemMessage(content="你是一个严谨的源码讲解老师。"),HumanMessage(content="请解释 Runnable 的设计。"),]读模型调用源码时,message 层非常关键。因为具体供应商包通常会做两件事:
- 把 LangChain 的
BaseMessage转成供应商 SDK 接受的请求格式。 - 把供应商返回结果转回
AIMessage或带 tool call 的 message。
Messages 是模型供应商之间的通用语言,读 ChatModel 源码一定绕不开它。
Prompts:从模板到 PromptValue
Prompt 相关源码主要回答一个问题:
用户传入变量后,模板如何变成模型能消费的消息列表?
例如:
fromlangchain_core.promptsimportChatPromptTemplate prompt=ChatPromptTemplate.from_messages([("system","你是一个技术导师。"),("human","请解释:{topic}"),])value=prompt.invoke({"topic":"LangChain 源码结构"})源码阅读重点:
ChatPromptTemplatePromptTemplateMessagesPlaceholderPromptValue- 模板变量校验
- message 格式转换
Prompt 的关键不是字符串拼接,而是把结构化模板稳定地转换成 message 数据。
这也是为什么 Prompt 可以无缝接在模型前面:
chain=prompt|model因为prompt本身就是 Runnable。
Language Models:BaseChatModel 的统一入口
模型抽象通常从BaseChatModel读起。
它解决的是:
- 所有 ChatModel 都应该如何调用?
- 同步、异步、流式、批处理如何统一?
- 模型返回值如何包装成 message?
- 回调、重试、缓存、tracing 如何接入?
一个具体模型包,比如langchain-openai,会实现核心包定义的抽象。
大致关系是:
langchain_core.language_models.BaseChatModel ^ | langchain_openai.ChatOpenAI langchain_anthropic.ChatAnthropic langchain_ollama.ChatOllama你在应用层写:
model.invoke(messages)底层会走到具体供应商实现,再调用对应 SDK。
BaseChatModel定义“模型应该长什么样”,ChatOpenAI这类实现负责“这个模型具体怎么调”。
Tools:函数如何变成模型可调用工具
Tool 相关源码可以分两层看:
langchain-core里定义工具抽象。langgraph或 Agent 执行层负责根据模型返回的 tool call 执行工具。
最常见的入口是:
fromlangchain_core.toolsimporttool@tooldefget_weather(city:str)->str:"""Get weather for a city."""returnf"{city}is sunny."这个装饰器会把普通 Python 函数包装成带 schema、name、description、args 的工具对象。
读源码时要关注:
- 函数签名如何提取参数 schema。
- docstring 如何变成工具描述。
- Pydantic schema 如何用于参数校验。
- 工具执行异常如何传递。
- 工具结果如何包装回模型上下文。
后面第 26 篇会专门追踪这条链路。
langchain:高层入口,不是所有底层实现都在这里
langchain主包最容易被误解。
很多人以为langchain包里应该包含所有代码:模型、向量库、文档加载器、工具、Agent、Memory、部署。
langchain更像是面向用户的高层入口,重点提供:
create_agent()init_chat_model()- middleware
- structured output
- model initialization
- agents 相关 API
官方文档中,create_agent()被定位为一个 minimal、highly configurable agent harness。也就是说,它不是“一个大而全的 Agent 框架”,而是围绕模型循环、工具调用、Prompt、中间件和结构化输出搭起来的高层运行壳。
create_agent:主包里最值得读的入口
一个典型写法:
fromlangchain.agentsimportcreate_agentdefget_weather(city:str)->str:"""Get weather for a given city."""returnf"It's always sunny in{city}!"agent=create_agent(model="openai:gpt-4o-mini",tools=[get_weather],system_prompt="You are a helpful assistant",)result=agent.invoke({"messages":[{"role":"user","content":"What's the weather in San Francisco?"}]})读create_agent()源码时,不要只看函数参数,要带着这几个问题读:
model字符串如何被解析成具体模型?- 普通函数如何被转换成 tool?
system_prompt如何进入 message 列表?response_format如何触发结构化输出策略?- middleware 如何包裹模型调用和工具调用?
- checkpointer 如何接入短期记忆和持久化状态?
- 最终返回的对象为什么可以
invoke()?
这个函数背后会连接langchain-core、供应商包和 LangGraph。
第 23 篇会专门拆create_agent(),本文先把它放在架构地图上。
init_chat_model:统一模型入口
init_chat_model()的价值是把供应商差异压到初始化阶段。
例如:
fromlangchain.chat_modelsimportinit_chat_model model=init_chat_model("openai:gpt-4o-mini",temperature=0,)源码阅读重点:
- 模型字符串如何拆分 provider 和 model name。
- 不同 provider 如何映射到不同包。
- 对应集成包是否已安装,未安装时如何报错。
- 参数如何传给具体 ChatModel。
- 返回对象如何满足
BaseChatModel接口。
这也是 LangChain 包拆分的一个典型案例:
用户入口:langchain.chat_models.init_chat_model 统一抽象:langchain_core.language_models.BaseChatModel 具体实现:langchain_openai.ChatOpenAI / langchain_anthropic.ChatAnthropic / ...一句话总结:langchain主包负责让常用路径好用,但真正的核心抽象在langchain-core,真正的供应商实现通常在独立集成包。
langchain-community:历史包、社区包和迁移时的高频入口
langchain-community在 LangChain 生态里经常出现。
它的定位可以理解为:
承载大量社区贡献集成、长尾工具、历史实现和非核心实现的包。
在早期 LangChain 版本中,很多 integration 都集中在主包或 community 包里。后来生态逐渐拆分,热门供应商和重点集成越来越多地迁移到独立包,例如:
langchain-openailangchain-anthropiclangchain-google-genailangchain-ollamalangchain-chromalangchain-qdrantlangchain-pinecone
所以读源码时要区分两种情况:
| 场景 | 建议 |
|---|---|
| 新项目接入热门模型或向量库 | 优先看独立langchain-<provider>包 |
| 维护旧项目 | 经常会遇到langchain-community |
| 查长尾 loader/tool/vector store | 可以先搜langchain-community |
| 想理解核心抽象 | 不要从 community 包开始 |
举个例子,如果你想看 OpenAI 模型调用,不应该优先去langchain-community里找,而应该看langchain-openai。
如果你想看某个不常见的数据源 loader,则很可能会在langchain-community中看到实现。
langchain-community适合查集成实现和历史代码,不适合作为理解 LangChain 核心设计的第一站。
供应商独立包:厂商差异被隔离在哪里?
LangChain 的一个核心价值是统一接口。
但统一接口不代表所有供应商真的一样。OpenAI、DeepSeek、Anthropic、Gemini、Ollama 的请求参数、消息格式、tool call 格式、流式事件格式都可能不同。
这些差异主要被隔离在独立供应商包里。
例如:
langchain-openai |-- ChatOpenAI |-- OpenAIEmbeddings langchain-anthropic |-- ChatAnthropic langchain-ollama |-- ChatOllama |-- OllamaEmbeddings这些类通常会做四类事情:
- 接收 LangChain 标准参数。
- 把标准 message 转成供应商 API 格式。
- 调用供应商 SDK。
- 把供应商响应转回 LangChain 标准 message 或 chunk。
模型调用链路可以简化成:
应用代码 | v BaseChatModel.invoke(messages) | v 具体供应商类._generate(...) | v 供应商 SDK / HTTP API | v 供应商返回结果 | v AIMessage / AIMessageChunk这层代码特别适合用来学习“框架如何兼容多供应商”。
重点关注:
- message 转换函数。
- tool call 格式转换。
- streaming chunk 合并。
- token usage 元数据。
- 错误处理和重试。
- provider-specific 参数透传。
供应商独立包是 LangChain 把“不稳定厂商差异”隔离出去的地方,读模型适配源码一定要进入这些包。
LangGraph:Agent 执行引擎为什么不完全放在 langchain 里?
从 1.x 开始,理解 Agent 源码不能只看langchain。
官方文档也明确强调:LangChain agents 构建在 LangGraph 之上,从而获得 durable execution、human-in-the-loop、persistence 等能力。
这句话很关键。
它意味着:
create_agent 是高层入口 LangGraph 是底层执行骨架如果你的问题是:
- Agent 为什么能多轮调用工具?
- 工具调用后为什么还能回到模型?
- checkpointer 如何保存状态?
- interrupt 如何暂停等待人工审批?
- 条件边如何决定下一步?
- 为什么复杂 Agent 不只是一个 while 循环?
这些问题最终都要进入 LangGraph。
一个简化的 Agent 图可以这样理解:
在源码层面,你会看到类似这些概念:
StateGraph: 定义状态图。Node: 执行一个步骤。Edge: 固定流转。Conditional Edge: 条件流转。Checkpointer: 保存状态。ToolNode: 执行工具调用。interrupt: 暂停图执行,等待外部输入。
所以读 Agent 源码时,不要把create_agent()当成终点。它更像一段自动装配逻辑,真正的执行能力来自图。
LangChain 负责把 Agent 配出来,LangGraph 负责让 Agent 按状态图可靠地跑起来。
LangSmith:源码之外的运行时地图
源码告诉你框架“理论上怎么跑”。
LangSmith trace 告诉你你的应用“实际上怎么跑”。
这两者应该结合看。
当你读Runnable、ChatModel、Tool、Agent源码时,会频繁看到 callback、run manager、trace、metadata、tags 这些概念。它们背后就是为了让一次运行过程可观测。
一次 Agent 调用可能包含:
root run | |-- prompt format |-- model call |-- tool call |-- model call |-- output parsing如果没有 tracing,复杂链路出问题时只能靠日志猜。
有了 LangSmith,你可以看到:
- 每一次模型调用输入输出。
- 每一次工具调用参数和结果。
- 每一步耗时。
- token 消耗。
- 中间状态。
- 错误栈。
- prompt 版本和 metadata。
读源码时,看到 callback 不要觉得它是边角逻辑。对于生产级 LLM 应用,它是调试和质量评估的入口。
LangSmith 不是业务执行层,但它解释了为什么 LangChain 源码里到处都有 callback、run id、metadata 和 tracing。
从功能反推源码位置:想看某个能力该去哪?
下面这张表可以作为后续源码篇的导航索引:
| 你想看的能力 | 优先进入的包 | 重点对象 |
|---|---|---|
| LCEL 管道语法 | langchain-core | Runnable、RunnableSequence、RunnableParallel |
invoke/stream/batch | langchain-core | Runnable、config、callbacks |
| Prompt 模板 | langchain-core | ChatPromptTemplate、PromptTemplate、MessagesPlaceholder |
| 消息结构 | langchain-core | BaseMessage、HumanMessage、AIMessage、ToolMessage |
| 输出解析 | langchain-core | StrOutputParser、结构化 parser |
| 模型统一接口 | langchain-core | BaseChatModel、language model base classes |
| OpenAI 调用 | langchain-openai | ChatOpenAI、OpenAIEmbeddings |
| Claude 调用 | langchain-anthropic | ChatAnthropic |
| DeepSeek 调用 | langchain-deepseek | DeepSeek chat model |
| Ollama 本地模型 | langchain-ollama | ChatOllama |
| 工具定义 | langchain-core | @tool、BaseTool |
| 工具执行 | langgraph | ToolNode、tool call routing |
| Agent 创建 | langchain | create_agent() |
| Agent 状态流转 | langgraph | StateGraph、nodes、edges |
| 短期记忆 | langgraph/langchain | checkpointer、Agent state |
| Middleware | langchain | middleware hooks、wrap model/tool call |
| 结构化输出 | langchain | ProviderStrategy、ToolStrategy、response format |
| 文档结构 | langchain-core | Document |
| 文本切分 | langchain-text-splitters | RecursiveCharacterTextSplitter |
| Retriever 抽象 | langchain-core | BaseRetriever |
| 向量库具体实现 | provider 包 / community 包 | Chroma、Qdrant、Pinecone 等 |
| 运行追踪 | langsmith/ callbacks | trace、run manager、metadata |
这张表可以帮你避免“全局搜索式读源码”。
全局搜索不是不能用,但它应该是第二步。第一步应该先判断代码属于哪一层。
推荐源码阅读顺序:从稳定抽象到复杂 Agent
如果你是第一次系统读 LangChain 源码,我建议按这个顺序:
第一步:读 Runnable
先理解:
- 为什么所有组件都能
invoke()? |管道是怎么实现的?RunnableSequence如何串联多个步骤?stream和batch如何在统一接口下暴露?
这是第 22 篇的主题。
第二步:读 Prompt、Message、Parser
这一层最容易形成完整闭环:
输入变量 -> Prompt 模板 -> Messages -> Model -> AIMessage -> Parser -> 字符串/结构化对象这条链路短,但覆盖了 LangChain 最核心的数据流。
第三步:读 BaseChatModel 和一个具体供应商实现
建议组合阅读:
BaseChatModel + ChatOpenAI 或 ChatAnthropic这样你会同时看到“统一接口”和“供应商适配”。
只读BaseChatModel会太抽象,只读ChatOpenAI又容易陷入 provider 细节。
第四步:读 Tool 抽象和 ToolNode
工具调用横跨模型、schema、Agent 和 LangGraph。
推荐先看:
@tool -> BaseTool -> model tool schema -> tool call -> ToolNode -> ToolMessage这是第 26 篇会展开的主题。
第五步:读 create_agent
读create_agent()时,你已经理解了:
- Runnable
- ChatModel
- Messages
- Tools
- Structured output
- LangGraph 基本概念
这时再读它,才不会觉得参数太多、分支太乱。
第 23 篇会重点拆这一块。
第六步:读 Middleware
Middleware 是生产化能力的入口:
- 调用模型前改写请求。
- 调用模型后检查结果。
- 包裹模型调用做 fallback。
- 包裹工具调用做审批、重试、日志。
- 注入运行时上下文。
读它之前,最好先理解 Agent 模型循环。
第 24 篇会专门讲中间件源码。
常见误区
最后说几个很常见的坑。
误区一:以为所有东西都在 langchain 主包
现在很多能力已经拆到独立包。
你看到:
fromlangchain_openaiimportChatOpenAI不要觉得奇怪。这正是包拆分后的推荐形态。
误区二:把 langchain-community 当核心源码入口
langchain-community很重要,但不适合作为理解核心设计的第一站。
核心设计先看langchain-core。
误区三:只读高层 API,不读核心抽象
只读create_agent(),你会看到大量装配逻辑,但不知道为什么这些对象可以组合。
先读Runnable,再读 Agent,会顺很多。
误区四:忽略 LangGraph
复杂 Agent 的执行能力已经大量依赖 LangGraph。
如果你只在langchain主包里找“循环、状态、持久化、中断恢复”,很容易找不到完整答案。
误区五:把 LangSmith 当可选周边
对 demo 来说,LangSmith 可选。
对生产系统来说,tracing 和 evaluation 基本是必需品。源码里的 callback 体系也要放在这个背景下理解。
总结
本文是源码篇的地图,不是某个类的逐行分析。
真正开始读代码前,先记住这五条主线:
langchain-core是核心抽象层,优先读Runnable、messages、prompts、models、tools。langchain是高层入口层,重点看create_agent()、init_chat_model()、middleware 和 structured output。- 供应商独立包负责具体实现,模型调用细节要去
langchain-openai、langchain-anthropic、langchain-ollama等包里看。 - LangGraph 是复杂 Agent 的执行引擎,状态、节点、边、持久化和中断恢复都要在那里找。
- LangSmith 是运行时观测地图,callback、trace、metadata 这些源码细节都和它有关。
最后给出一张最简源码导航图:
想看组合语法? -> langchain-core.runnables 想看 Prompt? -> langchain-core.prompts / messages 想看模型抽象? -> langchain-core.language_models 想看具体模型调用? -> langchain-openai / langchain-anthropic / langchain-ollama / ... 想看工具定义? -> langchain-core.tools 想看工具执行? -> langgraph.prebuilt ToolNode 想看 Agent 怎么创建? -> langchain.agents.create_agent 想看 Agent 怎么跑? -> langgraph StateGraph 想看调用链怎么追踪? -> callbacks / langsmith读 LangChain 源码,不要从“函数搜索”开始,而要从“分层定位”开始。先知道代码应该在哪一层,再去读具体实现。