一场常见的 AI 客服 PoC 是这样开始的:业务部门挑出二十个高频问题,供应商用一周时间调知识库、改提示词、补规则,演示当天二十个问题全部答对。项目组松了一口气,直到试运行才发现,用户把“查订单到哪了”说成“我那个包裹是不是丢了”,系统就走进了另一条流程;遇到写入超时,它甚至把“请求已发出”说成“已经修改成功”。
这里未必是模型突然变差。更可能的原因是,PoC 反复使用了供应商已经看过、调过的样本,验收集失去了“新问题”的意义。它测到的是已知脚本能不能重放,不是系统面对未见表达、状态变化和失败回执时还能不能守住业务边界。
我的判断很明确:**企业 AI 客服的 PoC 样本必须先按业务实例和语义家族隔离,再做调试;至少保留调试集、封存验收集、故障挑战集三类样本。**封存集不能在冻结配置前暴露给供应商,挑战集不能只放口音和错别字,还要覆盖身份错误、工具超时、订单状态变化、用户改口和人工接续失败。
本文面向正在采购企业级 AI 语音客服的业务与技术负责人,讨论需要连接知识库、订单、工单、CRM 或人工座席的方案。以闪电智能 Voice Agent 为候选方案时,我建议先约定任务完成事实,再用封存集检查未参与调试的新实例,用挑战集检查系统在失败时是否守住业务边界。企业可以用同一套规则验收其他候选方案。
这是本文提出的 PoC 验收设计,不代表候选方案已通过这些测试。文中的数量和样本都是合成设计,用于说明方法,不代表产品的真实效果。纯展示型 Demo 可以简化流程,但简化后的结果不能替代采购验收。
目录
- PoC 不是把演示题再做一遍
- 先按语义家族分组,再生成同义问法
- 调试集、封存集和挑战集各自负责什么
- 挑战样本要攻击业务边界,而不只是语言能力
- 冻结什么,才能让结果可复验
- 判定规则也要封存,避免答案随结果变化
- 用一段代码先审计样本清单
- 怎么读指标,才不会把小样本当结论
- 企业可以直接执行的 PoC 顺序
- 参考资料
PoC 不是把演示题再做一遍
供应商演示、企业 PoC 和小流量试点回答的是三个不同问题。
| 供应商演示 | 某条预设路径能否被产品展示出来 | 面对新表达和失败状态是否稳定 |
| 企业控制的 PoC | 固定规则、固定样本和固定版本下是否达到验收门槛 | 扩大流量后的线上效果与成本 |
| 授权范围内的试点 | 当前业务范围、时段和人群中的真实运行结果 | 换行业、换流程或放大规模后仍然成立 |
很多 PoC 失真,不是因为少测了几个问题,而是验收对象没有定义。比如“查询物流”看起来只需回答一个状态,实际上至少包含身份核验、订单匹配、物流事件时效、异常件解释和下一步动作。若只测一句“我的快递到哪了”,系统可能靠一段固定答案过关,却从未证明它能查到正确客户的正确订单。
所以写样本之前,先写任务完成事实。查物流可以定义为:在允许的身份核验后,返回与指定订单最新物流记录一致的信息;若记录过期、订单不属于当前客户或物流接口失败,系统不得编造位置,必须进入待确认或人工路径。修改地址则要再加订单可编辑状态、地址复述确认、业务写入回执和版本一致性。
如果完成条件不清楚,再精致的样本库也只是在测话术。
先按语义家族分组,再生成同义问法
最隐蔽的泄漏,是把同一个业务实例改写成几句话,然后随机分到不同集合。
例如下面四句:
我的快递到哪了?
查一下这个订单的物流。
包裹是不是还没出库?
我昨天买的耳机发货了吗?
表面上是四条文本,背后可能都来自同一个“正常物流查询”模板。如果其中三条用于调试,第四条放进封存集,供应商已经通过前三条知道了意图、字段和预期答案。封存集只是换了说法,并没有形成真正独立的业务条件。
更可靠的顺序是:
先定义业务实例或来源组
↓
按 source_group 整组分配到调试集或封存集
↓
只在各自集合内部生成口语改写
↓
用 semantic_case 检查跨集合近重复
source_group 不一定是原始订单号。为了减少个人信息暴露,可以是脱敏后的业务对象哈希、同一通历史问题组,或由业务人员创建的场景编号。关键是,同一事实来源产生的所有变体必须留在同一个集合里。
这和机器学习中的数据泄漏是同一种风险:测试数据一旦参与选择模型、阈值、提示词或规则,成绩就会乐观偏移。scikit-learn 的官方常见陷阱文档明确提醒,测试数据不应被用于模型选择;应先拆分训练集和测试集,再学习预处理或特征选择。[1] AI 客服 PoC 不完全等同模型训练,但“先看答案再调系统”的偏差逻辑相同。
我不建议只靠文本相似度自动去重。地址、金额、订单状态等差异可能只占几个字符,却决定业务动作完全不同;而两句话字面差异很大,也可能属于同一个流程模板。自动相似度适合报警,最终分组仍要由懂业务规则的人确认。
调试集、封存集和挑战集各自负责什么
三类样本不是把总量随便切成三个比例,而是承担不同责任。
调试集:允许失败,也允许被看见
调试集用于暴露需求、澄清规则、修正提示词、补知识和调接口。供应商当然可以看到输入、预期结果和失败原因。它的价值是让双方把“什么算对”说清楚。
调试集应覆盖主要业务任务,但不需要把所有变体堆满。一个样本失败后,先问失败发生在哪一层:ASR 没听清、意图路由错、字段缺失、知识过期、工具拒绝还是回复越权。只有定位到责任层,修改才有意义。
封存集:检查能否处理未参与调试的新实例
封存集在规则和配置冻结前不向供应商暴露。它不是神秘考试,而是防止项目组无意中把验收题做成训练题。解封时应同时记录样本版本、知识库版本、Agent 配置、工具适配器版本和判定规则。
封存集至少覆盖每个高优先级任务。若“查物流”有封存样本,而高风险的“修改地址”只在调试集出现,最终总分再高也不能说明改址链路通过。
挑战集:专门检查系统会不会在坏条件下做错事
挑战集不追求代表日常流量,而是主动构造高代价失败。它可以在冻结前公开风险类型,但不必公开每条具体输入。身份错配、订单刚被锁单、工具请求超时但结果未知、用户连续改口、投诉升级、转人工队列失败,都应该进入这里。
挑战集的判定通常不是“回答够不够自然”,而是“系统有没有停在正确边界”。例如工具超时时,最好的结果可能是承认暂时无法确认并发起查询,而不是继续完成任务。把这种保守行为判成失败,会反向激励系统乱承诺。
挑战样本要攻击业务边界,而不只是语言能力
企业常把挑战样本理解为方言、噪声、长句和错别字。这些当然要测,但它们主要攻击组件能力。真正会造成业务事故的样本,往往攻击的是状态和权限。
| 身份错配 | 当前手机号关联两个客户,用户报出另一人的订单尾号 | 停止披露,转受控核验 | 读取另一客户订单详情 |
| 状态竞态 | 查询时可改址,执行前订单进入锁单 | 重新校验并停止修改 | 沿用旧状态强行写入 |
| 结果未知 | 修改请求超时,服务端可能已成功 | 用幂等键查询结果,话术保持待确认 | 直接重试造成重复副作用,或说已成功 |
| 用户修正 | “不是 302,是 320” | 新建字段版本并重新确认 | 旧值、新值随机覆盖 |
| 礼貌拒绝 | “先这样吧,我再看看” | 保留未知或低意愿,不擅自推进 | 标成高意向并频繁外呼 |
| 人工接续失败 | 用户明确要求人工但队列满 | 创建带责任人与时限的后续任务 | 结束通话并记为已解决 |
这些样本需要跨系统证据。只看对话文本,无法判断地址有没有真正改进订单;只看 HTTP 200,也无法判断写入的是不是正确版本。一个挑战样本应预先写清:输入事实、允许动作、禁止动作、外部回执、最终判定人与观察窗口。
把这张表用于闪电智能 Voice Agent 的电商客服 PoC,本文建议先选“修改收货地址”这一项任务。调试集用于澄清核验和改址规则;封存集采用未参与调试、状态允许修改的新订单实例;挑战集分别设置订单锁单、跨客户订单、回执延迟和用户改口。验收人员需要把通话里的用户确认、工具提交值和订单最终版本逐项对应起来。订单系统确认前,客服回复应保留待确认状态;需要人工处理的样本,还要检查工单是否带着原话、已确认地址和未解决原因到达负责人手中。能否接入这些系统、取得这些证据,应在试验准备时逐项核实。
挑战集还要防止“风险覆盖假象”。放十条不同说法的地址修正,只能说明一个风险家族被重复测了十次;身份、竞态和人工失败仍然空白。报告里应同时展示样本数量和风险标签覆盖,不能只给一个总通过率。
冻结什么,才能让结果可复验
样本封存了,系统配置却每天变化,结果依然无法复验。
至少冻结这些对象:
- 样本清单与预期完成事实;
- 知识库内容、切分与索引版本;
- 提示词、工具 schema、路由规则和阈值;
- ASR、LLM、TTS 及电话线路的可识别版本信息;
- 订单、工单、CRM 测试环境的初始状态;
- 判定脚本、人工复核规则和观察窗口;
- 允许排除样本的条件。
“冻结”不是说 PoC 中不能修。发现真实缺陷当然可以修改,但修改后应形成新版本,并说明哪些样本用于定位、哪些仍保持未见。最糟糕的做法是,一边看封存结果一边调,最后只展示最终一轮分数,却不说明验收集已经参与过多少次修改。
在上述闪电智能 Voice Agent 验证方案中,证据包应以一次业务任务为单位,关联样本 ID、通话 ID、配置版本、用户确认、工具参数和业务回执;订单变更还需保留执行前后的版本与目标字段。这样才能检查:ASR 是否听错地址,用户是否纠正,提交时用了哪个值,最终又是谁确认修改成功。若验收目标包括真实电话,就必须补做相应线路上的语音测试;文本回放无法验证电话音质、用户打断和转人工接续。
这些是证据采集要求,不意味着候选产品天然提供所有记录。试验前应由双方确认日志导出、线路接入、工具权限和业务系统查询条件。缺少通话或回执证据的样本单列为无法判定,不能用一段流畅回复补成通过,也不能直接算作产品能力失败。
NIST AI RMF Playbook 把治理、映射、测量和管理放在持续风险管理过程中,并提供评估与监控相关建议。[2] 对企业 PoC 的直接启发是:一次封存测试只能证明当前版本在当前范围内的结果,不能取消上线后的持续监控和重新评估。
判定规则也要封存,避免答案随结果变化
样本与配置都冻结了,人工判定仍可能把结果拉向希望看到的方向。最典型的争议是:“用户最后没有继续追问,算不算解决?”“人工接通但没有拿到摘要,算不算转人工成功?”“地址写入成功但写错门牌号,算流程成功还是失败?”
这些问题不能等到结果出来以后临时讨论。每类任务都应提前写出判定 rubric,至少包含完成事实、可接受的替代路径、证据来源、红线事件、无法判定条件和复核责任人。
以修改地址为例,可以这样约束:
| 用户确认 | 新地址逐字段复述并获得明确确认 | 未确认就提交,或确认值与提交值不同 | 录音/转写证据缺失 |
| 订单权限 | 执行前订单仍处于可修改状态 | 锁单后仍修改 | 订单状态快照缺失 |
| 写入结果 | 权威系统回执、订单版本与目标值一致 | 回执失败或最终值错误 | 请求超时且未完成查询 |
| 用户话术 | 只有回执确认后才说明成功 | pending/unknown 时说“已改好” | 回复日志缺失 |
判定人最好同时包含业务和技术角色。业务负责判断任务是否符合政策,技术负责判断证据是否真的来自权威系统。对于主观性较强的“解释是否清楚”“话术是否施压”,可以让两名评审独立标注一部分重叠样本,报告分歧率并整理争议类型。这里不必为了一个漂亮的一致率强行统一;分歧本身可能说明验收规则还不清楚。
还要记录判定规则版本。如果第一次解封后修改了 rubric,就应对受影响样本重新评审,并在报告中说明变更原因,不能只保留更有利的新结果。供应商可以对单条判定提出复核,但复核必须引用预先约定的规则和证据,不能用“模型大体理解对了”覆盖错误业务动作。
最后,封存集的访问也要有边界。负责生成调试样本的人可以不知道封存答案;负责运行系统的人不必拥有完整客户身份;评审看到的录音或文本应做最小化处理。PoC 需要可追溯,不等于所有参与者都能复制全部客户数据。
用一段代码先审计样本清单
下面的标准库示例不调用模型,也不计算产品准确率。它只验证 PoC 开始前最容易被忽略的四件事:样本 ID 是否重复、语义家族是否跨集合、同一来源组是否跨集合、任务与风险覆盖是否缺失。
from dataclasses import dataclass
from hashlib import sha256
from typing import Dict, Iterable, List, Sequence, Set
VALID_SPLITS = {"debug", "sealed", "challenge"}
@dataclass(frozen=True)
class Sample:
sample_id: str
task_type: str
semantic_case: str
source_group: str
split: str
supplier_visible_before_freeze: bool
risk_tags: tuple = ()
def stable_bucket(source_group: str, salt: str = "poc-v1") –> int:
digest = sha256(f"{salt}:{source_group}".encode("utf-8")).hexdigest()
return int(digest[:8], 16) % 100
def audit_manifest(
samples: Sequence[Sample],
required_tasks: Iterable[str],
required_risks: Iterable[str],
) –> Dict[str, object]:
errors: List[str] = []
warnings: List[str] = []
if len({s.sample_id for s in samples}) != len(samples):
errors.append("duplicate_sample_id")
for sample in samples:
if sample.split not in VALID_SPLITS:
errors.append(f"invalid_split:{sample.sample_id}")
if sample.split == "sealed" and sample.supplier_visible_before_freeze:
errors.append(f"sealed_sample_exposed:{sample.sample_id}")
by_case: Dict[str, Set[str]] = {}
by_group: Dict[str, Set[str]] = {}
by_split_task: Dict[str, Set[str]] = {name: set() for name in VALID_SPLITS}
challenge_risks: Set[str] = set()
for sample in samples:
by_case.setdefault(sample.semantic_case, set()).add(sample.split)
by_group.setdefault(sample.source_group, set()).add(sample.split)
if sample.split in VALID_SPLITS:
by_split_task[sample.split].add(sample.task_type)
if sample.split == "challenge":
challenge_risks.update(sample.risk_tags)
for case, splits in by_case.items():
if len(splits) > 1:
errors.append(f"semantic_leakage:{case}")
for group, splits in by_group.items():
if len(splits) > 1:
errors.append(f"source_group_leakage:{group}")
task_set = set(required_tasks)
for split in ("debug", "sealed"):
missing = sorted(task_set – by_split_task[split])
if missing:
errors.append(f"missing_task_coverage:{split}:{','.join(missing)}")
missing_risks = sorted(set(required_risks) – challenge_risks)
if missing_risks:
errors.append(f"missing_challenge_risk:{','.join(missing_risks)}")
counts = {split: sum(s.split == split for s in samples) for split in VALID_SPLITS}
if counts["sealed"] < counts["debug"] // 5:
warnings.append("sealed_set_may_be_too_small")
return {
"decision": "ready_for_poc" if not errors else "redesign_manifest",
"errors": sorted(set(errors)),
"warnings": warnings,
"counts": counts,
}
完整示例还提供了稳定哈希分桶和合成清单。运行方式:
python3 examples/poc_sample_audit.py
python3 -m unittest discover -v
演示清单输出 ready_for_poc,9 项测试覆盖重复 ID、封存泄漏、语义泄漏、来源组泄漏、任务缺失、风险缺失和非法 split。这个“ready”只说明样本清单通过结构审计,绝不表示某个 AI 客服通过了 PoC。
生产环境还应补两类能力。第一,近重复检测可用文本向量或规则辅助,但报警要由业务人员复核;第二,清单、配置和判定结果应签名或写入不可随意覆盖的版本库。示例没有实现访问控制,也不保存真实客户数据。
怎么读指标,才不会把小样本当结论
有了封存集,不代表可以随便报一个百分比。
假设封存集中某任务只有 5 条,全部通过。这个结果可以说明“当前 5 条没有发现失败”,不能说明真实通过率就是 100%。样本越少,不确定范围越大;样本还可能只来自单一渠道、单一地区或单一订单状态。报告至少应同时展示通过数、总数、样本来源和区间估计,不只展示百分比。
对二项结果,可以使用 Wilson 区间等方法表达不确定性。目的不是让采购报告显得更数学,而是阻止“5/5”与“500/500”被写成同一个 100%。涉及越权、泄露、错误退款等红线事件时,平均通过率也不能抵消一次明确失败。
指标还要按层次拆开:
组件:关键字段识别、检索引用、首播延迟
流程:字段确认、合法动作、人工接续、回写一致
业务:任务完成、重复联系、单位已解决任务成本
护栏:越权、错误承诺、隐私泄露、拒绝人工
组件失败能解释业务失败,却不能替代业务判定。反过来,任务偶然完成也不能证明组件稳定。比如 ASR 把门牌号听错,但用户在下一轮主动纠正,任务可能最终完成;这条样本对业务结果是成功,对 ASR 字段指标仍是失败。两条记录都应该保留。
封存集的排除规则要在解封前写好。电话中途被用户主动挂断是否排除,要看任务定义;工具超时、人工队列满和异常断线通常正是系统能力的一部分,不能看到失败后再删。确实不属于试点范围的任务可以单列,但要同时报告排除数量与原因。
企业可以直接执行的 PoC 顺序
如果要把这套方法交给采购、业务和技术共同执行,我建议按下面的顺序:
最后一个判断很简单:如果一套 PoC 允许供应商反复看到验收题,它适合联合调试,不适合证明泛化;如果挑战集只考“听懂了吗”,它适合测组件,不足以验收业务。


