Skip to content

修复 Corpus chunks 计数严重偏离(Harness Engineering 849→14)— Phase 1 hotfix - #483

Closed
ThreeFish-AI wants to merge 1 commit into
feature/1.x.xfrom
ThreeFish-AI/fix-corpus-chunk-count
Closed

修复 Corpus chunks 计数严重偏离(Harness Engineering 849→14)— Phase 1 hotfix#483
ThreeFish-AI wants to merge 1 commit into
feature/1.x.xfrom
ThreeFish-AI/fix-corpus-chunk-count

Conversation

@ThreeFish-AI

Copy link
Copy Markdown
Owner

背景与问题

「Harness Engineering」Corpus 卡片显示 chunks: 849,但点开唯一文档详情仅 14 Chunks,hierarchical 切片(parent: 3000 / child: 750),用户此前删除过一个 Document。两端口径相差近 60 倍。

双重根因(事件级 RCA)

经代码核对,UI 默认走软删api.py:1684 hard_delete: bool = Query(default=False));list_documentsstatus == "active" 过滤(storage/service.py:338),所以软删 doc 在文档列表"消失",但 DB 行仍在(status='deleted')。

主因 A — Corpus chunks 计数 SQL 没有 JOIN docs 过滤软删

apps/negentropy/src/negentropy/knowledge/api.py:613-637 / 664-688

select(Corpus, func.count(Knowledge.id))
    .outerjoin(Knowledge, Knowledge.corpus_id == Corpus.id)
    ...

完全没有过滤 KnowledgeDocument.status——软删 doc 的全部 chunks(父+子)仍记入 corpus 计数。这是 849 数字偏离的真正主因

主因 B — 父子 chunks 计数口径与文档详情不一致

  • corpus 列表:父+子全计 (api.py:618-619)
  • 文档详情:(item.metadata or {}).get("chunk_role") != "child" 仅算父/leaf (api.py:1939)
  • hierarchical 父:子 ≈ 1:5,14 父 + ~70 子 ≈ 84,与 849 缺口剩约 765 来自软删 doc 的全部父+子 chunks

隐患 C/D(本 PR 不解决,留待 Phase 2/3)

  • C:硬删 (hard_delete=True) 未级联清理 Knowledge 行(FK 仅指向 Corpus,与 KnowledgeDocument 靠 source_uri 文本软关联),是潜在孤儿源头
  • D:软删 doc 的 chunks 仍可被 RAG 检索命中(is_enabled=true + searchable=true + 无 archived 标记)

修复方案(Phase 1 hotfix · 零迁移、即时生效)

抽取共享 helper

apps/negentropy/src/negentropy/knowledge/api.py 新增:

  • _user_facing_chunk_filter_clauses() 返回 (not_child_clause, doc_active_or_kg_clause)
  • _user_facing_chunk_count_subquery(corpus_id_expr) 返回相关标量子查询

语义:

  • coalesce(metadata->>'chunk_role', 'leaf') != 'child' 排除 child(与文档详情口径一致)
  • EXISTS(SELECT 1 FROM knowledge_documents d WHERE d.corpus_id=k.corpus_id AND d.app_name=k.app_name AND d.status='active' AND (d.gcs_uri=k.source_uri OR d.metadata->>'origin_url'=k.source_uri))source_uri IS NULL:前者排除软删/孤儿,后者保留 KG 类直连知识

4 处端点统一

端点 改动
list_corpora outerjoin + group_by → 相关标量子查询
get_corpus 同上
update_corpus 直接拼 filter clauses
get_dashboard 直接拼 filter clauses
delete_corpus 保留物理 count(删除审计日志,不变)

业界口径对齐

LangChain ParentDocumentRetriever[1] / LlamaIndex HierarchicalNodeParser[2] / AWS Bedrock KB Hierarchical Chunking[3] 均以顶层(非 child)chunks 为统计粒度;child 是检索实现细节,不暴露到产品计数。本仓 metadata.chunk_role 已为该口径预留正交语义,无需新建字段。

集成测试

新增 apps/negentropy/tests/integration_tests/knowledge/test_corpus_chunk_count_filter.py,6 象限场景串行覆盖:

场景 物理 用户面
hierarchical 5 父 + 25 子 30 5
非 hierarchical 10 leaf 10 10
软删 doc(关键复刻 849 vs 14):active 3 父 + 软删 7 父 + 软删 35 子 45 3
硬删孤儿:active 2 父 + 孤儿 20 父 22 2
KG NULL source_uri 4 leaf 4 4
真实世界混合:active 3 父+18 子 + 软删 5 父+25 子 + 孤儿 7 父 + KG 2 leaf 60 5

受现有 db_engine 函数级 fixture 与 asyncpg 连接池跨事件循环边界限制,采用「单 test 函数 + 多场景子断言」模式,每场景独立 app_name 命名空间隔离。

uv run pytest tests/integration_tests/knowledge/test_corpus_chunk_count_filter.py 通过。

影响面 & 兼容性

  • CorpusResponse / DashboardResponseknowledge_count: int 字段类型不变,UI 零改动
  • delete_corpus 审计日志仍记录物理 count(删除条数审计语义保持)
  • 既有 19 项 API 单测全数 pass
  • SQL 改动均为 Knowledge.metadata->>'chunk_role' JSONB 表达式 + 相关子查询;单 corpus chunks ≤ 万级无需新增索引(如未来体量大可参考 0029 加部分表达式索引)

Phase 化交付路线(本 PR 仅 Phase 1)

  • Phase 1(本 PR · Hotfix):SQL 口径修正 + 集成测试 + issue 文档;零迁移、即时生效;UI 数字立即恢复正常;单 PR revert 可回滚、零数据风险
  • Phase 2(独立 PR · 应用层级联)delete_document 硬删 chunks 级联清理 + 软删 archive chunks + reactivation 旧 chunks purge + 检索路径 archived 过滤复核
  • Phase 3(独立 PR · Schema FK + CLI):alembic 0030 加 Knowledge.document_id FK + ORM 同步 + 写入路径全量补丁 + 独立 CLI cleanup_orphan_knowledge dry-run/commit + 观测 metric

Test plan

  • pytest tests/integration_tests/knowledge/test_corpus_chunk_count_filter.py 6 象限场景全 pass
  • pytest tests/unit_tests/knowledge/test_api_corpus.py tests/unit_tests/knowledge/test_api_documents.py 19 项既有用例无回归
  • SQL 编译产物经 PG dialect 校验(EXISTS 子查询正确相关到外层 corpus 行)
  • docs/issue.md 追加 ISSUE-078 完整 RCA + 防范条款 + 同类问题影响
  • Reviewer staging 复现验证(执行 PR 描述末尾的 SQL 对比脚本,期望 Harness Engineering old_count=849, new_count=14

Staging SQL 对比脚本

```sql
SELECT name,
(SELECT COUNT() FROM negentropy.knowledge k WHERE k.corpus_id = c.id) AS old_count,
(
SELECT COUNT(
) FROM negentropy.knowledge k
WHERE k.corpus_id = c.id
AND COALESCE(k.metadata->>'chunk_role', 'leaf') <> 'child'
AND (
k.source_uri IS NULL
OR EXISTS (
SELECT 1 FROM negentropy.knowledge_documents d
WHERE d.corpus_id = k.corpus_id AND d.app_name = k.app_name
AND d.status = 'active'
AND (d.gcs_uri = k.source_uri OR d.metadata->>'origin_url' = k.source_uri)
)
)
) AS new_count
FROM negentropy.corpus c WHERE c.name = 'Harness Engineering';
```

参考文献(IEEE)

[1] LangChain, "ParentDocumentRetriever," LangChain Python Documentation, https://python.langchain.com/docs/how_to/parent_document_retriever/, 2024.
[2] J. Liu, "HierarchicalNodeParser — Auto-Merging Retriever," LlamaIndex Documentation, https://docs.llamaindex.ai/en/stable/examples/retrievers/auto_merging_retriever/, 2024.
[3] Amazon Web Services, "Hierarchical chunking — Knowledge Bases for Amazon Bedrock," AWS Documentation, https://docs.aws.amazon.com/bedrock/latest/userguide/kb-chunking-parsing.html, 2024.

🤖 Generated with Claude Code

主因 A: list_corpora / get_corpus / update_corpus / get_dashboard 四处计数 SQL 没有 JOIN
knowledge_documents 过滤 status='active',软删 doc 的全部父+子 chunks 仍被计入;
主因 B: 父子分片混算口径与文档详情 (chunk_role != 'child') 不一致,hierarchical
切片下放大近 6 倍偏差。两因叠加致 Harness Engineering Corpus 显示 849 vs 真实 14。

修复:
- 抽取共享 helper _user_facing_chunk_filter_clauses / _user_facing_chunk_count_subquery
  封装 chunk_role!=child + active doc 过滤 (含 source_uri IS NULL 的 KG 类直连放行)
- 4 处端点改用相关子查询,与文档详情口径完全一致
- 业界对齐 LangChain ParentDocumentRetriever / LlamaIndex HierarchicalNodeParser /
  AWS Bedrock KB 的「用户面 chunks = 顶层 (非 child)」口径
- 集成测试 6 象限场景覆盖 hierarchical / 非 hierarchical / 软删 / 孤儿 / KG /
  混合,单 test 多场景规避 conftest db_engine + asyncpg 跨循环边界

CorpusResponse / DashboardResponse 字段不变,UI 零改动;隐患 C (硬删未级联) 与
隐患 D (软删 chunks 仍可被检索) 留待 Phase 2/3 处理。

详见 docs/issue.md ISSUE-078。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
@ThreeFish-AI
ThreeFish-AI deleted the ThreeFish-AI/fix-corpus-chunk-count branch May 10, 2026 04:18
ThreeFish-AI added a commit that referenced this pull request Jul 5, 2026
…-2026-4372 处置) (#1057)

* fix(deps): ts-deepmerge 补丁地板至 8.0.0 修复 GHSA-87mf-gv2c-c62c (#481);

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(negentropy): pydantic-settings 升 2.14.2 + joserfc>=1.6.7 补丁地板修复 #482/#488;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(cognizes): joserfc/msgpack/python-socketio/python-engineio 补丁地板修复 #483/#486/#487/#490/#491;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(perceives): ISSUE-092 增补 CVE-2026-4372 威胁模型;transformers 钉 4.x + dismiss #492(marker 5.x 不兼容实测);

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
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.

1 participant