当前位置: 首页 > news >正文

文本分块策略与预处理

一、引言

小说知识库的质量,很大程度上取决于文本分块的质量。如果分块太大,单个块会包含过多不相关信息,检索精度下降;如果分块太小,又可能破坏语义完整性,导致检索结果无法支撑有效的RAG应用。在长文本的小说场景中,找到合适的分块策略尤为关键。

StoryVerse的文本处理流程经历了多轮迭代,从最初的简单按字符切分,到后来基于段落的语义保留策略,每个设计决策都经过仔细权衡。本文将从多个维度剖析这些设计思路。

二、文本预处理流程

2.1 文件读取与基础清洗

当用户上传小说文件后,系统首先将其读取为纯文本,并进行初步清洗。在实际处理中,需要考虑各种编码问题、换行符差异、特殊字符等。虽然当前实现相对简洁,但其中蕴含着重要设计考虑。

文本清洗有几个基本目标:一是统一换行符格式,将Windows的\r\n和老式Mac的\r统一为\n;二是移除无效的控制字符,保留有意义的文本内容;三是处理BOM(Byte Order Mark)标记,避免解析问题。这些虽然是细节工作,但对后续处理影响重大。

2.2 文本长度限制策略

系统对处理文本进行长度限制,这是一个重要的权衡:

private static final int MAX_PROCESSING_CHARS = 20000; private String limitProcessingText(String fullText) { if (fullText == null) { return ""; } if (fullText.length() <= MAX_PROCESSING_CHARS) { return fullText; } return fullText.substring(0, MAX_PROCESSING_CHARS); }

限制为20000字符的设计考虑了多重因素:首先,LLM的上下文窗口有限,过长的文本会导致处理成本过高或超时;其次,大多数小说的开篇部分已经足够引入主要角色和世界观设定,这对MVP阶段的角色扮演功能已经够用;最后,这也为后续的渐进式处理预留了空间。

在更完善的版本中,可以考虑先处理前20000字符建立基础知识库,然后在后台继续处理后续内容,逐步丰富知识库。这种策略平衡了即时可用性和完整性。

三、分块策略实现

3.1 基于段落的分块算法

核心的分块逻辑围绕着保持段落完整性设计:

private static final int MAX_CHUNK_CHARS = 1200; private List<NovelKnowledgeClient.PlotChunk> buildPlotChunks(String fullText) { List<NovelKnowledgeClient.PlotChunk> chunks = new ArrayList<>(); // 按段落分割,过滤空行 List<String> paragraphs = fullText.lines() .map(String::trim) .filter(line -> !line.isBlank()) .toList(); if (paragraphs.isEmpty()) { throw new BusinessException("解析后的小说文本为空"); } // 合并段落形成块 StringBuilder buffer = new StringBuilder(); int chunkIndex = 0; for (String paragraph : paragraphs) { // 避免截断段落 if (!buffer.isEmpty() && buffer.length() + paragraph.length() + 1 > MAX_CHUNK_CHARS) { chunks.add(new NovelKnowledgeClient.PlotChunk(buffer.toString(), chunkIndex++)); buffer.setLength(0); } if (!buffer.isEmpty()) { buffer.append('\n'); } buffer.append(paragraph); } if (!buffer.isEmpty()) { chunks.add(new NovelKnowledgeClient.PlotChunk(buffer.toString(), chunkIndex)); } return chunks; }

这个算法有几个精妙之处。首先,它按段落作为基本单位,确保单个段落不会被拆分到两个不同的块中。段落通常表达一个完整的意思,保持段落完整性对语义理解至关重要。

其次,分块决策是在添加新段落前进行的:if (!buffer.isEmpty() && buffer.length() + paragraph.length() + 1 > MAX_CHUNK_CHARS)。这个判断确保只有在添加当前段落会导致溢出时,才将缓冲区内容作为新块,然后开始新的缓冲区。这样可以尽可能地保持段落间的联系。

最后,为每个块分配chunkIndex,记录块在原文中的顺序。这个索引在后续检索中很有价值,因为有时最相关的信息可能不只是最相似的,还可能出现在相近的位置。

3.2 分块大小的经验选择

1200字符的限制是经过实验调整的结果。对于中文文本,1200字符大致相当于200-300个词语,这既足够包含一个完整的场景或对话,又不会让单个块包含太多不同的主题。

分块大小的选择需要考虑多种因素:Embedding模型的最大输入长度、向量检索的精度、下游LLM能处理的上下文窗口大小等。例如,如果使用的Embedding模型支持更大的输入,可能可以适当增加分块大小,以获得更好的语义连贯性。

值得注意的是,不同类型的文本可能需要不同的分块策略。对于对话密集的小说,可能需要更小的块来捕捉特定对话;对于描述性强的小说,可能需要稍大的块来保留环境和氛围描写。

四、知识提取中的文本处理

4.1 世界观提取中的文本选择

在构建世界观总结时,系统使用了小说全文但进行了截断:

MAX_WORLD_SUMMARY_CHARS = 12000 world_text = self._chat_completion( system_prompt=( "你是小说知识库整理助手。请基于小说正文提炼世界观总结。" "输出一段中文正文,不要标题,不要列表,不要解释。" ), user_prompt=( f"小说标题:{request_model.novel_title}\n\n" f"小说全文:\n{self._truncate_text(request_model.full_text, self.MAX_WORLD_SUMMARY_CHARS)}" ), )

限制为12000字符的设计是因为世界观设定通常出现在小说的前半部分,特别是开篇章节。通过限制输入长度,既控制了LLM调用成本,又避免了后面内容对世界观提取的干扰。

截断方法本身也有设计:

def _truncate_text(self, text: str, max_chars: int) -> str: if len(text) <= max_chars: return text return text[:max_chars] + "\n...[truncated]"

在截断处添加标记,让LLM知道文本被截断了,这可以微妙地影响模型行为,使其更谨慎地总结,避免做出超出给定文本范围的断言。

4.2 角色识别中的摘要策略

角色识别使用了不同的文本选择策略,它不仅使用全文摘要,还提供前几个剧情块的摘录:

MAX_CHARACTER_FULL_TEXT_CHARS = 6000 MAX_CHARACTER_CHUNKS = 8 MAX_CHARACTER_CHUNK_CHARS = 240 plot_excerpt = "\n\n".join( f"[chunk {item.chunk_index}] {self._truncate_text(item.text, self.MAX_CHARACTER_CHUNK_CHARS)}" for item in request_model.plot_chunks[: self.MAX_CHARACTER_CHUNKS] )

这种双管齐下的策略很重要。全文摘要提供了整体背景,而剧情块摘录则提供了更具体的角色出场和互动的细节。每个剧情块被截断为240字符,这样可以在有限的上下文窗口中提供更多样化的片段,增加模型识别到所有主要角色的机会。

为每个块添加[chunk index]前缀也很有意义,这可以让模型知道文本的相对顺序,可能有助于理解角色的出场顺序和关系发展。

五、向量化前的文本规范化

5.1 向量生成中的文本处理

在生成向量之前,系统不需要复杂的预处理,因为Embedding模型通常能够处理原始文本。但在构建本地兜底方案的摘要时,系统进行了空白字符规范化:

private String truncateText(String text, int maxChars) { if (text == null || text.isBlank()) { return ""; } String normalized = text.replaceAll("\\s+", " ").trim(); if (normalized.length() <= maxChars) { return normalized; } return normalized.substring(0, maxChars) + "..."; }

将所有空白字符序列替换为单个空格,确保文本的格式一致性。这对本地兜底很重要,因为它们直接向用户展示,格式整齐度会影响用户体验。

5.2 稳定ID生成中的文本处理

另一个重要的文本处理是用于生成稳定向量ID的slugify方法:

def _slugify(self, value: str) -> str: normalized = unicodedata.normalize("NFKC", value).strip().lower() slug = re.sub(r"[^0-9a-z\u4e00-\u9fff]+", "-", normalized) return slug.strip("-") or "unknown"

这里使用了unicodedata.normalize("NFKC")进行Unicode归一化,这确保了看起来相同但编码不同的字符(如全角和半角字符)能生成相同的ID。将文本转为小写,确保不区分大小写的一致性。

正则表达式[^0-9a-z\u4e00-\u9fff]+只允许数字、小写字母和中文字符,其他所有字符都被替换为连字符。这种设计既保持了足够的可识别性,又避免了特殊字符导致的问题。

六、分块质量的优化方向

6.1 基于语义边界的分块

当前基于段落和长度的策略虽然有效,但仍有改进空间。更智能的分块策略可以尝试识别文本中的自然语义边界,比如章节划分、场景转换、视角切换等,在这些边界处切分,而不是仅仅依赖长度。

例如,可以检测章节标题、明显的场景转换指示(如空行分隔、时间地点标记)等,优先在这些地方进行切分,使每个块更可能包含一个完整的语义单元。

6.2 重叠分块策略

在检索中,相关信息可能出现在分块边界附近。重叠分块(Overlapping Chunks)策略让相邻块有一定重叠,这样即使切分不完美,重要信息也更可能完整地出现在至少一个块中。

典型的重叠大小可以是分块大小的10-20%。当然,这会增加存储和计算成本,需要在质量和效率之间进行权衡。

6.3 分层分块设计

另一个方向是分层分块:同时维护几个不同粒度的分块方案,如短段落级、场景级、章级。在检索时,可以根据查询性质选择合适粒度的块进行搜索,或者融合不同粒度的检索结果。这种设计提供了更大的灵活性。

七、总结

文本分块和预处理是构建高质量知识库的基石,其重要性不亚于Embedding模型选择或向量数据库设计。StoryVerse的实现虽然简洁,但每个决策都有其考虑:基于段落的分块策略保持了语义完整性,精心选择的长度限制平衡了多种需求,各种文本规范化处理确保了系统的鲁棒性。

分块工作看似平凡,实则直接影响检索质量,进而影响整个RAG应用的效果。通过持续优化分块策略,结合语义边界识别、重叠分块、分层设计等技术,可以不断提升知识库的质量,为用户提供更准确、更相关的知识检索服务。

http://www.jsqmd.com/news/710782/

相关文章:

  • 鸿蒙应用如何测试?这两个工具必须掌握!
  • 从零预训练BERT模型的完整指南与实现
  • 2026年降AI工具处理速度对比:哪款工具最快出结果详细横评
  • 硬件指纹保护实战:三分钟掌握EASY-HWID-SPOOFER核心功能
  • 零代码自动化革命:5分钟用taskt告别重复工作,效率提升300%
  • 八大网盘直链下载终极指南:一键获取真实下载地址的完整教程
  • 2026年招牌广告灯箱实力厂商推荐,聚隆运灯箱为何成为连锁品牌首选,赋能商业未来的专业解决方案
  • BotVisibility Checker:基于37项清单的AI友好度网站审计代理
  • 2026 主流 RPA 产品全方位测评:国际厂商与国产信创 RPA 能力对比
  • 跨平台修复引擎:深度解析GMod性能优化技术方案
  • GRANT模型:3D任务调度与空间定位的融合技术
  • 2026年高含量皂苷冻干三七哪个牌子好?大品牌综合评测+选购避坑+血管养护优选指南 - 资讯焦点
  • 那些年我用过的“网红”开源项目
  • 基于确定性图与分层控制的复杂RAG智能体架构设计与实践
  • 2026年北京实测最新榜单:五大GEO服务商技术实力与落地效率综合横评 - GEO优化
  • 2026年有水票和桶押金的送水店微信小程序怎么做?哪家可以做? - 企业数字化改造和转型
  • 2026年食品科学论文降AI工具推荐:食品安全和营养研究部分降AI方案
  • OmenSuperHub:专为惠普OMEN游戏本打造的开源性能控制工具
  • 20252328 2025-2026-2 《Python程序设计》实验三报告
  • “放心住”标准发布:什么样的上海装修公司才敢承诺让你真正放心住 - 资讯焦点
  • Android开发:suspend函数、Flow、StateFlow详解
  • OpCore-Simplify:智能黑苹果配置工具的3大技术突破与实战指南
  • 南宁家长告别“押注式消费”:广西大学家教网何以十八年“零差评”? - 教育快讯速递
  • AI辅助写作普及背景下高校为什么要查AI率:政策背景深度解读
  • 嵌入模型训练与HRSA分析:从对比学习到表征相似性
  • 告别Selenium弹窗噩梦:用Playwright+Python实现无头浏览器文件下载(附完整代码)
  • “零增项”标杆家悦可可装饰凭借“五大承诺”成为上海省心装修口碑王 - 资讯焦点
  • Nexus MCP:基于MCP协议的AI智能调度器,实现多模型并行协同工作流
  • 浏览器端BIM革命:Three.js官方IFC加载器深度揭秘
  • 视频下载助手:这款Chrome插件让你轻松保存任何在线视频!