
在使用langchain进行文档处理时,开发者常遇到`textloader`和`charactertextsplitter`在处理多个文档或大文件时表现异常,如只处理首个文档、分块大小不准确等问题。本教程将详细介绍如何通过采用`recursivecharactertextsplitter`和一套健壮的目录文档加载策略,有效解决这些挑战,确保所有文档被正确分块并持久化到chromadb。
在构建基于大型语言模型(LLM)的应用时,高效地加载、分割和存储文档是核心环节。许多开发者在使用LangChain的TextLoader结合CharacterTextSplitter处理多个文本文件时,可能会遇到一系列问题,例如:
这些问题通常源于TextLoader默认一次只加载一个文件,以及CharacterTextSplitter在处理复杂文本结构和多文件场景时的局限性。为了克服这些挑战,我们推荐采用更灵活的RecursiveCharacterTextSplitter,并结合一个能够批量加载目录中所有文档的策略。
为了能够处理目录中的多个文档,我们需要一个能够遍历指定路径并根据文件类型加载文档的通用函数。这不仅提升了代码的复用性,也为未来扩展支持更多文档类型(如PDF、Word等)奠定了基础。
首先,定义一个映射,将文件扩展名与对应的LangChain文档加载器关联起来。
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 chromadb.config import Settings
from langchain_community.vectorstores import Chroma # 导入Chroma
# 定义支持的文档加载器映射
DOC_LOADERS_MAPPING = {
".txt": (TextLoader, {"encoding": "utf8"}),
# 可以根据需要添加更多文档类型,例如:
# ".pdf": (PyPDFLoader, {}),
# ".docx": (Docx2txtLoader, {}),
}
def load_document(path: str) -> Document:
"""
根据文件路径加载单个文档。
支持DOC_LOADERS_MAPPING中定义的文档类型。
"""
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() 方法通常返回一个文档列表,这里我们假设每个文件只生成一个主文档
return loader.load()[0]
raise ValueError(f"不支持的文件扩展名: {ext}")
except Exception as exception:
raise ValueError(f"加载文档时出错 '{path}': {exception}")
def load_documents_from_dir(path: str) -> List[Document]:
"""
从指定目录加载所有支持的文档。
"""
try:
all_files = []
# 遍历所有支持的文件扩展名,查找目录中的对应文件
for ext in DOC_LOADERS_MAPPING:
# 使用glob查找所有匹配的文件,包括子目录
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}")RecursiveCharacterTextSplitter是一个更强大的文本分割器,它会尝试使用一系列分隔符(如\n\n, \n, `,.等)递归地分割文本,直到分块满足chunk_size`要求。这使得它在处理结构复杂的文档时表现更佳,并能更好地保持语义连贯性。
# 假设我们已经通过 load_documents_from_dir 加载了文档
# documents = load_documents_from_dir("./folder/")
# 初始化RecursiveCharacterTextSplitter
# chunk_size: 每个分块的最大字符数
# chunk_overlap: 相邻分块之间的重叠字符数,有助于保留上下文
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
# 对所有加载的文档进行分块
# split_documents 方法可以直接处理 Document 对象列表
texts = text_splitter.split_documents(documents)
print(f"原始文档数量: {len(documents)}")
print(f"分割后的文本块数量: {len(texts)}")
# 打印一些分块信息以验证
# for i, chunk in enumerate(texts[:5]): # 打印前5个分块
# print(f"Chunk {i+1} (长度: {len(chunk.page_content)}): {chunk.page_content[:100]}...")chunk_size和chunk_overlap是两个关键参数:
将处理后的文本块(texts)和对应的嵌入(embeddings)存储到ChromaDB,以便后续进行语义搜索。重要的是,要正确配置ChromaDB的持久化设置,确保数据在程序关闭后不会丢失。
# 假设 embeddings 已经是一个有效的嵌入模型实例
# 例如:from langchain_openai import OpenAIEmbeddings
# embeddings = OpenAIEmbeddings()
chromaDirectory = "./folder/chroma_db"
# 初始化ChromaDB并持久化文档
# client_settings 用于配置ChromaDB客户端,确保持久化
chroma_db = Chroma.from_documents(
texts,
embeddings,
persist_directory=chromaDirectory,
client_settings= Settings(
persist_directory=chromaDirectory, # 再次指定持久化目录
chroma_db_impl="duckdb+parquet", # 指定存储实现
anonymized_telemetry=False, # 关闭匿名遥测
),
)
# 显式调用 persist() 方法确保数据写入磁盘
chroma_db.persist()
print(f"文档已成功持久化到ChromaDB: {chromaDirectory}")
# 在不需要时,可以将 chroma_db 设为 None 释放资源
chroma_db = None
# 之后可以通过以下方式重新加载数据库
# chroma_db_reloaded = Chroma(
# persist_directory=chromaDirectory,
# embedding_function=embeddings,
# client_settings= Settings(
# persist_directory=chromaDirectory,
# chroma_db_impl="duckdb+parquet",
# anonymized_telemetry=False,
# ),
# )
# print("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 chromadb.config import Settings
from langchain_community.vectorstores import Chroma
# 假设您已安装并配置了嵌入模型,例如OpenAIEmbeddings
from langchain_openai import OpenAIEmbeddings # 示例导入
# --- 1. 定义文档加载器映射 ---
DOC_LOADERS_MAPPING = {
".txt": (TextLoader, {"encoding": "utf8"}),
# 可在此处扩展更多文档类型,例如:
# ".pdf": (PyPDFLoader, {}), 确保已安装 pypdf
# ".docx": (Docx2txtLoader, {}), 确保已安装 python-docx
}
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}")
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__":
# 确保有一个用于测试的文件夹和一些txt文件
# 例如,创建一个名为 'test_folder' 的目录,并在其中放置 file1.txt, file2.txt 等
# os.makedirs("./test_folder", exist_ok=True)
# with open("./test_folder/file1.txt", "w", encoding="utf8") as f:
# f.write("这是第一个文件的内容,包含一些重要的信息。")
# with open("./test_folder/file2.txt", "w", encoding="utf8") as f:
# f.write("这是第二个文件的内容,关于其他主题的详细描述。")
# with open("./test_folder/long_file.txt", "w", encoding="utf8") as f:
# f.write("这是一个非常长的文件内容,需要被分割成多个小块。"*100) # 制造一个长文件
document_folder = "./test_folder" # 替换为您的文档目录
chroma_db_path = "./test_folder/chroma_db" # ChromaDB的持久化目录
# 1. 加载所有文档
print(f"正在从目录 '{document_folder}' 加载文档...")
documents = load_documents_from_dir(document_folder)
print(f"已加载 {len(documents)} 个原始文档。")
if not documents:
print("未找到任何文档,请检查目录和文件类型配置。")
else:
# 2. 初始化文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
# 3. 分割文档
print("正在分割文档...")
texts = text_splitter.split_documents(documents)
print(f"文档分割完成,共生成 {len(texts)} 个文本块。")
# 4. 初始化嵌入模型
# 请确保您已配置环境变量 OPENAI_API_KEY
# 或者使用其他嵌入模型,例如 HuggingFaceEmbeddings
try:
embeddings = OpenAIEmbeddings()
print("OpenAIEmbeddings 初始化成功。")
except Exception as e:
print(f"初始化嵌入模型失败: {e}")
print("请检查您的API密钥和网络连接。教程将在此处停止。")
exit()
# 5. 将分块持久化到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。")
# 示例:从ChromaDB中检索
# query = "关于第一个文件的信息是什么?"
# print(f"\n正在查询: '{query}'")
# docs = chroma_db.similarity_search(query)
# print("检索结果:")
# for doc in docs:
# print(f"- {doc.page_content[:150]}...")通过遵循本教程的方法,您可以有效地解决LangChain在处理多个文档和分块时的常见问题,构建一个健壮且高效的RAG(检索增强生成)系统。
以上就是优化LangChain文档加载与ChromaDB集成:解决多文档处理与分块问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号