【AI】LangChain:从传统链到LCEL链式编程
本篇会详细介绍 LangChain 中的两套链式编程体系:传统的 Chain 类和现代的 LCEL 语法。前者功能强大但稍显繁琐,后者简洁优雅且易于理解。
LangChain:从传统链到LCEL链式编程
前言
这篇是尚硅谷 LangChain 系列的学习笔记,这次整理的是第三章 Chains(链)。
你想想,前面学了怎么调用模型、怎么写提示词、怎么解析输出,但每次都要手动一步步调用,累不累?Chains 就是来解决这个问题的——它把各个组件串联起来,数据自动流转,就像工厂的流水线一样。
本篇会详细介绍 LangChain 中的两套链式编程体系:传统的 Chain 类和现代的 LCEL 语法。前者功能强大但稍显繁琐,后者简洁优雅且易于理解。看完这篇,你就知道什么时候该用哪种方式了。
🏠个人主页:山沐与山
文章目录
- 一、什么是Chains?为什么需要它?
- 二、LCEL语法快速入门
- 三、传统Chain的使用
- 四、基于LCEL的高级Chains
- 五、传统Chain vs LCEL:该如何选择?
- 六、常见问题
- 七、总结
- 热门专栏推荐
一、什么是Chains?为什么需要它?
在前面的文章里,我们学会了怎么调用模型、怎么写提示词模板、怎么解析输出。但如果每次都要手动调用这些组件,代码会变得很冗长:
# 没有Chain的时候,需要手动传递数据
prompt = prompt_template.format(question="什么是勾股定理?")
response = llm.invoke(prompt)
result = parser.parse(response)
这样写有几个问题:
- 重复劳动:每次都要手动调用 format、invoke、parse
- 容易出错:忘记某一步或者传错参数
- 不够优雅:代码可读性差,维护困难
Chain(链) 就是为了解决这些问题而生的。它把多个组件串联起来,数据自动从前一个组件流向后一个组件,就像工厂的流水线一样:
输入 → Prompt模板 → LLM → 输出解析器 → 最终结果
有了 Chain 之后,上面的代码可以简化成:
# 使用Chain,一行搞定
chain = prompt_template | llm | parser
result = chain.invoke({"question": "什么是勾股定理?"})
看到没有?这就是 LCEL(LangChain Expression Language) 的魔力。
二、LCEL语法快速入门
2.1 什么是LCEL?
LCEL(LangChain Expression Language)是 LangChain 推出的一种链式编程语法,用 管道符 | 连接多个组件。它的设计灵感来自 Unix 的管道命令:
# Unix管道:前一个命令的输出作为后一个命令的输入
cat file.txt | grep "error" | wc -l
LCEL 的核心思想就是:用 | 把组件像管道一样串起来,数据自动从左到右流动。
2.2 基础三件套:Prompt + Model + Parser
LCEL 最常见的组合就是这三件套:
- Prompt:提示词模板,负责格式化输入
- Model:语言模型,负责生成回复
- Parser:输出解析器,负责解析模型输出
来看一个完整的例子:
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 1. 定义提示词模板
prompt_template = PromptTemplate.from_template(
"""请将以下中文文本翻译成英文,并以JSON格式返回,格式为:{{"text": "翻译结果"}}
中文文本:{text}
"""
)
# 2. 初始化模型
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 3. 定义JSON解析器
json_parser = JsonOutputParser()
# 4. 用管道符连接三个组件
chain = prompt_template | llm | json_parser
# 5. 调用链
result = chain.invoke({"text": "我喜欢吃苹果和香蕉。"})
print(result)
输出:
{'text': 'I like to eat apples and bananas.'}
2.3 LCEL的管道符 | 魔法
管道符 | 的工作原理很简单:
- prompt_template 接收输入
{"text": "我喜欢吃苹果和香蕉。"},生成格式化后的提示词 - llm 接收提示词,调用OpenAI模型生成回复
- json_parser 接收模型输出,解析成Python字典
整个过程自动串联,无需手动传递数据。这就是LCEL的核心优势:简洁、直观、易维护。
三、传统Chain的使用
虽然 LCEL 很方便,但 LangChain 还保留了一套传统的 Chain 类,适合更复杂的场景。接下来我们详细看看这些传统链。
3.1 LLMChain:最基础的链
LLMChain 是最基础的链,它把 Prompt 和 LLM 组合在一起。
代码示例:
from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 定义提示词模板
prompt_template = PromptTemplate.from_template("请详细解释:{question}")
# 初始化模型
llm = ChatOpenAI(model="gpt-4o-mini")
# 创建LLMChain
chain = LLMChain(
llm=llm,
prompt=prompt_template,
verbose=True # 显示执行日志
)
# 调用链
response = chain.invoke({"question": "什么是勾股定理?"})
print(response)
输出:
{
'question': '什么是勾股定理?',
'text': '勾股定理是数学中的一个基本定理...'
}
特点:
- verbose=True:打印执行过程,方便调试
- 返回字典:包含输入(question)和输出(text)
- 支持两种模板:PromptTemplate(用于非对话模型)和 ChatPromptTemplate(用于对话模型)
3.2 SimpleSequentialChain:简单顺序链
SimpleSequentialChain 用于按顺序执行多个 LLMChain,前一个的输出自动作为后一个的输入。
适用场景: 需要多步处理,但每一步只有一个输入和一个输出。
代码示例:
from langchain.chains import SimpleSequentialChain
# ChainA:详细解释一个概念
chainA_prompt = PromptTemplate.from_template("请详细解释:{input}")
chainA_chains = LLMChain(llm=llm, prompt=chainA_prompt)
# ChainB:总结ChainA的输出
chainB_prompt = PromptTemplate.from_template("请用20个字以内总结:{input}")
chainB_chains = LLMChain(llm=llm, prompt=chainB_prompt)
# 组合成顺序链
full_chain = SimpleSequentialChain(
chains=[chainA_chains, chainB_chains],
verbose=True
)
# 调用链
result = full_chain.invoke({"input": "什么是爱情?"})
执行流程:
- 输入:“什么是爱情?”
- ChainA 生成详细解释(可能几百字)
- ChainB 把 ChainA 的输出总结成20字以内
输出示例:
ChainA: 爱情是一种复杂的情感,涉及情感、心理、生理和社会文化等多个层面...
ChainB: 爱情是复杂情感,涉及情感、心理、生理和社会文化等多层面。
注意事项:
- 所有链的输入变量名必须是
input(这是 SimpleSequentialChain 的硬性要求) - 只能传递单个输出,不支持多个变量
3.3 SequentialChain:支持多输入输出的高级顺序链
SequentialChain 是 SimpleSequentialChain 的增强版,支持:
- 多个输入变量
- 多个输出变量
- 灵活的变量传递
代码示例:
from langchain.chains import SequentialChain
# SchainA:解释知识点
schainA_prompt = ChatPromptTemplate.from_template("请详细解释:{knowledge}")
schainA_chains = LLMChain(
llm=llm,
prompt=schainA_prompt,
output_key="schainA_chains_key" # 指定输出变量名
)
# SchainB:给出实践建议
schainB_prompt = ChatPromptTemplate.from_template("根据以下知识,给出实践建议:{action}")
schainB_chains = LLMChain(
llm=llm,
prompt=schainB_prompt,
output_key="schainB_chains_key" # 指定输出变量名
)
# 组合成SequentialChain
Seq_chain = SequentialChain(
chains=[schainA_chains, schainB_chains],
input_variables=["knowledge", "action"], # 声明所有输入变量
output_variables=["schainA_chains_key", "schainB_chains_key"], # 声明所有输出变量
verbose=True
)
# 调用链
result = Seq_chain.invoke({
"knowledge": "什么是TDD?",
"action": "如何在实际项目中应用TDD?"
})
更复杂的例子:四链顺序处理
# Chain 1: 英文翻译成中文
chain_one_prompt = ChatPromptTemplate.from_template(
"将以下英文翻译成中文:\n\n{content}"
)
chain_one = LLMChain(llm=llm, prompt=chain_one_prompt, output_key="Chinese_Review")
# Chain 2: 总结中文内容
chain_two_prompt = ChatPromptTemplate.from_template(
"用一句话总结:\n\n{Chinese_Review}"
)
chain_two = LLMChain(llm=llm, prompt=chain_two_prompt, output_key="Chinese_Summary")
# Chain 3: 识别语言
chain_three_prompt = ChatPromptTemplate.from_template(
"识别以下文本的语言:\n\n{Chinese_Summary}"
)
chain_three = LLMChain(llm=llm, prompt=chain_three_prompt, output_key="Language")
# Chain 4: 用识别的语言进行评论
chain_four_prompt = ChatPromptTemplate.from_template(
"用{Language}对以下内容进行评论:\n\n{Chinese_Summary}"
)
chain_four = LLMChain(llm=llm, prompt=chain_four_prompt, output_key="Comment")
# 组合四个链
overall_chain = SequentialChain(
chains=[chain_one, chain_two, chain_three, chain_four],
input_variables=["content"],
output_variables=["Chinese_Review", "Chinese_Summary", "Language", "Comment"],
verbose=True
)
# 调用
result = overall_chain.invoke({
"content": "LangChain is a framework for developing applications powered by language models."
})
执行流程:
- 输入:英文内容
- Chain 1:翻译成中文
- Chain 2:总结中文内容
- Chain 3:识别语言(中文)
- Chain 4:用中文进行评论
关键点:
- 每个 LLMChain 必须指定
output_key - SequentialChain 必须声明
input_variables和output_variables - 链之间通过变量名传递数据
3.4 LLMMathChain:数学计算链
LLMMathChain 是专门用于数学计算的链,它会把自然语言转换成数学表达式,然后计算结果。
代码示例:
from langchain.chains import LLMMathChain
llm_math = LLMMathChain.from_llm(llm)
# 自然语言提问
res = llm_math.invoke("10的3次方加100的结果是多少?")
print(res)
输出:
{
'question': '10的3次方加100的结果是多少?',
'answer': 'Answer: 1100'
}
工作原理:
- LLM 把问题转换成数学表达式:
10 ** 3 + 100 - Python 执行计算:
1000 + 100 = 1100 - 返回结果
适用场景: 需要精确计算的场景,比如价格计算、统计分析等。
3.5 StuffDocumentsChain:文档处理链
StuffDocumentsChain 用于处理文档内容,把多个文档合并后传递给 LLM。
代码示例:
from langchain.chains import StuffDocumentsChain
from langchain.document_loaders import PyPDFLoader
# 定义提示词
prompt = PromptTemplate.from_template("总结以下内容:\n\n{text}")
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=prompt)
# 创建StuffDocumentsChain
stuff_chain = StuffDocumentsChain(
llm_chain=llm_chain,
document_variable_name="text" # 指定文档内容在提示词中的变量名
)
# 加载PDF文档
loader = PyPDFLoader("example.pdf")
docs = loader.load()
# 调用链
result = stuff_chain.invoke(docs)
工作原理:
- 加载文档:PyPDFLoader 读取 PDF 文件
- 合并内容:把所有页的内容合并成一个长文本
- 传递给LLM:一次性传递给 LLM 处理
- 返回结果:总结或问答结果
适用场景: 少量/中等长度文档的处理,比如文档摘要、多源问答等。
注意: 如果文档太长,超过模型的上下文窗口,需要用其他方法(如 MapReduceDocumentsChain)。
四、基于LCEL的高级Chains
除了传统的 Chain 类,LangChain 还提供了一些基于 LCEL 的高级链,它们更简洁、更易用。
4.1 create_sql_query_chain:自然语言转SQL
create_sql_query_chain 可以把自然语言问题转换成 SQL 查询语句。
代码示例:
from langchain.chains.sql_database.query import create_sql_query_chain
from langchain_community.utilities import SQLDatabase
# 连接MySQL数据库
db = SQLDatabase.from_uri(
f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
)
# 创建SQL查询链
chain = create_sql_query_chain(llm, db)
# 自然语言提问
response = chain.invoke({"question": "请问有多少个用户?"})
print(response)
输出:
SELECT COUNT(`id`) AS `user_count` FROM `auth_user`;
工作原理:
- 分析数据库结构:chain 自动读取表结构(表名、字段名、类型等)
- 生成SQL:LLM 根据问题和表结构生成 SQL 语句
- 返回SQL:直接返回 SQL 字符串(不执行)
执行查询:
result = db.run(response)
print(result) # 输出:[(150,)]
适用场景:
- 数据分析工具(自然语言查询数据库)
- BI系统(业务人员用自然语言提问)
- 数据探索(快速查询数据)
支持的数据库: MySQL、PostgreSQL、SQLite、Oracle等。
4.2 create_stuff_documents_chain:文档合并处理
create_stuff_documents_chain 把多个文档内容合并成一个长文本,一次性传递给 LLM。
代码示例:
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.documents import Document
# 定义提示词
prompt = ChatPromptTemplate.from_messages([
("system", "根据以下文档内容回答问题:\n\n{docs}"),
("human", "{question}")
])
# 创建文档处理链
chain = create_stuff_documents_chain(
llm,
prompt,
document_variable_name="docs" # 指定文档变量名
)
# 准备文档
docs = [
Document(page_content="苹果是红色的水果,富含维生素C。"),
Document(page_content="香蕉是黄色的水果,富含钾元素。"),
Document(page_content="蓝莓是蓝色的浆果,富含花青素。")
]
# 提问
result = chain.invoke({
"docs": docs,
"question": "香蕉是什么颜色的?"
})
print(result)
输出:
香蕉是黄色的水果。
工作原理:
- 合并文档:把所有文档内容拼接成一个长文本
- 构造提示词:把文档内容和问题一起传递给 LLM
- 生成回答:LLM 根据文档内容回答问题
适用场景:
- 文档问答:基于多个文档回答问题
- 文档摘要:总结多个文档的内容
- 内容提取:从多个文档中提取特定信息
注意事项:
- 所有文档内容会一次性传递给 LLM,如果文档太长,可能超过上下文窗口
- 适合少量/中等长度文档,如果文档很多或很长,建议用 MapReduceDocumentsChain
五、传统Chain vs LCEL:该如何选择?
看完这么多链,你可能会问:什么时候用传统 Chain,什么时候用 LCEL?
这里给大家一个简单的对比表:
| 对比维度 | 传统Chain | LCEL |
|---|---|---|
| 语法 | 对象实例化(如 LLMChain(llm=llm, prompt=prompt)) |
管道符 ` |
| 可读性 | 较复杂,代码量大 | 简洁直观,一眼看懂 |
| 灵活性 | 功能丰富,支持复杂场景 | 适合简单场景,复杂场景可能力不从心 |
| 调试 | verbose=True 显示执行日志 | 内置调试支持 |
| 学习成本 | 需要记住各种 Chain 类的用法 | 只需理解管道符概念 |
| 社区趋势 | 逐渐被 LCEL 取代 | 官方推荐,未来主流 |
我的建议:
-
简单场景优先用 LCEL
如果只是 Prompt + Model + Parser 的组合,直接用 LCEL,代码更简洁。# LCEL:简洁优雅 chain = prompt | llm | parser -
复杂场景考虑传统 Chain
如果需要多步处理、多变量传递、特殊逻辑,用传统 Chain 更合适。# 传统Chain:功能强大 chain = SequentialChain( chains=[chain1, chain2, chain3], input_variables=["var1", "var2"], output_variables=["out1", "out2"] ) -
新项目建议用 LCEL
LangChain 官方正在推动 LCEL,未来会有更多基于 LCEL 的高级功能。传统 Chain 虽然还能用,但逐渐会被淘汰。 -
特殊需求用专用链
比如数学计算用LLMMathChain,SQL查询用create_sql_query_chain,这些专用链封装了特定逻辑,直接用就好。
六、常见问题
学 Chains 过程中,这几个问题经常被问到:
6.1 LCEL 和传统 Chain 能混用吗?
可以的。LCEL 返回的 Runnable 对象可以作为传统 Chain 的一部分使用,反过来也一样。
# LCEL 链作为组件
lcel_chain = prompt | llm | parser
# 可以在更复杂的逻辑中使用
result = lcel_chain.invoke({"input": "..."})
但是,建议统一风格。如果项目主要用 LCEL,就全用 LCEL;如果用传统 Chain,就保持一致。
6.2 链太长,调试起来很麻烦怎么办?
方法1:使用 verbose=True
传统 Chain 可以设置 verbose=True,打印每一步的执行过程:
chain = LLMChain(llm=llm, prompt=prompt, verbose=True)
方法2:拆分调试
把长链拆成短链,逐个调试:
# 不要一次性调试整个链
# full_chain = chain1 | chain2 | chain3 | chain4
# 拆开调试
result1 = chain1.invoke(input)
print(f"chain1 输出: {result1}")
result2 = chain2.invoke(result1)
print(f"chain2 输出: {result2}")
方法3:使用 LangSmith
LangChain 官方提供的调试工具,可以可视化查看每一步的执行情况。
6.3 SimpleSequentialChain 和 SequentialChain 怎么选?
简单一句话:看你需要几个输入输出变量。
| 场景 | 推荐 | 原因 |
|---|---|---|
| 单输入、单输出 | SimpleSequentialChain |
简单够用 |
| 多输入或多输出 | SequentialChain |
支持多变量 |
| 变量需要跨链传递 | SequentialChain |
可以指定 output_key |
6.4 文档太长,超过模型上下文窗口怎么办?
StuffDocumentsChain 是把所有文档一次性塞给模型,如果文档太长就会报错。
解决方案:
MapReduceDocumentsChain:先对每个文档单独处理,再汇总结果RefineDocumentsChain:逐个文档迭代优化答案- 截断文档:只取文档的前 N 个字符
- 用向量检索:只检索相关段落,不处理全部文档
# 示例:截断文档
max_chars = 10000
truncated_docs = [
Document(page_content=doc.page_content[:max_chars])
for doc in docs
]
七、总结
这篇文章详细介绍了 LangChain 的 Chains 模块,主要内容包括:
-
Chains 的作用:把多个组件串联起来,数据自动流转,减少重复代码。
-
LCEL 语法:用管道符
|连接组件,简洁直观,适合简单场景。chain = prompt | llm | parser -
传统 Chain 类型:
- LLMChain:最基础的链,Prompt + LLM
- SimpleSequentialChain:简单顺序链,单输入单输出
- SequentialChain:高级顺序链,多输入多输出
- LLMMathChain:数学计算链
- StuffDocumentsChain:文档处理链
-
基于 LCEL 的高级链:
- create_sql_query_chain:自然语言转 SQL
- create_stuff_documents_chain:文档合并处理
-
如何选择:
- 简单场景用 LCEL
- 复杂场景用传统 Chain
- 特殊需求用专用链
Chains 是 LangChain 的核心功能,掌握了它,你就能构建出更复杂、更强大的 AI 应用。下一篇我们会学习 Memory(记忆)模块,让 LLM 能记住上下文,实现真正的对话功能。
热门专栏推荐
- Agent小册
- 服务器部署
- Java基础合集
- Python基础合集
- Go基础合集
- 大数据合集
- 前端小册
- 数据库合集
- Redis 合集
- Spring 全家桶
- 微服务全家桶
- 数据结构与算法合集
- 设计模式小册
- 消息队列合集
等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟
更多推荐


所有评论(0)