0

0

LangChain多文档处理与ChromaDB持久化:解决文本加载与分割挑战

霞舞

霞舞

发布时间:2025-11-27 11:09:43

|

959人浏览过

|

来源于php中文网

原创

LangChain多文档处理与ChromaDB持久化:解决文本加载与分割挑战

本教程旨在解决langchain中`textloader`和`charactertextsplitter`在处理多个文本文件及大型文本块时遇到的常见问题,如仅处理首个文档、分割失效及chunk大小异常。我们将详细介绍如何利用`recursivecharactertextsplitter`实现智能文本分割,并构建一个支持批量加载多类型文档的解决方案,最终将处理后的文本高效、可靠地持久化至chromadb向量数据库,确保llm能准确检索所需信息。

在构建基于大型语言模型(LLM)的检索增强生成(RAG)系统时,准确高效地加载、分割和存储文档是至关重要的一步。然而,开发者在使用LangChain的TextLoader和CharacterTextSplitter时,常会遇到一些挑战,例如系统仅处理目录中的第一个文档、文本块(chunk)大小远超预期、以及后续文档未能被正确分割和存储,导致LLM无法检索到这些信息。

遇到的问题:LangChain文本加载与分割的常见挑战

在使用LangChain处理本地文档时,如果代码逻辑未能正确迭代处理所有文件,TextLoader默认可能只加载指定路径的单个文件。例如,以下代码片段在处理多个文件时,通常只会加载./folder/file.txt这一个文件,而忽略同目录下的其他文件。

        loader = TextLoader("./folder/file.txt") # 明确指向单个文件
        documents = loader.load()
        text_splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0)
        # texts = text_splitter.split_documents(documents) # 假设这里有split操作
        chromaDirectory = "./folder/chroma_db"
        # Chroma.from_documents(texts, embeddings, persist_directory=chromaDirectory)

此外,CharacterTextSplitter在面对非常大的文本块时,可能会出现分割异常,例如即便设置了chunk_size=300,也可能生成远超此限制的文本块,甚至在处理后续文本时完全失效,不再进行分割。这通常是由于其基于简单字符分割的机制,对于结构复杂的文档或超长无分隔符的文本段落表现不佳。当这些未正确分割的文本被存储到向量数据库(如ChromaDB)中时,LLM在检索时自然无法找到相关信息,因为其上下文窗口和检索机制依赖于合理大小的文本块。

解决方案概述:多文档处理与智能文本分割

为了克服上述挑战,我们需要一套更健壮的文档加载和文本分割策略。核心解决方案包括:

  1. 批量加载多类型文档: 实现一个函数,能够遍历指定目录,识别并加载所有支持的文档类型(如.txt),而不仅仅是单个文件。
  2. 采用RecursiveCharacterTextSplitter: 替代CharacterTextSplitter,RecursiveCharacterTextSplitter能够根据一系列分隔符递归地分割文本,从而更好地处理结构复杂或长度不一的文本,确保文本块大小符合预期。
  3. 正确持久化ChromaDB: 确保ChromaDB的配置正确,特别是persist_directory和client_settings,以保证数据在程序运行结束后能够被保存。

逐步实现:构建健壮的文档处理流程

我们将通过以下步骤,构建一个能够高效处理多文档、智能分割文本并持久化到ChromaDB的完整流程。

1. 灵活的文档加载器

首先,定义一个映射表,用于支持不同文件类型的加载器。这使得我们的系统更具扩展性,可以轻松添加对.pdf、.docx等其他文件类型的支持。

喵记多
喵记多

喵记多 - 自带助理的 AI 笔记

下载
import os
import glob
from typing import List

from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma # 使用 langchain_community 替代旧的 Chroma 导入
from langchain_openai import OpenAIEmbeddings # 假设使用OpenAI的嵌入模型
from chromadb.config import Settings

# 定义支持的文档加载器映射
DOC_LOADERS_MAPPING = {
    ".txt": (TextLoader, {"encoding": "utf8"}),
    # 可以根据需要添加更多文档加载器,例如:
    # ".pdf": (PyPDFLoader, {}),
    # ".docx": (Docx2txtLoader, {}),
}

def load_document(path: str) -> Document:
    """
    加载单个文档。
    """
    try:
        ext = "." + path.rsplit(".", 1)[-1]
        if ext in DOC_LOADERS_MAPPING:
            loader_class, loader_args = DOC_LOADERS_MAPPING[ext]
            loader = loader_class(path, **loader_args)
            # load() 方法返回一个 Document 列表,我们通常只取第一个
            return loader.load()[0]

        raise ValueError(f"不支持的文件扩展名: {ext}")
    except Exception as exception:
        raise ValueError(f"加载文档时发生错误 '{path}': {exception}")

2. 批量加载目录文档

接着,实现一个函数来遍历指定目录及其子目录,查找所有支持的文件类型,并使用load_document函数批量加载它们。

def load_documents_from_dir(path: str) -> List[Document]:
    """
    从指定目录加载所有支持的文档。
    """
    try:
        all_files = []
        for ext in DOC_LOADERS_MAPPING:
            # 递归查找目录中所有匹配扩展名的文件
            all_files.extend(
                glob.glob(os.path.join(path, f"**/*{ext}"), recursive=True)
                )

        # 批量加载文件
        return [load_document(file_path) for file_path in all_files]
    except Exception as exception:
        raise RuntimeError(f"加载文件时发生错误: {exception}")

3. 智能文本分割

现在,我们使用RecursiveCharacterTextSplitter来对加载的文档进行智能分割。它会尝试不同的分隔符(如\n\n, \n, `,.等),直到文本块大小符合预期,这比CharacterTextSplitter`更灵活和鲁棒。

# 加载所有文档
documents = load_documents_from_dir("./folder/")

# 初始化RecursiveCharacterTextSplitter
# chunk_size: 每个文本块的最大长度
# chunk_overlap: 相邻文本块之间的重叠字符数,有助于保持上下文连贯性
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=50
)
# 分割文档
texts = text_splitter.split_documents(documents)

4. 持久化到ChromaDB

最后一步是将分割后的文本块及其对应的嵌入(embeddings)存储到ChromaDB中。确保ChromaDB的persist_directory设置正确,并且通过client_settings明确指定持久化选项,以保证数据在程序关闭后不会丢失。

# 初始化嵌入模型,例如OpenAIEmbeddings
# 请确保已设置OPENAI_API_KEY环境变量
embeddings = OpenAIEmbeddings() 

chroma_db_path = "./folder/chroma_db"

# 初始化ChromaDB并持久化
chroma_db = Chroma.from_documents(
    texts,
    embeddings,
    persist_directory=chroma_db_path,
    client_settings= Settings(
            persist_directory=chroma_db_path,
            chroma_db_impl="duckdb+parquet", # 指定ChromaDB的实现方式,确保持久化
            anonymized_telemetry=False, # 关闭匿名遥测
        ),    
)
# 显式调用persist()方法确保数据写入磁盘
chroma_db.persist()
# 清除内存中的ChromaDB实例(可选,但有助于释放资源)
chroma_db = None

完整代码示例

将上述所有组件整合,形成一个完整的文档处理和ChromaDB持久化流程。

import os
import glob
from typing import List

from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings # 假设使用OpenAI的嵌入模型
from chromadb.config import Settings

# --- 1. 定义支持的文档加载器映射 ---
DOC_LOADERS_MAPPING = {
    ".txt": (TextLoader, {"encoding": "utf8"}),
    # 可以根据需要添加更多文档加载器
    # ".pdf": (PyPDFLoader, {}),
    # ".docx": (Docx2txtLoader, {}),
}

# --- 2. 加载单个文档函数 ---
def load_document(path: str) -> Document:
    """
    加载单个文档。
    """
    try:
        ext = "." + path.rsplit(".", 1)[-1]
        if ext in DOC_LOADERS_MAPPING:
            loader_class, loader_args = DOC_LOADERS_MAPPING[ext]
            loader = loader_class(path, **loader_args)
            return loader.load()[0]

        raise ValueError(f"不支持的文件扩展名: {ext}")
    except Exception as exception:
        raise ValueError(f"加载文档时发生错误 '{path}': {exception}")

# --- 3. 批量加载目录文档函数 ---
def load_documents_from_dir(path: str) -> List[Document]:
    """
    从指定目录加载所有支持的文档。
    """
    try:
        all_files = []
        for ext in DOC_LOADERS_MAPPING:
            all_files.extend(
                glob.glob(os.path.join(path, f"**/*{ext}"), recursive=True)
                )

        return [load_document(file_path) for file_path in all_files]
    except Exception as exception:
        raise RuntimeError(f"加载文件时发生错误: {exception}")

# --- 主执行流程 ---
if __name__ == "__main__":
    # 确保存在一个名为 'folder' 的目录,并在其中放置一些 .txt 文件进行测试
    # 例如:
    # ./folder/doc1.txt
    # ./folder/doc2.txt
    # ...

    # 1. 设置文档目录和ChromaDB持久化目录
    source_directory = "./folder/"
    chroma_db_path = "./folder/chroma_db"

    # 确保ChromaDB目录存在
    os.makedirs(chroma_db_path, exist_ok=True)

    # 2. 批量加载文档
    print(f"正在从目录 '{source_directory}' 加载文档...")
    documents = load_documents_from_dir(source_directory)
    print(f"共加载了 {len(documents)} 个文档。")

    if not documents:
        print("未找到任何文档,请检查目录和文件。")
    else:
        # 3. 初始化文本分割器
        print("正在初始化文本分割器...")
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=300,
            chunk_overlap=50
        )
        # 4. 分割文档
        print("正在分割文档...")
        texts = text_splitter.split_documents(documents)
        print(f"文档被分割成 {len(texts)} 个文本块。")

        # 5. 初始化嵌入模型
        # 请确保已设置OPENAI_API_KEY环境变量
        # 或者使用其他本地嵌入模型,例如 SentenceTransformers
        print("正在初始化嵌入模型...")
        try:
            embeddings = OpenAIEmbeddings() 
        except Exception as e:
            print(f"初始化OpenAIEmbeddings失败,请检查OPENAI_API_KEY:{e}")
            print("尝试使用其他嵌入模型或退出。")
            exit() # 或者选择使用其他嵌入模型

        # 6. 持久化到ChromaDB
        print(f"正在将文本块及嵌入持久化到ChromaDB,路径:'{chroma_db_path}'...")
        chroma_db = Chroma.from_documents(
            texts,
            embeddings,
            persist_directory=chroma_db_path,
            client_settings= Settings(
                    persist_directory=chroma_db_path,
                    chroma_db_impl="duckdb+parquet",
                    anonymized_telemetry=False,
                ),    
        )
        chroma_db.persist()
        print("ChromaDB数据已成功持久化。")

        # 7. 验证(可选):加载并查询ChromaDB
        print("正在加载ChromaDB并进行简单查询验证...")
        loaded_db = Chroma(
            persist_directory=chroma_db_path, 
            embedding_function=embeddings,
            client_settings= Settings(
                    persist_directory=chroma_db_path,
                    chroma_db_impl="duckdb+parquet",
                    anonymized_telemetry=False,
                ),
        )
        # 尝试查询一个与文档内容相关的短语
        query = "关于文档内容的关键信息" # 根据你的文档内容修改查询
        results = loaded_db.similarity_search(query, k=2)
        print(f"查询 '{query}' 的结果:")
        for i, doc in enumerate(results):
            print(f"--- 结果 {i+1} ---")
            print(f"内容: {doc.page_content[:100]}...") # 打印前100字符
            print(f"元数据: {doc.metadata}")
            print("-" * 20)

        print("文档处理和ChromaDB持久化流程完成。")

关键注意事项与最佳实践

  • RecursiveCharacterTextSplitter的优势: 它是处理复杂文档的最佳选择,因为它会尝试多种分隔符策略,例如先按段落分割,再按句子,最后按单词,确保分割的语义完整性。
  • chunk_size与chunk_overlap:
    • chunk_size:应根据LLM的上下文窗口大小和你的应用需求来设置。过大可能导致LLM处理效率下降或无法完全理解上下文;过小可能导致信息碎片化。
    • chunk_overlap:适当的重叠可以确保在文本块边界处的信息不会丢失,有助于LLM在检索时获得更完整的上下文。
  • 多文件类型支持: DOC_LOADERS_MAPPING提供了一个灵活的框架来扩展对不同文档类型的支持。只需导入相应的LangChain加载器并添加到映射中即可。
  • ChromaDB持久化: 务必设置persist_directory并在Chroma.from_documents或Chroma初始化时通过client_settings指定chroma_db_impl="duckdb+parquet",并显式调用chroma_db.persist()。这确保了数据在应用程序关闭后仍然存在。
  • 错误处理: 在加载文档的函数中加入try-except块,可以提高程序的健壮性,及时捕获文件不存在、编码错误等问题。
  • 嵌入模型选择: 示例中使用OpenAIEmbeddings,但在实际生产环境中,你可能需要考虑成本、性能和数据隐私,选择其他本地或云端的嵌入模型(如HuggingFace SentenceTransformers)。

总结

通过本教程,我们解决了LangChain在处理多文档和文本分割时遇到的常见问题。通过采用RecursiveCharacterTextSplitter进行智能文本分割,并构建一个支持批量加载多类型文档的健壮流程,我们能够确保所有文档都被正确处理,并高效、可靠地持久化到ChromaDB。这一优化方案将显著提升基于LLM的RAG系统的检索准确性和整体性能,使得LLM能够从你提供的所有信息中有效地学习和回答问题。

相关专题

更多
数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

345

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2074

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

347

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

255

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

322

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

410

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

393

2023.10.16

vb连接数据库的方法
vb连接数据库的方法

vb连接数据库的方法有使用ADO对象库、使用OLEDB数据提供程序、使用ODBC数据源等。详细介绍:1、使用ADO对象库方法,ADO是一种用于访问数据库的COM组件,可以通过ADO连接数据库并执行SQL语句。可以使用ADODB.Connection对象来建立与数据库的连接,然后使用ADODB.Recordset对象来执行查询和操作数据;2、使用OLEDB数据提供程序方法等等。

219

2023.10.19

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.9万人学习

Rust 教程
Rust 教程

共28课时 | 4.4万人学习

Git 教程
Git 教程

共21课时 | 2.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号