Langchain新手大规模语言模型开发应用指南
来源:知乎
Langchain可能是目前AI领域最火的东西之一,仅次于向量数据库。
Langchain 是一个用于开发 GPT、LLama、Hugging Face 模型等大型语言模型应用程序的框架。
Langchain 最初是一个 Python 包,但现在也有 TypeScript 版本正在慢慢追赶。功能,而且还刚刚推出了Ruby版本。
你需要 Langchain 做什么?

但是为什么需要它呢?我们可以只发送一个 API 请求或模板然后就到此为止吗?你是对的,在简单的应用程序中这是可能的。
但是,当您开始增加复杂性时,例如将语言模型连接到您自己的数据(例如 Google Analytics、Stripe、SQL、PDF、CSV 等)或使语言模型适用于发送电子邮件等,在线搜索或在终端中运行代码,事情可能会变得混乱和重复。
LangChain 使用组件的方式解决了这个问题。
我们可以使用文档下载器从 PDF、Stripe 等来源下载数据,并可以选择使用文本分割器对其进行切片,然后将其保存到矢量数据库。
在运行过程中,可以将数据输入到提示模板中,然后作为输入发送到模板。我们还可能使用工具来执行操作,例如使用打印输出发送电子邮件。
事实上,这些抽象意味着您可以轻松切换到另一种语言模型以节省成本或享受其他功能、测试另一个矢量数据库的功能或使用另一个数据源。几行代码即可实现。
通过链条,我们实现了这一魔力,我们将组件连接在一起以执行特定任务。
代理更加抽象。考虑首先使用语言模式来思考自己需要做什么,然后使用工具和其他方法来实现它。
如果你有兴趣将语言模型与你自己的数据和外界连接起来,你可以查看与LangChain大约同时发表的研究,例如Self-Ask、With Search和ReAct。
新手应该知道哪些模块?
现在让我们看看幕后到底发生了什么。LangChain目前有新手应该知道的七个模块,包括模板、提示、索引、内存、链和代理。
核心模块概述
关于模板 有两种不同类型的高级模板:语言模板和文本嵌入模板。嵌入模型将文本转换为一组数字,之后我们可以将文本处理为向量空间。
上图中,我们可以看到,在二维空间中,“男人”是“国王”,“女人”是“女王”。它们代表不同的事物,但我们可以看到一个模式。 。这实现了语义搜索,我们可以在向量空间中找到满足给定参数的最相似的文本片段。
例如,OpenAI的文本嵌入模型可以准确嵌入大型文本片段,尤其是8100个字符,当字词比为0.75时,可以处理约6143个单词。它产生一个 1536 维向量。
我们可以使用LangChain连接到各种嵌入提供商,例如OpenAI和Cohere的API,但我们也可以使用Hugging Faces开源嵌入在本地使用它以实现自由和隐私。
现在,您只需使用四行代码即可在自己的计算机上创建自己的嵌入。然而,测量的数量可能会有所不同,并且嵌入的质量可能较低,这可能会导致搜索不太准确。
LLM 和聊天模型
接下来是语言模型,它有两种不同的子类型:LLM 和聊天模型。 LLM 封装了接受文本输入并返回文本的 API,而聊天模型则封装了接受聊天消息输入并返回对话消息的模型。尽管它们之间存在细微差别,但用户界面是相同的。我们可以导入这两个类,实现它们,然后对这两个类使用预测函数并观察它们之间的差异。但是,您不必将文本直接传递到模板,而是使用提示。
提示
提示指的是模型输入。我们经常希望使用比硬编码字符串更灵活的方法,LangChain 提供了 Prompt Template 类来构建采用多个值的提示。重要的提示概念包括提示模板、结果解析器、示例选择器和聊天提示模板。
PromptTemplate
提示模板是一个示例。您必须首先创建一个提示模板对象。有两种方法可以做到这一点,一种是导入提示模板,然后使用构造函数定义一个包含输入变量的数组,并将它们放在花括号中的模板字符串中。如果您愿意,还可以使用模板的辅助方法,这样就不必单独定义输入变量。
在任何一种情况下,您都可以通过告诉提示要替换占位符的值来格式化提示。
内部默认使用 F 字符串来格式化提示,但您也可以使用 Ginger 2。
但为什么不只是 F 弦呢?提示提高了可读性,与生态系统的其他部分很好地契合,并支持常见的用例,例如少样本学习或解析结果。
Few Shot Learning 意味着我们给出提示的示例来控制其输出。
让我们看看如何做到这一点,好吗?首先,创建一个包含一些示例的列表。
from langchain import PromptTemplate, FewShotPromptTemplate
examples = [
{"word": "happy", "antonym": "sad"},
{"word": "tall", "antonym": "short"},
]
然后我们定义用于格式化所提供的每个示例的模板。
example_formatter_template = """Word: {word}
Antonym: {antonym}
"""
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template=example_formatter_template,
)
"""
最后,我们创建一个Few Shot Prompt Template对象,它传递样本、模板格式化程序、前缀、命令和后缀,所有这些都旨在控制LLM的输出。 ?带有前缀 前缀
的示例与后缀 后缀
分开。现在我们可以创建一个如下所示的提示。
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="Give the antonym of every input\n",
suffix="Word: {input}\nAntonym: ",
input_variables=["input"],
example_separator="\n",
)
print(few_shot_prompt.format(input="big"))
这是一个非常有用的范例,可以指导LLM的输出并指导其响应。
输出解析器 (output_parsers)
类似地,我们可能希望使用自动将语言模型解析为输出对象的输出解析器。这需要更多的复杂性,但对于解析 LLM 的随机输出非常有用。
假设我们想使用 OpenAI 创建一个笑话对象,我们可以定义 Joke 类来指定笑话的布局和结尾。我们添加描述来帮助语言模型理解它们的含义,然后我们可以实例化一个解析器并告诉它使用我们的 Joke 类进行解析。
我们使用最强大和推荐的 Pydantic 输出解析器,然后创建我们的提示模板。
from langchain.prompts import PromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
class Joke(BaseModel):
setup: str = Field(description="question to set up a joke")
punchline: str = Field(description="answer to resolve the joke")
parser = PydanticOutputParser(pydantic_object=Joke)
传递模板字符串和输入变量,并在提示模板中输入子变量字段解析指令。然后我们可以请法学硕士给我们讲一个笑话。
现在我们准备将其提交给 OpenAI,首先从 .env 文件加载 OpenAI 的 API 密钥,然后实例化模型,调用其调用方法,并使用实例化的解析器解析模型的输出。
from langchain.llms import OpenAI
from dotenv import load_dotenv
load_dotenv()
model = OpenAI(model_name="text-davinci-003", temperature=0.0)
然后我们就有了一个带有设置和定义结束的笑话对象。生成的提示相当复杂,建议查看 GitHub 了解更多信息。
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
joke_query = "Tell me a joke."
formatted_prompt = prompt.format_prompt(query=joke_query)
print(formatted_prompt.to_string())
打印的结果是:
Answer the user query.
The output should be formatted as a JSON instance
that conforms to the JSON schema below.
As an example, for the schema
{
"properties": {
"foo": {
"title": "Foo",
"description": "a list of strings",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"foo"
]
}
the object {"foo": ["bar", "baz"]} is a well-formatted
instance of the schema.
The object {"properties": {"foo": ["bar", "baz"]}} is
not well-formatted.
Here is the output schema:
{
"properties": {
"setup": {
"title": "Setup",
"description": "question to set up a joke",
"type": "string" },
"punchline": {
"title": "Punchline",
"description": "answer to resolve the joke",
"type": "string" } },
"required": [
"setup",
"punchline" ] }
Tell me a joke.
"""
我们将提示模板喂给模板,使用结果解析器来解析结果:
output = model(formatted_prompt.to_string())
parsed_joke = parser.parse(output)
print(parsed_joke)
我们前面讲过Few Shot Prompt学习,我们通过一些例子来说明模板对于特定类型的查询预期响应有效。我们可以举出很多这样的例子,但我们无法将它们全部匹配。这很快就会变得非常昂贵。
这就是样本选择器发挥作用的地方。
示例选择器 (example_selector)
为了保持提示成本相对恒定,我们使用基于长度的示例选择器 默认情况下,长度是指提示将在格式化程序的示例部分中使用的单词数和换行符 使用对话模型怎么样?这将我们带到前面提到的聊天提示模板。聊天模板将聊天消息列表作为输入。该列表称为提示。它们的不同之处在于,每条消息都有一个预先附加的角色,可以是人工智能、人类或系统。模型必须严格遵循系统消息中的说明。 一开始只有一条系统消息,有时听起来很催眠。“你是友好的客户服务,逐渐回答客户的问题”……类似这样的话,告诉聊天机器人如何表现。 AI 消息是来自模型的消息,人类消息是我们输入的消息。人物角色为法学硕士提供了更好的持续讨论背景。 模板和提示整齐且标准化。 但是我们如何使用自己的数据呢?这就是索引模块派上用场的地方。 数据是新的石油,您当然可以在任何地方开采并找到大量数据。 Langchain 通过提供文档加载器(文档是文本的一种奇特方式)来提供“数据挖掘器”。有许多受支持的格式和服务,例如 CSV、电子邮件、SQL、Discord、AWS S3、PDF 等。您只需要导入三行代码。就是这么简单! 首先导入下载器,然后指定文件路径,然后调用download方法。这会将 PDF 文件作为文本加载到内存中,作为一个表,其中每个索引代表一个页面。 这很棒,但是当我们想要构建提示并包含这些页面中的文本时,它们可能太大而无法适应我们之前讨论的输入标签的大小,即为什么我们要使用文本分割器将它们分割。 读取文本后,我们可以创建一个递归字符文本分割器 然后我们得到一堆文件。 现在我们有了文本块,我们想要嵌入并存储它们,以便我们最终可以通过语义搜索来检索它们,这就是我们拥有向量存储的原因。 索引模块的这一部分提供了与矢量数据库的多种集成,例如pinecone、redis、SuperBass、chromaDB等。 文档准备好后,您应该选择嵌入提供程序并使用向量数据库帮助程序方法保存文档。下面的示例代码是 OpenAI 的 现在我们可以编写一个问题,在向量空间中搜索最相似的结果 从构建提示到索引文档再到搜索向量空间的一切都可以通过导入模块并执行几行代码来完成。 希望您喜欢这个旅程,让我们开始我们的聊天机器人之旅吧!LengthBasedExampleSelector❀。和以前一样,我们定义了一个示例提示。这决定了每个示例的格式。我们缩短选择器,传递样本,然后传递最大长度。
max_length
。 from langchain.prompts import PromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector
examples = [
{"word": "happy", "antonym": "sad"},
{"word": "tall", "antonym": "short"},
{"word": "energetic", "antonym": "lethargic"},
{"word": "sunny", "antonym": "gloomy"},
{"word": "windy", "antonym": "calm"},
]
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template="Word: {word}\nAntonym: {antonym}",
)
example_selector = LengthBasedExampleSelector(
examples=examples,
example_prompt=example_prompt,
max_length=25,
)
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="Give the antonym of every input",
suffix="Word: {adjective}\nAntonym:",
input_variables=["adjective"],
)
print(dynamic_prompt.format(adjective="big"))
指数
文档加载器
Text_splitter (text_splitter)
RecursiveCharacterTextSplitter
并确定块大小和段落重叠。我们调用 create_documents
方法并将文本作为参数传递。 from langchain.text_splitter import RecursiveCharacterTextSplitter
with open("example_data/state_of_the_union.txt") as f:
state_of_the_union = f.read()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=20,
)
texts = text_splitter.create_documents([state_of_the_union])
print(f"\nFirst chunk: {texts[0]}\n")
print(f"Second chunk: {texts[1]}\n")
与矢量数据库集成
搜索向量空间
OpenAIEMbeddings
。similarity_search
并返回其文本。 from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
with open("example_data/state_of_the_union.txt") as f:
state_of_the_union = f.read()
text_splitter = CharacterTextSplitter(
chunk_size=1000,
chunk_overlap=0,
)
texts = text_splitter.create_documents([state_of_the_union])
embeddings = OpenAIEmbeddings()
docsearch = Chroma.from_texts(texts, embeddings)
query = "What did the president say about Ketanji Brown Jackson"
docs = docsearch.similarity_search(query)
print(docs[0].page_content)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。