LangChain:从传统链到LCEL链式编程

前言

这篇是尚硅谷 LangChain 系列的学习笔记,这次整理的是第三章 Chains(链)。

你想想,前面学了怎么调用模型、怎么写提示词、怎么解析输出,但每次都要手动一步步调用,累不累?Chains 就是来解决这个问题的——它把各个组件串联起来,数据自动流转,就像工厂的流水线一样。

本篇会详细介绍 LangChain 中的两套链式编程体系:传统的 Chain现代的 LCEL 语法。前者功能强大但稍显繁琐,后者简洁优雅且易于理解。看完这篇,你就知道什么时候该用哪种方式了。

🏠个人主页:山沐与山


文章目录


一、什么是Chains?为什么需要它?

在前面的文章里,我们学会了怎么调用模型、怎么写提示词模板、怎么解析输出。但如果每次都要手动调用这些组件,代码会变得很冗长:

# 没有Chain的时候,需要手动传递数据
prompt = prompt_template.format(question="什么是勾股定理?")
response = llm.invoke(prompt)
result = parser.parse(response)

这样写有几个问题:

  1. 重复劳动:每次都要手动调用 format、invoke、parse
  2. 容易出错:忘记某一步或者传错参数
  3. 不够优雅:代码可读性差,维护困难

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 最常见的组合就是这三件套:

  1. Prompt:提示词模板,负责格式化输入
  2. Model:语言模型,负责生成回复
  3. 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的管道符 | 魔法

管道符 | 的工作原理很简单:

  1. prompt_template 接收输入 {"text": "我喜欢吃苹果和香蕉。"},生成格式化后的提示词
  2. llm 接收提示词,调用OpenAI模型生成回复
  3. 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": "什么是爱情?"})

执行流程:

  1. 输入:“什么是爱情?”
  2. ChainA 生成详细解释(可能几百字)
  3. 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."
})

执行流程:

  1. 输入:英文内容
  2. Chain 1:翻译成中文
  3. Chain 2:总结中文内容
  4. Chain 3:识别语言(中文)
  5. Chain 4:用中文进行评论

关键点:

  • 每个 LLMChain 必须指定 output_key
  • SequentialChain 必须声明 input_variablesoutput_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'
}

工作原理:

  1. LLM 把问题转换成数学表达式:10 ** 3 + 100
  2. Python 执行计算:1000 + 100 = 1100
  3. 返回结果

适用场景: 需要精确计算的场景,比如价格计算、统计分析等。

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)

工作原理:

  1. 加载文档:PyPDFLoader 读取 PDF 文件
  2. 合并内容:把所有页的内容合并成一个长文本
  3. 传递给LLM:一次性传递给 LLM 处理
  4. 返回结果:总结或问答结果

适用场景: 少量/中等长度文档的处理,比如文档摘要、多源问答等。

注意: 如果文档太长,超过模型的上下文窗口,需要用其他方法(如 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`;

工作原理:

  1. 分析数据库结构:chain 自动读取表结构(表名、字段名、类型等)
  2. 生成SQL:LLM 根据问题和表结构生成 SQL 语句
  3. 返回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)

输出:

香蕉是黄色的水果。

工作原理:

  1. 合并文档:把所有文档内容拼接成一个长文本
  2. 构造提示词:把文档内容和问题一起传递给 LLM
  3. 生成回答:LLM 根据文档内容回答问题

适用场景:

  • 文档问答:基于多个文档回答问题
  • 文档摘要:总结多个文档的内容
  • 内容提取:从多个文档中提取特定信息

注意事项:

  • 所有文档内容会一次性传递给 LLM,如果文档太长,可能超过上下文窗口
  • 适合少量/中等长度文档,如果文档很多或很长,建议用 MapReduceDocumentsChain

五、传统Chain vs LCEL:该如何选择?

看完这么多链,你可能会问:什么时候用传统 Chain,什么时候用 LCEL?

这里给大家一个简单的对比表:

对比维度 传统Chain LCEL
语法 对象实例化(如 LLMChain(llm=llm, prompt=prompt) 管道符 `
可读性 较复杂,代码量大 简洁直观,一眼看懂
灵活性 功能丰富,支持复杂场景 适合简单场景,复杂场景可能力不从心
调试 verbose=True 显示执行日志 内置调试支持
学习成本 需要记住各种 Chain 类的用法 只需理解管道符概念
社区趋势 逐渐被 LCEL 取代 官方推荐,未来主流

我的建议:

  1. 简单场景优先用 LCEL
    如果只是 Prompt + Model + Parser 的组合,直接用 LCEL,代码更简洁。

    # LCEL:简洁优雅
    chain = prompt | llm | parser
    
  2. 复杂场景考虑传统 Chain
    如果需要多步处理、多变量传递、特殊逻辑,用传统 Chain 更合适。

    # 传统Chain:功能强大
    chain = SequentialChain(
        chains=[chain1, chain2, chain3],
        input_variables=["var1", "var2"],
        output_variables=["out1", "out2"]
    )
    
  3. 新项目建议用 LCEL
    LangChain 官方正在推动 LCEL,未来会有更多基于 LCEL 的高级功能。传统 Chain 虽然还能用,但逐渐会被淘汰。

  4. 特殊需求用专用链
    比如数学计算用 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 是把所有文档一次性塞给模型,如果文档太长就会报错。

解决方案:

  1. MapReduceDocumentsChain:先对每个文档单独处理,再汇总结果
  2. RefineDocumentsChain:逐个文档迭代优化答案
  3. 截断文档:只取文档的前 N 个字符
  4. 用向量检索:只检索相关段落,不处理全部文档
# 示例:截断文档
max_chars = 10000
truncated_docs = [
    Document(page_content=doc.page_content[:max_chars])
    for doc in docs
]

七、总结

这篇文章详细介绍了 LangChain 的 Chains 模块,主要内容包括:

  1. Chains 的作用:把多个组件串联起来,数据自动流转,减少重复代码。

  2. LCEL 语法:用管道符 | 连接组件,简洁直观,适合简单场景。

    chain = prompt | llm | parser
    
  3. 传统 Chain 类型

    • LLMChain:最基础的链,Prompt + LLM
    • SimpleSequentialChain:简单顺序链,单输入单输出
    • SequentialChain:高级顺序链,多输入多输出
    • LLMMathChain:数学计算链
    • StuffDocumentsChain:文档处理链
  4. 基于 LCEL 的高级链

    • create_sql_query_chain:自然语言转 SQL
    • create_stuff_documents_chain:文档合并处理
  5. 如何选择

    • 简单场景用 LCEL
    • 复杂场景用传统 Chain
    • 特殊需求用专用链

Chains 是 LangChain 的核心功能,掌握了它,你就能构建出更复杂、更强大的 AI 应用。下一篇我们会学习 Memory(记忆)模块,让 LLM 能记住上下文,实现真正的对话功能。


热门专栏推荐

等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐