学习路程九 langchain核心组件 Chains
前序
之前完成了Models I/O , 输入提示词,输出解释器。下面学习langchain的另一个核心组件:
链(Chain),也叫链式语言表达式(LCEL),是 LangChain 的核心组件。如果把用 LangChain 构建 AI 大模型应用的过程比作“积木模型”的搭建与拼接,那么 Chain 就是是该模型搭建过程中的骨骼部分,通过它将各模块快速组合在一起就可以快速搭建一个应用。langchain提供的chains使用声明式方法将多个操作链接在一起,形成一个完整的处理流程。常见链的类型有3种:LLMChain,SequentialChain、LLMRouteChain。
官网文档:https://python.langchain.com/docs/concepts/lcel/
大模型链(LLMChain)
LLMChain是最常用的一个
在之前学习的过程种,完成一次调用大致分一下步骤:
#1.创建模版
prompt_template = PromptTemplate.from_template("你是一个产品顾问。请给公司刚生产出来的 {production},取一个好听的名字和广告语。")
messages = prompt_template.invoke({"production": "充电宝"})
#2.实例化模型
chat_model = ChatDeepSeek(model="deepseek-chat")
#3.通过模版完整的提示词
messages = prompt_template.invoke({"production": "充电宝"})
#4.把提示词传给大模型
response = chat_model.invoke(messages)
# 5.把结果给输出解释器
res= StrOutputParser().invoke(response)
通过 LLMChain(大模型链) 可以直接将数据、Prompt、以及想要应用的大模型串到一起连贯调用。
老版本的定义方式已经被弃用了。
chain = LLMChain(model,prompt)
新的方式如下:
prompt_template = "你是一个产品顾问。请给公司刚生产出来的 {production},取一个好听的名字和广告语。"
chat_model = ChatDeepSeek(model="deepseek-chat")
chain = PromptTemplate.from_template(prompt_template) | chat_model | StrOutputParser()
chain.invoke({"production": "充电宝"})
这里的chain写法类似于linux的管道符"|",把上一个的输出作为下一个的输入。
序列链(Sequential Chains)
不同于基本的 LLMChain,Sequential Chain(序列链)是由一系列的链组合而成的,许将多个链按顺序链接在一起,前一个链的输出作为下一个链的输入。序列链有两种类型,一种是单个输入输出,另一个则是多个输入输出。
import os
import langchain
# LangChain相关模块的导入
from langchain.chains import LLMChain
from langchain_deepseek import ChatDeepSeek
from langchain.prompts import ChatPromptTemplate
from langchain.chains.sequential import SequentialChain
os.environ['DEEPSEEK_API_KEY'] = "sk-e243xxx"
# 在全局范围开启详细模式,能将调用大模型时发送的数据打印到控制台,绿色文本
langchain.verbose = True
llm = ChatDeepSeek(model="deepseek-chat", temperature=0.5)
# Chain1 获得内容
prompt1 = ChatPromptTemplate.from_template(
"请简单介绍一下历史人物: {username}"
)
chain1 = LLMChain(
llm=llm,
prompt=prompt1,
output_key="introduce"
)
# Chain2 根据内容,简明扼要
prompt2 = ChatPromptTemplate.from_template(
"基于给出的人物信息, 简明扼要,整理出不多于40个字符内容: {introduce}"
)
chain2 = LLMChain(
llm=llm,
prompt=prompt2,
output_key="brief_introduce"
)
# Chain3 把简介翻译成英文
prompt3 = ChatPromptTemplate.from_template(
"把给出的内容翻译为英文?: {brief_introduce}"
)
chain3 = LLMChain(
llm=llm,
prompt=prompt3,
output_key="english_brief_introduce"
)
# 标准版的序列Chain,SequentialChain,其中每个chain都支持多个输入和输出,
# 根据chains中每个独立chain对象,和chains中的顺序,决定参数的传递,获取最终的输出结果
seq_chain = SequentialChain(
chains=[chain1, chain2, chain3],
input_variables=["username"],
output_variables=["introduce", "brief_introduce", "english_brief_introduce"],
verbose=True
)
username = "秦始皇"
res = seq_chain(username)
print(res)
这里还存在一点问题,LLMChain在0.1.17版本就被弃用了,在1.0将会被移除。
所以这里LLMChain 需要更改成官方说的 chain = prompt | llm
这样,但是这样不支持显式的展示输出的key了。怎么展示并显示出运行过程,需要后面研究研究,有结果了再补充回这里。
LLMChain,是有input_keys,但是RunnableSequence
源码并没有,所以运行时,prompt | llm 的是一个RunnableSequence
对象,然后又说 RunnableSequence
对象没有input_keys
,然后报错,不能单纯只修改成prompt | llm
的样式。
如果修改成prompt | llm
样式,一下方式也可以运行。但是没有输出过程,只有最终结果
# 方式一,普通序列链调用
seq_chain = chain1 | chain2 | chain3
# 方式二,通过RunnablePassthrough
runnable = RunnablePassthrough.assign(username=lambda inputs: inputs['username']) | chain1 | chain2 | chain3
res = runnable.invoke({'username': '秦始皇'})
print(res)
路由链(RouterChain)
**路由链(RouterChain)**是由LLM根据输入的Prompt去选择具体的某个链。路由链中一般会存在多个Prompt,Prompt结合LLM决定下一步选择哪个链。
路由链一般涉及到2个核心类,LLMRouterChain
和MultiPromptChain
LLMRouterChain:使用LLM路由到可能的prompt中
MultiPromptChain :是一种在自然语言处理(NLP)中使用的技术,主要用于在多个提示词之间进行路由选择。当你有多个提示词并且只想路由到其中一个时,可以使用MultiPromptChain。其基本原理是根据输入的Prompt选择合适的链进行处理
使用场景和基本原理
MultiPromptChain通常用于复杂的聊天机器人或NLP应用中,特别是在需要根据用户输入选择不同处理路径的场景。其使用步骤包括:
- 准备多个链的Prompt提示词,并封装成链。
- 将可能路由到的链封装到destination_chains里。
- 构建多提示词和RouterChain,负责选择下一个要调用的链。
- 构建默认链,用于未匹配到合适链时的处理
以下代码是原本的多路由链使用,但是运行给出警告,MultiPromptChain
将会被移除
原使用代码,被废弃
import os
from langchain_deepseek import ChatDeepSeek
import langchain
langchain.verbose = True
# 初始化模型
os.environ['DEEPSEEK_API_KEY'] = "sk-e2xxx"
model = ChatDeepSeek(model="deepseek-chat")
from langchain.chains.router import LLMRouterChain, MultiPromptChain
from langchain.chains.router.llm_router import RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
from langchain.chains import LLMChain, ConversationChain
from langchain.prompts import PromptTemplate
###### 准备多个提示词封装成链 #####
# 准备3条目的链:一条物理链,一条数学链
# 1. 物理链
physics_template = """
你是一位物理学家,擅长回答物理相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
"""
physics_prompt = PromptTemplate.from_template(physics_template)
# physics_chain = LLMChain(llm=model, prompt=physics_prompt)
physics_chain = physics_prompt | model
# 2. 数学链
math_template = """
你是一个数学家,擅长回答数学相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
"""
math_prompt = PromptTemplate.from_template(math_template)
# math_chain = LLMChain(llm=model, prompt=math_prompt)
math_chain = math_prompt | model
# 3. 英语链
english_template = """
你是一个非常厉害的英语老师,擅长回答英语相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
"""
english_prompt = PromptTemplate.from_template(english_template)
# english_chain = LLMChain(llm=model, prompt=english_prompt)
english_chain = english_prompt | model
###### 将可能路由到的链封装到destination_chains里。 #####
destination_chains = {}
destination_chains["physics"] = physics_chain
destination_chains["math"] = math_chain
destination_chains["english"] = english_chain
######### 构建默认链,用于未匹配到合适链时的处理
default_chain = ConversationChain(llm=model, output_key="text")
# 让多路由模板 能找到合适的 提示词模板
destinations_template_str = """
physics:擅长回答物理问题
math:擅长回答数学问题
english:擅长回答英语问题
"""
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(
destinations=destinations_template_str
)
##### 构建多提示词和RouterChain,负责选择下一个要调用的链。 #####
# 通过路由提示词模板,构建路由提示词
router_prompt = PromptTemplate(
template=router_template,
input_variables=["input"],
output_parser=RouterOutputParser(),
)
######### 路由链
router_chain = LLMRouterChain.from_llm(llm=model, prompt=router_prompt)
######### 最终的链
multi_prompt_chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=destination_chains,
default_chain=default_chain,
verbose=True,
)
# multi_prompt_chain.invoke({"input": "重力加速度是多少?"})
# multi_prompt_chain.invoke("y=x^2+2x+1的导数是多少?")
multi_prompt_chain.run("将以下英文翻译成中文,只输出中文翻译结果:\n The largest community building the future of LLM apps.")
建议使用新的 langgraph,基于图结构的
官网:https://python.langchain.com/docs/versions/migrating_chains/multi_prompt_chain/
from typing import Literal
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, START, StateGraph
from typing_extensions import TypedDict
import os
from langchain_deepseek import ChatDeepSeek
import langchain
langchain.verbose = True
# 初始化模型
os.environ['DEEPSEEK_API_KEY'] = "sk-e243xxx"
llm = ChatDeepSeek(model="deepseek-chat")
# Define the prompts we will route to
prompt_1 = ChatPromptTemplate.from_messages(
[
("system", "你是一个数学家,擅长解答数学问题."),
("human", "{input}"),
]
)
prompt_2 = ChatPromptTemplate.from_messages(
[
("system", "你是一个生物学家,擅长解决生物学相关的问题."),
("human", "{input}"),
]
)
chain_1 = prompt_1 | llm | StrOutputParser()
chain_2 = prompt_2 | llm | StrOutputParser()
default_chain = llm | StrOutputParser()
route_system = "对用户的提问进行合适的分类"
route_prompt = ChatPromptTemplate.from_messages(
[
("system", route_system),
("human", "{input}"),
]
)
# Define schema for output:
class RouteQuery(TypedDict):
"""Route query to destination expert."""
destination: Literal["数学", "生物", "其它"]
route_chain = route_prompt | llm.with_structured_output(RouteQuery)
# 定义数据结构
class State(TypedDict):
query: str
destination: RouteQuery
answer: str
query:
async def route_query(state: State, config: RunnableConfig):
destination = await route_chain.ainvoke(state["query"], config)
return {"destination": destination}
# 添加节点
async def prompt_1(state: State, config: RunnableConfig):
return {"answer": await chain_1.ainvoke(state["query"], config)}
async def prompt_2(state: State, config: RunnableConfig):
return {"answer": await chain_2.ainvoke(state["query"], config)}
async def prompt_3(state: State, config: RunnableConfig):
return {"answer": await default_chain.ainvoke(state["query"], config)}
# 定义选择节点的逻辑
def select_node(state: State) -> Literal["prompt_1", "prompt_2", "prompt_3"]:
if state["destination"] == "数学":
return "prompt_1"
elif state["destination"] == "生物":
return "prompt_2"
else:
return "prompt_3"
# 定义图的入口和边
graph = StateGraph(State)
graph.add_node("route_query", route_query)
graph.add_node("prompt_1", prompt_1)
graph.add_node("prompt_2", prompt_2)
graph.add_node("prompt_3", prompt_3)
graph.add_edge(START, "route_query")
graph.add_conditional_edges("route_query", select_node)
graph.add_edge("prompt_1", END)
graph.add_edge("prompt_2", END)
graph.add_edge("prompt_3", END)
app = graph.compile()
# 生成图结构
try:
app.get_graph().draw_mermaid_png(output_file_path="graph.png")
except Exception:
# This requires some extra dependencies and is optional
pass
async def run_app():
result = await app.ainvoke({"query": "人类起源是什么?"})
print(result["destination"])
print(result["answer"])
# 使用 asyncio 运行异步函数
import asyncio
asyncio.run(run_app())
这块内容感觉比较复杂,只能了解了一下大概实现逻辑,然后好多资料依旧在用MultiPromptChain,LLMChain,就感觉目前学习下来,找到的好多资料都在继续使用即将丢弃的内容,不太新。新的内容学习起来比较困难。
其他
from langchain import chains
点到chains里面,可以看到很多链,可以自行了解,我是看不下去了,推荐的也就是序列链和路由链。