用户说:“别再解释规则了,我只想知道这笔重复扣款什么时候退。”

这时再追加一段长篇安抚,往往会让投诉升级。可系统如果跳过确认,直接说“会尽快处理”,又可能把未核实的订单、时效和权限说成了承诺。真正要解决的不是“先共情还是先解决”,而是:这通电话现在是否具备安全推进的条件;不具备时,该把哪些事实交给人工。

我不建议把这个判断交给“用户情绪类型”或音高、音量之类的特征。对投诉场景更可靠的输入,是用户明确提出的要求、已确认的问题对象、工具执行结果、重复解释次数和风险状态。闪电智能 Voice Agent 可以据此切换表达方式,但不能据此给用户贴上“难沟通”“情绪化”或人格标签。

本文用一组不含真实客户数据的合成事件,演示投诉路由、转人工上下文和测试口径。它只验证规则边界,不证明真实客服中的满意度、投诉率或处理时长。

目录

先把投诉路由放回 Voice Agent 链路

投诉路由不该只发生在大模型生成话术的那一刻。它至少跨过五个位置:ASR 或文本输入确认用户说了什么,业务查询确认对象和状态,策略层决定澄清、推进还是交接,工具层执行可授权的动作,最后由 TTS 或文本把下一步说清楚。

当前表达 + 已确认字段 + 工具结果 + 风险标记
                    ↓
              投诉策略路由
      ↓             ↓             ↓
    澄清          推进          转人工
      ↓             ↓             ↓
  补齐事实       返回可核验结果   生成交接载荷

这里最容易混淆的地方是:承接语属于表达层,能不能继续自动处理属于策略层。系统可以先说“我已确认你在问重复扣款”,但不能因为这句话说得足够温和,就忽略支付争议、身份核验或工具失败。

安抚不是独立状态,推进也不是默认答案

投诉电话里的“承接”应该很短,只负责告诉用户系统抓住了当前问题。之后的动作取决于证据,而不是句子里的情绪强度。

路由动作进入条件系统应做什么不该做什么
澄清订单、时间、诉求或问题对象不一致复述一项待确认事实,再提一个必要问题继续播报固定流程
简短承接后收集对象基本明确,但还缺执行所需字段说明缺什么、为什么需要用泛泛道歉代替收集
推进问题已确认、工具可执行、没有风险标记返回工具结果或明确下一步许诺系统没有权限保证的结果
转人工用户明确要求人工、高风险、重复解释未解决、流程被阻塞说明转交原因并附带已确认上下文让用户从头再说一遍

例如,“你根本没听懂”不是给用户贴情绪标签的理由,而是一个澄清信号;“我要投诉到平台”也不是单纯提高安抚强度的提示,而是需要升级处理的显式意图。两者的系统动作不同。

哪些事实可以驱动策略切换

下面这组字段只描述当前会话和业务状态。它们不推断人格,也不保存“好说话”之类的长期标签。

字段例子用途
issue_confirmed订单号、业务类型和争议点已一致决定能否进入工具查询或处理
explicit_human_request“转人工”“找主管”立即交接,不与默认策略竞争
high_risk身份、资金、重大权益或合规风险立即交接,由业务规则提供来源
repeated_question_count同一已确认问题已解释两次仍未解决避免机器人第三次重复同一套说法
blocked_action_count工具无权限、查询失败或流程无法继续区分“再问一个字段”与“系统已卡住”
user_denied_understanding用户明确否认系统理解回到澄清,不沿用旧答案
reply_preference“直接说结果”只影响回答长度和顺序,不能绕过风险规则

repeated_question_count >= 2blocked_action_count >= 1 是下面 Demo 的测试门槛,不是生产阈值。真实项目应由业务、客服和合规团队根据失败样本、人工负载和错误成本共同确认,并保留规则版本。

一个可运行的投诉路由器

最小版本常常只有一条 if high_risk: handoff。它能挡住明显风险,却解释不了“系统一直没解决”“用户已要求主管”或“工具刚刚失败”这些更常见的投诉升级路径。下面的示例把这些事实留在事件中,输出动作、理由和表达模式。

from dataclasses import dataclass
from typing import Literal

Action = Literal["handoff", "clarify", "advance", "acknowledge_collect"]


@dataclass(frozen=True)
class ComplaintEvent:
    conversation_id: str
    issue_id: str | None
    explicit_human_request: bool = False
    escalation_requested: bool = False
    high_risk: bool = False
    issue_confirmed: bool = False
    user_denied_understanding: bool = False
    repeated_question_count: int = 0
    blocked_action_count: int = 0
    can_execute: bool = False
    wants_result: bool = True
    reply_preference: Literal["answer_first", "normal"] = "normal"
    last_tool_result: str | None = None
    risk_flags: tuple[str, ...] = ()


@dataclass(frozen=True)
class Decision:
    action: Action
    reason: str
    reply_mode: Literal["answer_first", "brief"]


def choose_decision(event: ComplaintEvent) -> Decision:
    reply_mode = "answer_first" if event.reply_preference == "answer_first" else "brief"

    if event.explicit_human_request or event.escalation_requested:
        return Decision("handoff", "explicit_escalation", reply_mode)
    if event.high_risk:
        return Decision("handoff", "high_risk", reply_mode)
    if event.repeated_question_count >= 2:
        return Decision("handoff", "repeated_unresolved_question", reply_mode)
    if event.blocked_action_count >= 1:
        return Decision("handoff", "blocked_business_action", reply_mode)
    if not event.issue_confirmed or event.user_denied_understanding:
        return Decision("clarify", "need_fact_confirmation", reply_mode)
    if event.can_execute and event.wants_result:
        return Decision("advance", "safe_to_execute", reply_mode)
    return Decision("acknowledge_collect", "missing_execution_input", reply_mode)

这段代码刻意把 reply_preference 限制在 reply_mode:用户要求“别绕弯子”时,系统可以先报工具结果,但不能因此跳过资金争议或人工请求。把偏好写进 if high_risk 的前面,是投诉策略里很危险的顺序错误。

转人工时到底该交什么

“已为您转人工”不是一次完成的交接。若人工只拿到一句“客户不满”,用户仍要重复描述问题,系统前面的澄清也就白做了。

交接载荷至少应保留以下最小信息:会话 ID、已确认的问题对象、路由原因、风险标记、最近一次工具结果、用户明确的表达偏好,以及规则版本。不要默认塞入原始音频、人格标签或与当前投诉无关的历史判断。

POLICY_VERSION = "complaint-policy-demo-v1"


def build_handoff_payload(event: ComplaintEvent, decision: Decision) -> dict[str, object]:
    if decision.action != "handoff":
        raise ValueError("handoff payload requires a handoff decision")

    return {
        "conversation_id": event.conversation_id,
        "issue_id": event.issue_id,
        "issue_confirmed": event.issue_confirmed,
        "handoff_reason": decision.reason,
        "risk_flags": list(event.risk_flags),
        "last_tool_result": event.last_tool_result,
        "reply_preference": event.reply_preference,
        "policy_version": POLICY_VERSION,
    }

生产接入还需要把 issue_id 映射为允许人工查看的订单或工单字段,并按数据权限做脱敏。本文不提供支付、身份或 CRM 的真实接口,也不把这份字典当成完整的生产 Schema。

如何用合成事件验证规则

项目中的 examples/complaint_policy.pytests/test_complaint_policy.py 不依赖第三方库。测试覆盖七条最小边界:明确要求人工、高风险、重复追问、工具阻塞、事实未确认、安全推进,以及交接载荷包含原因与规则版本。

cd CSDN-VASI-投诉动态策略
python3 -m unittest discover -v

测试不模拟真实电话音频,也不测满意度;它验证的是一条更基础的约束:同一组事件进入同一版规则时,路由结果应稳定、可解释、可回归。

指标、边界与两个常见问题

没有真实业务数据时,先把指标定义清楚,不要给出“投诉下降”之类的结论。可以从以下四个口径开始记录:

指标计算口径用来发现什么
重复解释率同一已确认问题进入第三次澄清的会话数 / 已确认投诉会话数路由是否把用户困在重复话术中
工具阻塞转交率工具阻塞后成功生成交接载荷的会话数 / 工具阻塞会话数流程卡住时是否仍能完整交接
交接字段完整率必填字段齐全的交接载荷数 / 全部交接载荷数人工是否需要重新追问
错误推进率实际应交接却进入 advance 的会话数 / 审核样本数风险规则是否过松

这些只是评测设计,不是本文的真实测量结果。若要上线,应先用脱敏历史样本和人工复核构建评测集,再在灰度环境观察错误推进和人工接管负担。

用户已经很生气,系统是不是应该立刻转人工?

不能只凭语速、音量或一句情绪化表达做判断。用户明确要求人工、高风险争议、重复解释未解决、业务动作被阻塞,才是本示例中的可审计交接条件。业务方可以增加其他条件,但要能解释它来自什么事实。

用户说“不要解释,直接告诉我结果”,系统还能要求补充信息吗?

可以,但要把缺什么和为什么说清楚。answer_first 只改变表达顺序:有可验证结果就先报结果;没有足够字段或没有执行权限时,仍需澄清或交接。

投诉策略的难点不在于找一句更会安抚人的话。它在于系统能否承认自己现在不能继续,并把已经确认的事实完整交给下一位处理者。

参考资料

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐