开心听书完整版
203.53MB · 2025-10-27
Chain:链,用于将多个组件(提示模板,LLM模型,记忆,工具等)链接起来,形成可复用的工作流,完成复杂的任务。
Chain的核心思想是通过组合不同的模板化单元,实现比单一组件更强大的功能。比如:
LLM与Prompt Template(提示模板)结合LLM与输出解析器结合LLM与外部数据结合,例如用于问答LLM与长期记忆结合,例如勇敢与聊天历史记录第一个LLM的输出作为第二个LLM的输入,...,将多个LLM按顺序结合在一起使用LCEL,可以构造出结构最简单的Chain
LangChain表达式语言(LCEL,LangChain Expression Language)是一种声明式的方法,可以轻松地将多个组件链接成AI工作流。它通过Python原生操作符(如管道符|)将组件链接成可执行流程,显著简化了AI应用的开发。
LCEL的基本构成:提示(Prompt)+ 模型(model)+ 输出解释器(OutputParser)
即:
PromptValue。PromptValue可以传递给LLM(它以字符产作为输入)或ChatModel(它以消息列表作为输入)BaseMessage|运算符轻松创建这个Chain。|运算符在LangChain中用于将两个元素组合在一起Runnable协议,保证一致的调用性(invoke/batch/stream)Runnable是LangChain定义的一个抽象接口(Protocol),它强制要求所有LCEL组件是西安一组标准方法:
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()代码会变成:
prompt_text = prompt.format(topic = "猫")
model_out = model.generate(prompt_text)
result = parser.paese(model_out)
每个组件调用方式不同,组合时需要手动适配。
LCEL解决方案
通过Runnable协议统一:
# 分步调用
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
# 调用大模型的前置环境配置
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将不同的组件组合成一个单一的链条
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'>
当然可以!这是一个关于卫生纸的笑话:
有一天,卫生纸和纸巾在聊天。卫生纸自豪地说:“我最厉害,谁都离不开我!”
纸巾笑着回答:“可你有多久没出门了?你就只在厕所里待着!”
卫生纸不以为然:“那又怎样?我可是‘急需’的角色!”
纸巾反击:“但我可是‘随时待命’的明星!”
最后,卫生纸叹了口气:“好吧,我们都是‘纸’上谈兵!”
希望这个笑话能让你开心!
LCEL之前,最基础也最常见的类型是LLMChain
这个链至少包括一个提示词模板(PromptTemplate),一个语言模型(LLM或者聊天模型)
特点:
1.配置任务链:使用LLMChain类将任务与提示词结合,形成完整的任务链
chain = LLMChain(llm = chat_model, prompt = prompt_template)
2.执行任务链:使用invoke()等方法执行任务链,并获取生成结果。可以根据需要对输出进行处理和展示。
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 |
顺序链(SequentialChain)允许将多个链顺粗连接起来,每个Chain的输出作为下一个Chain的输入,形成特定场景的流水线(Pipline)
顺序链有两种类型:
SimpleSequentialChain:最简单的顺序链,多个链串行执行,每个步骤都有单一的输入和输出,一个步骤的输出就是下一个步骤的输入,无需手动映射。
例:
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:更通用的顺序链,具体来说:
多变量支持:允许不同子链由独立输入/输出变量灵活映射:需显式定义变量如何从一个链传递到下一个链。即精准地命名输入关键字和输出关键字,来明确链之间的关系复杂流程控制:支持分支,条件逻辑(分别通过input_variables和output_variables配置输入和输出)例:
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)
还可以单独输出
print(response["chainA_chains_key"])
print(response["chainB_chains_key"])
场景:多数据源处理
举例:根据商品名查询-->数据库获取价格,生成促销文案
使用 SimpleSequentialChain(失败)
# 假设链1返回{"price":100},链2需要{product:"...",price:...}
# 结构不匹配,无法自动传递
使用SequentialChain(正确)
# 查询链
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将用户的问题转换为数学问题,然后将数学问题转换为可以使用 Python 的 numexpr 库执行的表达式。使用运行此代码的输出来回答问题。
路由链(RounterChain)用于创建可以动态选择下一条链的链。可以自动分析用户的需求,任何引导到最适合的链中执行,获取响应并发挥最终结果
比如,我们目前有三类chain,分别对应三种学科的问题解答。我们的输入内容也是与这三种学科对应,但是随机的,比如第一次输入数学问题、第二次有可能是历史问题... 这时候期待的效果是:可以根据输入的内容是什么,自动将其应用到对应的子链中。RouterChain就为我们提供了这样一种能力。
RounterChain图示:
StuffDocumentsChain是一种文档处理链,他的核心作用是将多个文档内容合并(“填充”或“塞入”)到单个提示词(promt)中,然后传递给语言模型(LLM)处理
使用场景:由于所有文档被完整拼接,LLM能同时看到全部内容,所以适合需要全局理解的任务,如总结,问答,对比分析等。但注意,仅适合处理少量/中等长度文档的场景。
前面讲解的都是Legacy Chains,下面看最新的基于LCEL构建的Chains
create_sql_query_chain,SQL查询链,时创建生成SQL查询的链,用于将自然语言转换成数据库的SQL查询
例:
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用于将多个文档内容合并成单个长文本的链式工具,并一次性传递给LLM处理(而不是分多次处理)
适合场景:
例:多文档摘要
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})
'蓝莓是蓝色的浆果。'