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 一句话总结
2. 访问入口
2.1 路由与菜单
- 路由:
/foundry/fusion - 路由名称:
FoundryFusion - 路由 meta:
title: 实体消解,requiresAuth: true,挂在父路由/foundry(FoundryLayout)下 - 菜单位置:Foundry 左侧边栏「实体消解」(FoundryLayout 菜单项,V5 Stage 3 接线时加入)
- 前端源码:
action/web/src/views/FusionPage.vue - API 客户端:
action/web/src/api/fusionApi.js
2.2 认证与权限
- 页面路由挂
requiresAuth: true,未登录访问会被全局路由守卫重定向到/login(并携带redirect参数,登录后回跳)。 - 页面所有数据请求走
fusionApi实例:请求拦截器自动从localStorage.getItem('aip_token')取令牌并以Authorization: Bearer <token>附带;响应拦截器遇到 401 时清理aip_token与aip_username,并把页面跳转到/login。 - 后端所有
/fusion/*端点挂 protected 语义(authMiddleware 校验 JWT),未带有效令牌一律 401。 - 若登录页反复出现但令牌仍被清空,多半是后端返回 401(令牌过期或 SECRET_KEY 变更),重新登录即可。
2.3 端口与 API 前缀
- Foundry 后端端口:18081(Vite 开发服务器将
/api前缀代理到该端口)。 - API 前缀:
/api/v1(fusionApi 的 baseURL)。 - 完整请求示例:
GET /api/v1/fusion/projects、POST /api/v1/fusion/projects/:id/run。
3. 界面布局
页面为「左窄右宽」两栏布局(.fusion-layout,grid 240px + 1fr):
┌──────────────────────────────────────────────────────────────┐
│ Fusion · 实体消解(页头 + 描述) │
│ [alert 操作结果提示条(可关闭)] │
├──────────────┬───────────────────────────────────────────────┤
│ 消解项目(左)│ [未选项目时:从左侧选择或新建一个消解项目开始。] │
│ [输入框: 新项目名称] [新建] │
│ ┌ 项目1 ┐ │ ① 项目配置(保存按钮) │
│ │ 名称 │ │ form-grid:对象类型/key_fields/source_field/ │
│ │ 类型·启用│ │ 阈值/LLM 复核线/输出模式/输出数据集/选主策略/ │
│ └───────┘ │ 字段来源覆盖 + 打分字段表(+ 添加字段) │
│ 删除 │ ② 运行(运行消解 / 自动轮询 / 最近一次摘要) │
│ │ ③ 运行历史(时间/状态/簇/合并/错误) │
│ │ ④ 匹配结果(状态筛选 + 刷新 / 簇/成员/来源/得分/ │
│ │ 依据/状态/操作:确认|拒绝) │
└──────────────┴───────────────────────────────────────────────┘
各板块职责:
- 消解项目(左栏卡片):新建、选择、删除消解项目;当前项目高亮。每个项目一行显示
name、object_type · 启用/停用,右侧是「删除」链接按钮。 - 项目配置(右侧第 1 张卡片):对象的全部消解规则都在这里编辑,含对象类型、分桶字段、来源字段、候选阈值、LLM 复核线、LLM 开关、输出模式、输出数据集、选主策略、字段来源覆盖,以及一张「打分字段」子表。「保存」按钮把配置提交到后端。
- 运行(右侧第 2 张卡片):「运行消解」触发一次异步 run;「运行后自动轮询」勾选后前端以退避策略轮询 runs 直到终态(1.5s 起、每次 ×1.5、封顶 10s,总上限 5 分钟),超时后露出「继续轮询」按钮可一键续轮询至终态,无需用户去别处手动刷新;卡片底部展示最近一次运行摘要。
- 运行历史:展示 run 的时间、状态徽标、簇数、合并数与错误信息;一次取满后端 limit 上限(50 条),列表按「加载更多」客户端分批渲染以控制单屏行数。
- 匹配结果:按状态筛选(全部/候选/已合并/已拒绝)查看匹配记录,每条记录展示簇 key、成员对象、来源、得分、匹配依据(rule/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,默认 0 | required 字段低于该值整体拒绝 |
| 删除 | 移除该字段 | — | 从表格中移除(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 客户端
- 文件:
action/web/src/api/fusionApi.js - baseURL:
/api/v1;超时 60000ms(runProject单独 120000ms);Content-Type: application/json - 请求拦截器:自动附带
Authorization: Bearer <aip_token> - 响应拦截器:401 时清除
aip_token/aip_username并跳转/login - 导出函数:
listProjects / createProject / getProject / updateProject / deleteProject / runProject / listRuns / listMatches / confirmMatch / rejectMatch
5.2 端点表
| 方法 | 路径 | 请求体 | 说明 |
|---|---|---|---|
| GET | /fusion/projects | — | 项目列表(倒序不限) |
| POST | /fusion/projects | ProjectInput | 创建项目 |
| GET | /fusion/projects/:id | — | 项目详情 |
| PUT | /fusion/projects/:id | ProjectInput | 更新项目 |
| 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.go | REST 端点与统一 {code,data} 响应、错误映射(ierr) |
products/foundry/fusion/models.go | 表结构:ff_projects / ff_matches / ff_merge_runs,状态与依据常量 |
products/foundry/fusion/config.go | rule_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 关键机制
- 异步任务化:
RunProject经平台任务系统mgr.Submit提交,立即返回MergeRun(status=running);前端轮询runs接口直到status != "running"。 - 轮询语义:前端
pollUntilDone采用退避轮询——初始 1500ms、每次乘 1.5、封顶 10000ms,总 deadline 300000ms(5 分钟);loadRuns取列表首条作为lastRun,返回runs[0].status !== 'running'作为「完成」信号。到 deadline 仍未终态时置pollTimedOut并露出「继续轮询」按钮(pollUntilDone可重复调用),不再要求用户手动点「刷新」。 - 运行历史获取:
listRuns默认limit=50(后端ListRuns对limit<=0 || limit>50一律回落 20,故 50 为可取上限);前端一次取满后按每批 10 条「加载更多」渲染。 - 状态机:run 状态
running → success | failed;match 状态candidate → merged | rejected(rejected 不再参与后续 run);matched_by为rule | llm | manual。 - 簇语义:同一
cluster_key的成员构成一个传递闭包簇(疑似同一实体);score为簇内成员对平均相似度;确认/拒绝以簇为单位(调用成员 id 的 confirm/reject 作用于整簇)。 - 写回方式:
in_place_edit经注入 EditWriter 编辑态改 winner 属性值;materialize经 DatasetWriter 写输出数据集。
6. 核心流程详解
6.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),创建成功后自动打开并填入表单。
- 编辑配置:在项目配置卡修改对象类型、分桶字段、打分字段(属性/权重/比较器/必比/字段阈值)、候选阈值、LLM 复核线与开关、输出模式与数据集、选主策略与字段来源覆盖,点「保存」提交
PUT /fusion/projects/:id。 - 运行消解:点「运行消解」,前端提交
POST /fusion/projects/:id/run;若勾选「运行后自动轮询」,则 1.5 秒轮询 runs 直到终态(最长 120 秒),完成后自动加载匹配结果并提示「消解运行完成」。 - 人工审核:在匹配结果表筛选「候选」,逐簇查看成员对象、来源、得分与依据(rule/llm),对确认无误的簇点「确认」、对有误的簇点「拒绝」。确认/拒绝后列表自动刷新,该簇移出候选。
- 结果写回:确认合并的簇在运行写回语义生效——
in_place_edit将 winner 属性值写回本体编辑态;materialize写入输出数据集。拒绝簇保留 rejected 状态,后续 run 不再产出。
6.2 分支流程:运行失败
运行失败时 run 记录 status=failed 并携带 error;前端「最近一次」摘要与运行历史表均以红色展示 error。排查顺序:检查对象类型与 key_fields 是否真实存在 → 确认数据源/本体对象可查询 → 确认打分字段名与本体属性 api_name 一致 → 查看 runs 列表错误列的具体报错。
6.3 状态机与终态语义
- run:
running(已提交、任务执行中)→success(聚类完成、候选已生成)或failed(规则/取数异常,error 说明原因)。 - match:
candidate(本次 run 产出,待人工审核)→merged(人工确认,整簇合并)或rejected(人工拒绝;rejected 不参与后续 run)。 - 确认/拒绝调用成功后状态即刻更新;若对同一簇重复调用,后端按当前状态给出响应(已终态的簇操作列显示「—」)。
7. 权限与安全
- 认证:全部端点经 authMiddleware(JWT,aip_token),未授权返回 401,前端自动跳登录。
- 数据级安全:项目按用户维度管理(currentUserID 注入),后端写回经 EditWriter/DatasetWriter 接口注入,不越过本体权限口径。
- 写操作防护:删除项目与删除运行记录均为级联操作,前端用
window.confirm二次确认;人工确认/拒绝动作直接影响本体数据,操作前应核对簇成员与来源。 - LLM 复核安全:llm_enabled 时仅对达到 llm_confirm_score 的候选批量复核;LLM 未注入时后端自动降级为纯规则,不影响主链路。
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,无法服务端翻页);超出部分需另行导出,列表按「加载更多」客户端分批渲染 |
| 删除级联 | 删除项目会级联删除匹配与运行记录,无回收站 |