简介
本文全面介绍了RAG系统中的文本切块策略,包括21种从基础到进阶的方法,如朴素切块、固定大小切块、滑动窗口切块、基于句子/段落/文档结构切块、基于关键词/实体/主题/语义切块等。每种策略都详细分析了应用场景、优缺点并提供代码实现,最后还介绍了混合切块策略作为综合方案。掌握这些切块技术是构建高效RAG系统的关键,能显著提升检索质量和生成效果。

在深入每一种切块策略之前,需要先把基础工具和模拟数据准备好。
import reimport nltkfrom nltk.tokenize import sent_tokenize, word_tokenizefrom transformers import AutoTokenizerfrom sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.metrics.pairwise import cosine_similarityimport numpy as np# 确保已经下载了nltk的punkt分词器try: nltk.data.find('tokenizers/punkt')except nltk.downloader.DownloadError: nltk.download('punkt')# 模拟一些文本数据sample_text_long = """RAG(Retrieval-Augmented Generation)是一种结合了检索和生成能力的AI技术。它的核心思想是,当大语言模型(LLM)需要回答问题时,它不再是凭空生成答案,而是首先从一个庞大的知识库中检索出最相关的上下文信息。这些检索到的信息随后会被作为输入的一部分,提供给LLM,LLM再基于这些“额外”的知识来生成最终的回答。这种方式能够显著减少模型“胡说八道”(幻觉)的现象,并使得模型能够回答特定领域的问题,因为它的知识不再仅仅局限于训练时的数据。在实际应用中,RAG系统涉及到多个关键组件。首先是**数据摄取和预处理**,这通常包括将原始文档(如PDF、网页、数据库记录等)清洗、解析,并转换为适合存储和检索的格式。接着是**切块(Chunking)**,这是RAG流程中至关重要的一步。切块的目的是将长文本分割成较小的、易于管理和检索的片段。切块策略的选择直接影响检索的质量。如果切块过大,可能包含太多无关信息,增加LLM处理的负担;如果切块过小,则可能丢失上下文,导致LLM无法理解完整语义。然后是**嵌入(Embedding)**,将文本切块转换为高维向量,这些向量能够捕捉文本的语义信息。接着是**向量数据库(Vector Database)**,用于高效存储和检索这些嵌入向量。当用户提出问题时,**检索器(Retriever)**会根据用户问题的嵌入向量,在向量数据库中查找最相似的文本切块。最后,**生成器(Generator)**,也就是LLM,会结合用户的原始问题和检索到的相关上下文信息,生成最终的答案。整个RAG流程的优化是一个迭代的过程,需要不断地调整各个环节,才能达到最佳效果。"""sample_text_structured = """# 第一章 引言## 1.1 RAG的魅力RAG技术有效解决了大模型幻觉问题。## 1.2 本文目的本文将深入探讨RAG中的切块策略。# 第二章 切块基础## 2.1 朴素切块这是一种简单的切块方法。## 2.2 固定大小切块我们将会看到固定大小切块的实现。---**Note:** 本文档旨在提供RAG切块的全面指南。"""sample_text_with_tables = """这是一段关于公司业绩的文本。| Month | Sales | Profit ||---|---|---|| Jan | 100 | 20 || Feb | 120 | 25 || Mar | 110 | 22 |以上是第一季度的财务数据。下面是团队成员信息:| Name | Role ||---|---|| Alice | Engineer || Bob | Designer |"""sample_text_mixed_format = """这是一个段落。它包含一些重要的信息,需要被完整保留。1. 这是一个列表项。2. 这是另一个列表项。 * 子项1 * 子项2## 重要提示请注意以下表格数据:| Item | Quantity | Price ||---|---|---|| Apple | 10 | 1.0 || Banana | 5 | 0.5 |"""
一、基础篇:简单粗暴,但有时特好用!
这些方法操作起来贼简单,对付特定数据效果还真不赖。
1. 朴素切块(Naive Chunking):
- 场景使用: 当你的文本天然就以行为单位组织,并且每一行都承载一个相对完整、独立的想法时,这种方法特别高效。比如,会议纪要、聊天记录、产品FAQ列表、项目待办事项、带有明确换行符的笔记等等。
- 优点:
- 实现简单: 几乎是所有切块方法中最容易实现的,一行代码搞定。
- 速度快: 处理大量文本时效率极高,不会引入复杂计算。
- 语义清晰(特定场景下): 如果文本就是按行划分语义,那么切出来的块语义很纯粹。
- 缺点:
- 上下文丢失风险: 如果一句话或一个完整想法跨越多行,这种方法会直接将其“腰斩”,导致重要的上下文信息丢失。
- 块大小不均: 每行长度不一,导致切出来的块大小差异大,可能超出LLM的token限制或过短导致信息不足。
- 不适用于连续文本: 对于小说、论文、博客等段落式的连续文本,效果通常很差。
def naive_chunking(text): """ 按行分割文本。 """ return text.split('\n')print("--- 1. 朴素切块 ---")chunks_naive = naive_chunking(sample_text_long)for i, chunk in enumerate(chunks_naive[:5]): # 只打印前5个示例 print(f"Chunk {i+1}: '{chunk.strip()}'")print("...")

2. 固定大小切块(Fixed-size/Fixed Window Chunking):
- 场景使用: 面对“一锅粥”式的原始、混乱文本数据时,比如从PDF中OCR(光学字符识别)出来的、没有标点符号或格式的文本,或者大型的日志文件、数据流。当你对文本结构一无所知,又需要快速将数据拆分成固定大小的片段以适应模型输入时,这是最直接的选择。
- 优点:
- 简单且快速: 实现起来也很简单,切割效率高。
- 易于管理: 每个块的大小固定,便于批量处理和模型输入管理,尤其是在LLM有严格token限制时。
- 兜底策略: 在其他结构化切块方法都失效时,可以作为一种普适的兜底方案。
- 缺点:
- 上下文被截断: 最大的缺点是它会毫不留情地在任何位置切开文本,常常会把句子、段落甚至完整的想法截断,导致语义不完整或上下文信息流失严重。
- 信息冗余: 在处理结构化文本时,一个块可能包含多余的信息,或与下一个块的内容高度重叠。
def fixed_size_chunking(text, chunk_size, overlap=0): """ 按固定大小切块,可选择重叠。 chunk_size: 每个切块的字符数 overlap: 重叠的字符数 """ chunks = [] text_len = len(text) # 注意:这里 i 的步长是 chunk_size - overlap,确保重叠 for i in range(0, text_len, chunk_size - overlap): chunk = text[i:i + chunk_size] chunks.append(chunk) if i + chunk_size >= text_len: # 防止最后一个切块不完整,但又跳过重叠部分 break return chunksprint("\n--- 2. 固定大小切块 ---")chunks_fixed = fixed_size_chunking(sample_text_long, chunk_size=200, overlap=0)for i, chunk in enumerate(chunks_fixed[:3]): print(f"Chunk {i+1}: '{chunk.strip()}'")print("...")

3. 滑动窗口切块(Sliding Window Chunking):
- 场景使用: 当你的文本内容上下文关联紧密、信息连续性强时,比如小说、叙事性报告、详细的技术文档、自由流动的随笔等。它能有效缓解固定大小切块中上下文被截断的问题,尤其适用于LLM需要更广阔语境才能准确理解和生成回答的场景。
- 优点:
- 保持上下文: 通过块之间的重叠,能有效保留跨块的上下文信息,降低LLM理解时出现“断层”的风险。
- 提高检索精度: 检索时,即使查询命中一个重叠部分,也能带回包含更完整上下文的块。
- 适用于无结构文本: 对没有明确标题、段落分隔的文本也能较好地处理。
- 缺点:
- 冗余度增加: 重叠部分会增加存储和处理的冗余,导致向量数据库更大,嵌入和检索成本增加。
- 计算开销: 更多的块意味着更多的嵌入计算和检索操作。
- 参数调优:
chunk_size和overlap的比例需要根据实际数据和LLM的特性进行仔细调优,否则可能效果不佳。
def sliding_window_chunking(text, chunk_size, overlap): """ 滑动窗口切块,每个切块与前一个重叠。 chunk_size: 每个切块的字符数 overlap: 重叠的字符数 """ chunks = [] text_len = len(text) start = 0 while start < text_len: end = min(start + chunk_size, text_len) chunk = text[start:end] chunks.append(chunk) if end == text_len: break start += (chunk_size - overlap) return chunksprint("\n--- 3. 滑动窗口切块 ---")chunks_sliding = sliding_window_chunking(sample_text_long, chunk_size=200, overlap=50)for i, chunk in enumerate(chunks_sliding[:3]): print(f"Chunk {i+1}: '{chunk.strip()}'")print("...")

这份完整版的大模型 AI 学习和面试资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

4. 基于句子切块(Sentence-based Chunking):
- 场景使用: 最适合语法结构完整、句子独立承载完整语义的文本,如新闻报道、博客文章、产品说明书、法律条文、论文摘要、结构化的文档或纯文本数据。它可以作为更复杂切块策略的“第一步”,得到粒度最小的语义单元。
- 优点:
- 语义完整性高: 每个切块都是一个完整的句子,通常能保证最小的语义单元不被破坏。
- 粒度精细: 提供了最细粒度的信息,便于后续的重排、过滤或更复杂的组合操作。
- 易于理解: LLM处理完整句子时,理解成本更低。
- 缺点:
- 上下文不足: 单个句子可能缺乏足够的上下文来完全理解其含义,尤其是在上下文分散于多个句子的复杂概念中。
- 数量庞大: 对于长文档,句子切块会生成大量小块,增加存储和检索的负担。
- 标点依赖: 严重依赖文本中的标点符号来识别句子边界,如果文本质量差(如OCR错误、缺乏标点),效果会大打折扣。
def sentence_based_chunking(text): """ 使用nltk进行句子级别的切块。 """ return sent_tokenize(text)print("\n--- 4. 基于句子切块 ---")chunks_sentence = sentence_based_chunking(sample_text_long)for i, chunk in enumerate(chunks_sentence[:5]): print(f"Chunk {i+1}: '{chunk.strip()}'")print("...")

5. 基于段落切块(Paragraph-based Chunking):
- 场景使用: 适用于传统意义上以段落为单位组织内容的文档,如博客文章、报告、论文、书籍章节等。当你想在保持一定上下文量的同时,又能根据文章的自然逻辑进行分割时,这是非常好的选择。
- 优点:
- 上下文适中: 比句子切块能提供更多的上下文,通常一个段落能表达一个完整的想法或论点。
- 结构清晰: 尊重文档原有的段落结构,切块结果更符合人类阅读习惯。
- 实现简单: 通常通过双换行符(
\n\n)或简单的文本解析就能实现。
- 缺点:
- 段落长度不一: 不同段落的长度差异可能很大,有些段落可能过长超出LLM的token限制,有些可能过短信息量不足。
- 上下文跨段落: 如果一个逻辑概念跨越多个段落,可能会被切断。
- 格式依赖: 依赖于文本中正确的段落分隔符,如果原始文本格式混乱,效果会受影响。
def paragraph_based_chunking(text): """ 按双换行符分割文本(通常表示段落)。 """ # 使用正则表达式匹配一个或多个换行符,并移除空字符串 paragraphs = [p.strip() for p in re.split(r'\n{2,}', text) if p.strip()] return paragraphsprint("\n--- 5. 基于段落切块 ---")chunks_paragraph = paragraph_based_chunking(sample_text_long)for i, chunk in enumerate(chunks_paragraph[:3]): print(f"Chunk {i+1}: '{chunk.strip()}'")print("...")

6. 基于页面切块(Page-based Chunking):
- 场景使用: 主要针对具有明确分页结构的文档,如PDF文档、扫描的纸质文档、幻灯片(PPT)、书籍等。当你的检索结果需要引用到页码,或者文档的布局(如图片、表格分布)与页面的逻辑高度相关时,这种方法就显得尤为重要。
- 优点:
- 保留原始布局信息: 每个切块对应一个物理页面,能完整保留该页面的所有信息和布局,方便在原始文档中定位。
- 易于引用: 直接关联页码,便于用户或LLM引用原始出处。
- 简化处理: 对于已分页的文档,省去了复杂的语义分析。
- 缺点:
- 上下文被截断: 如果一个概念、句子或表格跨越页面,则会被无情截断,导致语义不完整。
- 块大小差异大: 不同页面的内容量差异可能很大,导致切块大小不均,影响LLM处理效率。
- 依赖于文档格式: 必须是已经分页的文档才能使用,对于纯文本或无分页概念的文档不适用。
def page_based_chunking(text, page_delimiter="---PAGE_BREAK---"): """ 模拟页面切块,假设文本中存在页面分隔符。 实际应用中需要从PDF等文件读取。 """ # 模拟一个多页文本 multi_page_text = ( "这是第一页的内容。\n一些重要的信息在这里。\n" + page_delimiter + "\n" + "这是第二页的内容。\n继续重要的讨论。\n" + page_delimiter + "\n" + "第三页是总结。\n全文到此结束。" ) return [p.strip() for p in multi_page_text.split(page_delimiter) if p.strip()]print("\n--- 6. 基于页面切块 ---")chunks_page = page_based_chunking(sample_text_long) # 使用模拟文本for i, chunk in enumerate(chunks_page): print(f"Page {i+1}:\n'{chunk}'")

7. 结构化切块(Structured Chunking):
- 场景使用: 当你处理的是具有明确内部结构的数据时,如日志文件(按日志条目)、JSON数据(按字段)、XML/HTML文档(按标签)、Markdown文档(按标题或特定元素)、CSV文件(按行或特定列)。这种方法能确保切块结果严格遵循数据本身的逻辑和层级。
- 优点:
- 语义完整性强: 每个切块都对应数据中的一个逻辑单元,语义上高度完整和聚焦。
- 准确性高: 不依赖模糊的文本特征,而是基于确定的结构规则,切块准确率高。
- 便于信息抽取: 切块后可以直接提取结构化信息,方便后续的知识图谱构建或特定字段检索。
- 缺点:
- 依赖于结构: 如果数据结构不一致或有错误,切块会失败。
- 解析复杂性: 需要针对不同结构编写特定的解析逻辑,增加了实现的复杂性。
- 通用性差: 每种结构需要一套独立的切块规则,无法通用。
def structured_chunking(text): """ 根据Markdown标题结构进行切块。 """ chunks = [] # 匹配Markdown标题,同时捕获标题和其后的内容 # 注意:这里会把每个标题下的内容切成一个块 sections = re.split(r'^(#+ .*)$', text, flags=re.MULTILINE) current_heading = "" current_content = [] # 第一个元素通常是空字符串或标题之前的内容 if sections[0].strip(): chunks.append(sections[0].strip()) for i in range(1, len(sections)): part = sections[i].strip() if part.startswith('#'): # 这是一个标题 if current_content: # 如果有之前收集的内容,先作为一个块 chunks.append("\n".join(current_content).strip()) current_content = [] # 重置 current_heading = part current_content.append(current_heading) # 将标题也包含在块内 else: # 这是一个标题下的内容 current_content.append(part) if current_content: # 添加最后一个块 chunks.append("\n".join(current_content).strip()) return [chunk for chunk in chunks if chunk] # 过滤空块print("\n--- 7. 结构化切块 ---")chunks_structured = structured_chunking(sample_text_structured)for i, chunk in enumerate(chunks_structured): print(f"Chunk {i+1}:\n'{chunk}'")

8. 基于文档结构切块(Document-Based Chunking):
- 场景使用: 适用于具有清晰的章节、小节和标题层级的文档,例如技术手册、教科书、研究论文、长篇报告、企业知识库文档。当你希望用户能够根据文档的自然逻辑结构进行检索,或者LLM需要理解某个特定章节的完整语境时,这种方法是首选。它也是实现分层切块(Hierarchical Chunking)的基础。
- 优点:
- 高度贴合文档原意: 切块结果与文档的逻辑结构保持一致,非常自然。
- 上下文丰富: 每个块通常包含一个完整的章节或小节内容,提供足够的上下文。
- 便于导航和理解: 用户和LLM都能清晰地知道信息所属的章节位置。
- 缺点:
- 依赖文档格式: 需要文档有明确的标题或章节标记,对于非结构化文本无效。
- 解析复杂: 需要更智能的解析器来识别不同级别的标题和其对应的内容。
- 块大小不均: 不同章节的长度可能差异巨大,导致一些块过大。
def document_based_chunking(text): """ 基于文档的自然结构(如Markdown的章节和子章节)进行切块。 这里我们将捕获顶级标题下的所有内容作为一个块,直到下一个同级或更高级的标题。 """ chunks = [] lines = text.split('\n') current_chunk_lines = [] for line in lines: if line.startswith('#'): # 匹配任何级别的标题 if current_chunk_lines: # 如果当前块有内容,就结束并添加 chunks.append("\n".join(current_chunk_lines).strip()) current_chunk_lines = [] current_chunk_lines.append(line) # 将标题作为新块的开始 else: current_chunk_lines.append(line) if current_chunk_lines: # 添加最后一个块 chunks.append("\n".join(current_chunk_lines).strip()) return [chunk for chunk in chunks if chunk]print("\n--- 8. 基于文档结构切块 ---")chunks_doc_struct = document_based_chunking(sample_text_structured)for i, chunk in enumerate(chunks_doc_struct): print(f"Chunk {i+1}:\n'{chunk}'")

二、进阶篇:聪明切分,解决复杂问题!
这些方法需要更多的策略和算法支持,能应对更复杂的场景。
9. 基于关键词切块(Keyword-based Chunking):
- 场景使用: 当文档没有明确的标题结构,但特定的关键词或短语总是标志着新主题或重要信息的开始时,这种方法非常有效。比如,法律合同中的“WHEREAS”、“THEREFORE”,医疗记录中的“Diagnosis:”、“Treatment:”,或者产品说明书中的“Warning:”、“Troubleshooting:”。
- 优点:
- 聚焦特定信息: 能有效地将包含特定关键词的重要信息切分出来。
- 规则灵活: 可以根据业务需求自定义关键词列表。
- 适用于半结构化文本: 对缺乏严格结构,但有固定标记的文本很有用。
- 缺点:
- 关键词依赖: 严重依赖预定义的关键词,如果关键词选择不当或缺失,切块效果会很差。
- 上下文丢失: 关键词可能出现在句子中间,切块时可能导致句子被截断。
- 人工成本: 确定有效的关键词列表可能需要人工分析和迭代。
def keyword_based_chunking(text, keywords): """ 在指定关键词处进行切块。 """ chunks = [] # 构建正则表达式,匹配所有关键词并保留关键词本身 # 使用非捕获组 (?:...) 结合 | 运算符 pattern = '|'.join(re.escape(k) for k in keywords) parts = re.split(f'({pattern})', text) # 使用捕获组保留分隔符 current_chunk = "" for part in parts: if part.strip() in keywords: # 如果当前部分是关键词 if current_chunk.strip(): # 将之前的累积作为新块 chunks.append(current_chunk.strip()) current_chunk = part # 关键词作为新块的开始 else: current_chunk += part if current_chunk.strip(): # 添加最后一个块 chunks.append(current_chunk.strip()) return [chunk for chunk in chunks if chunk] # 过滤空块print("\n--- 9. 基于关键词切块 ---")keywords_for_chunking = ["Note:", "首先是", "接着是"]chunks_keyword = keyword_based_chunking(sample_text_long + sample_text_structured, keywords_for_chunking)for i, chunk in enumerate(chunks_keyword): print(f"Chunk {i+1}:\n'{chunk}'")

10. 基于实体切块(Entity-based Chunking):
- 场景使用: 适用于文档中特定实体(人名、地名、公司、产品等)是核心信息的场景,如新闻文章(围绕特定人物或事件)、法律合同(围绕当事人)、医学报告(围绕患者、疾病)、电影剧本(围绕角色)。当你希望检索结果能聚焦于某个实体及其相关描述时,这种方法能提供高度相关的上下文。
- 优点:
- 高度聚焦: 每个切块都围绕一个或一组实体,保证了信息的强相关性。
- 提升检索精度: 用户查询某个实体时,能精准召回所有与该实体相关的描述。
- 知识图谱构建: 为后续构建知识图谱提供了结构化的基础。
- 缺点:
- 依赖NER模型: 需要高质量的命名实体识别(NER)模型,模型性能直接影响切块效果。
- 计算开销大: NER处理本身有计算成本,且切块逻辑可能更复杂。
- 通用性受限: 对于没有明显实体的文本,效果不佳。
# pip install spacy# python -m spacy download en_core_web_smimport spacytry: nlp = spacy.load("en_core_web_sm")except OSError: print("Downloading spacy model 'en_core_web_sm'...") spacy.cli.download("en_core_web_sm") nlp = spacy.load("en_core_web_sm")def entity_based_chunking(text): """ 使用NER模型识别实体,并围绕实体聚合文本。 这里为了简化,我们找到实体所在句子,并以句子为单位聚合。 """ doc = nlp(text) entities = {} # {entity_text: [sentences containing this entity]} for sent in doc.sents: found_entities_in_sent = False for ent in sent.ents: if ent.label_ in ["PERSON", "ORG", "GPE", "PRODUCT"]: # 关注人、组织、地理、产品等实体 if ent.text notin entities: entities[ent.text] = [] entities[ent.text].append(sent.text.strip()) found_entities_in_sent = True ifnot found_entities_in_sent: # 如果句子没有实体,作为独立块或添加到“无实体”块 if"NO_ENTITY"notin entities: entities["NO_ENTITY"] = [] entities["NO_ENTITY"].append(sent.text.strip()) # 将字典转换为列表,每个实体或无实体组一个块 chunks = [] for entity, sents in entities.items(): chunk_content = f"Related to {entity}:\n" + "\n".join(list(set(sents))) # 使用set去重 chunks.append(chunk_content) return chunksprint("\n--- 10. 基于实体切块 ---")sample_ner_text = "Apple公司发布了新的iPhone 15。Tim Cook在发布会上强调了其强大的A17芯片。用户可以在纽约的Apple Store购买。"chunks_entity = entity_based_chunking(sample_ner_text)for i, chunk in enumerate(chunks_entity): print(f"Chunk {i+1}:\n'{chunk}'")

这份完整版的大模型 AI 学习和面试资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

11. 基于Token切块(Token-based Chunking):
- 场景使用: 主要用于需要精确控制LLM输入token数量的场景,比如LLM有严格的上下文窗口限制(token limit),或者你希望最大化单个token的使用效率。它通常作为其他切块方法(如句子切块)的补充或后处理步骤,以确保最终的块大小符合LLM要求,同时避免语义被完全破坏。
- 优点:
- 精确控制块大小: 能够严格控制每个块的token数量,避免超出LLM的输入限制。
- 适用于非结构化文本: 对于没有明确语义结构(如标题、段落)的文本,可以作为一种有效的切块方式。
- 与LLM兼容性好: 直接以LLM理解的token为单位进行切块,减少了LLM处理时的额外计算。
- 缺点:
- 语义完整性风险: 和固定大小切块类似,可能在token级别直接截断句子或单词,导致语义不完整。
- 需要与语义策略结合: 单独使用时容易丢失上下文,通常需要与句子切块等语义方法结合,先按语义切小段,再对过长的段落进行token切分。
- 依赖分词器: 切块结果依赖于所选分词器的行为,不同分词器结果可能不同。
# pip install transformersdef token_based_chunking(text, tokenizer_name="bert-base-uncased", max_tokens=128): """ 使用分词器按token数量进行切块。 """ tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) tokens = tokenizer.encode(text, add_special_tokens=False) # 不添加特殊token chunks = [] for i in range(0, len(tokens), max_tokens): chunk_tokens = tokens[i:i + max_tokens] chunk_text = tokenizer.decode(chunk_tokens) chunks.append(chunk_text) return chunksprint("\n--- 11. 基于Token切块 ---")chunks_token = token_based_chunking(sample_text_long, max_tokens=60)for i, chunk in enumerate(chunks_token[:3]): print(f"Chunk {i+1}:\n'{chunk}'")print("...")
12. 基于主题切块(Topic-based Chunking):
- 场景使用: 当你的文档涵盖多个主题,且主题之间有清晰的界限(但可能没有明确的标题或关键词标记),或者你希望每个切块都能高度聚焦于一个单一主题时。比如,一个关于科技趋势的报告可能同时讨论AI、区块链和元宇宙,用主题切块可以确保每个块只包含一个主题的内容。
- 优点:
- 高语义相关性: 每个切块都包含一个或少数几个紧密相关的主题,检索命中后能提供高度聚焦的信息。
- 应对复杂文档: 能够处理主题交织、没有明确结构的长文档。
- 提升检索质量: 减少了切块中的无关信息,提高了召回的精确性。
- 缺点:
- 实现复杂: 需要主题模型(如LDA、NMF)或高级聚类算法,计算成本较高。
- 主题边界模糊: 在主题过渡平滑的文档中,确定清晰的主题边界可能很困难。
- 参数调优: 聚类算法的参数(如主题数量、相似度阈值)需要仔细调优。
# pip install scikit-learndef topic_based_chunking(text, min_sentences_per_topic=3): """ 通过句子相似度模拟主题切块。 将相似的句子聚类成一个主题块。 """ sentences = sent_tokenize(text) if len(sentences) < min_sentences_per_topic: return [text] # 句子太少,无法有效分主题 # 使用TF-IDF向量化句子 vectorizer = TfidfVectorizer().fit(sentences) sentence_vectors = vectorizer.transform(sentences) chunks = [] current_topic_sentences = [sentences[0]] for i in range(1, len(sentences)): # 计算当前句子与当前主题块中所有句子的平均相似度 current_sentence_vector = sentence_vectors[i] # 将当前主题块的句子向量合并 current_topic_vectors = vectorizer.transform(current_topic_sentences) avg_similarity = np.mean(cosine_similarity(current_sentence_vector, current_topic_vectors)) # 如果相似度低于某个阈值,或者当前主题块句子太多,就认为主题切换 # 这里阈值和数量都是启发式的,实际应用中需调优 if avg_similarity < 0.3or len(current_topic_sentences) >= 5: # 假设相似度低于0.3或句子多于5句视为新主题 chunks.append(" ".join(current_topic_sentences)) current_topic_sentences = [sentences[i]] else: current_topic_sentences.append(sentences[i]) if current_topic_sentences: chunks.append(" ".join(current_topic_sentences)) return chunksprint("\n--- 12. 基于主题切块 ---")chunks_topic = topic_based_chunking(sample_text_long)for i, chunk in enumerate(chunks_topic): print(f"Chunk {i+1}:\n'{chunk}'")

13. 表格感知切块(Table-aware Chunking):
- 场景使用: 当你的文档中包含重要的表格数据,并且你希望这些表格能够作为一个完整的语义单元被处理和检索时。这在财务报表、产品规格、统计数据、研究数据等文档中尤为常见。通过将表格独立切块,LLM能更好地理解表格的结构和内容。
- 优点:
- 保留表格结构和完整性: 确保表格作为一个整体,不会被中间切断,方便LLM理解其数据关系。
- 提升表格数据检索: 用户查询表格内容时,能精确召回整个表格。
- 有利于LLM处理: LLM对结构化表格数据(如Markdown或JSON格式)的处理能力通常优于纯文本。
- 缺点:
- 解析复杂: 需要强大的表格解析能力,尤其对于非标准格式的表格或图像中的表格(需要OCR+表格检测)。
- 上下文丢失: 表格周围的文本上下文可能与表格内容紧密相关,但如果表格被单独切块,这种关联可能会被削弱。
- 依赖于格式: 仅适用于能够识别出表格结构的文档。
def table_aware_chunking(text): """ 识别并单独切块表格。将表格内容转换为Markdown格式。 """ chunks = [] # 匹配Markdown表格的正则表达式 # 捕获表格内容,包括表头、分隔线和行 table_pattern = re.compile(r'(\|.*\|\n\|[-: ]+\|\n(?:\|.*\|\n?)+)', re.MULTILINE) last_end = 0 for match in table_pattern.finditer(text): # 添加表格前的内容 pre_table_text = text[last_end:match.start()].strip() if pre_table_text: chunks.append(pre_table_text) # 添加表格本身 chunks.append(match.group(0).strip()) last_end = match.end() # 添加最后一个表格后的内容 post_table_text = text[last_end:].strip() if post_table_text: chunks.append(post_table_text) return [chunk for chunk in chunks if chunk]print("\n--- 13. 表格感知切块 ---")chunks_table_aware = table_aware_chunking(sample_text_with_tables)for i, chunk in enumerate(chunks_table_aware): print(f"Chunk {i+1}:\n'{chunk}'")

14. 内容感知切块(Content-aware Chunking):
- 场景使用: 适用于包含多种内容类型和结构的复杂文档,如网页文章(包含段落、列表、图片、嵌入视频)、学术论文(包含正文、图表、公式、参考文献)、商业报告(包含文字、表格、图示)。它是一种“智能”的切块方法,能根据不同内容的特点采用最合适的分割策略。
- 优点:
- 语义完整性强: 能够根据内容类型灵活调整,最大程度地保持语义完整性,例如段落不被切断,表格保持完整。
- 通用性广: 能够处理混合格式的复杂文档,适应性强。
- 提升检索质量: 每个切块的内容更加聚焦和完整,提高了检索的准确性和LLM的理解能力。
- 缺点:
- 实现复杂: 需要一套复杂的规则引擎来识别和区分不同类型的内容,并应用相应的切块逻辑。
- 性能开销: 解析和识别内容类型可能增加处理时间。
- 规则维护: 随着文档格式的变化,可能需要不断更新和维护切块规则。
def content_aware_chunking(text): """ 根据内容类型(段落、列表、表格、标题等)应用不同的切块规则。 这是一个结合了多种策略的示例。 """ chunks = [] lines = text.split('\n') current_chunk_lines = [] in_table = False for line in lines: stripped_line = line.strip() # 检查是否是表格行(简单的启发式判断) if stripped_line.startswith('|') and'|'in stripped_line[1:]: ifnot in_table: # 如果刚进入表格,先结束前一个非表格块 if current_chunk_lines: chunks.append("\n".join(current_chunk_lines).strip()) current_chunk_lines = [] in_table = True current_chunk_lines.append(line) elif in_table: # 如果在表格中,但当前行不是表格行,则表格结束 if current_chunk_lines: # 添加完整的表格块 chunks.append("\n".join(current_chunk_lines).strip()) current_chunk_lines = [] in_table = False current_chunk_lines.append(line) # 当前行作为新块的开始 elif stripped_line.startswith('#'): # 匹配标题 if current_chunk_lines: # 如果有内容,结束前一个块 chunks.append("\n".join(current_chunk_lines).strip()) current_chunk_lines = [] current_chunk_lines.append(line) # 标题作为新块的开始 elifnot stripped_line and current_chunk_lines: # 空行作为段落分隔符 if current_chunk_lines[-1].strip() != "": # 避免连续空行导致空块 chunks.append("\n".join(current_chunk_lines).strip()) current_chunk_lines = [] else: # 普通文本行 current_chunk_lines.append(line) if current_chunk_lines: # 添加最后一个块 chunks.append("\n".join(current_chunk_lines).strip()) return [chunk for chunk in chunks if chunk['content']]print("\n--- 14. 内容感知切块 ---")chunks_content_aware = content_aware_chunking(sample_text_mixed_format)for i, chunk in enumerate(chunks_content_aware): print(f"Chunk {i+1}:\n'{chunk}'")
15. 上下文切块(Contextual Chunking):
- 场景使用: 当你的知识库内容复杂、主题关联性强,且LLM的上下文窗口足够大,能够容纳额外注入的上下文信息时。这对于金融报告、法律合同、技术规范等需要深入理解文本背后逻辑和关联性的场景非常有用。LLM可以生成关于某个切块的摘要、主题标签或与相关切块的链接,从而丰富每个块的信息。
- 优点:
- 提升理解深度: 通过LLM添加额外上下文,增强了每个切块的语义丰富性,帮助下游LLM更好地理解和推理。
- 降低幻觉: LLM对检索到的信息理解更全面,减少了生成错误答案的风险。
- 灵活适应: LLM可以根据具体需求生成不同类型的上下文信息。
- 缺点:
- 成本高昂: 需要调用LLM进行额外处理,会增加API调用成本和计算延迟。
- token消耗: 添加额外上下文会增加每个切块的token数量,可能更快达到LLM的上下文限制。
- LLM依赖: 效果严重依赖LLM的生成能力和对知识库的理解程度。
def mock_llm_add_context(chunk_text, knowledge_base_overview): """ 模拟LLM为每个切块添加相关上下文。 在实际中,这需要调用一个真正的LLM。 """ if"RAG"in chunk_text and"检索"in chunk_text: returnf"Context: This chunk details the core retrieval mechanism of RAG and its purpose related to knowledge bases. ---\n{chunk_text}" elif"切块"in chunk_text and"影响"in chunk_text: returnf"Context: This chunk elaborates on the criticality of chunking strategies and their impact on LLM performance and context. ---\n{chunk_text}" else: # 模拟一个通用上下文 returnf"Context: This text fragment discusses general AI concepts or system components. ---\n{chunk_text}"def contextual_chunking(text, base_chunking_strategy=paragraph_based_chunking, knowledge_base_overview="Overview of AI and RAG systems."): """ 先进行基础切块,然后用LLM为每个切块添加上下文。 """ base_chunks = base_chunking_strategy(text, **{}) # 确保可以传入空字典 contextualized_chunks = [mock_llm_add_context(chunk, knowledge_base_overview) for chunk in base_chunks] return contextualized_chunksprint("\n--- 15. 上下文切块 ---")chunks_contextual = contextual_chunking(sample_text_long)for i, chunk in enumerate(chunks_contextual[:3]): print(f"Chunk {i+1}:\n'{chunk}'")print("...")

16. 语义切块(Semantic Chunking):
- 场景使用: 当你的文档主题连贯但缺乏明确结构,或者不同主题的句子交织在一起时,如访谈记录、会议纪要的自由转录、长篇小说中人物情感的起伏、对某个复杂概念的多角度阐述。这种方法通过识别句子或段落的语义相似性,将真正“谈论同一件事”的内容聚合在一起。
- 优点:
- 高语义纯度: 确保每个切块中的内容在语义上高度相关,减少无关信息的干扰。
- 应对无结构文本: 在没有明确结构的情况下,也能找到自然的语义边界。
- 提升检索质量: 用户查询某个概念时,能召回所有语义上相关的片段,即使它们在原文中不相邻。
- 缺点:
- 实现复杂: 需要使用句子嵌入模型(如Sentence Transformers),并进行向量计算和聚类分析。
- 计算开销: 嵌入生成和相似度计算会增加处理时间。
- 阈值敏感: 相似度阈值的设置非常关键,过高可能导致块过小,过低可能导致块过大并包含多个主题。
- 模型依赖: 效果取决于所选嵌入模型的语义理解能力。
# pip install sentence-transformers # 实际应用会用这个from sentence_transformers import SentenceTransformer# 加载一个预训练的句子嵌入模型 (首次运行可能需要下载)try: embedding_model = SentenceTransformer('all-MiniLM-L6-v2')except Exception: print("Failed to load sentence-transformers model. Please ensure you have internet or download it manually.") # 提供一个备用/跳过策略 embedding_model = Nonedef semantic_chunking(text, model=embedding_model, similarity_threshold=0.7): """ 先嵌入所有句子,然后根据相似度聚合。 """ if model isNone: print("Embedding model not loaded, skipping embedding chunking demo.") return [text] # 返回原始文本或进行其他默认切块 sentences = sent_tokenize(text) if len(sentences) <= 1: return sentences sentence_embeddings = model.encode(sentences) chunks = [] current_chunk_sentences = [sentences[0]] for i in range(1, len(sentences)): # 计算当前句子与前一个句子嵌入的余弦相似度 similarity = cosine_similarity([sentence_embeddings[i]], [sentence_embeddings[i-1]])[0][0] if similarity < similarity_threshold: # 如果相似度低,则认为语义不连续,结束当前块 chunks.append(" ".join(current_chunk_sentences)) current_chunk_sentences = [sentences[i]] else: current_chunk_sentences.append(sentences[i]) if current_chunk_sentences: # 添加最后一个块 chunks.append(" ".join(current_chunk_sentences)) return chunksprint("\n--- 16. 语义切块 ---")if embedding_model: chunks_semantic = semantic_chunking(sample_text_long, similarity_threshold=0.5) # 调整阈值以观察不同效果 for i, chunk in enumerate(chunks_semantic[:3]): print(f"Chunk {i+1}:\n'{chunk}'") print("...")else: print("跳过嵌入切块演示,因为SentenceTransformer模型未加载。")
17. 递归切块(Recursive Chunking):
- 场景使用: 对于长度不确定、结构不规则的文本,如采访记录、自由形式的写作、用户评论、非结构化文档等。当你想确保每个切块都满足LLM的最大token限制,同时尽可能保持语义完整性时,递归切块是一个非常强大的通用解决方案。它会优先使用大的语义分隔符,如果仍超出限制,则尝试更小的分隔符,直至满足要求。
- 优点:
- 灵活性高: 能够处理各种长度和结构的文本,适应性强。
- 平衡完整性与粒度: 优先保留较大的语义单元(如段落),在必要时才进一步细分到句子或单词,尽量减少上下文破坏。
- 通用性强: 适合作为大多数RAG系统的通用切块策略。
- 缺点:
- 实现略复杂: 相较于简单切块,逻辑更复杂,需要定义分隔符优先级。
- 分隔符依赖: 分隔符的选择和顺序会影响切块质量,需要一定的经验和实验。
- 可能仍然截断: 在极端情况下,如果所有分隔符都用完仍无法满足长度要求,最终可能还是会强制截断文本。
def recursive_chunking(text, separators, max_chunk_size_char=500): """ 递归切块,尝试不同的分隔符,直到块大小符合要求。 separators: 分隔符列表,从大到小排列 (如 ['\n\n', '\n', '. ', ' ']) max_chunk_size_char: 最大切块字符数 """ chunks = [] ifnot text: return [] # 如果文本已经小于最大块大小,直接返回 if len(text) <= max_chunk_size_char: return [text] # 尝试当前最大的分隔符 if separators: current_separator = separators[0] remaining_separators = separators[1:] parts = text.split(current_separator) for part in parts: part_stripped = part.strip() if part_stripped: # 确保不是空字符串 if len(part_stripped) > max_chunk_size_char: # 如果部分仍然太大,递归调用更小的分隔符 chunks.extend(recursive_chunking(part_stripped, remaining_separators, max_chunk_size_char)) else: chunks.append(part_stripped) else: # 没有更多分隔符可用,直接按字符切分(作为兜底) for i in range(0, len(text), max_chunk_size_char): chunks.append(text[i:i + max_chunk_size_char]) return [chunk for chunk in chunks if chunk] # 过滤空块print("\n--- 17. 递归切块 ---")# 模拟一个非常长的段落,需要递归切分long_paragraph = "这是一个非常非常长的段落,它包含了多句话,并且可能在语义上可以被分割。我们希望这个段落能够被智能地切分成更小的部分,以便于RAG系统处理。如果直接固定大小切块,可能会切断句子的上下文,导致信息丢失。所以,我们需要一个更灵活的策略来处理这种长文本。RAG的成功很大程度上取决于切块的质量。我们在这里模拟一个非常长的输入,以测试递归切块的能力。请注意,这个段落的长度远远超过了我们设定的最大块大小,所以它将被进一步切分。切块的艺术在于平衡信息的完整性和粒度。适当的切块能够帮助大模型更好地理解检索到的信息,从而生成更准确、更相关的回答。这是一项技术挑战,也是RAG优化的关键一步。通过不同的分隔符进行递归切分,我们可以确保每个块都不会过大,同时尽量保持语义的完整性。当遇到一个超长的段落时,首先尝试用段落符切分,如果还超长,就用句号切分,再超长就用逗号,直到达到预设的最大长度。"separators = ['\n\n', '. ', ',', ' '] # 尝试从大到小的分隔符chunks_recursive = recursive_chunking(long_paragraph, separators, max_chunk_size_char=100)for i, chunk in enumerate(chunks_recursive): print(f"Chunk {i+1} (len={len(chunk)}):\n'{chunk}'")
18. 嵌入切块(Embedding Chunking):
- 场景使用: 当你的文档完全非结构化,缺乏任何标点、标题或清晰的段落分隔,或者简单的启发式切块效果不佳时。这种方法特别适合处理口语化的转录文本、网络爬取的混乱数据流等。它基于语义相似度来决定切块边界,从而在缺乏显式结构的情况下创建有意义的块。
- 优点:
- 应对无结构文本: 对没有明确结构的信息非常有效,能自动识别语义边界。
- 语义准确性高: 直接利用句子嵌入的语义信息,确保切块内容的相关性。
- 自动化程度高: 无需手动定义规则或关键词,自动化程度高。
- 缺点:
- 计算成本高: 需要为所有句子生成嵌入,这比简单的文本分割计算量更大。
- 模型依赖: 效果严重依赖所使用的嵌入模型的质量和适用性。
- 阈值敏感: 相似度阈值的设置对最终切块结果有很大影响,需要仔细调优。
# pip install sentence-transformersfrom sentence_transformers import SentenceTransformer# 加载一个预训练的句子嵌入模型 (首次运行可能需要下载)try: embedding_model = SentenceTransformer('all-MiniLM-L6-v2')except Exception: print("Failed to load sentence-transformers model. Please ensure you have internet or download it manually.") # 提供一个备用/跳过策略 embedding_model = Nonedef embedding_chunking(text, model=embedding_model, similarity_threshold=0.7): """ 先嵌入所有句子,然后根据相似度聚合。 """ if model isNone: print("Embedding model not loaded, skipping embedding chunking demo.") return [text] # 返回原始文本或进行其他默认切块 sentences = sent_tokenize(text) if len(sentences) <= 1: return sentences sentence_embeddings = model.encode(sentences) chunks = [] current_chunk_sentences = [sentences[0]] for i in range(1, len(sentences)): # 计算当前句子与前一个句子嵌入的余弦相似度 similarity = cosine_similarity([sentence_embeddings[i]], [sentence_embeddings[i-1]])[0][0] if similarity < similarity_threshold: # 如果相似度低,则认为语义不连续,结束当前块 chunks.append(" ".join(current_chunk_sentences)) current_chunk_sentences = [sentences[i]] else: current_chunk_sentences.append(sentences[i]) if current_chunk_sentences: # 添加最后一个块 chunks.append(" ".join(current_chunk_sentences)) return chunksprint("\n--- 18. 嵌入切块 ---")if embedding_model: chunks_embedding = embedding_chunking(sample_text_long, similarity_threshold=0.5) # 调整阈值以观察不同效果 for i, chunk in enumerate(chunks_embedding[:3]): print(f"Chunk {i+1}:\n'{chunk}'") print("...")else: print("跳过嵌入切块演示,因为SentenceTransformer模型未加载。")
19. Agentic / 基于LLM切块(Agentic / LLM-based Chunking):
- 场景使用: 适用于极其复杂、高度非结构化且难以用规则或启发式方法有效切块的文本。例如,包含大量口语、多主题交织、推理链条复杂的会议讨论、自由形式的用户反馈、专业领域的专家报告等。当人类判断是最佳的切块方式,但又需要自动化时,可以考虑让LLM来“智能”地完成这个任务。
- 优点:
- 高度智能: LLM能够理解文本的深层含义、逻辑关系和上下文,从而做出更符合语义的切块决策。
- 灵活性和适应性强: 可以应对各种复杂和未知的文本结构。
- 减少人工干预: 在一些传统方法难以处理的场景下,可以自动化切块过程。
- 缺点:
- 成本高昂: 调用大型LLM进行切块会产生显著的API费用和计算延迟。
- 速度较慢: LLM推理速度通常比基于规则或嵌入的切块慢得多。
- 不可控性: LLM的切块决策可能不够稳定或可解释,有时会出现“意料之外”的分割。
- token限制: 需要将文本分成LLM可以处理的较小段落进行处理。
def mock_llm_chunking_decision(text_segment): """ 模拟LLM决定如何切块。 在实际中,需要给LLM提供文本段落和切块规则,让它返回分割点或直接返回切好的块。 例如,可以给LLM一个Prompt: "给定以下文本,请将其分割成语义连贯的、不超过200字的独立片段,并以'---CHUNK---'作为分隔符返回:" """ # 模拟LLM智能地将文本分割成几个逻辑块 if"RAG"in text_segment and"幻觉"in text_segment: return ["RAG技术有效解决了大模型幻觉问题。", "其核心在于结合检索和生成能力。"] elif"切块"in text_segment and"影响"in text_segment: return ["切块策略直接影响检索的质量。", "如果切块过大,会增加LLM处理负担;如果过小,则可能丢失上下文。"] else: # 如果LLM无法智能切分,就回退到句子切块 return sent_tokenize(text_segment)def agentic_llm_based_chunking(text, max_segment_for_llm=500): """ 使用LLM来决定切块边界。 由于LLM调用成本,通常我们会先将大文本切分成适合LLM处理的段落, 然后让LLM对这些段落进行细粒度切块。 """ # 先进行一个粗粒度的切块(例如,按段落或固定大小),确保每个段落大小适合LLM处理 coarse_chunks = fixed_size_chunking(text, max_segment_for_llm, overlap=0) final_chunks = [] for chunk in coarse_chunks: # 模拟LLM对每个粗粒度块进行智能切分 llm_decided_sub_chunks = mock_llm_chunking_decision(chunk) final_chunks.extend(llm_decided_sub_chunks) return [c.strip() for c in final_chunks if c.strip()]print("\n--- 19. Agentic / 基于LLM切块 ---")chunks_llm_based = agentic_llm_based_chunking(sample_text_long, max_segment_for_llm=300)for i, chunk in enumerate(chunks_llm_based[:5]): print(f"Chunk {i+1}:\n'{chunk}'")print("...")
20. 分层切块(Hierarchical Chunking):
- 场景使用: 对于结构清晰、具有多级标题的复杂文档,如书籍、学术论文、法律法规、复杂的公司规章制度、带有严格目录的技术文档。当你需要支持用户在不同粒度(从章节概览到具体段落)进行检索,并且LLM在生成回答时需要理解信息的层级关系时,这种方法是理想选择。
- 优点:
- 全面性与粒度兼顾: 提供了多粒度的检索能力,用户可以先获取高层级概览,再深入细节。
- 保留结构上下文: 每个块都带有其所属的层级信息(如章节标题),LLM在处理时能更好地理解其在文档中的位置和作用。
- 提升检索效率: 可以根据查询的广度在不同层级进行检索,提高效率。
- 缺点:
- 实现最复杂: 需要复杂的解析器来识别和构建文档的层级结构,并处理各种边缘情况。
- 存储冗余: 某些内容可能在不同层级的块中重复出现(如一个段落既是其小节块的一部分,也是其章节块的一部分),增加存储负担。
- 依赖文档结构: 对于非结构化文档或结构混乱的文档,无法应用。
class HierarchicalChunk: def __init__(self, content, level, title=None, children=None): self.content = content self.level = level self.title = title self.children = children if children isnotNoneelse [] def __repr__(self): returnf"Level {self.level} '{self.title or self.content[:30]}...'"def parse_markdown_hierarchy(text): """ 解析Markdown文本,构建分层结构。 返回一个包含顶级HierarchicalChunk对象的列表。 """ lines = text.split('\n') # 存储当前的层级路径,方便构建嵌套结构 # Stack stores (level, parent_chunk) root_chunks = [] current_path = [(0, None)] # (level, parent_chunk) for line in lines: stripped_line = line.strip() ifnot stripped_line: continue match = re.match(r'^(#+)\s*(.*)$', stripped_line) if match: level = len(match.group(1)) # 标题级别 title = match.group(2).strip() new_chunk = HierarchicalChunk(content="", level=level, title=title) # 回溯到正确的父级 while current_path and current_path[-1][0] >= level: current_path.pop() if current_path and current_path[-1][1]: # 有父级 current_path[-1][1].children.append(new_chunk) else: # 顶级标题 root_chunks.append(new_chunk) current_path.append((level, new_chunk)) else: # 普通内容,添加到当前最低层级块的内容 if current_path and current_path[-1][1]: # 如果是第一个内容行,直接赋值,否则追加 if current_path[-1][1].content: current_path[-1][1].content += "\n" + line else: current_path[-1][1].content = line else: # 没有标题的开头内容,作为顶级块 ifnot root_chunks or root_chunks[-1].level != 0or root_chunks[-1].title: # 如果没有顶级块或者上一个是标题,就创建一个新的 root_chunks.append(HierarchicalChunk(content=line, level=0)) else: # 追加到第一个无标题顶级块 root_chunks[-1].content += "\n" + line # 递归清理并整合内容 def consolidate_chunks(chunk_list): final_chunks = [] for chunk in chunk_list: # 将标题本身和内容整合到content中 full_content = "" if chunk.title: full_content += "#" * chunk.level + " " + chunk.title + "\n" full_content += chunk.content.strip() if full_content: # 确保内容不为空 final_chunks.append(HierarchicalChunk(full_content, chunk.level, chunk.title)) if chunk.children: final_chunks.extend(consolidate_chunks(chunk.children)) return final_chunks return consolidate_chunks(root_chunks)print("\n--- 20. 分层切块 ---")# 使用一个更适合分层切块的结构化文本hierarchical_text = """# 第一章 RAG概述RAG是一种强大的AI技术。## 1.1 RAG的原理结合检索和生成。### 1.1.1 检索部分从知识库中获取信息。## 1.2 RAG的优势减少幻觉,提升准确性。# 第二章 切块策略切块是RAG的关键一步。"""hierarchical_chunks = parse_markdown_hierarchy(hierarchical_text)for i, chunk_obj in enumerate(hierarchical_chunks): print(f"Chunk {i+1} (Level {chunk_obj.level}, Title: '{chunk_obj.title}'):\n'{chunk_obj.content}'")

21. 模态感知切块(Modality-Aware Chunking):
- 场景使用: 适用于包含不同类型数据(文本、图像、表格、图表、代码等)的多模态文档,如多媒体报告、带有图表的PDF文档、网页内容。当每种模态的信息都需要以其最适合的方式处理(例如,文本切块,图像生成描述,表格转换为结构化数据)时,这种方法至关重要。
- 优点:
- 优化信息处理: 针对不同模态采用最佳处理方式,确保每种信息的完整性和可读性。
- 提升多模态检索: 能够支持跨模态的查询,例如查询“关于产品销量的图表”。
- 丰富LLM上下文: 为LLM提供更全面的信息视图,包括文本描述和结构化数据。
- 缺点:
- 实现最复杂: 需要图像识别、表格检测、文本内容分析等多种技术结合,甚至需要多模态LLM支持。
- 工具依赖: 需要集成多个不同的解析库和AI模型。
- 成本高昂: 多模态处理通常涉及更复杂的模型和更高的计算资源。
def modality_aware_chunking(text): """ 分离不同模态的内容(文本、表格)。 这里只处理文本和Markdown表格。 """ chunks = [] lines = text.split('\n') current_chunk_lines = [] in_table_block = False for line in lines: stripped_line = line.strip() # 检查是否是Markdown表格行 is_table_line = stripped_line.startswith('|') and'|'in stripped_line[1:] if is_table_line: ifnot in_table_block: # 结束之前的文本块 if current_chunk_lines: chunks.append({"type": "text", "content": "\n".join(current_chunk_lines).strip()}) current_chunk_lines = [] in_table_block = True current_chunk_lines.append(line) else: if in_table_block: # 结束表格块 if current_chunk_lines: chunks.append({"type": "table", "content": "\n".join(current_chunk_lines).strip()}) current_chunk_lines = [] in_table_block = False current_chunk_lines.append(line) # 处理最后一个块 if current_chunk_lines: chunk_type = "table"if in_table_block else"text" chunks.append({"type": chunk_type, "content": "\n".join(current_chunk_lines).strip()}) return [chunk for chunk in chunks if chunk['content']]print("\n--- 21. 模态感知切块 ---")chunks_modality = modality_aware_chunking(sample_text_mixed_format)for i, chunk in enumerate(chunks_modality): print(f"Chunk {i+1} (Type: {chunk['type']}):\n'{chunk['content']}'")
BONUS:混合切块(Hybrid Chunking):集大成者,无往不利!
- 场景使用: 当你的数据非常复杂,单一的切块策略无法完美解决问题时。这是一种实践中非常常见的方案,你可以根据具体的数据特点和业务需求,灵活地组合上述一种或多种策略。比如,先用段落切块,再对过长的段落进行递归切块;或者先识别表格并单独处理,然后对剩余文本进行语义切块。
- 优点:
- 高度定制化: 可以根据特定文档类型和应用场景,设计出最匹配的切块流程。
- 兼顾多种需求: 结合不同策略的优势,在语义完整性、块大小、处理效率等方面找到最佳平衡。
- 解决复杂问题: 能有效应对单一策略无法处理的复杂文档结构和内容。
- 缺点:
- 实现和调试复杂: 组合多种策略会显著增加代码的复杂性和调试难度。
- 参数调优: 多个策略的参数需要协同调优,工作量大。
- 无通用模板: 混合切块是高度定制的,没有一个放之四海而皆准的方案。
def hybrid_chunking(text, primary_strategy, secondary_strategy, primary_args={}, secondary_args={}): """ 混合切块策略示例:先用一种策略粗切,再用另一种策略细切。 primary_strategy: 第一阶段切块函数 (如 paragraph_based_chunking) secondary_strategy: 第二阶段切块函数 (如 recursive_chunking) """ # 步骤1:用主要策略进行粗粒度切块 coarse_chunks = primary_strategy(text, **primary_args) final_chunks = [] # 步骤2:对每个粗粒度块,再用次要策略进行细粒度切块 for chunk in coarse_chunks: # 如果粗粒度块仍然太大或需要进一步细分 if len(chunk) > 500: # 假设一个启发式条件,可以根据token数或语义复杂度来定 # 注意:这里需要确保secondary_strategy能够处理传入的参数 fine_grained_chunks = secondary_strategy(chunk, **secondary_args) final_chunks.extend(fine_grained_chunks) else: final_chunks.append(chunk) return [chunk for chunk in final_chunks if chunk.strip()]print("\n--- BONUS: 混合切块 ---")# 示例:先按段落切块,然后对超过一定长度的段落进行递归切块chunks_hybrid = hybrid_chunking( sample_text_long + sample_text_structured, primary_strategy=paragraph_based_chunking, primary_args={}, # 段落切块不需要额外参数 secondary_strategy=recursive_chunking, secondary_args={'separators': ['. ', ','], 'max_chunk_size_char': 200})for i, chunk in enumerate(chunks_hybrid[:5]): print(f"Chunk {i+1} (len={len(chunk)}):\n'{chunk}'")print("...")
深入理解每种切块策略的应用场景、优缺点,再结合代码实现,你就能在RAG的实践中更加游刃有余。记住,切块是RAG成功的基石之一,选择合适的策略,往往能让你的RAG系统事半功倍!
如何学习AI大模型?
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习和面试资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】


第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

1041

被折叠的 条评论
为什么被折叠?



