LangChain框架全解析——架构、组件与实战
本篇为 AI 知识系列第 04 篇。素材整合自 LangChain 官方文档、腾讯云开发者社区、MyScale 技术博客、LangGraph 深度教程(2026 版)以及 Claude Code Skill 机制实战经验,并结合 2026 年最新实践整理而成。
核心问题列表
在正式开始之前,先把本篇要回答的核心问题列出来。带着问题读,效率更高。
- LangChain 到底是什么? 它是一个模型,还是一个框架?为什么不提供自己的 LLM?
- “Agent = Model + Harness” 是什么意思? 这个公式为什么能决定整个生态的设计哲学?
- LangChain 生态有哪些层次? Deep Agents、LangChain、LangGraph、LangSmith 之间是什么关系?我该选哪一层?
- 六大核心组件分别管什么? Model I/O、Data Connection、Chains、Memory、Agents、Callbacks,它们如何协作?
- LCEL 是什么? 为什么说管道符语法是对传统 Chains 的现代化替代?Runnable 接口统一了什么?
- LangGraph 为什么会出现? 传统 Agent 循环哪里不够用?State、Nodes、Edges、Reducer 怎么协同工作?
- 怎么实现人工干预? interrupt() 和 Human-in-the-Loop 在生产中怎么落地?
- LangSmith 怎么帮我们调试 Agent? 追踪、评估、监控分别解决什么痛点?
- LangServe 还能用吗? 官方推荐的新部署方案是什么?
- LangChain vs LlamaIndex vs CrewAI vs AutoGen,怎么选? 有没有一棵决策树?
- 什么时候不该用框架,而该手搓? 框架的边界在哪里?
- Skill 机制和 LangChain 的工具有什么异同? 从 Claude Code 的 Skill 实战中能学到什么?
引言
2022 年底 ChatGPT 横空出世后,开发者们很快发现一个尴尬的事实:直接调用 LLM 的 API 其实很简单,一个 requests.post 就能搞定;但要把它变成一个"产品",却要处理无数工程问题——提示词怎么管理?长对话的上下文怎么保持?怎么让模型调用外部工具?怎么把检索到的文档喂给模型?怎么调试一个跑了十步才报错的 Agent?
这些问题,每一个单独看都不难,但叠在一起就成了拦路虎。LangChain 就是在这个背景下诞生的:它不提供模型,而是提供一整套标准化抽象,让你把"模型 + 提示词 + 工具 + 记忆 + 流程编排"像搭积木一样组合起来。
经过几年迭代,LangChain 已经从一个单一库,长成了一个包含 Deep Agents、LangChain、LangGraph、LangSmith、LangDeployment 的完整产品体系。它经历了从"大而全的 Chain"到"LCEL 管道符语法"再到"LangGraph 图编排"的范式演进,也经历了 LangServe 弃用、LangGraph Platform 更名等架构调整。理解这个演进过程,比记住某个 API 更重要——因为框架会变,但背后解决问题的思路是稳定的。
本篇将从定位、生态、组件、语法、编排、调试、部署、选型、最佳实践九个维度,把 LangChain 体系彻底拆解一遍。每个概念都会配 Python 代码和 ASCII 架构图,每章末尾有实践要点小结。读完之后,你不仅知道"怎么用",更知道"为什么这么设计"以及"什么时候不该用"。
第一章 LangChain 概述与定位
1.1 什么是 LangChain
LangChain 是一个用于构建大语言模型(LLM)应用的多功能框架。这里有一个关键认知必须先纠正:LangChain 不提供自己的 LLM。它不训练模型,也不卖模型,而是提供了一个标准接口,让你能用统一的 API 调用 OpenAI、Anthropic、Google、Ollama、Azure、AWS Bedrock、HuggingFace 等几十家模型提供商的模型。
这意味着什么?意味着你可以今天用 GPT-5.5,明天切换到 Claude Sonnet,后天换成本地部署的 Ollama 模型,而你的业务代码几乎不用改。这种"模型可替换性"是 LangChain 最早也是最持久的价值主张。
它的核心定位用一句话概括:Agent 框架——提供模型、工具、Agent 的标准化抽象,让开发者高效地设计适用于各种用例的定制解决方案。
1.2 核心理念:Agent = Model + Harness
这是理解整个 LangChain 生态的钥匙。
┌─────────────────────────────────────────────────────────┐
│ Agent = Model + Harness │
│ │
│ ┌──────────┐ ┌──────────────────────────────┐ │
│ │ Model │ + │ Harness(运行框架) │ │
│ │ (大脑) │ │ 提示词 + 工具 + 中间件 │ │
│ └──────────┘ └──────────────────────────────┘ │
│ GPT-5.5 create_agent 提供的部分 │
│ Claude │
│ Gemini 模型循环周围的一切 │
└─────────────────────────────────────────────────────────┘
LangChain 官方对此的解释是:create_agent 提供的是一个最小化、高度可配置的运行框架(harness)。这个框架是"模型循环周围的一切"——提示词(prompt)、工具(tools),以及任何塑造行为的中间件(middleware)。
换句话说,模型负责"思考",harness 负责把思考过程组织成一个可运行的循环:给模型喂什么提示、模型说要调什么工具就去调、调完结果怎么拼回提示、什么时候该停。这个循环的结构是固定的,但里面填什么内容是高度可配置的。
为什么要强调"从基础原语开始组合"?因为 LangChain 的设计哲学是:不给你一个黑盒 Agent,而是给你积木。你从基础原语(模型接口、工具定义、提示模板)开始,组合出你的用例所需的确切能力。这与后面要讲的 Skill 机制理念相通——好的抽象不是把复杂性藏起来,而是让你能精确控制每一层。
1.3 框架定位
| 维度 | 说明 |
|---|---|
| 核心模块 | 六大核心模块:Model I/O、Data Connection、Chains、Memory、Agents、Callbacks |
| 抽象层级 | 中等(组件化,不像 LangGraph 那么底层,也不像 Deep Agents 那么开箱即用) |
| 核心能力 | 模型/工具/链/Agent 抽象 |
| 使用方式 | 可独立使用,也可与 LangGraph 组合 |
| 适合场景 | 快速构建 Agent 和 LLM 应用 |
| 模型支持 | OpenAI、Anthropic、Google、Ollama、Azure、AWS Bedrock、HuggingFace 等 |
1.4 典型应用场景
LangChain 在以下场景表现出色:
- 聊天机器人应用:长对话上下文保持、多轮记忆
- 文本生成与创造性生成:文案、代码、故事
- 查询回答(RAG):检索增强生成,让模型基于你的私有数据回答
- 语言翻译:多语言转换
- 自主操作和复杂问题解决(Agent):模型自主决策调用工具,完成多步骤任务
- 情感分析、内容生成等 NLP 任务
1.5 一个最小示例
在深入组件之前,先看一个最小的 LangChain 应用长什么样,建立直觉:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 1. 定义提示模板
prompt = ChatPromptTemplate.from_template(
"你是一位资深 {role},请用通俗易懂的方式解释 {concept}"
)
# 2. 选择模型
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
# 3. 定义输出解析器
parser = StrOutputParser()
# 4. 用管道符组合成链
chain = prompt | llm | parser
# 5. 调用
result = chain.invoke({"role": "物理学家", "concept": "量子纠缠"})
print(result)
这五行核心代码就完成了一个"提示词 → 模型 → 解析输出"的完整链路。后续每一章,我们都会在这个骨架上叠加更多能力。
第一章实践要点
- 记住
Agent = Model + Harness,这是理解整个生态的钥匙。你的工作大多是"配置 harness",而不是改模型。- LangChain 的核心价值是"标准接口 + 可组合积木",不要把它当黑盒用,要理解每一块积木的职责。
- 模型可替换性是第一价值主张:业务代码与具体模型解耦,今天用 GPT,明天能无痛切 Claude。
第二章 生态系统全景
2.1 五层架构
LangChain 早已不是一个单一库,而是一个分层的产品体系。从上到下,抽象级别递减,灵活性递增:
┌──────────────────────────────────────────────────────────┐
│ LangChain 产品体系(五层) │
├──────────────────┬───────────────────────────────────────┤
│ Deep Agents │ 开箱即用的 Agent,最上层抽象 │
│ (最高层) │ 含自动上下文压缩、虚拟文件系统、子Agent │
├──────────────────┼───────────────────────────────────────┤
│ LangChain │ Agent 框架:模型/工具/Agent 抽象 │
│ (框架层) │ create_agent,高度可定制的 harness │
├──────────────────┼───────────────────────────────────────┤
│ LangGraph │ 编排运行时:持久化/流式/人工干预 │
│ (运行时层) │ 低级图结构,有状态多步骤工作流 │
├──────────────────┼───────────────────────────────────────┤
│ LangSmith │ 可观测性平台:追踪/评估/监控/部署 │
│ (可观测层) │ 框架无关,能追踪任何 Agent 技术栈 │
├──────────────────┼───────────────────────────────────────┤
│ LangDeployment │ 部署方案:将 Chain/Graph 封装为 API │
│ (部署层) │ Server + Studio + Cloud + 自托管 │
└──────────────────┴───────────────────────────────────────┘
2.2 各组件职责划分
| 组件 | 定位 | 职责 |
|---|---|---|
| Deep Agents | 最高层抽象 | 开箱即用的 Agent,含自动上下文压缩、虚拟文件系统、子 Agent 生成 |
| LangChain | Agent 框架 | 模型/工具/链/Agent 抽象,create_agent 高度可定制 |
| LangGraph | 编排运行时 | 状态管理/流程控制/持久化/人工干预,低级图结构 |
| LangSmith | 可观测性平台 | 追踪/调试/评估/监控/部署,框架无关 |
| LangServe / LangDeployment | 部署方案 | 将 Chain/Graph 封装为稳定 API 服务 |
理解这五层的关键在于:它们是可组合的,不是互斥的。你可以只用 LangChain 搭一个简单 Chain;也可以用 LangChain + LangGraph 搭一个有状态多 Agent 工作流;无论哪种,都建议挂上 LangSmith 做追踪;最后用 LangDeployment 部署成 API。
2.3 层级关系图
你的应用
│
┌────────────┼────────────┐
▼ ▼ ▼
Deep Agents LangChain LangGraph ← 选择一层作为主体
│ │ │
└────────────┼────────────┘
▼
LangSmith ← 横切所有层(追踪/评估)
│
▼
LangDeployment ← 最后部署成 API
│
▼
生产环境
2.4 选型建议(官方推荐)
- Deep Agents:需要"开箱即用"的 Agent(含自动上下文压缩、虚拟文件系统、子 Agent 生成),不想自己搭 harness
- LangChain(
create_agent):需要高度可定制的 harness,轻松适配用例和数据 - LangGraph:低级编排框架,用于结合确定性和 Agent 工作流的高级需求
- LangSmith:追踪、调试和评估用任何框架构建的 Agent(包括非 LangChain 的)
一个常见误区是"先选最高层的 Deep Agents,省事"。但开箱即用的代价是灵活性低。如果你的业务流程有特殊的确定性步骤(比如必须先查数据库、再调审核、最后发邮件),Deep Agents 的自动循环可能反而不如 LangGraph 的图结构可控。选型的核心问题是:你的工作流有多需要确定性控制? 越需要确定性,越往下层走。
第二章实践要点
- 五层不是"高低优劣",而是"抽象级别"。上层省事但灵活度低,下层灵活但要多写代码。
- LangSmith 是横切的,不管你用哪层都建议挂上,追踪数据是后续优化的基础。
- 选型先问"确定性 vs 自主性"的比例,再决定用哪一层做主体。
第三章 核心组件详解
LangChain 框架的核心模块主要有六个:模型输入输出(Model I/O)、数据连接(Data Connection)、链(Chains)、记忆(Memory)、代理(Agents)和回调(Callbacks)。这六个模块覆盖了一个 LLM 应用从"输入"到"输出"到"记忆"到"自主行动"的完整链路。
┌──────────────────────────────────────────────────────────┐
│ LangChain 六大核心组件 │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Model I/O│──▶│ Chains │──▶│ Agents │ │
│ │ (输入输出)│ │ (链式组合) │ │ (自主决策) │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Memory │ │Data Connection│ │ Callbacks │ │
│ │ (记忆) │ │ (数据连接) │ │ (回调) │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────┘
3.1 模型输入输出(Model I/O)
任何语言模型应用程序的核心元素都是模型。Model I/O 模块是与模型交互的构建块,包含四个子组件。
3.1.1 语言模型(Language Models)
LangChain 为两种类型的模型提供统一接口:
- LLM:将文本字符串作为输入并返回文本字符串的模型(传统补全模型)
- ChatModel:将聊天消息列表作为输入并返回聊天消息的模型(对话模型)
from langchain_openai import ChatOpenAI, OpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
# ChatModel(推荐,现代模型都是对话模型)
chat_model = ChatOpenAI(model="gpt-4", temperature=0)
# 方式一:直接传字符串
response = chat_model.invoke("什么是 RAG?")
# 方式二:传消息列表(更灵活,能设定 system 角色)
messages = [
SystemMessage(content="你是一位耐心的 AI 老师,回答要简洁准确"),
HumanMessage(content="什么是 RAG?"),
]
response = chat_model.invoke(messages)
print(response.content)
print(f"Token 用量: {response.usage_metadata}")
聊天模型在底层使用语言模型,但暴露的接口不同:它们不暴露"文本输入文本输出"的 API,而是把聊天消息(ChatMessage)列表作为输入输出。这种设计让多轮对话和 system/human/ai 角色区分变得自然。
LangChain 支持按照 LLM 接口标准集成自定义的语言模型,提供统一的 API 调用不同的模型。切换模型只需改一行:
# 切换到 Anthropic Claude
from langchain_anthropic import ChatAnthropic
chat_model = ChatAnthropic(model="claude-sonnet-4-20250514")
# 切换到本地 Ollama
from langchain_ollama import ChatOllama
chat_model = ChatOllama(model="llama3")
# 业务代码完全不用改
3.1.2 提示模板(Prompt Templates)
提示模板是预定义的配方,用于为语言模型生成提示。模板可能包括说明、少量示例(few-shot)、适合给定任务的特定上下文和问题。
两种主要类型:
PromptTemplate:用于生成字符串提示,使用 Python 的字符串格式ChatPromptTemplate:用于生成聊天提示作为聊天消息列表
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
# 字符串模板(用于 LLM)
string_prompt = PromptTemplate.from_template(
"请把以下文本翻译成{language}:\n{text}"
)
# 聊天模板(用于 ChatModel,推荐)
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是一位专业的{role},回答要{style}"),
("human", "{question}"),
])
# 渲染
formatted = chat_prompt.invoke({
"role": "数据科学家",
"style": "深入浅出",
"question": "解释什么是梯度下降"
})
print(formatted)
# -> messages=[SystemMessage('你是一位专业的数据科学家...'), HumanMessage('解释什么是梯度下降')]
提示模板的目标是使跨不同模型重用提示变得容易,将提示工程与模型调用分开。这比你每次调用都手拼字符串要好得多——模板可以版本管理、A/B 测试、团队共享。
3.1.3 示例选择器(Example Selectors)
允许用户为模型提供示例输入和输出(few-shot),以帮助模型学习执行特定任务。用途包括训练新模型、调优现有模型、测试模型、说明模型能力、调试模型、控制模型行为。
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotChatMessagePromptTemplate
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
# 准备示例库
examples = [
{"input": "高兴", "output": "悲伤"},
{"input": "高大", "output": "矮小"},
{"input": "精力充沛", "output": "无精打采"},
{"input": "简单", "output": "复杂"},
{"input": "快", "output": "慢"},
]
# 语义相似度选择器:根据输入动态选择最相关的示例
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples, OpenAIEmbeddings(), Chroma(), k=2
)
# 构建 few-shot 提示
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_selector=example_selector,
example_prompt=ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}"),
]),
)
# 组合成完整提示
final_prompt = ChatPromptTemplate.from_messages([
("system", "给出输入词的反义词"),
few_shot_prompt,
("human", "{input}"),
])
chain = final_prompt | ChatOpenAI(model="gpt-4")
print(chain.invoke({"input": "热情"}).content)
3.1.4 输出解析器(Output Parsers)
语言模型输出内容是文本格式,但开发 AI 应用时希望能拿到格式化的内容(如目标对象、JSON、数组等)。输出解析器用于格式化语言模型返回的结果。
一个输出解析器必须实现两种必要的方法:
get_format_instructions:返回要求语言模型应该返回什么格式内容的提示词parse:将模型返回的内容解析为目标格式
可选方法:parse_with_prompt:接受响应和提示,处理重试或修复输出。
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# 定义期望的输出结构
class MovieReview(BaseModel):
title: str = Field(description="电影标题")
rating: int = Field(description="评分 1-10")
summary: str = Field(description="一句话影评")
pros: list[str] = Field(description="优点列表")
cons: list[str] = Field(description="缺点列表")
# 创建解析器
parser = PydanticOutputParser(pydantic_object=MovieReview)
# 解析器会自动生成格式说明,注入到提示里
prompt = ChatPromptTemplate.from_messages([
("system", "你是一位影评人。请按指定格式输出影评。\n{format_instructions}"),
("human", "请评价电影:{movie}"),
]).partial(format_instructions=parser.get_format_instructions())
chain = prompt | ChatOpenAI(model="gpt-4", temperature=0) | parser
# 拿到的是结构化对象,不是字符串
review = chain.invoke({"movie": "盗梦空间"})
print(f"电影: {review.title}, 评分: {review.rating}/10")
print(f"优点: {review.pros}")
输出解析器允许定义期望的输出结构(如 Pydantic 模型),然后解析语言模型的文本输出来填充该结构,可进行验证、访问特定字段等。这把"模型输出的是文本"这个根本性摩擦给抹平了。
Model I/O 完整流程图
用户输入 提示模板 模型 输出解析器
│ │ │ │
▼ ▼ ▼ ▼
{topic:"RAG"} → ChatPromptTemplate → ChatOpenAI → StrOutputParser
(注入变量+格式说明) (调用 LLM) (解析为 str/JSON/对象)
│
▼
结构化结果
3.2 数据连接(Data Connection)
在许多 LLM 应用中,用户特定的数据不在模型的训练集中,这需要通过检索增强生成(RAG)实现。RAG 的主要方法是检索外部数据,并在生成步骤中传递给 LLM。LangChain 为 RAG 应用程序提供了完整的构建块。
┌───────────────────────────────────────────────────────────┐
│ RAG 数据连接全流程 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │Document │──▶│Document │──▶│Text │ │
│ │Loaders │ │Transformers│ │Embedding │ │
│ │(加载) │ │(拆分/转换) │ │Models │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │Vector │ │
│ │Stores │ │
│ │(存储+检索) │ │
│ └────────────┘ │
│ │ │
│ ┌────────────┐ │ │
│ │Retrievers │◀────────────┘ │
│ │(检索接口) │ │
│ └────────────┘ │
│ │ │
│ ▼ │
│ Indexing API(避免重复写入/重算嵌入) │
└───────────────────────────────────────────────────────────┘
3.2.1 文档加载器(Document Loaders)
将来自不同数据源的非结构化文本加载为文档对象(含文本片段和元数据)。支持简单文本文件、网页内容、YouTube 视频转录、PDF 等。提供 load 方法,支持"延迟加载"以节省内存。
from langchain_community.document_loaders import (
TextLoader, WebBaseLoader, PyPDFLoader, DirectoryLoader
)
# 加载单个文本文件
loader = TextLoader("./knowledge_base.txt")
docs = loader.load()
# 加载网页
web_loader = WebBaseLoader("https://example.com/article")
web_docs = web_loader.load()
# 加载 PDF
pdf_loader = PyPDFLoader("./report.pdf")
pdf_docs = pdf_loader.load() # 每页一个 Document
# 批量加载目录下所有文件
dir_loader = DirectoryLoader("./docs/", glob="**/*.pdf",
loader_cls=PyPDFLoader)
all_docs = dir_loader.load()
print(f"共加载 {len(all_docs)} 个文档")
print(f"第一个文档元数据: {all_docs[0].metadata}")
3.2.2 文档转换器(Document Transformers)
对加载的文档进行转换和处理,主要包括文本拆分器、冗余过滤器、元数据提取器、多语言转换器、对话转换器。其中最常用的是文本拆分器,它将长文本拆分成语义上相关的小块,适应上下文窗口限制。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 递归字符拆分器:优先按段落、然后按句子、最后按字符拆分
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每块最大 1000 字符
chunk_overlap=200, # 块之间重叠 200 字符,保持上下文连贯
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
)
chunks = splitter.split_documents(all_docs)
print(f"拆分前 {len(all_docs)} 个文档 → 拆分后 {len(chunks)} 个块")
3.2.3 文本嵌入模型(Text Embedding Models)
将文本转换为向量表示,用于文本检索(语义搜索)、信息推荐、知识挖掘、语义匹配。LangChain 通过统一的 API 调用不同的文本嵌入模型。
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 嵌入单条文本
vector = embeddings.embed_query("LangChain 是一个 LLM 应用框架")
print(f"向量维度: {len(vector)}")
# 批量嵌入
texts = ["RAG 是检索增强生成", "Agent 能自主调用工具"]
vectors = embeddings.embed_documents(texts)
3.2.4 矢量存储(Vector Stores)
存储和搜索非结构化数据的最常见方法之一是嵌入它并存储生成的嵌入向量,然后在查询时嵌入查询并检索"最相似"的嵌入向量。
from langchain_chroma import Chroma
# 从文档块创建向量库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=OpenAIEmbeddings(),
persist_directory="./chroma_db", # 持久化到磁盘
)
# 相似度搜索
results = vectorstore.similarity_search(
"LangChain 的核心组件有哪些?",
k=3, # 返回最相似的 3 个
)
for doc in results:
print(doc.page_content[:100], "...")
# 带分数的搜索
results_with_score = vectorstore.similarity_search_with_score("什么是 RAG", k=3)
for doc, score in results_with_score:
print(f"分数: {score:.4f} | {doc.page_content[:80]}...")
3.2.5 检索器(Retrievers)
一种用于响应非结构化查询的接口,返回符合查询要求的文档。相较于矢量存储,检索器更加通用——它是一个接口,任何"给查询返回文档"的逻辑都能包装成检索器。
from langchain_core.retrievers import BaseRetriever
# 最简单:把向量库转成检索器
retriever = vectorstore.as_retriever(
search_type="similarity", # 也可用 mmr(最大边际相关性,去重)
search_kwargs={"k": 4},
)
docs = retriever.invoke("LangChain 的 Memory 组件")
print(f"检索到 {len(docs)} 个相关文档")
# 自定义检索器
class CustomRetriever(BaseRetriever):
def _get_relevant_documents(self, query):
# 这里可以混用:向量检索 + 关键词检索 + 规则过滤
return vectorstore.similarity_search(query, k=2)
LangChain 提供矢量检索器、文档检索器、网站研究检索器等,可自定义检索逻辑。主要作用是提高问答系统的覆盖面、提供额外的上下文、支持开放域问答。
3.2.6 索引(Indexing)
索引 API 能够将来自各种源的文档同步到矢量存储中,避免不必要的重复写入和重新计算嵌入,节省时间和金钱,改善矢量搜索结果。
from langchain.indexes import SQLRecordManager, index
# 记录管理器:追踪哪些文档已索引
record_manager = SQLRecordManager(
namespace="chroma/my_docs",
db_url="sqlite:///record_manager.db"
)
record_manager.create_schema()
# 执行索引(自动去重、增量更新)
result = index(
chunks,
record_manager,
vectorstore,
cleanup="incremental", # 增量模式:新增的写入,删除的移除
source_id_key="source",
)
print(f"新增: {result['num_added']}, 删除: {result['num_deleted']}, 更新: {result['num_updated']}, 跳过: {result['num_skipped']}")
一个完整 RAG 链
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
# 检索 + 生成
template = """请根据以下上下文回答问题。如果上下文中没有相关信息,请说"我不知道"。
上下文:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4", temperature=0)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 经典 RAG 链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("LangChain 的 Memory 组件有什么作用?")
print(answer)
3.3 链(Chains)
链允许将多个组件组合在一起,创建单一的、连贯的应用程序。例如,创建一个链接受用户输入,使用提示模板格式化,然后传递给 LLM。
LangChain 中主要链类型:
| 链类型 | 说明 |
|---|---|
| 基础链 LLMChain | 围绕语言模型添加功能,由 PromptTemplate 和 LLM/ChatModel 组成 |
| 路由链 RouterChain | 动态选择下一条链,含 LLMRouterChain 和 EmbeddingRouterChain |
| 顺序链 SequentialChain | 将多个链顺序连接,输出作为下一个链的输入;SimpleSequentialChain(单输入输出)和 SequentialChain(多输入输出) |
| 转换链 TransformChain | 在链之间添加自定义转换函数(清理、过滤、格式化数据) |
| 文档链 DocumentsChain | 将多个文档作为输入传递给下游链,支持合并、抽取、路由 |
# 传统 LLMChain(已被 LCEL 替代,但理解原理有价值)
from langchain.chains import LLMChain
chain = LLMChain(
llm=ChatOpenAI(model="gpt-4"),
prompt=ChatPromptTemplate.from_template("讲一个关于{subject}的笑话"),
)
print(chain.invoke({"subject": "程序员"}))
# 顺序链:第一步总结,第二步翻译
from langchain.chains import SimpleSequentialChain
summary_chain = LLMChain(
llm=ChatOpenAI(model="gpt-4"),
prompt=ChatPromptTemplate.from_template("用一句话总结:{text}")
)
translate_chain = LLMChain(
llm=ChatOpenAI(model="gpt-4"),
prompt=ChatPromptTemplate.from_template("把以下内容翻译成英文:{text}")
)
overall_chain = SimpleSequentialChain(
chains=[summary_chain, translate_chain], verbose=True
)
result = overall_chain.run("LangChain 是一个用于构建 LLM 应用的框架,提供六大核心组件...")
print(result)
链支持序列化到磁盘或从磁盘加载,可子类化自定义实现特定 NLP 任务。但注意:新版 LangChain 推荐用 LCEL(第四章)替代传统 Chains,传统 Chains 主要用于理解原理和维护旧代码。
3.4 记忆(Memory)
Memory 组件用于在链之间存储和传递信息,实现对话的上下文感知能力。
关键功能:
- 存储之前对话和验证信息的状态,用于后续链的输入
- 允许链访问和操作共享的内存,实现链之间的协作
- 支持不同的内存存储后端(字典、数据库等)
- 可存储各种数据类型(文本、图像、音频等)
- 实现对话系统的用户个性化、任务跟踪
- 存储链的中间执行状态,实现断点恢复
from langchain.chains import ConversationChain
from langchain.memory import (
ConversationBufferMemory, # 完整对话历史
ConversationBufferWindowMemory, # 只保留最近 N 轮
ConversationTokenBufferMemory, # 按 Token 数限制
ConversationSummaryMemory, # 摘要式记忆
)
# 1. 完整缓冲区记忆(简单但会撑爆上下文)
memory = ConversationBufferMemory()
# 2. 窗口记忆(只保留最近 5 轮,省 Token)
memory = ConversationBufferWindowMemory(k=5)
# 3. Token 限制记忆(按 Token 数截断)
memory = ConversationTokenBufferMemory(
llm=ChatOpenAI(model="gpt-4"),
max_token_limit=1000
)
# 4. 摘要记忆(把历史对话压缩成摘要)
memory = ConversationSummaryMemory(
llm=ChatOpenAI(model="gpt-4")
)
conversation = ConversationChain(
llm=ChatOpenAI(model="gpt-4", temperature=0),
memory=memory,
verbose=True,
)
# 多轮对话,记忆自动维护
conversation.predict(input="我叫小明,今年 25 岁")
conversation.predict(input="我喜欢 Python 编程")
print(conversation.predict(input="我叫什么?多大了?")) # 能回忆起前面说的
注意:在 LangGraph 时代,Memory 类的复杂状态管理能力已被 LangGraph 的 State + Checkpointer 机制取代(见第五章)。传统 Memory 主要用于简单的 Chain 场景。
3.5 代理(Agents)
代理的核心思想是使用 LLM 作为大脑自动思考,自动决策选择执行不同的动作,最终完成目标任务。
关键组件
| 组件 | 说明 |
|---|---|
| Agent(代理) | 通过 LLM 决定下一步执行什么动作,扮演决策角色 |
| Tools(工具) | 代理调用的函数或 API,需被正确描述以便代理调用 |
| Toolkits(工具集) | 一组可供选择的工具集,让 LLM 有更多能力和选择 |
| AgentExecutor(代理执行器) | 处理代理选择工具时的异常,提供日志记录和可观察性 |
定义工具
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气信息。输入城市名称,返回天气描述。"""
# 实际项目中这里调用真实天气 API
weather_map = {"北京": "晴 25°C", "上海": "多云 28°C", "广州": "雷雨 30°C"}
return weather_map.get(city, f"{city}:暂无天气数据")
@tool
def calculate(expression: str) -> str:
"""计算数学表达式的结果。输入如 '2+3*4',返回计算结果。"""
try:
result = eval(expression) # 生产环境请用安全的表达式解析器
return f"{expression} = {result}"
except Exception as e:
return f"计算失败:{e}"
@tool
def search_wiki(query: str) -> str:
"""搜索维基百科获取知识。输入搜索关键词。"""
return f"关于'{query}'的维基百科摘要:..."
tools = [get_weather, calculate, search_wiki]
工具描述(docstring)的质量直接决定 Agent 调用成功率。这和 Claude Code 的 Skill 机制有异曲同工之妙——后面会专门对比。
最新演进:create_agent
LangChain 最新版本提供 create_agent 作为最小化、高度可配置的 Agent harness:
from langchain.agents import create_agent
agent = create_agent(
model="openai:gpt-5.5",
tools=[get_weather, calculate, search_wiki],
system_prompt="你是一个能查天气、算数学、搜百科的助手,回答要简洁",
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "北京天气怎么样?2的10次方是多少?"}]}
)
# Agent 会自主决定调用哪些工具、按什么顺序调用
print(result["messages"][-1].content)
create_agent 的设计体现了 “Agent = Model + Harness” 理念:你提供模型、工具、提示词和中间件,框架负责把"模型循环"组织起来。支持多模型提供商:OpenAI、Google Gemini、Anthropic Claude、OpenRouter、Fireworks、Ollama、Azure、AWS Bedrock、HuggingFace 等。
工具绑定
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
# bind_tools 把工具信息注入模型,模型能"看到"工具并决定调用
llm_with_tools = llm.bind_tools([get_weather, calculate])
response = llm_with_tools.invoke("北京天气怎么样?")
if response.tool_calls:
for tc in response.tool_calls:
print(f"模型决定调用: {tc['name']}, 参数: {tc['args']}")
3.6 回调(Callbacks)
LangChain 提供了一个回调系统,允许连接到 LLM 申请的各个阶段。这对于日志记录、监控、流传输和其他任务(添加标签、计算 Token 等)非常有用。
from langchain_core.callbacks import BaseCallbackHandler
class MyCallbackHandler(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
print(f"[LLM 开始] 模型: {serialized.get('name')}")
def on_llm_new_token(self, token, **kwargs):
# 流式输出时,每个 token 都会触发
print(token, end="", flush=True)
def on_llm_end(self, response, **kwargs):
print(f"\n[LLM 结束] 耗时统计完成")
def on_tool_start(self, serialized, input_str, **kwargs):
print(f"[工具开始] {serialized['name']}: {input_str}")
def on_tool_end(self, output, **kwargs):
print(f"[工具结束] 返回: {output}")
def on_chain_error(self, error, **kwargs):
print(f"[链错误] {error}")
# 使用回调
llm = ChatOpenAI(model="gpt-4", streaming=True,
callbacks=[MyCallbackHandler()])
llm.invoke("用三句话介绍量子计算")
回调系统是 LangSmith 追踪的底层基础——LangSmith 本质上就是一套预配置的回调处理器,把每一步的输入输出、耗时、工具调用都记录下来。
第三章实践要点
- 六大组件的协作链路:Model I/O 负责"说",Data Connection 负责"找资料",Chains 负责"串",Memory 负责"记",Agents 负责"想",Callbacks 负责"观察"。
- 工具描述(docstring)质量决定 Agent 成败,把"什么场景该调我、参数怎么传"写清楚。
- 传统 Memory 在复杂场景已被 LangGraph State 取代,简单对话用 Memory 够了,复杂状态管理上 LangGraph。
- Callbacks 是可观测性的底层抓手,LangSmith 就是基于它实现的。
第四章 LCEL(LangChain Expression Language)
4.1 什么是 LCEL
LCEL(LangChain Expression Language)是 LangChain 框架中用于构建链式调用的表达式语言,通过管道符号 | 串联提示词模板、模型和输出解析器等组件。它以"管道"方式组合组件,是对传统 Chains 的现代化替代。
如果你用过 Unix 管道 cat file | grep "error" | wc -l,那么 LCEL 的心智模型完全一样:前一步的输出,自动成为后一步的输入。
4.2 Runnable:统一接口
LCEL 的基石是 Runnable 协议。所有 LCEL 组件(prompt、llm、parser、retriever,甚至自定义函数)都实现了 Runnable 接口,提供统一的方法:
┌─────────────────────────────────────────────────────────┐
│ Runnable 统一接口 │
├─────────────────────────────────────────────────────────┤
│ invoke(input) 同步单次调用 │
│ ainvoke(input) 异步单次调用 │
│ stream(input) 同步流式(逐块返回) │
│ astream(input) 异步流式 │
│ batch(inputs) 批量调用(并行处理多个输入) │
│ abatch(inputs) 异步批量 │
│ │
│ ├ | 管道组合:chain = prompt | llm | parser │
│ └ ~ 也能用 RunnablePassthrough 透传原输入 │
└─────────────────────────────────────────────────────────┘
这意味着:不管你组合多少个组件,最终得到的 chain 对象都有 invoke/stream/batch 方法。你不需要为流式单独写一套代码,也不需要为异步单独写一套——LCEL 在底层帮你处理了。
4.3 LCEL 核心优势
- 简化流式输出(Simplify streaming):LCEL 链可以被流式处理,允许在链执行时增量输出。LangChain 可以优化输出流式,最小化首 Token 时间(time-to-first-token)。
- 异步支持:LCEL 链天然支持异步执行,适用于高并发场景。
- 批量处理:支持批量处理多个输入。
- 并行执行:使用
RunnableParallel等组件实现并行。 - 回退机制:支持 fallback,当主流程失败时自动切换备用方案。
- 统一接口:所有 Runnable 组件遵循统一接口(invoke/ainvoke/stream/astream/batch)。
4.4 基本用法
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 用管道符 | 串联组件
prompt = ChatPromptTemplate.from_template("请解释什么是 {topic}")
llm = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()
chain = prompt | llm | parser
# 统一接口调用
result = chain.invoke({"topic": "LCEL"})
print(result)
# 流式调用(Token 级输出)
for chunk in chain.stream({"topic": "LCEL"}):
print(chunk, end="", flush=True)
# 批量调用
results = chain.batch([{"topic": "RAG"}, {"topic": "Agent"}, {"topic": "Memory"}])
for r in results:
print(r[:50], "...")
4.5 RunnableParallel 并行执行
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
summary_prompt = ChatPromptTemplate.from_template("总结:{input}")
keyword_prompt = ChatPromptTemplate.from_template("提取3个关键词:{input}")
sentiment_prompt = ChatPromptTemplate.from_template("判断情感(正面/负面/中性):{input}")
llm = ChatOpenAI(model="gpt-4", temperature=0)
parser = StrOutputParser()
# 三条链并行执行,共享同一个输入
parallel_chain = RunnableParallel(
summary=summary_prompt | llm | parser,
keywords=keyword_prompt | llm | parser,
sentiment=sentiment_prompt | llm | parser,
# RunnablePassthrough 透传原始输入
original=RunnablePassthrough(),
)
result = parallel_chain.invoke({
"input": "LangChain 是一个强大的 LLM 应用开发框架,社区活跃但学习曲线较陡。"
})
print(result["summary"])
print(result["keywords"])
print(result["sentiment"])
并行执行在多维度分析场景非常有用:同一段文本同时做摘要、关键词提取、情感分析,一次调用出三个结果。
4.6 RunnablePassthrough 透传
# 经典 RAG 模式:检索结果 + 原始问题一起喂给模型
rag_chain = (
RunnablePassthrough.assign(context=retriever | format_docs)
| prompt
| llm
| parser
)
# RunnablePassthrough.assign() 在不丢失原始输入的基础上,追加 context 字段
4.7 回退机制(Fallbacks)
from langchain_core.runnables import RunnableWithFallbacks
# 主模型失败时,自动切到备用模型
primary_llm = ChatOpenAI(model="gpt-4") # 可能限流
fallback_llm = ChatOpenAI(model="gpt-3.5-turbo") # 更便宜更稳定
llm_with_fallback = primary_llm.with_fallbacks([fallback_llm])
chain = prompt | llm_with_fallback | parser
# 如果 gpt-4 限流报错,自动用 gpt-3.5-turbo 重试
4.8 自定义 Runnable
from langchain_core.runnables import RunnableLambda
# 任何函数都能用 RunnableLambda 包装成 Runnable
def extract_user_intent(text: str) -> dict:
"""简单意图识别"""
if "?" in text or "?" in text:
return {"intent": "question", "text": text}
return {"intent": "statement", "text": text}
intent_runnable = RunnableLambda(extract_user_intent)
# 然后就能用管道符串
chain = intent_runnable | prompt | llm | parser
4.9 LCEL vs 传统 Chains
| 维度 | 传统 Chains | LCEL |
|---|---|---|
| 组合方式 | 类继承、预定义链 | 管道符 | 表达式 |
| 流式输出 | 需额外配置 | 天然支持 |
| 异步 | 需手动实现 | 自动支持 |
| 并行 | 复杂 | RunnableParallel 简洁 |
| 可读性 | 中等 | 高(声明式) |
| 灵活性 | 受限于预定义结构 | 完全自由组合 |
| 回退 | 需手写 try/except | with_fallbacks 一行搞定 |
同一个需求,两种写法对比
# === 传统 Chains 写法 ===
from langchain.chains import LLMChain
chain_old = LLMChain(
llm=ChatOpenAI(model="gpt-4"),
prompt=ChatPromptTemplate.from_template("翻译成英文:{text}"),
verbose=True,
)
result = chain_old.run(text="你好世界")
# === LCEL 写法 ===
chain_new = (
ChatPromptTemplate.from_template("翻译成英文:{text}")
| ChatOpenAI(model="gpt-4")
| StrOutputParser()
)
result = chain_new.invoke({"text": "你好世界"})
# 更短、更声明式、还免费获得了 stream/batch/async 能力
4.10 LCEL 的数据流图
invoke({"topic": "RAG"})
│
▼
┌─────────────────────────┐
│ ChatPromptTemplate │ 渲染模板
│ {topic} → "请解释RAG" │
└────────────┬────────────┘
│ messages
▼
┌─────────────────────────┐
│ ChatOpenAI │ 调用模型
│ → AIMessage("RAG是…") │
└────────────┬────────────┘
│ AIMessage
▼
┌─────────────────────────┐
│ StrOutputParser │ 解析输出
│ → "RAG是检索增强生成" │
└────────────┬────────────┘
│ str
▼
最终结果
stream() 时:每个 | 之间的输出都逐块传递,实现真正的流式
batch() 时:多个输入并行走完整条链
第四章实践要点
- LCEL 是新代码的首选,传统 Chains 只用于维护旧代码。
- 掌握 Runnable 统一接口(invoke/stream/batch/ainvoke),所有组件都能用同一套方法。
- RunnableParallel 做并行分析,RunnablePassthrough 做输入透传,with_fallbacks 做容错——这三个是 LCEL 的高频武器。
- 流式是"免费"的:只要你的链是 LCEL 组合的,
chain.stream()就能逐 Token 输出。
第五章 LangGraph 多 Agent 编排
5.1 什么是 LangGraph
LangGraph 是一个用于构建有状态、多步骤 AI 应用和自主智能体的低级编排框架。它将 Agent 工作流建模为有向图,通过节点(Nodes)和边(Edges)的组合,实现对复杂 AI 工作流的精确控制。
核心定位:LangGraph 专注于 Agent 编排(Orchestration),提供持久化执行、流式输出、人工干预等底层基础设施。
如果用一句话区分 LangChain 和 LangGraph:LangChain 提供积木,LangGraph 提供脚手架。积木让你能组合组件,脚手架让你能精确控制组件之间的执行流程、状态流转和容错恢复。
5.2 为什么需要 LangGraph
传统 Agent 框架(如 LangChain 的 ReAct Agent)存在以下局限:
| 问题 | 说明 | LangGraph 解决方案 |
|---|---|---|
| 流程不可控 | Agent 自主循环,难以精确控制 | 图结构定义流程,条件路由精确控制 |
| 状态管理弱 | 依赖 Memory 类,难以管理复杂状态 | 内置 State 机制,支持 Reducer 聚合 |
| 无持久化 | 进程崩溃后状态丢失 | Checkpoint 持久化,断点恢复 |
| 无法人工干预 | Agent 全自动,关键决策无法暂停 | interrupt() 动态中断 + Human-in-the-loop |
| 循环工作流难 | 依赖递归或 while 循环 | 图天然支持循环(边可指回已访问节点) |
5.3 LangGraph 架构全景
┌──────────────────────────────────────────────────────────────┐
│ LangGraph 架构全景 │
│ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ State │◀───│ Nodes │───▶│ Edges │ │
│ │ (状态) │ │ (节点) │ │ (边) │ │
│ └─────────┘ └──────────┘ └─────────┘ │
│ │ │ │ │
│ │ Reducer │ 条件路由 │ 普通边/循环边 │
│ │ (聚合策略) │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ StateGraph (有向图) │ │
│ │ compile() → 可执行图 │ │
│ └────────────────────┬────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Checkpoint│ │Interrupt │ │ Streaming│ │
│ │(持久化) │ │(人工干预)│ │ (流式) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 多 Agent 模式:Supervisor / Network / Reflection │
└──────────────────────────────────────────────────────────────┘
5.4 核心概念
5.4.1 状态(State)
State 是图的核心数据结构,表示应用在任意时刻的快照。所有节点读取和更新同一个 State。
定义方式:
- TypedDict(最常用)
- dataclass(支持默认值)
- Pydantic BaseModel(数据验证)
Reducer 机制决定节点返回的更新如何应用到 State——这是 LangGraph 区别于普通函数调用的关键:
| Reducer 类型 | 说明 | 示例 |
|---|---|---|
| 默认(无 Reducer) | 新值直接覆盖旧值 | current_step: str |
operator.add |
列表累加 | messages: Annotated[list, add] |
| 自定义 Reducer | 自定义合并逻辑 | 传入任意函数 |
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # 累加模式:新消息追加到列表
current_step: str # 覆盖模式:新值替换旧值
results: Annotated[list, operator.add] # 累加模式
为什么需要 Reducer?因为多个节点可能同时更新同一个字段。比如 messages,每个节点都会产生新消息,你希望它们累加而不是覆盖。如果没有 Reducer,后一个节点的返回会直接覆盖前一个,导致历史消息丢失。这是新手最常踩的坑之一。
5.4.2 节点(Nodes)
节点是图中的计算单元,封装 Agent 的逻辑。每个节点是一个函数,接收当前 State,执行计算,返回 State 更新。
核心原则:节点做工作,边决定下一步。
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4")
def analyze_node(state: AgentState):
"""分析用户输入,确定意图"""
user_msg = state["messages"][-1].content
intent = "search" if "查" in user_msg else "chat"
return {"current_step": intent} # 只返回要更新的字段
def llm_node(state: AgentState):
"""使用 LLM 生成回复"""
response = llm.invoke(state["messages"])
return {"messages": [response]} # Reducer 会自动累加到 messages
def search_node(state: AgentState):
"""执行检索"""
query = state["messages"][-1].content
docs = retriever.invoke(query)
context = "\n".join(d.page_content[:200] for d in docs)
response = llm.invoke([
HumanMessage(content=f"根据以下资料回答:{context}\n问题:{query}")
])
return {"messages": [response], "results": [context]}
5.4.3 边(Edges)
| 边类型 | 说明 | 示例 |
|---|---|---|
| 普通边 | 固定从一个节点到另一个节点 | A → B |
| 条件边 | 根据条件路由到不同节点 | A → B or C |
| START 边 | 图的入口 | START → A |
| END 边 | 图的出口 | A → END |
条件边是 LangGraph 的核心能力:
from langgraph.graph import StateGraph, START, END
def route_by_intent(state: AgentState) -> str:
"""根据意图路由到不同节点"""
if state["current_step"] == "search":
return "search_node"
else:
return "llm_node"
graph = StateGraph(AgentState)
graph.add_node("analyze", analyze_node)
graph.add_node("search_node", search_node)
graph.add_node("llm_node", llm_node)
graph.add_edge(START, "analyze")
graph.add_conditional_edges("analyze", route_by_intent, {
"search_node": "search_node",
"llm_node": "llm_node",
})
graph.add_edge("search_node", END)
graph.add_edge("llm_node", END)
5.4.4 编译与执行
图必须编译后才能使用:
# 基础编译
app = graph.compile()
# 带 Checkpointer 编译(启用持久化)
from langgraph.checkpoint.memory import InMemorySaver
app = graph.compile(checkpointer=InMemorySaver())
# 带断点编译(在指定节点前暂停)
app = graph.compile(checkpointer=checkpointer, interrupt_before=["approval_node"])
执行方法:invoke(同步)、stream(流式)、ainvoke(异步)、astream(异步流式)。
config = {"configurable": {"thread_id": "user-123"}}
result = app.invoke(
{"messages": [HumanMessage(content="帮我查一下 LangChain 是什么")]},
config=config,
)
print(result["messages"][-1].content)
5.4.5 完整图结构示例
┌─────────┐
│ START │
└────┬────┘
│
▼
┌─────────────┐
│ analyze │ 分析意图
└──────┬──────┘
│
条件路由 route_by_intent
┌────┴────┐
▼ ▼
┌──────────┐ ┌──────────┐
│search_node│ │ llm_node │
└─────┬────┘ └────┬─────┘
│ │
└─────┬──────┘
▼
┌─────────┐
│ END │
└─────────┘
5.5 持久化与记忆
Checkpointer 选型
| Checkpointer | 适用场景 | 持久化 | 性能 | 并发支持 |
|---|---|---|---|---|
| InMemorySaver | 开发调试 | 否(进程内) | 最快 | 单进程 |
| SqliteSaver | 本地/单机 | 是 | 中等 | 单进程 |
| PostgresSaver | 生产环境 | 是 | 高 | 多进程 |
两种记忆类型
| 记忆类型 | 实现方式 | 作用域 | 用途 |
|---|---|---|---|
| 短期记忆 | Checkpointer | 单线程(Thread) | 对话上下文 |
| 长期记忆 | BaseStore | 跨线程(全局) | 用户偏好、知识积累 |
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.memory import InMemoryStore
# 生产级持久化
checkpointer = PostgresSaver.from_conn_string("postgresql://...")
# 长期记忆存储(跨对话线程)
store = InMemoryStore() # 生产用 PostgresStore
# 记住用户偏好
store.put(("user", "123"), "preferences", {"language": "zh", "tone": "formal"})
app = graph.compile(checkpointer=checkpointer, store=store)
短期记忆(Checkpointer)让对话能在崩溃后恢复、能跨请求延续上下文;长期记忆(Store)让不同对话线程之间共享用户画像和知识积累。这是 Memory 类做不到的——Memory 只能在一个 Chain 实例内保持上下文。
5.6 人工干预(Human-in-the-Loop)
interrupt() 是 LangGraph 的核心人工干预机制,可在节点内任意位置暂停执行:
from langgraph.types import interrupt, Command
def approval_node(state: AgentState):
# 暂停执行,等待人工审核
approved = interrupt("请确认是否执行此操作?")
if approved:
return {"approved": True, "result": "操作已执行"}
else:
return {"approved": False, "result": "操作已取消"}
# 第一次调用:遇到 interrupt 暂停
config = {"configurable": {"thread_id": "approval-1"}}
result = app.invoke({"messages": [HumanMessage("删除数据库")]}, config=config)
# 此时执行暂停在 approval_node,等待人工输入
# 恢复执行:传入人工审核结果
result = app.invoke(Command(resume=True), config=config)
人工干预的典型场景:
- 危险操作审核:删除、支付、发邮件等不可逆操作前暂停
- 关键决策确认:模型选了方案 A,让人确认是否采纳
- 内容审核:生成的内容发布前让人过目
5.7 流式输出
LangGraph 提供多种流模式,满足不同场景需求:
| 模式 | 说明 | 适用场景 |
|---|---|---|
values |
每步之后的完整 State | 调试全貌 |
updates |
每步之后的 State 增量更新 | 调试状态变化 |
messages |
LLM Token 级别流式输出 | 用户交互(打字机效果) |
custom |
节点自定义流式数据 | 进度条、中间结果 |
checkpoints |
检查点事件 | 持久化监控 |
tasks |
任务开始/完成事件 | 任务调度监控 |
# Token 级流式(用户看到打字机效果)
for event in app.stream(
{"messages": [HumanMessage("讲个故事")]},
config=config,
stream_mode="messages",
):
# event 是 (message, metadata) 元组
msg, meta = event
if msg.content:
print(msg.content, end="", flush=True)
# 状态增量流式(调试用)
for event in app.stream(
{"messages": [HumanMessage("查 LangChain")]},
config=config,
stream_mode="updates",
):
for node_name, update in event.items():
print(f"[节点 {node_name}] 更新: {list(update.keys())}")
5.8 多 Agent 架构模式
模式一:Supervisor 模式(主管模式)
一个主管 Agent 负责任务调度,将任务分配给不同的专家 Agent:
def supervisor(state: AgentState):
"""主管 Agent:决定将任务分配给哪个专家"""
response = llm.invoke([
SystemMessage(content="""你是一个任务调度器。
可选专家:researcher(查资料)、writer(写文章)、reviewer(审稿)。
只返回专家名字。"""),
*state["messages"]
])
return {"next_agent": response.content.strip().lower()}
def researcher(state: AgentState):
"""研究员 Agent"""
docs = retriever.invoke(state["messages"][-1].content)
return {"messages": [AIMessage(content=f"研究完成:{docs[0].page_content[:200]}")]}
def writer(state: AgentState):
"""写作 Agent"""
response = llm.invoke(state["messages"])
return {"messages": [response]}
def reviewer(state: AgentState):
"""审稿 Agent"""
response = llm.invoke([
SystemMessage(content="你是审稿人,给出修改建议"),
*state["messages"]
])
return {"messages": [response]}
# 构建图
graph = StateGraph(AgentState)
graph.add_node("supervisor", supervisor)
graph.add_node("researcher", researcher)
graph.add_node("writer", writer)
graph.add_node("reviewer", reviewer)
graph.add_edge(START, "supervisor")
graph.add_conditional_edges("supervisor", lambda s: s["next_agent"], {
"researcher": "researcher",
"writer": "writer",
"reviewer": "reviewer",
"FINISH": END,
})
# 每个专家做完回到主管
graph.add_edge("researcher", "supervisor")
graph.add_edge("writer", "supervisor")
graph.add_edge("reviewer", "supervisor")
app = graph.compile()
Supervisor 模式结构图:
┌────────────┐
│ Supervisor │◀─────────────┐
└─────┬──────┘ │
条件路由 │ │
┌────────┬───┴────┐────────┐ │
▼ ▼ ▼ ▼ │
┌──────┐┌──────┐┌──────┐┌──────┐ │
│researcher│writer│reviewer│ FINISH │ │
└──┬───┘└──┬───┘└──┬───┘└──────┘ │
└────────┴───────┴──────────────────┘
(每个专家做完回到主管,由主管决定下一步)
模式二:Network 模式(多 Agent 协作)
多个 Agent 并行执行,由综合器合并结果:
graph.add_edge(START, "researcher_a") # 并行执行
graph.add_edge(START, "researcher_b") # 并行执行
graph.add_edge("researcher_a", "synthesizer")
graph.add_edge("researcher_b", "synthesizer")
graph.add_edge("synthesizer", END)
┌─────────┐
│ START │
└──┬───┬──┘
┌────┘ └────┐
▼ ▼
┌──────────┐ ┌──────────┐ 并行
│researcher│ │researcher│ 执行
│ _a │ │ _b │
└────┬─────┘ └────┬─────┘
└────┬───┬───┘
▼ ▼
┌──────────┐
│synthesizer│ 合并结果
└─────┬────┘
▼
┌────────┐
│ END │
└────────┘
模式三:反思与自我改进 Agent
通过"生成→批评→修改→再批评"的循环实现自我改进:
def should_continue(state: AgentState) -> str:
if state.get("iteration_count", 0) >= 3:
return "finalize"
if state.get("critique_approved", False):
return "finalize"
return "revise"
graph.add_edge(START, "generate")
graph.add_edge("generate", "critique")
graph.add_conditional_edges("critique", should_continue, {
"revise": "revise",
"finalize": "finalize"
})
graph.add_edge("revise", "critique") # 循环
graph.add_edge("finalize", END)
START → generate → critique ──┬── revise ──┐
▲ │ │
│ ▼ │
└──────────┘ (循环) │
│ │
should_continue │
│ │
┌──────────┴──────────┐ │
▼ ▼ │
revise finalize → END
5.9 LangGraph vs 其他编排框架
| 特性 | LangGraph | Temporal + AI | CrewAI | AutoGen |
|---|---|---|---|---|
| 编排方式 | 有向图 | 工作流引擎 | 角色协作 | 对话驱动 |
| 状态管理 | 内置 State + Checkpoint | 外部持久化 | 有限 | 有限 |
| 人工干预 | 原生支持 | 支持 | 不支持 | 部分 |
| 灵活性 | 极高(低级 API) | 高 | 中 | 中 |
| 学习曲线 | 较陡 | 陡 | 平缓 | 平缓 |
| LLM 集成 | LangChain 生态 | 需自建 | 内置 | 内置 |
5.10 仿 Dify 工作流系统
LangGraph 可以实现类 Dify 的可视化工作流引擎,核心是"DSL 驱动 + 动态图构建"架构:
| Dify 节点类型 | LangGraph 实现方式 |
|---|---|
| start | 图的输入 State |
| llm | LLM 节点函数 |
| knowledge-retrieval | LangChain VectorStore 节点 |
| code | exec() 沙箱节点 |
| http-request | requests/httpx 节点 |
| if-else | 条件边 add_conditional_edges() |
| human-review | interrupt() + Command(resume=…) |
| loop/iteration | 循环边 + 计数器 |
| answer | END 节点 |
# 用 YAML DSL 定义工作流,动态解析为 LangGraph 图
import yaml
workflow_yaml = """
nodes:
- id: start
type: input
- id: retrieve
type: knowledge-retrieval
config:
vectorstore: chroma
k: 3
- id: generate
type: llm
config:
model: gpt-4
prompt_template: "根据资料回答:{context}\\n问题:{question}"
- id: review
type: human-review
- id: answer
type: output
edges:
- {from: start, to: retrieve}
- {from: retrieve, to: generate}
- {from: generate, to: review}
- {from: review, to: answer}
"""
# 动态构建图(伪代码,展示思路)
def build_graph_from_dsl(dsl):
spec = yaml.safe_load(dsl)
graph = StateGraph(AgentState)
for node in spec["nodes"]:
graph.add_node(node["id"], create_node_function(node))
for edge in spec["edges"]:
graph.add_edge(edge["from"], edge["to"])
return graph.compile(checkpointer=PostgresSaver(...))
app = build_graph_from_dsl(workflow_yaml)
通过 YAML DSL 定义工作流配置,动态解析为 LangGraph 图,实现可视化工作流编排。这种架构让你能把工作流配置和代码解耦——产品经理在界面上拖拽节点生成 YAML,后端解析成图执行。
第五章实践要点
- LangGraph 的心智模型:节点做工作,边决定下一步。把流程画成图,问题就清晰了。
- Reducer 是最常踩的坑:并行节点更新同一字段必须用累加 Reducer,否则后写覆盖前写。
- 生产环境必用 PostgresSaver,InMemorySaver 只用于开发调试。
- interrupt() + Command(resume=…) 是人工干预的标准范式,危险操作前必须暂停。
- 仿 Dify 的 DSL 驱动架构能让工作流可配置化,适合需要频繁调整流程的场景。
第六章 LangSmith 调试与监控
6.1 什么是 LangSmith
LangSmith 是 LangChain 生态系统的可观测性平台,提供 Agent 和 LLM 应用的完整可观测性。一个关键特性是:它框架无关——支持追踪首选框架,或通过 Python、TypeScript、Go、Java SDK 集成任何 Agent 技术栈。即使你不用 LangChain,只用原生 OpenAI SDK 手搓 Agent,LangSmith 依然能帮你追踪。
6.2 为什么需要 LangSmith
Agent 应用的调试难度远超传统软件。一个 Agent 可能跑了十步:调了 3 次模型、2 次工具、1 次检索,最后输出一个错误答案。到底哪一步出了问题?没有追踪,你只能靠猜。
没有 LangSmith: 有 LangSmith:
┌────────────────────┐ ┌────────────────────────────┐
│ 用户提问 │ │ 用户提问 │
│ ↓ │ │ ↓ │
│ ??? 黑盒 ??? │ │ Step1: analyze (0.2s) ✓ │
│ ↓ │ │ Step2: search (1.3s) ✓ │
│ 错误答案 │ │ Step3: llm (2.1s) ✗ 格式错误│
│ │ │ ↓ 原因:提示词缺格式约束 │
│ 只能瞎猜哪错了 │ │ 错误答案 → 精确定位到 Step3 │
└────────────────────┘ └────────────────────────────┘
6.3 核心能力
| 能力 | 说明 |
|---|---|
| 追踪(Tracing) | 检查追踪、工具调用、状态转换和延迟 |
| 调试(Debugging) | 定位失败模式,查找问题根因 |
| 评估(Evaluation) | 评估输出质量,改进 Agent 行为 |
| 监控(Monitoring) | LangSmith Engine 监控追踪、检测问题、提出修复建议 |
| 部署(Deployment) | 支持 AI Agent 应用的 CI/CD 管道 |
| 数据集管理 | 跟踪数据样本或上传自定义数据集 |
6.4 快速上手
import os
# 设置追踪环境变量(只需设置一次,全局生效)
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "your-langsmith-key"
os.environ["LANGSMITH_PROJECT"] = "langchain-project"
# 设置后,所有 LangChain/LangGraph 的调用都会自动被追踪
# 不需要改任何业务代码
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
response = llm.invoke("什么是 RAG?")
# ↑ 这次调用会自动出现在 LangSmith 平台,含完整输入输出和耗时
设置后,所有 LangChain/LangGraph 的调用都会自动被追踪,可在 LangSmith 平台查看:
- 每个步骤的模型输入输出
- 调用性能问题定位
- 工具调用详情
- 状态转换过程
- 延迟分析
6.5 追踪一个完整 Agent
# 定义 Agent
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def search_db(query: str) -> str:
"""搜索数据库"""
return f"查询结果:{query} 的数据"
agent = create_agent(
model="openai:gpt-4",
tools=[search_db],
system_prompt="你是一个数据查询助手",
)
# 执行(自动追踪)
result = agent.invoke({
"messages": [{"role": "user", "content": "查一下上月销售额"}]
})
# 在 LangSmith 平台你会看到这样的追踪树:
#
# Agent Run (total: 3.2s)
# ├── Step 1: LLM 决策 (1.1s)
# │ └── 输出:调用 search_db("上月销售额")
# ├── Step 2: 工具执行 search_db (0.3s)
# │ └── 返回:"查询结果:上月销售额的数据"
# ├── Step 3: LLM 总结 (1.5s)
# │ └── 输出:"上月销售额为..."
# └── Step 4: 返回最终回复 (0.3s)
6.6 评估与数据集
from langsmith import Client
from langsmith.evaluation import evaluate
client = Client()
# 创建数据集
dataset = client.create_dataset("rag-eval-v1")
# 添加测试用例
client.create_examples(
inputs=[
{"question": "LangChain 的核心组件有哪些?"},
{"question": "什么是 LCEL?"},
{"question": "LangGraph 和 LangChain 的区别?"},
],
outputs=[
{"answer": "Model I/O, Data Connection, Chains, Memory, Agents, Callbacks"},
{"answer": "LangChain Expression Language,用管道符组合组件"},
{"answer": "LangChain 提供积木,LangGraph 提供图编排脚手架"},
],
dataset_id=dataset.id,
)
# 评估你的 RAG 链
def accuracy_evaluator(run, example):
"""自定义评估器:检查答案是否包含关键信息"""
prediction = run.outputs.get("output", "")
expected = example.outputs.get("answer", "")
score = 1.0 if any(kw in prediction for kw in expected.split()) else 0.0
return {"key": "accuracy", "score": score}
results = evaluate(
lambda inputs: rag_chain.invoke(inputs["question"]),
data="rag-eval-v1",
evaluators=[accuracy_evaluator],
)
print(f"准确率: {results['aggregate_metrics']['accuracy']:.1%}")
6.7 LangSmith Engine
LangSmith Engine 会自动监控你的追踪,检测问题,并提出修复建议。它不仅能发现问题,还能主动帮助改进 Agent 行为。例如:
- 检测到某个工具调用频繁失败 → 建议检查工具描述
- 检测到某步耗时异常高 → 标记为性能瓶颈
- 检测到输出格式不稳定 → 建议加强输出解析器
6.8 LangSmith 与 Callbacks 的关系
LangSmith 的追踪底层就是基于 Callbacks 实现的。当你设置 LANGSMITH_TRACING=true,LangSmith 会自动注册一套回调处理器,拦截 LLM 调用、工具调用、链执行的每一个事件,上报到 LangSmith 平台。所以第四章讲的 Callbacks 是"手动版"的可观测性,LangSmith 是"自动版 + 平台化"的可观测性。
第六章实践要点
- LangSmith 是横切所有层的,不管你用 LangChain、LangGraph 还是手搓,都建议挂上。
- 只需设置环境变量就能自动追踪,零代码侵入,没理由不用。
- 评估要建数据集做回归测试,Agent 改了提示词后跑一遍评估集,量化质量变化。
- LangSmith Engine 的自动问题检测能帮你发现"人肉看不过来"的异常模式。
第七章 LangServe / LangDeployment
7.1 LangServe 现状
重要提示:LangServe 已于 2024 年 11 月 18 日正式弃用。官方推荐新项目使用 LangSmith Deployment(原 LangGraph Platform,2025 年 10 月更名)作为 Agent 部署方案。
7.2 为什么 LangServe 被弃用
LangServe 当初的设计是把 Chain 封装成 FastAPI 服务,简单直接。但随着 Agent 应用变复杂,出现了 LangServe 难以处理的需求:
- 有状态工作流:LangGraph 的 Checkpointer 需要跨请求恢复状态,LangServe 的无状态 API 模型不匹配
- 流式输出:LangGraph 的多种 stream_mode 需要更复杂的流式协议
- 人工干预:interrupt/resume 需要请求挂起和恢复机制,简单 REST API 做不到
- 持久化:生产级 Agent 需要数据库持久化,LangServe 不内置
于是官方推出了 LangGraph Platform(后更名 LangDeployment),专门为有状态 Agent 设计。
7.3 LangDeployment(新部署方案)
LangDeployment(原 LangGraph Platform)是官方推荐的部署方案,将 Chain/Graph 封装为稳定 API 服务。
部署能力
| 能力 | 说明 |
|---|---|
| Server | 将 LangGraph 应用部署为 REST API 服务 |
| Studio | 可视化调试和监控界面 |
| Cloud | 云端托管部署 |
| 自托管 | 支持私有化部署 |
7.4 部署一个 LangGraph 应用
# langgraph.json - 部署配置文件
{
"dependencies": ["."],
"graphs": {
"my_agent": "./app/agent.py:graph"
},
"env": ".env"
}
# app/agent.py
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.postgres import PostgresSaver
# ... 定义 State、Nodes、Edges(同第五章)...
graph = StateGraph(AgentState)
# ... add_node / add_edge ...
# 编译成可部署的图
checkpointer = PostgresSaver.from_conn_string(os.environ["DATABASE_URL"])
app = graph.compile(checkpointer=checkpointer)
部署命令:
# 安装 LangGraph CLI
pip install langgraph-cli
# 本地开发运行
langgraph dev
# 构建部署镜像
langgraph build -t my-agent:latest
# 部署到 LangGraph Cloud
langgraph deploy
7.5 调用部署后的 API
import requests
# 同步调用
response = requests.post(
"http://localhost:8000/threads/my-agent/runs",
json={
"assistant_id": "my_agent",
"input": {"messages": [{"role": "user", "content": "你好"}]},
"config": {"configurable": {"thread_id": "session-1"}},
}
)
print(response.json())
# 流式调用
import sseclient
response = requests.post(
"http://localhost:8000/threads/my-agent/runs/stream",
json={
"assistant_id": "my_agent",
"input": {"messages": [{"role": "user", "content": "讲个故事"}]},
"stream_mode": "messages",
},
stream=True,
)
client = sseclient.SSEClient(response)
for event in client.events():
print(event.data, end="", flush=True)
7.6 LangDeployment 部署架构
┌──────────────────────────────────────────────────────────┐
│ LangDeployment 部署架构 │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Client │───▶│ API Server │───▶│ LangGraph │ │
│ │ (前端/SDK)│ │ (REST+SSE) │ │ App (图) │ │
│ └──────────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ ┌────────┴────────┐ │
│ │ ▼ ▼ │
│ ┌──────┴───────┐ ┌──────────┐ ┌────────┐│
│ │ Studio │ │PostgreSQL│ │ LLM ││
│ │ (可视化调试) │ │(持久化) │ │ Provider││
│ └──────────────┘ └──────────┘ └────────┘│
│ │
│ 横切:LangSmith 追踪贯穿所有层 │
└──────────────────────────────────────────────────────────┘
7.7 部署选型建议
- 简单 Demo / 轻量 RAG:只用 LangChain + LangSmith 做调试,无需 LangGraph,直接用 FastAPI 包一层即可
- 生产级 Agent:使用 LangDeployment 将 Chain/Graph 封装为稳定 API 服务
- CI/CD 管道:LangSmith 部署 API 支持全面的 CI/CD 管道
第七章实践要点
- LangServe 已弃用,新项目直接上 LangDeployment。
- 生产级 Agent 必须用 LangDeployment + PostgresSaver,别用 InMemorySaver 上生产。
- Studio 可视化调试是部署阶段的神器,能看到图的实时执行过程。
- 简单场景不必上 LangDeployment,FastAPI 包一层 LCEL 链就够了。
第八章 框架选型与对比
8.1 LangChain vs LlamaIndex
这是最常见的框架选型对比。两者定位不同,各有优势。
核心定位对比
| 维度 | LlamaIndex | LangChain |
|---|---|---|
| 主要关注点 | 高效组织和检索信息 | 连接不同的 AI 工具和流程 |
| 主要用例 | 构建可搜索的信息数据库 | 创建可执行多个任务的复杂 AI 系统 |
| 数据处理 | 专注于组织不同类型的数据 | 可以处理数据,但不是主要优势 |
| 集成 | 与现有数据协同工作 | 更擅长连接不同的 AI 工具 |
| 复杂性 | 基本任务更容易使用 | 提供更多选项,学习更难 |
| 查询优化 | 内置功能加快和改善搜索 | 通常需要手动优化搜索 |
| 自定义 | 更少的更改选项 | 允许广泛的自定义 |
| 学习曲线 | 通常可以快速学习 | 需要更多时间掌握 |
关键差异点
| 特性 | LlamaIndex | LangChain |
|---|---|---|
| 数据索引 | 快速组织和分类大量信息,高效转化为嵌入 | 模块化架构,设计定制解决方案 |
| 排名算法 | 根据语义相似性对文档排名 | 将检索算法与 LLM 集成,生成上下文感知输出 |
| 性能效率 | 优先优化数据检索,快速准确访问 | 强调灵活性和集成性 |
| 上下文保留 | 基本上下文保留,不适合长时间交互 | 先进上下文保留,适合长复杂对话 |
| 自定义 | 主要集中在索引和检索任务 | 支持复杂工作流,广泛自定义选项 |
选型建议
| 需求 | 推荐框架 |
|---|---|
| 高效的索引和检索(RAG 核心) | LlamaIndex |
| 灵活性和创造性生成 | LangChain |
| 构建可搜索的信息数据库 | LlamaIndex |
| 创建可执行多个任务的复杂 AI 系统 | LangChain |
| 需要长对话上下文的聊天机器人 | LangChain |
| 需要快速数据访问和简单搜索 | LlamaIndex |
| 高度定制化需求 | LangChain |
| 快速上手 | LlamaIndex |
一句话总结:LlamaIndex 是数据检索专家,LangChain 是流程编排通才。如果你的核心痛点是"怎么把海量文档高效检索出来",LlamaIndex 更专业;如果你要搭一个"能查资料、能调工具、能多轮对话"的复杂 Agent,LangChain 更合适。当然,两者可以混用——用 LlamaIndex 做检索层,用 LangChain 做编排层。
8.2 多 Agent 框架对比
2026 年最热门的 Agent 编排框架对比:
| 框架 | 核心理念 | 优势 | 适用场景 |
|---|---|---|---|
| LangGraph | 多 Agent 协作建模为有向图,节点是 Agent/工具,边是状态流转 | 持久化执行、人工干预、状态管理强 | 复杂有状态工作流 |
| CrewAI | 企业级工作流和工具集成,角色协作 | 易用、内置工具集成 | 企业级 Agent 工作流 |
| AutoGen | 对话驱动的多 Agent | 平缓学习曲线、内置 LLM 集成 | 对话式 Agent 协作 |
| AgentX(华为云开源) | 中文支持完善 | 国内生态 | 国内企业场景 |
LangGraph vs CrewAI
- LangGraph:底层、灵活、学习曲线陡。你要画图、定义状态、写 reducer。适合需要精确控制流程的复杂场景。
- CrewAI:上层、易用、学习曲线平缓。你定义角色和任务,框架帮你编排。适合快速搭建企业级工作流。
选型原则:流程确定性高、需要精确控制 → LangGraph;快速验证、角色协作为主 → CrewAI。
LangGraph vs AutoGen
- LangGraph:图驱动,状态在节点间流转,适合有明确流程的工作流。
- AutoGen:对话驱动,Agent 之间通过对话协作,适合探索性、开放式协作。
8.3 LangChain vs Semantic Kernel
Semantic Kernel 是微软开源的 AI 编排框架,与 LangChain 类似但生态不同:
| 维度 | LangChain | Semantic Kernel |
|---|---|---|
| 语言 | Python/JS/TS | C#/Python/Java |
| 生态 | LangGraph/LangSmith 全家桶 | 微软 Azure 生态 |
| 定位 | Agent 框架 + 编排运行时 | AI 编排 + 插件系统 |
| 优势 | 社区活跃、组件丰富 | 与 .NET 深度集成 |
8.4 框架选型决策树
你的需求是什么?
│
├─ 需要开箱即用的 Agent(含上下文压缩、文件系统、子 Agent)
│ └─→ Deep Agents
│
├─ 需要高度可定制的单 Agent
│ └─→ LangChain (create_agent)
│
├─ 需要复杂有状态工作流 / 多 Agent 编排
│ └─→ LangGraph
│
├─ 需要高效数据索引和检索(RAG 核心)
│ └─→ LlamaIndex(或 LangChain Retrievers)
│
├─ 需要追踪、调试、评估
│ └─→ LangSmith(适用于以上所有框架)
│
├─ 需要部署为 API 服务
│ └─→ LangDeployment(原 LangGraph Platform)
│
├─ 团队是 .NET 技术栈,深度依赖 Azure
│ └─→ Semantic Kernel
│
├─ 快速搭建企业级角色协作 Agent,不想学图编程
│ └─→ CrewAI
│
├─ 对话式多 Agent 协作,探索性强
│ └─→ AutoGen
│
└─ 国内企业场景,需要完善中文支持
└─→ AgentX(华为云)
8.5 什么时候该手搓(不用框架)
框架不是银弹。以下场景,手搓可能比用框架更好:
- 极简需求:只是调一次模型 API,一个
requests.post搞定,引入 LangChain 反而是负担 - 极致性能:框架的抽象层有开销,高 QPS 场景手搓更可控
- 特殊流程:你的工作流极其特殊,框架的抽象反而碍事
- 学习目的:手搓一遍 ReAct 循环,能深刻理解框架在帮你做什么
# 手搓一个最小 ReAct Agent(不用任何框架)
import openai
def react_agent(question, tools, max_iter=5):
"""手搓 ReAct:思考→行动→观察→思考..."""
messages = [
{"role": "system", "content": f"""你是一个能使用工具的助手。
可用工具:{tools}
当你要使用工具时,输出 JSON:{{"action": "工具名", "input": "参数"}}
当你有最终答案时,输出:{{"action": "final_answer", "input": "答案"}}"""},
{"role": "user", "content": question},
]
for i in range(max_iter):
resp = openai.chat.completions.create(model="gpt-4", messages=messages)
thought = resp.choices[0].message.content
messages.append({"role": "assistant", "content": thought})
# 解析 action 并执行(省略解析逻辑)
if "final_answer" in thought:
return thought
# 执行工具,把结果作为 observation 喂回去
messages.append({"role": "user", "content": f"观察结果:{tool_result}"})
return "达到最大迭代次数"
手搓的价值在于理解原理。知道框架在帮你做什么,你才能判断什么时候该用框架、什么时候该绕过。
第八章实践要点
- LlamaIndex 擅长检索,LangChain 擅长编排,两者可混用。
- LangGraph 灵活但陡,CrewAI 易用但浅,按"确定性控制需求"选。
- 决策树从需求出发,不要"先选框架再找需求"。
- 极简需求手搓更干净,框架的抽象层在简单场景是负担。
- 手搓一遍 ReAct 循环是理解 Agent 的最佳方式。
第九章 最佳实践与工程落地
9.1 状态设计最佳实践
- 使用 TypedDict 定义 State,清晰且类型安全
- 合理使用 Reducer:消息列表用
operator.add累加,配置类用覆盖模式 - 使用多 Schema 设计(InputState/OutputState/OverallState)分离关注点
- 并行节点使用自定义 Reducer 避免覆盖问题
from typing import TypedDict, Annotated
import operator
# 多 Schema 设计:分离输入、输出、内部状态
class InputState(TypedDict):
"""用户输入的 State(只含用户能提供的字段)"""
question: str
class OutputState(TypedDict):
"""输出给用户的 State(只含需要返回的字段)"""
answer: str
sources: list[str]
class OverallState(InputState, OutputState):
"""内部完整 State(含所有中间字段)"""
messages: Annotated[list, operator.add]
retrieved_docs: Annotated[list, operator.add]
iteration_count: int # 覆盖模式
# 自定义 Reducer:解决并行节点覆盖问题
def merge_dicts(left, right):
"""并行节点的 dict 合并:递归合并而非覆盖"""
if left is None:
return right
if right is None:
return left
result = {**left}
for k, v in right.items():
if k in result and isinstance(result[k], list):
result[k] = result[k] + v
else:
result[k] = v
return result
class ParallelState(TypedDict):
# 多个并行节点都写这个字段,用自定义 Reducer 合并
results: Annotated[dict, merge_dicts]
9.2 持久化最佳实践
- 开发阶段:InMemorySaver(最快)
- 单机生产:SqliteSaver
- 多进程/分布式生产:PostgresSaver(推荐)
- 长期记忆使用 BaseStore(跨线程共享用户偏好、知识积累)
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.postgres import PostgresStore
from contextlib import asynccontextmanager
# 生产级配置
DB_URI = "postgresql://user:pass@localhost:5432/langgraph"
# 持久化 + 长期记忆
checkpointer = PostgresSaver.from_conn_string(DB_URI)
store = PostgresStore.from_conn_string(DB_URI)
app = graph.compile(checkpointer=checkpointer, store=store)
# 长期记忆:记住用户偏好,跨对话线程
def save_user_preference(user_id, key, value):
store.put(("user", user_id), key, {"value": value})
def get_user_preference(user_id, key):
item = store.get(("user", user_id), key)
return item.value.get("value") if item else None
9.3 流式输出最佳实践
- 用户交互场景优先使用
stream_mode="messages"实现 Token 级流式 - 调试场景使用
stream_mode="updates"观察状态变化 - 使用
get_stream_writer()发送自定义进度数据 - LangGraph >= 1.1 推荐使用
version="v2"格式
from langgraph.config import get_stream_writer
def long_running_node(state):
"""模拟长时间运行节点,发送自定义进度"""
writer = get_stream_writer()
steps = ["加载模型", "检索文档", "生成回复", "格式化输出"]
for i, step in enumerate(steps):
writer({"progress": f"步骤 {i+1}/{len(steps)}: {step}"})
# ... 实际工作 ...
return {"answer": "完成"}
# 客户端接收自定义流
for mode, chunk in app.stream(
{"question": "复杂问题"},
config=config,
stream_mode=["messages", "custom"],
):
if mode == "custom":
print(f"[进度] {chunk.get('progress')}")
elif mode == "messages":
msg = chunk[0]
if msg.content:
print(msg.content, end="", flush=True)
9.4 人工干预最佳实践
- 关键决策使用
interrupt()暂停等待人工审核 - 危险工具调用(删除、支付、发邮件)执行前审核
- 使用
Command(resume=...)恢复执行 - 并行多中断使用
resume_map一次性恢复
from langgraph.types import interrupt, Command
def dangerous_action_node(state):
"""执行危险操作前的审核"""
action_desc = f"即将执行:{state['pending_action']}"
# interrupt 暂停,把决策权交给人类
approval = interrupt({
"type": "action_approval",
"description": action_desc,
"severity": "high",
})
if approval == "approved":
# 执行危险操作
result = execute_dangerous_action(state)
return {"result": result, "status": "executed"}
elif approval == "modified":
# 人类修改了操作
result = execute_dangerous_action(state, modified=True)
return {"result": result, "status": "executed_modified"}
else:
return {"result": "操作被拒绝", "status": "rejected"}
# 恢复执行
result = app.invoke(Command(resume="approved"), config=config)
9.5 可观测性最佳实践
- 必须配置 LangSmith 追踪(
LANGSMITH_TRACING=true) - 设置 LangSmith Engine 自动监控和问题检测
- 利用追踪数据定位性能瓶颈和失败模式
- 用数据集做回归测试,评估 Agent 质量
import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "ls__xxx"
os.environ["LANGSMITH_PROJECT"] = "prod-agent-v2"
# 给重要调用打标签,方便在 LangSmith 筛选
from langchain_core.tracers.context import tracing_v2_enabled
with tracing_v2_enabled(
project_name="prod-agent-v2",
tags=["customer-service", "v2"],
metadata={"user_id": "12345", "session": "abc"},
):
result = agent.invoke({"messages": [...]})
# 这次调用在 LangSmith 会带上 tag 和 metadata,方便筛选分析
9.6 模型与工具最佳实践
- 使用 LangChain 标准模型接口,保持模型可替换性
- 工具描述要清晰准确,影响 Agent 调用成功率
- 工具集(Toolkits)按场景组织,避免过多工具导致选择困难
- 使用
bind_tools()将工具绑定到模型
from langchain_core.tools import tool, BaseToolkit
# 工具描述黄金法则:写"什么场景该调我",不写"我是干嘛的"
@tool
def query_sales_db(date_range: str, region: str) -> str:
"""查询销售数据库。
什么时候用我:当用户问"某段时间某个地区的销售额/订单量/客户数"时。
参数说明:
- date_range: 日期范围,格式 "2024-01-01~2024-01-31" 或 "最近7天"
- region: 地区,如 "华东"、"华南"、"全国"
返回:包含销售额、订单量、客单价的 JSON。
"""
return query_db(date_range, region)
# 工具不宜过多:超过 10 个工具时,模型选择准确率显著下降
# 解决方案:用路由 Agent 先选工具集,再传给执行 Agent
9.7 模块化设计最佳实践
- 复杂工作流拆分为子图(Subgraphs),每个子图独立状态管理
- 使用 DSL 驱动 + 动态图构建实现可配置工作流(类 Dify 模式)
- 节点职责单一:节点做工作,边决定下一步
- 支持工作流的可视化(Mermaid 图)
# 子图:把复杂工作流拆成可复用的模块
def build_research_subgraph():
"""研究子图:检索→分析→总结"""
sub_graph = StateGraph(ResearchState)
sub_graph.add_node("retrieve", retrieve_node)
sub_graph.add_node("analyze", analyze_node)
sub_graph.add_node("summarize", summarize_node)
sub_graph.add_edge(START, "retrieve")
sub_graph.add_edge("retrieve", "analyze")
sub_graph.add_edge("analyze", "summarize")
sub_graph.add_edge("summarize", END)
return sub_graph.compile()
# 主图中嵌入子图
main_graph = StateGraph(OverallState)
main_graph.add_node("research", build_research_subgraph()) # 子图作为节点
main_graph.add_node("write", write_node)
main_graph.add_edge(START, "research")
main_graph.add_edge("research", "write")
main_graph.add_edge("write", END)
9.8 生产部署最佳实践
- 使用 PostgreSQL Checkpointer 实现持久化和容错恢复
- 异步 API(
ainvoke/astream)提升并发性能 - 配置超时和重试机制
- 实现健康检查和监控告警
- CI/CD 管道集成 LangSmith 评估
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential
# 异步 + 重试
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
async def call_agent_async(user_input, thread_id):
config = {"configurable": {"thread_id": thread_id}}
result = await app.ainvoke(
{"messages": [HumanMessage(content=user_input)]},
config=config,
)
return result["messages"][-1].content
# 并发处理多个请求
async def handle_batch(inputs):
tasks = [call_agent_async(inp["text"], inp["thread_id"]) for inp in inputs]
return await asyncio.gather(*tasks, return_exceptions=True)
9.9 常见陷阱与解决方案
| 陷阱 | 原因 | 解决方案 |
|---|---|---|
| 并行节点结果丢失 | state 字段默认覆盖写入 | 使用自定义 Reducer 改为追加语义 |
| Agent 无限循环 | 缺少终止条件 | 添加迭代计数器和完成标志 |
| 状态臃肿 | 消息列表无限增长 | 实现上下文压缩/摘要机制 |
| 工具调用失败无处理 | 未处理异常 | 使用 ToolNode 自动处理工具错误 |
| 流式中断未处理 | 忽略 __interrupt__ 事件 |
检查流中的中断信息并处理 |
# 防止 Agent 无限循环:加迭代计数器
def should_continue(state: AgentState) -> str:
count = state.get("iteration_count", 0)
if count >= 10: # 最大 10 轮
return "END"
return "continue"
# 上下文压缩:消息太多时自动摘要
def compress_if_needed(state: AgentState):
messages = state["messages"]
if len(messages) > 20: # 超过 20 条消息就压缩
summary = llm.invoke([
SystemMessage("把以下对话压缩成一段摘要"),
*messages[:-4], # 保留最近 4 条不压缩
])
state["messages"] = [summary] + messages[-4:]
return state
9.10 技术栈推荐
| 层级 | 推荐技术 |
|---|---|
| 模型 | GPT-5.5 / Claude Sonnet 4 / Gemini 2.5 |
| Embedding | OpenAI Embeddings / M3E |
| 向量数据库 | Chroma(开发)/ pgvector(生产)/ MyScale(SQL+向量) |
| 持久化 | PostgreSQL |
| 编排 | LangGraph |
| Agent | LangChain create_agent / Deep Agents |
| 监控 | LangSmith |
| 部署 | LangDeployment |
| 前端 | Gradio / Streamlit / Agent Chat UI |
9.11 从 Claude Code Skill 机制中学到的工程智慧
在研究 LangChain 的工具机制时,有必要横向对比 Claude Code 的 Skill 机制。虽然两者面向不同场景(LangChain 是通用 Agent 框架,Claude Code 是编程助手),但 Skill 机制沉淀的工程经验对设计任何 Agent 工具系统都有启发。
核心洞察一:工具描述是触发器,不是说明书。
Claude Code 的 Skill 机制揭示了一个反直觉的事实:模型决定用不用某个工具/技能,唯一的依据就是那行 description。它没读过正文,不知道你内容写得多用心。description 没写好,正文写出花来也白搭。Anthropic 内部甚至给 description 设了硬性预算:整张工具清单只允许占用 context 窗口的 1%,单个工具描述最多 250 个字符。
这对 LangChain 的工具设计直接适用:@tool 的 docstring 不是写给人看的摘要,是写给模型看的触发条件。不要写"这是一个查询数据库的工具"(人类视角),要写"当用户询问销售额、订单量、客户数等业务指标时使用此工具"(模型视角)。
核心洞察二:工具越多越糊涂。
Claude Code 发现:装太多 Skill 反而都不触发。因为 1% 的 context 预算被挤爆后,所有 description 被压缩,最后变成一排只有名字的哑巴。LangChain 同理:给 Agent 挂太多工具,模型选择准确率显著下降。解法是用分层路由——先由一个轻量 Agent 判断意图、选工具集,再把选中的少量工具传给执行 Agent。
核心洞察三:验证类工具回报最大。
Anthropic 内部实测,让 Agent 能"自己验证工作成果"的工具,对输出质量提升最明显。原话甚至说值得让一个工程师花一整周专门打磨。在 LangChain 体系里,这意味着你的 Agent 不能只会"干活",还要会"检查自己干得对不对"——加一个验证节点,用断言检查输出格式、用测试用例验证逻辑正确性。
核心洞察四:坑点清单比操作说明值钱。
Skill 正文里含金量最高的不是"怎么做",而是"别踩什么坑"。同样,LangChain 工具的 docstring 里,最有价值的是边界条件和失败模式描述。告诉模型"这个工具在什么情况下会失败、失败时返回什么",比告诉它"成功时返回什么"更重要。
LangChain 工具设计 vs Claude Code Skill 设计的共通原则:
┌─────────────────────────────────────────────────────────┐
│ 1. description 是触发条件,不是说明书(≤250字符) │
│ 2. 工具贵精不贵多,多了用分层路由 │
│ 3. 验证类工具回报最大(让 Agent 自检) │
│ 4. 坑点清单 > 操作说明(写边界条件和失败模式) │
│ 5. 按需加载:不用的工具别占 context │
└─────────────────────────────────────────────────────────┘
第九章实践要点
- 状态设计用多 Schema 分离关注点,并行节点务必用自定义 Reducer。
- 生产环境 PostgresSaver + LangSmith 追踪 + 异步 API,三件套缺一不可。
- 工具描述写"什么场景该调我"而非"我是干嘛的",工具数控制在 10 个以内。
- 防无限循环加计数器,防状态臃肿加上下文压缩。
- 从 Skill 机制学到的:验证工具优先做,坑点清单比操作说明值钱。
结尾
核心知识点回顾
本篇从定位到落地,把 LangChain 体系拆解了一遍。核心知识点回顾如下:
-
核心理念:Agent = Model + Harness。LangChain 提供的是"模型循环周围的一切"——提示词、工具、中间件。你的工作大多是配置 harness,不是改模型。
-
五层生态:Deep Agents(开箱即用)→ LangChain(可定制 harness)→ LangGraph(图编排运行时)→ LangSmith(可观测性)→ LangDeployment(部署)。上层省事但灵活度低,下层灵活但要多写代码。
-
六大组件:Model I/O(说)、Data Connection(找资料)、Chains(串)、Memory(记)、Agents(想)、Callbacks(观察)。完整覆盖 LLM 应用链路。
-
LCEL:管道符语法 + Runnable 统一接口,天然支持流式/异步/批量/并行/回退。是新代码首选,传统 Chains 只用于维护旧代码。
-
LangGraph:把工作流建模为有向图,State + Nodes + Edges + Reducer 协同工作。解决传统 Agent 的流程不可控、状态管理弱、无持久化、无法人工干预、循环工作流难五大痛点。
-
人工干预:interrupt() 暂停 + Command(resume=…) 恢复,是危险操作审核的标准范式。
-
LangSmith:框架无关的可观测性平台,设置环境变量即自动追踪。追踪、调试、评估、监控四件套,是 Agent 工程化的基础设施。
-
部署:LangServe 已弃用,新项目用 LangDeployment(原 LangGraph Platform),支持 Server/Studio/Cloud/自托管。
-
选型:LlamaIndex 擅长检索,LangChain 擅长编排,LangGraph 灵活但陡,CrewAI 易用但浅。从需求出发选框架,不要先选框架再找需求。
-
工程智慧:工具描述是触发器不是说明书,工具贵精不贵多,验证类工具回报最大,坑点清单比操作说明值钱。
主题关联
本篇是 AI 知识系列的第 04 篇,与系列其他篇目的关联:
- LLM 基础篇:理解 Transformer 架构和注意力机制,是理解"模型为什么需要 prompt 工程"的基础
- RAG 深度篇:本篇第三章的 Data Connection 是 RAG 的骨架,RAG 篇会展开检索策略、重排序、混合检索等进阶主题
- Agent 设计篇:本篇的 Agents 和 LangGraph 章节是 Agent 设计的工程实现,Agent 篇会展开 ReAct/Plan-and-Execute/Reflection 等认知架构
- Prompt 工程篇:本篇的 PromptTemplate 是 Prompt 工程的代码化,Prompt 篇会展开 CoT/Few-shot/自洽性等技巧
- Claude Code / Skill 机制篇:本篇第九章对比了 Skill 机制,两者都揭示了"工具描述质量决定 Agent 成败"的共通规律
进一步阅读
| 主题 | 资源 |
|---|---|
| LangChain 官方文档 | https://python.langchain.com/docs/concepts/lcel/ |
| LangGraph 深度教程 | https://baiweijieku.github.io/posts/langgraph/ |
| LangChain vs LlamaIndex | https://www.myscale.com/blog/zh/llamaindex-vs-langchain-detailed-comparison/ |
| LangGraph 多 Agent 工作流 | https://www.langchain.com/blog/langgraph-multi-agent-workflows |
| Anthropic Skill 实战博客 | https://claude.com/blog/lessons-from-building-claude-code-how-we-use-skills |
| LangChain 核心组件解读 | https://cloud.tencent.com/developer/article/2324297 |
| 2026 Agent 框架更新 | https://learnagent.org/library/playbooks/framework-updates-2026/ |
架构会演进,API 会更迭,但"模型 + 框架 = Agent"的公式不变,“节点做工作,边决定下一步"的图思维不变,“工具描述决定 Agent 成败"的规律不变。抓住这些不变量,框架的版本号再怎么跳,你都能从容应对。