理解 Document 对象的结构及其在LangChain中的重要性。 掌握 LangChain 中各种 Document Loaders 的使用,从不同来源(如文本文件、网页、PDF)加载数据。 学习 Text Splitters 的原理和最佳实践,尤其是 RecursiveCharacterTextSplitter,实现高效智能的文本切分。 理解 Embedding Models 的作用,并学会如何将文本内容转换为数值向量。 通过LCEL将数据加载、切分、向量化流程串联起来,为RAG应用奠定数据基础。
page_content (字符串): 这是文档的主要文本内容。 metadata (字典): 这是一个可选的字典,用于存储关于文档的附加信息,如源文件路径、作者、创建日期、页码等。这些元数据在后续的检索和过滤中非常有用。
from langchain_core.documents import Documentmy_document = Document(page_content="LangChain是一个用于开发大语言模型应用的框架。",metadata={"source": "tutorial_intro", "date": "2025-06-02"})print(my_document)print(my_document.page_content)print(my_document.metadata)
最简单直接的加载器。 通常会把文件路径作为 metadata。
# 1. 准备一个文本文件 (先手动创建一个 example.txt)# 文件内容:# LangChain是一个开源框架。# 它的主要目标是帮助开发者构建大语言模型应用。# RAG是LangChain的重要应用之一。with open("example.txt", "w", encoding="utf-8") as f:f.write("LangChain是一个开源框架。\n")f.write("它的主要目标是帮助开发者构建大语言模型应用。\n")f.write("RAG是LangChain的重要应用之一。")from langchain_community.document_loaders import TextLoaderloader = TextLoader("example.txt", encoding="utf-8")documents = loader.load() # load() 返回一个 Document 列表print("\n--- TextLoader 示例 ---")print(f"加载的文档数量: {len(documents)}")print(f"第一个文档内容:\n{documents[0].page_content}")print(f"第一个文档元数据:\n{documents[0].metadata}")
需要安装 bs4 (BeautifulSoup) 库:pip install beautifulsoup4 从指定的URL抓取网页内容。
from langchain_community.document_loaders import WebBaseLoaderloader = WebBaseLoader("https://www.langchain.com/langsmith") # LangChain官方博客页web_documents = loader.load()print("\n--- WebBaseLoader 示例 ---")print(f"加载的文档数量: {len(web_documents)}")if web_documents:print(f"第一个网页文档内容 (部分):\n{web_documents[0].page_content[:200]}...")print(f"第一个网页文档元数据:\n{web_documents[0].metadata}")
需要安装 pypdf 库:pip install pypdf 每个PDF页面通常会作为一个独立的 Document。
from langchain_community.document_loaders import PyPDFLoadertry:pdf_loader = PyPDFLoader("sample.pdf") # 替换为你的PDF文件路径pdf_documents = pdf_loader.load()print("\n--- PyPDFLoader 示例 ---")print(f"加载的PDF文档数量 (按页分): {len(pdf_documents)}")if pdf_documents:print(f"第一个PDF页面内容 (部分):\n{pdf_documents[0].page_content[:200]}...")print(f"第一个PDF页面元数据:\n{pdf_documents[0].metadata}")except FileNotFoundError:print("\n--- PyPDFLoader 示例 (跳过): 请放置一个 'sample.pdf' 文件在当前目录 ---")except Exception as e:print(f"\n--- PyPDFLoader 示例 (错误): {e} ---")
load() 会一次性加载所有内容到内存。对于非常大的文件或大量文件,这可能导致内存问题。 lazy_load() 返回一个生成器(generator)。它不会立即加载所有数据,而是在你需要时逐个生成 Document 对象。这对于处理大规模数据非常高效。
from langchain_community.document_loaders import TextLoader# 假设 example.txt 很大loader = TextLoader("example.txt", encoding="utf-8")print("\n--- lazy_load() 示例 ---")for i, doc in enumerate(loader.lazy_load()):print(f"正在处理第 {i+1} 个文档 (内容部分: {doc.page_content[:50]}...)")if i >= 1: # 仅处理前2个作为示例breakprint("懒加载完成。\n")
这是最常用且推荐的文本切分器。它的核心思想是:递归地尝试不同的分隔符来切分文本,直到满足 chunk_size 要求。 它会优先尝试大粒度的分隔符(如 \n\n 段落),如果不行再尝试小粒度的(如 \n 换行符),最后是单个字符。这有助于尽可能保持语义完整性。 参数: chunk_size:每个块的最大字符数(不是Token数!)。 chunk_overlap:相邻块之间重叠的字符数。这有助于确保上下文不会在块的边界处被切断,提高检索的鲁棒性。
from langchain.text_splitter import RecursiveCharacterTextSplitterlong_text = """LangChain 是一个强大的框架,用于开发由大型语言模型驱动的应用程序。作为一名LangChain教程架构师,我负责设计一套全面、深入且易于理解的LangChain系列教程,旨在帮助读者从入门到精通,掌握LangChain的核心技术和应用。RAG(检索增强生成)是LangChain中的一个关键应用场景。通过RAG,我们可以将LLM与外部知识库相结合,从而让LLM能够回答其训练数据之外的问题。这大大扩展了LLM的应用范围,解决了幻觉和知识过时的问题。"""text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, # 每个块的最大字符数chunk_overlap=20, # 相邻块之间的重叠字符数length_function=len, # 使用 Python 的 len() 函数来计算长度is_separator_regex=False, # 分隔符不是正则表达式)# 可以直接切分字符串chunks_from_str = text_splitter.split_text(long_text)print("\n--- RecursiveCharacterTextSplitter 示例 (切分字符串) ---")for i, chunk in enumerate(chunks_from_str):print(f"块 {i+1} (长度 {len(chunk)}):\n'{chunk}'\n")# 也可以切分 Document 对象列表 (这是更常见的用法)# 首先创建一个 Documentdoc_to_split = Document(page_content=long_text, metadata={"source": "example_doc"})chunks_from_doc = text_splitter.split_documents([doc_to_split])print("\n--- RecursiveCharacterTextSplitter 示例 (切分 Document) ---")for i, chunk_doc in enumerate(chunks_from_doc):print(f"块 {i+1} 内容 (长度 {len(chunk_doc.page_content)}):\n'{chunk_doc.page_content}'")print(f"块 {i+1} 元数据:\n{chunk_doc.metadata}\n")
CharacterTextSplitter:最简单的,只根据一个或一组字符进行切分(例如,每 100 个字符或每个换行符)。 MarkdownTextSplitter/ HTMLHeaderTextSplitter: 结构感知型切分器。它们会识别Markdown或HTML的结构(如标题、代码块),尽量不破坏这些结构来切分,确保语义完整性。 TokenTextSplitter:基于Token数量而不是字符数量进行切分,更精确地控制LLM的上下文。
由OpenAI提供,通常是 text-embedding-3-large模型。性能强大且稳定。 需要 OpenAI API Key。
from langchain_openai import OpenAIEmbeddingsembeddings_model = OpenAIEmbeddings(model="text-embedding-3-large")# 将单个文本转换为向量text1 = "苹果是一种水果"embedding1 = embeddings_model.embed_query(text1) # embed_query 用于单个文本print("\n--- OpenAIEmbeddings 示例 ---")print(f"'{text1}' 的向量长度: {len(embedding1)}")# print(f"向量:\n{embedding1[:10]}...") # 打印部分向量值text2 = "香蕉是一种水果"text3 = "苹果手机没国产的好用"embedding2 = embeddings_model.embed_query(text2)embedding3 = embeddings_model.embed_query(text3)# 简单计算相似度 (这里只是示意,实际会用向量数据库的相似度计算)from sklearn.metrics.pairwise import cosine_similarityimport numpy as np# 将列表转换为 numpy 数组进行计算sim1_2 = cosine_similarity(np.array(embedding1).reshape(1, -1), np.array(embedding2).reshape(1, -1))[0][0]sim1_3 = cosine_similarity(np.array(embedding1).reshape(1, -1), np.array(embedding3).reshape(1, -1))[0][0]print(f"'{text1}' 和 '{text2}' 的相似度: {sim1_2:.4f} (语义相似)")print(f"'{text1}' 和 '{text3}' 的相似度: {sim1_3:.4f} (语义不相似,但词语重合)\n")# 你会发现 sim1_2 远高于 sim1_3,因为“苹果”和“香蕉”都是水果,语义上更近。
允许你使用 Hugging Face 上托管的各种开源Embedding模型,甚至可以在本地运行。 需要安装 sentence-transformers 库:pip install sentence-transformers 模型下载可能需要时间。
embed_documents(texts: List[str]) -> List[List[float]]: 接受一个字符串列表(通常是切分后的文档块),返回每个字符串对应的向量列表。 embed_query(text: str) -> List[float]: 接受单个字符串(通常是用户查询),返回其向量。在RAG中,我们会把用户查询也向量化,然后去向量数据库中找相似的文档块。
from dotenv import load_dotenvimport osfrom langchain_community.document_loaders import TextLoaderfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddingsfrom langchain_core.runnables import RunnableLambdaload_dotenv()# --- 1. 定义数据加载器 ---loader = TextLoader("example.txt", encoding="utf-8") # 使用我们之前创建的 example.txt# --- 2. 定义文本切分器 ---text_splitter = RecursiveCharacterTextSplitter(chunk_size=100,chunk_overlap=20,length_function=len)# --- 3. 定义Embedding模型 ---embeddings_model = OpenAIEmbeddings(model="text-embedding-3-large")# --- 4. 构建LCEL数据准备流水线 ---# 步骤 A: 加载文档 (loader.load() 返回 List[Document])# 步骤 B: 切分文档 (text_splitter.split_documents() 接受 List[Document],返回 List[Document])# 步骤 C: 提取每个 Document 的 page_content,形成 List[str]# 步骤 D: 将 List[str] 转换为 List[List[float]] (Embedding 向量)data_preparation_pipeline = (RunnableLambda(lambda x: loader.load()) # A: 加载文档| RunnableLambda(lambda docs: text_splitter.split_documents(docs)) # B: 切分文档| RunnableLambda(lambda chunks: [chunk.page_content for chunk in chunks]) # C: 提取文本内容| RunnableLambda(lambda texts: embeddings_model.embed_documents(texts)) # D: 生成向量)print("\n--- LCEL 数据准备流水线示例 ---")# 运行流水线# 注意:这里 invoke() 的输入可以为空字典 {},因为 loader.load() 不依赖外部输入all_embeddings = data_preparation_pipeline.invoke({})print(f"生成的块数量: {len(all_embeddings)}")if all_embeddings:print(f"第一个块的向量长度: {len(all_embeddings[0])}")# print(f"第一个块的向量 (部分):\n{all_embeddings[0][:10]}...")
我们用 RunnableLambda 将普通的函数调用封装成 Runnable,使其能够通过 | 连接。 流水线清晰地展示了数据如何从文件加载,经过切分,最终被向量化。 invoke({}) 用于启动流水线,因为 loader.load() 内部不依赖用户输入。
数据加载: 掌握了 Document 对象和各种 Document Loaders 的使用,尤其强调了 lazy_load() 的高效性。 文本切分: 理解了 Text Splitters 的重要性,并学会了使用 RecursiveCharacterTextSplitter 进行智能切分。 向量化: 了解了 Embedding Models 的原理,并实践了如何将文本转换为数值向量。
现在,你手中的数据不再是散乱的文本,而是一系列高维向量,它们是理解语义的数字指纹。在下一期教程中,我们将学习如何存储这些向量(向量数据库),以及如何利用它们进行高效的检索与召回,为RAG的核心功能打上坚实的基础!敬请期待!
文章转载自AI云枢,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




