LCEL使得从基本组件构建复杂链变得容易,并且支持诸如流处理、并行处理和日志记录等开箱即用的功能。
基本示例:提示 + 模型 + 输出解析器
最基本和常见的用例是将提示模板和模型链接在一起。为了看到这是如何工作的,让我们创建一个链,它接受一个主题并生成一个笑话:
%pip install –upgrade –quiet langchain-core langchain-community langchain-openai
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
chain.invoke({"topic": "ice cream"})
“Why don’t ice creams ever get invited to parties?\n\nBecause they always drip when things heat up!”
注意这行代码,我们将不同的组件拼接成一个单一的链使用 LCEL:
chain = prompt | model | output_parser
|
符号类似于unix 管道操作符, 它将不同的组件链接在一起,将一个组件的输出作为下一个组件的输入。
在这个链中,用户输入被传递给提示模板,然后提示模板的输出被传递给模型,然后模型的输出被传递给输出解析器。让我们分别看一下每个组件,以真正理解发生了什么。
1. Prompt
prompt
是一个 BasePromptTemplate
,这意味着它接收一个模板变量的字典并产生一个 PromptValue
。PromptValue
是一个完成提示的包装器,可以传递给 LLM
(接受字符串作为输入)或 ChatModel
(接受一系列消息作为输入)。它可以与任何语言模型类型一起工作,因为它定义了生成 BaseMessage
和生成字符串的逻辑。
prompt_value = prompt.invoke({"topic": "ice cream"})
prompt_value
#output
ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])
prompt_value.to_messages()
#output
[HumanMessage(content='tell me a short joke about ice cream')]
'Human: tell me a short joke about ice cream'
2. Model
PromptValue
然后传递给 model
。在这种情况下,我们的 model
是一个 ChatModel
,这意味着它将输出一个 BaseMessage
。
message = model.invoke(prompt_value)
message
AIMessage(content="Why don't ice creams ever get invited to parties?\n\nBecause they always bring a melt down!")
如果我们的 model
是一个 LLM
,它将输出一个字符串。
from langchain_openai.llms import OpenAIllm = OpenAI(model="gpt-3.5-turbo-instruct")llm.invoke(prompt_value)
'\n\nRobot: Why did the ice cream truck break down? Because it had a meltdown!'
3. Output parser
最后,我们将 model
输出传递给 output_parser
,它是一个 BaseOutputParser
,这意味着它接受字符串或 BaseMessage
作为输入。StrOutputParser
将任何输入简单地转换为字符串。
output_parser.invoke(message)
"Why did the ice cream go to therapy? \n\nBecause it had too many toppings and couldn't find its cone-fidence!"
4. Entire Pipeline
为了跟随这些步骤:
- 我们将用户输入作为
{"topic": "ice cream"}
传入。 prompt
组件接受用户输入,然后使用topic
构造提示后,用它来构造PromptValue
。model
组件接受生成的提示,并将其传递给 OpenAI LLM 模型进行评估。模型生成的输出是一个ChatMessage
对象。- 最后,
output_parser
组件接受ChatMessage
,并将其转换为 Python 字符串,该字符串从 invoke 方法返回。
请注意,如果您对任何组件的输出感到好奇,您可以始终测试链的较小版本,例如 prompt
或 prompt | model
,以查看中间结果:
input = {"topic": "ice cream"}
prompt.invoke(input)
# > ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])
(prompt | model).invoke(input)
# > AIMessage(content="Why did the ice cream go to therapy?\nBecause it had too many toppings and couldn't cone-trol itself!")
RAG 搜索示例
对于下一个示例,我们想要运行一个检索增强生成链,以在回答问题时添加一些上下文。
# Requires:# pip install langchain docarray tiktoken
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
vectorstore = DocArrayInMemorySearch.from_texts(
["harrison worked at kensho", "bears like to eat honey"], embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel( {"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
chain.invoke("where did harrison work?")
在这种情况下,组合链是:
chain = setup_and_retrieval | prompt | model | output_parser
为了解释这个,我们首先可以看到上面的提示模板接收 context
和 question
作为要在提示中替换的值。在构建提示模板之前,我们想要检索与搜索相关的文档,并将它们作为上下文的一部分。
作为初步步骤,我们已经使用内存存储设置了检索器,它可以基于查询检索文档。这也是一个可运行的组件,可以与其他组件链在一起,但你也可以尝试单独运行它:
retriever.invoke("where did harrison work?")
然后我们使用 RunnableParallel
来准备进入提示的预期输入,使用检索到的文档的条目以及原始用户问题,使用检索器进行文档搜索,和 RunnablePassthrough
来传递用户的问题:
setup_and_retrieval = RunnableParallel( {"context": retriever, "question": RunnablePassthrough()})
回顾一下,完整的链是:
setup_and_retrieval = RunnableParallel( {"context": retriever, "question": RunnablePassthrough()})chain = setup_and_retrieval | prompt | model | output_parser
流程如下:
-
第一步创建一个带有两个条目的
RunnableParallel
对象。第一个条目,context
将包含检索器获取的文档结果。第二个条目,question
将包含用户的原始问题。为了传递问题,我们使用RunnablePassthrough
来复制这个条目。 -
将上一步的字典输入到
prompt
组件。然后它接收用户输入,即question
,以及检索到的文档,即context
,来构造一个提示并输出一个PromptValue
。 -
model
组件接收生成的提示,并将其传入 OpenAI LLM 模型进行评估。模型生成的输出是一个ChatMessage
对象。 最后,output_parser
组件接收一个ChatMessage
,并将其转换为 Python 字符串,该字符串从 invoke 方法返回。