在上一节中,我们使用llama cpp初始化了LLM。现在,让我们利用LangChain框架来开发使用LLM的应用程序。通过文本是你与LLM进行交互的主要接口。简单来说,很多模型都是通过输入文本,输出文本的方式工作的。因此,LangChain中的很多接口都围绕文本展开。
在不断发展的编程领域中,出现了一种迷人的范式:引言。引言的目的是为语言模型提供特定的输入,以引发期望的响应。这种创新的方法使我们能够根据我们提供的输入来塑造模型的输出。
令人惊讶的是,在构建引言的方式上措辞的微妙差别可以对模型的响应的性质和内容产生显著影响。结果可能根据措辞的不同而基本上不同,突显了在构思引言时认真考虑的重要性。
为了提供与LLM的无缝交互,LangChain提供了几个类和函数,使用引言模板来简化构建和处理引言。引言模板是一种可重复生成引言的方式。它包含一个文本字符串模板,可以接受来自最终用户的一组参数并生成引言。让我们来看一些例子。
作者提供的图片:没有输入变量的引言
作者提供的图片:一个输入变量的引言
作者提供的图片:多个输入变量的引言
希望前面的解释能更清楚地理解引言的概念。现在,让我们继续引导LLM。
作者提供的图片:通过Langchain LLM进行引导
这个工作得非常好,但这并不是LangChain的最佳利用方式。到目前为止,我们已经使用了单独的组件。我们取得了引言模板并进行格式化,然后取得了LLM,然后将这些参数传递给LLM来生成答案。在简单的应用程序中,只是使用单独的LLM是可以的,但更复杂的应用程序需要链式连接LLM —— 可能是彼此之间链式连接,也可能是与其他组件链式连接。
LangChain为这样的链式连接🔗应用程序提供了Chain接口。我们将一个Chain定义为对组件的一系列调用,其中可以包括其他链。链允许我们将多个组件组合在一起,创建一个单一的,连贯的应用程序。例如,我们可以创建一个链,接收用户输入,使用引言模板进行格式化,然后将格式化后的响应传递给LLM。我们可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链。
为了理解链,让我们创建一个非常简单的链式连接🔗,该链式连接将接收用户输入,使用它对引言进行格式化,然后使用我们已经创建的上面的单独组件将格式化的响应发送给LLM。
作者提供的图片:在LangChain中实现链式连接
当涉及多个变量时,你可以选择使用字典来集体输入它们。这结束了本节。现在,让我们深入探讨将外部文本作为问题回答检索器的一部分的主要内容。
在许多LLM应用中,需要用户特定的数据,这些数据不包含在模型的训练集中。LangChain为您提供了加载、转换、存储和查询数据的基本组件。
LangChain中的数据连接:源
具体步骤如下:
- 文档加载器: 用于加载文档数据。
- 文档转换器: 将文档分割为较小的块。
- 嵌入: 将块转换为向量表示,也称为嵌入。
- 向量存储: 用于将上述块向量存储在向量数据库中。
- 检索器: 用于检索与查询最相似的向量集合/向量,这些向量在相同的潜在空间中被嵌入。
文档检索/问答循环
现在,我们将逐步介绍上述五个步骤,以便根据查询检索与查询最相似的文档块,并生成基于检索向量块的答案,如提供的图像所示。
然而,在继续之前,我们需要准备一段文本来执行上述任务。为了进行这个虚构的测试,我从维基百科上复制了一段关于一些流行的DC超级英雄的文本。下面是文本内容:
作者提供的原始文本:用于测试的文本
首先,让我们创建一个文档对象。在本例中,我们将使用文本加载器。但是,LangChain支持多个文档,因此您可以根据特定的文档选择使用不同的加载器。接下来,我们将使用**load**
方法从预配置的源中检索数据并将其加载为文档。
一旦文档加载完成,我们可以将其转换为较小的块。为了实现这一点,我们将使用TextSplitter。默认情况下,拆分器将文档在“\n\n”分隔符处分成块。但是,如果将分隔符设置为null并定义一个特定的块大小,每个块将具有指定的长度。因此,结果列表长度将等于文档长度除以块大小。总结起来,它将类似于这样:**list length = length of doc / chunk size**
。让我们实际操作一下。
作者提供的图像:加载和转换文档
这是最重要的一步。嵌入将文本内容生成为向量表示。这是非常重要的,因为它使我们能够在向量空间中构建文本概念。
词嵌入只是一个词的向量表示,向量包含实数。由于语言通常包含至少成千上万个单词,简单的二进制词向量在维度高的情况下可能变得不实用。词嵌入通过在低维向量空间中提供单词的稠密表示来解决此问题。
当我们谈论检索时,我们是指检索一组与查询最相似的向量,这些向量以一种被嵌入在相同潜在空间中的向量形式存在。
LangChain中的基本嵌入类公开了两种方法:一种用于嵌入文档,另一种用于嵌入查询。前者以多个文本作为输入,而后者以单个文本作为输入。
作者提供的图像:嵌入
为了全面理解嵌入,请深入研究其基础知识,因为它是神经网络处理文本数据的核心。我在使用TensorFlow的其中一篇博客中广泛涵盖了这个主题。这是链接👇
向量存储有效地管理嵌入数据的存储,并在您的代表操作上为您执行向量搜索操作。嵌入和存储生成的嵌入向量是存储和搜索非结构化数据的常用方法。在查询时,对非结构化查询也进行嵌入,并检索与嵌入查询最相似的嵌入向量。这种方法能够有效地从向量存储中检索相关信息。
在这里,我们将使用Chroma,一个专门用于简化包含嵌入的AI应用开发的嵌入数据库和向量存储。它提供了一整套内置工具和功能,以便于您的初始设置,所有这些工具都可以通过执行简单的**pip install chromadb**
命令方便地安装在本地计算机上。
作者提供的图像:创建向量存储
到目前为止,我们见证了在广泛的文档集合中使用嵌入和向量存储来检索相关块的出色能力。现在,是时候将检索到的块与查询一起呈现给LLM,作为上下文。然后,我们将祈求LLM根据我们提供的信息生成答案。其中重要的部分是提示结构。
但是,强调良好结构化提示的重要性是至关重要的。通过制定一个精心设计的提示,我们可以减少LLM在面对不确定性时创造事实的可能性。
不再拖延,让我们继续进入最后的阶段,看看我们的LLM是否能够产生引人入胜的答案。我们即将见证我们努力的成果和结果。Here we Goooooo ⚡
作者提供的图像:与文档的问答
这就是我们一直在等待的时刻!我们成功了! 👏👏我们刚刚建立了我们自己的问答机器人 🤖,并在本地运行LLM ⚡⚡
本节是完全可选的,因为它并不是Streamlit的全面指南。我不会深入探讨这部分,而是提供一个基本应用程序,允许用户上传任何文本文档。然后,他们可以通过文本输入提出问题。在幕后,功能与我们在前一节中介绍的功能保持一致。
然而,在涉及到Streamlit中的文件上传时有一个注意事项。为了防止潜在的内存错误,特别是考虑到LLM的内存密集性质,我将简单地将文档读取并写入我们文件结构中的临时文件夹中,将其命名为**raw.txt**
。这样,无论文档的原始名称如何,Textloader都可以无缝处理该文档。
目前,该应用程序设计用于文本文件,但您可以适应于PDF、CSV或其他格式。基本概念仍然相同,因为LLM主要用于文本输入和输出。此外,您还可以尝试使用由Llama C++绑定支持的不同LLM。
没有进一步深入细节,我展示了应用程序的代码。请随意根据您的具体用例进行自定义。
这是streamlit应用程序的样子。🔥
这一次,我输入了从维基百科上复制的《黑暗骑士》的剧情,并问“谁的脸严重烧伤了?”LLM回答说——“Harvey Dent”。
好的,好的,好的!随着这一篇博客的结束,我们也告一段落。
希望你们喜欢这篇文章,并且觉得它有用和有趣。你可以关注我Afaque Umer,以获取更多的类似文章。
我将尽力带来更多的机器学习/数据科学概念,并尝试将那些听起来很高级的术语和概念化简。
好了,好了,好了!再见 👋