(17-7)检索增强生成(RAG):索引(Indexing)

假期快乐,朋友们要多聚会:

5.7  索引(Indexing)

在LangChain中,索引(Indexing)是一种将文档同步到向量存储(Vector Store)的机制,能够将从任何来源的数据同步到向量存储中并保持同步。

5.7.1  索引(Indexing)介绍

在LangChain中,langchain.indexes模块提供了一系列的工具和类,用于管理和优化数据的索引过程,特别是在向量存储(vectorstore)中。langchain.indexes模块的主要目的是避免在向量存储中写入重复内容,以及在内容未发生变化时避免重写内容。此外,langchain.indexes还支持从LangChain数据加载器到向量存储的索引工作流程,并能够在内容通过一系列转换从源内容派生时继续工作,例如,通过分块处理父文档来索引子文档。

1. 记录管理器RecordManager

RecordManager是一个抽象基类,定义了记录管理器的接口。RecordManager用于跟踪和协调文档的索引状态,确保内容的唯一性和最新性。RecordManager通常使用哈希值和时间戳来识别和处理重复或过时的内容。

(1)成员方法

  1. __init__(namespace):用于初始化记录管理器,接收一个 namespace 参数,该参数定义了记录管理器的命名空间。
  2. acreate_schema():异步方法,用于创建记录管理器的数据库架构。
  3. adelete_keys(keys):异步方法,用于从数据库中删除指定的记录。
  4. aexists(keys):异步方法,用于检查提供的键是否存在于数据库中。
  5. aget_time():异步方法,用于获取当前服务器时间作为高精度时间戳。
  6. alist_keys(*, before=None, after=None, group_ids=None, limit=None):异步方法,用于基于提供的过滤器列出数据库中的记录。
  7. aupdate(keys, *, group_ids=None, time_at_least=None):异步方法,用于将记录更新(或插入,如果不存在)到数据库。

(2)参数说明

  1. namespace (str):命名空间,用于区分不同的记录管理器实例。
  2. keys (Sequence[str]):键的序列,用于指定要操作的记录。
  3. group_ids (Optional[Sequence[str]]):组ID的序列,用于指定记录所属的组。
  4. limit (Optional[int]):可选的记录返回数量限制。
  5. time_at_least (Optional[float]):如果提供,只有在 updated_at 字段至少是这个时间时,才执行更新操作。

(3)注意事项

  1. 方法名称以 a 开头的表示它们是异步方法,需要在异步环境中调用。
  2. 方法get_time()强调使用服务器时间来确保单增时钟,这对于清理旧文档时避免数据丢失很重要。
  3. 方法update()和方法aupdate()在执行更新操作时,如果提供了参数time_at_least,只有在记录的 updated_at 字段值至少等于这个时间戳时,才会更新记录。

类RecordManager是索引模块langchain.indexes模块的核心,它通过维护一个记录数据库来确保向量存储中的文档是最新的,并且没有重复。这在处理大规模数据集和保持索引一致性方面非常有用。实际的记录管理器实现需要继承这个基类并实现所有的抽象方法。

2. GraphIndexCreator

类GraphIndexCreator提供了创建图索引的功能。图索引是一种特殊的索引结构,它将数据表示为节点和边的集合,这对于表示和查询复杂的关系和网络结构非常有用。

(1)属性和参数

  1. graph_type:图类型,默认为 NetworkxEntityGraph 类型,这是一个用于表示实体图的类。
  2. llm:一个可选的 BaseLanguageModel 实例,用于提供语言模型功能。

(2)方法

  1. afrom_text:异步方法,用于从文本中创建图索引。
  2. construct:类方法,用于从信任或预先验证的数据创建新模型。
  3. copy:实例方法,用于复制模型,可以选择包含、排除字段或更改值。
  4. dict:实例方法,用于生成模型的字典表示。
  5. from_orm:类方法,用于从 ORM(对象关系映射)对象创建模型。
  6. from_text:同步方法,功能与 afrom_text 相同,但不是异步的。
  7. json:实例方法,用于生成模型的 JSON 表示。
  8. parse_file:类方法,用于从文件中解析模型。
  9. parse_obj:类方法,用于从对象中解析模型。
  10. parse_raw:类方法,用于从原始数据中解析模型。
  11. schema:类方法,用于获取模型的 JSON Schema。
  12. schema_json:类方法,用于获取模型的 JSON Schema 的 JSON 字符串表示。
  13. update_forward_refs:类方法,用于更新模型字段的前向引用。
  14. validate:类方法,用于验证传入的值并创建模型。

3. VectorStoreIndexWrapper

VectorStoreIndexWrapper是一个包装类,提供了便捷访问向量存储的功能。主要包括如下所示的成员方法。

(1)aquery:异步方法,用于向向量存储查询问题。

  1. 参数 question 是要查询的问题字符串。
  2. 参数 llm 是一个可选的 BaseLanguageModel 实例,用于提供语言模型功能。
  3. 参数 retriever_kwargs 是一个可选的字典,包含检索器的关键字参数。
  4. 参数 kwargs 是其他任意关键字参数。
  5. 返回值:是查询结果的字符串。

(2)aquery_with_sources:异步方法,与 aquery 类似,但返回包含来源信息的字典。

(3)construct:类方法,用于从信任或预先验证的数据创建新模型。

(4)copy:实例方法,用于复制模型,可以选择包含、排除字段或更改值。

(5)dict:实例方法,用于生成模型的字典表示。

(6)from_orm:类方法,用于从 ORM(对象关系映射)对象创建模型。

(7)json:实例方法,用于生成模型的 JSON 表示。

(8)parse_file:类方法,用于从文件中解析模型。

(9)parse_obj:类方法,用于从对象中解析模型。

(10)parse_raw:类方法,用于从原始数据中解析模型。

(11)query:同步方法,功能与 aquery 相同,但不是异步的。

(12)query_with_sources:同步方法,功能与 aquery_with_sources 相同,但不是异步的。

(13)schema:类方法,用于获取模型的 JSON Schema。

(14)schema_json:类方法,用于获取模型的 JSON Schema 的 JSON 字符串表示。

(15)update_forward_refs:类方法,用于更新模型字段的前向引用。

(16)validate:类方法,用于验证传入的值并创建模型。

4. VectorStoreIndexCreator

类VectorStoreIndexCreator用于创建向量存储索引,它基于 BaseModel,是一个模型类,提供了从文档或加载器创建向量存储索引的逻辑。

(1)属性和参数

  1. embedding:一个可选的 Embeddings 实例,用于将文本转换为向量表示。
  2. text_splitter:一个可选的 TextSplitter 实例,用于将文本分割成更小的块或段落。
  3. vectorstore_cls:向量存储的类,默认为 InMemoryVectorStore,一个在内存中存储向量的类。
  4. vectorstore_kwargs:一个可选的字典,包含创建向量存储实例时的关键字参数。

(2)方法

  1. afrom_documents:异步方法,用于从文档列表创建向量存储索引。参数 documents 是一个 Document 对象列表,返回值是 VectorStoreIndexWrapper 的一个实例,它包装了创建的向量存储。
  2. afrom_loaders:异步方法,用于从加载器列表创建向量存储索引。参数 loaders 是 BaseLoader 对象的列表,返回值同样是 VectorStoreIndexWrapper 的一个实例。
  3. construct:类方法,用于从信任或预先验证的数据创建新模型。
  4. copy:实例方法,用于复制模型,可以选择包含、排除字段或更改值。
  5. dict:实例方法,用于生成模型的字典表示。
  6. from_documents:同步方法,功能与 afrom_documents 相同,但不是异步的。
  7. from_loaders:同步方法,功能与 afrom_loaders 相同,但不是异步的。
  8. from_orm:类方法,用于从 ORM(对象关系映射)对象创建模型。
  9. json:实例方法,用于生成模型的 JSON 表示。
  10. parse_file:类方法,用于从文件中解析模型。
  11. parse_obj:类方法,用于从对象中解析模型。
  12. parse_raw:类方法,用于从原始数据中解析模型。
  13. schema:类方法,用于获取模型的 JSON Schema。
  14. schema_json:类方法,用于获取模型的 JSON Schema 的 JSON 字符串表示。
  15. update_forward_refs:类方法,用于更新模型字段的前向引用。
  16. validate:类方法,用于验证传入的值并创建模型。

请看下面的例子,演示了使用langchain.indexes实现索引文档的过程,包括如何处理文档的去重、更新和删除。另外,本实例还展示了如何使用自定义加载器来索引文档,以及如何执行向量存储的相似性搜索的用法。在实际使用中,开发者需要根据自己的环境配置相应的参数,例如 Elasticsearch 的 URL 和索引名称。

实例5-15使用langchain.indexes实现索引文档(源码路径:codes\5\jian15.py

实例文件jian15.py的具体实现代码如下所示。

# 初始化向量存储和嵌入模型
collection_name = "test_index"
embedding = OpenAIEmbeddings()
vectorstore = ElasticsearchStore(
    es_url="http://localhost:9200", 
    index_name="test_index", 
    embedding=embedding
)

# 初始化记录管理器
namespace = f"elasticsearch/{collection_name}"
record_manager = SQLRecordManager(namespace, db_url="sqlite:///record_manager_cache.sql")

# 创建记录管理器的模式
record_manager.create_schema()

# 定义清除内容的辅助方法
def _clear():
    index([], record_manager, vectorstore, cleanup="full", source_id_key="source")

# 创建测试文档
doc1 = Document(page_content="kitty", metadata={"source": "kitty.txt"})
doc2 = Document(page_content="doggy", metadata={"source": "doggy.txt"})

# 索引空向量存储中的内容(无删除模式)
_clear()
index(
    [doc1, doc1, doc1, doc1, doc1],
    record_manager,
    vectorstore,
    cleanup=None,
    source_id_key="source",
)

# 索引向量存储中的内容(无删除模式)
_clear()
index([doc1, doc2], record_manager, vectorstore, cleanup=None, source_id_key="source")

# 再次索引相同的内容将被跳过
index([doc1, doc2], record_manager, vectorstore, cleanup=None, source_id_key="source")

# "增量"删除模式
_clear()
index(
    [doc1, doc2],
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)

# 再次索引相同的内容将被跳过,包括嵌入操作
index(
    [doc1, doc2],
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)

# 如果没有提供文档,增量索引模式下什么也不会改变
index([], record_manager, vectorstore, cleanup="incremental", source_id_key="source")

# 如果我们更改文档,新版本将被写入,并且所有旧版本将被删除
changed_doc_2 = Document(page_content="puppy", metadata={"source": "doggy.txt"})
index(
    [changed_doc_2],
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)

# "完整"删除模式
_clear()
all_docs = [doc1, doc2]
index(all_docs, record_manager, vectorstore, cleanup="full", source_id_key="source")

# 假设有人删除了第一个文档
del all_docs[0]
all_docs

# 使用完整模式将清理已删除的内容
index(all_docs, record_manager, vectorstore, cleanup="full", source_id_key="source")

# 使用 CharacterTextSplitter 分割文档
doc1 = Document(
    page_content="kitty kitty kitty kitty kitty", metadata={"source": "kitty.txt"}
)
doc2 = Document(page_content="doggy doggy the doggy", metadata={"source": "doggy.txt"})

new_docs = CharacterTextSplitter(
    separator="t", keep_separator=True, chunk_size=12, chunk_overlap=2
).split_documents([doc1, doc2])

# 索引分割后的文档
_clear()
index(
    new_docs,
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)

# 更改文档内容并重新索引
changed_doggy_docs = [
    Document(page_content="woof woof", metadata={"source": "doggy.txt"}),
    Document(page_content="woof woof woof", metadata={"source": "doggy.txt"}),
]

index(
    changed_doggy_docs,
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)

# 执行相似性搜索
results = vectorstore.similarity_search("dog", k=30)
for doc in results:
    print(doc)

# 使用自定义加载器
class MyCustomLoader(BaseLoader):
    def lazy_load(self):
        text_splitter = CharacterTextSplitter(
            separator="t", keep_separator=True, chunk_size=12, chunk_overlap=2
        )
        docs = [
            Document(page_content="woof woof", metadata={"source": "doggy.txt"}),
            Document(page_content="woof woof woof", metadata={"source": "doggy.txt"}),
        ]
        yield from text_splitter.split_documents(docs)

    def load(self):
        return list(self.lazy_load())

# 清除内容并使用自定义加载器索引
_clear()
loader = MyCustomLoader()
docs = loader.load()

index(
    docs,
    record_manager,
    vectorstore,
    cleanup="full",
    source_id_key="source",
)

# 执行相似性搜索
results = vectorstore.similarity_search("dog", k=30)
for doc in results:
    print(doc)

上述代码用于将文档索引到向量存储中,并支持后续的内容检索。具体实现流程如下所示:

(1)初始化向量存储和嵌入模型:创建一个 ElasticsearchStore 实例,它是一个向量存储,用于存储文档的嵌入向量。然后,使用 OpenAIEmbeddings 作为文档内容的嵌入模型。

(2)初始化记录管理器:使用 SQLRecordManager 来跟踪文档的索引状态。定义一个命名空间,通常与向量存储和集合名称相关。

(3)创建记录管理器的模式:调用 create_schema 方法来初始化记录管理器所需的数据库结构。

(4)创建测试文档:创建两个 Document 实例,包含页面内容和元数据。

(5)索引文档到向量存储

  1. 定义一个辅助函数 _clear 来清除向量存储中的内容。
  2. 使用不同的删除模式(cleanup 参数)来索引文档:
  3. None:不自动清理旧版本,但会进行内容去重。
  4. incremental:增量模式,仅索引自上次索引以来发生变化的文档。
  5. full:完全模式,重新索引所有文档,并删除未包含在索引中的文档。

(6)处理文档变更:如果文档内容发生变化,则使用 incremental 模式来更新文档。

(7)使用分割器处理文档:使用 CharacterTextSplitter 对长文档进行分割,以便将它们作为单独的文档索引。

(8)索引分割后的文档:使用 incremental 模式索引分割后的文档。

(9)使用自定义加载器:创建一个自定义的 MyCustomLoader 类,它使用 CharacterTextSplitter 来分割文档,并生成可由索引器处理的文档流。

(10)执行相似性搜索:使用向量存储的 similarity_search 方法来执行基于内容的搜索。

上述实现流程的核心是 index 函数,它负责将文档添加到向量存储中,并根据指定的删除模式处理文档的更新和删除。通过这种方式,可以确保向量存储中的内容始终是最新的,并且没有重复。此外,通过使用 SQLRecordManager,可以跟踪每个文档的索引状态,包括它们的哈希值和最后更新时间,这有助于高效地管理和维护索引。执行后会输出:

# 索引空向量存储中的内容(无删除模式)
{'num_added': 1, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

# 索引向量存储中的内容(无删除模式)
{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

# 再次索引相同的内容将被跳过
{'num_added': 0, 'num_updated': 0, 'num_skipped': 2, 'num_deleted': 0}

# 增量删除模式下索引文档
{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

# 再次索引相同的文档将被跳过
{'num_added': 0, 'num_updated': 0, 'num_skipped': 2, 'num_deleted': 0}

# 没有提供文档,增量索引模式下什么也不会改变
{'num_added': 0, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

# 增量模式下,文档变更后新版本被写入,旧版本被删除
{'num_added': 1, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 1}

# 完整删除模式下,除了提供的文档外,其他所有文档都被删除
{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

# 删除特定文档后,使用完整模式清理内容
{'num_added': 0, 'num_updated': 0, 'num_skipped': 1, 'num_deleted': 1}

另外,当执行 vectorstore.similarity_search("dog", k=30) 时,会返回与查询 "dog" 语义相似的文档列表,参数k用于指定返回的结果数量。执行后将显示一系列 Document 对象,其中包含了与搜索查询 "dog" 相关的页面内容和元数据。例如下面的输出结果:

[Document(page_content='kitty kit', metadata={'source': 'kitty.txt'}),
 Document(page_content='tty kitty ki', metadata={'source': 'kitty.txt'}),
 Document(page_content='tty kitty', metadata={'source': 'kitty.txt'}),
 Document(page_content='doggy doggy', metadata={'source': 'doggy.txt'}),
 Document(page_content='the doggy', metadata={'source': 'doggy.txt'})]

5.7.2  基于Apify爬虫的向量索引查询系统

Apify是一个用于网络爬取和数据提取的云平台,提供了一个包含一千多个现成应用(称为 Actors)的生态系统,这些应用用于各种网络爬取、爬行和数据提取用例。例如,可以使用它来提取 Google 搜索结果、Instagram 和 Facebook 个人资料、亚马逊或 Shopify 上的产品、Google Maps 评论等。在下面的例子中,将使用 Website Content Crawler Actor(可以深入爬取网站信息,如文档、知识库、帮助中心或博客)从网页中爬取文本内容,然后将文档输入到向量索引中,并从中回答问题。

实例5-16基于Apify爬虫的向量索引查询系统(源码路径:codes\5\jian16.py

实例文件jian16.py的具体实现代码如下所示。

import os
from langchain.indexes.vectorstore import VectorstoreIndexCreator
from langchain_community.utilities import ApifyWrapper
from langchain_core.documents import Document

# 设置环境变量,存储您的 OpenAI 和 Apify API 密钥
os.environ["OPENAI_API_KEY"] = "Your_OpenAI_API_Key"
os.environ["APIFY_API_TOKEN"] = "Your_Apify_API_Token"

# 初始化 ApifyWrapper
apify = ApifyWrapper()

# 调用 Apify 的 Website Content Crawler Actor 并获取结果
loader = apify.call_actor(
    actor_id="apify/website-content-crawler",
    run_input={"startUrls": [{"url": "https://python.langchain.com/en/latest/"}]},
    # 将 Apify 数据集记录转换为 LangChain Document 对象
    dataset_mapping_function=lambda item: Document(
        page_content=item["text"] or "", metadata={"source": item["url"]}
    ),
)

# 使用爬取的文档初始化向量索引
index = VectorstoreIndexCreator().from_loaders([loader])

# 使用向量索引查询信息
query = "What is LangChain?"
result = index.query_with_sources(query)

# 打印查询结果和来源
print(result["answer"])
print(result["sources"])

上述代码的实现流程如下所示:

(1)爬取网站内容:使用 Apify 的 website-content-crawler Actor 从指定的 URL 爬取网站内容。

(2)创建文档加载器:通过 call_actor 方法获取爬取结果,并使用 dataset_mapping_function 将 Apify 数据集记录转换为 LangChain 能够理解的 Document 对象。

(3)初始化向量索引:使用 VectorstoreIndexCreator 类的 from_loaders 方法,从文档加载器中创建一个向量索引。这个索引是基于文档内容和元数据构建的,可以用于后续的查询操作。

(4)查询向量索引:通过 query_with_sources 方法向向量索引发出查询,获取问题的答案和来源信息。

在运行本实例之前,请确保已经将Your_OpenAI_API_Key和Your_Apify_API_Token替换为自己的API密钥。执行后会输出:

LangChain is a standard interface through which you can interact with a variety of large language models (LLMs). It provides modules that can be used to build language model applications, and it also provides chains and agents with memory capabilities.

https://python.langchain.com/en/latest/modules/models/llms.html, https://python.langchain.com/en/latest/getting_started/getting_started.html

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值