ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

形近字归一化把两个客户合并成一个人:一次“串聊”事故的技术复盘

形近字归一化把两个客户合并成一个人:一次“串聊”事故的技术复盘 客服机器人把 A 客户的订单信息、聊天历史回给了另一位毫不相干的 B 客户——不是提示词写错也不是模型抽风而是上游一个看似人畜无害的形近字归一化模块把李文宇和李文字两个真人合并成了同一个人。本文按时间线复盘这起串聊事故它是怎么发生的为什么会在测试里漏掉以及修复时沉淀下来的三条通用原则。背景为容错而生的形近字映射表这套客服系统的输入里有相当一部分来自截图和 OCR客户转发的聊天截图、12px 小字的消息标题、低分辨率的缩略图。在 12px 的字号下宇和字只差顶部一小截笔画OCR 把宇认成字的概率不低。人名李文宇被读成李文字后续按人名做会话归属匹配时就对不上号机器人便认不出这位客户。为了容错我们建了一张形近字映射表收的都是真实出现过的误读对宇↔字、己↔已、末↔未 这一类。整体流程是OCR 得到文本 → 查表把形近字映射成标准形 → 再拿归一化后的名字去匹配会话归属。思路本身很常见问题出在归一化被当成了无条件执行的动作。事故现场四个锚点都指向另一个人那天坐席侧反馈机器人回复张冠李戴把别的客户的事安在了当前客户头上。排查结论是真机上 4 个锚点——历史消息、群昵称、备注、近期消息发送者——全部命中李文宇而当前窗口里的对话者是另一位真实存在、名字就叫李文字的客户。归一化之后李文宇和李文字都变成同一个标准形两者相等系统判定当前会话就是李文宇的会话于是用李文宇的上下文——他聊过的订单、提过的诉求——去回复李文字的消息。出问题的匹配逻辑简化后大致是这段def normalize(text: str) - str: # 形近字映射宇-字已-己 … return .join(SIMILAR_MAP.get(ch, ch) for ch in text) def find_session(msg) - Session | None: name_ocr ocr(msg.screenshot) # 李文字 或 李文宇 key normalize(name_ocr) # 两者都变成同一个标准形 for s in sessions: if normalize(s.owner_name) key: # 两个真人撞在一起 return s return None这段代码单看没有语法错误问题在于它把猜测当成了事实归一化是一种猜测——这个名字也许是 OCR 误读的结果而李文字是真人、是既成事实。当猜测作用于一个真名字上两个不同的人就被无声地合并了。更隐蔽的是测试用的样例里恰好只有李文宇 OCR 误读这一种组合没有构造过李文字真人在线的反例所以全链路测试是绿的。连锁反应污染不止一层事故本身只是开始后续两件事让影响放大。其一被误拦的那几条回复已经带着李文宇的上下文写进了李文字的会话历史。之后即便匹配逻辑修好李文字的上下文窗口里也躺着一段他从没说过的话机器人后续回复继续被这段脏上下文带着跑。串聊不止是回错一次而是把错误写进了记忆需要人工清理会话历史才能止损。其二运营同学为了兜住系统认不出李文宇的工单把 OCR 误读出来的李文字当作别名补进了李文宇的白名单——方向反了。这下李文宇一个人裂成两个身份原本按白名单精确匹配的路径开始分裂同一个客户在不同场景下被当成两个人统计、召回、跟进全都对不齐。修复三条可以复用的工程原则原则一精确命中白名单时原样返回绝不再做归一化白名单是人工确认过的事实归一化是模型给的猜测事实的优先级高于猜测。只要输入精确命中白名单里的名字就直接原样返回、直接精确匹配不再经过任何形近字映射。修复后的守卫逻辑大致如下def resolve_key(raw: str) - str: # 1) 精确命中白名单这是事实原样返回 if raw in WHITELIST: return raw # 2) 多锚点一致且无 OCR 参与高置信原样返回 if is_multi_anchor_agreed(raw): return raw # 3) 仅当确认来自 OCR 且在疑似误读集合里才允许归一化 if comes_from_ocr(raw) and raw in OCR_SUSPECT_SET: return normalize(raw) return raw守卫的含义很直白归一化从默认路径降级为持证上岗——必须先证明当前输入确实可能是误读才有资格动手改写。白名单命中、多锚点一致这类高置信输入一律绕开模糊层。原则二形近字表只收真实误读对并加硬约束映射表不允许出现看起来像就可能混的字对只收线上真实发生、且经过人工确认的误读对每个条目要能追溯到具体 badcase。同时加一道结构性约束两个字若要定义为可归并在字表里的差异度必须足够大——规则是字表差异≥2 字才可并。像李女士/李先生这种只差 1 个字的称呼无论 OCR 给出多高的置信度永远不会被并成同一个人。这类称呼恰好是客服场景里密度很高的词1 字之差就是两个人宁可让少数误读漏过去也不能让真人被并掉。原则三容错逻辑下沉到归一化层不散在判断分支修复前散落在各判断分支里的容错补丁有七八处有的先精确比对再做模糊匹配有的做前缀截断有的看编辑距离。同一类容错在多处各写一遍行为不一致排查时没人说得清哪一层在起作用。修复后所有模糊匹配收敛到归一化这一个入口上游各分支只做精确比较。容错变成一个可单测、可审计、可灰度的独立模块而不是散在代码里的暗桩。通用教训模糊化之前先问一句这起事故不该算在 OCR 头上也不是映射表本身的错而是为了容错而模糊化这个动作没有边界。复盘之后我们给团队立了一条评审准则凡是打算引入为了容错而模糊化的逻辑——归一化、模糊匹配、别名合并、相似度阈值——都必须先回答一个问题两个真东西会不会被模糊成一样会必须先加守卫比如白名单原样返回、来源校验、差异度下限把模糊化的作用域压到已证实会出错的输入上。不会或不确定才允许默认开启同时给出可以一键关掉它的开关。容错的价值在于兜住少见的错误输入代价是给所有正常输入加了一层不确定性。好的容错应该让这层不确定性只作用于真正需要它的那一小部分输入而不是让所有李文字陪着李文宇一起为 OCR 的误读买单。参考文章串聊的本质是会话归属判错它与机器人什么时候该交还给人属于同一类边界设计问题下面两篇做了更系统的梳理人机配合自动回复转人工的触发设计客服回复模板库高频话术整理
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进