支持私有化部署
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


数据工程:RAG系统的基石

发布日期:2025-05-23 05:22:50 浏览次数: 1562 作者:CreatorAI
推荐语

探索RAG系统在数据工程中的核心作用,深入了解其构建与应用。

核心内容:
1. RAG系统结合外部知识源与大型语言模型的生成能力
2. RAG系统从Naive到Agentic的演进过程
3. 构建RAG问答系统的技术选型与代码示例

杨芳贤
53A创始人/腾讯云(TVP)最具价值专家

前言

检索增强生成(Retrieval-Augmented Generation, RAG)系统通过结合外部知识源与大型语言模型(LLM)的生成能力,旨在提供更准确、更具上下文感知能力的回答。我们在之前的“Agentic RAG”文章中详细介绍了RAG从Naive RAG到Agentic RAG的发展过程。

一个优秀的RAG系统必定是个复杂的系统,这涉及到诸多层面的因素考量,宏观层面,从索引、检索、到增强,最后大模型进行输出,每一个环节都非常重要。每个环节都有对应的技术选型,如Embedding模型、向量数据库、Rerank模型、到最后的LLM。搭建一个简单的RAG问答系统很简单,借助AI,一句话就可以帮你生成:

import osfrom langchain_community.document_loaders import TextLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddings, ChatOpenAIfrom langchain_community.vectorstores import FAISSfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParser# --- 1. 设置 OpenAI API 密钥 ---# 确保你已经设置了环境变量 OPENAI_API_KEY# 或者直接在这里设置:# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"# --- 2. 准备并加载文档 ---# 假设你有一个名为 my_document.txt 的文件# 你可以手动创建这个文件并添加一些文本内容with open("my_document.txt", "w", encoding="utf-8") as f:    f.write("Langchain 是一个强大的框架,用于构建基于大语言模型的应用程序。\n")    f.write("RAG 系统结合了检索和生成的能力。\n")    f.write("FAISS 是一个高效的相似性搜索库。\n")    f.write("OpenAI 提供了先进的语言模型和嵌入模型。\n")loader = TextLoader("my_document.txt", encoding="utf-8")documents = loader.load()# --- 3. 分割文档 ---text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)texts = text_splitter.split_documents(documents)# --- 4. 创建文本嵌入模型 ---# 确保你的 OPENAI_API_KEY 环境变量已设置try:    embeddings = OpenAIEmbeddings()except ImportError:    print("OpenAIEmbeddings 未找到。请确保已安装 langchain-openai 并且 OPENAI_API_KEY 已设置。")    exit()# --- 5. 创建向量存储 ---# FAISS 是一个内存中的向量数据库,你也可以选择其他的,比如 Chroma, Pinecone 等try:    vectorstore = FAISS.from_documents(texts, embeddings)except Exception as e:    print(f"创建向量存储时出错: {e}")    print("这可能是因为 OpenAI API 密钥无效或网络问题。")    exit()# --- 6. 创建检索器 ---retriever = vectorstore.as_retriever(search_kwargs={"k": 2}) # 检索最相关的2个文档块# --- 7. 创建 Prompt 模板 ---template = """基于以下检索到的上下文来回答问题。如果你不知道答案,就说你不知道,不要试图编造答案。最多使用三句话,并保持回答简洁。上下文:{context}问题: {question}有用的回答:"""prompt = ChatPromptTemplate.from_template(template)# --- 8. 创建大语言模型 ---llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.7)# --- 9. 创建 RAG 链 ---# LCEL (Langchain Expression Language) 的写法rag_chain = (    {"context": retriever, "question": RunnablePassthrough()}    | prompt    | llm    | StrOutputParser())# --- 10. 运行 RAG 链并提问 ---if __name__ == "__main__":    print("RAG 系统已准备就绪。请输入你的问题:")    while True:        user_question = input("你: ")        if user_question.lower() in ["退出", "exit", "quit"]:            break        if user_question:            try:                response = rag_chain.invoke(user_question)                print(f"AI: {response}")                # 如果想查看检索到的上下文内容,可以这样做:                # retrieved_docs = retriever.invoke(user_question)                # print("\n--- 检索到的上下文 ---")                # for i, doc in enumerate(retrieved_docs):                #     print(f"文档 {i+1}:\n{doc.page_content}\n")            except Exception as e:                print(f"处理请求时发生错误: {e}")        else:            print("请输入一个问题。")

然而,这个简单RAG系统仅限于demo,真实的个人场景、企业场景,信息、数据、知识的处理和AI赋能要远比这复杂得多。LLM在做最后的输出时,本质上就是利用参考的知识片段作为提示词的一部分。数据的质量和组织方式直接决定了后续检索信息的关联性和准确性,进而对LLM生成内容的质量产生决定性影响。今天我们就来详细拆解下RAG的第一步,也是最关键的一步:数据工程

一、数据加载

RAG 索引流程的第一步是从各种来源加载数据,这些数据可能是非结构化、半结构化或结构化的,确保数据准确、完整地被摄取,对于 RAG 的性能至关重要。

非结构化数据的挑战。对于非结构化数据格式,“数据加载”这一术语往往掩盖了一个复杂的、针对特定格式的子流程,该流程涉及多种专用工具(如 OCR、布局分析、表格提取、图像字幕生成)。此处的工程工作量不容小觑,一个通用的“加载器”不足以应对这些挑战:

  • PDF 文件:PDF 文件具备多样性:可能是基于文本的、基于图像的(扫描件),或者包含复杂的布局,如表格、图表、公式和多栏文本;对于扫描版 PDF(需要 OCR)或具有复杂布局的 PDF,简单的文本提取往往会失败;

  • DOCX/Office 文件:虽然通常比 PDF 更结构化,但它们可能包含复杂元素,如嵌入式对象、表格和修订记录;

  • HTML文件:需要强大的解析能力来移除样板代码(导航栏、页脚、广告)并提取有意义的内容,通常会保留一些结构化标签(如标题、列表)作为元数据或包含在切片本身中;

  • 多模态文件:在 RAG 中处理图像通常意味着提取嵌入文本(OCR)或使用视觉模型生成文本描述/标题。

处理半结构化与结构化数据。对于结构化/半结构化数据,核心困难不在于解析格式本身,而在于将其固有的结构(行、列、键、嵌套)转换为线性的文本表示(切片),同时保留语义含义,且不丢失关键的关系信息,以便 LLM 理解,这些数据格式及其特点:

  • JSON/CSV 文件:虽然是结构化的,但挑战在于如何有意义地将表格或嵌套数据转换为 LLM 能够理解的文本切片。这可能涉及序列化行、汇总数据或基于模式提取特定字段;

  • 表格(独立或文档内):需要专门的解析来维护行/列关系,有效地对表格进行切分本身就是一个独特的挑战;

  • 代码库:索引代码需要解析抽象语法树(AST)或使用特定语言的解析器来创建有意义的切片(例如,函数、类),而不是任意分割。函数签名、依赖关系或文档字符串等元数据至关重要。

二、数据清洗

数据清洗并非一次性的通用步骤,而是一个迭代过程,必须根据具体的数据源和 RAG 系统的目标进行调整。过度激进的清洗可能会移除重要的上下文线索。不同的数据类型和来源会有不同的“噪声”特征,数据清洗主要包含以下几个部分:

  • 清洗:移除不相关内容(样板文件、广告、导航栏、特殊字符、多余空格,并纠正错误(拼写错误、语法错误);

  • 规范化:统一文本(例如,大小写、日期格式、度量单位)和数据格式以确保一致性;

  • 去重:识别并移除重复或近似重复的文档/切片对于防止检索结果偏差和降低存储/处理成本;

  • 降噪:过滤掉与知识库目的无关的不相关部分或文档。

三、数据分块

大语言模型(LLM)存在上下文窗口限制,而更小、更集中的切片能提高检索精度,数据分块至关重要,切分策略的选择深刻影响检索效果和生成质量。

基本切分方法。基础切分方法易于实现,但它们往往是在计算效率和语义完整性之间进行权衡,主要缺点是可能导致语义碎片化:

  • 固定大小切分:将文本按预定长度分割成片段,通常带有重叠部分;

  • 递归字符/文本切分:通过一组预设字符(如换行符)进行参数化,按顺序尝试分割,直到切片足够小。目标是尽可能保持段落、句子、词语的完整性;

  • 基于句子的切分:根据句子边界分割文本,每个切片包含一个或多个完整句子。

高级与上下文感知切分方法。高级切分技术反映了从依赖表层句法线索(固定长度、句子终止符)到更深层次内容语义理解的根本转变。这需要更复杂的数据工程(将嵌入模型、NLP 库甚至 LLM 集成到切分流程中),但有望产生更高质量的切片:

  • 语义切分 利用自然语言处理技术,通常是嵌入模型,来识别文本中意义或主题的自然断点,将语义相关的句子/内容组合在一起;

  • 文档结构感知切分:利用文档中明确的结构元素,如 Markdown 标题、HTML 标签、章节、段落、列表或表格作为切分边界;

  • Agent切分:由 LLM 自行根据语义含义和内容结构(段落类型、章节标题、说明步骤等)确定合适的文档分割点;

  • 混合方法:结合多种策略,例如,先进行结构化切分,然后在大的章节内进行语义分割,或者固定大小切分结合语义分析进行边界调整。

切片与精度的权衡。不存在通用的“最佳”切片大小或重叠。具体方法必须根据数据特性、嵌入模型、检索策略、LLM 上下文窗口和特定任务需求进行调整,通常通过迭代评估来完成,主要的考量因素:

  • 较小切片:检索精度更高,更容易精确定位特定事实,信噪比更好,处理速度更快,然而也可能缺乏足够的上下文供 LLM 完全理解或生成全面的答案;

  • 较大切片:提供更多上下文,可能带来更好的生成质量和连贯性。但可能会稀释检索的相关性信号,包含更多噪声,消耗更多 LLM 上下文窗口;

  • 切片重叠在相邻切片的开头重复前一个切片末尾的一定数量的文本,确保切片边界之间的上下文连续性,防止当一个概念跨越多个切片时丢失信息或意义,但过多的重叠会增加冗余和存储成本。

四、数据增强

除了基本的切分之外,还可以采用多种技术来丰富数据,以提高检索准确性并为 LLM 提供更好的上下文。这些技术通常涉及从切片本身或关于切片生成额外信息,缺点是增加了预处理成本。

丰富元数据。元数据是描述文档/切片的附加信息,例如来源、作者、创建/修改日期、关键词、主题、章节标题、页码、URL、内容类型标签。可以通过手动、基于规则或使用 NLP 技术(如命名实体识别 NER、主题建模)自动提取元数据;

语义增强。为每个切片生成简洁的摘要、为切片生成假设性问答对,语义增强旨在创建更丰富、更符合查询意图的数据表示,从而可能改善对细微信息或以非典型查询方式表达的信息的检索。

知识图谱。将信息表示为实体和关系,识别文本切片中提及的实体(人物、地点、组织、概念)、关系构成的图网络,允许查询针对特定实体来提高检索的精确性。

上下文检索。将特定于切片的解释性上下文,预置到每个切片,保留了切片与其更广泛的文档上下文之间的关系。

这些高级技术代表了从将切片视为孤立的词袋/词元串,转向将其表示为更大、结构化的知识网络中的组件的转变。这允许基于关系和规范实体进行更细致和精确的检索,而不仅仅是基于关键字或表面的语义相似性。

五、系统考量

RAG 数据工程并非静态的一次性构建。它们是“活的系统”,必须适应新的数据、不断变化的数据格式以及扩展需求。这从一开始就需要一种优先考虑适应性和演进式设计的架构方法。知识库很少是静态的;新文档会不断添加,现有文档会更新或过时。

  • 可扩展性:数据工程必须能够处理不断增长的数据量和更新频率;

  • 可维护性:清晰的代码、模块化设计、良好的文档和稳健的错误处理对于日益复杂的流程至关重要;

  • 模块化:将加载、清洗、切分、增强等流程设计为一系列独立的、可互换的模块有助于更轻松地更新、测试和试验不同组件;

  • 运营效率:自动化流程、实施稳健的日志记录和监控以及优化资源利用有助于提高运营效率和控制成本。

许多核心索引步骤(如嵌入质量或切分策略的“可检索性”)的“黑箱”特性,使得必须通过间接评估来衡量其有效性。它们的有效性通常通过下游检索任务的性能来推断。这使得评估变得复杂且具有迭代性。建立一个监控、评估和迭代优化数据工程的框架也很关键:

  • 监控:跟踪流程健康状况、处理时间、资源使用情况和错误率;

  • 数据质量指标:(隐式)噪声水平、去重率、规范化一致性;

  • 切分效果:上下文相关性、答案相关性、忠实度、切片内的语义连贯性、切片大小分布;

  • 元数据影响:评估元数据过滤器是否提高检索精度,或元数据丰富是否带来更好的上下文关联;

  • 迭代优化:利用评估结果改进数据策略(切分参数、元数据提取规则),这是一个持续的反馈循环。

六、总结

许多RAG系统挑战的根源在于将数据预处理视为简单的步骤序列,而不是一个复杂的软件工程和数据管理问题,后者需要严格的设计、测试和维护。识别并规避 RAG 索引设计与执行中的常见陷阱是有必要的:

  • 忽视数据质量:这是最常见的陷阱。未能严格清洗、规范化和去重数据会导致索引充满噪声且不可靠;

  • 切分策略不当:选择了不适合数据类型或查询模式的切分策略;

  • 元数据不足或不正确:缺失有价值的元数据或元数据不准确/不一致会妨碍过滤和上下文关联;

  • 缺乏迭代评估和优化:将数据处理视为一次性工程,而不是持续改进的过程;

  • 可扩展性瓶颈:设计的流程无法随数据量或更新频率的增加而扩展;

  • 忽视成本影响:实施过于复杂或计算密集型的数据处理过程,而未考虑成本效益权衡。

最后,回到索引这个模块,RAG 索引的未来趋势是更加动态、多模态和智能化。当前的 RAG 索引主要是一个离线的批处理过程。未来的趋势指向“实时 RAG”,需要对索引进行动态更新。“多模态内容”意味着索引需要处理图像、视频、音频,而不仅仅是文本。结合了关键字、语义和图搜索的“混合模型”意味着更复杂、多层面的索引。这些趋势共同指向一个与检索甚至生成阶段更深度集成、更自动化,并且能够处理数据类型和语义关系方面更大复杂性的索引阶段。这将进一步放大 RAG 中数据工程的重要性和难度。

END

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询