Skip to content

feat(entity-linker): 实体关联记忆全链路 - #114

Open
openjiuwen-sync-bot[bot] wants to merge 1 commit into
openJiuwen-ai:mem2.0from
openjiuwenai:sync/pr-194
Open

feat(entity-linker): 实体关联记忆全链路#114
openjiuwen-sync-bot[bot] wants to merge 1 commit into
openJiuwen-ai:mem2.0from
openjiuwenai:sync/pr-194

Conversation

@openjiuwen-sync-bot

@openjiuwen-sync-bot openjiuwen-sync-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

Paired: GitHub #114GitCode !194

What type of PR is this?

/kind

Self-checklist:(请自检,在[ ]内打上x,我们将检视你的完成情况,否则会导致pr无法合入

    • 设计:PR对应的方案是否已经经过Maintainer评审,方案检视意见是否均已答复并完成方案修改
    • 测试:PR中的代码是否已有UT/ST测试用例进行充分的覆盖,新增测试用例是否随本PR一并上库或已经上库
    • 验证:PR描述信息中是否已包含对该PR对应的Feature、Refactor、Bugfix的预期目标达成情况的详细验证结果描述
    • 接口:是否涉及对外接口变更,相应变更已得到接口评审组织的通过,API对应的注释信息已经刷新正确
    • 文档:是否涉及官网文档修改,如果涉及请及时提交资料到Doc仓

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

变更摘要

本 PR 实现了实体关联记忆(entity-linker)的全链路功能:从记忆内容中抽取实体、建立"实体 → 关联记忆 id"反向索引并持久化到 Elasticsearch,到召回阶段通过实体锚定记忆候选。同时,EntityExtractor 五路实体抽取支持按 model_name 配置中英文 spaCy 模型,由模型名前缀自动推断语言分支。写入侧按 MemoryTier 准入控制(SEMANTIC/CORE/EPISODIC 准入),三级匹配(hash 精确 → 向量语义归并 → INSERT/LINK)落 ES;召回侧两阶段(hash 精确 + 向量 kNN)产出 ScoredUnit 候选。此外,MemoryUnit 新增 entities 字段支持大模型抽取实体优先于 spaCy,Docker 部署支持构建期安装中英文 spaCy 模型。

主要改动

  • 新增 common/entity_extractor 包:中英文可配置的五路实体抽取EntityExtractormodel_name(如 en_core_web_sm / zh_core_web_sm)加载 spaCy 模型,get_lang_from_model_name 从模型名前缀推断语言,NER/专名 span/名词短语三路走中英文独立分支(中文 NER 标签集兼容 NAME/ORGANIZATION/LOCATION 等,英文保持原有 PERSON/ORG/GPE 等),技术标识符和引号路中英文复用;spacy_models.py 按模型名缓存单例,加载失败静默降级返回空实体。

  • 新增实体反向索引存储层EntityStore 抽象端口定义 hash 精确查询、向量 kNN 检索、bulk 变更(INSERT/LINK/UNLINK_UPDATE/DELETE)、反查关联四类能力;ElasticsearchEntityStore 完整实现 ES 后端(client 惰性建、ensure_index 建/校验索引、painless 脚本原子追加 linked_memory_ids、dense_vector+cosine 向量索引),复用主链路同一 ES 集群但独立 memory_entities 索引。

  • 新增 construction/entity_linker 写入侧编排EntityLinkService.link_memories 消费 MemoryUnit 列表,EntityIndexAdmissionPolicy 按 tier 准入,优先使用 unit.entities(LLM 抽取)构造 EntityMention、为空时回退 spaCy 批量抽取,分组按 (space_id, 隔离三元组) 做三级匹配(hash 精确 → kNN 语义归并 → INSERT/LINK),unlink_memory 清理记忆时按实体是否还有关联记忆决定 UNLINK_UPDATE 或 DELETE。

  • 新增 retrieval/recaller_impl/entity_recaller.py 召回侧EntityRecaller 自己调用 EntityExtractor.extract(query.raw) 保证与写入侧同归一化(normalize+sha256),两阶段召回——hash 精确命中给 score=1.0×频率衰减,未命中实体走向量 kNN(cosine≥0.5),同 unit_id 多实体命中取最高分经 aggregate_to_units 归并;作为 PipelineRetriever 并行通道之一,通过 entity_enabled 开关控制。

  • HybridIndexBuilder 组合 EntityIndexBuilder,全链路集成EntityIndexBuilder 实现 IndexBuilder 契约,内部委托 EntityLinkService,build/update/remove 分别对应 link/unlink+link/unlink;HybridIndexBuilder._build 通过 entity_enabled 配置项和 EntityStoreProducer 解析结果决定是否注入 entity_linkerSimpleQueryParserentity_enabled 时建议 RecallChannel.ENTITYpipeline_retriever 注册 entity_recaller 通道。

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

代码审查

所有 34 个文件已审查完毕。以下是汇总。


审查结论汇总

优先级 数量 说明
P0 0
P1 0
P2 1 英文 NER 2 字符实体静默丢弃
P3 3 skipped_count 未返回、死代码、文档字符串不准确

整体风险评估:中等偏低。核心链路(Docker 构建、配置展开、bootstrap 注册、ES 存储、builder/recaller 编排)无阻断性问题。唯一的 P2 问题(英文 2 字符实体丢失)在中文场景(本项目主要面向)不触发,但英文模式下会影响 NER 召回覆盖率。三个 P3 项均为非关键质量改进。建议在合入前修复 P2 问题。


逐文件审查结果

  1. deploy/docker/local/.env.example — 无问题
  2. deploy/docker/local/Dockerfile — 无问题
  3. deploy/docker/local/config.yml — 无问题
  4. deploy/docker/local/docker-compose.yml — 无问题
  5. deploy/docker/online/.env.example — 无问题
  6. deploy/docker/online/Dockerfile — 无问题
  7. deploy/docker/online/config.yml — 无问题
  8. deploy/docker/online/docker-compose.yml — 无问题
  9. src/common/bootstrap.py — 无问题
  10. src/common/entity_extractor/__init__.py — 无问题
  11. src/common/entity_extractor/extractor.pyP2(英文 NER 2 字符实体被丢弃)、P3_has_artifacts 死检查)
  12. src/common/entity_extractor/models.py — 无问题
  13. src/common/entity_extractor/normalizer.py — 无问题
  14. src/common/entity_extractor/scope_adapter.py — 无问题
  15. src/common/entity_extractor/spacy_models.pyP3(文档字符串与实际行为不符)
  16. src/common/type_def/memory.py — 无问题
  17. src/common/type_def/memory_codec.py — 无问题
  18. src/common/type_def/retrieval.py — 无问题
  19. src/construction/bootstrap.py — 无问题
  20. src/construction/entity_linker/__init__.py — 无问题
  21. src/construction/entity_linker/admission.py — 无问题
  22. src/construction/entity_linker/linker.pyP3skipped_count 未写入返回结果)
  23. src/construction/index_builder_impl/__init__.py — 无问题
  24. src/construction/index_builder_impl/entity_index_builder.py — 无问题
  25. src/construction/index_builder_impl/hybrid_index_builder.py — 无问题
  26. src/retrieval/query_parser_impl/simple_query_parser.py — 无问题
  27. src/retrieval/recaller_impl/__init__.py — 无问题
  28. src/retrieval/recaller_impl/entity_recaller.py — 无问题
  29. src/retrieval/retriever_impl/pipeline_retriever.py — 无问题
  30. src/storage/__init__.py — 无问题
  31. src/storage/bootstrap.py — 无问题
  32. src/storage/entity_impl/__init__.py — 无问题
  33. src/storage/entity_impl/elasticsearch_entity_store.py — 无问题
  34. src/storage/entity_store.py — 无问题
类型 数量
🔴 阻塞 0
🟡 建议 3

💬 仅评论

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

ent_tokens[-1].i + 1,
0.95,
0,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

🟡 Medium Priority

建议:在 _add_ner_candidates_add_proper_name_candidates 调用 _add_candidate 时传入 language="en"(显式但语义不变),同时将 _add_candidate 内英文最小长度从 2 放宽为 1(与中文一致),然后在英文路单独增加更细粒度的无效词/停用词过滤。

),
normalizer=EntityNormalizer(),
admission_policy=EntityIndexAdmissionPolicy(),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

🟡 Medium Priority

changed line: _build 函数(第 111-130 行)中 entity linker 的构造链路——EntityStoreProducer.dep()EntityLinkService(...) 以及内部的 EntityExtractor(...) / EmbedderProducer.dep()——没有任何 try/except 保护。

affected behavior: 若 entity store 后端不可达、spaCy 模型未安装、或 EntityLinkService 构造过程抛出任何异常,整个 _build 函数崩溃,导致 HybridIndexBuilder 无法创建,fulltext + vector 索引构建也一并不可用。

failure mode: 这与 EntityIndexBuilder 自身的设计原则矛盾——entity 索引被明确定义为"增强层,坏了不拖累主流程"(见 entity_index_builder.py docstring 第 6 行),但 producer 工厂函数没有任何降级保护,entity 链路故障会级联到整个索引子系统。

suggested fix: 将 entity linker 构造逻辑包裹在 try/except 中,失败时 log warning 并保持 entity_linker = None,让系统以降级模式运行(只有 fulltext + vector,无 entity 索引)。

建议:将第 112-130 行的 entity linker 构造逻辑用 try: ... except Exception as exc: logger.warning(...); entity_linker = None 包裹,失败时保持 entity_linker=None 以降级模式继续。





Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 1cf91231969c92622f1dc7076bb93b8662747f9c

🟡 Medium Priority

changed line: HybridIndexBuilder.remove_with_scope(第 90-93 行)仅委托给 _fulltext_builder_vector_builder,未调用 _entity_builder 的任何清理方法。

affected behavior: 当通过 scope 批量删除索引条目时,fulltext/vector 索引被正确清理,但 ES 中对应的实体文档及其 linked_memory_ids 不会被更新——这些 entity 记录仍保留已被删除的 memory_id,成为孤立链接。

failure mode: 召回侧通过实体检索命中这些孤立 entity 记录后,取到的 linked_memory_ids 指向已不存在的 memory,导致召回结果含无效 unit_id;后续检索链路可能因查不到对应 unit 而出错或返回空结果。

suggested fix: 要么为 EntityIndexBuilder 增加 remove_with_scope 方法(利用 space_id + unit_ids 进行 unlink),要么在 HybridIndexBuilder.remove_with_scope 中至少记录 warning 并在文档中标注 entity 索引不会随 scope 删除。

Suggested change
`EntityIndexBuilder` 增加 `remove_with_scope(self, unit_ids: list[str], scope: Scope) -> None` 方法内部对每个 unit_id 调用 `self._linker.unlink_memory(space_id=space_id_from_scope(scope), memory_id=uid)`然后在 `HybridIndexBuilder.remove_with_scope` 中增加 `if self._entity_builder is not None: self._entity_builder.remove_with_scope(unit_ids, scope)`

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 4b7a1a2287df15eee7601e476f9bb97090252ecc

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 869e138a2123d710f02e35733915482f7d4618ca

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 093cd82c8dbb43b50607b90181fa9d6a6380d782

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 093cd82c8dbb43b50607b90181fa9d6a6380d782

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

head_sha: a65c2c1489350642d4163d41b11d8c590ea987a2

大特性要在docs feature construction里添加设计文档

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: d62be053c394fd9e01576f23a788fd987936b5dd

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 65b1d94542b4c9cb566747a4b9a7ed65efa5adc1

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
禁用词扫描 N/A N/A
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 65b1d94542b4c9cb566747a4b9a7ed65efa5adc1

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
禁用词扫描 N/A N/A
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 65b1d94542b4c9cb566747a4b9a7ed65efa5adc1

路径不对,应该在 construction下继承extractor.py,并在extractor_impl里实现

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 65b1d94542b4c9cb566747a4b9a7ed65efa5adc1

命名不符合规则,能否继承graph实现

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 65b1d94542b4c9cb566747a4b9a7ed65efa5adc1

默认实现应装配到stroage.py里

新增实体反向索引链路:写入侧 HybridIndexBuilder 组合 EntityIndexBuilder,
经 EntityLinkService 抽实体→归一化→embedding→写 entity 索引;召回侧新增
EntityRecaller 两阶段匹配(hash 精确 + 向量 kNN),接入 pipeline_retriever。
EntityStore 走 Elasticsearch,复用主链路同集群。

EntityExtractor 国际化:
- spacy_models.py: get_nlp_full 接受 model_name 按名缓存单例,失败不重试
- extractor.py: 加 model_name/language,五路抽取按语言分流——NER/专名 span/
  名词短语三路走中文或英文分支,技术标识符/引号两路语言无关复用
- 中文 NER 接受双套标签集(NAME/ORGANIZATION/LOCATION + PERSON/ORG/GPE)
- 中文专名 span 去掉 isupper() 判断,改靠 PROPN 或 nr/ns/nt/nz 词性
- 中文 noun_chunks 不可用时整路返空降级
- _add_candidate 加 language 参数,中文最小长度阈值降到 1(2字实体如张三/北京)
- 写入侧(hybrid_index_builder)与召回侧(entity_recaller)工厂均注入
  config.get("entity_extractor_model", "en_core_web_sm")

镜像构建按配置装 spaCy 模型:
- local/online Dockerfile 加 ARG SPACY_MODELS=en,构建期 python -m spacy
  download 装入 site-packages
- compose build.args 从 .env 传入 SPACY_MODELS,决定装中/英/两者
- config.yml/.env.example 加 entity_extractor_model 与 SPACY_MODELS 说明
@openjiuwen-sync-bot openjiuwen-sync-bot Bot changed the title feat(entity-linker): 实体关联记忆全链路 + EntityExtractor 中英文可配置 feat(entity-linker): 实体关联记忆全链路 Aug 13, 2026
@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: d253a270bc2b4be20777589fd852cf65023e7d4b

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
禁用词扫描 N/A N/A
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants