业务故事站
P2 Foundry

Fusion 实体消解

多数据源里同一个真实实体常有多条形态不同的脏记录("Acme Corp" 与 "acme corp"、CRM 与 ERP 各一份客户)。Fusion 配置规则分桶、桶内两两加权打分,阈值外交 LLM 复核,union-find 聚簇后 survivorship 选出"黄金记录"写回,再人工确认/拒绝闭环。看完这 4 个故事,你就能把重复的客户 / 供应商合并成一条可信记录。

数据工程师 主数据管理员 规则分桶 加权打分 LLM 复核 选主写回 共 4 个故事

能 / 不能速览

✅ 这个主题能做
  • key_fields 精确分桶 + blocking_fields 缩小两两比较域,桶内才做相似度比较
  • 字段级加权打分:exact 精确 / prefix 前缀 / levenshtein 编辑距离,weight 加权平均得 0-1 分
  • 得分 ≥ match_threshold 成候选对;灰区对(≥ llm_confirm_score)批量交 LLM 复核,失败自动降级
  • union-find 传递闭包聚类,每簇写 ff_matches,score / matched_by 落库
  • survivorship 选主(source_priority):in_place_edit 经编辑态写 winner 值 / materialize 写输出数据集
  • 人工确认 / 拒绝闭环:POST /fusion/matches/:id/confirm|reject 整簇置态
⛔ 这个主题做不了
  • 分桶是精确匹配:归一后必须完全相等才同桶,"形近字分桶"不在 v1 范围,会直接漏配
  • 无分桶字段时退化为全表两两比较 O(n²),大数据集必须配 key_fields / blocking_fields 剪枝
  • survivorship 仅 source_priority 一种策略,选主按"非空属性数"而非加权完整性
  • LLM 复核是批量单次调用,无逐对重试 / 多轮
  • materialize 每次重建输出行表,并发 run 相互覆盖;matches 无分页

适用角色

本主题面向三个角色:

  • 数据工程师:配规则(分桶字段、打分字段、阈值)、跑消解、看候选簇质量,是核心使用者。
  • 主数据管理员:在匹配表里逐簇人工确认 / 拒绝,把候选黄金记录拍板定稿。
  • 业务分析师:消费消解后的数据(编辑态读叠加 / 输出数据集),客户数、供应商数等统计口径被修正。

平台管理员通过审计掌握 run 与 ai_used 情况;LLM 复核走平台 llm.LLMService(deepseek-v4-flash)。

能力速览(能做什么)

规则分桶

key_fields 精确分桶 + blocking_fields 可选剪枝,同桶才两两比较,大幅缩小 O(n²) 比较域;无分桶退化为单桶全比较。

字段加权打分

exact / prefix / levenshtein 三比较器(rune 级,中文友好),weight 加权平均得 0-1 分;required 字段低于阈值整对拒绝。

LLM 复核

灰区候选对批量交 LLM 判定 same / different,返回覆盖规则分并标记 matched_by=llm;LLM 失败自动降级按规则分处理。

选主与输出

survivorship source_priority:属性最完整者胜出;in_place_edit 经编辑态写 winner 值(mode=pre),materialize 重建输出数据集行表。

人工审核闭环

匹配表按簇展示,confirm 整簇置 merged、reject 整簇置 rejected;merged / rejected 终态簇成员对后续 run 均自动排除(对称的跨 run 记忆,确认合并的簇同样不再重提)。

调整指南(怎么调整)

  • 改分桶:key_fields 选"同实体必同值"的字段(如 city),blocking_fields 再收窄;跨城绝不当同一实体,分桶即候选剪枝。
  • 改打分:name 用 levenshtein 容忍拼写差异、phone 用 exact 归一后精确,weight 越关键的字段权重越高;阈值 match_threshold 调低候选变多、调高更保守。
  • 改 LLM 复核:llm_enabled=true + llm_confirm_score 设灰区线,低于该线的对不走 LLM;LLM 未注入时自动降级。
  • 改输出:评估阶段用 in_place_edit 先看候选质量;要下发下游报表用 materialize 配 output_dataset_id 写黄金记录行表。
  • 改审核:先 reject 误配的簇再重跑,rejected 簇成员对会被记忆排除;人工 confirm 的 merged 簇同样进入排除集(merged / rejected 对称,不再重提);confirm 后状态不可回退。

做得好的场景

Fusion 把"手写模糊 SQL + 人工比对"变成"规则流水线 + 人工拍板",特别适合以下场景:
  • 多来源客户主数据合并:CRM 与 ERP 各一份客户,消解后客户数统计修正、订单归属归位。
  • 供应商去重 + 黄金记录物化:采购域多系统重复,materialize 输出每簇一行黄金记录供下游报表 / 定价模型直接引用。
  • 灰区人工把关:LLM 复核 + 人工确认 / 拒绝,机器产出候选、人拍板定稿,质量与效率兼顾。
  • 消解评估(dry run):不注入输出器也能跑,run 仍 success 仅输出降级跳过,先看候选簇质量再决定是否写回。

限制与不足

以下是明确的边界,使用前先知道:
  • 分桶精确匹配:归一后不完全相等即不同桶,"形近字分桶"(省市区编码差异)不在 v1 范围,会直接漏配。
  • 无分桶即 O(n²):全表两两比较,大数据集必须配 key_fields / blocking_fields 剪枝,否则性能失控。
  • 选主策略单一:仅 source_priority;"完整性"按非空属性数,不按权重 / 重要属性加权。
  • LLM 复核粗粒度:批量单次调用,无逐对重试 / 多轮;坏 JSON 重试 1 次仍失败即降级。
  • materialize 覆盖写:每次重建输出行表(DROP + CREATE + INSERT),并发 run 相互覆盖,列宽 / 类型固定 TEXT。
  • matches 全量返回:无分页,大簇数时响应偏大;同项目可并发提交多条 running run(无互斥锁)。

场景故事

故事 1 CRM 与 ERP 的同一家客户:分桶打分聚簇,编辑态写回
背景
集团 CRM 与 ERP 各自维护客户:CRM 的 name="Acme Corp"、city="Shanghai"、source_system="crm";ERP 的 name="acme corp"、city="Shanghai"、phone="+86-21-xxxx"、source_system="erp"。同一家公司两条记录,BI 报表客户数虚高、订单归属错乱。张工建一个 Fusion 消解项目把 customer 对象合并。
传统做法对比
以前要么 DBA 手写模糊 SQL(LIKE %acme% 一把抓),要么人工比对几百条客户记录,几天工作量且口径不可复现。现在配置规则:分桶、打分、选主全部声明式,跑一次即出候选簇,可复现可调整。
角色
数据工程师(配规则 + 跑消解 + 看候选质量)。
操作步骤
  1. 侧边栏"实体消解"新建项目"客户消解",object_type=customer
  2. rule_config 配 key_fields=["city"]、source_field="source_system"
  3. fields 配 name 0.6 levenshtein + phone 0.4 exact
  4. match_threshold=0.8、llm_confirm_score=0.85、llm_enabled=true
  5. source_ranks 配 {"crm":1,"erp":2},survivorship field_priority 配 {"phone":"erp"}
  6. output_mode=in_place_edit,点"运行",轮询 runs 看终态
系统响应
运行 POST /fusion/projects/:id/run 返回 {"code":0,"data":{"id":"<run uuid>","project_id":"<id>","status":"running","clusters":0,"merged":0,"finished_at":null}};轮询后终态 success,匹配列表:
{"code":0,"data":{"matches":[
  {"id":"...","project_id":"...","cluster_key":"clu-0f1e8c2d-00",
   "member_object_id":"1001","member_pk":"1001","source_ref":"crm",
   "score":1.0,"matched_by":"rule","status":"candidate","created_at":"..."}],
  "total":1}}
结果洞察
同城(city 归一相等)进同一桶 → name(levenshtein 高分)与 phone(exact)加权得 1.0 ≥ 0.8 成候选对 → 成 1 簇。选主时 CRM 记录属性更完整(有 name/city)胜出为 winner,phone 因 field_priority 指定 erp 取 ERP 非空值补全。in_place_edit 把 winner 值经编辑态写回(ontology_edits,mode=pre),本体查询因编辑态读叠加永久优先展示合并后数据,源表不动。
调整建议
分桶字段务必选"同实体必同值"的字段,否则漏配;打分字段权重调优前先用 dry run 看候选质量;llm_confirm_score 设得越高灰区越小,越低 LLM 介入越多;确认合并前先在匹配表核对 score 与来源。
动手试一试
登录:admin / admin1。页面路径:实体消解 → 新建"客户消解"。输入内容:key_fields=["city"]、name 0.6 levenshtein、phone 0.4 exact、threshold 0.8、in_place_edit。预期结果:run success、clusters=1,匹配表出现 1 簇 candidate,确认后本体查询同一客户只出现一次。
限制提示
分桶是精确匹配,归一后 city 不完全相等即不同桶,会漏配;in_place_edit 写的是编辑态(mode=pre)不改源表,若"编辑态永久优先"语义被推翻,写回结果会受影响;LLM 复核只针对已过 match_threshold 的候选对,不会把低于阈值的对捞回来。
故事 2 供应商去重:黄金记录物化到输出数据集,供下游直接引用
背景
采购域多个系统(Ariba / 本地 SRM)供应商重复:同一家供应商"上海联创精工"在 Ariba 存为 "Lianchuang",在 SRM 存为 "联创精工(上海)"。张工配 output_mode=materialize + output_dataset_id,让 run 后输出数据集行表重建为每簇一行黄金记录,供下游报表 / 定价模型直接引用。
传统做法对比
以前每家供应商在多个系统各占一行,采购报表供应商数量虚高、对账对不上;做物化去重要写专门的清洗脚本再建表。现在 materialize 模式每次 run 自动重建输出行表:cluster_key / object_id / source_ref + 各打分字段规范值。
角色
主数据管理员(配输出 + 审核确认);下游报表 / 定价模型消费输出数据集。
操作步骤
  1. 新建消解项目"供应商去重",object_type=supplier
  2. rule_config 配分桶与打分字段(name levenshtein + code exact)
  3. output_mode=materialize,output_dataset_id 填输出数据集 id
  4. 点"运行",轮询 runs 到 success
  5. 到数据集页面打开输出行表,核对黄金记录
  6. 在匹配表对候选簇确认 / 拒绝
系统响应
run 终态返回 {"id":"<run>","status":"success","clusters":3,"merged":3,"error":"","finished_at":"..."};materialize 输出行表列 = cluster_key, object_id, source_ref + 各打分字段,列名白名单正则校验(^[a-z][a-z0-9_]{0,59}$),重建在事务内 DROP + CREATE + INSERT。
结果洞察
materialize 把"消解结论"物化成物理数据:每簇一行黄金记录,下游报表 / 定价模型直接引用输出数据集,不必重复算消解逻辑。写回全程参数化 SQL + 列名白名单,防注入有保障;run 的 clusters / merged 落库,消解规模可量化。
调整建议
materialize 前先用 in_place_edit 或 dry run 评估候选质量,避免把误配写进黄金记录;输出数据集 id 必填,缺省会 422;并发 run 会相互覆盖输出行表,建议串行跑;行表列宽 / 类型固定 TEXT,数值字段注意下游解析。
动手试一试
登录:admin / admin1。页面路径:实体消解 → 新建"供应商去重"。输入内容:output_mode=materialize + output_dataset_id,name levenshtein + code exact 打分。预期结果:run success,输出数据集行表出现每簇一行黄金记录(cluster_key 前缀 clu-)。
限制提示
materialize 每次 run 重建整个输出行表,并发 run 相互覆盖;列名白名单外的字段无法写入;输出器未注入时降级跳过(记 warning 不失败),run 仍 success——评估路径可用,但别以为已写盘。
故事 3 灰区候选交 LLM 复核:规则打分兜底,AI 判定有据可依
背景
张工的规则跑出 8 个候选簇,其中 3 簇得分在 0.85~0.95 的灰区(如 "Acme Corp" 与 "Acme LLC" 拼写高度相似但不是同一家)。他把 llm_confirm_score=0.85、llm_enabled=true,灰区对自动批量交 LLM 复核,LLM 判定 same / different 并覆盖规则分。
传统做法对比
以前灰区只能人肉逐对核对,一天看不了几十对;或者干脆放宽阈值让误配混进去。现在规则打分兜底 + LLM 复核灰区 + 人工拍板,机器处理量级、人处理判断,误配率与工作量同时下降。
角色
数据工程师(配 LLM 复核线);LLM 服务(deepseek-v4-flash)批量判定灰区对。
操作步骤
  1. rule_config 配 llm_confirm_score=0.85、llm_enabled=true
  2. 点"运行",等待异步任务完成
  3. 轮询 runs 到 success,看 clusters / merged
  4. 打开匹配表,过滤 matched_by=llm 的簇核对
  5. 对判定结果确认或拒绝
系统响应
LLM 复核构造 JSON 列表 prompt(每项 idx / a / b / score),期望返回 JSON 数组(same / score / reason);坏 JSON 重试 1 次仍失败则降级按规则分处理(日志 warning)。判定 same=true 的簇 matched_by=llm、分被 LLM 分覆盖;判定 different 的对落 rejectedPairs 写 rejected。
结果洞察
匹配表里同一簇可能混合 rule / llm 依据(簇内存在 LLM 复核过的对即整簇标 llm)。LLM 复核只针对已过 match_threshold 的候选对,不把低于阈值的对捞回来——规则打底、AI 精修。matched_by 字段让"这个簇是机器定的还是 AI 判的"一目了然,人工复核有重点。
调整建议
llm_confirm_score 是"灰区上线",想多让 AI 复核就调低,想少依赖 AI 就调高;LLM 不可用时自动降级,不阻塞作业;对 LLM 误判的簇直接 reject,被拒对后续 run 自动排除,无需反复人工干预。
动手试一试
登录:admin / admin1。页面路径:实体消解 → 项目配置。输入内容:llm_confirm_score=0.85、llm_enabled=true,跑一次 run。预期结果:runs 终态 success,匹配表部分簇 matched_by=llm,score 为 LLM 判定分。
限制提示
LLM 复核是批量单次调用,无逐对重试 / 分片;LLM 判定 different 的对写 rejected 簇(rej- 前缀),下次 run 该对被排除;LLM 失败降级按规则分处理时该对仍可能以 rule 依据成簇。
故事 4 人工确认 / 拒绝闭环:误配的簇 reject 后重跑不再出现
背景
首轮消解出了 6 个候选簇,王姐发现其中 1 簇是误配:"上海联创"和"杭州联创"被 rule 配到同一簇(name 前缀相似度高)。她要在匹配表里对误配簇点"拒绝",对正确的簇点"确认",再重跑验证被拒对不再出现。
传统做法对比
以前消解脚本跑完就完了,误配只能靠手工 SQL 修数据,改完下次脚本重跑又错回来。现在人工结论有记忆:rejected(拒绝)与 merged(确认合并)终态簇的成员对都会被保存,后续 run 打分前自动跳过——merged / rejected 对称、不再重提,误配一次修好、不再复发。
角色
主数据管理员(逐簇确认 / 拒绝);数据工程师(必要时调规则再跑)。
操作步骤
  1. 打开"匹配结果",status 筛 candidate,按 cluster_key 查看候选簇
  2. 核对"上海联创 / 杭州联创"误配簇的成员与 score
  3. 对误配簇点"拒绝"(POST /fusion/matches/:id/reject)
  4. 对正确的簇点"确认"(POST /fusion/matches/:id/confirm)
  5. 重跑一次消解,确认被拒对不再出现在新匹配里
系统响应
确认返回 {"code":0,"data":{"status":"merged","matched_by":"manual"}}(整簇置 merged);拒绝返回 {"code":0,"data":{"status":"rejected","matched_by":"manual"}}。状态机 candidate → merged | rejected 单向不可回退;已 merged 再 confirm 幂等返回当前状态,已 merged 再 reject 返回 400。
结果洞察
确认 / 拒绝都是"整簇"操作:按单条记录定位簇,同一真实实体的全体成员状态一致。rejected / merged 终态簇都被持久化记忆,重跑时 discoverPairs 在打分前用 pairKeyOf 查排除集(历史终态成员对),命中即跳过——人工确认合并与拒绝都不再重提,误配一次修好不再复发,人工结论沉淀为规则。
调整建议
先看 score 与来源再决定确认 / 拒绝;被拒对只影响该轮之后的 run,不删历史匹配记录;若误配反复出现,说明打分字段或权重要调(如 name 降权、加 code 必比字段),人工拒绝治标、规则调优治本。
动手试一试
登录:admin / admin1。页面路径:实体消解 → 匹配结果。输入内容:对 candidate 簇分别点"确认"与"拒绝",然后重跑。预期结果:确认簇置 merged、拒绝簇置 rejected;重跑后新匹配中不再出现被拒成员对。
限制提示
确认与拒绝互斥单向,不可回退(确认后不能反悔为候选);status 过滤参数非法返回 400;matches 全量返回无分页;重跑清理逻辑只删非终态的历史匹配,merged / rejected 记录均保留并进排除集。

常见问题

分桶和打分是什么关系?

分桶是"候选剪枝":key_fields + blocking_fields 归一后精确拼接成桶键,同一桶内才做两两打分;分桶字段相同意味着"大概率同一实体"。打分是对桶内每对逐字段算相似度再加权平均。没配分桶字段就退化为单桶全表两两比较(O(n²))。

三种比较器有什么区别?

exact 是归一后精确相等(1/0);prefix 是公共前缀长度 / 最大长度;levenshtein 是 1 - 编辑距离 / 最大长度(中文按 rune 计)。归一会折叠大小写与连续空白,所以 "Acme Corp" 与 "acme corp" 在 exact 下也相等。

LLM 复核会捞回低于阈值的对吗?

不会。LLM 复核只针对已过 match_threshold 的候选对(得分 ≥ llm_confirm_score 的那部分灰区),规则分是底线,LLM 只在灰区里做精修。LLM 判定 different 的对会被记为 rejected 并在后续 run 排除(人工 confirm 的 merged 簇同样进入排除集,不再重提)。

in_place_edit 和 materialize 有什么差别?

in_place_edit(缺省)把 winner 属性值经编辑态写回(ontology_edits,mode=pre),本体查询因编辑态读叠加永久优先展示合并后数据,源表不动;materialize 把每簇黄金记录物化到输出数据集行表(每次 run 重建),供下游直接引用。

人工拒绝的簇会复发吗?

不会。merged / rejected 匹配都被持久化,重跑时在打分前就把同一终态簇内成员两两组成的成员对排除掉——合并与拒绝对称、均不参与后续 run,这是跨 run 的人工结论记忆。

主题小结

一句话:Fusion 实体消解是"规则流水线 + 人工拍板"的闭环——key_fields 分桶 + 桶内加权打分(exact/prefix/levenshtein)出候选,LLM 复核灰区,union-find 聚簇,survivorship 选主写回(in_place_edit 编辑态 / materialize 输出数据集),人工确认 / 拒绝闭环且 merged / rejected 终态成员对对称跨 run 记忆、均不再重提。记住几个边界:分桶精确匹配、无分桶即 O(n²)、选主仅 source_priority、materialize 覆盖写。