text
场景:Python 入门试听课结束后的第二天
教学系统输入:
attendance_status = 未签到
room_event = 待核验
booking_status = 已预约
客户可能的真实情况:
- 找不到上课入口
- 临时加班或时间冲突
- 已提前取消,但名单未同步
- 课程内容与报名页理解不一致
不能直接推断:
- 未签到 = 不想学习
- 没有改约 = 低意向
- 愿意听解释 = 愿意付费
这里有两次跳跃:没有签到,不一定没有到课;即使确实没有到课,也不能证明不想学。直接进入挽留,机构很容易把服务问题变成第二次打扰。分层应回答"下一步由谁处理什么",而不是给学员的学习能力或付费意愿打分。
yaml
回访任务:
教学系统:
- 核实到课事实和课次状态
- 返回可用课位及有效期
语音沟通层:
- 说明来意并确认是否方便
- 记录客户明确说明的原因和选择
课程顾问:
- 处理课程适配、争议和人工解释
- 接收任务并留下回执
退出规则:
- 客户拒绝解释或要求停止联系,立即停止追问
- 未确认课位,不提交改约
本文把闪电智能 Voice Agent 放在这条任务链里,讨论它在未到课回访中应承担哪一段、哪些事实必须由教学系统确认、哪些情况必须交给课程顾问。下文的对话、课位、时间戳和回执都是合成样本,未连接真实教学系统或 CRM,不代表招生结果或学习效果。
text
状态流转:
未核验到课事实 -> 补齐事实 -> 客户表达原因
客户同意改约 -> 校验具体课位 -> 教学系统返回回执
客户拒绝/不再联系 -> 立即停止后续回访
课位冲突/课程争议/回执未知 -> 转课程顾问
目录
- 先确认这条未到课任务有没有发错
- 分层回访按什么分,才不会变成给人贴标签
- 一次时间冲突,怎样走到有效改约
- 课程不匹配与不再需要,应怎样结束或交接
- 把判断写成一个可运行的动作门
- 中文电话中最容易丢掉的限定词
- 顾问收到什么,才不用重新问一遍
- 到课率上升,为什么仍可能说明不了问题
- 上线时先审查失败样本,再扩大联系范围
- 参考资料
先确认这条未到课任务有没有发错
我不建议把 CRM 中的"未签到"直接作为外呼依据。CRM 往往接收某个时刻的结果,而教学系统可能还在补齐直播间记录、教务签到或转班信息。名单生成得快,未必生成得对。
先对齐三个对象:预约记录、具体课次和学员账户。同一门课程有周二与周四两个班,"学员没有出现在周二班"不能覆盖"已经调到周四班"的预约事实。线上课程还可能存在昵称、报名手机与学习账号映射不同的问题。本文不设计身份核验问卷;应由机构授权的账户匹配规则在外呼前完成对应,匹配冲突交教务确认。
教学系统输出至少需要 appointment_id、session_id、到课状态、状态来源、核验时间、版本和是否终态。no_show 应表示:目标课次已经结束,机构规定的补录窗口已经结束,仍无有效到课记录。多长的补录窗口合理,取决于机构实际同步和教务流程,不能把代码里的示例时间直接设成行业标准。
| 教学侧事实 | 本次回访任务如何处理 | 为什么 |
|---|---|---|
| 课次未结束或记录仍在补录 | 暂不发送未到课回访 | 此时只是尚未观察到到课 |
| 已取消或已转班 | 关闭原未到课任务,核对对应服务任务 | 不应要求客户解释一笔已撤销预约 |
| 已到课、CRM 尚未同步 | 修正派单事实 | 提醒话术不能弥补错误名单 |
| 到课来源冲突或身份未对应 | 交教务核验 | 模型不能选一个看起来更可信的结果 |
| 教学系统最终确认未到课 | 检查联系许可后进入原因澄清 | 事实成立,才讨论后续服务 |
一个容易忽视的反例是:学员确实进了直播间,但只停留了两分钟。是否算到课,应使用机构事先公布并一致执行的教学口径;这与本次回访是否需要帮助是两件事。不能为了让回访名单更"精准",临时把停留短等同于学习动力不足。若机构希望询问提前离开的原因,应另建课后体验任务,不混入未到课分母。
在本文的接入设计中,名单适配器必须提供这些事实和核验结果。若项目采用闪电智能 Voice Agent,语音层只能复述被批准的状态。例如"想确认一下昨晚试听安排是否顺利",比在状态未核定时直接说"您昨天缺课了"更合适。但换一句委婉话不能取代前置核验;记录存在冲突时,应先处理数据问题。
分层回访按什么分,才不会变成给人贴标签
分层应回答"下一步需要谁做什么"。可以先使用四种原因,外加独立的退出与人工请求。四类不要求覆盖人的全部动机,也不要求通话结束时每人都填满。
| 客户明确说明的原因 | 可确认内容 | 当前动作 | 不应推出的结论 |
|---|---|---|---|
| 时间冲突 | 这次冲突、是否仍想试听、愿意查看的时段 | 客户愿意时查询同课程可用课位 | 愿意改时间不等于愿意付费 |
| 课程不匹配 | 对课程内容的理解、需要顾问解释的问题 | 经客户同意交课程顾问 | 不匹配不等于学习能力不足 |
| 不再需要 | 客户当前不需要本次试听服务 | 结束本次跟进 | 不需要试听不等于永久无学习需求 |
| 不明 | 没有获得可确认原因及未决问题 | 最多一次中性澄清,仍不明则记录或人工处理 | 沉默、礼貌话和音色不能补原因 |
"明确说明"需要原话和来源轮次。系统抽出 time_conflict 后,至少要保留"昨晚临时加班,没赶上"的证据。如果只有"最近忙",应确认客户是在解释昨晚未到课,还是表示现在不方便接电话。后者首先影响本次通话安排,不能被当作缺席原因。
一个人也可能同时遇到两种问题:"加班没赶上,而且你们这个班是不是讲得太基础?"这时不应强迫分类器二选一。记录两项表达,先处理课程适配疑问,再决定是否查询新课位。示例代码用单一主要原因演示动作门;真实系统应允许多项证据,若时间冲突与课程不匹配并存,转顾问优先。否则改约得越顺利,第二次失约的原因反而越没有得到处理。
退出状态独立于原因。学员说"不用解释了,不要再打电话",系统应立即结束自动跟进并登记联系限制,不继续追问到底是哪一类原因。拒绝提供原因也是有效选择。退出登记失败则交联系运营处理,不能让任务仍留在可拨号队列中。
"不再需要"和"不要联系"也不同:前者关闭这笔试听任务,后者限制后续联系。机构不能利用这个区别继续挽留;它的用途是把客户的原意正确执行。客户只拒绝本次试听时,不应自行写成永久退订;客户明确要求停止电话时,也不能仅仅关闭当前任务而让另一条活动外呼继续打来。
一次时间冲突,怎样走到有效改约
下面是一段合成对话,用于说明顺序,不是客户录音。
AI:您好,我是课程服务的 AI 助手。想就您预约的 Python 入门试听确认一下安排,现在方便简短沟通吗?
学员:可以。昨天临时加班没赶上,还是想听一下。
AI:明白,是临时加班影响了昨天的安排。您希望我查一下同一门入门课的其他时段吗?
学员:查一下周六下午。
AI:教学系统当前显示,周六下午三点的线上试听有可用课位,课程仍是 Python 入门,时长四十五分钟。是否把这次预约改到这个场次?
学员:确认,就这个周六下午三点的线上课。
AI:我现在提交改约,结果需要以教学系统确认为准。
教学系统返回与原预约、新课位匹配的确认回执后,AI:已确认改约到这个场次,预约编号为......您可以按约定方式查看上课信息。
最后一句的前置条件最关键。客户确认了选择,系统才获得提交请求的依据;教学系统接受并返回新预约结果,才可以说改约确认。电话里答应"周六可以",不能创造一个周六课位。
课位描述要把容易改变决定的要素讲清:课程与课次、日期、具体时间及时区、线上或线下、地点或入口、时长,以及是否存在已核实的限制。无需把全部课程目录念一遍。若从线上改成线下、从入门课换成进阶课,即使日期相同,也属于新方案,必须重新确认。
系统应给每次可选方案生成 offer_id。它对应一组具体课位条件及有效期。客户确认 O-02 后,课位售罄,系统重新查到 O-03,旧确认不能移植到新方案。把确认绑定到版本,比只存 user_confirmed=true 多了一些字段,却能避免"客户确认的是三点,系统订成五点"的错误。
还有一个现实问题:查询到空位不代表提交时仍有空位。教学系统需要执行最终容量校验;若支持临时保留,应提供保留凭证和到期规则;若不支持,AI 就必须承认提交仍可能失败。本文只演示查询快照与回执解释,不模拟并发抢课位,也不把本地返回 ready_to_book 称为锁位成功。
提交超时后,不要重新生成一句"已安排好"来结束通话。先查预约结果;无法查明就保留"待核验",交教务跟进。客户可以收到已经核实的处理状态,不能收到编排层猜出的成功状态。回复中应明确说出预约是否确认,以及未确认时由谁继续核实。
课程不匹配与不再需要,应怎样结束或交接
课程不匹配经常被粗糙地写成"异议",于是系统继续背课程卖点。可客户说的可能是事实差异:"我想学办公自动化,报名页让我以为不需要编程,现在看到课纲都是语法。"此时要核对的是课程说明与客户目标是否一致。AI 不应承诺"您肯定学得会",也不应为了留住试听把课程内容说得比正式课纲更宽。
适合交给顾问的记录是:"客户期望使用表格处理工作数据;对 Python 语法比重有疑问;请求先看适用说明;尚未决定是否继续试听。"顾问可核对课程目标、前置要求和正式材料,再由客户决定。这比"基础薄弱,低意向,需要重点说服"更有用,因为后一条把未经确认的能力评价和销售策略混在了一起。
如果客户说"我已经通过单位培训解决了,不需要试听",结束当前任务即可。不要自动切换到续费、进阶班或促销。服务结束应留下原因、客户原话、是否限制联系与关闭动作。关闭回访任务并不证明问题都解决了,但它能防止同一笔试听不断生成新任务。
客户要求人工时,也不必先完成原因分类。先确认人工接续方式是否得到同意:现在转接,还是在客户指定的时间回电。不能保证即时接通时,应如实说明当前状态,并由真实队列接收任务。顾问未接收、回电时间未约定、任务超时无人处理,都是待处理状态。
如果项目采用闪电智能 Voice Agent,它可以被放在语音沟通和任务回流这一段:保留原话和本次动作记录;课程顾问负责课程解释与争议处理,教务负责事实核验与预约结果。这里的字段、接收回执和异常归属是本文给出的验收要求,不依赖某个厂商产品矩阵来替代教学系统的接入证明。
把判断写成一个可运行的动作门
示例使用 Python 3.9 及以上版本,只依赖标准库。项目中的 examples/no_show_policy.py 定义四类输入:Attendance 是教学事实,Choice 是经会话确认适配器校验的客户选择,Offer 是教学系统提供的方案,Context 将这些事实与联系范围组合。它不识别音频,也不从一段自由文本自动判断原因。
将下面的完整代码保存为 no_show_policy.py,即可单独运行;它与配套项目文件一致。时间用合成整数秒表示,max_age=300 是为了复现过期分支的演示参数。
python
"""Synthetic adult vocational-training workflow; no calls or booking API."""
from __future__ import annotations
from dataclasses import dataclass, replace
import json
@dataclass(frozen=True)
class Attendance:
appointment_id: str
status: str
source: str
version: str
final: bool
checked_at: int
@dataclass(frozen=True)
class Choice:
reason: str = "unknown"
quote: str = ""
turn_id: str = ""
reason_confirmed: bool = False
stop_contact: bool = False
wants_human: bool = False
wants_rebook: bool = False
confirmed_offer_id: str = ""
@dataclass(frozen=True)
class Offer:
offer_id: str
slot_id: str
course_id: str
source: str
available: bool
expires_at: int
@dataclass(frozen=True)
class Context:
adult_scope_confirmed: bool
contact_allowed: bool
attendance: Attendance
choice: Choice
course_id: str
offer: Offer | None = None
clarification_count: int = 0
def decide(ctx: Context, now: int, max_age: int = 300) -> dict:
"""Route verified facts; freshness values are synthetic policy parameters."""
c, a, offer = ctx.choice, ctx.attendance, ctx.offer
if c.stop_contact:
return {"action": "stop", "owner": "contact_ops", "reason": "explicit_exit"}
if not ctx.adult_scope_confirmed or not ctx.contact_allowed:
return {"action": "hold", "owner": "contact_ops", "reason": "scope_or_permission"}
if c.wants_human:
return {"action": "handoff", "owner": "course_advisor", "reason": "requested"}
verified = (a.source == "teaching_system" and a.final and bool(a.version)
and 0 <= now - a.checked_at <= max_age)
if not verified:
return {"action": "verify_attendance", "owner": "teaching_ops", "reason": "missing_authority"}
if a.status in {"attended", "cancelled", "rescheduled"}:
return {"action": "close_no_show_task", "owner": "teaching_ops", "reason": a.status}
if a.status != "no_show":
return {"action": "verify_attendance", "owner": "teaching_ops", "reason": "unknown_status"}
reasons = {"time_conflict", "course_mismatch", "no_longer_needed", "unknown"}
if c.reason not in reasons:
return {"action": "handoff", "owner": "course_advisor", "reason": "invalid_reason"}
evidenced = c.reason_confirmed and bool(c.quote.strip()) and bool(c.turn_id)
if c.reason == "unknown" or not evidenced:
action = "clarify_once" if ctx.clarification_count == 0 else "handoff"
return {"action": action, "owner": "course_advisor", "reason": "unknown"}
if c.reason == "no_longer_needed":
return {"action": "close_followup", "owner": "course_advisor", "reason": c.reason}
if c.reason == "course_mismatch":
return {"action": "handoff", "owner": "course_advisor", "reason": c.reason}
if not c.wants_rebook:
return {"action": "record_only", "owner": "course_advisor", "reason": "no_rebook_request"}
live_offer = (offer is not None and offer.source == "teaching_system"
and offer.available and now < offer.expires_at
and offer.course_id == ctx.course_id
and bool(offer.offer_id) and bool(offer.slot_id))
if not live_offer:
return {"action": "query_slots", "owner": "teaching_ops", "reason": "no_live_offer"}
if c.confirmed_offer_id != offer.offer_id:
return {"action": "confirm_offer", "owner": "voice_flow", "reason": "not_confirmed"}
return {"action": "ready_to_book", "owner": "teaching_system",
"appointment_id": a.appointment_id, "slot_id": offer.slot_id,
"offer_id": offer.offer_id, "attendance_version": a.version}
def interpret_receipt(decision: dict, receipt: dict | None) -> str:
"""Receipt must come from the trusted teaching adapter, never model output."""
if decision.get("action") != "ready_to_book":
return "not_requested"
if not receipt or receipt.get("source") != "teaching_system":
return "pending_check"
same = all(receipt.get(k) == decision[k]
for k in ("appointment_id", "slot_id", "offer_id"))
if not same:
return "handoff_mismatched_receipt"
if receipt.get("status") == "confirmed" and receipt.get("booking_id"):
return "booking_confirmed"
if receipt.get("status") == "rejected":
return "booking_failed"
return "pending_check"
def sample() -> Context:
return Context(
True, True, Attendance("AP-SYN-01", "no_show", "teaching_system", "v3", True, 1000),
Choice("time_conflict", "昨晚加班没赶上,还是想试听。", "t4", True,
wants_rebook=True, confirmed_offer_id="O-SYN-02"),
"PY-INTRO", Offer("O-SYN-02", "S-SYN-06", "PY-INTRO", "teaching_system", True, 1300))
def run_demo() -> None:
ctx = sample()
decision = decide(ctx, 1100)
receipt = dict(source="teaching_system", status="confirmed", booking_id="B-SYN-07",
appointment_id="AP-SYN-01", slot_id="S-SYN-06", offer_id="O-SYN-02")
cases = {"ready": decision, "receipt": interpret_receipt(decision, receipt),
"expired": decide(ctx, 1300),
"exit": decide(replace(ctx, choice=replace(ctx.choice, stop_contact=True)), 1100)}
print(json.dumps({"synthetic": True, "cases": cases}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
run_demo()
这段代码故意先检查退出,再检查回访资格,随后处理人工请求与事实核验。退出不能因为签到接口故障而延后生效。reason_confirmed 也不能由模型自己填为真;生产适配器应核对原话、来源轮次、确认问题和最终客户响应,并把多原因冲突送顾问。
文件中的 interpret_receipt 还会检查原预约、新课位和方案 ID 是否匹配。它信任调用方传入的教学适配器结果,不实现鉴权或签名验证;真实接入必须在适配器完成来源验证。把字符串 source 填成 teaching_system,当然不能凭空获得权威性。
只有本文时,将上面的代码保存后执行:
bash
python3 --version
python3 no_show_policy.py
python3 -m py_compile no_show_policy.py
已取得配套项目与测试文件时,在项目根目录执行:
bash
python3 --version
python3 -m examples.no_show_policy
python3 -m unittest discover -s tests -v
python3 -m py_compile examples/no_show_policy.py tests/test_no_show_policy.py
演示输出中,正常样本为 ready_to_book,合成匹配回执解释为 booking_confirmed;方案有效期恰好结束时输出 query_slots;显式退出输出 stop。这些输出验证分支,不表示真的创建了预约。测试覆盖错误来源、未终结/过期事实、已到课、未知原因、课程不匹配、没有改约要求、课位过期和旧确认误用等边界。
中文电话中最容易丢掉的限定词
"我不是不想来,是不方便来"和"不想来了"可能只差几个词,却对应完全不同的动作。窄带电话、用户打断和重叠说话会放大这个问题。单看整段 ASR 字错率,无法判断系统有没有把关键否定词漏掉。
验收时可以对同一任务建立成对样本:"周六可以"与"周六不可以","不需要改课"与"不需要,改课吧","只想问课程"与"想约这个课程"。这些句子都是合成测试素材;真实上线还应补充经授权的口音、背景噪声和打断样本。评价单位是错误动作,例如有没有提交客户明确拒绝的改约,而不只是转写相似度。
具体日期也要复述。"下周六"取决于当前日期和日常语言习惯;"三点"可能需要确认下午三点;异地线上学员需要明确时区。课程名中的"基础""入门""零基础"也不能随意替换,它们可能对应不同课程。系统应读教学系统的正式名称,再用核实的说明解释含义。
客户在 AI 复述时说"等等,不是这个",应使正在准备的确认失效。不能等上一句播报结束后才处理打断,也不能让已取消的生成结果继续提交改约。本文动作门不实现媒体停播和并发事件取消;这些是需要真实电话验收的独立条件。通过本地路由测试,无法替代语音时序验证。
顾问收到什么,才不用重新问一遍
下面的任务卡可以直接复制到实施评审中。填不出的字段应明确写"未知"及原因,不以一句"待顾问跟进"覆盖缺口。
text
任务:成人职业培训试听未到课回访
预约 / 课次 / 课程:
教学系统核验:状态、来源、版本、核验时间、是否终态
客户当前联系选择:允许方式、时间范围、退出原话与轮次
主要原因:时间冲突 / 课程不匹配 / 不再需要 / 不明
证据:客户原话、来源轮次、确认问题与最终回答
其他并存问题:
改约:客户是否要求、可用课位、offer_id、有效期、确认依据
执行:尚未提交 / 待核验 / 已确认 / 失败;教学回执编号
未决问题:谁核实、需要什么材料、何时处理
人工接续:队列、接收人、接收回执、约定联系范围
关闭依据:客户选择或系统事实;不得填"推测不想学习"
语音摘要便于顾问阅读,结构字段便于任务分配,二者都要能回到原话。涉及个人工作安排时,只记录解释本次时间冲突所需的信息;"临时加班"已足够,不需要顺便采集单位、收入或岗位评价。企业还应按既有授权和制度设置访问权限、留存与删除路径,不能因做回访就默认允许无限期保留录音。
交接是否完成,可以用"顾问接收了哪个任务版本"核对。客户在等待期间改口要求停止联系,原任务即使已经接收,也应更新退出状态并通知承担接续责任的人。否则语音端听起来尊重选择,顾问端仍可能照旧回电。
到课率上升,为什么仍可能说明不了问题
机构通常关心改约和后续到课,但分母很容易被流程改变。只对愿意改约的人统计二次到课率,会排除不再需要、课位不合适和未联系到的人;新流程又可能把这些人更早筛掉。比例上升时,需要先检查样本结构,再讨论流程是否更有效。
建议按原始预约批次保留完整队列,分别记录最终未到课名单、可联系人数、有效接通人数、客户请求改约数、系统确认改约数以及观察窗口结束后的实际到课数。各层之间有自然流失,不应把它们合成一个"转化率"。同一人多次改约还需按预约任务去重,不能把每次请求都算成一次成功。
| 项目验收指标 | 分子 / 分母 | 用途与限制 |
|---|---|---|
| 名单事实错误率 | 不应进入未到课队列的任务 / 抽检任务 | 判断教务与数据链路,先于话术优化 |
| 原因有据率 | 分类有原话与有效确认的任务 / 已分类任务 | 检查分类证据,不代表原因一定完整 |
| 改约执行完成率 | 到期队列中已确认的新预约任务 / 处理等待期限已到的改约任务 | 按预约任务去重,统计固定观察点的状态;不表示实际到课 |
| 后续到课率 | 该成熟队列中有效到课的改约任务 / 已到观察期限的确认改约任务 | 需同时披露未成熟任务和后续取消 |
| 明确退出后的触达 | 自客户明确退出表达时间起,退出要求所限制范围内的触达事件数及涉及客户数 | 包含表达至系统生效的同步空窗;按事件和客户分别去重 |
| 退出请求未生效 | 待生效请求数、超过约定处理期限仍未生效请求数 | 防止只有已生效记录进入统计;不把未生效当作可继续联系 |
| 生效后的错误触达 | 限制生效后仍发生的范围内触达次数 | 作为执行系统诊断项,不能代替从客户表达起算的风险指标 |
| 人工接续逾期率 | 截至约定期限仍未处理的任务 / 需人工且处理期限已到的任务 | 尚未到期任务单列;区分接收与实际处理 |
若名单事实错误率高,先修教学数据;若客户经常反馈课程描述不符,先核对招生材料;若客户愿意改约而确认率低,查课位、确认与提交链路;若改约成功但到课依旧少,检查课次时间、入口通知和课程适配。没有这些拆解,团队可能不断换话术,却没有碰到主要原因。
改约任务的等待期限应在试点前统一定义,例如从首次提交起经过机构批准的处理窗口;这是项目参数,不是本文给出的固定时长。重试与重复回执不增加分母,同一任务以约定观察点的最终状态计一次。改约执行完成率的分子分母属于同一到期队列,近期提前成功但尚未到期的任务也单列。等待期限未到的 pending 单列为未成熟任务;期限已到仍 pending 的保留在成熟分母中,计未确认并交教务核验。后续到课率同样只在已到观察期限的成熟队列中判断到课。人工接续也按到期时是否完成计算,迟到处理可以另报补救结果,不能回头抹去已发生的逾期。
退出风险的时钟从客户明确表达开始。联系系统还未同步时发生的拨号,也必须出现在风险记录中,不能因为限制尚未"生效"而被排除。按退出范围分别统计拨号/发送事件与受影响客户;重试日志按实际触达事件去重,未生效请求单独跟踪负责人和期限。客户后来重新授权的,应保留新证据与适用范围,不能直接抹掉此前事件。
本文没有提供线上指标值。评估闪电智能 Voice Agent 的这一方案,应把通话记录、教学回执、顾问接收和最终到课观察放进同一任务证据包,同时保留对照流程与样本变化。不能把合成测试通过写成招生提升,也不能把任何一个人的缺席归因到学习能力或付费意愿。
上线时先审查失败样本,再扩大联系范围
先用已经脱敏并获得使用授权的历史任务检查名单来源与状态规则,再让顾问在不自动发起联系的条件下审阅原因分类和下一步动作。确认事实来源、客户选择和人工职责后,才进入小范围真实电话试点。每个阶段都应单独记录边界,避免"文本演练没问题"被记成电话链路通过。
试点停止条件应直接对应错误:未核实到课就责问客户,退出后继续挽留,未确认具体课位就提交改约,预约结果未知却宣布成功,或者把客户表达变成学习能力判断。出现其中任何一种,应先定位该分支和受影响任务,修正后重跑相关失败样本,而非用整体满意度抵消。
可以把首批评审压缩到两张证据卡:一张是"客户要改约,但课位已失效",另一张是"客户不解释原因,只要求停止联系"。前者检验系统是否尊重真实资源,后者检验它是否尊重客户选择。两张卡都处理清楚,再讨论扩大回访范围。
参考资料
- Nielsen Norman Group:7 Ways to Analyze a Customer-Journey Map,用于拆解试听预约、到课、改约和退出等关键触点。
- AAAI:Customer Service Combining Human Operators and Virtual Agents,用于人工接续、任务交接和多角色协作的参考。
- Google People + AI Guidebook,用于用户控制、出错后的支持和人机协作设计。
- NIST AI Risk Management Framework 1.0,用于风险识别、测试证据和责任边界。
- Python dataclasses 与 unittest,用于本文示例的数据结构和测试框架。
本文的未到课分层、课位确认和动作门均为作者设计。示例未执行真实外呼、课程预约或 CRM 写入。