Repository navigation
fix(knowledge): 分块合并使用配置的分隔符 - #1150
Conversation
naive_merge 接收 delimiter 参数却从不使用:chunk_token_num <= 0 时硬编码 "\n" 拼接,预算路径每段也以 "\n" + sec 追加。结果是默认分块器 general 忽略调用方 配置的分隔符,并把分隔符本身替换为换行,chunk 内容与原文不一致;对空格敏感的 文本语义会被改变,向量索引随之偏移。 同一输入在 separator 分块器下行为正确,可作为对照。 按 separator = delimiter or "\n" 归一后两条路径共用:无预算路径传给 join_source_text,预算路径追加时使用它且首段不加分隔符,消除前导分隔符。 字符合并仍经 join_source_text,源位置映射不变。 Fixes xerrors#1148
|
Friendly bump on this one as well — also Recap: The budget path also emitted a leading separator ( Fix: normalise Separator is untouched: it splits and does not merge, so it was already correct. |
|
方向可以保留,但实测有两处回归,先修完再合并:
请区分切分分隔符和片段连接符,并在确定追加旧块还是开启新块后再拼接,保留 overlap 和自定义分隔符行为。只补这两类最小回归用例即可。 |
naive_merge 的 delimiter 是片段之间的连接符,切分已由调用方完成,两者不能混用。 上一版把 delimiter 默认设成配置值,导致两处回归: - 片段自己开启新块时仍带前导分隔符(general,delimiter="|"、num=2 时第二块变成 "|delta|epsilon|zeta"); - book/laws 的片段边界来自按行切分,行间换行属于原文结构,被 delimiter 覆盖后 "alpha\nbeta" 变成 "alpha|beta"。 改为在 add_chunk 里区分「并入已有块」和「开启新块」后再决定是否加连接符; delimiter 默认 None,表示片段边界来自文档结构,此时保留换行。book/laws 不再 把 delimiter 透传给 naive_merge,它们的切分与连接都按行/按句进行。 overlap 与自定义分隔符(反引号)行为未变。
|
两处回归都修好了(7eb22f03),谢谢实测。 根因: 回归 1(前导分隔符):原来在进循环前就拼好 if chunks[-1] and token_nums[-1] <= threshold:
text = chunks[-1] + sep + sec if sep else chunks[-1] + sec
...
prev = _remove_pdf_tags(chunks[-1])
start = int(len(prev) * (100 - overlap) / 100.0)
text = prev[start:] + sec # 新块不带前导分隔符
回归 2(行间换行被改写):
用例:按你说的只补这两类最小回归,共 2 条,在旧代码上均失败:
chunking 相关 26 个测试全过,overlap 与反引号自定义分隔符的行为没动。 |
|
对照 base
原先的前导分隔符和普通行间换行问题已修复。只补上述两类最小回归用例即可。 |
上一版还有两处回归: - overlap 截取上一块尾部时,尾部与当前片段直接拼接。overlap=25 时 "eta" + "gamma" 粘成 "etagamma"。尾部非空时同样要用连接符隔开。 - book/laws 完全不再透传 delimiter,导致反引号声明的自定义切分能力丢失, "alpha CUT beta CUT gamma" 不再被切分。 新增 custom_delimiter_config:只把反引号包裹的自定义切分配置透传给 naive_merge,普通值返回 None 由它回退到换行连接。这样按行取段的 book/laws 既能保留自定义切分,又不会把行间换行改写成分隔符配置。 overlap 截断本身仍是按字符截取(既有行为),本次只补回连接符。
|
两处都修好了(a0f7e606),谢谢实测。 回归 1(overlap 尾部粘连):确认是我引入的。base 里 prev = _remove_pdf_tags(chunks[-1])
start = int(len(prev) * (100 - overlap) / 100.0)
tail = prev[start:]
text = tail + sep + sec if tail and sep else tail + sec
回归 2(自定义切分丢失):这个是我上一版处理过头了。你说得对,不能直接删掉透传。改成只把反引号声明的自定义切分配置透传出去: def custom_delimiter_config(delimiter: str) -> str | None:
custom_delimiters = _extract_custom_delimiters(delimiter)
if not custom_delimiters:
return None
return "".join(f"`{item}`" for item in custom_delimiters)
对照 base 与修复后的完整输出(
(laws 那两行本来就走 用例:按你说的只补这两类最小回归,共 2 条,在 7eb22f0 上均失败、在 a0f7e60 上通过:
chunking 相关 28 个测试全过,ruff 检查与格式化通过。 |
修改内容
naive_merge接收delimiter参数,但常规合并路径从不使用它:chunk_token_num <= 0时硬编码"\n"拼接,预算路径每段也以"\n" + sec追加。后果是默认分块器
general忽略调用方配置的分隔符,并把分隔符本身替换为换行,chunk 内容与原文不一致。复现
通过公开入口
dispatcher.chunk_markdown,输入 40 个空格分隔的词,配置delimiter=" "、chunk_token_num=10:separatorgeneral(默认)\ngeneral的 chunk 数量由 token 预算决定(这是它的设计),本 PR 只修正分隔符被忽略与原文被改写这两点。另一处
预算路径的输出还带一个前导分隔符:
调用方
.strip()掩盖了它,但说明该路径的拼接未经验证。naive_merge此前没有任何单元测试覆盖。改动
backend/yuxi/modules/knowledge/chunking/ragflow_like/nlp.py:入口按separator = delimiter or "\n"归一,两条路径共用;预算路径首片段不加分隔符。backend/test/unit/knowledge/chunking/test_naive_merge_delimiter.py:4 项回归测试。2026-11-04-naive-merge-delimiter.md。字符合并仍经
join_source_text,source_positions映射不变,入库行号定位不受影响。delimiter为空时回退换行,与_iter_sections的默认语义一致。验证
test_semantic_chunking.py、test_chunk_source_lines.py合并运行:23 项通过。ruff check、ruff format --check、ruff check --select I均通过。未覆盖
general的 token 预算逻辑未改动。test/unit由 CI 执行。Fixes #1148