小白举手06-青训营ai笔记-rag | 豆包MarsCode AI 刷题

183 阅读13分钟

一、概念

(一)RAG

其全称为Retrieval-Augmented Generation,即检索增强生成,它结合了检索和生成的能力,为文本序列生成任务引入外部知识。RAG将传统的语言生成模型与大规模的外部知识库相结合,使模型在生成响应或文本时可以动态地从这些知识库中检索相关信息。这种结合方法旨在增强模型的生成能力,使其能够产生更为丰富、准确和有根据的内容,特别是在需要具体细节或外部事实支持的场合。

步骤:

  1. 检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如ChromaDB、Faiss这样的向量数据库。
  2. 上下文编码:找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
  3. 生成:使用编码的上下文信息,模型生成输出(答案)。这通常当然是通过大模型完成的。

image.png

(二)常用的文档加载器的列表:

image.png

(三)文本分割器

LangChain中,文本分割器的工作原理如下:

  1. 将文本分成小的、具有语义意义的块(通常是句子)。
  2. 开始将这些小块组合成一个更大的块,直到达到一定的大小。
  3. 一旦达到该大小,一个块就形成了,可以开始创建新文本块。这个新文本块和刚刚生成的块要有一些重叠,以保持块之间的上下文。

因此,LangChain提供的各种文本拆分器可以帮助你从下面几个角度设定你的分割策略和参数:

  1. 文本如何分割
  2. 块的大小
  3. 块之间重叠文本的长度

image.png

文本分割在实践,有哪些具体的考量因素,我总结了下面几点。

首先,就是LLM 的具体限制。GPT-3.5-turbo支持的上下文窗口为4096个令牌,这意味着输入令牌和生成的输出令牌的总和不能超过4096,否则会出错。为了保证不超过这个限制,我们可以预留约2000个令牌作为输入提示,留下约2000个令牌作为返回的消息。这样,如果你提取出了五个相关信息块,那么每个片的大小不应超过400个令牌。

此外,文本分割策略的选择和任务类型相关。

  • 需要细致查看文本的任务,最好使用较小的分块。例如,拼写检查、语法检查和文本分析可能需要识别文本中的单个单词或字符。垃圾邮件识别、查找剽窃和情感分析类任务,以及搜索引擎优化、主题建模中常用的关键字提取任务也属于这类细致任务。
  • 需要全面了解文本的任务,则使用较大的分块。例如,机器翻译、文本摘要和问答任务需要理解文本的整体含义。而自然语言推理、问答和机器翻译需要识别文本中不同部分之间的关系。还有创意写作,都属于这种粗放型的任务。

最后,你也要考虑所分割的文本的性质。例如,如果文本结构很强,如代码或HTML,你可能想使用较大的块,如果文本结构较弱,如小说或新闻文章,你可能想使用较小的块。

你可以反复试验不同大小的块和块与块之间重叠窗口的大小,找到最适合你特定问题的解决方案。

(三)其它形式的文本转换

  1. 过滤冗余的文档:使用 EmbeddingsRedundantFilter 工具可以识别相似的文档并过滤掉冗余信息。这意味着如果你有多份高度相似或几乎相同的文档,这个功能可以帮助识别并删除这些多余的副本,从而节省存储空间并提高检索效率。
  2. 翻译文档:通过与工具 doctran 进行集成,可以将文档从一种语言翻译成另一种语言。
  3. 提取元数据:通过与工具 doctran 进行集成,可以从文档内容中提取关键信息(如日期、作者、关键字等),并将其存储为元数据。元数据是描述文档属性或内容的数据,这有助于更有效地管理、分类和检索文档。
  4. 转换对话格式:通过与工具 doctran 进行集成,可以将对话式的文档内容转化为问答(Q/A)格式,从而更容易地提取和查询特定的信息或回答。这在处理如访谈、对话或其他交互式内容时非常有用。

(四)文本嵌入

文本块形成之后,我们就通过LLM来做嵌入(Embeddings),将文本转换为数值表示,使得计算机可以更容易地处理和比较文本。OpenAI、Cohere、Hugging Face 中都有能做文本嵌入的模型。

from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()

Embeddings 会创建一段文本的向量表示,让我们可以在向量空间中思考文本,并执行语义搜索之类的操作,在向量空间中查找最相似的文本片段。 它提供两种方法:

  1. 第一种是 embed_documents 方法,为文档创建嵌入。这个方法接收多个文本作为输入,意味着你可以一次性将多个文档转换为它们的向量表示。
embeddings = embeddings_model.embed_documents(
    [        "您好,有什么需要帮忙的吗?",        "哦,你好!昨天我订的花几天送达",        "请您提供一些订单号?",        "12345678",    ]
)
len(embeddings), len(embeddings[0])

  1. 第二种是 embed_query 方法,为查询创建嵌入。这个方法只接收一个文本作为输入,通常是用户的搜索查询。
embedded_query = embeddings_model.embed_query("刚才对话中的订单号是多少?")
embedded_query[:3]

(五)存储嵌入

计算嵌入可能是一个时间消耗大的过程。为了加速这一过程,我们可以将计算出的嵌入存储或临时缓存,这样在下次需要它们时,就可以直接读取,无需重新计算。

1、缓存存储

CacheBackedEmbeddings是一个支持缓存的嵌入式包装器,它可以将嵌入缓存在键值存储中。具体操作是:对文本进行哈希处理,并将此哈希值用作缓存的键。

要初始化一个CacheBackedEmbeddings,主要的方式是使用from_bytes_store。其需要以下参数:

  • underlying_embedder:实际计算嵌入的嵌入器。
  • document_embedding_cache:用于存储文档嵌入的缓存。
  • namespace(可选):用于文档缓存的命名空间,避免与其他缓存发生冲突。

不同的缓存策略如下:

  1. InMemoryStore:在内存中缓存嵌入。主要用于单元测试或原型设计。如果需要长期存储嵌入,请勿使用此缓存。
  2. LocalFileStore:在本地文件系统中存储嵌入。适用于那些不想依赖外部数据库或存储解决方案的情况。
  3. RedisStore:在Redis数据库中缓存嵌入。当需要一个高速且可扩展的缓存解决方案时,这是一个很好的选择。

在内存中缓存嵌入的示例代码如下:

(六)向量库嵌入

根据具体需求进行选型

  1. 数据规模和速度需求:考虑你的数据量大小以及查询速度的要求。一些向量数据库在处理大规模数据时更加出色,而另一些在低延迟查询中表现更好。
  2. 持久性和可靠性:根据你的应用场景,确定你是否需要数据的高可用性、备份和故障转移功能。
  3. 易用性和社区支持:考虑向量数据库的学习曲线、文档的完整性以及社区的活跃度。
  4. 成本:考虑总体拥有成本,包括许可、硬件、运营和维护成本。
  5. 特性:考虑你是否需要特定的功能,例如多模态搜索等。
  6. 安全性:确保向量数据库符合你的安全和合规要求。

在进行向量数据库的评测时,进行性能基准测试是了解向量数据库实际表现的关键。这可以帮助你评估查询速度、写入速度、并发性能等。

没有“最好”的向量数据库,只有“最适合”的向量数据库。在你的需求上做些研究和测试,确保你选择的向量数据库满足你的业务和技术要求就好。

(七)数据检索

image.png

(八)索引

索引是一种高效地管理和定位文档信息的方法,确保每个文档具有唯一标识并便于检索。

它的优势包括:

  • 避免重复内容:确保你的向量存储中不会有冗余数据。
  • 只更新更改的内容:能检测哪些内容已更新,避免不必要的重写。
  • 省时省钱:不对未更改的内容重新计算嵌入,从而减少了计算资源的消耗。
  • 优化搜索结果:减少重复和不相关的数据,从而提高搜索的准确性。

LangChain 利用了记录管理器(RecordManager)来跟踪哪些文档已经被写入向量存储。

在进行索引时,API 会对每个文档进行哈希处理,确保每个文档都有一个唯一的标识。这个哈希值不仅仅基于文档的内容,还考虑了文档的元数据。

一旦哈希完成,以下信息会被保存在记录管理器中:

  • 文档哈希:基于文档内容和元数据计算出的唯一标识。
  • 写入时间:记录文档何时被添加到向量存储中。
  • 源 ID:这是一个元数据字段,表示文档的原始来源。

这种方法确保了即使文档经历了多次转换或处理,也能够精确地跟踪它的状态和来源,确保文档数据被正确管理和索引。

二、代码

(一)documents嵌入

# 设置OpenAI的API密钥
import os

from volcenginesdkarkruntime import Ark
from typing import List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel

# 初始化Embedding类
class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        """
        生成输入文本的 embedding.
        Args:
            texts (str): 要生成 embedding 的文本.
        Return:
            embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
        """
        embeddings = self.client.embeddings.create(model=self.model, input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True


embeddings_model = DoubaoEmbeddings(
    model=os.environ["EMBEDDING_MODELEND"],
)

# Embed文本
embeddings = embeddings_model.embed_documents(
    [
        "您好,有什么需要帮忙的吗?",
        "哦,你好!昨天我订的花几天送达",
        "请您提供一些订单号?",
        "12345678",
    ]
)
print(len(embeddings), len(embeddings[0]))

# Embed查询
embedded_query = embeddings_model.embed_query("刚才对话中的订单号是多少?")
print(embedded_query[:3])

(二)缓存嵌入


# 设置OpenAI的API密钥
import os

from volcenginesdkarkruntime import Ark
from typing import List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel

# 初始化Embedding类
class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        """
        生成输入文本的 embedding.
        Args:
            texts (str): 要生成 embedding 的文本.
        Return:
            embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
        """
        embeddings = self.client.embeddings.create(model=self.model, input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True


# 导入内存存储库,该库允许我们在RAM中临时存储数据
from langchain.storage import InMemoryStore

# 创建一个InMemoryStore的实例
store = InMemoryStore()

# 导入与嵌入相关的库。OpenAIEmbeddings是用于生成嵌入的工具,而CacheBackedEmbeddings允许我们缓存这些嵌入
from langchain.embeddings import CacheBackedEmbeddings

# 创建一个OpenAIEmbeddings的实例,这将用于实际计算文档的嵌入
underlying_embeddings = DoubaoEmbeddings(
    model=os.environ["EMBEDDING_MODELEND"],
)
# 创建一个CacheBackedEmbeddings的实例。
# 这将为underlying_embeddings提供缓存功能,嵌入会被存储在上面创建的InMemoryStore中。
# 我们还为缓存指定了一个命名空间,以确保不同的嵌入模型之间不会出现冲突。
embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings,  # 实际生成嵌入的工具
    store,  # 嵌入的缓存位置
    namespace=underlying_embeddings.model,  # 嵌入缓存的命名空间
)

# 使用embedder为两段文本生成嵌入。
# 结果,即嵌入向量,将被存储在上面定义的内存存储中。
embeddings = embedder.embed_documents(["你好", "智能鲜花客服"])

(三)数据检索

# 设置OpenAI的API密钥
import os
from volcenginesdkarkruntime import Ark
from typing import List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel

# 初始化Embedding类
class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        """
        生成输入文本的 embedding.
        Args:
            texts (str): 要生成 embedding 的文本.
        Return:
            embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
        """
        embeddings = self.client.embeddings.create(model=self.model, input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True


embeddings = DoubaoEmbeddings(
    model=os.environ["EMBEDDING_MODELEND"],
)

# 导入文档加载器模块,并使用TextLoader来加载文本文件
from langchain_community.document_loaders import TextLoader
from langchain_openai import ChatOpenAI  # ChatOpenAI模型

loader = TextLoader("./OneFlower/花语大全.txt", encoding="utf8")

# 使用VectorstoreIndexCreator来从加载器创建索引
from langchain.indexes import VectorstoreIndexCreator

index = VectorstoreIndexCreator(embedding=embeddings).from_loaders([loader])

llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)

# 定义查询字符串, 使用创建的索引执行查询
query = "玫瑰花的花语是什么?"
result = index.query(llm=llm, question=query)
print(result)  # 打印查询结果

# 替换成你所需要的工具
from langchain.text_splitter import CharacterTextSplitter

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
from langchain_community.vectorstores import Qdrant

index_creator = VectorstoreIndexCreator(
    vectorstore_cls=Qdrant,
    embedding=embeddings,
    text_splitter=CharacterTextSplitter(chunk_size=1000, chunk_overlap=0),
)

注释:

1、index = VectorstoreIndexCreator().from_loaders([loader]) 的作用是创建一个向量存储索引,具体来说,它使用了 VectorstoreIndexCreator 类来从提供的加载器(loader)中创建索引。下面从 Python 语法和结构的角度进行详细解释:

1. 类的实例化

  • VectorstoreIndexCreator() 是一个类的实例化过程。这里调用了 VectorstoreIndexCreator 类的构造函数,创建了一个该类的对象。
  • VectorstoreIndexCreator 是来自 langchain.indexes.vectorstore 模块的一个类,主要用于处理向量存储索引的创建。

2. 方法调用

  • .from_loaders([loader]) 是对刚刚创建的 VectorstoreIndexCreator 实例调用的方法。这个方法用于从传入的加载器列表中生成向量索引。
  • from_loaders 方法接受一个列表参数,这里传入的是 [loader],表示将一个名为 loader 的对象(通常是某种文档加载器)作为输入。

3. 列表语法

  • [loader] 是 Python 中创建列表的语法。即使只包含一个元素,仍然需要用方括号将其包裹起来,这样就形成了一个单元素列表。

4. 赋值操作

  • index = ... 表示将方法调用的结果赋值给变量 index。该变量将持有由 from_loaders 方法返回的向量存储索引对象。