核查发现:话术不稳定的六个原因
每一条都有实测证据,不是推测。前三条解释「为什么改了不管用」,后三条解释「为什么会一直有新问题冒出来」。
改的地方,和线上跑的地方,根本不是同一份
近 30 天企微一共生成 130 次,其中用到代码里那份话术的次数是 0——线上 100% 跑的是 Langfuse 后台那一份。而代码里那份,近 120 天被改了 17 次。
换句话说:很多次「修好了」,其实一次都没上线。更糟的是半同步——只改了一半,留下自相矛盾:
【答产品用真信息】…缺字段时说"我让设计师按你户型拉清单" 【红线(违反就是事故)】…绝不说"我发你 / 让设计师整理好发你"
模型在两条互斥规则之间只能随机挑一条——这就是「时好时坏」的直接来源。
每天的话术质检,对照的是错的规则基线
质检会把「我方现行规则」喂给判定模型,让它回答「这条问题现有规则管没管」。但拼这份规则的代码,读的正是那份从不生效的兜底版。
后果是双向的:线上其实没有的规则,被判成「有规则但没遵守」→ 建议你去查代码;线上明明有的,被判成「缺规则」→ 建议你再加一条。建议的方向本身就是错的,照着改当然按下葫芦浮起瓢。
规则只加不减——而且最具体的那条,常常自己就写错了
企微话术已经堆到 7,346 token(约 6000 多中文字,还没算对话历史)。光「一条回复该多长、该不该抛钩子」就叠了五套互相拉扯的口径,且每轮全量注入。
最初判断是「规则互相打架,需要提示词大改造」。但真去查报价那条(实测只有 1/8 的时候带上「原价 868 → 补贴 300」),发现根因更具体:
报价提示块最后一条——最贴合「问多少钱一平」这个场景、最具体的那条指令——逐字规定了要说 "568 一平(…全包)",那句处方里根本没有 868 和补贴。而要求锚定的是一个遥远的通用块。
具体的赢过笼统的。所以修法不是减负,是把最具体那条改对。改完 1/8 → 8/8。
启示:动大手术之前,先逐条查「有没有哪条具体指令自己就写错了」。
没有回归闸:只验「这条修好没」,不验「以前修好的还在不在」
全仓 3 个评测脚本,没有一个覆盖话术;1000+ 条自动化测试全是纯函数(正则、切句、相似度),一条都不回答「给定这段对话,回复应该满足哪些条件」。
所以每次改话术都是裸改、无保护——这是「修一个坏一个」能一直循环下去的直接原因。
观测有盲区,看不见自己修的东西有没有生效
| 实测信号(近 30 天 / 130 次) | 次数 | 意味着 |
|---|---|---|
| 回复被程序事后清洗 | 76 / 130 | 58% 带了脏东西:导演说明、人设名前缀、句内复读 |
| 撞明禁项、重写后照样撞 | 6 | 然后照样发给客户了,没有第二道拦截 |
| 空回复兜底 | 11 | 8.5% 的轮次模型返回空,靠重试救 |
更麻烦的是两处真盲区:反复读硬闸触发时不写任何记录;明禁项重写那次调用不进追踪系统。修完没法验证,只能靠肉眼看对话。
有整块场景压根没接上电
不只是「某条规则写得不好」,而是几整块能力建好了、没通电:
- 决策期话术是死库存——59 条企微会话的阶段分布是
培育 50 / 破冰 5 / 已转人工 4,决策期 0 条。没有任何代码会把会话推进到决策期。于是 52 条决策期金牌问答 + 11 条决策原则 全是接不上电的库存:客户问「哪天能去看」「定金怎么算」,AI 还在用培育期口吻。 - 企微没有转人工出口——AI 写什么就发什么。抖音有一道置信度闸(负面情绪、投诉、临门决策转人工),企微是空的。
- 长对话会失忆——过了第 8 轮,AI 对客户的记忆只剩最近 5 轮对话 + 我方报过的价。客户第 3 轮说的「家里有小孩怕甲醛」「预算 3 万」直接蒸发。
生产里从来没有过 50 轮。59 条企微会话中,客户消息 ≥10 条的只有 3 条,最长 27 轮——而且那条的会话名是测试壳。
也就是说,此前所有关于「50 轮质量」的判断都来自推演,不是真客户。
方案:先让改动生效,再让改动不互相打架
顺序是刻意的。前两步不做,后面全白干——因为你改的东西不一定生效,质检给的方向也不一定对。
- 装一道漂移闸已上线自动比对「线上正在跑的话术」与「代码里的兜底版」,不一致就报红,并指出**具体分叉的那一句**。彻底堵死「改了以为上线了、其实没动线上」。
- 让质检读线上真规则已上线改成运行时直接拉线上那份,拉不到才退兜底、且如实标注「这是兜底版」。质检建议的方向从此是对的。
- 修掉线上那句自相矛盾已上线话术发到 v3,删掉「拉清单」,与红线口径统一。
- 把历次修过的问题逐条固化成用例27 条每条 = 一段对话 + 客户这一句 + 一组确定性断言(不能出现什么、必须出现什么、多长、能不能换行)。刻意不用 AI 当裁判——裁判自己会漂,漂了就没人知道闸是真绿还是假绿。
- 走生产路径,验的是线上话术直接调线上同一套逻辑、读真实知识库,所以运营在后台改完话术立刻能验,不用等发版。一轮成本约一毛五。
- 定规矩:改前改后各跑一次,只许「全绿或更绿」这样「以前修好的又坏了」会在上线前被抓住,而不是等客户聊崩了才发现。
- 必须 100% 兜住的,从话术升级成代码硬规则「有规则但 AI 就是不遵守」的,别再往七千字的话术里加第二遍——那只会加剧规则打架。改成代码里确定性拦截 + 自动重写。误伤边界是这里的重点:「定金不能退」「对开门不加价」「不用再加微信」都是正确说法,拦错了等于把 AI 从正确答案上推开。
- 最具体那条指令,把它改对见原因 3。报价那条一行改动换来 700% 提升。
- 互相打架的规则,逐对拆掉例:话术风格段写着「可以拆成 2-3 段连着发」,节奏铁律写着「整条禁止换行」——同一份话术里直接对撞。企微那句改掉(抖音多气泡是正常的,留着)。
- 决策期接线已实现·待合并52 条决策问答终于通电。判据是按企微的节奏另立的——抖音收口是「引导加微信」,企微双方早是好友、收口是「引导到店」,所以信号完全不同(见效果区)。
- 转人工出口待做,见卡点。
- 长对话记忆待做,见卡点。
实现后的效果
全部为实测:每条用例跑 8 次采样、低并发、零作废样本。
总分:19 → 27 / 27
| 修的问题 | 修前 | 修后 | 怎么修的 |
|---|---|---|---|
| AI 编造「7 天犹豫期可退」(公司没这政策) | 1/4 | 8/8 | 升级成代码硬规则;白名单守住「环保不达标全额退」这类真承诺 |
| 报价只甩「568 一平」,不给占便宜锚定 | 1/8 | 8/8 | 把锚定写进最具体那条处方本身 |
| 企微里说「加微信」(已是好友,红线) | 7/8 | 8/8 | 代码硬拦;否定式「不用再加微信」放行 |
| 推拉门说成「不加价」(签约后加钱=投诉) | 7/8 | 8/8 | 按小句判,不误伤「对开门不加价」 |
| 回复带换行被拆成多条气泡刷屏 | 6/8 | 8/8 | 拆掉话术里那处「能换行/禁换行」的对撞 |
| 答上一句、漏最新一句 | 55% | 92% | 见下 |
两件闸一建起来就抓到的事
八次采样编了六次,措辞一次比一次具体像真的:
「交定后有7天犹豫期,期内可随时退定。开始量尺服务后不能退。」
话术里那条「退款别乱承诺」写了快一个月,模型照样编。客户拿这句去对账就是纠纷。已改成代码硬规则,修后八次全过。
客户前面问过甲醛、最新一句问工期,AI 把环保讲得头头是道、工期只字不提。实测通过率只有 55%~60%。
查下去:「只回答客户最新这一句」这条铁律,只有会话超过 16 条消息才注入。而企微真实会话绝大多数不到 10 条——绝大部分会话从没收到过这条指令。
短会话也补上后:55% → 92%。
决策期接线:判据必须按企微的节奏另立
接线时差点直接复用抖音那套决策信号检测——幸好被拦下。它的信号清单第 5 条写着「要求加微信/留电话」:
| 抖音 | 企微 | |
|---|---|---|
| 收口动作 | 把人导到微信 | 约到店 / 交定 |
| 「要微信」意味着 | 最强的决策信号 | 异常——双方早是好友,这是红线明禁的话 |
拿抖音判据套企微,等于把一个异常信号读成了利好。企微另立一套,全部围绕到店/交定:问店址、约时间过去、问量尺安排、问定金付款合同、明确说要做——任一即进决策期;问工期、追问名额算弱信号,要两条才进。
刻意不收的:纯问价格板材(还是培育期咨询)、「我再看看」(那是收口信号,另有处理)、以及否定式「先不用上门量」——客户明确踩刹车时切进决策期,等于在人家说「先不用」时反而逼单。
新增两条用例验证,其中第二条是刻意设计的防「切了阶段就把红线丢了」:决策期话术更激进,得确认「绝不承诺退款」「不导微信」在新阶段照样生效。结果都是 8/8。
过程中纠正的两个自己的错误
- 用 3 次采样定基线是假的。3 次分不清「33% 遵守」和「67% 遵守」——报价那条在不同的跑里给出过 0/1、2/3、2/3、0/3,看着像抖动,提到 8 次才看清真相是 1/8,此前严重低估。现在定基线一律 8 次起。
- 闸差点把网络抖动报成话术回归。某次跑完全绿从 20 掉到 10,一片红,看着像刚上的护栏把话术全搞坏了——查下来 66/200 次采样是网络连接失败,被当成了断言失败。一个会把网络抖动报成话术回归的闸,比没有闸更危险,它会让人回滚掉正确的改动。已修:失败自动重试、仍失败则该样本作废剔出分母、作废率过高直接报「闸没跑成」而不是「话术坏了」。
后续待做的卡点
按优先级排。前两条需要业务先拍板,不是纯技术问题。
1 · 企微没有「转人工」出口已修
已完成分两档:转人工(客户要真人 / 投诉发火 / 连续 2 次拒绝)→ 会话停止自动回复; 本轮不发(生成为空 / 超长 / 护栏没救回)→ 只跳这一轮。 告警推新建的独立「企微回复监控群」,按销售号 @ 到人(夏夏 / 饭粒 / 野荞已肉眼验证能 @,Gus 刚补上待确认,只差子扬)。 刻意不照搬抖音那道「决策期一律转人工」的闸 —— 照搬会把刚接上线的决策期又全掐掉。
2 · 长对话失忆已修
已完成确定性画像抽取 + 滚动摘要已上线。另外补上了「只答最新这一句」铁律 —— 它原先只有超过 16 条消息的会话才注入,而真实会话绝大多数不到 10 条,等于绝大部分从没收到过。
3 · 决策期信号已定档
已完成定义定为「该开始推他去到店了」(引导型,不是「客户已经想去了」)。 收口信号(「行有数了」「我再看看」)升为强信号、紧迫/要总价为弱信号、 并加了早期观望否决闸(「明年才交房」的客户也会说「我再看看」,不拦住就是对着还早的人逼单)。
4 · 观测盲区已修
已完成反复读闸命中写信号、明禁项重写接入追踪,另加一张按轮次分段的质量看板。首跑就推翻一个假设:「回复被事后清洗」前 5 轮最高(52%)、越往后越低,跟轮次无关。
5 · 金牌问答 165 条只有 20 条能用已修
已完成取数是先全局取前 20 条、之后才按阶段筛,导致抖音破冰只剩 1 条示例可用 —— 近 14 天 500/500 次破冰注入的都是同一条,而那条还标错了(是对话中途的转接话,不是开场白)。 改成按阶段各取 25 条,受控对比验过 25 优于 12 优于 8。
6 · 语料/知识库入库时没有红线体检已修
已完成这次清出 5 处跟公司政策相反的退款口径,其中两处在问价必被检索到的知识库文档里。 运行时已加过滤(注入前拦 + 报出是哪条),入库那一侧的闸也已补上(命中红线即拦 + 推卡片 @ 到人核对), 知识库与语料两侧的错误口径已更正、并留了回滚快照。
7 · 说话人标反(我方话术被记成客户消息)已修
已完成至少 4 条会话里,我方话术被记成了客户消息(云电脑截屏读左右气泡出错)。 AI 自己都发现了,回过一句「哈哈您抢我词儿啦」。 这会同时污染决策信号判据、话术质检、回归语料三处地基 —— 它们的前提都是「只看客户说的话」。 消息落库这一侧已补上嗅探(命中即跳过这一轮 + 推卡片要人核对,不改数据); 7-22 又把存量清了:分三轮收敛改掉 8 行、并清空这些会话的滚动摘要让它按干净历史重建。
上面所有「修后」分数都是回归闸的实测,不是线上真实客户对话的效果。闸测的是「给定这段对话,回复该满足哪些条件」——它能保证以前修好的不再掉,但不等于客户满意度提升。
真实效果要等这批改动在生产里跑一段时间,再看回复率和加微率。
关于 @ 人的一个更正:过程中我一度判断「Lark open_id 按机器人隔离、跨告警不能复用」,据此加了警告。这条是错的 —— 团队 2026-07-14 已用探针卡实测证伪,跨告警复用同一串 id 没问题;我把 lark-cli 的一个报错当成了真实限制。真正要小心的是另一件事:@ 渲染成没成功,接口看不出来,只能人眼看群里的卡,所以每加一个人都发一张探针卡确认。
另外:企微会话里混着同事测试的探针号、同行名片和企微系统消息,所以任何按会话数算的百分比都偏低、不能直接当真实客户表现读。清洗这层之前,只看绝对条数和逐条实录更可靠。
换一把尺子再量:能把人引导到店吗,能撑到 50 轮吗
上面四节量的是「话术稳不稳」。这一节换成两个业务目标当尺子重新量一遍: 把客户引导到店、能跟客户聊满 50 轮。 结论很反直觉——话术已经不是瓶颈了。
62 条企微会话,剔掉自测号、同事演练号和归档壳,剩 46 条像真客户。然后:
- 33 / 46 客户开口 ≤2 句就没下文了
- 最长的一条真人对话是 11 个来回(不是 50,也不是 27)
- 决策期会话 0 条、滚动摘要 0 条 —— 7-21/7-22 刚上线的这两个能力,一次都没在真实会话上触发过
- 回归闸复跑 27/27 全绿
所以 50 轮记忆是造好了没派上用场——真实对话平均活不过 3 轮。 瓶颈不在「记不住」,在「聊不下去」和「根本没在跟真人聊」。 这一节的六个问题,全部落在话术的前后两层。
转人工闸会把正常客户永久闭麦
发现:判据是裸词匹配——「人工」「真人」「找个人」「骗」「垃圾」「烦」出现即转人工。拿全屋定制最常见的问句回放,9 句里中 5 句:
「安装人工费是另算的吗」 → 命中「人工」 「你们是真人上门量尺吗」 → 命中「真人」 「我找个人帮我看看户型」 → 命中「找个人」 「板材是不是真的兔宝宝,别骗我」 → 命中「骗」 「垃圾桶要留个位置吗」 → 命中「垃圾」
而「已转人工」是终态——全仓没有任何一处能把它改回来,只能手工改数据库。也就是说:客户问一句人工费,从此石沉大海。
方案:判据全改成带上下文的正则(要「转人工 / 人工客服 / 有真人吗」才算,「人工费 / 真人上门量尺」放行),并对顾虑句和转述句放行(「就怕被骗」「网上说有的商家会骗人」不是投诉)。再加一道 24 小时冷静期自动复活:过了冷静期客户主动再来、且这一轮不再命中任何判据,就把会话接回自动回复;还在发火的照样接不回来。
效果:把「问一句人工费就永久失联」这个最贵的失败模式关掉;即使判错,代价也从永久降级成最多一天。
认人认错,同一个人被切成好几条会话
发现:企微单聊没有稳定的用户 ID,身份就是云电脑截屏读出来的那个显示名。读歪一次就凭空多一条会话、历史清零。库里实测躺着这些「客户」:
「装修设计参考,还有两份必坑指南发送中」 ← 把消息正文读成了名字 「在我们 可以开始聊天了」 ← 把系统提示条读成了名字 「原」「屋」 ← 名字被截断 「����̽��A」 ← 名字整串编码坏掉
这直接顶死了 50 轮:一条对话要活到 50 轮,前提是这 50 轮都被认成同一个人。
方案:加一道「这串字符像不像人名」的闸,只拦「凭空建新会话」——已经存在的同名会话照常用(万一真有人叫这名,他的历史不会丢),拦下来就推卡片要人核对,不静默丢弃。
首版还拦了「单字名」「纯符号/纯数字名」「带句读的长昵称」。上线前拿库里 974 个真实抖音昵称回放,误伤 5.4%(53 个),全是活生生的人:
单字:战 芬 晨 禅 孙 妃 香 龙 颖 静 陈 …(20 个) 纯符号数字:👀 🌈🌈🌈 🚘🚘 1656623122 369 带句读的:「初心易得,始终难守」「人生若只如初见,何事秋风悲画扇」
我原来写「真实单字昵称极少」纯属拍脑袋——中国人这么起名太常见了;抖音的默认昵称本来就是一串数字。
拦掉 5% 真客户(他们的消息会被整轮丢掉)比放过几条僵尸会话严重得多。所以那三条判据全删了,只留下拿同一批真名回放零误报的三类:编码乱码 / 含成句短语 / 超长。代价是「原」「屋」这种截断成单字的漏过去——认了,宁可漏。
启示:任何「拦截类」规则上线前,都要拿真实数据回放量一遍误伤率,不能靠常识判断。
一句「我再看看」,就把人锁进永久催单
发现:决策期的节奏规则是「每条回复都要顺一个到店钩子」。而「我再看看吧」「心里有数了」被列为强信号,一句话就切进决策期,而且写进数据库永不回退。于是一个只是随口客气一句的培育期客户,从此每条都被推到店——正是 7-02 刚修掉的「像催单机器人」。
方案:把「这一轮用哪套话术」和「数据库里记什么阶段」拆开。话术阶段每轮按最近 4 条客户消息重算,信号没了就自动降温回培育期口吻;数据库里的阶段仍然只进不退,留着当漏斗指标用。
效果:既不丢「这个客户到过决策期」这个指标,又不会把还在咨询的人按在催单模式里。顺带堵死一类事故:阶段值只剩两个取值,异常阶段掉进「抖音冷开场」分支、对已加微的客户说「发个微信号我让设计师加你」这条路从源头走不到了。
在跟系统号、资讯号、广告号认真聊天
发现:「服务总结」「行业资讯」「BibiCoding」「A杨琳 精艺图文广告」这些全都在被正常回复——白烧 token,还把它们算进了转化率的分母。
方案:服务通知类、资讯推送类判为系统号闭麦;公司/广告号单独一档——只在话术复盘时跳过,代回照常回。因为「某装饰公司的老板自己要装房子」是真实存在的单,绝不能闭麦。
客户隔了很久再问一遍,会被当成老话吞掉
发现:为了防「客户没说话我方却狂发」,系统会把客户这一轮说的话跟全部历史比对,重复的就不回。结果客户在第 30 轮又问一次「多少钱」,因为这行历史里出现过 → 判成老话 → 整轮不回,AI 一声不吭。对话越长越容易撞,正好跟 50 轮的目标反着来。
方案:比对窗口从「全部历史」收窄到最近 8 条。防重发照样有效(截屏重复读最多也就翻出最近几条),而隔了很久的重新提问会被正常接住。
「到店」是业务目标本身,却从来没有被度量过
发现:会话阶段只有「破冰 / 培育 / 决策 / 已转人工」,没有「约成」这个终点;每天的企微日报报的是加好友数(已通过 / 等通过 / 加不上),跟对话质量、跟到店一点关系没有。
后果是:所有话术改动在业务层都不可证伪——只能看回归闸绿不绿,永远看不到「到底约成了几个」。
方案:两件事。① 加一条确定性的约成判据(必须有确定的时间锚点或明确应约,「我哪天过去方便」这种反问不算),命中就落库并推一张绿卡,让人去接待、提前备样品色卡。② 每天 21:00 推一张五档漏斗。
近 30 天 新建会话 43 ① 客户开过口 39 (占新建 91%) ② 聊起来了(≥3句) 12 (占开口 31%) ③ 聊得深(≥6句) 7 (占聊起来 58%) ④ 到过决策期 0 ⑤ 约成到店 0 转人工 4 · 客户说了话我方没回 21 · 最长对话 15 个来回
效果:第一次能回答「今天约成几个」「卡在哪一级」。而且每一档都带「占上一档的比例」——单看绝对值看不出哪一级在漏,而这条通道最大的漏点恰恰在最前面。
漏斗第一次跑出来,最刺眼的是「21 个客户说了话我方没回」。第一反应是「云电脑没在跑,先去查机器」。
逐条查下来,这个数大部分是假象——而且假象的来源正好就是这次修的那几类:
| 被算成「客户在等回复」的会话 | 那条「客户消息」其实是什么 |
|---|---|
| BibiCoding | 资讯推送号转发的新闻标题 |
__probe__ [探针残留归档] | 测试探针残留,不是客户 |
| 张伟 | 「不客气,有进展我会及时通知你。」= 我方话术被标反 |
| 世界好坏我想睡觉 | 「没有找到相关结果 可以开始聊天了*」= 截屏串屏的垃圾文本 |
真正在等回复的真人客户只有个位数,而且都是很久以前的。云电脑其实是活的(其中一台几分钟前还在拉单)。
启示:一个新指标第一次亮起来的时候,先验分母干不干净,再下结论。否则很容易把「数据脏」误诊成「业务差」,然后去修一台根本没坏的机器。
回归闸建好了,但没人保证它会被跑
发现:7-21 列的根因第 4 条就是「缺乏回归闸」。闸后来建起来了(27 条用例),但它不在持续集成里——要额度、要花钱。更要命的是:线上话术在后台改,根本不发版、不经过任何检查。也就是说「改一个坏一个」随时能从代码搬到后台原样重演,而且没人会知道。
方案:挂成每天凌晨 4 点自动跑(一轮约 ¥0.15),失败自动推红牌到告警群。
接下来该做真实案例实测,而不是继续排查
这是这次复查最重要的一个判断,理由有三条。
- 话术层已经稳了,继续静态排查的边际收益在掉。回归闸 27/27 全绿,7-21 列的 7 个卡点全部收口。再往话术里挖,挖出来的多半是「理论上可能」而不是「实际在发生」。
- 决策期和 50 轮记忆,至今 0 次 真实触发。没跑过的东西没法优化——只能等真实对话把它们激活,才知道判据准不准、摘要有没有把事实记丢。
- 今天最有价值的那个发现,恰恰说明再挖脏数据只会挖出更多假象。「21 个没回」是假的,「决策期 0 条」也可能只是因为真人对话根本没聊到那一步。需要新鲜的真实对话来产生可信信号。
1 · 每天看那张漏斗卡,只盯三个数:客户开口率(现在 91%)、聊到 3 句以上的比例(现在 31%,这是最大的漏点)、约成到店数(现在 0)。
2 · 攒 5–10 个真实多轮对话再复盘。标准是客户开口 ≥6 次。少于这个量的复盘,结论会被个例带偏。
3 · 定向验证两个新能力:找一个真实客户把对话推到「问店址 / 问定金」,看决策期有没有正确切换、话术有没有变;再找一个聊满 16 轮以上的,看滚动摘要有没有把早期说的城市/面积/预算记住。
4 · 一周后带着漏斗数据回来排查。那时候的排查才有靶子——是卡在第一轮承接、卡在中段没话说、还是卡在临门不敢约,漏斗会直接告诉你。
这一轮没有让对话直接变长,它做的是把挡在 50 轮前面的东西挪开——误闭麦、认错人、重复提问被吞、被永久催单——再把「到店」变成一个每天看得见的数。
能力已经就位。下一步缺的不是代码,是真实客户把这条链路跑一遍。