【LangChain核心组件】文档加载器

📅 2026/6/24 13:03:47
【LangChain核心组件】文档加载器
个人主页秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343 系列专栏https://blog.csdn.net/qinjh_/category_13137010.html​​​目录文档加载器Document loadersRAG 介绍RAG 概念RAG 流程RAG 示例Document 文档类加载 PDF 文档加载 Markdown 文件前言 hello! 各位铁子们大家好哇。今日更新了LangChain相关内容 欢迎大家关注点赞收藏⭐️留言文档加载器Document loadersRAG 介绍RAG 概念RAG Retrieval-Augmented Generation检索增强生成。这是当前大语言模型应用的核心模式。RAG 的流程相对复杂为了更好的理解 RAG我们先用 AI 搜索 来引出 RAG。对于【AI 大模型】来说它最擅长的是语义理解和文本总结最不擅长的就是获取实时的信息。因 为大模型的训练数据是有截止日期的对于【搜索引擎】来说它最擅长的就是获取实时的信息缺点是信息分散每次都需要人为进行 总结。大模型与搜索引擎的结合就是给 AI 配备了一个活字典让 AI 可以随时进行查阅。下图展示了一个最简单的 AI 搜索工作流程搜索引擎在这里充当知识库结合我们的查询语句大模 型便可以从知识库中获取相应的查询结果有了以上流程的铺垫接下来正式进入 RAG 的学习。首先先来思考一个问题搜索引擎可以帮我们解决实时数据的获取但获取到的数据也是受限的。 它只能获取到公开在网络中的数据而无法获取到一些本地数据或企业内部的私有数据等此时该 如何答案是使用 RAG检索增强生成技术当用户向 LLM 提问时系统首先在知识库如公司内部文 档中进行语义搜索找到最相关的内容然后将这些内容和问题一起交给 LLM 来生成答案。与 AI 搜 索类比本质是知识库改变了从搜索引擎线上搜索改为了本地或私有知识库中搜索。RAG 流程RAG 的流程分为【离线数据处理】和【在线检索】两个过程。上面提到RAG 知识库可以是本地文档、公司内部文档等一些私有化数据。但这些私有数据或文档实 际上并不能很好地被直接进行检索访问。因此需要将这些私有化数据构建成可以被检索的知识库这 就是离线数据处理要干的事情。经过离线数据后知识则会按照某种格式以及排列方式存储在知识库 中等待被使用。而在线检索则是我们依赖知识库查询通过大模型生成结果的过程。过程如下图所示这张图将会是我们后续要学习 LangChain 组件知识地图所有的组件都会一 进行讲解现在我们只 需掌握其流程接触相关概念即可。文档加载 (Document Loading)加载多种不同来源加载文档。LangChain 提供了 100 多种不同的 文档加载器包括 PDF 在内的非结构化的数据、SQL 在内的结构化的数据以及 Python、Java 之类的代码等。文本分割 (Splitting)文本分割器把 Documents 切分为指定大小的块。存储 (Storage)存储涉及到两个环节分别是将切分好的文档块进行嵌入Embedding即将文档块转换成向量的形式。将 Embedding 后的向量数据存储到向量数据库中。检索 (Retrieval)数据存入向量数据库后。当我们需要进行数据检索时会通过某种检索算法找到 与输入问题相似的文档块。输出 (Output)把问题以及检索出来的文档块一起提交给 LLMLLM 会通过问题和检索出来的提 示一起来生成更加合理的答案。我们现在已经知道了 RAG 的完整流程但也仅是知道 RAG 是什么至于流程中为什么要进行文档加 载、文本分割、存储我们还无从了解。因此后续的各个 LangChain 组件讲解时都会涉及每个步骤被 设计出来的原因。RAG 示例讲这么多不如一见。接下来就来演示一个 RAG 系统。在这个示例中我们提供了《租房项目QA》文档希望通过聊天方式提问关于项目的任何问题最终得到答案。要求最多只用三句话回答要简明扼要。代码如下不用理解代码含义只需要看结果即可from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_redis import RedisConfig, RedisVectorStore from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough # 定义聊天模型 model ChatOpenAI(modelgpt-4o-mini) # 定义嵌入模型 embeddings OpenAIEmbeddings(modeltext-embedding-3-large) # 配置 Redis 客户端 redis_url redis://192.168.100.238:6379 config RedisConfig( index_nameqa, redis_urlredis_url, metadata_schema[ {name: category, type: tag}, {name: num, type: numeric}, ], ) # 定义 Redis 向量存储 vector_store RedisVectorStore(embeddings, configconfig) # 生成检索器 retriever vector_store.as_retriever() # 定义提示词模板 prompt ChatPromptTemplate.from_messages( [ ( human, 你是负责回答问题的助手。使用以下检索到的上下文片段来回答问题。如果你不知道答案就说你不知道。最多只用三句话回答要简明扼要。 Question: {question} Context: {context} Answer:, ), ] ) # 将文档转换为字符串 def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) # 定义链 rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | model | StrOutputParser() ) # 循环输入问题 while True: # 获取用户输入 question input(\n请输入您的问题输入退出或quit结束程序: ).strip() # 检查是否退出 if question.lower() in [退出, quit]: print(程序已结束再见) break # 检查输入是否为空 if not question: print(问题不能为空请重新输入。) continue # 执行链流式输出 print(回答: , end, flushTrue) chunks [] for chunk in rag_chain.stream(question): chunks.append(chunk) print(chunk, end, flushTrue) print() # 换行运行代码请输入您的问题输入退出或quit结束程序: 介绍一下这个项目 回答: 这个项目是一个基于脚手架的微服务在线租房系统旨在模仿贝壳、安居客等流行应用具备真 实的交互体验和架构设计。通过结合理论和实践我在这个项目中加深了对 Java 编程语言的理解。这 个项目的灵感来源于我在大学时和同学合租的经历。 请输入您的问题输入退出或quit结束程序: 项目设计难点有哪些 回答: 项目设计的难点主要包括非技术方面的需求把控和技术方面的明确项目范围与目标。缺乏产品经 理的介入使得设计过程中需自行绘制原型图并拆分功能这增加了设计的复杂性。同时测试和质量保 证也是项目中的重要挑战需要合理安排时间以确保项目质量。 请输入您的问题输入退出或quit结束程序: 介绍数据存储的相关设计 回答: 数据存储的设计中使用MySQL作为关系数据库管理系统并结合MyBatis简化SQL操作。为了 更好地应对高并发场景设计了Redis缓存方案来优化内存利用和减轻数据库压力。引入OSS对象存储 是为了实现无限扩展性相对于本地存储和MySQL它具备更高的性能和成本效益。 请输入您的问题输入退出或quit结束程序: 详细介绍 Redis 与 Mysql 数据一致性方案 回答: Redis与MySQL的数据一致性方案通常采用“双写一致性”模式通过Cache-Aside方法实现。在 这一方案中应用程序在读取数据时先查询Redis缓存如果不存在再查询MySQL数据库并将结果缓 存到Redis中在更新数据时需同时更新MySQL和Redis以确保两者状态一致。此方法也包括设置 合理的缓存过期时间和使用布隆过滤器以解决缓存穿透等问题。 请输入您的问题输入退出或quit结束程序:我们提供的文档越详细生成的结果越符合预期。Document 文档类要想实现 RAG首先就需要从源中获取数据即加载数据或文档。这是通过 LangChain 的文档加载器 完成的。LangChain 文档加载器可以将各种数据源加载成一系列的文档对象 Document 。class langchain_core.documents.base.Document 用于存储一段文本和相关元数据的 类我们可以直接定义LangChain 文档列表如下所示# 手动定义的文档列表 documents [ # 对于单个Document文档它一般表示较大的文档的某个块或者某一页 Document( # 内容 page_content狗是忠实的伴侣, # 元数据字典 # 元数据属性可以包含文档源与其他文档的关系以及其他属性信息 metadata{source: pets-doc}, ), Document( # 内容 page_content猫是独立的宠物, # 元数据字典 # 元数据属性可以包含文档源与其他文档的关系以及其他属性信息 metadata{source: pets-doc}, ), ]这里我们定义了一个 documents 文档列表其内包含了两个 Document 文档对象。通常单个 Document对象表示较大文档的一个块/页。每个 Document 对象包含了以下参数id 可选的文档标识符。理想情况下这应该在整个文档集合中是唯一的并格式化为 UUID但不会强制执行。page_content 字符串文本metadata 与内容关联的任意元数据。类型为 dict [Optional]加载 PDF 文档将本地的 PDF 文档加载到 LangChain 中其实就是将 PDF 文档转换为一个个 Document 对象。这时 就需要我们使用 PyPDFLoader 文档加载器完成这一功能。class langchain_community.document_loaders.pdf.PyPDFLoader 类有以下关键 函数init() 初始化函数入参 file_path 表示要加载的 PDF 文件的路径。load() → list[Document] 将数据加载到文档对象中。返回文档对象列表。现在让我们加载一个本地 PDF 文档看下效果。# 文档加载器PDF loader PyPDFLoader(file_path../Docs/pdf/脚手架级微服务租房平台QA.pdf) # 加载生成文档列表 # 将 PDF 文件的每一页转换为一个独立的 Document 对象并存储在列表 docs 中。 docs loader.load() # PDF加载器默认将文档按分页进行拆分 print(fPDF文档总页数\n{len(docs)}\n) print(f第一页文本的内容(前200)是\n{docs[0].page_content[:200]}\n) print(f第一页的元数据字典是\n{docs[0].metadata}\n) print(f第二页文本的内容(前200)是\n{docs[1].page_content[:200]}\n) print(f第二页的元数据字典是\n{docs[1].metadata}\n)现在许多 LLM 支持对多模态输入例如图像进行推理。在某些应用程序中例如对具有复杂布局、 图表或扫描的 PDF 进行问答可以跳过 PDF 解析直接将 PDF 页面转换为图像并将其直接传递给模 型可能是更准确的。加载 Markdown 文件将本地的 Markdown 文档加载到 LangChain 中需要我们使用 UnstructuredMarkdownLoader 文档加载器完成这一功能。class langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader 类有以下关键函数init() 初始化函数所需参数file_path 表示要加载的 Markdown 文件的路径。mode 加载文件时要使用的模式。可以是 single 或 elements。默认为 single。single文档将作为单个 Document 对象返回elements会将文档拆分为 Title 和 NarrativeText 等不同类型的元素。load() → list[Document] 将数据加载到文档对象中。返回文档对象列表。LangChain 实现的 UnstructuredMarkdownLoader 需要依赖 Unstructured 包。因此在使用前我 们需要先安装它pip install unstructured[md] nltk现在让我们加载一个本地 Markdown 文档看下效果。# 文档加载器MD md_loader UnstructuredMarkdownLoader( ../Docs/markdown/脚手架级微服务租房平台QA.md, # modesingle, # MD 加载器默认将文档加载为一个 modeelements, # 拆分成不同类型的子块 ) # Document 列表 docs md_loader.load() print(fMD文档总数\n{len(docs)}\n) print(f第一个文档的内容是\n{docs[0].page_content}\n) # source: ../Docs/markdown/脚手架级微服务租房平台QA.md # category: Title, # 分类 # element_id: 3a0670f9bfd58576e430ef11def41593 # 每个文档的唯一标识 print(f第一个文档的元数据字典是\n{docs[0].metadata}\n) print(f第二个文档的内容是\n{docs[1].page_content}\n) # source: ../Docs/markdown/脚手架级微服务租房平台QA.md # parent_id: 3a0670f9bfd58576e430ef11def41593, # category: Title, # element_id: fcb08b2a85942455eecebb9467ffca4c print(f第二个文档的元数据字典是\n{docs[1].metadata}\n) print(f第三个文档的内容是\n{docs[2].page_content}\n) # source: ../Docs/markdown/脚手架级微服务租房平台QA.md # parent_id: fcb08b2a85942455eecebb9467ffca4c, # category: UncategorizedText, # 未分类文本 # element_id: a6fc0b5a457d21234bf1c4a6ae0a18db print(f第三个文档的元数据字典是\n{docs[2].metadata}\n) # { # Table, 表格 # Image, 图像 # NarrativeText, 叙事性文本 # Title, 标题 # ListItem, 列表项 # UncategorizedText 未分类的文本 # } print(f当前MD文档的所有分类{set(document.metadata[category] for document in docs)})打印结果如下可以看见文档被分成了 441 个。根据什么规则拆分呢答案是根据类型拆分。在元数据中有一个表示类型的字段 category 。这些类型都是现代文档解析库如 Unstructured.io中用于分类 MarkdownMD文档或从其他格式如 PDF, Word, HTML解析后 转换为 Markdown 的常见元素类型。让我们把当前文档包含的所有类型拿出来看看print(set(document.metadata[category] for document in data))结果{Image, Title, ListItem, Table, NarrativeText, UncategorizedText}含义如下Image图像。使用 ![alt text](image-url) 语法插入的图片。Title标题。这里包含了一级、二级、三级等标题。ListItem列表项。以 - , * , 开头的无序列表项或以 1. , 2. 等开头的有序列表项。Table表格。使用 | 和 - 语法创建的表格。NarrativeText叙述性文本。一个或多个连续的段落。UncategorizedText未分类文本。通常是表格中的脚注或注释、图片下方的简短说明、项目 符号中非常简短的词组、文档页眉/页脚中的日期或页码等元数据。这里的标题都是用 Title 表示那么如何区分到底是几级标题呢首先每个文档对象的元数据中都包含一个 element_id 用来识别其唯一性除此之外还有一个 parent_id 元素用来表示其归属于那个文档对象下可以用来表示层级关系。如示例中的前三个 文档对象就有明显的层级关系。经过这种设计就可以还原出整个 MD 文档的内容了。对于 LangChain 来说能加载的文档类型远不止这些它还能加载网页、一些云提供商文件、社交媒 体平台文档等