1. 页面概览

1.1 是什么

「实体消解」页面(页面内标题为 Fusion · 实体消解)是 LightFoundry 提供的重复实体识别与合并确认工作台,V5 Stage 3(B3-3)交付。页面副标题给出了它的完整技术定位:「重复实体识别:规则分桶 + 加权打分 + LLM 复核 + 传递闭包聚类,survivorship 选主写回。」

在企业数据中,同一个真实世界实体(客户、商品、员工……)经常以多条「长相不同」的记录散落在不同来源(CRM、ERP、POS 等):电话号码相同但姓名写法不同、公司名带不带「有限公司」、地址缩写不一致等。实体消解要回答的核心问题是:这些记录是不是同一个人/同一家公司?如果是,以哪一条为准(选主),并把合并结果写回到本体中去。

页面对标 OpenFoundry 的 fusion-service 的确定性规则部分与 Gotham resolution 的三段式流水线(collectCandidates → scorePair 规则+AI → union-find 聚类),但在 Foundry 内单机闭环实现,不跨服务依赖。整条链路在页面上表现为:项目配置(对象 + key_fields 分桶 + 加权字段 + 阈值 + LLM 复核 + survivorship 选主 + 输出模式)→ 异步运行(轮询 runs)→ 匹配结果表(按簇聚合 + 确认/拒绝人工闭环)

1.2 核心价值

维度说明
规则分桶以 key_fields(精确分桶字段)把全量两两比较压缩到桶内,避免 O(n²) 爆炸;可选 blocking 字段归一后缩小比较域
加权打分每个打分字段配置权重、比较器(exact / prefix / levenshtein)与字段阈值,加权求和归一为 0~1 相似度
LLM 复核候选对得分 ≥ llm_confirm_score 时可选批量 LLM 复核,命中靠规则、复核靠 LLM 双保险
聚类合并union-find 传递闭包把两两相似关系聚成簇(一个簇 = 一个疑似实体),簇内每条记录一行
人工闭环候选簇由人确认合并或拒绝,拒绝簇不再参与后续 run,防止机器误判造成数据污染
选主写回survivorship 策略选出 winner(属性最完整者),in_place_edit 编辑态写回 winner 值 / materialize 写输出数据集

1.3 一句话总结

在 Foundry 里配置「谁算同一个实体」的规则,异步跑一遍消解把相似记录聚成候选簇,再由人来确认/拒绝,最后按选主策略把合并结果写回本体或输出数据集——从「重复数据」到「干净的唯一实体」的完整闭环。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

页面为「左窄右宽」两栏布局(.fusion-layout,grid 240px + 1fr):

┌──────────────────────────────────────────────────────────────┐
│ Fusion · 实体消解(页头 + 描述)                                │
│ [alert 操作结果提示条(可关闭)]                                 │
├──────────────┬───────────────────────────────────────────────┤
│ 消解项目(左)│ [未选项目时:从左侧选择或新建一个消解项目开始。]      │
│ [输入框: 新项目名称] [新建]                                     │
│ ┌ 项目1 ┐     │ ① 项目配置(保存按钮)                            │
│ │ 名称   │    │    form-grid:对象类型/key_fields/source_field/  │
│ │ 类型·启用│   │    阈值/LLM 复核线/输出模式/输出数据集/选主策略/    │
│ └───────┘     │    字段来源覆盖 + 打分字段表(+ 添加字段)         │
│ 删除          │ ② 运行(运行消解 / 自动轮询 / 最近一次摘要)        │
│               │ ③ 运行历史(时间/状态/簇/合并/错误)               │
│               │ ④ 匹配结果(状态筛选 + 刷新 / 簇/成员/来源/得分/   │
│               │    依据/状态/操作:确认|拒绝)                     │
└──────────────┴───────────────────────────────────────────────┘

各板块职责:

4. 交互元素详解

4.1 左栏:项目列表

元素位置含义必填与默认值操作效果触发后端调用
新项目名称输入框左栏顶部新建项目名称必填,回车亦可无(仅收集输入)
「新建」按钮输入框右侧创建消解项目校验非空后创建,弹出「项目已创建」成功提示并自动打开新项目POST /fusion/projects
项目行(点击)列表切换当前项目加载项目详情并填入表单,刷新运行历史与匹配结果GET /fusion/projects/:id
「删除」链接按钮每行右侧删除项目window.confirm 二次确认「确认删除项目「name」及其全部匹配/运行记录?」后删除DELETE /fusion/projects/:id

4.2 项目配置表单

元素含义必填与默认值操作效果触发后端调用
对象类型(api_name)要消解的对象类型标识必填,如 customer运行时的取数对象保存时写入 object_type
分桶字段 key_fields(逗号分隔)精确分桶字段默认空,保存时按逗号拆分为数组决定两两比较的分桶域保存时写入 rule_config.key_fields
来源字段 source_field(可选)标识成员来源的属性可空,如 source_system用于来源溯源与选主优先级写入 rule_config.source_field
候选阈值 match_threshold得分达到该值即候选对number,step 0.05,min 0,max 1,默认 0.8影响候选数量写入 rule_config.match_threshold
LLM 复核线 llm_confirm_score得分达该值且开启 LLM 时批量复核number,step 0.05,默认 0.85决定哪些候选走 LLM写入 rule_config.llm_confirm_score
启用 LLM 复核是否启用 LLM 复核checkbox,默认关闭未注入 LLM 时后端自动降级写入 rule_config.llm_enabled
输出模式 output_mode结果写回方式下拉:in_place_edit(编辑态写回 winner)/ materialize(写输出数据集),默认 in_place_edit决定运行后写哪写入 output_mode
输出数据集 id(materialize 必填)materialize 模式的目标数据集默认空选 materialize 时必须填写写入 output_dataset_id
选主策略 strategy唯一选项 source_priority默认 source_priority选主规则写入 survivorship.strategy
字段来源覆盖 field_priority(JSON)字段级来源优先,如 {"phone":"erp"}可空;非法 JSON 时弹「field_priority 须为合法 JSON,已按空处理」指定字段优先取某来源的非空值写入 survivorship.field_priority
「保存」按钮配置卡片右上组装 payload 更新项目,提示「项目已保存」PUT /fusion/projects/:id

4.3 打分字段子表

元素含义必填与默认值操作效果
属性打分字段 api_name默认两条:name(levenshtein) 与 phone(exact),权重各 0.5参与相似度加权
权重加权权重number step 0.1 min 0,默认 1加权求和
比较器相似度算法下拉:exact(归一后精确相等 1/0)、prefix(公共前缀长度比)、levenshtein(1 - 编辑距离/max 长度)字段相似度计算
必比required 字段checkbox,默认 false必比字段不一致则整体不匹配
字段阈值字段级阈值number step 0.05 min 0 max 1,默认 0required 字段低于该值整体拒绝
删除移除该字段从表格中移除(splice)
「+ 添加字段」按钮子表下方追加一行空字段(name 空、weight 1、comparator exact)

4.4 运行与结果

元素含义操作效果触发后端调用
「运行消解」按钮触发一次 run运行中/轮询中置灰并显示「运行中...」/「轮询中...」;提交后提示「运行已提交,异步执行中...」POST /fusion/projects/:id/run(timeout 120s)
运行后自动轮询 checkbox是否自动轮询默认勾选;退避轮询(1.5s 起、×1.5、封顶 10s,总上限 5 分钟),到达终态提示「消解运行完成」内部循环 GET /fusion/projects/:id/runs?limit=50
「继续轮询」按钮续轮询至终态仅在「轮询超时」或「历史首条仍为 running 且未在轮询」时出现;一键续轮询,无需另找刷新入口同上
「加载更多」按钮运行历史卡片底部一次取满 50 条后按每批 10 条分段渲染,控制单屏行数无(纯前端切片)
最近一次摘要运行卡片内 hint显示「最近一次:{status} · 簇 {clusters} · 合并 {merged}」,失败时红色展示 error由 runs 首条驱动
匹配结果状态筛选下拉:全部 / 候选 / 已合并 / 已拒绝切换即重新加载匹配GET /fusion/projects/:id/matches?status=
「刷新」按钮筛选右侧重载匹配结果同上(无 status 参数)
「确认」按钮候选行的操作列确认整簇合并,提示「已确认簇 {key} 合并」POST /fusion/matches/:id/confirm
「拒绝」按钮候选行的操作列拒绝整簇,提示「已拒绝簇 {key}」POST /fusion/matches/:id/reject

5. 后端关联

5.1 API 客户端

5.2 端点表

方法路径请求体说明
GET/fusion/projects项目列表(倒序不限)
POST/fusion/projectsProjectInput创建项目
GET/fusion/projects/:id项目详情
PUT/fusion/projects/:idProjectInput更新项目
DELETE/fusion/projects/:id删除项目(级联匹配/运行记录)
POST/fusion/projects/:id/run{}异步运行,返回 merge_run
GET/fusion/projects/:id/runs?limit=(后端上限 50,超出回落默认 20)运行记录(倒序)
GET/fusion/projects/:id/matches?status=匹配列表(candidate/merged/rejected)
POST/fusion/matches/:id/confirm人工确认合并(整簇)
POST/fusion/matches/:id/reject人工拒绝(整簇)

5.3 响应结构

统一响应体 {code: 0, data: ...}(错误时 {code, error}):

{ "code": 0, "data": { "projects": [ { "id": "...", "name": "...", "object_type": "customer",
  "rule_config": {...}, "survivorship": {...}, "output_mode": "in_place_edit",
  "output_dataset_id": "", "enabled": 1 } ], "total": 1 } }

匹配记录(matches 数组元素):

{ "id": "...", "project_id": "...", "cluster_key": "9f8b...", "member_object_id": "customer/123",
  "member_pk": "123", "source_ref": "erp", "score": 0.923, "matched_by": "llm",
  "status": "candidate", "created_at": "2026-08-30T..." }

运行记录(runs 数组元素):

{ "id": "...", "project_id": "...", "status": "success", "clusters": 5, "merged": 12,
  "error": "", "created_at": "...", "finished_at": "..." }

5.4 关联模块表

后端包职责
products/foundry/fusion/rest.goREST 端点与统一 {code,data} 响应、错误映射(ierr)
products/foundry/fusion/models.go表结构:ff_projects / ff_matches / ff_merge_runs,状态与依据常量
products/foundry/fusion/config.gorule_config / survivorship JSON 解析与默认值(match_threshold 0.8、llm_confirm_score 0.85)
products/foundry/fusion/algorithm.go分桶、加权打分、union-find 聚类、survivorship 选主
products/foundry/fusion/service.go项目 CRUD、RunProject 异步任务化、确认/拒绝
products/foundry/fusion/review.go人工确认/拒绝的审核语义
products/foundry/server/server.go服务组装与适配器(OntologyReader / QueryExec / EditWriter / DatasetWriter)

5.5 关键机制

6. 核心流程详解

6.1 主流程:配置 → 运行 → 审核 → 写回

  1. 新建项目:左栏输入名称点「新建」,前端用默认模板创建(object_type=customer,key_fields=['city'],name/phone 各 0.5,match_threshold=0.8,llm_confirm_score=0.85,llm_enabled=false,survivorship source_priority,output_mode in_place_edit),创建成功后自动打开并填入表单。
  2. 编辑配置:在项目配置卡修改对象类型、分桶字段、打分字段(属性/权重/比较器/必比/字段阈值)、候选阈值、LLM 复核线与开关、输出模式与数据集、选主策略与字段来源覆盖,点「保存」提交 PUT /fusion/projects/:id
  3. 运行消解:点「运行消解」,前端提交 POST /fusion/projects/:id/run;若勾选「运行后自动轮询」,则 1.5 秒轮询 runs 直到终态(最长 120 秒),完成后自动加载匹配结果并提示「消解运行完成」。
  4. 人工审核:在匹配结果表筛选「候选」,逐簇查看成员对象、来源、得分与依据(rule/llm),对确认无误的簇点「确认」、对有误的簇点「拒绝」。确认/拒绝后列表自动刷新,该簇移出候选。
  5. 结果写回:确认合并的簇在运行写回语义生效——in_place_edit 将 winner 属性值写回本体编辑态;materialize 写入输出数据集。拒绝簇保留 rejected 状态,后续 run 不再产出。

6.2 分支流程:运行失败

运行失败时 run 记录 status=failed 并携带 error;前端「最近一次」摘要与运行历史表均以红色展示 error。排查顺序:检查对象类型与 key_fields 是否真实存在 → 确认数据源/本体对象可查询 → 确认打分字段名与本体属性 api_name 一致 → 查看 runs 列表错误列的具体报错。

6.3 状态机与终态语义

7. 权限与安全

8. 常见问题与排错

问题一:点击「运行消解」后一直停留在「运行中...」,结果迟迟不出

现象:按钮长期显示「运行中...」,匹配结果不刷新。

原因:未勾选「运行后自动轮询」,或达到 5 分钟轮询上限提前退出;也可能是后端任务卡住。

排查步骤:先勾选「运行后自动轮询」再运行;若已达上限,点运行卡片内的「继续轮询」按钮续轮询到终态(无需去别处点刷新);观察「运行历史」表格是否有 running 状态行;用 curl -H "Authorization: Bearer <aip_token>" http://localhost:18081/api/v1/fusion/projects/<id>/runs?limit=50 直接查 runs;若 status 长期 running,检查后端日志中该任务的报错。

问题二:匹配结果表为空,但项目配置正确

现象:运行成功后 matches 列表为空。

原因:候选全部被拒绝(筛选在「已拒绝」状态),或本次 run 未产出任何簇(对象数据量小、分桶太细、阈值过高)。

排查步骤:把状态筛选切到「全部」确认总数;降低 match_threshold(如 0.8 → 0.7)重新运行;检查 key_fields 是否把本应合并的记录分到了不同桶;确认打分字段权重和为 1 且字段名与对象属性一致。

问题三:field_priority 填了 JSON 但保存时报「field_priority 须为合法 JSON,已按空处理」

现象:提示非法 JSON 且该值被按空处理。

原因:输入了非 JSON 文本(如 phone:erp 少了引号与花括号)。

排查步骤:按 {"phone":"erp"} 的格式书写;可用在线 JSON 校验器验证;保存后检查「保存」后表单是否回显该值。

问题四:删除项目后误删了需要保留的匹配数据

现象:项目及其匹配/运行记录全部消失。

原因:删除项目是级联删除(匹配与运行记录一并删除),前端 confirm 文案已明确提示。

排查步骤:删除前阅读 window.confirm 弹窗文案「确认删除项目「name」及其全部匹配/运行记录?」;如需保留建议导出后再删;删除不可恢复,需从备份恢复。

问题五:LLM 复核没有生效,匹配依据仍全是 rule

现象:matched_by 全是 rule,从未出现 llm。

原因:未勾选「启用 LLM 复核」,或候选得分低于 llm_confirm_score,或后端未注入 LLM 服务自动降级。

排查步骤:确认 llm_enabled 为开启;把 llm_confirm_score 下调观察;检查后端 LLM 配置(.env 中 deepseek/dashscope key)。

9. 已知缺陷与边界

边界说明
分桶假设key_fields 精确分桶假设「同桶才可能相似」,若分桶字段本身脏(如 city 有大小写/全半角差异)会漏配;blocking 字段归一化可缓解
阈值敏感性match_threshold 过高漏配、过低误配;需结合数据分布调参
比较器局限exact/prefix/levenshtein 为简化相似度,不支持拼音/同义词/数值范围相似
LLM 复核粒度为批量单次调用,不逐对解释;依赖后端注入的 LLM 服务
写回范围in_place_edit 仅改 winner 属性值(modify),不新增/删除对象行;materialize 需预建输出数据集
轮询超时前端退避轮询总上限 5 分钟;超时后不自动刷新,但提供「继续轮询」按钮一键续轮询(无需手动找刷新入口)
运行历史条数前端一次取满后端 limit 上限 50 条(无 offset,无法服务端翻页);超出部分需另行导出,列表按「加载更多」客户端分批渲染
删除级联删除项目会级联删除匹配与运行记录,无回收站