【Bug已解决】MarkdownHeaderTextSplitter splits nested custom headers into separate chunks when strip_hea…
【Bug已解决】MarkdownHeaderTextSplitter splits nested custom headers into separate chunks when strip_headersFalse 解决方案一、现象长什么样用 LangChain 的MarkdownHeaderTextSplitter按 Markdown 标题切分文档当设置strip_headersFalse即希望保留标题文本在块里且文档里有嵌套的自定义标题多级标题、或非标准标题写法时切分结果不对# 期望每个块都带着它之上的完整标题层级内容不丢失 [ {content: # H1\n## H2\n正文...}, ... ] # 实际嵌套/自定义标题被单独切成一个空块正文被拆到别的块 [ {content: ## H2}, {content: 正文...}, ... ] # H2 与正文分离具体表现只在strip_headersFalse时出现strip_headersTrue标题不保留时反而“正常”因为标题被丢掉了问题被掩盖。文档里有多级嵌套标题如#/##/###连续或同一级出现多次或自定义标题标记时某些标题被切成一个“只有标题、没有正文”的孤立块而正文去了下一个块。下游 RAG 检索时这些“只有标题的空块”会污染索引或正文块丢失了上下文标题。现象表明splitter 在“保留标题”模式下对嵌套标题的归属判断错了——标题没被正确“挂到”后续正文上。关键特征strip_headersFalse时嵌套/自定义标题被错误地切成独立块与正文脱节导致块内容结构破坏。二、背景MarkdownHeaderTextSplitter的作用是按 Markdown 标题层级把长文档切成块常用于 RAG 的文档预处理让每个块携带它所属的标题路径比如“第一章 第二节 正文”这样检索时块带有结构上下文。它有两个关键行为按标题切分遇到一个标题就结束当前块、开始新块。strip_headersTrue时标题文本不进入块内容只作为元数据False时标题文本要保留在块内容里便于直接阅读/拼接。正确的“保留标题”行为应该是当前块要包含“从当前层级到正文”的所有标题 正文。例如遇到## H2后是一段正文块应该是# H1\n## H2\n正文H1 是更早的上级应随 H2 一起带下来因为它们是这块内容的上下文。bug 出在splitter 在处理嵌套标题连续的多个同级/上级标题或自定义标题模式时把“标题行本身”当成了一个独立的切分点于是生成了“只有标题、没有正文”的块而真正的正文被推到了下一个块。本质是对“标题行是否自带正文、标题之间如何合并”的判断有缺陷。三、根因根因是MarkdownHeaderTextSplitter在strip_headersFalse模式下把每个标题行都当成独立切分边界没有把连续/嵌套标题与紧随的正文归并到同一个块导致标题被切成孤立空块标题行即切分点splitter 遇到标题就flush当前累积内容成块。当strip_headersFalse时标题文本要进块但如果紧接着又是一个标题嵌套当前块就只有“上一个标题”、没有正文于是产出空/标题块。未合并嵌套标题连续的# H1/## H2应该合并成“H1H2正文”一块但实现把它们逐个 flushH2 单独成块、正文又单独成块。自定义标题模式处理错用户自定义了标题识别正则比如把某些行当标题这些自定义标题在strip_headersFalse下同样被错误切分。strip_headersTrue掩盖问题标题不进块时孤立标题块只是“空块被丢弃”看似正常于是 bug 只在False时暴露。一句话strip_headersFalse时splitter 把标题行当作独立边界 flush没有把嵌套标题与正文归并导致标题被切成孤立块、与正文脱节。四、最小可运行复现下面用 Python 模拟“标题行 flush”vs“标题正文归并”的切分机理import re from typing import List, Dict def split_buggy(text: str, headers_to_split: List[str]) - List[Dict]: 错误每个标题行都 flush 成独立块。 chunks [] buf for line in text.splitlines(): if re.match(r^#{1,6} , line): if buf.strip(): chunks.append({content: buf.strip()}) buf line \n # 标题行另起下一行正文又分开 else: buf line \n if buf.strip(): chunks.append({content: buf.strip()}) return chunks def split_fixed(text: str, headers_to_split: List[str]) - List[Dict]: 修复标题行累积进当前块遇新标题才 flush标题正文归并。 chunks [] buf for line in text.splitlines(): if re.match(r^#{1,6} , line): # 标题总是归并进当前块只有“已有正文”才先 flush 上一块 if buf.strip() and \n in buf.strip(): # 已有完整块才 flush避免孤立标题块 chunks.append({content: buf.strip()}) buf buf line \n else: buf line \n if buf.strip(): chunks.append({content: buf.strip()}) return chunks doc # H1\n## H2\n正文内容\n## H3\n更多正文 print(buggy:, [c[content] for c in split_buggy(doc, [#, ##])]) # [# H1, ## H2\n正文内容, ## H3, 更多正文] - 标题孤立 print(fixed:, [c[content] for c in split_fixed(doc, [#, ##])]) # [# H1\n## H2\n正文内容, ## H3\n更多正文] - 归并正确buggy把# H1切成孤立块fixed把标题与正文正确归并——正是需要修的逻辑。五、解决方案第一层最小直接修复最小修复是在strip_headersFalse时标题行累积进当前块、仅当块已有正文时才在该标题处 flush避免产生只含标题的孤立块# markdown_header_splitter.py修复片段 def split_text(self, text: str) - List[Document]: chunks [] buf [] for line in text.splitlines(): if self._is_header(line): # 仅当已累积正文才 flush 上一块避免孤立标题块 if buf and any(not self._is_header(l) for l in buf): chunks.append(self._make_doc(buf)) buf [] buf.append(line) # 标题归并进当前块 else: buf.append(line) if buf: chunks.append(self._make_doc(buf)) return chunks这一层让strip_headersFalse下嵌套标题与正文正确归并到同一块不再出现孤立标题块。六、解决方案第二层结构性改进把“Markdown 标题切分如何归并标题与正文尤其 strip_headersFalse”收口成唯一的配置对象LangChainMdHeaderSplitPolicysplitter 读它from dataclasses import dataclass from typing import Tuple dataclass(frozenTrue) class LangChainMdHeaderSplitPolicy: MarkdownHeaderTextSplitter 切分归并的单一事实来源。 # strip_headersFalse 时标题必须归并进块不产生孤立标题块 merge_headers_into_chunk: bool True # 仅当块已含正文时才在该标题处 flush flush_only_if_has_body: bool True # 嵌套标题连续多级合并到同一块不被逐个切分 merge_nested_headers: bool True # 自定义标题模式同样适用上述规则 apply_to_custom_header_patterns: bool True # 代码评审卡点 forbidden_patterns: Tuple[str, ...] ( flush on every header line, header-only chunk allowed when strip_headersFalse, ) def should_flush(self, buf: list) - bool: if not self.merge_headers_into_chunk: return True # 只有块里已有非标题正文行才在该标题处 flush return self.flush_only_if_has_body and any( not self._is_header(l) for l in buf) def describe(self) - str: return strip_headersFalse 时标题归并进块、不产生孤立标题块 POLICY LangChainMdHeaderSplitPolicy() def plan_md_flush(buf: list, policy: LangChainMdHeaderSplitPolicy POLICY) - bool: return policy.should_flush(buf)所有 Markdown 标题切分都读POLICY归并语义被固化嵌套/自定义标题在strip_headersFalse下不再被切成孤立块。七、解决方案第三层断言 / CI 守护把“标题归并、不产生孤立块、嵌套合并”做成断言。下面用 pytest 守护import pytest def test_no_isolated_header_chunk(policy): # 块若只含标题行不应被 flush 成孤立块 assert policy.merge_headers_into_chunk is True assert header-only chunk allowed when strip_headersFalse \ in policy.forbidden_patterns def test_flush_only_with_body(policy): assert policy.flush_only_if_has_body is True # 只有标题的 buf 不应 flush assert policy.should_flush([# H1, ## H2]) is False # 含正文的 buf 应在新标题处 flush assert policy.should_flush([# H1, 正文]) is True def test_merge_nested(policy): assert policy.merge_nested_headers is True def test_custom_patterns(policy): assert policy.apply_to_custom_header_patterns is True def test_forbid_flush_every_header(policy): assert flush on every header line in policy.forbidden_patterns这五组断言锁住(1) 无孤立标题块(2) 仅含正文才 flush(3) 嵌套合并(4) 自定义模式适用(5) 禁止逐标题 flush。CI 跑通即代表strip_headersFalse切分结构正确。八、排查清单遇到MarkdownHeaderTextSplitter在strip_headersFalse下块结构错看是否孤立标题块块里只有标题没正文 → 标题被独立切分本题。确认 strip_headersFalseTrue时问题被掩盖。查 flush 逻辑是不是每个标题行都触发 flush没归并正文。改归并标题累积进块仅块含正文时在该标题处 flush。统一到LangChainMdHeaderSplitPolicyCI 断言禁止孤立标题块。覆盖嵌套/自定义标题多级标题和自定义模式同样归并。端到端切分后每块都含完整标题层级 正文无空块。九、小结MarkdownHeaderTextSplitter splits nested custom headers into separate chunks when strip_headersFalse的根因是MarkdownHeaderTextSplitter在strip_headersFalse保留标题文本模式下把每个标题行都当作独立切分边界 flush 成块没有把连续的嵌套标题与紧随的正文归并到同一块于是产生“只有标题、没有正文”的孤立块正文被推到下一个块块结构被破坏strip_headersTrue时标题不进块问题被掩盖。最小修复是让标题行累积进当前块、仅当块已含正文时才在该标题处 flush避免孤立标题块结构性改进是用唯一的LangChainMdHeaderSplitPolicy固化归并语义CI 用五组断言守护“标题归并、无孤立块、嵌套合并”。记住保留标题的切分器标题是块的上下文前缀而不是独立内容必须和正文归并否则 RAG 索引会被空块污染。

相关新闻

【Bug已解决】Related to #31887 (custom header pattern support) 解决方案

【Bug已解决】Related to #31887 (custom header pattern support) 解决方案

【Bug已解决】Related to #31887 (custom header pattern support) 解决方案 一、现象长什么样 在用 LangChain 的某些聊天模型/LLM 客户端(这一支与 issue #31887 关联)对接需要自定义请求头的模型服务时,发现客户端不允许注入自定义 header…

2026/10/14 0:42:18 阅读更多 →
springboot个人成长足迹与数据分析系统开发与设计

springboot个人成长足迹与数据分析系统开发与设计

1. 项目背景与意义在个人成长与职业发展过程中,我们常常面临一个挑战:如何系统性地记录、追踪和分析自己的学习轨迹、技能提升、项目经验以及时间投入?传统的笔记工具或零散的文件记录难以进行结构化管理和深度分析。因此,开发一个…

2026/10/13 23:53:46 阅读更多 →
OpenClaw架构解析:AI工程化实战中的工作流编排与算子设计

OpenClaw架构解析:AI工程化实战中的工作流编排与算子设计

1. 项目概述:为什么OpenClaw是AI工程师的“实战教科书”?最近在AI工程化社区里,OpenClaw这个名字的讨论热度持续攀升。如果你是一名AI工程师,或者正朝着这个方向努力,却感觉学了一堆算法理论,一到实际部署、…

2026/10/2 13:30:44 阅读更多 →

最新新闻

SpringBoot+Vue构建高效办公管理系统实战

SpringBoot+Vue构建高效办公管理系统实战

1. 项目背景与核心价值 办公管理系统作为企业数字化转型的基础设施,正在从传统的C/S架构向B/S架构快速迁移。这个基于SpringBootVue的前后端分离项目,完美契合了现代企业对高效协同、移动办公和低维护成本的核心需求。 我去年为一家中型科技公司实施类似…

2026/10/14 9:09:51 阅读更多 →
多技术栈融合的在线蛋糕商城系统架构实践

多技术栈融合的在线蛋糕商城系统架构实践

1. 项目概述与核心价值 在线蛋糕商城系统是烘焙行业数字化转型的典型解决方案,它通过多技术栈融合实现了从商品展示到订单履约的全流程线上化。这个项目最显著的特点在于采用了PHP、ASP.NET、Java三大后端技术体系,同时整合了SpringBoot、SSM等主流框架&…

2026/10/14 9:09:51 阅读更多 →
PS5全场景调校指南:从开箱设置到串流与存储

PS5全场景调校指南:从开箱设置到串流与存储

“AnyPS5”这个名字,我第一次看到时第一反应是“这怕不是个万能折腾工具”,后来仔细一想,把它理解成“一台PS5能不能适配你的全部使用场景”其实更有意思。我在玩PS5的这几年里,踩过的坑和摸索出的方法不算少,从新机开…

2026/10/14 9:09:51 阅读更多 →
SpringBoot+Vue高校实验室管理系统开发实践

SpringBoot+Vue高校实验室管理系统开发实践

1. 项目概述高校实验室教学管理系统是基于SpringBoot框架开发的一套综合性管理平台,旨在解决传统实验室管理中存在的信息孤岛、流程繁琐、资源利用率低等问题。作为一名有10年开发经验的Java全栈工程师,我在实际教学管理场景中经常遇到以下痛点&#xff…

2026/10/14 9:09:51 阅读更多 →
Oracle 19c OPatch升级实战:p6880880补丁包替换与避坑指南

Oracle 19c OPatch升级实战:p6880880补丁包替换与避坑指南

简介:此压缩包是Oracle 19c数据库的官方更新补丁,面向64位Linux平台,供DBA、系统管理员在修复已知缺陷、加固安全防护或进行版本维护时使用。补丁编号p6880880来自Oracle标准发布渠道,配套内置的OPatch工具,支持补丁安…

2026/10/14 9:09:51 阅读更多 →
Vue3调度系统:任务队列、优先级与更新机制解密

Vue3调度系统:任务队列、优先级与更新机制解密

Vue3架构设计里,调度系统是最容易被忽略、却又实打实决定应用流畅度的核心模块。很多人把Vue3的更新机制简单理解为“数据变了就通知视图更新”,但实际上,从响应式数据发生变化,到DOM真正被修改,中间隔着一整套复杂且有…

2026/10/14 9:08:50 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →