Skip to content

fix(knowledge): 语义切分校验向量条数与句子一致 - #1147

Merged
xerrors merged 2 commits into
xerrors:mainfrom
lianbie:fix/semantic-embedding-alignment
Nov 4, 2026
Merged

xerrors merged 2 commits into
xerrors:mainfrom
lianbie:fix/semantic-embedding-alignment

Conversation

@lianbie

@lianbie lianbie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

修改内容

语义切分在向量条数与句子数不一致时会静默丢句,部分原文永远不进知识库。修改见 backend/yuxi/modules/knowledge/chunking/ragflow_like/utils/semantic_utils.py。

semantic_chunking_with_auto_clusters 在聚类后按位置配对:

labels = AgglomerativeClustering(...).fit_predict(embeddings)
for sentence, label, token_count in zip(sentences, labels, sentence_token_counts):

labels 的长度等于 embeddings 的行数,而 zip 未加 strict。条数不等时按较短方截断,后面的句子被直接丢弃——不报错、不告警,logger 只记录输入长度。

为什么会被触发

sklearn 不因条数不匹配报错。实测 4 句配 2 个向量:

labels -> [1, 0],长度 2
zip(sentences, labels, ...) -> 2 对,句子 'c' 和 'd' 静默丢失

且进入该函数的 embed_fn 是 semantic.py:56 的 select_embedding_model(...).encode——单次调用,不经 batch_encode;调用方注入自定义 embed_fn(单测、外部集成)时完全绕过模型层校验。

这比 #1143 修的向量错配更隐蔽:错配至少还能检索到错误文本,丢句是内容直接缺失。

决策

  1. 聚类前校验 len(embeddings) == len(sentences),不符抛出固定 ValueError。
  2. 同函数两条路径的 zip 均加 strict=True 兜底。

校验放在切分工具内部而非模型层:无论 embed_fn 来自模型还是调用方注入,契约都在此成立。#1143 / #1145 处理模型响应边界,本变更处理切分算法自身的输入契约,两者互补。

不选择「不足时降级为按预算合并」:那会把部分失败伪装成成功,用户无从得知有句子未参与切分。

详情见 docs/develop-guides/decisions/implemented/2026-11-04-semantic-chunking-embedding-alignment.md。

验证

  • backend/test/unit/knowledge/chunking/test_semantic_embedding_alignment.py,3 项:向量少于句子数报错、向量多于句子数报错、条数对齐时输出包含全部原文句子。
  • 与既有 test_semantic_chunking.py、test_semantic_tables.py 合并运行:24 项通过。
  • 修复前新增用例 2 项失败、1 项通过。
  • ruff check、ruff format --check、ruff check --select I 均通过。

未覆盖

  • 未调用真实 embedding 服务,未验证聚类质量。
  • 条数正确但向量语义错误不在本契约范围内。
  • 本机为 Windows,仅在隔离环境运行了相关用例;容器内全量 test/unit 需由 CI 执行。

Fixes #1146

semantic_chunking_with_auto_clusters 在聚类后按位置配对句子、簇标签与 token 数,
而 labels 的长度等于 embeddings 的行数。zip 未加 strict,条数不等时按较短方截断,
后面的句子被静默丢弃:不进任何 chunk,也无日志或异常。

sklearn 不会因条数不匹配报错;进入该函数的 embed_fn 来自 select_embedding_model
(...).encode,单次调用不经 batch_encode,调用方注入自定义 embed_fn 时更是完全
绕过模型层校验。

在聚类前校验条数,两处 zip 加 strict=True 兜底。

Fixes xerrors#1146
@lianbie

lianbie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Friendly bump on this one — it is mergeable: clean and ready for review.

Quick recap of what it fixes: semantic_chunking_with_auto_clusters builds labels from embeddings, then zips them against sentences without strict. When the provider returns fewer vectors than sentences (sklearn does not complain — it just returns fewer labels), the trailing sentences are dropped from every chunk with no error and no log. That means part of the source document never reaches the knowledge base at all.

semantic.py:56 injects select_embedding_model(...).encode, which is a single call and does not go through batch_encode, so the model-layer checks in #1143 / #1145 do not cover this path either.

The fix is a count check before clustering plus strict=True on both zips. Tests: 24 passed alongside the existing chunking suites; without the fix the two new cases fail.

Happy to adjust the contract if you would rather degrade to "merge by budget" instead of raising.

xerrors commented Nov 4, 2026

Copy link
Copy Markdown
Owner

这个防御校验可以保留,自定义 embed_fn 返回向量不足时确实会静默丢句。合并前请小改两处:

  • 修正说明:当前 OtherEmbedding.encode 已校验返回条数,默认调用链没有绕过模型层保护;向量多于句子也不会丢句。
  • 把现有短向量用例的 max_chunk_size 从 10 改为 30。实测 10 时旧代码先报聚类数超过样本数,30 才能复现静默丢失后两句。

调整现有用例即可,不需要再扩测试。

chunk_token_num=10 时簇数算成 4,sklearn 会先以「簇数多于样本数」拒绝,
测不到 zip 截断这条路径;改成 30 后簇数为 2,与返回的两条向量一致,
旧代码能正常聚类再静默丢掉后两句。
@lianbie

lianbie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

两处都改好了,谢谢实测。

1. 说明修正:你说得对。OtherEmbedding.encode 已经把 expected 和 dimension 传给 _extract_embeddings,默认链路(select_embedding_model(...).encode)有模型层保护,向量多于句子也不会丢句。我上一版描述里「默认链路也绕过了保护」是错的,实际受益的是自定义 embed_fn 的调用方——防御校验作为契约保留即可。

2. 用例预算:也确认了。句子长度是 13/13/15/14,合计 55:

  • max_chunk_size=10 → 簇数 ceil(55/10)=6 截到 4,但只给 2 条向量,sklearn 先报 Cannot extract more clusters than samples
  • max_chunk_size=30 → 簇数 ceil(55/30)=2,与 2 条向量一致,旧代码正常聚类后返回 ["Sentence one.", "Sentence two."],后两句静默消失

已把该用例改成 30,并在 docstring 里写清为什么是 30,方便以后不被人「优化」回 10。没有新增测试(f2eede58)。

@xerrors
xerrors merged commit 220ae2b into xerrors:main Nov 4, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 语义切分在向量条数不足时静默丢句,部分原文不进知识库

2 participants