概述

前几章讲了项目实战,有同学可能对源码的实现感兴趣,接下来我们将进入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 抽象 RunnableBaseChatModelBaseMessageChatPromptTemplateBaseTool
langchain 面向用户的高层入口和 Agent harness create_agent()init_chat_model()、middleware、structured output
langchain-community 社区集成、历史集成、非核心实现 第三方 loader、tool、retriever、vector store 的社区实现
langchain-openai OpenAI 相关具体实现 ChatOpenAIOpenAIEmbeddings
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 编排、持久化、中断恢复 StateGraphToolNode、checkpointer
langsmith 追踪、调试、评估、实验管理 trace client、evaluation、dataset
langchain-classic 旧版 chains、memory、classic API 兼容 迁移旧项目时常见

这张表可以直接当源码导航手册。

比如:

  • 想看 prompt | model | parser 为什么能跑:先看 langchain-corerunnables
  • 想看 Prompt 模板如何生成 messages:看 langchain-corepromptsmessages
  • 想看模型统一接口:看 langchain-corelanguage_models,再跳到具体供应商包。
  • 想看 ChatOpenAI 怎么调 OpenAI SDK:看 langchain-openai
  • 想看 create_agent() 如何组装 Agent:看 langchainagents
  • 想看工具节点如何执行:看 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
  |-- exceptions

Runnable: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 的很多能力能自然支持 invokestreambatch 和 tracing。

如果只能先读一个模块,先读 langchain-corerunnables。它是理解 LCEL、Chain、Agent 执行流的入口。

Messages:对话数据结构的统一表示

LLM 应用里最基础的数据结构不是字符串,而是 message。

常见类型包括:

  • SystemMessage
  • HumanMessage
  • AIMessage
  • ToolMessage
  • BaseMessage

这些类解决的是“不同模型供应商如何统一表达对话上下文”的问题。

OpenAI、Anthropic、Gemini、Ollama 的底层 message 格式并不完全一样,但在 LangChain 里,高层代码通常只需要处理统一的 message 对象。

from langchain_core.messages import HumanMessage, SystemMessage

messages = [
    SystemMessage(content="你是一个严谨的源码讲解老师。"),
    HumanMessage(content="请解释 Runnable 的设计。"),
]

读模型调用源码时,message 层非常关键。因为具体供应商包通常会做两件事:

  1. 把 LangChain 的 BaseMessage 转成供应商 SDK 接受的请求格式。
  2. 把供应商返回结果转回 AIMessage 或带 tool call 的 message。

Messages 是模型供应商之间的通用语言,读 ChatModel 源码一定绕不开它。

Prompts:从模板到 PromptValue

Prompt 相关源码主要回答一个问题:

用户传入变量后,模板如何变成模型能消费的消息列表?

例如:

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个技术导师。"),
    ("human", "请解释:{topic}"),
])

value = prompt.invoke({"topic": "LangChain 源码结构"})

源码阅读重点:

  • ChatPromptTemplate
  • PromptTemplate
  • MessagesPlaceholder
  • PromptValue
  • 模板变量校验
  • 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 执行工具。

最常见的入口是:

from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"{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:主包里最值得读的入口

一个典型写法:

from langchain.agents import create_agent

def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"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() 源码时,不要只看函数参数,要带着这几个问题读:

  1. model 字符串如何被解析成具体模型?
  2. 普通函数如何被转换成 tool?
  3. system_prompt 如何进入 message 列表?
  4. response_format 如何触发结构化输出策略?
  5. middleware 如何包裹模型调用和工具调用?
  6. checkpointer 如何接入短期记忆和持久化状态?
  7. 最终返回的对象为什么可以 invoke()

这个函数背后会连接 langchain-core、供应商包和 LangGraph。

第 23 篇会专门拆 create_agent(),本文先把它放在架构地图上。

init_chat_model:统一模型入口

init_chat_model() 的价值是把供应商差异压到初始化阶段。

例如:

from langchain.chat_models import init_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-openai
  • langchain-anthropic
  • langchain-google-genai
  • langchain-ollama
  • langchain-chroma
  • langchain-qdrant
  • langchain-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

这些类通常会做四类事情:

  1. 接收 LangChain 标准参数。
  2. 把标准 message 转成供应商 API 格式。
  3. 调用供应商 SDK。
  4. 把供应商响应转回 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 图可以这样理解:

用户消息

call_model 节点

是否需要调用工具

返回最终答案

tools 节点

在源码层面,你会看到类似这些概念:

  • StateGraph: 定义状态图。
  • Node: 执行一个步骤。
  • Edge: 固定流转。
  • Conditional Edge: 条件流转。
  • Checkpointer: 保存状态。
  • ToolNode: 执行工具调用。
  • interrupt: 暂停图执行,等待外部输入。

所以读 Agent 源码时,不要把 create_agent() 当成终点。它更像一段自动装配逻辑,真正的执行能力来自图。

LangChain 负责把 Agent 配出来,LangGraph 负责让 Agent 按状态图可靠地跑起来。

LangSmith:源码之外的运行时地图

源码告诉你框架“理论上怎么跑”。

LangSmith trace 告诉你你的应用“实际上怎么跑”。

这两者应该结合看。

当你读 RunnableChatModelToolAgent 源码时,会频繁看到 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 RunnableRunnableSequenceRunnableParallel
invoke/stream/batch langchain-core Runnable、config、callbacks
Prompt 模板 langchain-core ChatPromptTemplatePromptTemplateMessagesPlaceholder
消息结构 langchain-core BaseMessageHumanMessageAIMessageToolMessage
输出解析 langchain-core StrOutputParser、结构化 parser
模型统一接口 langchain-core BaseChatModel、language model base classes
OpenAI 调用 langchain-openai ChatOpenAIOpenAIEmbeddings
Claude 调用 langchain-anthropic ChatAnthropic
DeepSeek 调用 langchain-deepseek DeepSeek chat model
Ollama 本地模型 langchain-ollama ChatOllama
工具定义 langchain-core @toolBaseTool
工具执行 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 ProviderStrategyToolStrategy、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 如何串联多个步骤?
  • streambatch 如何在统一接口下暴露?

这是第 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 主包

现在很多能力已经拆到独立包。

你看到:

from langchain_openai import ChatOpenAI

不要觉得奇怪。这正是包拆分后的推荐形态。

误区二:把 langchain-community 当核心源码入口

langchain-community 很重要,但不适合作为理解核心设计的第一站。

核心设计先看 langchain-core

误区三:只读高层 API,不读核心抽象

只读 create_agent(),你会看到大量装配逻辑,但不知道为什么这些对象可以组合。

先读 Runnable,再读 Agent,会顺很多。

误区四:忽略 LangGraph

复杂 Agent 的执行能力已经大量依赖 LangGraph。

如果你只在 langchain 主包里找“循环、状态、持久化、中断恢复”,很容易找不到完整答案。

误区五:把 LangSmith 当可选周边

对 demo 来说,LangSmith 可选。

对生产系统来说,tracing 和 evaluation 基本是必需品。源码里的 callback 体系也要放在这个背景下理解。

总结

本文是源码篇的地图,不是某个类的逐行分析。

真正开始读代码前,先记住这五条主线:

  1. langchain-core 是核心抽象层,优先读 Runnable、messages、prompts、models、tools。
  2. langchain 是高层入口层,重点看 create_agent()init_chat_model()、middleware 和 structured output。
  3. 供应商独立包负责具体实现,模型调用细节要去 langchain-openailangchain-anthropiclangchain-ollama 等包里看。
  4. LangGraph 是复杂 Agent 的执行引擎,状态、节点、边、持久化和中断恢复都要在那里找。
  5. 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 源码,不要从“函数搜索”开始,而要从“分层定位”开始。先知道代码应该在哪一层,再去读具体实现。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐