AI 情报库 · AI项目
600行合成客户表对比四种去重法:邮箱基线误合并30对,保守配置降为0但仍漏30对;先建最小真值集,再接入CRM、RAG或Agent。
关注这个号,每天只挑一件 AI 里真正值得理解的事。
客户表去重最危险的麻烦,不是少删了几行,而是把两个真人合成一个人:客服看到错的历史记录,营销系统把不同客户的行为混在一起,进入 RAG 或 AI 应用记忆后,模型还会围绕这个错误身份生成一套看似完整的答案。这类后果会跨过数据清洗边界,长期留在客户主键和自动化工作流里。
GoldenMatch 是一个开源 Python 实体消歧工具。实体消歧的任务,是判断两行记录是否指向同一个现实对象;它会先做字段标准化和候选分组,再按姓名、邮箱、电话、地址等信号打分、聚类,最后输出去重后的实体。这里的“AI工具”并不等于调用大模型猜身份。本次匹配关闭了外部 LLM 和网络,检查的是确定性的规则与打分管线,目标也很具体:在一张故意放入共享邮箱、拼写变化和缺失值的客户表里,同时数清误合并与漏合并,再判断它能否进入 CRM、RAG 或其他 AI 工作流。
4个客户共享前台邮箱,先把“同值”和“同人”拆开
很多 Excel 或 pandas 去重脚本都从邮箱开始:去掉首尾空格,转成小写,相同就保留一行。这条规则便宜、直观,也确实能处理 Alice@example.com 与 alice@EXAMPLE.com 这样的格式差异。
麻烦出在共享账号。公司前台、家庭、门店、供应商联系人都可能共用一个邮箱。同值只能说明两条记录共享一个联系入口,不能自动证明它们属于同一个人。若一个前台邮箱对应 4 位客户,直接合并会形成 6 个两两组合;5 组这样的邮箱,就会产生 30 个错误记录对。
这个区别决定了整套方法的方向。邮箱、电话、邮编和姓名不应一律获得相同资格:有些字段可以直接主张身份,有些只适合把可能相关的记录送进候选池,还有些只能作为反对合并的负向信号。难点不在“相似度算得多精细”,而在先回答哪一种字段有资格把两个人并成一个实体。
GoldenMatch 3.6.0 在这份表上触发了一条可解释的保护:共享同一邮箱的记录没有同时在其他身份字段上保持一致,因此自动配置跳过了邮箱的 exact matchkey。邮箱没有被丢弃,但不再承担“同值即同人”的强制结论。最终配置保留电话精确匹配,以邮编做 blocking,再用姓名和地址的模糊分数组合判断。
严格来说,这只说明该规则在这份固定数据形状上被触发,不能推成“共享邮箱永远不会误合并”。姓名语言、缺失比例、家庭电话、门店地址和字段污染方式一变,控制器可能选择另一套配置。这个限制也解释了为什么去重项目需要真值抽样,而不是看一次演示删掉多少行。
600行客户数据、60对真重复:四种方法同场记两本账
测试输入是 600 行合成英语客户记录,共对应 540 个实体。其中 60 个实体各有两行,形成 60 对真重复;其余 480 行都是单例。真重复分成六类,每类 10 对:邮箱大小写或空格变化、邮箱一个字符错误、邮箱完全更换、邮箱为空、邮箱和电话同时变化、姓名与地址词序变化。
另外放入 20 位共享前台邮箱的不同客户,共 5 组、每组 4 人。用于标注真值的 entity_id、记录编号和场景标签全部从匹配输入中排除,避免工具偷看答案。固定随机种子后,同一输入分别交给四种方法:规范化邮箱基线、GoldenMatch 零配置、一份被拒绝的激进配置,以及修订后的保守显式配置。
输出不只记“删了多少行”,而是展开聚类中的每一对记录,核对三项数字:TP 是正确找到的重复对,FP 是把不同人合并的误合并,FN 是没有找到的漏合并。Precision 回答“合并出去的对里有多少是真的”,Recall 回答“所有真重复里找回了多少”。
| 方法 | TP | 误合并 FP | 漏合并 FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| 规范化邮箱 exact 基线 | 10 | 30 | 50 | 0.250000 | 0.166667 | 0.200000 |
| GoldenMatch 3.6.0 零配置 | 28 | 0 | 32 | 1.000000 | 0.466667 | 0.636364 |
| 被拒绝的 fuzzy identifier 配置 | 60 | 5,013 | 0 | 0.011827 | 1.000000 | 0.023378 |
| 修订后的显式多字段配置 | 30 | 0 | 30 | 1.000000 | 0.500000 | 0.666667 |

邮箱基线找到的 10 对,全是大小写或首尾空格变化;30 个误合并则全部来自共享前台邮箱。零配置多找回了邮箱字符错误和部分词序变化,同时没有合并共享邮箱里的不同人。修订后的显式配置又多找回 2 对词序变化,但并没有解决换邮箱、空邮箱或电话也变化的难例。
这张表最值得保留的不是“谁的 F1 更高”,而是两本账必须一起看。对客户身份来说,FP 往往比 FN 更贵:漏掉一个重复客户,会留下两条记录;误合并两个真人,却可能让隐私、权益、营销动作和模型记忆全部串线。追求召回没有错,但高召回应由可接受的误合并成本来约束。
运行耗时不适合拿来排名。邮箱基线只是一次字典分组,零配置还包含控制器搜索,四种方法承担的工作不同。600 行的本机时间也不能代表生产吞吐,文章只使用成对准确性结果,不借这次小样本宣称速度优势。
0误合并不等于完成:零配置为什么仍是RED
零配置的 28 个 TP 分布很清楚:邮箱格式变化 10/10,邮箱单字符错误 10/10,姓名与地址词序变化 8/10。它漏掉邮箱完全更换 10 对、邮箱为空 10 对、邮箱与电话同时变化 10 对,还漏了 2 对词序变化,总计 32 个 FN。
与此同时,自动配置控制器把本次 profile 标为 RED。它进行了 4 次迭代,停止原因是 budget_iterations,随后以 best-effort 方式提交初始配置。标签结果显示 precision 为 1,并不能抹掉这条无标签健康告警。两套信号回答的是不同问题:真值指标评价已知答案上的输出,RED 则提醒自动配置没有找到它认为健康的 blocking 结构。
因此,0 个 FP 不能写成“自动去重完成”。在高风险数据上,RED 更合理的动作是停止自动回写,把候选对和未匹配记录保留下来,再扩大标注样本或调整字段资格。若只盯着“这次没有删错”,团队很容易忽略一半真重复仍分散在表里。
复现环境也有一道容易踩的门槛。隔离环境中只安装 goldenmatch==3.6.0 时,包可以导入,但 PyArrow 表进入 dedupe_df 后因缺少 Polars 失败,错误是 ModuleNotFoundError: No module named 'polars'。补装可选依赖后,同一固定夹具才完整运行;本次解析到 Polars 1.43.0。更稳妥的安装入口是:
pip install "goldenmatch[polars]==3.6.0"
匹配阶段设置 GOLDENMATCH_AUTOCONFIG_LLM=0、关闭跨次配置记忆,并显式传入 llm_scorer=False;进程内还阻断了 socket 连接。两轮零配置运行的预测对集合完全一致,网络连接尝试为 0,llm_cost 为空,本地 native 0.1.19 热路径实际运行。这里没有调用 LLM,也没有走 3.6.0 新增关注的 Fellegi-Sunter 路径,不能把结果解释成大模型推理或 FS 效果。
5,013个假合并:客户邮箱和电话的“有点像”有多危险
第一次显式配置看起来很合理:姓名、邮箱、电话、地址、邮编都参与打分,邮箱和电话也使用 Levenshtein 相似度,希望抓住一个字符或一位数字的录入错误。阈值设为 0.78,邮编负责生成候选。
结果的 Recall 达到 1,60 对真重复全部找回;代价却是 5,013 个 FP。Precision 只剩 0.011827,F1 为 0.023378。它不是一个“小幅过拟合”的配置,而是完全不可用。合成数据里的邮箱、电话号码和地址都有相似格式,给结构化标识符按字符相像程度加分,再经过聚类的传递性扩展,许多不同人被连进同一实体。
随后配置才被修订:邮箱和电话只接受 exact 信用,姓名和地址保留 fuzzy,邮编继续做 blocking,多字段共同跨过 0.75 阈值。这版得到 30 TP、0 FP、30 FN。六类中,它找齐邮箱格式变化、邮箱字符错误、姓名与地址词序变化各 10 对;换邮箱、空邮箱、邮箱与电话同时变化三类各漏 10 对。
这次修订发生在看到同一份真值结果之后,没有另设独立留出集。夹具在两版配置之前已经冻结,失败配置也完整保留,但第二版仍属于工程调试后的回看,不能包装成盲测或通用最优参数。它能支持的结论只有:在这份固定真值表上,把结构化标识符从 fuzzy 改回 exact,消除了观察到的误合并,却牺牲了三类变化记录的召回。
反例给出了一条比工具品牌更耐用的规律:字段越像身份证明,越不能因为“字符长得差不多”就轻易加身份信用。电话号码差一位,可能是录入错误,也可能就是另一个人;邮箱差一个字符,可能是手误,也可能属于完全不同的账号。没有业务真值时,相似度只能产生候选,不能单独签发合并结论。
从客户表到RAG:实体消歧为什么属于AI数据准备
这次实验没有让语言模型参与匹配,AI 相关性仍然很直接。CRM 里的实体 ID 往往会成为客服助手的用户记忆键、推荐系统的画像键、RAG 的客户知识节点,或者 Agent 执行动作时的对象标识。上游若把两个人合成一个实体,下游模型不会自动把错误拆开,反而可能把两份记录串成更连贯的叙述。
可以把字段分成三道门。第一道是 identity signal:只有在业务上足以主张同一实体的字段组合,才允许直接合并。第二道是 blocking signal:邮编、门店、共享电话之类字段只负责缩小比较范围,不负责定案。第三道是 review signal:姓名相似、地址词序变化、邮箱字符错误等证据达到一定强度后进入复核队列,由真值或业务规则决定是否合并。

这三道门会改变 AI 数据管线的分工。模型层负责生成、检索和规划,工具层负责可重复的匹配与指标,数据治理层决定哪些合并可以自动写回、哪些必须保留审计痕迹。实体消歧做得越早,RAG 和 Agent 越少在错误身份上浪费上下文;门槛设得越保守,人工复核成本又越高。自动化程度与审核成本之间存在真实取舍,不能靠一句“AI 去重”消失。
GoldenMatch 最近发布 3.6.0,为项目带来了时效入口,但仓库活跃、发布频率和项目自称的 benchmark 都不能替代本地业务字段。尤其这次没有覆盖中文姓名、多语言地址、真实 CRM 的缺失分布、跨源匹配、百万行性能、数据库同步、嵌入模型或其他实体消歧库。项目是否适合接入,关键取决于它在你的真值样本上如何处理 FP、FN 和停止条件。
20—50行真值集:先在业务字段上验收
不必一开始就标几千行。20—50 行足以搭一个最小验收集,但要故意覆盖会改变决策的场景,而不是随手抽一段干净数据。
第一步,从真实字段形状中挑出三类记录:确定是同一人的正对、确定不是同一人的负对、共享邮箱或电话但身份不同的高风险对。若业务里常见换邮箱、空电话、姓名缩写、门店地址和家庭账号,每类至少放 2—3 对。真值标签单独保存,运行时从输入列中排除。
第二步,先跑当前做法。无论是 Excel 删除重复项、SQL GROUP BY,还是 pandas drop_duplicates,都把聚类展开成记录对,数出 TP、FP、FN。没有这一步,团队只知道删了几行,不知道删错了谁。
第三步,再跑零配置或一份最小显式配置。输入是去掉真值列的 20—50 行表;项目动作是标准化、blocking、打分和聚类;输出是候选记录对、聚类与配置。不要只看 golden record,要保留每个合并对和实际配置,才能定位误合并来自哪个字段。
第四步,先写停止线,再看 F1。若客户权益、隐私或付款记录会串线,可以把“任意一个已知负对被合并”设为停止条件;若任务只是内部搜索去重,则可以接受更多候选,但不自动覆盖源数据。RED 健康告警、超大簇、共享字段形成连通片,或 FP 突然增加,都应停止自动写回。
第五步,只在小集通过后扩到下一批,并新增上轮暴露的反例。20 行通过不代表 2 万行安全;它的作用是尽早发现字段资格错了、阈值方向错了,或者安装路径根本跑不通。下一批优先加入前一轮漏掉的换邮箱、空值和号码变化,而不是重复加入容易匹配的大小写差异。
第六步,把竞争方法放在同一对账方式下。最便宜的替代方案仍可能是 exact 基线加共享账号黑名单;高误合并成本场景也可以完全不聚类,只生成候选供复核。Splink、dedupe 等概率或监督工具可以进入后续比较,但本次没有把它们纳入跑分,因此不能借别人的 benchmark 代替自己的字段验收。
要不要接入GoldenMatch,先看三道字段门
这次 600 行测试给出的采用判断并不复杂。GoldenMatch 零配置在固定夹具上把邮箱基线的 30 个误合并降到 0,并把漏合并从 50 降到 32;修订后的显式配置进一步降到 30 个漏合并。但两者都没有解决换邮箱、空邮箱与电话同时变化,零配置还带着 RED 控制器状态。它适合成为候选生成和配置实验工具,不适合凭一份合成夹具直接接管生产客户主键。
更实际的结论是:接入任何实体消歧项目之前,先把字段放进 identity、blocking、review 三道门,再用 20—50 行业务真值验收 FP、FN 和停止线。若共享字段仍能直接形成大簇,或一个激进阈值就让 FP 爆发,先保留源记录,用候选队列替代自动合并。只有真值样本、配置、版本和回滚路径同时可追踪,工具才有资格进入 CRM、RAG 或 Agent 的上游。
开源项目会继续更新,配置控制器也可能变得更强;竞争重点会从“默认能找出多少重复”转向“能否解释字段资格、暴露不确定性、保存审计并安全停止”。下次再看到一个号称零配置的数据清洗工具,真正该先问的是:它把哪些字段当作同一人的证明,哪些只当候选信号;一旦这两者混在一起,团队能不能在错误身份写进 AI 记忆之前把流程停住?
夜雨聆风