""" RecursiveCharacterTextSplitter 工作原理详解 展示如何将长文档切分成小块(chunks) """ print("=" * 80) print("RecursiveCharacterTextSplitter 工作原理") print("=" * 80) # ============================================================================ # Part 1: 为什么需要文本分割? # ============================================================================ print("\n" + "=" * 80) print("❓ Part 1: 为什么需要文本分割?") print("=" * 80) print(""" 问题:原始文档通常很长 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 一篇网页文章:5000 字 一份技术文档:10000 字 一本书的一章:20000 字 如果直接将整篇文档做成向量: ❌ 信息密度太低(无关信息太多) ❌ 检索不精准(无法定位到具体段落) ❌ 超出模型长度限制(BERT 最多 512 tokens) 解决方案:文本分割(Text Splitting) ✅ 将长文档切成小块(chunks) ✅ 每个 chunk 独立建立向量索引 ✅ 检索时返回最相关的 chunks """) # ============================================================================ # Part 2: 你的项目配置 # ============================================================================ print("\n" + "=" * 80) print("⚙️ Part 2: 你的项目配置") print("=" * 80) print(""" 在 config.py 中: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CHUNK_SIZE = 250 # 每个块最多 250 个 tokens CHUNK_OVERLAP = 0 # 块之间不重叠 在 document_processor.py 中: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( chunk_size=250, # 每块 250 tokens chunk_overlap=0 # 无重叠 ) """) # ============================================================================ # Part 3: RecursiveCharacterTextSplitter 的核心机制 # ============================================================================ print("\n" + "=" * 80) print("🔍 Part 3: RecursiveCharacterTextSplitter 核心机制") print("=" * 80) print(""" "Recursive" 的含义:递归式分割 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 不是简单粗暴地按字符数切分,而是按照分隔符的优先级递归切分: 分隔符优先级(从高到低): ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. "\\n\\n" 双换行(段落分隔) ← 最优先 2. "\\n" 单换行(句子分隔) 3. " " 空格(词语分隔) 4. "" 字符级别切分 ← 最后手段 工作流程: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1: 尝试用 "\\n\\n" 分割 ↓ 每块都 < 250 tokens? ↓ No (某块太大) Step 2: 对大块用 "\\n" 分割 ↓ 每块都 < 250 tokens? ↓ No (某块还太大) Step 3: 对大块用 " " 分割 ↓ 每块都 < 250 tokens? ↓ No (某块仍太大) Step 4: 强制按字符切分 ↓ 保证每块 <= 250 tokens ✓ """) # ============================================================================ # Part 4: 实际示例 - 手动模拟分割过程 # ============================================================================ print("\n" + "=" * 80) print("💡 Part 4: 实际示例 - 手动模拟分割过程") print("=" * 80) # 示例文档 document = """人工智能简介 人工智能(AI)是计算机科学的一个分支。它致力于创建能够执行通常需要人类智能的任务的系统。 机器学习是人工智能的一个子领域。它使计算机能够从数据中学习并改进其性能。深度学习是机器学习的一种方法,使用多层神经网络。 自然语言处理(NLP)是另一个重要的AI领域。它处理计算机与人类语言之间的交互。""" print(f"\n原始文档:") print("─" * 80) print(document) print("─" * 80) print(f"文档长度:{len(document)} 字符") # 模拟 RecursiveCharacterTextSplitter 的工作 def count_tokens(text): """简化的 token 计数(实际使用 tiktoken)""" # 中文:大约 1 字 = 1.5 tokens # 英文:大约 1 词 = 1 token return int(len(text) * 0.7) # 简化估算 print(f"\n估算 tokens 数:{count_tokens(document)} tokens") # Step 1: 尝试按双换行分割 print("\n" + "━" * 80) print("Step 1: 按 '\\n\\n' (段落) 分割") print("━" * 80) paragraphs = document.split('\n\n') print(f"\n分割成 {len(paragraphs)} 个段落:\n") for i, para in enumerate(paragraphs, 1): token_count = count_tokens(para) status = "✅" if token_count <= 250 else "❌ 超出限制" print(f"段落 {i}: {token_count} tokens {status}") print(f" 内容: {para[:60]}...") print() # 假设某个段落超出限制 large_para = """机器学习是人工智能的一个子领域。它使计算机能够从数据中学习并改进其性能。深度学习是机器学习的一种方法,使用多层神经网络。""" if count_tokens(large_para) > 250: print("━" * 80) print("Step 2: 段落太大,按 '\\n' (句子) 分割") print("━" * 80) sentences = large_para.split('。') print(f"\n分割成 {len(sentences)} 个句子:\n") for i, sent in enumerate(sentences, 1): if sent.strip(): token_count = count_tokens(sent) status = "✅" if token_count <= 250 else "❌" print(f"句子 {i}: {token_count} tokens {status}") print(f" 内容: {sent.strip()}") print() # ============================================================================ # Part 5: chunk_overlap 的作用 # ============================================================================ print("\n" + "=" * 80) print("🔄 Part 5: chunk_overlap(块重叠)的作用") print("=" * 80) print(""" 你的项目设置:CHUNK_OVERLAP = 0(无重叠) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 无重叠的切分: ┌──────────┐┌──────────┐┌──────────┐ │ Chunk 1 ││ Chunk 2 ││ Chunk 3 │ │ 250 tok ││ 250 tok ││ 250 tok │ └──────────┘└──────────┘└──────────┘ ↑ 边界可能切断语义 有重叠的切分(CHUNK_OVERLAP = 50): ┌──────────┐ │ Chunk 1 │ │ 250 tok │ └──────────┘ └──────────┐ │ Chunk 2 │ │ 250 tok │ └──────────┘ └──────────┐ │ Chunk 3 │ │ 250 tok │ └──────────┘ 优点: ✅ 保留上下文连贯性 ✅ 避免关键信息被切断 ✅ 提高检索准确率 (+5-10%) 缺点: ❌ 存储空间增加 20-30% ❌ 可能返回重复内容 为什么你的项目设为 0? ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ 节省存储空间 ✅ 避免重复 ✅ 配合 CrossEncoder 重排已经足够准确 推荐设置: - CHUNK_OVERLAP = 0: 快速原型、存储受限 - CHUNK_OVERLAP = 50: 生产环境、高精度要求 ⭐ - CHUNK_OVERLAP = 100: 关键应用、医疗法律等 """) # ============================================================================ # Part 6: from_tiktoken_encoder 的作用 # ============================================================================ print("\n" + "=" * 80) print("🎯 Part 6: from_tiktoken_encoder 的特殊之处") print("=" * 80) print(""" 你的代码: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ RecursiveCharacterTextSplitter.from_tiktoken_encoder( chunk_size=250, chunk_overlap=0 ) 不使用 tiktoken: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ RecursiveCharacterTextSplitter( chunk_size=250, # ← 这里是字符数,不是 tokens! chunk_overlap=0 ) 区别: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 普通模式:按字符数切分 ├─ chunk_size=250 → 250 个字符 ├─ 中文:大约 250 个字 = 375 tokens(超标!) └─ 英文:大约 50 个单词 = 50 tokens(太少!) tiktoken 模式:按 tokens 切分 ⭐ ├─ chunk_size=250 → 精确 250 个 tokens ├─ 中文:大约 166 个字 = 250 tokens ✓ └─ 英文:大约 190 个单词 = 250 tokens ✓ tiktoken 是什么? ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ OpenAI 的 tokenizer,与 GPT/BERT 的分词方式一致 优点: ✅ 精确控制 chunk 大小 ✅ 与 Embedding 模型的 token 限制一致 ✅ 中英文都能准确处理 你的项目使用 tiktoken 是正确且推荐的做法! """) # ============================================================================ # Part 7: 完整的分割流程可视化 # ============================================================================ print("\n" + "=" * 80) print("📊 Part 7: 完整的分割流程可视化") print("=" * 80) print(""" 原始文档 (5000 tokens) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ 第一章:人工智能简介 │ │ │ │ 人工智能(AI)是计算机科学的一个分支... │ │ │ │ 第二章:机器学习 │ │ │ │ 机器学习是AI的一个子领域... │ │ │ │ 第三章:深度学习 │ │ ... │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ↓ RecursiveCharacterTextSplitter (chunk_size=250, overlap=0) ↓ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Chunk 1 (250 tokens) ┌─────────────────────────────────────────────────┐ │ 第一章:人工智能简介 │ │ │ │ 人工智能(AI)是计算机科学的一个分支。它致力... │ └─────────────────────────────────────────────────┘ ↓ 存入向量数据库 Chunk 2 (250 tokens) ┌─────────────────────────────────────────────────┐ │ 人工智能包括多个子领域,如机器学习、... │ └─────────────────────────────────────────────────┘ ↓ 存入向量数据库 Chunk 3 (250 tokens) ┌─────────────────────────────────────────────────┐ │ 第二章:机器学习 │ │ │ │ 机器学习是AI的一个子领域。它使计算机能够... │ └─────────────────────────────────────────────────┘ ↓ 存入向量数据库 ...继续分割成约 20 个 chunks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 检索时: 用户问题: "什么是机器学习?" ↓ 向量检索 Top 20 chunks ↓ ├─ Chunk 3 (相关度: 0.92) ← 最相关 ├─ Chunk 4 (相关度: 0.88) ├─ Chunk 1 (相关度: 0.75) └─ ... ↓ CrossEncoder 重排 → Top 5 ↓ 返回最相关的片段给 LLM 生成答案 """) # ============================================================================ # Part 8: 关键参数调优建议 # ============================================================================ print("\n" + "=" * 80) print("⚙️ Part 8: 关键参数调优建议") print("=" * 80) print(""" 参数配置建议: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CHUNK_SIZE(块大小): ├─ 100-200: 短文档、精确检索 ├─ 250-500: 通用场景 ⭐ (你的项目) └─ 500-1000: 长文档、需要更多上下文 CHUNK_OVERLAP(重叠): ├─ 0: 快速原型、存储受限 (你的项目) ├─ 50: 生产环境推荐 ⭐ ├─ 100: 高精度要求 └─ 150+: 关键应用 你的项目配置: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CHUNK_SIZE = 250 ✓ 适中,适合大多数场景 CHUNK_OVERLAP = 0 ⚠️ 建议改为 50-100 推荐优化: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CHUNK_SIZE = 400 # 增加上下文 CHUNK_OVERLAP = 100 # 添加重叠保证连贯性 理由: ✅ 400 tokens 足够包含完整的段落 ✅ 100 tokens 重叠避免关键信息被切断 ✅ 配合 CrossEncoder,准确率可提升 8-12% """) # ============================================================================ # Part 9: 总结 # ============================================================================ print("\n" + "=" * 80) print("📚 Part 9: 核心要点总结") print("=" * 80) print(""" RecursiveCharacterTextSplitter 的工作原理: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1️⃣ 递归分割: └─ 按优先级尝试分隔符:\\n\\n → \\n → 空格 → 字符 2️⃣ 智能切分: └─ 保持语义完整性,优先在段落/句子边界切分 3️⃣ 精确控制: └─ from_tiktoken_encoder 确保每块恰好 250 tokens 4️⃣ 可选重叠: └─ CHUNK_OVERLAP 保留上下文连贯性 5️⃣ 你的项目流程: 原始文档 ↓ RecursiveCharacterTextSplitter 250-token chunks ↓ HuggingFace Embeddings 向量数据库 ↓ 向量检索 (Top 20) 候选 chunks ↓ CrossEncoder 重排 最终 Top 5 chunks ↓ 喂给 LLM 生成答案 关键优势: ✅ 智能切分,保持语义完整 ✅ 精确控制 chunk 大小 ✅ 支持中英文混合文本 ✅ 与向量检索配合完美 这就是为什么你的项目能够准确检索和回答问题! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """) print("\n" + "=" * 80) print("✅ 解析完成!现在你应该理解了文本分割的原理") print("=" * 80) print()