Week 1 -- 第1周学习笔记
第 1 周学习笔记:LangChain 基础入门与架构理解
学习时间:2026年5月(5天) | 核心主题:生态全景 → 环境搭建 → LCEL → 提示工程与输出解析 → 综合实战
一、本周学习路线总览
本周以"从零到一构建可运行的 LLM 应用"为主线,按以下路径递进:
生态认知 → 环境落地 → 核心范式(LCEL) → 输入输出工程 → 综合整合
Day1 Day2 Day3 Day4 Day5
每一天都建立在前一天的基础之上,最终在 Day 5 用一个多语言翻译助手项目将全部知识点串联起来。
二、Day 1:LangChain 生态全景
2.1 核心认知:LangChain 不是"一个库"
最大的认知转变是:LangChain 不是一个单体框架,而是一个分层生态。它由四个独立发布的包构成,每一层有明确的职责边界:
| 层级 | 包名 | 一句话定位 |
|---|---|---|
| 基础抽象层 | langchain-core |
定义"语法"——Runnable 接口 + LCEL |
| 第三方集成层 | langchain-community |
数百个外部服务的统一适配器 |
| 业务组件层 | langchain |
开箱即用的 Chains / Agents / Retrieval |
| 编排调度层 | langgraph |
图结构驱动的有状态工作流引擎 |
这种分层设计的关键收益是独立迭代:langchain-core 的接口变更不会波及 langgraph,langgraph 也可以按自己的节奏发版。各层之间通过统一的 Runnable 接口通信,形成松耦合的体系。
2.2 2026 年 v1.2.14 的关键信号
这个版本释放了几个重要信号:
- LangGraph 深度整合:不再是可选扩展,而是框架的原生组成部分 → 说明"有状态 Agent 工作流"已成为 LangChain 的核心场景
- MCP 协议支持:跨框架工具互操作 → LangChain 在主动打破生态壁垒,一套工具定义可被多个 AI 框架复用
create_agent()标准入口:降低 Agent 构建门槛 → 框架在从"灵活但复杂"向"简单且强大"演进
2.3 生态工具定位(易混淆点)
这三个工具经常被混淆,它们的本质区别是:
- LangSmith → 解决"应用质量"问题:调试追踪、评估测试、生产部署监控
- LangGraph → 解决"流程复杂度"问题:多步骤、有状态、带分支的工作流编排
- Deep Agents → 解决"任务复杂度"问题:长周期规划、多子 Agent 协作
三、Day 2:环境搭建与第一个应用
3.1 环境搭建的关键细节
实际踩过的坑:
- 包的独立性:
langchain-core是langchain的依赖,会自动安装;但langgraph和langchain-openai需要单独安装,因为它们是完全独立的项目。 - 模型兼容性:本次学习统一使用硅基流动 +
langchain-openai,因为市面上绝大多数模型(DeepSeek、Qwen、Ollama 本地模型等)都兼容 OpenAI API 格式,一个包覆盖几乎所有模型。 - 密钥安全:
.env+.gitignore是最低成本的防泄露方案。注意load_dotenv()必须在代码最顶部调用,且OPENAI_API_KEY这个变量名是 LangChain 的默认约定。
3.2 第一个 LCEL 链的拆解
chain = prompt | llm | output_parser
result = chain.invoke({"role": "Python专家", "question": "解释装饰器"})
这条链实际上做了三件事:
prompt:将{role}和{question}替换为实际值,生成格式化的消息列表llm:将消息列表发送给大模型 API,返回AIMessage对象output_parser:从AIMessage中提取.content,返回纯字符串
关键感悟:LangChain 的价值不在"能做什么"(裸调 API 也能做),而在"怎么做"(声明式组合、统一接口、关注点分离)。当需要切换模型时,只改一行初始化代码,chain 的其余部分完全不变——这就是接口抽象的意义。
3.3 对比裸调 API 的优势总结(<100字)
LangChain 将"拼字符串→调 API→解析结果"的散落逻辑收敛为声明式管道,切换模型只需改一行;Prompt 模板可复用可测试,输出解析自动处理格式差异,工程化程度显著高于裸调。
四、Day 3:LCEL 深度理解(本周最核心)
4.1 Runnable:LangChain 的"通用语言"
这是本周最重要的概念。LangChain 中几乎所有组件(Prompt 模板、LLM、Parser、Retriever、Tool……)都实现了 Runnable 接口。这意味着它们在 LCEL 管道中是等价的积木,可以任意拼接。
Runnable 提供三种标准调用方式,同一条 chain 无需修改即可切换:
| 方法 | 场景 | 特点 |
|---|---|---|
invoke() |
单次请求 | 同步阻塞,返回完整结果 |
stream() |
对话类应用 | 逐 token 迭代输出,实时反馈 |
batch() |
批量离线处理 | 内部并发执行,比循环 invoke 快 |
4.2 管道运算符 | 的本质
| 在 Python 中是位运算符,LangChain 通过重载 __or__ 将其变成组合运算符。step1 | step2 等价于 RunnableSequence(step1, step2)。
最关键的特性是惰性求值:
chain = prompt | llm | parser # ← 此时没有任何 API 调用
result = chain.invoke(...) # ← 真正的执行发生在这里
这种设计带来的工程收益:
- 构建与执行分离:链可以被序列化、缓存、跨函数传递
- 配置即代码:链的定义本身就是一个可版本管理的配置对象
- 复用性强:同一条链可以被多次
invoke而不需要重新构建
4.3 RunnableParallel:并发分发的利器
当同一份输入需要经过多条独立分析路径时,RunnableParallel 让这些路径并发执行,显著减少总等待时间。
parallel = RunnableParallel(
summary=summary_chain,
keywords=keywords_chain,
sentiment=sentiment_chain,
)
result = parallel.invoke({"article": "..."})
# result = {"summary": "...", "keywords": "...", "sentiment": "..."}
实际验证:三路分析的总耗时约等于最慢那一路的耗时,而非三路之和。
4.4 RunnableBranch:动态路由
本质是 LCEL 版的 if/elif/else,根据输入的谓词判断决定走哪条处理路径:
branch = RunnableBranch(
(lambda x: "紧急" in x["topic"], urgent_chain),
normal_chain, # 默认分支
)
RunnableBranch 本身也是 Runnable,可以嵌套进更大的 chain 中。
4.5 RunnableLambda:连接自定义逻辑的桥梁
任意 Python 函数包装成 RunnableLambda 后就可以接入 LCEL 管道:
postprocess = RunnableLambda(lambda text: f"[PROCESSED] {text.upper()}")
chain = prompt | llm | parser | postprocess
这让 LCEL 的边界可以无限扩展——任何业务逻辑,只要写成函数,就能无缝融入管道。
五、Day 4:提示工程与输出解析
5.1 Prompt 模板体系
ChatPromptTemplate.from_messages 支持三种消息类型:
| 类型 | 写法 | 用途 |
|---|---|---|
| 角色消息 | ("system", "你是{role}") |
定义 AI 的行为规范 |
| 用户消息 | ("user", "{input}") |
承载用户输入 |
| 消息插槽 | MessagesPlaceholder(variable_name="history") |
动态注入历史消息列表 |
5.2 MessagesPlaceholder 的本质区别(关键理解)
这是我本周最重要的理解之一。MessagesPlaceholder 和普通花括号占位符 {variable} 的本质区别在于接受的值的类型:
{role}接受的是单个字符串 → 渲染时直接替换文本MessagesPlaceholder("history")接受的是消息对象列表 → 渲染时将列表中的每条消息展开插入
这个区别决定了多轮对话的实现方式:你不需要手动拼接历史文本,只需维护一个 [HumanMessage, AIMessage, HumanMessage, AIMessage, ...] 列表并传入即可,LangChain 自动完成展开和排序。
手动维护历史的做法:
history = []
# 第一轮
result1 = chain.invoke({"history": history, "input": "问题1"})
# 第二轮:将上一轮的问答追加到 history
history = [HumanMessage("问题1"), AIMessage(result1)]
result2 = chain.invoke({"history": history, "input": "问题2"})
5.3 Few-Shot 提示
核心思想:用示例替代冗长的文字描述。3~5 个精心挑选的输入输出对,往往比一大段格式说明更能让模型理解期望。
使用步骤:
- 准备
examples列表(每个元素是{"input": ..., "output": ...}字典) - 用
FewShotChatMessagePromptTemplate包装示例 - 将
few_shot_prompt嵌入主 prompt 模板中
5.4 输出解析器对比
| 解析器 | 返回类型 | 类型安全 | 适用场景 |
|---|---|---|---|
StrOutputParser |
str |
无 | 纯文本回答 |
JsonOutputParser |
dict |
无 | 简单结构化提取 |
PydanticOutputParser |
Pydantic 实例 | ✅ | 需要类型校验的生产场景 |
推荐在工程中优先使用 PydanticOutputParser,因为:
- Pydantic 自动做类型强制(如
"28"→28) - IDE 中获得完整的类型提示和自动补全
- 字段缺失或类型错误时抛出明确的校验异常
5.5 OutputParserException 与降级策略
LLM 并不总是听话的。即使 prompt 中明确要求输出 JSON,模型有时也会在 JSON 前后附加额外文字。正确的工程实践是:
try:
result = chain.invoke({"input": user_input})
except OutputParserException as e:
# 降级:回退到纯文本,让下游自行处理
fallback_result = fallback_chain.invoke(...)
永远不要假设解析一定成功,这是从 Demo 走向生产的关键一课。
六、Day 5:综合项目——多语言翻译助手
6.1 项目架构
用户输入 → Prompt 模板 → LLM (Qwen3.6-35B) → PydanticOutputParser → TranslationResult
↑ ↓ (异常时)
format_instructions OutputParserException → 降级处理
6.2 技术点映射
| 本周知识点 | 在项目中的体现 |
|---|---|
| LCEL 管道 | chain = prompt | llm | parser |
| PydanticOutputParser | TranslationResult 的结构化输出 + 类型校验 |
prompt.partial() |
将 format_instructions 预先绑定到模板 |
| OutputParserException | try-except 降级处理 |
ChatPromptTemplate.from_messages |
system + user 双消息结构 |
Field(description=...) |
字段描述自动生成 JSON schema 格式说明 |
6.3 关键代码片段
class TranslationResult(BaseModel):
source_lang: str = Field(description="源语言,如'中文'或'英文'")
target_lang: str = Field(description="目标语言")
original: str = Field(description="用户输入的原文")
translated: str = Field(description="翻译后的文本")
confidence: float = Field(description="翻译置信度,0.0~1.0")
parser = PydanticOutputParser(pydantic_object=TranslationResult)
prompt = prompt.partial(format_instructions=parser.get_format_instructions())
chain = prompt | llm | parser
这个项目不到 100 行代码,但已经是一个有工程价值的 LLM 应用原型:结构化输出、类型安全、异常处理、可扩展——这些都是在"裸调 API"时需要额外编写大量代码才能获得的特性。
七、核心概念的个性化理解
7.1 对 LCEL "声明式组合"的理解
传统命令式写法关心的是怎么做(每一步如何调用、传什么参数、怎么处理结果),而 LCEL 关心的是数据流向(先经过 A,再经过 B,最后到 C)。prompt | llm | parser 读起来就像"输入 → 格式化 → 推理 → 解析 → 输出",代码结构本身就是流程图。
7.2 对 Runnable 接口的感悟
Runnable 是 LangChain 的"通用货币"。它让我想起 Python 的迭代器协议——只要实现了 __iter__,任何对象都可以被 for 循环消费。同样的,只要实现了 Runnable,任何组件都可以被 | 组合。这是一种协议胜于继承的设计哲学。
7.3 对提示工程 + 输出解析的认识
如果把 LLM 比作一个"黑盒函数",那么:
- Prompt 模板是函数的参数定义和类型注解(告诉模型"你该接收什么输入、扮演什么角色")
- Output Parser是函数的返回值类型约束(告诉模型"你该输出什么格式,我会这样校验你")
- 两者配合,让不可控的自然语言输入输出变得可预测、可校验、可工程化
八、踩坑记录与经验
-
Field(...)中的...(Ellipsis)vs 默认值:定义 Pydantic 模型时,必需字段用Field(...)(Ellipsis 表示 required),可选字段用Field(default=...)。一开始漏写导致字段都变成可选,解析时没有报错但数据不完整。 -
base_url参数:使用硅基流动等第三方 API 时,必须显式传入base_url="https://api.siliconflow.cn/v1",否则ChatOpenAI默认请求 OpenAI 官方服务器。 -
temperature=0对学习阶段的重要性:非零温度会让模型每次输出不同,导致调试时行为不一致。学习阶段设为 0 可以确保每次运行结果可复现。 -
prompt.partial()的使用场景:一些不随每次调用改变的参数(如format_instructions)应该用partial预绑定,这样调用时只需要传业务变量,接口更清晰。
九、下周展望
第 2 周的主题是 RAG(检索增强生成)。从本周的认知出发,RAG 在 LCEL 架构中本质上就是:
retriever | prompt | llm | parser
↑
在管道前端插入一个检索步骤
这意味着本周学到的所有 LCEL 知识在下周可以直接复用——管道的可组合性让"插入新组件"变得极其简单。同时,MessagesPlaceholder 管理对话历史的能力在 RAG 的多轮检索场景中也会发挥关键作用。

浙公网安备 33010602011771号