资讯动态

核心组件——文档加载器 Document loaders

发布时间:2026/10/22 2:23:22 来源:尧图企业网站定制
5.1 RAG介绍5.1.1 RAG概念Retrieval-Augmented Generation检索增强生成。 这是当前大语言模型应用的核心模式。RAG 的流程相对复杂为了更好的理解 RAG我们先用 AI 搜索来引出 RAG。对于【AI大模型】来说它最擅长的是语义理解和文本总结最不擅长的就是获取实时的信息。因为大模型的训练数据是有截止日期的对于【搜索引擎】来说它最擅长的就是获取实时的信息缺点是信息分散每次都需要人为进行总结。大模型与搜索引擎的结合就是给 AI 配备了一个活字典让 AI 可以随时进行查阅。下图展示了一个最简单的 AI 搜索工作流程搜索引擎在这里充当知识库结合我们的查询语句大模型便可以从知识库中获取相应的查询结果首先先来思考一个问题搜索引擎可以帮我们解决实时数据的获取但获取到的数据也是受限的。 它只能获取到公开在网络中的数据而无法获取到一些本地数据或企业内部的私有数据等此时该如何答案使用 RAG检索增强生成技术当用户向 LLM 提问时系统首先在知识库如公司内部文档中进行语义搜索找到最相关的内容然后将这些内容和问题一起交给 LLM 来生成答案。与 AI 搜索类比本质是知识库改变了从搜索引擎线上搜索改为了本地或私有知识库中搜索。有了以上流程的铺垫接下来正式进入 RAG 的学习。5.1.2 RAG流程RAG 的流程分为【离线数据处理】和【在线检索】两个过程。上面提到RAG 知识库可以是本地文档、公司内部文档等一些私有化数据。但这些私有数据或文档实际上并不能很好地被直接进行检索访问。因此需要将这些私有化数据构建成可以被检索的知识库这就是离线数据处理要干的事情。经过离线数据后知识则会按照某种格式以及排列方式存储在知识库中等待被使用。而在线检索则是我们依赖知识库查询通过大模型生成结果的过程。这张图将会是我们后续要学习 LangChain 组件知识地图所有的组件都会一一进行讲解现在我们只需掌握其流程接触相关概念即可。文档加载 (Document Loading)加载多种不同来源加载文档。LangChain 提供了 100 多种不同的文档加载器包括 PDF 在内的非结构化的数据、SQL 在内的结构化的数据以及Java、Python 之类的代码等。文本分割 (Splitting)文本分割器负责把 Documents 切分为指定大小的块。存储 (Storage)存储涉及到两个环节分别是将切分好的文档块进行嵌入Embedding即将文档块转换成向量的形式。将 Embedding 后的向量数据存储到向量数据库中。检索 (Retrieval)数据存入向量数据库后。当我们需要进行数据检索时会通过某种检索算法找到与输入问题相似的文档块。输出 (Output)把问题以及检索出来的文档块一起提交给 LLMLLM 会通过问题和检索出来的提示一起来生成更加合理的答案。我们现在已经知道了 RAG 的完整流程但也仅是知道 RAG 是什么至于流程中为什么要进行文档加载、文本分割、存储我们还无从了解。因此后续的各个 LangChain 组件讲解时都会涉及每个步骤被设计出来的原因。5.1.3 RAG示例讲这么多不如一见。接下来就来演示一个 RAG 系统。在这个示例中我们提供了《租房项目QA》文档希望通过聊天方式提问关于项目的任何问题最终得到答案。要求最多只用三句话回答要简明扼要。代码如下不用理解代码含义只需要看结果即可# 定义聊天模型 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链 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结束程序【说明】我们提供的文档越详细生成的结果越符合预期。5.2 Document 文档类要想实现 RAG首先就需要从源中获取数据即加载数据或文档。这是通过 LangChain 的文档加载器完成的。LangChain 文档加载器可以将各种数据源加载成一系列的文档对象 Document。class langchain_core.documents.base.Document用于存储一段文本和相关元数据的类我们可以直接定义LangChain 文档列表如下所示from langchain_core.documents import Document documents [ # 单个Document对象通常表示较大文档的一个块 Document( # 内容字符串 page_content狗是很好的伴侣以忠诚和友好而闻名。, # 元数据字典 # 元数据属性可以捕获有关文档源、与其他文档的关系以及其他信息的信息。 metadata{source: mammal-pets-doc}, ), Document( page_content猫是独立的宠物经常享受自己的空间。, metadata{source: mammal-pets-doc}, ), ]这里我们定义了一个documents文档列表其内包含了两个Document文档对象。通常单个Document对象表示较大文档的一个块/页。一个Document一般对应文档切片chunk大文件会被切分成多个 Document 对象组成列表。每个Document对象包含了以下参数id可选的文档标识符。理想情况下这应该在整个文档集合中是唯一的并格式化为UUID但不会强制执行。page_content文档的核心文本内容RAG 后续会对该字段做文本切分、向量化。metadata字典类型用来保存附加信息比如文档来源、页码、标题等检索时可以用来过滤、溯源。类型为dict [Optional]5.3 加载PDF文档将本地的 PDF 文档加载到 LangChain 中其实就是将 PDF 文档转换为一个个Document对象。这时就需要我们使用PyPDFLoader文档加载器完成这一功能。class langchain_community.document_loaders.pdf.PyPDFLoader类有以下关键函数__init__()初始化函数入参file_path表示要加载的 PDF 文件的路径。load() - list[Document]将数据加载到文档对象中。返回文档对象列表。现在让我们加载一个本地 PDF 文档看下效果。from langchain_community.document_loaders import PyPDFLoader file_path ../Docs/PDF/脚⼿架级微服务租房平台QA.pdf loader PyPDFLoader(file_path) # 将 PDF ⽂件的每⼀⻚转换为⼀个独⽴的 Document 对象并存储在列表 docs 中。 docs loader.load()接下来来看看Document重要的一些参数print(f问PDF 文件的总页数为: {len(docs)}\n) #问PDF 文件的总页数为:32 print(f问第一页文本内容的前200个字符是: \n{docs[0].page_content[:200]}\n) #问第一页文本内容的前200个字符是: 脚手架级微服务租房平台 通用问题 1. 为什么做这个项目? • 回答1: (出于兴趣爱好开发) 大学期间我和同学在外合租过一段时间使用了一些租房平台于是我有个想法自己能不能开发一个租房平台可以让我将理论知识与实践相结合。我希望通过实际项目来加深对Java编程语言和相关技术的理解。于是我便查找了一些资料看了一些开源项目进行了一些改进。 • 回答2: (开源项目的解释) 这个 print(f问第一页元数据: {docs[0].metadata}) #问第一页元数据:{producer: pdfcpu v0.8.1 dev, creator: Chromium, creationdate: 2025-08-28T17:52:3408:00, moddate: 2025-08-28T17:52:3408:00, source: ../PDF/脚手架级微服务租房平台QA.pdf, total_pages: 32, page: 0, page_label: 1}现在许多LLM支持对多模态输入(例如图像)进行推理。在某些应用程序中例如对具有复杂布局、图表或扫描的PDF进行问答可以跳过PDF解析直接将PDF页面转换为图像并将其直接传递给模型可能是更准确的。5.4 加载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现在让我们使用 single模式加载一个本地Markdown 文档看下效果。markdown_path ../Docs/Markdown/脚⼿架级微服务租房平台QA.md # single 模式加载后默认只有⼀个 Document 对象 # mode 不写默认就是 single loader UnstructuredMarkdownLoader(markdown_path) data loader.load() # 断言校验返回列表只有1个Document对象 assert len(data) 1 assert isinstance(data[0], Document) # 打印前200字符文本内容 print(data[0].page_content[:200]) # 打印元信息默认包含 source: 文件路径 print(data[0].metadata) #打印结果如下 通⽤问题 为什么做这个项⽬ 回答1 {source: ../Docs/Markdown/脚⼿架级微服务租房平台QA.md}elements模式下加载本地Markdown文档的效果# elements 模式加载后默认只有⼀个 Document 对象 loader UnstructuredMarkdownLoader(markdown_path, modeelements) data loader.load() print(f问⽂档个数为\n{len(data)}\n) print(问前三个⽂档数据) for document in data[:3]: print(f{document}\n) #打印结果如下 问⽂档个数为 441 问前三个⽂档数据 page_content通⽤问题 metadata{source: ../Docs/Markdown/脚⼿架级微服务租房平 台QA.md, category_depth: 0, languages: [zho], file_directory: ../Docs/Markdown, filename: 脚⼿架级微服务租房平台QA.md, filetype: text/markdown, last_modified: 2025-08-29T10:56:36, category: Title, element_id: 3a0670f9bfd58576e430ef11def41593} page_content为什么做这个项⽬ metadata{source: ../Docs/Markdown/脚⼿架级微 服务租房平台QA.md, category_depth: 2, emphasized_text_contents: [为什么做这 个项⽬], emphasized_text_tags: [b], languages: [zho], file_directory: ../Docs/Markdown, filename: 脚⼿架级微服务租房平台QA.md, filetype: text/markdown, last_modified: 2025-08-29T10:56:36, parent_id: 3a0670f9bfd58576e430ef11def41593, category: Title, element_id: fcb08b2a85942455eecebb9467ffca4c} page_content回答1出于兴趣爱好开发 metadata{source: ../Docs/Markdown/脚 ⼿架级微服务租房平台QA.md, emphasized_text_contents: [回答1出于兴趣爱好开 发], emphasized_text_tags: [b], languages: [zho], file_directory: ../Docs/Markdown, filename: 脚⼿架级微服务租房平台QA.md, filetype: text/markdown, last_modified: 2025-08-29T10:56:36, parent_id: fcb08b2a85942455eecebb9467ffca4c, category: UncategorizedText, element_id: a6fc0b5a457d21234bf1c4a6ae0a18db}可以看见文档被分成了441个。根据什么规则拆分呢?——答案是根据类型拆分。在元数据中有一个表示类型的字段:category。这些类型都是现代文档解析库(如Unstructured.io)中用于分类Markdown (MD)文档或从其他格式(如 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元素用来表示其归属于哪个文档对象下可以用来表示层级关系。如示例中的前三个文档对象就有明显的层级关系page_content通用问题 metadata{ category: Title, element_id: 3a0670f9bfd58576e430ef11def41593 } page_content为什么做这个项目 metadata{ parent_id: 3a0670f9bfd58576e430ef11def41593, category: Title, element_id: fcb08b2a85942455eecebb9467ffca4c } page_content回答1出于兴趣爱好开发 metadata{ parent_id: fcb08b2a85942455eecebb9467ffca4c, category: UncategorizedText, element_id: a6fc0b5a457d21234bf1c4a6ae0a18db }通用问题无parent_id→一级标题为什么做这个项目parent_id 指向一级标题的 element_id →二级标题回答1出于兴趣爱好开发parent_id 指向二级标题的 element_id类别是 UncategorizedText → 二级标题下属文本内容经过这种设计就可以还原出整个 MD 文档的内容了。对于 LangChain 来说能加载的文档类型远不止这些它还能加载网页、一些云提供商文件、社交媒体平台文档等更多文档加载器见LangChain Python integrations - Docs by LangChain

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑