来源:互联网 更新时间:2026-06-12 14:09
聊一个实际工程中特别有感触的话题——企业级RAG系统中,文档分片优化到底该怎么落地。这期内容,直接分享三种从粗到细的分片策略,每种都附上完整的实现逻辑和代码结构,希望对正在做RAG优化的朋友有直接帮助。
这段时间确实忙得脚不沾地,一边在做项目优化,一边还在探索口播自动化编程的方向,后面有机会再跟大家细聊。
坦白说,这是整个RAG优化链条里,最简单有效、但也是最容易被忽略的非Demo级手段。毕竟云厂商确实省心省力,花点钱能解决的,真没必要自己写代码。

这次的项目,我们用策略模式实现了三种分片方式:
| 文件 | 路径 | 说明 |
|---|---|---|
| ChunkingStrategyType | bootstrap/rag/chunking/ChunkingStrategyType.ja va | 策略类型枚举 |
| ChunkingStrategy | bootstrap/rag/chunking/ChunkingStrategy.ja va | 策略接口 |
| ChunkingStrategyFactory | bootstrap/rag/chunking/ChunkingStrategyFactory.ja va | 策略工厂 |
| ChunkingRequest | bootstrap/rag/chunking/ChunkingRequest.ja va | 分块请求 |
| ChunkingResult | bootstrap/rag/chunking/ChunkingResult.ja va | 分块结果 |
| DocumentChunk | bootstrap/rag/chunking/DocumentChunk.ja va | 文档分块 |
| SlidingWindowChunkingStrategy | bootstrap/rag/chunking/impl/SlidingWindowChunkingStrategy.ja va | 滑动窗口实现 |
| SemanticChunkingStrategy | bootstrap/rag/chunking/impl/SemanticChunkingStrategy.ja va | 语义切分实现 |
| PropositionChunkingStrategy | bootstrap/rag/chunking/impl/PropositionChunkingStrategy.ja va | 命题切分实现 |
| MultimodalChunkBuilder | bootstrap/rag/chunking/MultimodalChunkBuilder.ja va | 多模态分块构建 |
| ChunkingService | bootstrap/admin/service/ChunkingService.ja va | 分块业务服务 |
| DocumentProcessingJobService | bootstrap/admin/service/DocumentProcessingJobService.ja va | 文档处理管线 |
| KnowledgeController | bootstrap/admin/controller/KnowledgeController.ja va | API 控制器 |
说句大实话,在RAG系统里,文档分片这个环节,直接决定了检索质量的生死线。分片质量好不好,直接影响三个核心指标:
最偷懒的做法是按固定字符数一刀切,但后果很典型:
原文:"...2024年公司营收达到50亿元。其中,|华东区域贡献了32%的份额,华|南区域贡献了28%..."
↑ 粗暴切割点 ↑
这一刀下去,“华东区域贡献了32%的份额”直接被腰斩。当你检索“华东区域营收占比”时,结果可想而知——只能找到“华东区域贡献了3”这种残片。
这次我们实现了三种从粗到细的分片策略,好比三层过滤网:
| 策略 | 核心思想 | 适用场景 | 依赖 |
|---|---|---|---|
SLIDING_WINDOW | 固定窗口 + 重叠区域 + 段落感知 | 通用场景,无需额外依赖 | 无 |
SEMANTIC | 嵌入向量相似度下降点作为切分边界 | 语义结构丰富的文档 | EmbeddingModel |
PROPOSITION | LLM 拆解为原子级命题再重组 | 需要精细事实检索的场景 | ChatModel |
┌──────────────────────┐
│ ChunkingStrategy │ ◄── 策略接口
│──────────────────────│
│ + type() │
│ + chunk(request) │
│ + validate(config) │
│ + getDefaultConfig() │
│ + getVersion() │
└──────────┬───────────┘
│ implements
┌────────────────┼────────────────┐
│ │ │
┌──────────▼─────┐ ┌───────▼──────┐ ┌──────▼────────┐
│ SlidingWindow │ │ Semantic │ │ Proposition │
│ ChunkingStrat. │ │ ChunkingStr.│ │ ChunkingStr. │
└────────────────┘ └──────────────┘ └───────────────┘
┌──────────────────────┐
│ ChunkingStrategyFactory │ ◄── 策略工厂
│──────────────────────│
│ - strategyMap │
│ + getStrategy(type) │
│ + listStrategies() │
│ + validateConfig() │
└──────────┬───────────┘
│ used by
┌──────────▼───────────┐
│ ChunkingService │ ◄── 业务服务层
│──────────────────────│
│ + chunk() │
│ + preview() │
│ + validateConfig() │
│ + listStrategies() │
└──────────────────────┘
整个模块的核心设计思路很清晰:用
几个关键的设计决策:
ChunkingStrategy 实现类都标注 @Component,Spring 会自动注入一个 List,工厂在构造时就能完成注册。Map 传入,支持灵活配置,无需硬编码。strategyVersion,方便后续升级和数据迁移。文件:com.isy.rag.bootstrap.rag.chunking.ChunkingRequest
@Data
@Builder
public class ChunkingRequest {
private String content; // 文档文本内容
private ChunkingStrategyType strategyType; // 分块策略枚举
private Map
// 类型安全的配置读取方法
public T getConfigValue(String key, Class type, T defaultValue) { ... }
}
getConfigValue 方法这个方法的巧妙之处在于实现了类型安全的配置读取,支持:
也就是说,前端传入的 JSON 数值,比如 500 可能被 Jackson 解析成 Integer 或 Long,都能被正确处理。
文件:com.isy.rag.bootstrap.rag.chunking.DocumentChunk
@Data
@Builder
public class DocumentChunk {
// ===== 基础字段 =====
private String content; // 分块内容
private int index; // 分块索引
private int charCount; // 字符数
private Integer tokenCount; // token数(估算)
private Integer startOffset; // 原文起始偏移
private Integer endOffset; // 原文结束偏移
private String chunkType; // TEXT / PROPOSITION / SEMANTIC_SEGMENT / IMAGE / TABLE_IMAGE
private String strategyVersion; // 策略版本
private Map
// ===== 多模态扩展字段 =====
private List assetIds; // 关联资产ID列表
private Integer blockIndex; // 文档内顺序索引(多模态混合排序用)
private String sectionTitle; // 所属章节标题
private Integer pageNum; // 页码
private String pipelineStatus; // 管线状态
}
chunkType 枚举值说明| chunkType | 来源策略 | 说明 |
|---|---|---|
TEXT | SLIDING_WINDOW | 普通文本分块 |
SEMANTIC_SEGMENT | SEMANTIC | 语义段落分块 |
PROPOSITION | PROPOSITION | 命题分块 |
IMAGE | MultimodalChunkBuilder | 图片描述分块 |
TABLE_IMAGE | MultimodalChunkBuilder | 表格/图表描述分块 |
metadata 策略专属字段// SEMANTIC 策略 metadata 示例
{
"breakpointType": "percentile",
"boundaryScore": 0.35, // 断点处的相似度
"mergedFromSmallChunks": true // 是否由小chunk合并而来
}
// PROPOSITION 策略 metadata 示例
{
"propositionRange": "3-7", // 命题索引范围
"propositionCount": 5 // 包含的命题数量
}
@Data
public class ChunkingResult {
private List chunks; // 分块文档列表
private ChunkingStrategyType strategyType; // 使用的策略类型
private String strategyVersion; // 策略版本
private long durationMs; // 处理耗时(毫秒)
private int totalChars; // 原始文档总字符数
private Map metadata; // 额外元数据
}
public enum ChunkingStrategyType {
SLIDING_WINDOW("SLIDING_WINDOW", "滑动窗口切分", "固定窗口+重叠区域,确保跨边界信息不丢失"),
SEMANTIC("SEMANTIC", "语义切分", "按语义边界切分,用嵌入向量相似度下降点作为切分边界"),
PROPOSITION("PROPOSITION", "命题切分", "用LLM将文档拆解为原子级命题,每个命题是独立可验证的事实陈述");
// fromCode() 方法:code 为 null 或无效时默认返回 SLIDING_WINDOW
}
文件:com.isy.rag.bootstrap.rag.chunking.ChunkingStrategy
public interface ChunkingStrategy {
ChunkingStrategyType type(); // 策略类型标识
ChunkingResult chunk(ChunkingRequest request); // 执行分块(核心方法)
void validate(Map
Map getDefaultConfig(); // 获取默认配置
default String getVersion() { return "v1"; } // 策略版本号
}
type() 用于工厂注册和查找,每个实现类返回唯一标识。validate() 在执行分块前校验参数,把运行时异常扼杀在摇篮里。getDefaultConfig() 让前端可以直接展示推荐配置,降低使用门槛。getVersion() 默认返回 "v1",策略升级时覆写即可。文件:com.isy.rag.bootstrap.rag.chunking.ChunkingStrategyFactory
@Slf4j
@Component
public class ChunkingStrategyFactory {
private final Map strategyMap = new HashMap<>();
// Spring 自动注入所有 ChunkingStrategy 实现类
public ChunkingStrategyFactory(List strategies) {
for (ChunkingStrategy strategy : strategies) {
strategyMap.put(strategy.type(), strategy);
log.info("注册分块策略: {} - {}", strategy.type().getCode(), strategy.type().getName());
}
}
public ChunkingStrategy getStrategy(ChunkingStrategyType type) { ... }
public ChunkingStrategy getStrategy(String strategyCode) { ... }
public List listStrategies() { ... }
public void validateConfig(String strategyCode, Map config) { ... }
public boolean hasStrategy(ChunkingStrategyType type) { ... }
}
Spring 容器启动时,SlidingWindowChunkingStrategy、SemanticChunkingStrategy、PropositionChunkingStrategy 都标注了 @Component,Spring 自动把它们收集到 List 中注入给工厂。工厂在构造函数中遍历并注册到 strategyMap。
@Component,文件:com.isy.rag.bootstrap.admin.service.ChunkingService
@Slf4j
@Service
@RequiredArgsConstructor
public class ChunkingService {
private final ChunkingStrategyFactory chunkingStrategyFactory;
public ChunkingResult chunk(String content, String strategyCode, String configJson) {
ChunkingStrategyType strategyType = ChunkingStrategyType.fromCode(strategyCode);
ChunkingStrategy strategy = chunkingStrategyFactory.getStrategy(strategyType);
Map config = parseConfig(configJson);
strategy.validate(config); // 先校验
ChunkingRequest request = ChunkingRequest.builder()
.content(content).strategyType(strategyType).config(config).build();
return strategy.chunk(request); // 再执行
}
public ChunkingResult preview(String content, String strategyCode, String configJson) {
return chunk(content, strategyCode, configJson); // 预览与正式分块逻辑相同
}
}
Controller → ChunkingService.chunk() → Factory.getStrategy() → Strategy.chunk()
↓
parseConfig() (JSON → Map)
↓
Strategy.validate()
文件:com.isy.rag.bootstrap.rag.chunking.impl.SlidingWindowChunkingStrategy
文档: [AAAAAA|BBBBBB|CCCCCC|DDDDDD|EEEEEE]
固定窗口无重叠:
Chunk 0: [AAAAAA]
Chunk 1: [BBBBBB] ← 边界处信息可能丢失
Chunk 2: [CCCCCC]
滑动窗口有重叠(overlap=2):
Chunk 0: [AAAAAA]
Chunk 1: [AABBBBBB] ← 重叠区保留跨边界信息
Chunk 2: [BBCCCCCC]
Chunk 3: [CCDDDDDD]
private List splitByParagraph(String content, int chunkSize, int overlap, int minChunkSize) {
String[] paragraphs = content.split("\n\n+"); // 按双换行分割段落
StringBuilder currentChunk = new StringBuilder();
for (String paragraph : paragraphs) {
// 如果加入当前段落会超过 chunkSize → 先保存当前块
if (currentChunk.length() + paragraph.length() > chunkSize && currentChunk.length() > 0) {
// 保存 & 处理重叠部分
String overlapText = currentChunk.substring(currentChunk.length() - overlap);
currentChunk = new StringBuilder(overlapText);
}
currentChunk.append(paragraph).append("\n\n");
// 超长段落强制分割
if (currentChunk.length() > chunkSize * 1.5) {
// 按 chunkSize - overlap 步长强制切分
}
}
}
chunkSize * 1.5,退化为固定大小切分。minChunkSize 过滤掉过小的尾部碎片。private List splitByFixedSize(String content, int chunkSize, int overlap, int minChunkSize) {
int step = chunkSize - overlap; // 步长 = 窗口大小 - 重叠区
for (int i = 0; i < content.length(); i += step) {
int end = Math.min(i + chunkSize, content.length());
String chunkText = content.substring(i, end).trim();
if (chunkText.length() >= minChunkSize) {
chunks.add(buildChunk(chunkText, chunkIndex++, i, end));
}
}
}
public void validate(Map config) {
// chunkSize 范围: 100 ~ 8000
// chunkOverlap 范围: 0 ~ chunkSize/2
// 为什么 overlap 上限是 chunkSize/2?因为超过一半会导致新窗口中大部分是重复内容
}
private int estimateTokens(String text) {
return text.length() / 2; // 粗略估算:中文约1.5字/token,英文约4字符/token
}
文件:com.isy.rag.bootstrap.rag.chunking.impl.SemanticChunkingStrategy参考:LangChain SemanticChunker
句子序列: S1 ──S2 ──S3 ──S4 ──S5 ──S6 ──S7 ──S8
相似度: 0.92 0.88 0.85 0.31 0.90 0.87 0.28
↑ ↑
主题切换点 主题切换点
切分结果: [S1 S2 S3 S4] | [S5 S6 S7] | [S8]
private List splitIntoSentences(String content) {
// 中英文标点统一处理:。!?;. ! ? ;
String[] parts = content.split("(?<=[。!?;.!?;])");
// 如果分割后句子太少(≤1),退化为按换行分割
if (sentences.size() <= 1) {
String[] lines = content.split("\n");
...
}
return sentences;
}
List embeddings = embeddingModel.embed(sentences);
private double cosineSimilarity(double[] a, double[] b) {
double dotProduct = 0.0, normA = 0.0, normB = 0.0;
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
三种断点检测算法:
private List findBreakpoints(List similarities, String type, int amount) {
double threshold;
switch (type) {
case "percentile":
// 将相似度排序,取第 amount 百分位的值作为阈值
// amount=95 表示:只有最低5%的相似度点才被识别为断点
List sorted = new ArrayList<>(similarities);
Collections.sort(sorted);
int idx = (int) Math.ceil(amount / 100.0 * sorted.size()) - 1;
threshold = sorted.get(idx);
break;
case "standard_deviation":
// 均值 - 标准差 × 倍数
// amount=95 → 倍数=0.95,相似度显著低于均值的点为断点
double mean = similarities.stream().mapToDouble(Double::doubleValue).a verage().orElse(0);
double stdDev = Math.sqrt(similarities.stream()
.mapToDouble(s -> Math.pow(s - mean, 2)).a verage().orElse(0));
threshold = mean - stdDev * (amount / 100.0);
break;
case "interquartile":
// Q1 - IQR × 倍数
// 基于四分位距,对异常值更鲁棒
double q1 = sorted.get(sorted.size() / 4);
double q3 = sorted.get(sorted.size() * 3 / 4);
double iqr = q3 - q1;
threshold = q1 - iqr * (amount / 100.0);
break;
}
// 相似度低于阈值的点即为断点
for (int i = 0; i < similarities.size(); i++) {
if (similarities.get(i) < threshold) breakpoints.add(i);
}
}
// 先按断点合并 → 过滤小于 minChunkSize 的 → 超过 maxChunkSize 的二次切分
// 合并小于 minChunkSize 的候选块与相邻块
for (int i = 0; i < candidateTexts.size(); i++) {
String text = candidateTexts.get(i);
mergeBuffer.append(text);
if (mergeBuffer.length() >= minChunkSize || i == candidateTexts.size() - 1) {
mergedTexts.add(mergeBuffer.toString().trim());
mergeBuffer = new StringBuilder();
}
}
// 超长块二次切分(优先在句号处断开)
private List splitByMaxSize(String text, int maxSize, int minSize) {
// 尝试在句号、分号等标点处断开
int bestPunc = Math.max(
Math.max(text.lastIndexOf("。", end), text.lastIndexOf(".", end)),
text.lastIndexOf(";", end)
);
if (bestPunc > start + minSize) {
end = bestPunc + 1;
}
}
if (embeddingModel == null) {
throw new IllegalStateException(
"语义切分需要EmbeddingModel,但当前未配置。请选择SLIDING_WINDOW策略或配置Embedding模型。"
);
}
语义切分
文件:com.isy.rag.bootstrap.rag.chunking.impl.PropositionChunkingStrategy参考:NirDiamant/RAG_Techniques/proposition_chunking
将文档拆解为
原文: "GPT-4于2023年3月发布,参数量约为1.8万亿,训练成本超过1亿美元。"
提取命题:
- "GPT-4于2023年3月发布"
- "GPT-4的参数量约为1.8万亿"
- "GPT-4的训练成本超过1亿美元"
重组为块:
Chunk 0: "GPT-4于2023年3月发布
GPT-4的参数量约为1.8万亿
GPT-4的训练成本超过1亿美元"
private List splitIntoBatches(String content, int maxChars) {
String[] paragraphs = content.split("\n\n+");
// 按段落边界分批,每批不超过 maxInputCharsPerBatch(默认3000字符)
// 超长段落强制按 maxChars 切分
}
为什么要分批?因为 LLM 有输入长度限制,直接把整篇文档丢给 LLM 会导致截断或超时。
private List extractPropositions(String text, int maxProps) {
String systemPrompt = "You are an expert at decomposing text into atomic propositions.
"
+ "Rules:
"
+ "1. Each proposition must be a complete, standalone fact.
"
+ "2. Preserve specific details: numbers, dates, names, quantities.
"
+ "3. Do not add information not present in the source text.
"
+ "4. Output one proposition per line, prefixed with "- ".
"
+ "5. If the text contains no verifiable facts, return an empty response.";
// 调用 ChatModel
ChatResponse chatResponse = chatModel.chat(chatRequest);
String responseText = chatResponse.aiMessage().text();
// 解析 LLM 输出
for (String line : lines) {
// 去除 "- " 或 "1. " 前缀
if (line.startsWith("- ")) {
line = line.substring(2).trim();
} else if (line.matches("^\d+\.\s+.*")) {
line = line.replaceFirst("^\d+\.\s+", "").trim();
}
if (!line.isEmpty() && line.length() > 5) {
propositions.add(line);
}
}
}
private List reassemblePropositions(List propositions, int maxChunkSize, int minChunkSize) {
// 按顺序合并命题直到达到 maxChunkSize
for (int i = 0; i < propositions.size(); i++) {
if (currentChunk.length() + prop.length() + 2 > maxChunkSize && currentChunk.length() > 0) {
// 保存当前块,记录 propositionRange 和 propositionCount
meta.put("propositionRange", propStartIndex + "-" + (i - 1));
meta.put("propositionCount", i - propStartIndex);
}
currentChunk.append(prop);
}
}
private ChunkingResult fallbackChunk(ChunkingRequest request) {
log.warn("[KB检索] 命题切分使用兜底策略: SLIDING_WINDOW");
SlidingWindowChunkingStrategy fallback = new SlidingWindowChunkingStrategy();
// 使用同样的 maxChunkSize 作为 chunkSize
ChunkingResult result = fallback.chunk(fallbackRequest);
result.setMetadata(Map.of(
"fallbackFrom", "PROPOSITION",
"fallbackReason", "ChatModel不可用或产出为空"
));
return result;
}
chatModel == null(未配置 LLM)。降级时会在 metadata 中标记 fallbackFrom 和 fallbackReason,方便后续排查。
文件:com.isy.rag.bootstrap.rag.chunking.MultimodalChunkBuilder
将图片分析结果(VLM 描述 + OCR 文本)生成 IMAGE / TABLE_IMAGE 类型的 chunk,并与文本 chunk 合并排序。
对于每个 DocumentAssetDO:
if (analysisStatus == 2) { // VLM 分析完成
→ 生成 IMAGE 或 TABLE_IMAGE chunk
→ content 使用 embeddingText(用于向量化)
→ metadata 包含: asset_id, page_num, image_type, keywords 等
}
else if (analysisStatus == 3 // VLM 失败
&& ocrText 不为空) { // 但有 OCR 结果
→ 降级生成简短 IMAGE chunk
→ content = "[图片]" + ocrText
→ metadata 标记 degraded=true, degrade_reason="VLM_FAILED_WITH_OCR"
}
else {
→ 不生成 chunk(跳过)
}
public List mergeAndSortChunks(List textChunks, List imageChunks) {
// 1. 合并
List allChunks = new ArrayList<>();
allChunks.addAll(textChunks);
allChunks.addAll(imageChunks);
// 2. 为没有 blockIndex 的文本 chunk 分配估算的 blockIndex
int maxBlockIndex = allChunks.stream()
.filter(c -> c.getBlockIndex() != null)
.mapToInt(DocumentChunk::getBlockIndex)
.max().orElse(-1);
for (DocumentChunk chunk : textChunks) {
if (chunk.getBlockIndex() == null) {
maxBlockIndex++;
chunk.setBlockIndex(maxBlockIndex);
}
}
// 3. 按 blockIndex 排序
allChunks.sort(Comparator.comparingInt(c -> c.getBlockIndex() != null ? c.getBlockIndex() : Integer.MAX_VALUE));
// 4. 重新分配全局递增的 chunk index
for (int i = 0; i < allChunks.size(); i++) {
allChunks.get(i).setIndex(i);
}
return allChunks;
}
blockIndex 的作用blockIndex(文档中的物理顺序),文本 chunk 没有这个信息。混合排序时,图片按原始位置插入,文本按顺序分配 blockIndex,确保最终顺序与原文阅读顺序一致。
// PRIMARY 关系:一个 IMAGE chunk 与其关联的资产
public void sa vePrimaryRelation(Long chunkId, Long assetId) {
ChunkAssetRelDO rel = new ChunkAssetRelDO();
rel.setChunkId(chunkId);
rel.setAssetId(assetId);
rel.setRelationType("PRIMARY");
chunkAssetRelMapper.insert(rel);
}
// 重建时清理
public void cleanupChunksAndRelations(Long docId) {
chunkAssetRelMapper.softDeleteByDocId(docId, System.currentTimeMillis());
}
文件:com.isy.rag.bootstrap.admin.service.DocumentProcessingJobService
上传文件 → 解析(PARSING) → 资产提取(EXTRACTING_ASSETS) → 图片分析(ANALYZING_IMAGES)
→ 分块(CHUNKING) → 向量化(EMBEDDING) → 索引(INDEXING) → 完成(DONE)
// ---- 阶段4: 分块 (小事务) ----
if (recoveryService.shouldRun(resumeStage, "CHUNKING")) {
// 1. 先清理旧分块和关系
multimodalChunkBuilder.cleanupChunksAndRelations(docId);
chunkMapper.deleteByDocId(docId);
// 2. 文本分块(调用 ChunkingService)
ChunkingResult chunkingResult = chunkingService.chunk(content, strategyCode, configJson);
List textChunks = chunkingResult.getChunks();
// 3. 图片分块(调用 MultimodalChunkBuilder)
List imageChunks = new ArrayList<>();
if (multimodalEnabled) {
List analyzedAssets = documentAssetMapper.selectByDocId(docId);
imageChunks = multimodalChunkBuilder.buildImageChunks(analyzedAssets);
}
// 4. 合并排序(按 blockIndex 混合排序)
List allChunks = multimodalChunkBuilder.mergeAndSortChunks(textChunks, imageChunks);
// 5. 保存到数据库(小事务)
chunkDOs = sa veChunksInTransaction(docId, document.getKbId(), allChunks);
}
// 确定分块策略和配置(文档级 > 知识库级)
String strategyCode = StrUtil.isNotBlank(document.getChunkStrategy())
? document.getChunkStrategy() : kb.getChunkStrategy();
String configJson = StrUtil.isNotBlank(document.getChunkConfigJson())
? document.getChunkConfigJson() : kb.getChunkConfigJson();
优先使用文档级配置,如果文档没有指定则使用知识库级配置。这允许同一知识库下的不同文档使用不同策略。
管线通过 ProcessRecoveryService 实现断点恢复:
ProcessRecoveryService.ResumeStage resumeStage = recoveryService.resolveResumeStage(docId);
if ("DONE".equals(resumeStage.getStartStage())) {
return; // 已完成,跳过
}
// 每个阶段执行前检查是否需要运行
if (recoveryService.shouldRun(resumeStage, "CHUNKING")) { ... }
如果处理在分块阶段中断,重启后会从分块阶段继续,无需重新解析。
GET /api/admin/kb/chunking-strategies
{
"code": "00000",
"data": [
{
"type": "SLIDING_WINDOW",
"name": "滑动窗口切分",
"description": "固定窗口+重叠区域,确保跨边界信息不丢失",
"version": "v1",
"defaultConfig": {
"chunkSize": 500,
"chunkOverlap": 50,
"paragraphAware": true,
"minChunkSize": 80
}
},
{
"type": "SEMANTIC",
"name": "语义切分",
"description": "按语义边界切分,用嵌入向量相似度下降点作为切分边界",
"version": "v1",
"defaultConfig": {
"breakpointThresholdType": "percentile",
"breakpointThresholdAmount": 95,
"minChunkSize": 120,
"maxChunkSize": 900
}
},
{
"type": "PROPOSITION",
"name": "命题切分",
"description": "用LLM将文档拆解为原子级命题,每个命题是独立可验证的事实陈述",
"version": "v1",
"defaultConfig": {
"maxInputCharsPerBatch": 3000,
"maxPropositionsPerBatch": 50,
"fallbackStrategy": "SLIDING_WINDOW",
"maxChunkSize": 500,
"minChunkSize": 50
}
}
]
}
POST /api/admin/kb/{kbId}/chunk-preview
Content-Type: application/json
{
"text": "这是需要预览分块的文本内容...",
"chunkStrategy": "SLIDING_WINDOW",
"chunkConfig": {
"chunkSize": 200,
"chunkOverlap": 50
}
}
{
"code": "00000",
"data": {
"strategyType": "SLIDING_WINDOW",
"strategyVersion": "v1",
"totalChunks": 5,
"totalChars": 1000,
"durationMs": 12,
"chunks": [
{
"index": 0,
"content": "这是需要预览分块的文本内容...",
"charCount": 200,
"tokenCount": 100,
"chunkType": "TEXT",
"strategyVersion": "v1",
"metadata": null
}
]
}
}
POST /api/admin/kb/sa ve
Content-Type: application/json
{
"name": "我的知识库",
"description": "使用语义切分",
"chunkStrategy": "semantic",
"chunkConfigJson": "{"breakpointThresholdType":"percentile","breakpointThresholdAmount":90}"
}
POST /api/admin/kb/{kbId}/upload
Content-Type: multipart/form-data
file:
chunkStrategy: proposition
chunkConfig: {"maxChunkSize": 600}
POST /api/admin/kb/document/{docId}/rebuild-chunks
GET /api/admin/kb/document/{docId}/progress
{
"docId": 123,
"parseStatus": 2,
"vectorStatus": 1,
"processStage": "DONE",
"processProgress": 100,
"chunkCount": 15
}
| 参数 | 类型 | 默认值 | 范围 | 说明 |
|---|---|---|---|---|
chunkSize | Integer | 500 | 100~8000 | 每个分块的目标字符数 |
chunkOverlap | Integer | 50 | 0~chunkSize/2 | 重叠区字符数 |
paragraphAware | Boolean | true | true/false | 是否感知段落边界 |
minChunkSize | Integer | 80 | > 0 | 最小分块字符数,过小的碎片被过滤 |
| 参数 | 类型 | 默认值 | 范围 | 说明 |
|---|---|---|---|---|
breakpointThresholdType | String | "percentile" | percentile / standard_deviation / interquartile | 断点检测算法 |
breakpointThresholdAmount | Integer | 95 | > 0 | 断点阈值参数(含义随类型变化) |
minChunkSize | Integer | 120 | > 0 | 最小分块字符数 |
maxChunkSize | Integer | 900 | > minChunkSize | 最大分块字符数(超长块二次切分) |
breakpointThresholdAmount 含义| 算法 | amount=95 的含义 |
|---|---|
| percentile | 取相似度分布的第95百分位作为阈值,只有最低5%的点是断点 |
| standard_deviation | 阈值 = 均值 - 0.95 × 标准差 |
| interquartile | 阈值 = Q1 - IQR × 0.95 |
| 参数 | 类型 | 默认值 | 范围 | 说明 |
|---|---|---|---|---|
maxInputCharsPerBatch | Integer | 3000 | 500~10000 | 每批输入LLM的字符数 |
maxPropositionsPerBatch | Integer | 50 | > 0 | 每批最大命题数 |
fallbackStrategy | String | "SLIDING_WINDOW" | - | 兜底策略名称 |
maxChunkSize | Integer | 500 | > 0 | 命题重组后的最大块字符数 |
minChunkSize | Integer | 50 | > 0 | 命题重组后的最小块字符数 |
只需要
// ChunkingStrategyType.ja va
public enum ChunkingStrategyType {
SLIDING_WINDOW(...),
SEMANTIC(...),
PROPOSITION(...),
MY_NEW_STRATEGY("MY_NEW_STRATEGY", "新策略名称", "新策略描述"); // 新增
...
}
@Slf4j
@Component // 关键:标注 @Component 让 Spring 自动注册
public class MyNewChunkingStrategy implements ChunkingStrategy {
@Override
public ChunkingStrategyType type() {
return ChunkingStrategyType.MY_NEW_STRATEGY;
}
@Override
public ChunkingResult chunk(ChunkingRequest request) {
long startTime = System.currentTimeMillis();
String content = request.getContent();
// 1. 读取配置(使用 getConfigValue 安全获取)
int myParam = request.getConfigValue("myParam", Integer.class, 100);
// 2. 实现你的分块算法
List chunks = ...
// 3. 返回结果
long durationMs = System.currentTimeMillis() - startTime;
return ChunkingResult.of(chunks, type(), getVersion(), durationMs, content.length());
}
@Override
public void validate(Map config) {
// 校验配置参数
}
@Override
public Map getDefaultConfig() {
Map config = new LinkedHashMap<>();
config.put("myParam", 100);
return config;
}
}
无需修改工厂、服务层或控制器——Spring 自动注入 + 工厂自动注册,新策略立即可用。
GET /api/admin/kb/chunking-strategies,新策略应该出现在列表中。
开始
│
├─ 是否有 Embedding 模型?
│ ├─ 否 → SLIDING_WINDOW(唯一选择)
│ └─ 是 ↓
│
├─ 是否有 ChatModel (LLM)?
│ ├─ 否 ↓
│ │ ├─ 文档语义结构是否丰富(多主题切换)?
│ │ │ ├─ 是 → SEMANTIC
│ │ │ └─ 否 → SLIDING_WINDOW(更简单快速)
│ └─ 是 ↓
│ ├─ 是否需要原子级事实检索?
│ │ ├─ 是 → PROPOSITION
│ │ └─ 否 ↓
│ │ ├─ 文档语义结构丰富?
│ │ │ ├─ 是 → SEMANTIC
│ │ │ └─ 否 → SLIDING_WINDOW
| 维度 | SLIDING_WINDOW | SEMANTIC | PROPOSITION |
|---|---|---|---|
处理速度 | 极快(纯字符串操作) | 较慢(需调 Embedding API) | 最慢(需调 LLM API 多次) |
外部依赖 | 无 | EmbeddingModel | ChatModel |
Token 消耗 | 0 | 每句一次 Embedding | 每批一次 Chat |
语义完整性 | 低(可能切断语义) | 高(按语义边界切分) | 最高(原子级事实) |
可解释性 | 高(规则明确) | 中(断点阈值可调) | 中(依赖 LLM 输出质量) |
推荐文档长度 | 任意 | 中长文档(>2000字) | 中短文档(LLM 输入限制) |
《Off Campus》第二季官宣:这对CP还在,但不再是主角
和平精英如何做到压枪稳-和平精英怎样才能压枪稳
下载浏览器app下载安装选择推荐
免费影视剧APP推荐
客单价碾压宝马奥迪!极氪5月交付新车34377辆:连续4个月双增长
儿子穿新中式现身大会堂 马斯克罕见用中文回应:他正在学习普通话
Elysium Above 履云录官网在哪下载 最新官方下载安装地址
DOTA2 TI时隔七年重返上海!门票6月10日开抢,国服享受优先购买!
HBO 奇幻剧《龙之家族》第三季定档 6 月 22 日,最终预告片曝光喉道海战
抖音最火沙雕男生网名(精选100个)
帅气继父网名女生可爱英文(精选100个)
网络热词聊污是什么意思
阿里发布Qwen3.7-Max大模型,全球第五、国产第一
SpaceX狂揽AI人才,马斯克亲自面试且不看简历背景
金铲铲之战s17六暗星卡莎阵容玩法构筑指南
免费看电影的软件推荐
我的末日校园海斗手游上线时间是哪天
名单曝光!库克、马斯克等将随团到访中国 黄仁勋不在其中
晨字沙雕网名大全女生(精选100个)
短剧《情绪超市》剧情介绍
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc