多方通话:第二个声音改变的是谁被授权,而不只是谁在说话。
「等一下,我让我太太跟你说」——就是这句话把一个正常工作的语音智能体变成了一个未经授权的智能体,而几乎没有任何部署会注意到。你的轮替栈把这个新声音当成要转写的音频;你的授权把这通电话当成一方、且只在开头核验过一次。第二个人就这样悄悄继承了那次核验——所以真正的控制是:把每一个有后果的动作绑定到「你在这一句话上重新核验过的那一方」,而永远不绑定到这通电话。
步骤 1第二个声音有三种形态,而你一种都没为之设计。
多方不是一个场景,而是三个,失败模式各不相同:
- 换人。已核验的来电者把手机或话筒递给了别人——配偶、同事、照护者、父母。身份变了,而智能体的状态、槽位与授权没变。
- 加入。有第三方被接进会议,或者来电者一直开着免提。现在有两个以上的声音同时在带内,而智能体必须判断自己在回答哪一个。
- 旁人。没有人加入对话,但电视、桌子对面的同事或一个孩子在音频里。正是这些会产生幽灵轮次,偶尔还会产生幽灵指令。
第一种是最贵的,因为它看起来什么事都没发生。音质没变,转写依然干净,打断照常工作,而你盯着的每一项指标都是绿的——与此同时,正在给你的智能体下指令的那个人,从未被认证过。第三种是会让你当众难堪的那一种,而它是语音通道版本的「不可信输入」问题:任何听得见的东西都在提示词里。
如果你的智能体能动钱、改地址、取消服务或读出余额,而你的认证只发生在呼叫建立时那一次,那么一次「换人」就是对你身份控制的一次完整绕过——而且不需要任何技术手段。那正是来电者核验按设计留下的缺口:它核验的是一通电话,而一通电话不是一个人。
步骤 2已发表的数字:实时智能体在多方轮替上接近随机。
这不是一个「模型以后会搞定、现在可以搁置」的长尾问题。2026 年的三份基准直接量了它,而它们对形状的判断一致。
- MP-Bench(arXiv 2609.13076,Findings of EMNLP 2026)评估的是语音智能体作为多方对话参与者的能力,而不是作为其转写者。在十二个智能体上,它报告实时语音智能体在多方理解上处于 22% 或以下——该论文较早的 HTML 版本对同一结论给出的是 33%——而在多方轮替上表现接近随机;实时与非实时系统在隐式与否定式轮替(被间接指称,或被刻意不指称)上都接近随机。
- MultiPartyTurnBench(arXiv 2610.20172)测的是话轮控制,报告学习型系统的轮次结束召回率达到 82.5%–92.6%、误报率 4.3%–11.8%——这算体面——而打断检测在两个方向上都失手:漏掉接话,或者无故触发。
- MSI-Bench(arXiv 2609.24812)覆盖多说话人交互,共 1,152 个测试用例,在中文与英文之间均分——如果你要上线多语言语音智能体里讨论的那种双语场景,这一点很要紧。
把三者合起来读,工程结论毫不含糊。模型最不擅长的并不是「听见两个人」——流式说话人分离(diarization)把这件事做得还行——而是知道哪些话是对它说的。「在否定式轮替上接近随机」意味着一个智能体无法可靠地判断「这两个人是在互相说话,而不是在对我说话」。所以不要设计一条依赖模型推断受话人的多方流程。要么把「对谁说」显式化,要么把这通电话约束为一方。
步骤 3说话人标签是临时的,所以永远不要把一项权限绑在它上面。
那个微妙的缺陷就在流式分离本身的契约里。提供实时分离的厂商会在流中给说话人打标,并随着更多音频到来修正先前的标签——AssemblyAI 记载了最多十个说话人的流内打标,以及正是这种修正行为;Speechmatics 则把实时分离做在引擎内,并提供了面向「识别说话人的智能体」的 LiveKit 集成。对转写准确率来说,修正是正确的工程选择;而对授权来说,它是一场灾难。
后果是:在你动手的那一刻,标签是一个之后可能被更正的猜测。你归到已核验账户持有人名下的那一句话,可能在两秒后——工具调用已经跑完之后——被重新归到房间里另一个人名下。而你的 trace 会显示最终的标签,并且不会记下「那个决定是在另一个标签下做出的」。
# Wrong: a diarization label is not an identity. if turn.speaker == "SPEAKER_0": # may be revised later await transfer_funds(turn.args) # Right: bind to a party you verified, and record the label you acted on. party = session.parties.resolve(turn) # label -> party, or UNKNOWN if party.verified_for(action) and not party.stale(max_age="120s"): await act(turn, on_behalf_of=party.id, label_at_decision=turn.speaker, labels_final=False) else: await say("I need to confirm who I'm speaking with before I do that.") await reverify(party)
由此得出两条规则。一个新出现或发生变化的说话人标签是一次事件,而不是转写稿的一个属性——它应该进入和一次转接相同的代码路径。而 UNKNOWN 必须是一个一等的「方」取值,并且对任何有后果的动作失败即拦截——因为在一个嘈杂的房间里,「我无法归属这句话」是常态,而不是例外。
步骤 4把授权做成这句话的属性,并带一个显式的「方」作用域。
解法和智能体领域普遍适用的那条一样:别再把授权当作会话状态。一个呼叫级别的授予就是环境权限——所有听得见的人都继承了它——而语音通道让这种继承变得不可见,因为没有任何界面能显示「权限此刻在谁手里」。
把这通电话建模成一组「方」,每一方有自己的核验状态,并给每个动作一个所需的作用域:
- 只读、非个人(营业时间、政策问题)—— 任何一方,无需核验。通话的大部分时间都住在这里。
- 读取、个人(余额、订单状态、预约详情)—— 仅限已核验的账户方;若自上次核验以来说话人标签变过,则需重新确认。
- 写入或不可逆(支付、改地址、取消)—— 已核验的那一方,在这一句话上有新鲜的证明,再加一次带外确认——按来电者核验的论证,证明不该走在攻击者控制的那条音频上。
- 第三方权限(配偶、照护者、委托授权)—— 需要一份事先存在的委托记录,而不是智能体在通话中现场授予的东西。如果没有记录,正确答案是一个人。
实操上的判据只有一行策略:一个有后果的动作需要一个「方」作用域,而一个「方」作用域需要一次比最近一次说话人标签变化更新的核验。仅这一条规则就关上了换人绕过,而且它完全不需要任何模型能力——考虑到 STEP 2 里的基准数字,这恰恰是重点。它是任务作用域纪律在语音里的实例,而委托那一档属于受委托访问与同意记录里描述的那些记录。
步骤 5一方加入会重新触发告知与同意,而你的代码大概不知道。
有两项你已经在通话开头实现过的义务,会在通话人员构成发生变化时重新附着,而两者都很容易漏掉——因为音频里没有任何东西会给出状态转换的信号。
告知。一个中途加入的人,没有听到你开场时「你正在与一个 AI 系统通话」的告知。告知那一页已经主张用一个在转接、有人加入与会话恢复时重新求值的 must_disclose 谓词,而不是写在系统提示词里的一行;而多方正是让这个谓词挣回工钱的那个场景。请对新加入的那一方再说一次,说短,并在播到最后一帧时记入日志。
录音同意。在要求全体同意的司法辖区,你拿到的同意覆盖的是你拿到它时在线的那些方。一个新声音进入一通正在录音的电话,就是一项新的同意要求,而录音、同意与脱敏里那套「让缓冲区挂在同意上」的论证,是按方而不是按通话来适用的。实现并不光鲜:一次「有方加入」事件要把录音落地点暂停,直到同意被重新取得——这意味着你的录制器必须能在流中途被闸住。
这两项义务需要同一个原语,而它恰恰是多数技术栈所缺的:一个由分离结果与电话信令推导出来的 party_joined / party_left 事件,并且作为中断抬进智能体循环,而不是追加到转写稿里。如果在做授权那摊活之前你只从这一页里建一样东西,就建这个事件——告知、同意与重新核验三条路径全都挂在它上面。
步骤 6去评测它,并知道什么时候干脆拒掉这通多方电话。
你的黄金集几乎肯定是单说话人的,这意味着你的评测看不到上面任何一件事。请像评估语音智能体搭其余部分那样,用录下来的音频搭出多方那一片:真实发生过换人、会议加入或大声旁人的那些通话,并按三件不是词错误率的事情打分——智能体有没有检测到「方」的变化、有没有扣住那个有后果的动作、有没有重新告知。再加一片对抗样本:第二个声音去要一件第一个人本无权要的东西;那是一次穿着电话外衣的提示注入测试。
也请接受:对某些流程来说,正确答案是拒绝。当动作不可逆而委托记录并不存在、当司法辖区要求全体同意而你无法在通话中途重新取得、或者当这是一通紧急或危机联络——此时正确的动作是盲转,不论谁在说话——都该拒绝。拒绝是一条设计出来的分支:「这个我能帮,但我需要和账户持有人通话——我回拨给您,还是现在替您转过去?」只花一轮,却移除了一整类绕过。
这周就做,按顺序:(1)从你的分离流里抬出一个 party_joined 事件并记入日志——你会发现它今天就在真实通话里触发;(2)把 UNKNOWN 做成一个对任何有后果的动作都失败即拦截的「方」取值;(3)加上那一行策略:一个有后果的动作需要一次比最近一次说话人标签变化更新的核验。第 1、2 步是一天的活,而且单凭它们就关上了换人绕过;STEP 2 里那些模型能力问题不是你能解的,所以请绕着它们设计。