Conversation
LLM 抽取偶尔会把整段正文当成实体名。实测一篇 NASA 技术备忘录的表单封面页被抽出 一个 1672 字符的「实体」(内容是整段摘要),而 knowledge_graph_entities.name / normalized_name 是 varchar(512)。 后果被放大是因为写入方式:upsert_chunk_graph 把一个分块的所有实体放进**一条多行 INSERT**,一个值超长整批被 PostgreSQL 拒绝——该分块图谱全部丢失、graph_indexed 不 置位,于是永远停在 pending;每次重试都要再烧一次模型调用,还照样失败。目击的那次 构建停在 913/914,日志只有 write_failed=1,界面上看不出原因。 改动:在 normalize_extraction_result(落库前的规范化层)加长度守卫,丢弃越界的单位 而不是抛异常(抛异常会让整个分块的抽取都失败,比丢弃更糟): - 实体名/归一化名超过 512、标签超过 128 → 丢弃该实体,并一并丢弃引用它的关系 (否则会留下悬空关系);关系以裸字符串引用超长名时同样丢弃该关系 - 关系类型超过 256 → 丢弃该关系 - 丢弃数记入 metadata.dropped_entities / dropped_relations,避免静默降级 常量与列定义的一致性由单测断言,防止将来漂移。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
变更说明
问题:LLM 图谱抽取偶尔会把整段正文当成实体名。实测一例:一篇 NASA 技术备忘录的表单封面页里,模型把整段摘要抽成了一个「实体」,并通过
has abstract关系连到论文标题:而
knowledge_graph_entities.name/normalized_name是varchar(512)。后果被放大,而且不可自愈:
upsert_chunk_graph把一个分块的所有实体放进一条多行 INSERT,一个值超长,整批被 PostgreSQL 拒绝 —— 该分块的图谱全部丢失(这一批是 12 个实体、11 条关系)。mark_graph_structure_indexed也不执行,graph_indexed恒为 false。count_graph_pending_by_kb_id的判据就是graph_indexed IS NOT TRUE—— 于是这个分块永远停在 pending,每次「重试索引」都会重新抽一遍(再烧一次模型调用),然后以同样的原因再次失败。目击的那次构建停在 913/914,日志里只有一句
write_failed=1,界面上看不出原因。bug-fixbase.py+59/-4、测试 +157。工程主张与 Owner
Owner:
normalize_extraction_result(knowledge/graphs/extractors/base.py)。Owner:同上。
Owner:
metadata.dropped_entities/metadata.dropped_relations。Owner:
test_normalizer_limits_match_graph_column_lengths。验证情况
主张 1 + 2:真实失败数据上的前后对比
直接证据 / 命令:把生产库里那条真实抽取结果过一遍修复后的规范化:
负向案例:同一次改动之前,同样的输入产生 1672 字符的实体名,直接导致 PostgreSQL
StringDataRightTruncationError: value too long for type character varying(512)。结果:Passed
主张 1:真实链路端到端(原来永久卡死的分块)
直接证据:把修复部署到本地后重跑该知识库的图谱构建:
graph_indexedfalse(永久)truewrite_failed=1 remaining=1write_failed=0 remaining=0结果:Passed
主张 3:丢弃可观察
test_normalize_extraction_result_keeps_metadata_clean_without_drops断言无丢弃时metadata不变;有丢弃时写入两个计数;test_normalize_extraction_result_counts_dropped_entities_distinctly断言实体数按去重计、关系数按条计。主张 4:常量与列定义一致
test_normalizer_limits_match_graph_column_lengths直接比较MAX_ENTITY_NAME_LENGTH == KnowledgeGraphEntity.name.type.length等四处。主张 5:不回归
pytest test/unit/graphs -q→ 43 项通过(新增 8 项);ruff check→ All checks passed;git diff --check干净。Not run):pytest test/unit/knowledge在本环境有 3 项失败,全部是test_parser_facade.py的 XLS 用例,原因是本环境镜像未安装xlrd(ModuleNotFoundError: No module named 'xlrd'),与本改动无关(这些用例不导入图谱模块)。简化 / 删除验收
不涉及(
bug-fix)。独立语义 Review
已执行(本改动伴随的一次自检中发现并修掉了一处自身缺口):
entities[]里),守卫看不到它,仍会抛错让整个分块的抽取失败。已补上这条路径(引用名本身超过实体名列长度即丢弃该关系)。dropped_entities的计数从「出现次数」改为「按实体去重」——初版对同一个被两条关系引用的越界实体会报 2。替代方案
normalized_name上有(kb_id, normalized_name, label)唯一约束,放宽列会改变该约束的语义面。判为后续可选,不在本 PR。(kb_id, normalized_name, label)唯一约束 → 又是一次整批失败,等于没修干净。故取丢弃。未验证范围与风险
Not run):Neo4j 先写、PG 后写,PG 失败时 Neo4j 已落盘。本 PR 只消除了一个触发原因,没有改变这个顺序,也没有补偿/自动收敛。已有一条 open issue [Bug][数据一致性] 图谱写入跨 Neo4j 与 PostgreSQL 两个独立事务,Neo4j 成功而 PG 失败时靠重试收敛 #879 专门讨论这一点(含它建议的幂等补偿与顺序调整),本 PR 不与之重叠。目击的那次不一致(Neo4j 有 12 实体、PG 为 0)在修复后重跑时被 MERGE 收敛掉了。Passed,但值得注意):超长实体通常确实是抽取噪声(整段正文),本次样本里丢弃后同批 11 个实体完好;但若将来出现"合法的超长实体"(例如极长的机构全名),丢弃会损失信息。当前列长度下这是可接受的取舍。llm抽取器路径上验证(Not inspected):目前GraphExtractorFactory.supported_types() == ["llm"],其它抽取器尚未存在。Not run):端到端只在单个原失败分块上验证;全库 914 个分块此前已建完,未重跑整库。界面变更
不涉及(后端规范化层,界面上只表现为不再有分块卡在「待构建」,以及该分块的节点数从 0 变为 11)。
关联事项
相关但不重叠:#879(图谱写入跨 Neo4j 与 PostgreSQL 两个独立事务,Neo4j 成功而 PG 失败时靠重试收敛)。本 PR 消除其中一个具体的 PG 失败原因,不改变该 issue 描述的跨存储语义。
补充说明
normalize_extraction_result是「模型输出 → 落库」之间唯一的收敛点,且它已经在做同一类事(校验非空、合并重复实体、解析引用)。放在这里,任何调用方都绕不过去。