LangChain使用之Chains

Chains的基本使用

Chain的基本概念

Chain:链,用于将多个组件(提示模板,LLM模型,记忆,工具等)链接起来,形成可复用的工作流,完成复杂的任务。

Chain的核心思想是通过组合不同的模板化单元,实现比单一组件更强大的功能。比如:

  • LLMPrompt Template(提示模板)结合
  • LLM输出解析器结合
  • LLM外部数据结合,例如用于问答
  • LLM长期记忆结合,例如勇敢与聊天历史记录
  • 通过将第一个LLM的输出作为第二个LLM的输入,...,将多个LLM按顺序结合在一起

LCEL及其构成

使用LCEL,可以构造出结构最简单的Chain

LangChain表达式语言(LCEL,LangChain Expression Language)是一种声明式的方法,可以轻松地将多个组件链接成AI工作流。它通过Python原生操作符(如管道符|)将组件链接成可执行流程,显著简化了AI应用的开发。

LCEL的基本构成:提示(Prompt)+ 模型(model)+ 输出解释器(OutputParser)

即:

在这个链条中,用户输入被传递给提示词模板,然后提示模板的输入被传递给模型,然后模型的输出被传递给输出解析器
chain = prompt | model | output_parser chian.invoke("input":"你叫什么名字")

  • Prompt :Prompt是一个BasePromptTemplate,这意味着它接受一个模板变量的字典并生成一个PromptValue。PromptValue可以传递给LLM(它以字符产作为输入)或ChatModel(它以消息列表作为输入)
  • Model :将PromptValue传递给model。如果我们的model是一个ChatModel,这意味着它将输出一个BaseMessage
  • OutputParser:将model的输出传递给output_parser,他是一个BaseOutputParser,意味着它可以接收字符串或BaseMessage作为输入
  • chain :我们可以使用|运算符轻松创建这个Chain。|运算符在LangChain中用于将两个元素组合在一起
  • invoke :所有LCEL对象都实现了Runnable协议,保证一致的调用性(invoke/batch/stream

| 符号类似于shell里面的管道操作符,他将不同的组件链接在一起,将前一个组件的输出作为下一个组件的输入,这就形成了一个AI工作流

Runnable

Runnable是LangChain定义的一个抽象接口(Protocol),它强制要求所有LCEL组件是西安一组标准方法:

python 复制代码
class Runnable(Protocol):
    def invoke(self, input: Any) -> Any: ... # 单输入单输出
    def batch(self, inputs: List[Any]) -> List[Any]: ... # 批量处理
    def stream(self, input: Any) -> Iterator[Any]: ... # 流式输出
# 还有其他方法如 ainvoke(异步)等...

任何实现了这些方法的对象都被视为LCEL兼容组件。比如:聊天模型,提示词模板,输出解析器,检索器,代理(智能体)等

每个LCEL对象都实现了Runnable接口,该接口定义了一组公共的调用方法。这使得LCEL对象链也自动支持这些调用成为可能

为什么需要同意调用方式?

假设没有同一协议:

  • 提示词渲染用:.format()
  • 模型调用用:.generate()
  • 解析器解析用:.parse()
  • 工具调用用:.run()

代码会变成:

python 复制代码
prompt_text = prompt.format(topic = "猫")
model_out = model.generate(prompt_text)
result = parser.paese(model_out)

每个组件调用方式不同,组合时需要手动适配。

LCEL解决方案

通过Runnable协议统一:

python 复制代码
# 分步调用
prompt_text = prompt.invoke(topic = "猫")
model_out = model.invoke(prompt_text)
result = parser.invoke(model_out)

# LCEL管道式
chain = prompt | model | parser			# 用管道符组合
result = chain.invoke({"topic":"猫"})	# 所有组件同一用invoke
  • 一致性:无论组件的功能多复杂(模板/提示词/工具),调用方式完全相同
  • 组合性 :管道操作符|背后自动处理类型匹配和中间结果的传递

使用举例

例1:没有使用chain

python 复制代码
# 调用大模型的前置环境配置
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
import os
import dotenv


dotenv.load_dotenv()

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
os.environ["OPENAI_BASE_URL"] = os.getenv("OPENAI_BASE_URL")

chat_model = ChatOpenAI(
    model = "gpt-4o-mini"
)

# 开始调用
from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template(
    template = "给我将一个关于{topic}的笑话"
)
parser = StrOutputParser()
prompt_value = prompt_template.invoke({"topic":"卫生纸"})
result = chat_model.invoke(prompt_value)
output = parser.invoke(result)

print(type(output))
print(output)

<class 'str'>

当然可以!这是一个关于卫生纸的笑话:

有一天,有两个卷纸在讨论谁更受欢迎,卫生纸自豪地说:"我每天都被人用来擦屁股,大家都离不开我!"

另一卷纸微笑着说:"那你可真有'屁股'的自信!"

希望这个笑话能让你开心!


例2:使用Chain将提示模板,模型,解析器链接在一起。使用LCEL将不同的组件组合成一个单一的链条

python 复制代码
prompt_template = PromptTemplate.from_template(
    template = "给我将一个关于{topic}的笑话"
)
parser = StrOutputParser()

# 构建链式调用(LCEL语法)
chain = prompt_template | chat_model | parser
output = chain.invoke({"topic":"卫生纸"})

print(type(output))
print(output)

<class 'str'>

当然可以!这是一个关于卫生纸的笑话:

有一天,卫生纸和纸巾在聊天。卫生纸自豪地说:"我最厉害,谁都离不开我!"

纸巾笑着回答:"可你有多久没出门了?你就只在厕所里待着!"

卫生纸不以为然:"那又怎样?我可是'急需'的角色!"

纸巾反击:"但我可是'随时待命'的明星!"

最后,卫生纸叹了口气:"好吧,我们都是'纸'上谈兵!"

希望这个笑话能让你开心!


传统Chain的使用

基础链:LLMChain

使用说明

LCEL之前,最基础也最常见的类型是LLMChain

这个链至少包括一个提示词模板(PromptTemplate),一个语言模型(LLM或者聊天模型)

注意:LLMChain was deprecated in LangChain 0.1.17 and will be removed in 1.0. Use prompt | llm instead。

特点

  • 用于单次问答,输入一个Prompt,输出LLM的响应
  • 适合无上下文的简单任务(如翻译,摘要,分类等)
  • 无记忆:无法自动维护聊天历史

主要步骤

1.配置任务链:使用LLMChain类将任务与提示词结合,形成完整的任务链

python 复制代码
chain = LLMChain(llm = chat_model, prompt = prompt_template)

2.执行任务链:使用invoke()等方法执行任务链,并获取生成结果。可以根据需要对输出进行处理和展示。

python 复制代码
result = chain.invoke(...)
print(reslut)

参数说明

参数名 类型 默认值 必填 说明
llm Union[Runnable[LanguageModelInput,str],Runnable[LanguageModelInput,BaseMessage]] - 要调用的语言模型
prompt BasePromptTemplate - 要使用的提示对象
verbose bool False 是否以详细模式运行。在详细模式下,一些中间日志将被打印到控制台
memory Optional[BaseMemory None 可选的记忆对象
output_parser BaseLLMOutputParse - 要使用的输出解释器,默认为StrOutputParser
callbacks Callbacks None 可选的回调处理器列表或回调管理器。在调用链的生命周期中的不同阶段被调用,从on_chain_start开始,到on_chain_end或on_chain_error结束。自定义链可以选择调用额外的回调方法
llm_kwargs dict - 语言模型的关键字参数字典
metadata Optional[Dict[str, Any]] None 与链相关联的可选元数据。默认为None
return_final_only bool True 是否只返回最终解析结果。默认为True

顺序链-SimpleSequentialChain

顺序链(SequentialChain)允许将多个链顺粗连接起来,每个Chain的输出作为下一个Chain的输入,形成特定场景的流水线(Pipline)

顺序链有两种类型

  • 单个输入/输出:对应着:SimpleSequentialChain
  • 多个输入/输出:对应着:SequentialChain

说明

SimpleSequentialChain:最简单的顺序链,多个链串行执行,每个步骤都有单一的输入和输出,一个步骤的输出就是下一个步骤的输入,无需手动映射。

使用

例:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import LLMChain
from langchain.chains.sequential import SimpleSequentialChain

chainA_template = ChatPromptTemplate.from_messages([
    ("system","你是一个网上冲浪高手,善于搜集资料,并保证资料的真实性"),
    ("human","请你尽可能详细的解释一下:{question}")
])
chainA_chains = LLMChain(
    llm = chat_model,
    prompt = chainA_template,
    verbose = True,
)

chainB_template = ChatPromptTemplate.from_messages([
    ("system","你非常善于提取文本中的重要信息,并作除简短的总结"),
    ("human","这是针对一个提问的完整的解释说明内容:{description}"),
    ("human","请你根据上述说明,尽可能简短的输出重要结论,控制在20字以内")
])
chainB_chains = LLMChain(
    llm = chat_model,
    prompt = chainB_template,
    verbose = True
)

final_chain = SimpleSequentialChain(
    chains = [chainA_chains, chainB_chains],
    verbose = True,
)

response = final_chain.invoke(input={"input":"如何从读者口中评价《龙族》小说的作者江南"})
print(response)

在这个过程中,因为SimpleSequentialChain定义的是顺序链,所以在chains参数中传递的列表要按照顺序来进行传入,即chainA要在chainB之前。同时,在调用时,不再使用chainA中定义的{question}参数,也不是chainB中定义的{description}参数,而是要使用input进行变量的传递。

顺序链-SequentialChain

说明

SequentialChain:更通用的顺序链,具体来说:

  • 多变量支持:允许不同子链由独立输入/输出变量
  • 灵活映射:需显式定义变量如何从一个链传递到下一个链。即精准地命名输入关键字和输出关键字,来明确链之间的关系
  • 复杂流程控制:支持分支,条件逻辑(分别通过input_variablesoutput_variables配置输入和输出)

使用

例:

python 复制代码
from langchain.chains.sequential import SequentialChain
from langchain_core.prompts.chat import ChatPromptTemplate

chainA_prompt = ChatPromptTemplate.from_messages([
    ("system","你是一个精通各个领域知识的教授"),
    ("human","请你尽可能详细的解释一下:{konwledge},并且{action}")
])
chainA_chains = LLMChain(
    llm = chat_model,
    prompt = chainA_prompt,
    verbose = True,
    output_key = "chainA_chains_key"
)

chainB_prompt = ChatPromptTemplate([
    ("system","你非常善于提取文本中的重要信息,并做出简短的总结"),
    ("human","这是针对上一个提问的完整的解释说明内容:{chainA_chains_key}"),
    ("human","请你根据上述说明,尽可能简短的输出重要结论,请控制在100字以内"),
])
chainB_chains = LLMChain(
    llm = chat_model,
    prompt = chainB_prompt,
    verbose = True,
    output_key = "chainB_chains_key"
)

final_chain = SequentialChain(
    chains = [chainA_chains, chainB_chains],
    input_variables=["konwledge","action"],
    output_variables=["chainA_chains_key","chainB_chains_key"],
    verbose = True,
)

response = final_chain({
    "konwledge":"中国足球为什么踢得烂",
    "action":"举一个实际的例子"
})

print(response)

还可以单独输出

python 复制代码
print(response["chainA_chains_key"])

print(response["chainB_chains_key"])

使用场景

场景:多数据源处理

举例:根据商品名查询-->数据库获取价格,生成促销文案

使用 SimpleSequentialChain(失败)

python 复制代码
# 假设链1返回{"price":100},链2需要{product:"...",price:...}
# 结构不匹配,无法自动传递

使用SequentialChain(正确)

python 复制代码
# 查询链
query_chain = LLMChain(
    llm = chat_model,
    prompt = PromptTemplate.from_template(
        "请模拟查询{product}的市场价格,直接返回一个合理的价格数字(如899),不要包含其他任何文字或代码"
    ),
    verbose = True,
    output_key = "price"
)

# 文案链
prompt_chain = LLMChain(
    llm = chat_model,
    prompt = PromptTemplate.from_template(
        "为{product}(售价为{price}元)创作一篇50字以内的促销文案,要求突出产品卖点"
    ),
    verbose = True,
    output_key = "prompt_text"
)

final_chain = SequentialChain(
    chains = [query_chain, prompt_chain],
    verbose = True,
    input_variables = ["product"],
    output_variables = ["price","prompt_text"]
)

result = final_chain.invoke({"product":"华为mate60"})
print(result)

> Entering new SequentialChain chain...

> Entering new LLMChain chain...

Prompt after formatting:

> Entering new SequentialChain chain...

> Entering new LLMChain chain...

Prompt after formatting:

请模拟查询华为mate60的市场价格,直接返回一个合理的价格数字(如899),不要包含其他任何文字或代码

> Finished chain.

> Entering new LLMChain chain...

Prompt after formatting:

> Entering new SequentialChain chain...

> Entering new LLMChain chain...

Prompt after formatting:

请模拟查询华为mate60的市场价格,直接返回一个合理的价格数字(如899),不要包含其他任何文字或代码

> Finished chain.

> Entering new LLMChain chain...

Prompt after formatting:

为华为mate60(售价为5999元)创作一篇50字以内的促销文案,要求突出产品卖点

> Finished chain.

> Finished chain.

{'product': '华为mate60', 'price': '5999', 'prompt_text': '体验华为Mate60,5999元,搭载先进科技,强劲性能,超清影像,续航持久,轻松应对多任务。时尚设计与智能交互,助你畅享生活每一刻!立即拥有,开启智慧新生活!'}


数学链LLMMathChain

LLMMathChain将用户的问题转换为数学问题,然后将数学问题转换为可以使用 Python 的 numexpr 库执行的表达式。使用运行此代码的输出来回答问题。

路由链RounterChain

路由链(RounterChain)用于创建可以动态选择下一条链的链。可以自动分析用户的需求,任何引导到最适合的链中执行,获取响应并发挥最终结果

比如,我们目前有三类chain,分别对应三种学科的问题解答。我们的输入内容也是与这三种学科对应,但是随机的,比如第一次输入数学问题、第二次有可能是历史问题... 这时候期待的效果是:可以根据输入的内容是什么,自动将其应用到对应的子链中。RouterChain就为我们提供了这样一种能力。

他会首先决定将要传递下去的子链,然后把输入传递给那个链。并且在设置的时候需要注意为其设置默认chain,以兼容输入内容不满足任意一项时的情况

RounterChain图示

文档链StuffDocumentsChain

StuffDocumentsChain是一种文档处理链,他的核心作用是将多个文档内容合并("填充"或"塞入")到单个提示词(promt)中,然后传递给语言模型(LLM)处理

使用场景:由于所有文档被完整拼接,LLM能同时看到全部内容,所以适合需要全局理解的任务,如总结,问答,对比分析等。但注意,仅适合处理少量/中等长度文档的场景。

基于LCEL构建的Chains的类型

前面讲解的都是Legacy Chains,下面看最新的基于LCEL构建的Chains

  • create_sql_query_chain
  • create_stuff_documents_chain
  • create_openai_fn_runnable
  • create_structured_output_runnable
  • load_query_constructor_runnable
  • create_history_aware_retriever
  • create_retrieval_chain

create_sql_query_chain

create_sql_query_chain,SQL查询链,时创建生成SQL查询的链,用于将自然语言转换成数据库的SQL查询

例:

python 复制代码
from langchain_community.utilities import SQLDatabase
# 连接 MySQL 数据库
db_user = "root"
db_password = "abc123" #根据自己的密码填写
db_host = "127.0.0.1"
db_port = "3306"
db_name = "employees"
# mysql+pymysql://用户名:密码@ip地址:端口号/数据库名
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}:
{db_port}/{db_name}")
print("哪种数据库:", db.dialect)
print("获取数据表:", db.get_usable_table_names())
# 执行查询
res = db.run("SELECT count(*) FROM employees;")
print("查询结果:", res)

create_stuff_documents_chain

create_stuff_documents_chain用于将多个文档内容合并成单个长文本的链式工具,并一次性传递给LLM处理(而不是分多次处理)

适合场景:

  • 保持上下文完整,适合需要全局理解所有文档内容的任务(如总结、问答)
  • 适合处理少量/中等长度文档的场景

例:多文档摘要

python 复制代码
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.documents import Document

# 定义提示词模板
prompt = PromptTemplate.from_template(
    "如下文档{docs}中说,蓝莓是什么颜色的?"
)

# 创建链
chain = create_stuff_documents_chain(chat_model,prompt,document_variable_name ="docs")

# 文档输入
docs = [
    Document(
        page_content="苹果,学名Malus pumila Mill.,别称西洋苹果、柰,属于蔷薇科苹果属的植物。苹果是全球最广泛种植和销售的水果之一,具有悠久的栽培历史和广泛的分布范围。苹果的原始种群主要起源于中亚的天山山脉附近,尤其是现代哈萨克斯坦的阿拉木图地区,提供了所有现代苹果品种的基因库。苹果通过早期的贸易路线,如丝绸之路,从中亚向外扩散到全球各地。"
    ),
    Document(
        page_content="香蕉是黄色的水果,主要产自热带地区。"
    ),
    Document(
        page_content="蓝莓是蓝色的浆果,含有抗氧化物质。"
    )
]

# 执行摘要
chain.invoke({"docs":docs})

'蓝莓是蓝色的浆果。'


相关推荐
weixin_4380774917 小时前
langchain官网翻译:Build a Question/Answering system over SQL data
数据库·sql·langchain·agent·langgraph
深度学习机器1 天前
AI Agent上下文工程设计指南|附实用工具推荐
langchain·llm·agent
AI大模型1 天前
利用腾讯混元大模型搭建Cherry Studio自有知识库,打造“智能第二大脑”
程序员·llm·agent
大模型真好玩1 天前
低代码Agent开发框架使用指南(三)—小白5分钟利用Coze轻松构建智能体
人工智能·agent·coze
聚客AI2 天前
🥺单智能体总是翻车?可能是你缺了这份LangGraph多Agent架构指南
人工智能·llm·agent
大模型教程2 天前
半小时部署企业智能问答系统!MaxKB让知识管理效率翻倍
程序员·llm·agent
AI大模型2 天前
告别数据隐私焦虑!我用FastGPT免费私有化部署了AI个人知识管理系统辅助写作
程序员·llm·agent
大模型教程2 天前
基于Dify的RAG知识库搭建
程序员·llm·agent
AI大模型2 天前
微软AI Agents入门课程爆火!GitHub星标破万,零基础构建AI智能体
程序员·llm·agent