微信扫码
添加专属顾问
探索企业级RAG系统分片优化的实战方案,分享三种高效策略的实现逻辑与代码结构。 核心内容: 1. RAG系统中分片优化的必要性与价值 2. 固定窗口、语义、命题三种分片策略的详细实现 3. 完整的项目代码结构与服务设计解析
失踪人口回归,这段时间很忙,不仅是在做项目优化还得探索所谓的口播自动化编程,后面可以跟大家一起交流。
最简单有效非demo的手段
云厂商永远是你值得信赖的伙伴,花点钱可以解决的问题没必要写代码。
使用策略模式 实现了三种方式,固定窗口,语义,命题三种策略。在实际的开发过程中可以参考并延伸。
bootstrap/rag/chunking/ChunkingStrategyType.java | ||
bootstrap/rag/chunking/ChunkingStrategy.java | ||
bootstrap/rag/chunking/ChunkingStrategyFactory.java | ||
bootstrap/rag/chunking/ChunkingRequest.java | ||
bootstrap/rag/chunking/ChunkingResult.java | ||
bootstrap/rag/chunking/DocumentChunk.java | ||
bootstrap/rag/chunking/impl/SlidingWindowChunkingStrategy.java | ||
bootstrap/rag/chunking/impl/SemanticChunkingStrategy.java | ||
bootstrap/rag/chunking/impl/PropositionChunkingStrategy.java | ||
bootstrap/rag/chunking/MultimodalChunkBuilder.java | ||
bootstrap/admin/service/ChunkingService.java | ||
bootstrap/admin/service/DocumentProcessingJobService.java | ||
bootstrap/admin/controller/KnowledgeController.java |
在 RAG(Retrieval-Augmented Generation)系统中,文档分片是影响检索质量的第一道关卡。分片质量直接决定了:
最简单的做法是按固定字符数切割,但会带来严重问题:
原文:"...2024年公司营收达到50亿元。其中,|华东区域贡献了32%的份额,华|南区域贡献了28%..."
↑ 粗暴切割点 ↑
切割后,"华东区域贡献了32%的份额" 被拆成两半,检索 "华东区域营收占比" 时无法精准命中。
本项目实现了三种由粗到细的分片策略:
| SLIDING_WINDOW | |||
| SEMANTIC | |||
| PROPOSITION |
┌──────────────────────┐
│ 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() │
└──────────────────────┘
本模块采用 策略模式(Strategy Pattern) 定义算法族,用 工厂模式(Factory Pattern) 管理策略注册与获取。
关键设计决策:
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;
}
设计亮点 — 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// 关联资产ID列表
private Integer blockIndex; // 文档内顺序索引(多模态混合排序用)
private String sectionTitle; // 所属章节标题
private Integer pageNum; // 页码
private String pipelineStatus; // 管线状态
}
chunkType 枚举值说明:
TEXT | ||
SEMANTIC_SEGMENT | ||
PROPOSITION | ||
IMAGE | ||
TABLE_IMAGE |
metadata 策略专属字段:
// SEMANTIC 策略 metadata 示例
{
"breakpointType": "percentile",
"boundaryScore": 0.35, // 断点处的相似度
"mergedFromSmallChunks": true // 是否由小chunk合并而来
}
// PROPOSITION 策略 metadata 示例
{
"propositionRange": "3-7", // 命题索引范围
"propositionCount": 5 // 包含的命题数量
}
@Data
public class ChunkingResult {
private List// 分块文档列表
private ChunkingStrategyType strategyType; // 使用的策略类型
private String strategyVersion; // 策略版本
private long durationMs; // 处理耗时(毫秒)
private int totalChars; // 原始文档总字符数
private Map// 额外元数据
}
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; // 校验配置参数
MapgetDefaultConfig(); // 获取默认配置
default String getVersion() { return "v1"; } // 策略版本号
}
接口设计原则:
type() 用于工厂注册和查找,每个实现类返回唯一标识validate() 在执行分块前校验参数,防止运行时异常getDefaultConfig() 让前端可以展示推荐配置,降低使用门槛getVersion() 默认返回 "v1",策略升级时覆写文件:
com.isy.rag.bootstrap.rag.chunking.ChunkingStrategyFactory
@Slf4j
@Component
public class ChunkingStrategyFactory {
private final Mapnew HashMap<>();
// Spring 自动注入所有 ChunkingStrategy 实现类
public ChunkingStrategyFactory(List {
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
public void validateConfig(String strategyCode, Map { ... }
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
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]
模式一:段落感知(paragraphAware=true,默认)
private ListsplitByParagraph(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 过滤过小的尾部碎片模式二:纯固定大小(paragraphAware=false)
private ListsplitByFixedSize(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 {
// 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]
步骤1:句子分割
private ListsplitIntoSentences(String content) {
// 中英文标点统一处理:。!?;. ! ? ;
String[] parts = content.split("(?);
// 如果分割后句子太少(≤1),退化为按换行分割
if (sentences.size() 步骤2:逐句生成嵌入向量
List步骤3:计算相邻句子的余弦相似度
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));
}
步骤4:根据断点阈值确定切分边界
三种断点检测算法:
private ListfindBreakpoints(Listint amount) {
double threshold;
switch (type) {
case "percentile":
// 将相似度排序,取第 amount 百分位的值作为阈值
// amount=95 表示:只有最低5%的相似度点才被识别为断点
List
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).average().orElse(0);
double stdDev = Math.sqrt(similarities.stream()
.mapToDouble(s -> Math.pow(s - mean, 2)).average().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);
}
}
步骤5:合并句子为块
// 先按断点合并 → 过滤小于 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 ListsplitByMaxSize(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模型。"
);
}
语义切分必须依赖 Embedding 模型,没有配置时直接抛异常(而非降级),因为用户显式选择了语义切分,静默降级可能导致不符合预期的结果。
文件:
com.isy.rag.bootstrap.rag.chunking.impl.PropositionChunkingStrategy参考:NirDiamant/RAG_Techniques/proposition_chunking
将文档拆解为原子级命题(Atomic Proposition)——每个命题是一个独立可验证的事实陈述,然后将命题重新组装为适合检索的块。
原文: "GPT-4于2023年3月发布,参数量约为1.8万亿,训练成本超过1亿美元。"
提取命题:
- "GPT-4于2023年3月发布"
- "GPT-4的参数量约为1.8万亿"
- "GPT-4的训练成本超过1亿美元"
重组为块:
Chunk 0: "GPT-4于2023年3月发布\nGPT-4的参数量约为1.8万亿\nGPT-4的训练成本超过1亿美元"
为什么比直接切分更好?
步骤1:文档分批
private ListsplitIntoBatches(String content, int maxChars) {
String[] paragraphs = content.split("\n\n+");
// 按段落边界分批,每批不超过 maxInputCharsPerBatch(默认3000字符)
// 超长段落强制按 maxChars 切分
}
为什么要分批?因为 LLM 有输入长度限制,直接把整篇文档丢给 LLM 会导致截断或超时。
步骤2:LLM 提取命题
private ListextractPropositions(String text, int maxProps) {
String systemPrompt = "You are an expert at decomposing text into atomic propositions.\n"
+ "Rules:\n"
+ "1. Each proposition must be a complete, standalone fact.\n"
+ "2. Preserve specific details: numbers, dates, names, quantities.\n"
+ "3. Do not add information not present in the source text.\n"
+ "4. Output one proposition per line, prefixed with \"- \".\n"
+ "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);
}
}
}
Prompt 设计要点:
步骤3:命题重组
private ListreassemblePropositions(Listint 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);
}
}
为什么需要重组? 单个命题可能只有十几个字,直接作为 chunk 向量化后信息量不足。重组为包含多个相关命题的块,既有原子性又有足够上下文。
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 ListmergeAndSortChunks(List {
// 1. 合并
Listnew 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 的作用:图片 chunk 来自文档解析阶段,携带了解析时的 blockIndex(文档中的物理顺序),文本 chunk 没有这个信息。混合排序时,图片按原始位置插入,文本按顺序分配 blockIndex,确保最终顺序与原文阅读顺序一致。
// PRIMARY 关系:一个 IMAGE chunk 与其关联的资产
public void savePrimaryRelation(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
// 3. 图片分块(调用 MultimodalChunkBuilder)
Listnew ArrayList<>();
if (multimodalEnabled) {
List
imageChunks = multimodalChunkBuilder.buildImageChunks(analyzedAssets);
}
// 4. 合并排序(按 blockIndex 混合排序)
List
// 5. 保存到数据库(小事务)
chunkDOs = saveChunksInTransaction(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/save
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 | ||||
chunkOverlap | ||||
paragraphAware | ||||
minChunkSize |
breakpointThresholdType | ||||
breakpointThresholdAmount | ||||
minChunkSize | ||||
maxChunkSize |
breakpointThresholdAmount 含义:
maxInputCharsPerBatch | ||||
maxPropositionsPerBatch | ||||
fallbackStrategy | ||||
maxChunkSize | ||||
minChunkSize |
只需 3 步 即可添加新的分片策略:
// ChunkingStrategyType.java
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
// 3. 返回结果
long durationMs = System.currentTimeMillis() - startTime;
return ChunkingResult.of(chunks, type(), getVersion(), durationMs, content.length());
}
@Override
public void validate(Map {
// 校验配置参数
}
@Override
public MapgetDefaultConfig() {
Mapnew 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
| 处理速度 | |||
| 外部依赖 | |||
| Token 消耗 | |||
| 语义完整性 | |||
| 可解释性 | |||
| 推荐文档长度 |
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-07-14
9.7k Stars,阿里内部跑了几年的向量数据库开源了,不用起服务,import 就能用
2026-07-13
AI知识库从零到一:个人wiki+企业RAG方案
2026-07-10
千万文档级 RAG 如何逼近零幻觉
2026-07-09
字节跳动开源AI Agent「上下文数据库」,2.6万星,比传统RAG好用太多了
2026-07-09
企业级 RAG 的知识分层:实体、关系、属性与规则如何落库
2026-07-09
大模型负责聪明,本体负责靠谱
2026-07-08
企业AI三件套:语义层、动力层、决策层——少一件都做不出AI原生
2026-07-08
拆解2.8万Star开源项目Cognee:如何基于知识图谱做RAG和Agent记忆
2026-04-27
2026-04-23
2026-05-27
2026-04-20
2026-04-22
2026-05-14
2026-04-27
2026-04-30
2026-05-11
2026-06-18
2026-07-04
2026-06-23
2026-06-23
2026-06-15
2026-06-10
2026-06-10
2026-05-20
2026-05-18
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。