《LangChain简明教程》系列文章目录
模块 IV:链
LangChain 是一个专为在复杂应用中利用大型语言模型(LLMs)而设计的工具。它提供了用于创建组件链的框架,其中包括 LLMs 和其他类型的组件。LangChain 主要包含两个核心框架:
- LangChain Expression Language (LCEL)
- Legacy Chain interface
LangChain Expression Language (LCEL) 是一种语法,能够直观地实现链式组合。它支持许多高级功能,例如流式传输、异步调用、批处理、并行化、重试、回退和追踪。例如,你可以使用 LCEL 将提示词(prompt)、模型(model)和输出解析器(output parser)组合在一起,如下方代码所示:
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "You're a very knowledgeable historian who provides accurate and eloquent answers to historical questions."),
("human", "{question}")
])
runnable = prompt | model | StrOutputParser()
for chunk in runnable.stream({"question": "What are the seven wonders of the world"}):
print(chunk, end="", flush=True)
或者,LLMChain 是一个类似于 LCEL 的选项,用于组合组件。以下是 LLMChain 的示例:
from langchain.chains import LLMChain
chain = LLMChain(llm=model, prompt=prompt, output_parser=StrOutputParser())
chain.run(question="What are the seven wonders of the world")
在 LangChain 中,链(Chains)还可以通过引入 Memory 对象来实现状态化。这使得数据可以在多次调用之间持久化,如下例所示:
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
conversation = ConversationChain(llm=chat, memory=ConversationBufferMemory())
conversation.run("Answer briefly. What are the first 3 colors of a rainbow?")
conversation.run("And the next 4?")
LangChain 还支持与 OpenAI 的函数调用 API 集成,这在获取结构化输出以及在链中执行函数时非常有用。要获取结构化输出,你可以使用 Pydantic 类或 JsonSchema 来指定它们,如下所示:
from langchain.pydantic_v1 import BaseModel, Field
from langchain.chains.openai_functions import create_structured_output_runnable
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
class Person(BaseModel):
name: str = Field(..., description="The person's name")
age: int = Field(..., description="The person's age")
fav_food: Optional[str] = Field(None, description="The person's favorite food")
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = ChatPromptTemplate.from_messages([
# Prompt messages here
])
runnable = create_structured_output_runnable(Person, llm, prompt)
runnable.invoke({"input": "Sally is 13"})
对于结构化输出,还可以使用基于 LLMChain 的传统方法来实现:
from langchain.chains.openai_functions import create_structured_output_chain
class Person(BaseModel):
name: str = Field(..., description="The person's name")
age: int = Field(..., description="The person's age")
chain = create_structured_output_chain(Person, llm, prompt, verbose=True)
chain.run("Sally is 13")
LangChain 利用 OpenAI 函数创建了多种用于不同目的的特定链。这些链包括用于提取、标注、OpenAPI 以及带引用的问答(QA)等场景。
在提取的场景中,其过程与结构化输出链类似,但重点在于信息或实体的提取。对于标注,其目标是为文档打上诸如情感、语言、风格、涵盖主题或政治倾向等类别的标签。
下面通过一段 Python 代码来演示 LangChain 中标注的工作原理。该过程首先需要安装必要的软件包并设置环境:
pip install langchain openai
# Set env var OPENAI_API_KEY or load from a .env file:
# import dotenv
# dotenv.load_dotenv()
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import create_tagging_chain, create_tagging_chain_pydantic
标注的 schema 被定义出来,指定了属性及其预期类型:
schema = {
"properties": {
"sentiment": {"type": "string"},
"aggressiveness": {"type": "integer"},
"language": {"type": "string"},
}
}
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613")
chain = create_tagging_chain(schema, llm)
通过不同输入运行标注链的示例展示了模型在解读情感、语言和攻击性方面的能力:
inp = "Estoy increiblemente contento de haberte conocido! Creo que seremos muy buenos amigos!"
chain.run(inp)
# {'sentiment': 'positive', 'language': 'Spanish'}
inp = "Estoy muy enojado con vos! Te voy a dar tu merecido!"
chain.run(inp)
# {'sentiment': 'enojado', 'aggressiveness': 1, 'language': 'es'}
为了实现更精细的控制,可以更具体地定义 schema,包括可能的值、描述和必填属性。下面展示了这种增强控制的一个示例:
schema = {
"properties": {
# Schema definitions here
},
"required": ["language", "sentiment", "aggressiveness"],
}
chain = create_tagging_chain(schema, llm)
Pydantic schemas 也可以用于定义标注标准,提供了一种 Pythonic 的方式来指定所需的属性和类型:
在 LangChain 中为实现标注功能定义 schema 时,你可以使用 Pydantic 类或 JsonSchema 来明确地定义你希望从文本中提取的标签属性及它们的数据类型。例如,如果你想要标注文档的情感、语言和主题,你可以定义一个包含这些属性的 schema,并指定每个属性的类型,如下所示的例子:
from enum import Enum
from pydantic import BaseModel, Field
class Tags(BaseModel):
# Class fields here
sentiment: str
language: str
topics: list
chain = create_tagging_chain_pydantic(Tags, llm)
这段代码定义了一个名为 Tags
的 Pydantic 类,其中包含了三个属性:sentiment
(情感)、language
(语言)和 topics
(主题),并分别指定了它们的类型为字符串(str)和列表(list)。这样的 schema 可以帮助你在处理文本标注任务时确保数据的一致性和正确性。
此外,LangChain 的元数据标注器文档转换器可以用于从 LangChain 文档中提取元数据,功能类似于标注链,但专门应用于 LangChain 文档。
引用检索来源 是 LangChain 的另一项功能,利用 OpenAI 函数从文本中提取引用。以下代码展示了这一功能:
from langchain.chains import create_citation_fuzzy_match_chain
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613")
chain = create_citation_fuzzy_match_chain(llm)
# Further code for running the chain and displaying results
在 LangChain 中,大型语言模型(LLM)应用中的链式操作通常涉及将提示模板(prompt template)与 LLM 结合,并可选地加入输出解析器(output parser)。推荐的方式是使用 LangChain 表达式语言(LCEL),尽管传统的 LLMChain 方法也仍然支持。
通过 LCEL,BasePromptTemplate
、BaseLanguageModel
和 BaseOutputParser
都实现了 Runnable
接口,可以轻松地通过管道(pipe)将它们连接在一起。以下是一个示例:
from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.schema import StrOutputParser
prompt = PromptTemplate.from_template(
"What is a good name for a company that makes {product}?"
)
runnable = prompt | ChatOpenAI() | StrOutputParser()
runnable.invoke({"product": "colorful socks"})
# Output: 'VibrantSocks'
在 LangChain 中,路由(Routing) 可以创建非确定性链,其中前一个步骤的输出决定了下一个步骤。这有助于在与大型语言模型(LLM)的交互中构建结构并保持一致性。例如,如果你有两个针对不同类型问题优化的模板,可以根据用户输入选择合适的模板。
以下是如何使用 LCEL 和 RunnableBranch
实现这一功能的示例。RunnableBranch
通过一组(条件,可运行对象)对以及一个默认的可运行对象进行初始化:
from langchain.chat_models import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnableBranch
# Code for defining physics_prompt and math_prompt
general_prompt = PromptTemplate.from_template(
"You are a helpful assistant. Answer the question as accurately as you can.\n\n{input}"
)
prompt_branch = RunnableBranch(
(lambda x: x["topic"] == "math", math_prompt),
(lambda x: x["topic"] == "physics", physics_prompt),
general_prompt,
)
# More code for setting up the classifier and final chain
最终的链通过多种组件构建,例如主题分类器(topic classifier)、提示分支(prompt branch)和输出解析器(output parser),以根据输入的主题来确定流程:
from operator import itemgetter
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
final_chain = (
RunnablePassthrough.assign(topic=itemgetter("input") | classifier_chain)
| prompt_branch
| ChatOpenAI()
| StrOutputParser()
)
final_chain.invoke(
{
"input": "What is the first prime number greater than 40 such that one plus the prime number is divisible by 3?"
}
)
# Output: Detailed answer to the math question
在语言模型领域,一种常见的做法是通过一系列后续调用来跟进初始调用,其中将一个调用的输出作为下一个调用的输入。这种顺序方法在你希望基于之前交互生成的信息进行构建时特别有用。尽管 LangChain 表达式语言(LCEL)是创建这些序列的推荐方法,但为了向后兼容,SequentialChain
方法仍然被记录在案。
为了说明这一点,我们考虑一个场景:首先生成一部戏剧的概要,然后基于该概要生成一篇评论。使用 Python 的 langchain.prompts
,我们创建两个 PromptTemplate
实例:一个用于生成概要,另一个用于生成评论。以下是设置这些模板的代码:
from langchain.prompts import PromptTemplate
synopsis_prompt = PromptTemplate.from_template(
"You are a playwright. Given the title of play, it is your job to write a synopsis for that title.\n\nTitle: {title}\nPlaywright: This is a synopsis for the above play:"
)
review_prompt = PromptTemplate.from_template(
"You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.\n\nPlay Synopsis:\n{synopsis}\nReview from a New York Times play critic of the above play:"
)
在 LCEL 方法中,我们将这些提示与 ChatOpenAI
和 StrOutputParser
连接起来,创建一个先生成概要再生成评论的序列。代码片段如下:
from langchain.chat_models import ChatOpenAI
from langchain.schema import StrOutputParser
llm = ChatOpenAI()
chain = (
{"synopsis": synopsis_prompt | llm | StrOutputParser()}
| review_prompt
| llm
| StrOutputParser()
)
chain.invoke({"title": "Tragedy at sunset on the beach"})
对于涉及更复杂序列的场景,SequentialChain
方法便派上了用场。它允许多个输入和输出。例如,考虑一个需要根据戏剧的标题和时代生成概要的情况。以下是我们可能设置它的方式:
from langchain.llms import OpenAI
from langchain.chains import LLMChain, SequentialChain
from langchain.prompts import PromptTemplate
llm = OpenAI(temperature=0.7)
synopsis_template = "You are a playwright. Given the title of play and the era it is set in, it is your job to write a synopsis for that title.\n\nTitle: {title}\nEra: {era}\nPlaywright: This is a synopsis for the above play:"
synopsis_prompt_template = PromptTemplate(input_variables=["title", "era"], template=synopsis_template)
synopsis_chain = LLMChain(llm=llm, prompt=synopsis_prompt_template, output_key="synopsis")
review_template = "You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.\n\nPlay Synopsis:\n{synopsis}\nReview from a New York Times play critic of the above play:"
prompt_template = PromptTemplate(input_variables=["synopsis"], template=review_template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")
overall_chain = SequentialChain(
chains=[synopsis_chain, review_chain],
input_variables=["era", "title"],
output_variables=["synopsis", "review"],
verbose=True,
)
overall_chain({"title": "Tragedy at sunset on the beach", "era": "Victorian England"})
在需要在整个链或链的后半部分保持上下文的场景中,可以使用 SimpleMemory
。这在管理复杂的输入/输出关系时特别有用。例如,在一个需要根据戏剧的标题、时代、概要和评论生成社交媒体帖子的场景中,SimpleMemory
可以帮助管理这些变量:
from langchain.memory import SimpleMemory
from langchain.chains import SequentialChain
template = "You are a social media manager for a theater company. Given the title of play, the era it is set in, the date, time and location, the synopsis of the play, and the review of the play,it is your job to write a social media post for that play.\n\nHere is some context about the time and location of the play:\nDate and Time: {time}\nLocation: {location}\n\nPlay Synopsis:\n{synopsis}\nReview from a New York Times play critic of the above play:\n{review}\n\nSocial Media Post:"
prompt_template = PromptTemplate(input_variables=["synopsis", "review", "time", "location"], template=template)
social_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="social_post_text")
overall_chain = SequentialChain(
memory=SimpleMemory(memories={"time": "December 25th, 8pm PST", "location": "Theater in the Park"}),
chains=[synopsis_chain, review_chain, social_chain],
input_variables=["era", "title"],
output_variables=["social_post_text"],
verbose=True,
)
overall_chain({"title": "Tragedy at sunset on the beach", "era": "Victorian England"})
除了顺序链之外,还有一些专门用于处理文档的链。这些链各有不同的用途,从合并文档、基于迭代文档分析优化答案,到对文档内容进行映射和归纳以生成摘要或根据评分重新排序。这些链可以通过 LCEL 重新实现,从而提供更大的灵活性和定制能力。
- StuffDocumentsChain:将文档列表合并为一个单一提示,传递给大型语言模型(LLM)。
- RefineDocumentsChain:针对每个文档逐步更新答案,适用于文档超出模型上下文容量的任务。
- MapReduceDocumentsChain:对每个文档单独应用链,然后合并结果。
- MapRerankDocumentsChain:对基于文档的响应进行评分,并选择得分最高的结果。
以下是如何使用 LCEL 设置 MapReduceDocumentsChain 的示例:
from functools import partial
from langchain.chains.combine_documents import collapse_docs, split_list_of_docs
from langchain.schema import Document, StrOutputParser
from langchain.schema.prompt_template import format_document
from langchain.schema.runnable import RunnableParallel, RunnablePassthrough
llm = ChatAnthropic()
document_prompt = PromptTemplate.from_template("{page_content}")
partial_format_document = partial(format_document, prompt=document_prompt)
map_chain = (
{"context": partial_format_document}
| PromptTemplate.from_template("Summarize this content:\n\n{context}")
| llm
| StrOutputParser()
)
map_as_doc_chain = (
RunnableParallel({"doc": RunnablePassthrough(), "content": map_chain})
| (lambda x: Document(page_content=x["content"], metadata=x["doc"].metadata))
).with_config(run_name="Summarize (return doc)")
def format_docs(docs):
return "\n\n".join(partial_format_document(doc) for doc in docs)
collapse_chain = (
{"context": format_docs}
| PromptTemplate.from_template("Collapse this content:\n\n{context}")
| llm
| StrOutputParser()
)
reduce_chain = (
{"context": format_docs}
| PromptTemplate.from_template("Combine these summaries:\n\n{context}")
| llm
| StrOutputParser()
).with_config(run_name="Reduce")
map_reduce = (map_as_doc_chain.map() | collapse | reduce_chain).with_config(run_name="Map reduce")
这种配置充分利用了 LCEL 和底层语言模型的优势,从而能够对文档内容进行详细而全面的分析。