检索增强生成(Retrieval-Augmented Generation, RAG)正在改变大型语言模型(LLMs)利用外部知识的方式。问题在于许多开发者误解了 RAG 的实际作用。他们关注存储在向量数据库中的文档,并认为所有的“魔法”始于此、终于此,但这完全是错误的。索引和检索根本不是一回事。
索引在于你如何选择表示的知识。检索在于模型可以看到哪些部分的知识。一旦你了解到了这个差异,整个思路就会很清晰,就会明白自己对模型的推理、速度和基础性有多大的控制权。
什么是 RAG 索引?
RAG 索引是检索的基础。它是将原始知识转化为可经由相似性查询搜索的数值数据的过程。这些数值数据被称为嵌入(embeddings),嵌入捕获的是含义,而不仅仅是表面的文本。
可以将其视为构建一个可搜索的知识库语义地图。每个知识块、摘要或查询变体都成为地图上的一个点。地图组织得越好,当用户提问时,你的检索器就能越好地识别出相关的知识。
如果你的索引出了问题,例如知识块太大、嵌入捕捉到了噪音,或者数据的表示没有反映用户的意图,那么再好的 LLM 也帮不了你多少。检索的质量始终取决于数据索引的有效性,而不是你的机器学习模型有多优秀。
为什么它很重要?
你检索到的内容并不一定是你索引的内容。你的 RAG 系统的力量在于你的索引能否有效地反映含义而非文本。索引明确了你的检索器看待知识的框架。
当你将索引策略与你的数据和用户需求相匹配时,检索会变得更精确,模型将减少幻觉,用户将获得准确的补全。一个设计良好的索引能将 RAG 从一个检索管道转变为一个真正的语义推理引擎。
真正有效的 RAG 索引策略
假设我们有一篇关于 Python 编程的文档:
文档是一种多功能编程语言,广泛应用于数据科学、机器学习和开发。它支持多种编程范式,并拥有、和等丰富的库生态系统。
现在,让我们探讨何时有效地使用每种 RAG 索引策略,以及如何为构建高性能检索系统而实现这些策略。
1. 块索引(Chunk Indexing)
这是大多数 RAG 管道的起点。你将大型文档拆分成更小的、语义连贯的块,并使用某个嵌入模型对每个块进行嵌入。然后,这些嵌入被存储在一个向量数据库中。
示例代码:
# 1. Chunk Indexing def chunk_indexing(document, chunk_size=100): words = document.split() chunks = [] current_chunk = [] current_len = 0 for word in words: current_len += len(word) + 1# +1 for space current_chunk.append(word) if current_len >= chunk_size: chunks.append(" ".join(current_chunk)) current_chunk = [] current_len = 0 if current_chunk: chunks.append(" ".join(current_chunk)) chunk_embeddings = [embed(chunk) for chunk in chunks] return chunks, chunk_embeddings chunks, chunk_embeddings = chunk_indexing(doc_text, chunk_size=50) print("Chunks:\n", chunks)核心逻辑是将文档按字数或字符数分割成多个块,然后对每个块生成嵌入向量。
最佳实践:
- 对于短篇文本,始终将块保持在200-400 个 Token左右;对于长篇技术内容,可保持在500-800 个 Token。
- 确保避免在句中或段落中间进行分割,使用逻辑性的、语义上的断点以获得更好的分块效果。
- 最好使用20-30% 的重叠窗口,以确保在边界处不会丢失上下文。
权衡:块索引是简单且通用的索引方式。然而,更大的块可能会损害检索精度,而更小的块可能会分散上下文,并用不连贯的片段使 LLM 感到不知所措。
2. 子块索引(Sub-chunk Indexing)
子块索引是在块索引基础上进行精炼的一层。在嵌入正常知识块时,你会将知识块进一步划分成更小的子块。在进行检索时,你将子块与查询进行比较,一旦子块匹配你的查询,完整的父块就会作为输入传递给 LLM。
这种方法有效的原因:子块使你能够以一种更精确、更微妙、更准确的方式进行搜索,同时保留了你进行推理所需的大上下文。例如,你可能有一篇长篇研究文章,而该文章中某一部分内容的子块可能就是长段落中某个公式的解释,从而提高了精度和可解释性。
# 2. Sub-chunk Indexingdef sub_chunk_indexing(chunk, sub_chunk_size=25): words = chunk.split() sub_chunks = [] current_sub_chunk = [] current_len = 0 for word in words: current_len += len(word) + 1 current_sub_chunk.append(word) if current_len >= sub_chunk_size: sub_chunks.append(" ".join(current_sub_chunk)) current_sub_chunk = [] current_len = 0 if current_sub_chunk: sub_chunks.append(" ".join(current_sub_chunk)) return sub_chunks# Sub-chunks for first chunk (as example)sub_chunks = sub_chunk_indexing(chunks[0], sub_chunk_size=30)sub_embeddings = [embed(sub_chunk) for sub_chunk in sub_chunks]print("Sub-chunks:\n", sub_chunks)何时使用:对于每个段落中包含多个不同观点的数据集会很有优势;例如,知识库、教科书、研究文章等会是理想的选择。
权衡:由于嵌入有所重叠,预处理和存储成本略高,但它在查询和内容之间的对齐方面有实质性的提升。
3. 查询索引(Query Indexing)
在查询索引的情况下,原始文本不会被直接嵌入。相反,我们会生成针对每个知识块的几个假想问题,然后嵌入这些问题文本。这样做部分是为了弥合用户提问方式与文档描述事物方式之间的语义鸿沟。
例如,如果你的知识块说:“LangChain 拥有用于构建 RAG 管道的实用工具”
模型会生成类似这样的查询:
- 我如何在 LangChain 中构建 RAG 管道?
- LangChain 有哪些用于检索的工具?
# 3. Query Indexing - generate synthetic queries related to the chunkdef generate_queries(chunk): # Simple synthetic queries for demonstration queries = [ "What is Python used for?", "Which libraries does Python support?", "What paradigms does Python support?" ] query_embeddings = [embed(q) for q in queries] return queries, query_embeddingsqueries, query_embeddings = generate_queries(doc_text)print("Synthetic Queries:\n", queries)然后,当任何真实用户提出类似问题时,检索将直接命中其中一个索引的查询。
最佳实践:
- 在编写索引查询时,建议使用 LLM 为每个知识块生成3-5 个查询。
- 你也可以对所有相似的问题进行去重或聚类,以缩小实际索引的规模。
何时使用:
- 问答系统或大多数用户交互都由自然语言问题驱动的聊天机器人。
- 用户很可能会询问“是什么”、“如何做”或“为什么”等类型查询的搜索体验。
权衡:虽然合成扩展增加了预处理时间和空间,但它为面向用户的系统提供了有意义的检索相关性提升。
4. 摘要索引(Summary Indexing)
摘要索引允许你在嵌入之前,将资料片段重构成更小的摘要。你将完整的原始内容保留在另一个位置,然后在摘要版本上执行检索。
这种方法的好处:结构化、密集或重复的源材料(例如电子表格、政策文件、技术手册)通常是直接从原始文本嵌入会捕获噪音的材料。摘要抽象掉了不那么相关的表面细节,对于嵌入而言语义上更有意义。
例如:原始文本说:“2020 年至 2025 年的温度读数范围为 22 至 42 摄氏度,异常归因于厄尔尼诺现象。” 摘要将是:“年度温度趋势(2020-2025),涉及厄尔尼诺相关的异常现象。” 摘要表示形式将焦点集中在概念上。
示例代码
# 4. Summary Indexingdef summarize(text): # Simple summary for demonstration (replace with an actual summarizer for real use) if"Python"in text: return"Python: versatile language, used in data science and web development with many libraries." return textsummary = summarize(doc_text)summary_embedding = embed(summary)print("Summary:", summary)何时使用:
- 处理结构化数据(表格、CSV、日志文件)。
- 技术性或冗长的内容,如果使用原始文本嵌入,嵌入效果会不佳。
权衡:如果摘要过于抽象,可能会有损失细微差别/事实准确性的风险。对于特定领域(尤其是法律、金融等)的关键研究,应链接到原始文本进行参考。
5. 分层索引(Hierarchical Indexing)
分层索引将信息组织成多个不同级别:文档、章节、段落、子段落。你分阶段进行检索,从广泛的介绍开始,逐步缩小到特定的上下文。顶层组件检索相关文档的章节,下一层检索在这些检索到的文档章节内的特定上下文的段落或子段落。
这意味着什么?分层检索可以减少系统中的噪音,并且在你需要控制上下文大小时非常有用。当处理大量文档且无法一次性全部拉取时,这尤其有用。它还可以提高后续分析的可解释性,因为你可以知道是哪个文档的哪个部分促成了最终答案。示例代码
# 5. Hierarchical Indexing # Organize document into levels: document -> chunks -> sub-chunks hierarchical_index = { "document": doc_text, "chunks": chunks, "sub_chunks": {chunk: sub_chunk_indexing(chunk) for chunk in chunks} } print("Hierarchical index example:") print(hierarchical_index)最佳实践:
- 使用多个嵌入级别或嵌入与关键词搜索的组合。例如,最初仅使用 BM25 检索文档,然后使用嵌入更精确地检索那些相关的块或组件。
何时使用:
- 拥有数千份文档的企业级 RAG。
- 从书籍、法律档案或技术 PDF 等长篇来源中检索。
权衡:由于需要多个检索级别,复杂性增加。还需要额外的存储和预处理来进行元数据/摘要。由于多步骤检索,查询延迟增加,不适合大型非结构化数据。
6. 混合索引(Hybrid Indexing / Multi-Modal)
知识不仅仅存在于文本中。在混合索引形式中,RAG 通过做两件事来处理多种形式的数据或模态:检索器使用针对每种可能模态专门化或调优的不同编码器生成的嵌入。然后,它从每个相关的嵌入中获取结果,并使用评分策略或**晚期融合(late-fusion)**方法将它们结合起来生成响应。
以下是其用法的示例:
- 使用CLIP 或 BLIP处理图像和文本标题。
- 使用CodeBERT 或 StarCoder嵌入来处理代码。
示例代码
# 6. Hybrid Indexing (example with text + image)# Example text and dummy image embedding (replace embed_image with actual model)def embed_image(image_data): # Dummy example: image data represented as length of string (replace with CLIP/BLIP encoder) return [len(image_data) / 1000]text_embedding = embed(doc_text)image_embedding = embed_image("image_bytes_or_path_here")print("Text embedding size:", len(text_embedding))print("Image embedding size:", len(image_embedding))何时使用混合索引:
- 处理包含图像或图表的技术手册或文档。
- 多模态文档或支持文章。
- 产品目录或电子商务。
权衡:检索逻辑和存储模型更为复杂,但在响应中能提供更丰富的上下文理解,并在领域内具有更高的灵活性。
成功的 RAG 系统取决于针对数据类型和待回答问题的适当索引策略。索引指导着检索器找到什么,以及语言模型将以什么为基础进行生成,使其成为超越检索的关键基础。你使用的索引类型可能是块、子块、查询、摘要、分层或混合索引,并且该索引应遵循数据中存在的结构,这将提高相关性并消除噪音。精心设计的索引过程将减少幻觉,并提供一个准确、值得信赖的系统。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。