一、RAG文档切割介绍

        RAG文档切割是RAG应用的基础环节,其质量直接决定了系统设计的成败。合理的文档分割不仅影响检索的准确性和召回率,更关系到后续生成答案的相关性与可靠性。因此,必须根据领域特点和业务需求,精心设计切割策略,确保语义单元的完整性,为整个系统奠定坚实根基

二、为什么做分块

1、分块的必要性源于两个核心的限制

(1)模型上下文窗口

大语言模型(LLM)无法一次性处理无限制长度的文本,必须将长文本切分为适配模型处理能力的小片段

(2)检索新噪比

若一个文本块包含过多无关信息(噪音),会稀释核心信号,导致检索器难以精确匹配用户意图

2、理想的分块标准

在上下文完整性与信息密度之间取得平衡,按着策略复杂度分为四类

(1)基础分块

(2)结构感知

(3)语义/主题

(4)高级策略

三、分块策略

1、基础分块策略

(1)、固定长度分块

核心思想:按固定字符数切割文本,不考虑语义结构

优点:

  1. 智能分层:先尝试按大段落分割,再按句子分割

  2. 保持语义:尽可能在自然边界处切割

  3. 灵活配置:支持自定义块大小和重叠比例

  4. 医疗友好:适合医疗文档的层次化结构

缺点:易破坏句子完整性

适用场景:非结构化村文本预处理

示例代码

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentByCharacterSplitter;
import dev.langchain4j.data.segment.TextSegment;
import java.util.List;

public class SimpleSplitExample {
    
    public static void main(String[] args) {
        String text = "你的普通文本内容...";
        
        Document document = Document.from(text);
        
        // 直接使用LangChain4j的分割器:100字切割,15字重叠(15%)
        DocumentByCharacterSplitter splitter = new DocumentByCharacterSplitter(100, 15);
        
        List<TextSegment> segments = splitter.split(document);
        
        // 简单输出
        segments.forEach(segment -> 
            System.out.println("分块 (" + segment.text().length() + "字): " + segment.text())
        );
    }
}
(2)、递归字符分块(推荐)

核心思想:按优先级分割递归切分(\n\n--\n--""),尽量保留段落,句子完整性。

优点:实现比较简单

缺点:对于缺乏明确分隔符的医疗文本效果不佳

适用场景:绝大多数通用文本的首选策略

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import java.util.List;

public class RecursiveSplittingExample {

    public static void main(String[] args) {
        // 创建医疗文档
        String medicalText = "患者主诉:头痛、发热、咳嗽三天。\n\n" +
                           "体格检查:体温38.5℃,咽部充血,双肺呼吸音粗。\n\n" +
                           "初步诊断:上呼吸道感染。\n\n" +
                           "处理建议:休息、多饮水,对症退热治疗。\n\n" +
                           "注意事项:如症状加重请及时复诊。";
        
        Document document = Document.from(medicalText);
        
        // 创建递归分割器
        var splitter = DocumentSplitters.recursive(
            200,  // 最大块大小
            30    // 重叠大小(约15%)
        );
        
        // 执行分割
        List<TextSegment> segments = splitter.split(document);
        
        // 输出结果
        System.out.println("原始文档长度: " + medicalText.length() + " 字符");
        System.out.println("生成分块数量: " + segments.size());
        
        for (int i = 0; i < segments.size(); i++) {
            TextSegment segment = segments.get(i);
            System.out.println("\n--- 分块 " + (i + 1) + " ---");
            System.out.println("内容: " + segment.text());
            System.out.println("长度: " + segment.text().length() + " 字符");
        }
    }
}
(3)、基于句子的分块

核心思想:以完整句子为单位进行组合,确保语义的完整。

优点:保持完整的语义单元

缺点:依赖准确的句子边界检测

适用场景:法律文书、新闻报道、对句子完整性要求比较高

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentBySentenceSplitter;
import dev.langchain4j.data.segment.TextSegment;
import java.util.List;

public class SentenceBasedSplitting {

    public static void main(String[] args) {
        // 法律文书示例 - 适合句子分块
        String legalText = "根据《中华人民共和国合同法》第十二条规定,合同的内容由当事人约定。 " +
                         "当事人订立合同,应当具有相应的民事权利能力和民事行为能力。 " +
                         "当事人依法可以委托代理人订立合同。 " +
                         "合同的形式可以是书面形式、口头形式和其他形式。 " +
                         "法律、行政法规规定采用书面形式的,应当采用书面形式。";
        
        Document document = Document.from(legalText);
        
        // 创建句子分割器
        // 参数:最大块大小(字符数),重叠大小
        DocumentBySentenceSplitter splitter = new DocumentBySentenceSplitter(150, 20);
        
        // 执行分割
        List<TextSegment> segments = splitter.split(document);
        
        // 输出结果
        System.out.println("=== 基于句子的分块结果 ===");
        System.out.println("原始文本: " + legalText);
        System.out.println("分块数量: " + segments.size());
        
        for (int i = 0; i < segments.size(); i++) {
            TextSegment segment = segments.get(i);
            System.out.println("\n--- 分块 " + (i + 1) + " ---");
            System.out.println("内容: " + segment.text());
            System.out.println("长度: " + segment.text().length() + " 字符");
            System.out.println("句子数量: " + countSentences(segment.text()));
        }
    }
    
    /**
     * 简单统计句子数量(按句号、问号、感叹号分割)
     */
    private static int countSentences(String text) {
        if (text == null || text.trim().isEmpty()) {
            return 0;
        }
        String[] sentences = text.split("[。!?!?]");
        return sentences.length;
    }
}

2、结构感知分块

(1)、结构化文本分块

核心思想:根据标题层级(如#,##)或者html标签切割

优点:保持文档的层次结构

缺点:依赖文档的结构化标记,需要预先了解文档格式

适用场景:技术文档,博客,手册等格式规范文档

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentByHeaderSplitter;
import dev.langchain4j.data.document.splitter.Header;
import dev.langchain4j.data.segment.TextSegment;
import java.util.List;

public class StructuredTextSplitting {

    public static void main(String[] args) {
        // Markdown格式的技术文档
        String markdownText = """
            # 互联网医院系统架构
            
            ## 前端设计
            前端采用Vue.js框架,实现响应式设计。
            支持PC端和移动端访问。
            
            ## 后端服务
            后端使用Spring Boot框架,提供RESTful API。
            集成用户认证和权限管理。
            
            ## 数据库设计
            使用MySQL存储用户数据和医疗记录。
            采用Redis缓存热点数据。
            
            ## 安全规范
            所有数据传输使用HTTPS加密。
            患者隐私数据严格保护。
            """;

        Document document = Document.from(markdownText);
        
        // 创建基于标题的分割器
        DocumentByHeaderSplitter splitter = new DocumentByHeaderSplitter(
            List.of(
                Header.builder().level(1).name("H1").build(), // # 标题
                Header.builder().level(2).name("H2").build()  // ## 标题
            ),
            500,  // 最大块大小
            50    // 重叠大小
        );
        
        List<TextSegment> segments = splitter.split(document);
        
        System.out.println("=== 结构化文本分块结果 ===");
        System.out.println("分块数量: " + segments.size());
        
        for (int i = 0; i < segments.size(); i++) {
            TextSegment segment = segments.get(i);
            System.out.println("\n--- 分块 " + (i + 1) + " ---");
            System.out.println("内容: " + segment.text());
            System.out.println("长度: " + segment.text().length() + " 字符");
        }
    }
}
(2)、对话式分块

核心思想:按照发言人或者对话轮次切分

优点:便于理解对话流程和逻辑

缺点:需要识别不同的对话标记格式

适用场景:客户记录,访谈,会议纪要

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import java.util.ArrayList;
import java.util.List;

public class MeetingMinutesSplitting {

    public static List<TextSegment> splitMeetingByTopics(String meetingText) {
        List<TextSegment> segments = new ArrayList<>();
        
        String[] lines = meetingText.split("\n");
        StringBuilder currentTopic = new StringBuilder();
        String currentSpeaker = null;
        
        for (String line : lines) {
            if (line.startsWith("【主题】")) {
                // 新主题开始
                if (currentTopic.length() > 0) {
                    segments.add(TextSegment.from(currentTopic.toString()));
                }
                currentTopic = new StringBuilder(line).append("\n");
            } else if (line.matches("^\\w+:.*")) {
                // 发言人内容
                currentTopic.append(line).append("\n");
            } else if (!line.trim().isEmpty()) {
                // 其他内容(决议、行动项等)
                currentTopic.append(line).append("\n");
            }
        }
        
        // 添加最后一个主题
        if (currentTopic.length() > 0) {
            segments.add(TextSegment.from(currentTopic.toString()));
        }
        
        return segments;
    }
    
    public static List<TextSegment> splitBySpeakerGroups(String text, String[] speakerGroups) {
        List<TextSegment> segments = new ArrayList<>();
        String[] lines = text.split("\n");
        
        StringBuilder currentGroup = new StringBuilder();
        String currentGroupName = null;
        
        for (String line : lines) {
            boolean isNewGroup = false;
            for (String speaker : speakerGroups) {
                if (line.startsWith(speaker + ":")) {
                    if (currentGroupName != null && !currentGroupName.equals(speaker)) {
                        // 切换到新的发言人组
                        if (currentGroup.length() > 0) {
                            segments.add(TextSegment.from(currentGroup.toString()));
                        }
                        currentGroup = new StringBuilder();
                    }
                    currentGroupName = speaker;
                    isNewGroup = true;
                    break;
                }
            }
            
            if (isNewGroup || currentGroupName != null) {
                currentGroup.append(line).append("\n");
            }
        }
        
        if (currentGroup.length() > 0) {
            segments.add(TextSegment.from(currentGroup.toString()));
        }
        
        return segments;
    }

    public static void main(String[] args) {
        // 会议纪要示例
        String meetingMinutes = """
            【主题】项目进度汇报
            项目经理:目前项目完成度80%,按计划进行。
            技术负责人:核心功能已开发完成,正在测试。
            产品经理:用户反馈良好,建议增加新功能。
            
            【主题】风险讨论
            项目经理:存在进度延迟风险。
            技术负责人:技术难题已解决,风险可控。
            测试负责人:测试发现若干bug,需要修复。
            
            【主题】下一步计划
            项目经理:下周三前完成所有功能。
            技术负责人:周四开始集成测试。
            产品经理:周五组织用户验收。
            """;

        System.out.println("=== 会议纪要按主题分块 ===");
        List<TextSegment> topicSegments = splitMeetingByTopics(meetingMinutes);
        for (int i = 0; i < topicSegments.size(); i++) {
            System.out.println("\n--- 主题块 " + (i + 1) + " ---");
            System.out.println(topicSegments.get(i).text());
        }

        // 客户访谈记录
        String customerInterview = """
            销售:您好,请问对我们的产品有什么了解?
            客户:了解一些基本功能,但不太清楚具体能解决什么问题。
            销售:我们的产品主要帮助提升工作效率30%以上。
            客户:听起来不错,能具体演示一下吗?
            技术顾问:当然可以,我现在就为您演示核心功能。
            客户:这个功能确实很实用,价格方面怎么样?
            销售:根据您的需求,我们可以提供定制报价。
            """;

        System.out.println("\n=== 按发言人组分块 ===");
        String[] speakers = {"销售", "技术顾问", "客户"};
        List<TextSegment> speakerSegments = splitBySpeakerGroups(customerInterview, speakers);
        for (int i = 0; i < speakerSegments.size(); i++) {
            System.out.println("\n--- 发言人组块 " + (i + 1) + " ---");
            System.out.println(speakerSegments.get(i).text());
        }
    }
}

3、语义和主题分块

(1)、语义分块

核心思想:计算相邻两句话的向量相似度,在语义突变处理

优点:基于真实语义边界进行分割

缺点:依赖嵌入模型

适用场景:知识库、论文、多话题文档

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.all.minilm.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.Embedding;

import java.util.ArrayList;
import java.util.List;

public class SemanticChunking {

    private final EmbeddingModel embeddingModel;
    
    public SemanticChunking() {
        // 使用轻量级嵌入模型
        this.embeddingModel = new AllMiniLmL6V2EmbeddingModel();
    }
    
    public List<TextSegment> splitBySemanticSimilarity(String text, double similarityThreshold) {
        List<TextSegment> segments = new ArrayList<>();
        
        // 1. 先将文本分割成句子
        List<String> sentences = splitIntoSentences(text);
        
        if (sentences.size() <= 1) {
            segments.add(TextSegment.from(text));
            return segments;
        }
        
        // 2. 计算句子嵌入向量
        List<Embedding> embeddings = embeddingModel.embedAll(sentences).content();
        
        // 3. 基于语义相似度进行分块
        StringBuilder currentChunk = new StringBuilder(sentences.get(0));
        
        for (int i = 1; i < sentences.size(); i++) {
            // 计算当前句子与前一句的相似度
            double similarity = cosineSimilarity(
                embeddings.get(i - 1).vector(),
                embeddings.get(i).vector()
            );
            
            if (similarity < similarityThreshold) {
                // 语义突变,创建新分块
                segments.add(TextSegment.from(currentChunk.toString()));
                currentChunk = new StringBuilder(sentences.get(i));
            } else {
                // 语义连续,添加到当前分块
                currentChunk.append(" ").append(sentences.get(i));
            }
        }
        
        // 添加最后一个分块
        if (currentChunk.length() > 0) {
            segments.add(TextSegment.from(currentChunk.toString()));
        }
        
        return segments;
    }
    
    public List<TextSegment> adaptiveSemanticChunking(String text, 
                                                     double similarityThreshold,
                                                     int maxChunkSize) {
        List<TextSegment> segments = new ArrayList<>();
        List<String> sentences = splitIntoSentences(text);
        
        if (sentences.isEmpty()) {
            return segments;
        }
        
        List<Embedding> embeddings = embeddingModel.embedAll(sentences).content();
        
        List<String> currentChunkSentences = new ArrayList<>();
        currentChunkSentences.add(sentences.get(0));
        
        for (int i = 1; i < sentences.size(); i++) {
            double similarity = cosineSimilarity(
                embeddings.get(i - 1).vector(),
                embeddings.get(i).vector()
            );
            
            // 检查是否超过最大块大小
            int currentSize = String.join(" ", currentChunkSentences).length();
            int newSentenceSize = sentences.get(i).length();
            
            if (similarity < similarityThreshold || 
                currentSize + newSentenceSize > maxChunkSize) {
                // 语义突变或超过大小限制,创建新分块
                segments.add(TextSegment.from(String.join(" ", currentChunkSentences)));
                currentChunkSentences = new ArrayList<>();
            }
            
            currentChunkSentences.add(sentences.get(i));
        }
        
        // 添加最后一个分块
        if (!currentChunkSentences.isEmpty()) {
            segments.add(TextSegment.from(String.join(" ", currentChunkSentences)));
        }
        
        return segments;
    }
    
    private List<String> splitIntoSentences(String text) {
        List<String> sentences = new ArrayList<>();
        if (text == null || text.trim().isEmpty()) {
            return sentences;
        }
        
        // 简单的句子分割(可按需使用更复杂的分割器)
        String[] rawSentences = text.split("[。!?!?;;]");
        for (String sentence : rawSentences) {
            String trimmed = sentence.trim();
            if (!trimmed.isEmpty()) {
                sentences.add(trimmed);
            }
        }
        
        return sentences;
    }
    
    private double cosineSimilarity(float[] vectorA, float[] vectorB) {
        if (vectorA.length != vectorB.length) {
            return 0.0;
        }
        
        double dotProduct = 0.0;
        double normA = 0.0;
        double normB = 0.0;
        
        for (int i = 0; i < vectorA.length; i++) {
            dotProduct += vectorA[i] * vectorB[i];
            normA += Math.pow(vectorA[i], 2);
            normB += Math.pow(vectorB[i], 2);
        }
        
        if (normA == 0 || normB == 0) {
            return 0.0;
        }
        
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    public static void main(String[] args) {
        SemanticChunking semanticChunking = new SemanticChunking();
        
        // 多主题文档示例
        String multiTopicDocument = """
            深度学习是机器学习的一个分支。它使用多层神经网络进行特征学习。
            卷积神经网络在图像识别领域表现出色。循环神经网络适合序列数据处理。
            
            自然语言处理是人工智能的重要方向。Transformer架构 revolutionized 机器翻译。
            BERT模型在多项NLP任务中取得突破。GPT系列模型展示了强大的文本生成能力。
            
            计算机视觉关注图像和视频理解。目标检测可以识别图像中的物体位置。
            图像分割将像素分类到不同类别。姿态估计分析人体关键点位置。
            """;
        
        System.out.println("=== 语义分块(相似度阈值:0.7)===");
        List<TextSegment> segments1 = semanticChunking.splitBySemanticSimilarity(
            multiTopicDocument, 0.7);
        
        for (int i = 0; i < segments1.size(); i++) {
            System.out.println("\n--- 语义块 " + (i + 1) + " ---");
            System.out.println(segments1.get(i).text());
            System.out.println("长度: " + segments1.get(i).text().length() + " 字符");
        }
        
        System.out.println("\n=== 自适应语义分块(阈值:0.6,最大长度:200)===");
        List<TextSegment> segments2 = semanticChunking.adaptiveSemanticChunking(
            multiTopicDocument, 0.6, 200);
        
        for (int i = 0; i < segments2.size(); i++) {
            System.out.println("\n--- 自适应块 " + (i + 1) + " ---");
            System.out.println(segments2.get(i).text());
            System.out.println("长度: " + segments2.get(i).text().length() + " 字符");
        }
        
        // 测试不同相似度阈值
        testDifferentThresholds(semanticChunking, multiTopicDocument);
    }
    
    private static void testDifferentThresholds(SemanticChunking chunker, String text) {
        double[] thresholds = {0.5, 0.6, 0.7, 0.8};
        
        System.out.println("\n=== 不同相似度阈值对比 ===");
        for (double threshold : thresholds) {
            List<TextSegment> segments = chunker.splitBySemanticSimilarity(text, threshold);
            System.out.println("\n阈值: " + threshold + " -> 分块数: " + segments.size());
            
            for (int i = 0; i < segments.size(); i++) {
                System.out.println("  块 " + (i + 1) + ": " + 
                    segments.get(i).text().length() + " 字符");
            }
        }
    }
}
(2)、基于主题的分块(LDA-矩阵)

核心思想:适用LDA主题模型识别段落主题,在主题切换时切分

优点:真正基于语义主题进行分割

缺点:需要训练主题模型

适用场景:长篇报告,书籍章节

示例代码:

import cc.mallet.pipe.*;
import cc.mallet.pipe.iterator.*;
import cc.mallet.topics.*;
import cc.mallet.types.*;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;

public class AdvancedLDAChunking {

    private final int numTopics;
    private final ParallelTopicModel model;
    
    public AdvancedLDAChunking(int numTopics) throws Exception {
        this.numTopics = numTopics;
        this.model = new ParallelTopicModel(numTopics);
    }
    
    public List<TopicAwareChunk> advancedTopicChunking(String text, int chunkSize) throws Exception {
        // 1. 准备数据
        List<DocumentSegment> segments = createDocumentSegments(text, chunkSize);
        
        // 2. 训练LDA模型
        InstanceList instances = createMalletInstances(segments);
        model.addInstances(instances);
        model.setNumThreads(2);
        model.setNumIterations(1000);
        model.estimate();
        
        // 3. 获取主题分布
        return assignTopicsToChunks(segments, instances);
    }
    
    private List<DocumentSegment> createDocumentSegments(String text, int chunkSize) {
        List<DocumentSegment> segments = new ArrayList<>();
        List<String> sentences = splitIntoSentences(text);
        
        for (int i = 0; i < sentences.size(); i += chunkSize) {
            int end = Math.min(i + chunkSize, sentences.size());
            List<String> chunkSentences = sentences.subList(i, end);
            String chunkText = String.join(" ", chunkSentences);
            
            segments.add(new DocumentSegment(
                "seg_" + (segments.size() + 1),
                chunkText,
                i,
                end - 1
            ));
        }
        
        return segments;
    }
    
    private InstanceList createMalletInstances(List<DocumentSegment> segments) {
        ArrayList<Pipe> pipeList = new ArrayList<>();
        
        // 创建数据处理管道
        pipeList.add(new CharSequenceLowercase());
        pipeList.add(new CharSequence2TokenSequence(Pattern.compile("\\p{L}[\\p{L}\\p{P}]+\\p{L}")));
        pipeList.add(new TokenSequenceRemoveStopwords(new File("stoplists/en.txt"), "UTF-8", false, false, false));
        pipeList.add(new TokenSequence2FeatureSequence());
        
        InstanceList instances = new InstanceList(new SerialPipes(pipeList));
        
        // 添加文档到实例列表
        for (DocumentSegment segment : segments) {
            instances.addThruPipe(new Instance(
                segment.getText(),
                segment.getId(),
                segment.getId(),
                null
            ));
        }
        
        return instances;
    }
    
    private List<TopicAwareChunk> assignTopicsToChunks(List<DocumentSegment> segments, InstanceList instances) {
        List<TopicAwareChunk> topicChunks = new ArrayList<>();
        
        for (int i = 0; i < segments.size(); i++) {
            DocumentSegment segment = segments.get(i);
            Instance instance = instances.get(i);
            
            // 获取主题分布
            double[] topicDistribution = model.getTopicProbabilities(i);
            int dominantTopic = findDominantTopic(topicDistribution);
            double topicConfidence = topicDistribution[dominantTopic];
            
            // 获取主题关键词
            List<String> topicKeywords = getTopicKeywords(dominantTopic, 5);
            
            topicChunks.add(new TopicAwareChunk(
                segment.getText(),
                dominantTopic,
                topicConfidence,
                topicKeywords,
                segment.getStartSentence(),
                segment.getEndSentence()
            ));
        }
        
        return topicChunks;
    }
    
    private int findDominantTopic(double[] topicDistribution) {
        int dominant = 0;
        for (int i = 1; i < topicDistribution.length; i++) {
            if (topicDistribution[i] > topicDistribution[dominant]) {
                dominant = i;
            }
        }
        return dominant;
    }
    
    private List<String> getTopicKeywords(int topicIndex, int numKeywords) {
        ArrayList<IDSorter> keywordScores = new ArrayList<>();
        Object[] vocabulary = model.getAlphabet().toArray();
        
        for (int wordIndex = 0; wordIndex < model.getAlphabet().size(); wordIndex++) {
            keywordScores.add(new IDSorter(wordIndex, 
                model.getTopicWordProbabilities(topicIndex)[wordIndex]));
        }
        
        Collections.sort(keywordScores);
        
        List<String> keywords = new ArrayList<>();
        for (int i = 0; i < Math.min(numKeywords, keywordScores.size()); i++) {
            int wordIndex = keywordScores.get(i).getID();
            keywords.add((String) vocabulary[wordIndex]);
        }
        
        return keywords;
    }
    
    private List<String> splitIntoSentences(String text) {
        return Arrays.stream(text.split("[。!?!?;;\n]"))
                    .map(String::trim)
                    .filter(s -> !s.isEmpty())
                    .collect(Collectors.toList());
    }

    public static void main(String[] args) throws Exception {
        AdvancedLDAChunking advancedChunker = new AdvancedLDAChunking(4);
        
        String bookChapter = """
            人工智能的历史可以追溯到20世纪50年代。图灵测试提出了机器智能的判断标准。
            达特茅斯会议正式确立了人工智能这一学科。早期AI研究集中在符号推理和问题求解。
            
            专家系统在80年代成为主流。MYCIN系统能够进行医学诊断。知识表示成为研究重点。
            同时,神经网络研究也在进行。感知机模型被提出但存在局限性。
            
            90年代统计学习方法兴起。支持向量机在分类任务中表现优秀。机器学习开始受到关注。
            数据驱动的范式逐渐取代知识工程方法。
            
            21世纪深度学习革命到来。大规模数据和算力推动神经网络发展。
            ImageNet竞赛展示了深度学习的潜力。GPU加速了模型训练过程。
            """;
        
        System.out.println("=== 进阶LDA主题分块 ===");
        List<TopicAwareChunk> chunks = advancedChunker.advancedTopicChunking(bookChapter, 3);
        
        for (TopicAwareChunk chunk : chunks) {
            System.out.println("\n--- 主题分块 ---");
            System.out.println("主题ID: " + chunk.getTopicId());
            System.out.println("置信度: " + String.format("%.3f", chunk.getConfidence()));
            System.out.println("关键词: " + String.join(", ", chunk.getKeywords()));
            System.out.println("句子范围: " + chunk.getStartSentence() + " - " + chunk.getEndSentence());
            System.out.println("内容: " + chunk.getText());
        }
    }
    
    // 数据类
    public static class DocumentSegment {
        private final String id;
        private final String text;
        private final int startSentence;
        private final int endSentence;
        
        public DocumentSegment(String id, String text, int start, int end) {
            this.id = id;
            this.text = text;
            this.startSentence = start;
            this.endSentence = end;
        }
        
        public String getId() { return id; }
        public String getText() { return text; }
        public int getStartSentence() { return startSentence; }
        public int getEndSentence() { return endSentence; }
    }
    
    public static class TopicAwareChunk {
        private final String text;
        private final int topicId;
        private final double confidence;
        private final List<String> keywords;
        private final int startSentence;
        private final int endSentence;
        
        public TopicAwareChunk(String text, int topicId, double confidence, 
                             List<String> keywords, int start, int end) {
            this.text = text;
            this.topicId = topicId;
            this.confidence = confidence;
            this.keywords = keywords;
            this.startSentence = start;
            this.endSentence = end;
        }
        
        // Getters
        public String getText() { return text; }
        public int getTopicId() { return topicId; }
        public double getConfidence() { return confidence; }
        public List<String> getKeywords() { return keywords; }
        public int getStartSentence() { return startSentence; }
        public int getEndSentence() { return endSentence; }
    }
}

4、高级分块策略

(1)、小-大分块

核心思想:小块用于检索,大块用于生成

优点:检索精度高(小块精准匹配)生成质量好(大块提供完整上下文)

缺点:存储成本较高、需要维护分块间的关系、实现复杂度较高

适用场景:复杂问答系统,需要兼顾解锁准确与生成完整性

示例代码:

import java.util.*;
import java.util.regex.Pattern;

public class AdvancedSmallBigChunking {

    public static class EnhancedChunkPair {
        private final TextSegment smallChunk;
        private final TextSegment bigChunk;
        private final String chunkId;
        private final List<String> keyPoints;  // 关键信息点
        private final String summary;          // 摘要

        public EnhancedChunkPair(String chunkId, TextSegment smallChunk, 
                               TextSegment bigChunk, List<String> keyPoints, String summary) {
            this.chunkId = chunkId;
            this.smallChunk = smallChunk;
            this.bigChunk = bigChunk;
            this.keyPoints = keyPoints;
            this.summary = summary;
        }

        // Getters
        public TextSegment getSmallChunk() { return smallChunk; }
        public TextSegment getBigChunk() { return bigChunk; }
        public String getChunkId() { return chunkId; }
        public List<String> getKeyPoints() { return keyPoints; }
        public String getSummary() { return summary; }
    }

    /**
     * 创建增强版小-大分块
     */
    public static List<EnhancedChunkPair> createEnhancedChunks(String text, 
                                                              int smallChunkSize,
                                                              int bigChunkSize) {
        List<EnhancedChunkPair> enhancedPairs = new ArrayList<>();
        
        // 1. 按段落分割大块
        List<TextSegment> bigChunks = splitByParagraphs(text, bigChunkSize);
        
        for (int i = 0; i < bigChunks.size(); i++) {
            TextSegment bigChunk = bigChunks.get(i);
            String bigChunkText = bigChunk.text();
            
            // 2. 提取关键信息
            List<String> keyPoints = extractKeyInformation(bigChunkText);
            String summary = generateSummary(bigChunkText, smallChunkSize);
            
            // 3. 创建小块(使用摘要或关键点)
            TextSegment smallChunk = createSmallChunk(bigChunkText, keyPoints, summary, smallChunkSize);
            
            EnhancedChunkPair pair = new EnhancedChunkPair(
                "enhanced_chunk_" + i,
                smallChunk,
                bigChunk,
                keyPoints,
                summary
            );
            
            enhancedPairs.add(pair);
        }
        
        return enhancedPairs;
    }

    /**
     * 按段落分割
     */
    private static List<TextSegment> splitByParagraphs(String text, int maxChunkSize) {
        List<TextSegment> chunks = new ArrayList<>();
        String[] paragraphs = text.split("\n\n");
        
        StringBuilder currentChunk = new StringBuilder();
        for (String paragraph : paragraphs) {
            if (currentChunk.length() + paragraph.length() > maxChunkSize && currentChunk.length() > 0) {
                chunks.add(TextSegment.from(currentChunk.toString()));
                currentChunk = new StringBuilder();
            }
            currentChunk.append(paragraph).append("\n\n");
        }
        
        if (currentChunk.length() > 0) {
            chunks.add(TextSegment.from(currentChunk.toString().trim()));
        }
        
        return chunks;
    }

    /**
     * 提取关键信息
     */
    private static List<String> extractKeyInformation(String text) {
        List<String> keyPoints = new ArrayList<>();
        String[] sentences = text.split("[。!?!?]");
        
        // 识别包含重要信息的句子
        Pattern keyPattern = Pattern.compile("(主要|关键|重要|核心|包括|分为|标准是|诊断|治疗)");
        
        for (String sentence : sentences) {
            if (keyPattern.matcher(sentence).find() && sentence.length() > 5) {
                keyPoints.add(sentence.trim());
            }
        }
        
        // 如果没找到关键句,使用前两个句子
        if (keyPoints.isEmpty() && sentences.length > 0) {
            for (int i = 0; i < Math.min(2, sentences.length); i++) {
                keyPoints.add(sentences[i].trim());
            }
        }
        
        return keyPoints;
    }

    /**
     * 生成摘要
     */
    private static String generateSummary(String text, int maxLength) {
        String[] sentences = text.split("[。!?!?]");
        if (sentences.length == 0) return "";
        
        // 简单摘要:取开头和结尾的句子
        List<String> summarySentences = new ArrayList<>();
        
        if (sentences.length >= 1) {
            summarySentences.add(sentences[0]); // 第一句
        }
        
        if (sentences.length >= 3) {
            summarySentences.add(sentences[sentences.length - 1]); // 最后一句
        }
        
        String summary = String.join("。", summarySentences) + "。";
        
        // 确保不超过最大长度
        if (summary.length() > maxLength) {
            summary = summary.substring(0, maxLength);
        }
        
        return summary;
    }

    /**
     * 创建小块
     */
    private static TextSegment createSmallChunk(String bigChunkText, 
                                               List<String> keyPoints, 
                                               String summary, 
                                               int maxLength) {
        // 优先使用关键点
        if (!keyPoints.isEmpty()) {
            String keyPointsText = String.join("。", keyPoints) + "。";
            if (keyPointsText.length() <= maxLength) {
                return TextSegment.from(keyPointsText);
            }
        }
        
        // 其次使用摘要
        if (summary.length() <= maxLength) {
            return TextSegment.from(summary);
        }
        
        // 最后使用开头部分
        return TextSegment.from(bigChunkText.substring(0, Math.min(maxLength, bigChunkText.length())));
    }

    public static void main(String[] args) {
        String medicalText = "
            糖尿病是一种慢性代谢性疾病,主要特征是血糖水平持续升高。
            
            病因与发病机制:
            糖尿病的发病与胰岛素分泌不足或胰岛素抵抗有关。1型糖尿病主要是由于自身免疫反应破坏胰岛β细胞。
            2型糖尿病则与遗传因素、肥胖、缺乏运动等生活方式密切相关。胰岛素抵抗导致细胞对胰岛素的敏感性下降。
            
            临床表现:
            典型症状包括多饮、多尿、多食和体重下降。患者可能出现视力模糊、疲劳、伤口愈合缓慢等症状。
            长期高血糖会导致多种并发症,包括心血管疾病、肾病、视网膜病变和神经病变。
            
            诊断标准:
            空腹血糖≥7.0mmol/L,或餐后2小时血糖≥11.1mmol/L,或糖化血红蛋白≥6.5%。
            确诊需要重复检测或结合临床症状综合判断。口服葡萄糖耐量试验也可用于诊断。
            
            治疗与管理:
            治疗包括生活方式干预和药物治疗。饮食控制要求低糖、低脂、高纤维饮食。
            规律运动有助于提高胰岛素敏感性。药物治疗包括口服降糖药和胰岛素注射。
            患者需要定期监测血糖,控制血压和血脂,预防并发症的发生。
            """;
        
        List<EnhancedChunkPair> enhancedPairs = createEnhancedChunks(medicalText, 150, 600);
        
        System.out.println("=== 增强版小-大分块 ===");
        for (EnhancedChunkPair pair : enhancedPairs) {
            System.out.println("\n--- 分块ID: " + pair.getChunkId() + " ---");
            System.out.println("关键点: " + pair.getKeyPoints());
            System.out.println("摘要: " + pair.getSummary());
            System.out.println("小块: " + pair.getSmallChunk().text());
            System.out.println("大块长度: " + pair.getBigChunk().text().length() + " 字符");
        }
    }
}
(2)代理式分块

核心思想:用LLM模拟人类却阅读,动态识别

优点:智能识别语义边界、动态适应不同文档结构、理解内容重要性和类型、处理高度非结构化文本

缺点:LLM调用成本高、处理速度较慢、实现复杂度高

适用场景:实验性项目,高度非结构化文本

示例代码:

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class AgenticChunking {

    private final ChatLanguageModel model;
    private final Map<String, List<TextChunk>> documentChunks = new ConcurrentHashMap<>();
    
    public AgenticChunking(ChatLanguageModel model) {
        this.model = model;
    }
    
    public static class DynamicChunk {
        private final String content;
        private final String chunkId;
        private final String title;
        private final List<String> keywords;
        private final ChunkType type;
        private final int importance;
        
        public DynamicChunk(String content, String chunkId, String title, 
                          List<String> keywords, ChunkType type, int importance) {
            this.content = content;
            this.chunkId = chunkId;
            this.title = title;
            this.keywords = keywords;
            this.type = type;
            this.importance = importance;
        }
        
        // Getters
        public String getContent() { return content; }
        public String getChunkId() { return chunkId; }
        public String getTitle() { return title; }
        public List<String> getKeywords() { return keywords; }
        public ChunkType getType() { return type; }
        public int getImportance() { return importance; }
    }
    
    public enum ChunkType {
        INTRODUCTION,    // 介绍
        KEY_CONCEPT,     // 关键概念
        EXAMPLE,         // 示例
        PROCEDURE,       // 步骤流程
        CONCLUSION,      // 结论
        REFERENCE,       // 参考
        OTHER           // 其他
    }
    
    /**
     * 代理式分块 - 使用LLM模拟人类阅读行为
     */
    public List<DynamicChunk> agenticChunking(String documentId, String text) {
        System.out.println("🤖 开始代理式分块分析文档: " + documentId);
        
        // 1. 文档整体分析
        DocumentAnalysis analysis = analyzeDocumentStructure(text);
        
        // 2. 动态识别分块边界
        List<ChunkBoundary> boundaries = identifyChunkBoundaries(text, analysis);
        
        // 3. 创建智能分块
        List<DynamicChunk> chunks = createIntelligentChunks(text, boundaries, documentId);
        
        // 4. 缓存分块结果
        documentChunks.put(documentId, chunks);
        
        System.out.println("✅ 完成分块分析,生成 " + chunks.size() + " 个智能分块");
        return chunks;
    }
    
    /**
     * 文档结构分析
     */
    private DocumentAnalysis analyzeDocumentStructure(String text) {
        String prompt = """
            请分析以下文档的结构和内容特点:
            
            %s
            
            请以JSON格式返回分析结果,包括:
            - document_type: 文档类型
            - main_topics: 主要主题列表
            - complexity_level: 复杂度级别(简单/中等/复杂)
            - estimated_chunks: 建议分块数量
            - key_sections: 关键章节列表
            """.formatted(text.substring(0, Math.min(1000, text.length())));
        
        String analysisJson = model.generate(prompt);
        
        // 解析LLM返回的分析结果(简化处理)
        return new DocumentAnalysis(
            "混合型文档",
            Arrays.asList("主要概念", "应用示例", "实施步骤"),
            "中等",
            5,
            Arrays.asList("引言", "核心内容", "总结")
        );
    }
    
    /**
     * 识别分块边界
     */
    private List<ChunkBoundary> identifyChunkBoundaries(String text, DocumentAnalysis analysis) {
        List<ChunkBoundary> boundaries = new ArrayList<>();
        
        // 将文本分成较小的段落进行处理
        List<String> paragraphs = splitIntoParagraphs(text);
        
        for (int i = 0; i < paragraphs.size(); i++) {
            String paragraph = paragraphs.get(i);
            
            // 使用LLM判断段落性质和重要性
            ChunkAnalysis chunkAnalysis = analyzeParagraph(paragraph, i, paragraphs.size());
            
            boundaries.add(new ChunkBoundary(
                i,
                chunkAnalysis.startIndex,
                chunkAnalysis.endIndex,
                chunkAnalysis.chunkType,
                chunkAnalysis.importance,
                chunkAnalysis.shouldStartNewChunk
            ));
        }
        
        return mergeRelatedBoundaries(boundaries, paragraphs);
    }
    
    /**
     * 分析单个段落
     */
    private ChunkAnalysis analyzeParagraph(String paragraph, int index, int totalParagraphs) {
        String prompt = """
            分析以下段落的性质和重要性:
            
            段落内容: %s
            段落位置: 第%d段/共%d段
            
            请判断:
            1. 段落类型(介绍、关键概念、示例、步骤、结论、参考、其他)
            2. 重要性(1-10分,10分最重要)
            3. 是否应该开始新的分块(true/false)
            
            以JSON格式返回结果。
            """.formatted(paragraph, index + 1, totalParagraphs);
        
        String response = model.generate(prompt);
        
        // 解析响应(简化处理)
        return new ChunkAnalysis(
            index * 100,
            (index + 1) * 100,
            determineChunkType(paragraph),
            calculateImportance(paragraph),
            shouldStartNewChunk(paragraph, index)
        );
    }
    
    /**
     * 创建智能分块
     */
    private List<DynamicChunk> createIntelligentChunks(String text, 
                                                     List<ChunkBoundary> boundaries, 
                                                     String documentId) {
        List<DynamicChunk> chunks = new ArrayList<>();
        StringBuilder currentChunk = new StringBuilder();
        ChunkType currentType = ChunkType.OTHER;
        List<String> currentKeywords = new ArrayList<>();
        int importance = 5;
        int chunkNumber = 0;
        
        for (ChunkBoundary boundary : boundaries) {
            String segment = text.substring(boundary.startIndex, boundary.endIndex);
            
            if (boundary.shouldStartNewChunk && currentChunk.length() > 0) {
                // 完成当前分块
                String chunkContent = currentChunk.toString();
                DynamicChunk chunk = new DynamicChunk(
                    chunkContent,
                    documentId + "_chunk_" + chunkNumber,
                    generateChunkTitle(chunkContent, currentType),
                    extractKeywords(chunkContent),
                    currentType,
                    importance
                );
                chunks.add(chunk);
                chunkNumber++;
                
                // 重置
                currentChunk = new StringBuilder();
                currentKeywords = new ArrayList<>();
            }
            
            currentChunk.append(segment).append("\n\n");
            currentType = boundary.chunkType;
            importance = Math.max(importance, boundary.importance);
        }
        
        // 添加最后一个分块
        if (currentChunk.length() > 0) {
            String chunkContent = currentChunk.toString();
            DynamicChunk chunk = new DynamicChunk(
                chunkContent,
                documentId + "_chunk_" + chunkNumber,
                generateChunkTitle(chunkContent, currentType),
                extractKeywords(chunkContent),
                currentType,
                importance
            );
            chunks.add(chunk);
        }
        
        return chunks;
    }
    
    /**
     * 生成分块标题
     */
    private String generateChunkTitle(String content, ChunkType type) {
        String prompt = """
            为以下内容生成一个简洁的标题(最多10个汉字):
            内容类型: %s
            内容: %s
            
            只返回标题,不要其他内容。
            """.formatted(type, content.substring(0, Math.min(200, content.length())));
        
        return model.generate(prompt).trim();
    }
    
    /**
     * 提取关键词
     */
    private List<String> extractKeywords(String content) {
        String prompt = """
            从以下内容中提取3-5个最重要的关键词:
            %s
            
            以逗号分隔返回关键词。
            """.formatted(content.substring(0, Math.min(300, content.length())));
        
        String response = model.generate(prompt);
        return Arrays.asList(response.split(","));
    }
    
    // 辅助方法
    private List<String> splitIntoParagraphs(String text) {
        return Arrays.stream(text.split("\n\n"))
                    .filter(p -> !p.trim().isEmpty())
                    .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
    
    private List<ChunkBoundary> mergeRelatedBoundaries(List<ChunkBoundary> boundaries, List<String> paragraphs) {
        // 合并相关的边界(简化实现)
        List<ChunkBoundary> merged = new ArrayList<>();
        ChunkBoundary current = boundaries.get(0);
        
        for (int i = 1; i < boundaries.size(); i++) {
            ChunkBoundary next = boundaries.get(i);
            
            if (shouldMerge(current, next)) {
                current = mergeTwoBoundaries(current, next);
            } else {
                merged.add(current);
                current = next;
            }
        }
        
        merged.add(current);
        return merged;
    }
    
    private boolean shouldMerge(ChunkBoundary a, ChunkBoundary b) {
        return a.chunkType == b.chunkType && a.importance == b.importance;
    }
    
    private ChunkBoundary mergeTwoBoundaries(ChunkBoundary a, ChunkBoundary b) {
        return new ChunkBoundary(
            a.paragraphIndex,
            a.startIndex,
            b.endIndex,
            a.chunkType,
            a.importance,
            a.shouldStartNewChunk
        );
    }
    
    // 简化的方法实现
    private ChunkType determineChunkType(String paragraph) {
        if (paragraph.contains("介绍") || paragraph.contains("概述")) return ChunkType.INTRODUCTION;
        if (paragraph.contains("例如") || paragraph.contains("比如")) return ChunkType.EXAMPLE;
        if (paragraph.contains("步骤") || paragraph.contains("流程")) return ChunkType.PROCEDURE;
        if (paragraph.contains("总结") || paragraph.contains("结论")) return ChunkType.CONCLUSION;
        return ChunkType.KEY_CONCEPT;
    }
    
    private int calculateImportance(String paragraph) {
        if (paragraph.contains("重要") || paragraph.contains("关键")) return 9;
        if (paragraph.contains("注意") || paragraph.contains("警告")) return 8;
        return 6;
    }
    
    private boolean shouldStartNewChunk(String paragraph, int index) {
        return index == 0 || paragraph.length() > 200 || 
               paragraph.matches(".*[一二三四五六七八九十]、.*");
    }

    // 数据类
    private static class DocumentAnalysis {
        final String documentType;
        final List<String> mainTopics;
        final String complexityLevel;
        final int estimatedChunks;
        final List<String> keySections;
        
        DocumentAnalysis(String type, List<String> topics, String complexity, 
                        int chunks, List<String> sections) {
            this.documentType = type;
            this.mainTopics = topics;
            this.complexityLevel = complexity;
            this.estimatedChunks = chunks;
            this.keySections = sections;
        }
    }
    
    private static class ChunkBoundary {
        final int paragraphIndex;
        final int startIndex;
        final int endIndex;
        final ChunkType chunkType;
        final int importance;
        final boolean shouldStartNewChunk;
        
        ChunkBoundary(int pIndex, int start, int end, ChunkType type, 
                     int importance, boolean newChunk) {
            this.paragraphIndex = pIndex;
            this.startIndex = start;
            this.endIndex = end;
            this.chunkType = type;
            this.importance = importance;
            this.shouldStartNewChunk = newChunk;
        }
    }
    
    private static class ChunkAnalysis {
        final int startIndex;
        final int endIndex;
        final ChunkType chunkType;
        final int importance;
        final boolean shouldStartNewChunk;
        
        ChunkAnalysis(int start, int end, ChunkType type, int importance, boolean newChunk) {
            this.startIndex = start;
            this.endIndex = end;
            this.chunkType = type;
            this.importance = importance;
            this.shouldStartNewChunk = newChunk;
        }
    }

    public static void main(String[] args) {
        // 使用模拟的LLM(实际项目中应使用真实的LLM)
        ChatLanguageModel model = new MockChatModel();
        
        AgenticChunking agenticChunker = new AgenticChunking(model);
        
        // 高度非结构化的文本示例
        String unstructuredText = """
            今天我想跟大家分享一些关于机器学习的心得。
            
            其实机器学习并不神秘,就像教小孩认东西一样。比如你要教计算机认识猫,
            就给它看很多猫的图片,它慢慢就学会了。
            
            这里有个重要的概念叫"特征提取"。计算机会自动找出猫的共同特点,
            比如尖耳朵、胡须什么的。
            
            具体怎么做呢?首先需要收集数据,然后清洗数据,接着选择模型,
            训练模型,最后评估效果。这个过程可能需要反复调整。
            
            我最近用TensorFlow做了一个项目,识别手写数字,准确率能达到98%!
            代码其实很简单,就几十行。
            
            总之,机器学习现在应用越来越广泛,从推荐系统到自动驾驶都在用。
            学习曲线虽然有点陡,但坚持下去会有收获的。
            
            对了,昨天看到一篇论文,说新的Transformer架构在自然语言处理中效果很好,
            大家可以关注一下这个方向。
            """;
        
        System.out.println("🚀 开始代理式分块演示");
        List<DynamicChunk> chunks = agenticChunker.agenticChunking("doc_001", unstructuredText);
        
        System.out.println("\n📊 分块结果分析:");
        for (DynamicChunk chunk : chunks) {
            System.out.println("\n=== 分块: " + chunk.getTitle() + " ===");
            System.out.println("类型: " + chunk.getType());
            System.out.println("重要性: " + chunk.getImportance() + "/10");
            System.out.println("关键词: " + String.join(", ", chunk.getKeywords()));
            System.out.println("内容预览: " + 
                chunk.getContent().substring(0, Math.min(100, chunk.getContent().length())) + "...");
            System.out.println("长度: " + chunk.getContent().length() + " 字符");
        }
    }
    
    // 模拟LLM类
    static class MockChatModel implements ChatLanguageModel {
        @Override
        public String generate(String userMessage) {
            // 模拟LLM响应
            if (userMessage.contains("分析以下文档的结构")) {
                return """
                    {
                        "document_type": "技术分享",
                        "main_topics": ["机器学习基础", "特征提取", "项目实践"],
                        "complexity_level": "中等",
                        "estimated_chunks": 4,
                        "key_sections": ["介绍", "核心概念", "实践案例", "总结"]
                    }
                    """;
            } else if (userMessage.contains("分析以下段落的性质")) {
                return """
                    {
                        "chunk_type": "INTRODUCTION",
                        "importance": 7,
                        "should_start_new_chunk": true
                    }
                    """;
            } else if (userMessage.contains("生成一个简洁的标题")) {
                return "机器学习入门分享";
            } else if (userMessage.contains("提取3-5个最重要的关键词")) {
                return "机器学习,特征提取,模型训练,TensorFlow";
            }
            return "{}";
        }
        
        @Override
        public AiMessage generate(SystemMessage systemMessage, UserMessage userMessage) {
            return new AiMessage(generate(userMessage.text()));
        }
        
        @Override
        public AiMessage generate(UserMessage userMessage) {
            return new AiMessage(generate(userMessage.text()));
        }
        
        @Override
        public List<AiMessage> generate(List<dev.langchain4j.data.message.ChatMessage> messages) {
            return Arrays.asList(new AiMessage(generate(messages.get(messages.size()-1).text())));
        }
    }
}

5、混合分块

核心思想:先用宏观策略(如结构化分块)粗切,再对过大的块进行精细策略(如递归或语义分块)细切

优点:兼顾宏观结构和微观语义、自适应不同内容密度、平衡分块质量和效率、处理复杂文档结构能力强

缺点:实现复杂度最高、需要多轮处理、参数调优复杂、计算资源消耗大

适用场景:结构复杂,内容密度不匀(如技术白皮书、企业年报)

示例代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentByHeaderSplitter;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.all.minilm.AllMiniLmL6V2EmbeddingModel;

import java.util.*;
import java.util.stream.Collectors;

public class HybridChunking {

    private final EmbeddingModel embeddingModel;
    private final int maxChunkSize;
    private final int minChunkSize;
    
    public HybridChunking(int maxChunkSize, int minChunkSize) {
        this.embeddingModel = new AllMiniLmL6V2EmbeddingModel();
        this.maxChunkSize = maxChunkSize;
        this.minChunkSize = minChunkSize;
    }
    
    public static class HybridChunk {
        private final String content;
        private final String chunkId;
        private final ChunkLevel level;
        private final String parentId;
        private final Map<String, Object> metadata;
        
        public HybridChunk(String content, String chunkId, ChunkLevel level, 
                          String parentId, Map<String, Object> metadata) {
            this.content = content;
            this.chunkId = chunkId;
            this.level = level;
            this.parentId = parentId;
            this.metadata = metadata;
        }
        
        // Getters
        public String getContent() { return content; }
        public String getChunkId() { return chunkId; }
        public ChunkLevel getLevel() { return level; }
        public String getParentId() { return parentId; }
        public Map<String, Object> getMetadata() { return metadata; }
    }
    
    public enum ChunkLevel {
        COARSE,     // 粗粒度分块
        FINE,       // 细粒度分块
        SEMANTIC    // 语义分块
    }
    
    /**
     * 混合分块策略
     */
    public List<HybridChunk> hybridChunking(String documentId, String text) {
        System.out.println("🔄 开始混合分块处理文档: " + documentId);
        System.out.println("文档总长度: " + text.length() + " 字符");
        
        List<HybridChunk> allChunks = new ArrayList<>();
        
        // 第一阶段:宏观策略 - 结构化分块(粗切)
        System.out.println("📊 第一阶段:结构化分块(粗切)");
        List<HybridChunk> coarseChunks = coarseStructuredChunking(documentId, text);
        System.out.println("生成粗粒度分块: " + coarseChunks.size() + " 个");
        
        // 第二阶段:对过大的块进行精细分块
        System.out.println("🔍 第二阶段:精细分块(细切)");
        for (HybridChunk coarseChunk : coarseChunks) {
            if (needsFineChunking(coarseChunk)) {
                List<HybridChunk> fineChunks = applyFineChunking(coarseChunk);
                allChunks.addAll(fineChunks);
            } else {
                allChunks.add(coarseChunk);
            }
        }
        
        // 第三阶段:对高密度内容进行语义分块
        System.out.println("🎯 第三阶段:语义分块(优化)");
        List<HybridChunk> finalChunks = applySemanticOptimization(allChunks);
        
        System.out.println("✅ 混合分块完成,总计: " + finalChunks.size() + " 个分块");
        return finalChunks;
    }
    
    /**
     * 第一阶段:宏观结构化分块
     */
    private List<HybridChunk> coarseStructuredChunking(String documentId, String text) {
        List<HybridChunk> coarseChunks = new ArrayList<>();
        
        Document document = Document.from(text);
        
        // 策略1:尝试按标题分块
        try {
            DocumentByHeaderSplitter headerSplitter = new DocumentByHeaderSplitter(
                Arrays.asList(), // 自动检测标题
                maxChunkSize * 2, // 较大的块大小
                maxChunkSize / 4  // 重叠
            );
            List<TextSegment> headerChunks = headerSplitter.split(document);
            
            if (headerChunks.size() > 1) {
                System.out.println("  使用标题分块策略,分块数: " + headerChunks.size());
                for (int i = 0; i < headerChunks.size(); i++) {
                    TextSegment segment = headerChunks.get(i);
                    Map<String, Object> metadata = new HashMap<>();
                    metadata.put("chunking_strategy", "header_based");
                    metadata.put("coarse_chunk_index", i);
                    
                    coarseChunks.add(new HybridChunk(
                        segment.text(),
                        documentId + "_coarse_" + i,
                        ChunkLevel.COARSE,
                        null,
                        metadata
                    ));
                }
                return coarseChunks;
            }
        } catch (Exception e) {
            System.out.println("  标题分块失败,尝试其他策略");
        }
        
        // 策略2:按段落分块
        DocumentByParagraphSplitter paragraphSplitter = new DocumentByParagraphSplitter(
            maxChunkSize * 2,
            maxChunkSize / 4
        );
        List<TextSegment> paragraphChunks = paragraphSplitter.split(document);
        
        System.out.println("  使用段落分块策略,分块数: " + paragraphChunks.size());
        for (int i = 0; i < paragraphChunks.size(); i++) {
            TextSegment segment = paragraphChunks.get(i);
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("chunking_strategy", "paragraph_based");
            metadata.put("coarse_chunk_index", i);
            metadata.put("content_density", calculateContentDensity(segment.text()));
            
            coarseChunks.add(new HybridChunk(
                segment.text(),
                documentId + "_coarse_" + i,
                ChunkLevel.COARSE,
                null,
                metadata
            ));
        }
        
        return coarseChunks;
    }
    
    /**
     * 判断是否需要精细分块
     */
    private boolean needsFineChunking(HybridChunk chunk) {
        String content = chunk.getContent();
        
        // 条件1:长度超过阈值
        if (content.length() > maxChunkSize) {
            return true;
        }
        
        // 条件2:内容密度过高
        double density = calculateContentDensity(content);
        if (density > 0.8 && content.length() > minChunkSize * 2) {
            return true;
        }
        
        // 条件3:包含多个主题
        if (containsMultipleTopics(content)) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 应用精细分块策略
     */
    private List<HybridChunk> applyFineChunking(HybridChunk coarseChunk) {
        String content = coarseChunk.getContent();
        List<HybridChunk> fineChunks = new ArrayList<>();
        
        System.out.println("  对分块 " + coarseChunk.getChunkId() + " 进行精细分块");
        System.out.println("  原始大小: " + content.length() + " 字符");
        
        // 根据内容特性选择精细分块策略
        if (isTechnicalContent(content)) {
            // 技术内容使用递归分块
            fineChunks.addAll(recursiveChunking(coarseChunk));
        } else if (isNarrativeContent(content)) {
            // 叙述性内容使用句子分块
            fineChunks.addAll(sentenceBasedChunking(coarseChunk));
        } else {
            // 默认使用固定长度分块
            fineChunks.addAll(fixedLengthChunking(coarseChunk));
        }
        
        System.out.println("  精细分块结果: " + fineChunks.size() + " 个子分块");
        return fineChunks;
    }
    
    /**
     * 递归分块策略
     */
    private List<HybridChunk> recursiveChunking(HybridChunk coarseChunk) {
        List<HybridChunk> chunks = new ArrayList<>();
        
        Document document = Document.from(coarseChunk.getContent());
        var recursiveSplitter = DocumentSplitters.recursive(maxChunkSize, maxChunkSize / 4);
        List<TextSegment> segments = recursiveSplitter.split(document);
        
        for (int i = 0; i < segments.size(); i++) {
            TextSegment segment = segments.get(i);
            Map<String, Object> metadata = new HashMap<>(coarseChunk.getMetadata());
            metadata.put("chunking_strategy", "recursive");
            metadata.put("fine_chunk_index", i);
            metadata.put("parent_chunk", coarseChunk.getChunkId());
            
            chunks.add(new HybridChunk(
                segment.text(),
                coarseChunk.getChunkId() + "_fine_" + i,
                ChunkLevel.FINE,
                coarseChunk.getChunkId(),
                metadata
            ));
        }
        
        return chunks;
    }
    
    /**
     * 句子分块策略
     */
    private List<HybridChunk> sentenceBasedChunking(HybridChunk coarseChunk) {
        List<HybridChunk> chunks = new ArrayList<>();
        String[] sentences = coarseChunk.getContent().split("[。!?!?;;]");
        
        List<String> currentChunkSentences = new ArrayList<>();
        int currentLength = 0;
        
        for (String sentence : sentences) {
            String trimmed = sentence.trim();
            if (trimmed.isEmpty()) continue;
            
            if (currentLength + trimmed.length() > maxChunkSize && !currentChunkSentences.isEmpty()) {
                // 完成当前分块
                String chunkContent = String.join("。", currentChunkSentences) + "。";
                chunks.add(createFineChunk(coarseChunk, chunkContent, chunks.size(), "sentence_based"));
                
                currentChunkSentences = new ArrayList<>();
                currentLength = 0;
            }
            
            currentChunkSentences.add(trimmed);
            currentLength += trimmed.length();
        }
        
        // 添加最后一个分块
        if (!currentChunkSentences.isEmpty()) {
            String chunkContent = String.join("。", currentChunkSentences) + "。";
            chunks.add(createFineChunk(coarseChunk, chunkContent, chunks.size(), "sentence_based"));
        }
        
        return chunks;
    }
    
    /**
     * 固定长度分块策略
     */
    private List<HybridChunk> fixedLengthChunking(HybridChunk coarseChunk) {
        List<HybridChunk> chunks = new ArrayList<>();
        String content = coarseChunk.getContent();
        
        int start = 0;
        int chunkIndex = 0;
        
        while (start < content.length()) {
            int end = Math.min(start + maxChunkSize, content.length());
            
            // 尝试在句子边界处切割
            if (end < content.length()) {
                int sentenceEnd = findSentenceBoundary(content, end);
                if (sentenceEnd > start) {
                    end = sentenceEnd;
                }
            }
            
            String chunkContent = content.substring(start, end);
            chunks.add(createFineChunk(coarseChunk, chunkContent, chunkIndex, "fixed_length"));
            
            start = end;
            chunkIndex++;
        }
        
        return chunks;
    }
    
    /**
     * 第三阶段:语义优化
     */
    private List<HybridChunk> applySemanticOptimization(List<HybridChunk> chunks) {
        List<HybridChunk> optimizedChunks = new ArrayList<>();
        
        for (int i = 0; i < chunks.size(); i++) {
            HybridChunk current = chunks.get(i);
            String content = current.getContent();
            
            // 检查分块是否过小,考虑与相邻分块合并
            if (content.length() < minChunkSize && i < chunks.size() - 1) {
                HybridChunk next = chunks.get(i + 1);
                String combined = content + " " + next.getContent();
                
                if (combined.length() <= maxChunkSize && isSemanticallyRelated(content, next.getContent())) {
                    // 合并分块
                    Map<String, Object> metadata = new HashMap<>(current.getMetadata());
                    metadata.put("chunking_strategy", "semantic_merged");
                    metadata.put("merged_chunks", Arrays.asList(current.getChunkId(), next.getChunkId()));
                    
                    optimizedChunks.add(new HybridChunk(
                        combined,
                        current.getChunkId() + "_merged",
                        ChunkLevel.SEMANTIC,
                        null,
                        metadata
                    ));
                    i++; // 跳过下一个分块
                    continue;
                }
            }
            
            optimizedChunks.add(current);
        }
        
        return optimizedChunks;
    }
    
    // 辅助方法
    private double calculateContentDensity(String text) {
        // 计算内容密度:非空格字符比例
        long nonSpaceChars = text.chars().filter(c -> !Character.isWhitespace(c)).count();
        return (double) nonSpaceChars / text.length();
    }
    
    private boolean containsMultipleTopics(String text) {
        // 简单判断是否包含多个主题(通过连接词判断)
        String[] topicIndicators = {"另外", "此外", "另一方面", "同时", "不仅如此"};
        int count = 0;
        for (String indicator : topicIndicators) {
            if (text.contains(indicator)) count++;
        }
        return count >= 2;
    }
    
    private boolean isTechnicalContent(String text) {
        // 判断是否为技术内容
        String[] technicalTerms = {"代码", "算法", "函数", "参数", "接口", "架构", "部署"};
        for (String term : technicalTerms) {
            if (text.contains(term)) return true;
        }
        return false;
    }
    
    private boolean isNarrativeContent(String text) {
        // 判断是否为叙述性内容
        String[] narrativeIndicators = {"首先", "然后", "接着", "最后", "例如", "比如"};
        for (String indicator : narrativeIndicators) {
            if (text.contains(indicator)) return true;
        }
        return false;
    }
    
    private int findSentenceBoundary(String text, int position) {
        // 在位置附近查找句子边界
        for (int i = position; i < Math.min(position + 50, text.length()); i++) {
            char c = text.charAt(i);
            if (c == '。' || c == '!' || c == '?' || c == ';') {
                return i + 1;
            }
        }
        return position;
    }
    
    private boolean isSemanticallyRelated(String text1, String text2) {
        // 使用嵌入模型计算语义相似度(简化版)
        try {
            // 在实际应用中应该使用真实的嵌入计算
            return calculateTextSimilarity(text1, text2) > 0.7;
        } catch (Exception e) {
            // 回退到基于词汇重叠的简单方法
            return calculateVocabularyOverlap(text1, text2) > 0.3;
        }
    }
    
    private double calculateTextSimilarity(String text1, String text2) {
        // 简化的相似度计算(实际应使用嵌入模型)
        Set<String> words1 = new HashSet<>(Arrays.asList(text1.split("\\W+")));
        Set<String> words2 = new HashSet<>(Arrays.asList(text2.split("\\W+")));
        
        Set<String> intersection = new HashSet<>(words1);
        intersection.retainAll(words2);
        
        Set<String> union = new HashSet<>(words1);
        union.addAll(words2);
        
        return union.isEmpty() ? 0.0 : (double) intersection.size() / union.size();
    }
    
    private double calculateVocabularyOverlap(String text1, String text2) {
        Set<String> words1 = new HashSet<>(Arrays.asList(text1.split("\\W+")));
        Set<String> words2 = new HashSet<>(Arrays.asList(text2.split("\\W+")));
        
        Set<String> intersection = new HashSet<>(words1);
        intersection.retainAll(words2);
        
        return (double) intersection.size() / Math.min(words1.size(), words2.size());
    }
    
    private HybridChunk createFineChunk(HybridChunk parent, String content, int index, String strategy) {
        Map<String, Object> metadata = new HashMap<>(parent.getMetadata());
        metadata.put("chunking_strategy", strategy);
        metadata.put("fine_chunk_index", index);
        metadata.put("parent_chunk", parent.getChunkId());
        
        return new HybridChunk(
            content,
            parent.getChunkId() + "_fine_" + index,
            ChunkLevel.FINE,
            parent.getChunkId(),
            metadata
        );
    }

    public static void main(String[] args) {
        // 创建混合分块器
        HybridChunking hybridChunker = new HybridChunking(500, 100);
        
        // 技术白皮书示例(结构复杂,内容密度不均)
        String technicalWhitepaper = """
            # 人工智能平台技术白皮书
            
            ## 第一章 概述
            本白皮书介绍了新一代人工智能平台的核心架构和关键技术。随着数字化转型的深入,企业对AI能力的需求日益增长。
            
            ## 第二章 核心架构
            平台采用微服务架构,包含数据预处理、模型训练、推理服务、监控管理等核心模块。
            
            数据预处理模块支持多种数据格式,包括结构化数据、文本、图像和视频。该模块提供数据清洗、特征工程、数据标注等功能。
            
            模型训练模块集成多种机器学习框架,支持分布式训练和自动超参数调优。训练过程可实时监控,支持断点续训。
            
            推理服务模块提供高并发、低延迟的模型推理能力。支持模型版本管理、A/B测试和流量控制。
            
            ## 第三章 关键技术
            平台的核心技术包括联邦学习、自动机器学习、模型解释性等。
            
            联邦学习技术使得在保护数据隐私的前提下进行联合建模成为可能。各参与方在不共享原始数据的情况下共同训练模型。
            
            自动机器学习技术大幅降低了AI应用的门槛。平台自动进行特征工程、模型选择和超参数优化。
            
            模型解释性技术帮助用户理解模型决策过程。提供特征重要性分析、决策路径可视化等功能。
            
            ## 第四章 应用场景
            平台已成功应用于金融风控、医疗诊断、智能制造等多个领域。
            
            在金融领域,平台帮助银行构建反欺诈系统,准确识别可疑交易。在医疗领域,辅助医生进行疾病诊断,提高诊断准确率。
            
            ## 第五章 总结与展望
            未来平台将进一步加强在可解释AI、小样本学习等方面的能力,推动AI技术的普惠化发展。
            """;
        
        System.out.println("🚀 开始混合分块演示");
        List<HybridChunk> chunks = hybridChunker.hybridChunking("whitepaper_001", technicalWhitepaper);
        
        System.out.println("\n📊 最终分块结果:");
        for (HybridChunk chunk : chunks) {
            System.out.println("\n=== 分块: " + chunk.getChunkId() + " ===");
            System.out.println("层级: " + chunk.getLevel());
            System.out.println("策略: " + chunk.getMetadata().get("chunking_strategy"));
            System.out.println("大小: " + chunk.getContent().length() + " 字符");
            System.out.println("父分块: " + chunk.getParentId());
            System.out.println("内容预览: " + 
                chunk.getContent().substring(0, Math.min(150, chunk.getContent().length())) + "...");
        }
        
        // 分析分块分布
        analyzeChunkDistribution(chunks);
    }
    
    private static void analyzeChunkDistribution(List<HybridChunk> chunks) {
        System.out.println("\n📈 分块分布分析:");
        
        Map<ChunkLevel, Long> levelCount = chunks.stream()
            .collect(Collectors.groupingBy(HybridChunk::getLevel, Collectors.counting()));
        
        System.out.println("分块层级分布:");
        levelCount.forEach((level, count) -> 
            System.out.println("  " + level + ": " + count + " 个分块"));
        
        Map<String, Long> strategyCount = chunks.stream()
            .collect(Collectors.groupingBy(
                chunk -> (String) chunk.getMetadata().get("chunking_strategy"),
                Collectors.counting()
            ));
        
        System.out.println("分块策略分布:");
        strategyCount.forEach((strategy, count) -> 
            System.out.println("  " + strategy + ": " + count + " 个分块"));
        
        // 大小分布
        IntSummaryStatistics stats = chunks.stream()
            .mapToInt(chunk -> chunk.getContent().length())
            .summaryStatistics();
        
        System.out.println("分块大小统计:");
        System.out.println("  最小: " + stats.getMin() + " 字符");
        System.out.println("  最大: " + stats.getMax() + " 字符");
        System.out.println("  平均: " + (int) stats.getAverage() + " 字符");
    }
}

5、如何选择最佳分块策略

第一步:基准策略        通用性强,效果文档,适合建立性能基线

第二步:结构优化        若文档有结构,优先使用结构化分块

第三步:精度瓶劲        检索不准或生产不完整时引入语义/小大分块

第四步:极度复杂        多格式、长文本、高密度场景的终极方案

6、技术选型

1、选择医疗文档分块策略时考虑:
(1)、文档类型:病历、检查报告、处方等

(2)、内容密度:临床描述 vs 检查数值

(3)、检索需求:症状检索 vs 诊断检索

(4)、隐私保护:敏感信息处理

2、选择法律文档分块策略时考虑:

(1)、文档类型:合同、法规、判决书等

(2)、条款结构:是否有明确的条款编号

(3)、引用关系:条款间的引用和依赖

(4)、法律效力:保持法律语句的完整性

最终推荐:
医疗文档:优先使用基于章节的语义分块
法律文档:优先使用条款级的句子分块

7、法律文档分块方案

1. 合同协议

推荐方案:条款分块 + 义务分块

  • 定义条款:按术语定义完整切割

  • 权利义务:按主体分块(甲方义务、乙方权利)

  • 违约责任:按违约情形分块

  • 争议解决:完整保留仲裁/诉讼条款

2. 法律法规

推荐方案:条文分块 + 章节分块

  • 法律条文:以"第X条"为边界切割

  • 章节结构:按编、章、节层级分块

  • 罚则部分:按处罚类型分块

3. 诉讼文书

推荐方案:诉请分块 + 事实分块

  • 诉讼请求:按请求事项单独分块

  • 事实理由:按时间顺序或逻辑关系切割

  • 证据清单:按证据类型分块

  • 法律依据:按法条引用分块

4. 判决书/裁定书

推荐方案:结构分块 + 论证分块

  • 当事人信息:按诉讼地位分块

  • 审理经过:按程序阶段切割

  • 法院认定:按事实认定分块

  • 判决主文:完整保留判决结果

5. 法律意见书

推荐方案:问题分块 + 分析分块

  • 咨询问题:按问题点单独切割

  • 法律分析:按分析逻辑分块

  • 结论建议:完整保留最终意见

8、分块大小建议

(1)、医疗文档
  • 临床描述:200-300字符

  • 检查结果:150-200字符

  • 诊断结论:100-150字符

  • 用药指导:80-120字符

(2)、法律文档
  • 合同条款:250-350字符

  • 法律条文:200-300字符

  • 事实陈述:300-400字符

  • 判决主文:150-250字符

9、选择原则

(1)、医疗文档优先考虑
  1. 临床逻辑连续性

  2. 诊断完整性

  3. 时间顺序保持

  4. 专业术语不分割

(2)、法律文档优先考虑
  1. 法律效力完整性

  2. 条款逻辑严密性

  3. 引用关系保持

  4. 权利义务对应性

10、综合案例

1. PDF类医疗文档

(1) 电子病历PDF

切割方式:章节结构切割

适用:医院电子病历系统导出的PDF
切割策略:
• 按标准病历章节切割:主诉→现病史→既往史→体格检查→辅助检查→诊断
• 每个章节作为独立分块
• 章节内过长的内容按语义段落二次切割
优势:保持临床逻辑完整性,便于按病历结构检索

(2) 检验报告PDF

切割方式:表格感知切割

适用:实验室检验报告、影像学报告PDF
切割策略:
• 识别PDF中的表格结构
• 按检验项目组切割:血常规、生化全套、免疫检查等
• 每个项目组的"项目-结果-参考范围"作为整体
• 异常指标单独标记并重点切割
优势:保证检验数值与项目的对应关系,避免数据割裂

(3) 诊断小结PDF

text

适用:病程记录PDF
切割策略:
• 初诊情况→按时间线切割
• 诊疗经过→按治疗阶段切割(手术期、药物治疗期等)
• 诊后情况→现状描述整体切割
• 随访建议→完整保留
优势:保持医疗过程的时间连续性

2. Word类医疗文档

(1) 科研论文Word

切割方式:学术结构切割

适用:医学研究论文、综述Word文档
切割策略:
• 摘要→完整一块
• 引言→按研究背景和目的切割
• 方法→按实验设计、统计方法切割
• 结果→按图表和对应描述切割
• 讨论→按论点分块
• 结论→完整保留
优势:符合学术阅读习惯,便于按论文结构检索

(2) 药品说明书Word

切割方式:安全信息切割

适用:药品说明书、用药指南Word
切割策略:
• 适应症→独立分块
• 用法用量→按给药途径切割
• 不良反应→按系统器官分类切割
• 禁忌警告→完整保留强调
• 药物相互作用→按相互作用药物对切割
优势:重点突出安全信息,便于快速查阅

3. PDF类法律文档

(1) 合同协议PDF

切割方式:条款层级切割

适用:商业合同、协议PDF
切割策略:
• 按"第X条"进行一级切割
• 条款内按"款"、"项"进行二级切割  
• 定义条款→术语和定义整体切割
• 附件内容→单独切割并与主文关联
优势:保持合同条款的法律效力完整性

(2) 法律法规PDF

切割方式:条文精确切割

适用:法律、法规、规章PDF
切割策略:
• 严格按"第X条"边界切割
• 条标题与条文内容不可分割
• 修正案内容与原条文关联切割
• 施行日期和适用范围附加到相关条文
优势:确保法律条文的准确性和权威性

(3) 判决书PDF

切割方式:司法结构切割

适用:法院判决书、裁定书PDF
切割策略:
• 当事人信息→按原被告分别切割
• 诉讼请求→完整切割不拆分
• 事实认定→按争议焦点切割
• 法院认为→按法律论证逻辑切割
• 判决主文→绝对完整不切割
优势:维护司法文书的严肃性和完整性

4. TXT类法律文书

(1) 法律条文TXT

切割方式:递归句子切割

适用:纯文本格式的法律法规
切割策略:
• 按"第X条"进行一级递归切割
• 条内复杂句子按逻辑分句二次切割
• 保持条件句的完整性(如果...则...)
• 引用条文与上下文一起切割
优势:适应纯文本格式,保持法律逻辑

(2) 合同文本TXT

切割方式:固定长度语义切割

适用:无格式合同的纯文本
切割策略:
• 固定长度:300-400字符/块
• 重叠比例:15-20%
• 在句子边界处优先切割
• 条款标题强制作为切割点
优势:平衡切割效率与语义完整性

Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐