这是一个全双工语音预约前台,跑在 MiniCPM-o 4.5 原版权重上。语音模型一直在听、在说,它背后的推理 LLM 和确定性状态机负责调工具(查、约、取消),并写一块简短的操作员屏幕。语音模型每秒从视觉输入读一次这块屏幕,就像接线员盯着 CRM。另有一个裁判模块,专门抓不实陈述。没有微调,也没有轮次切换:推理始终不在音频链路上,来电的人也不用等回合。
当前分支:vision-booking。 操作员屏幕渲染成一帧图像,从模型的视频输入送进去。姊妹分支 text-booking 在视觉槽位里直接发同样的屏幕文本,不做图像编码,表现一致。两个分支只差这一条通道。
演示视频(3 分钟)
录制用的是一张 RTX 5090(32 GB),戴耳机,英文对话,一段对话内没有剪辑。
定位
这里选 MiniCPM-o 4.5 当前端模型,原因有三:开源、能在一张消费级 GPU 上跑,并且暴露了这套设计需要的两个钩子:逐单元的说 / 听决策,以及可以加窗的 KV cache。它不是全双工模型的终点:DuplexOmni(Huang et al., 2026, arXiv:2606.09186)把实时思考和听、看、说一起放进了模型内部,看起来是这类智能体更强的基座。论文解读:DuplexOmni: Real-Time Listening, Seeing, Thinking, and Speaking for Full-Duplex Interaction (YouTube)。下面的设计除了这两个钩子不依赖具体的前端模型,所以这套编排应该能迁移。
它能做什么
客户和前台对话。前台问月份、日期和时间,查日历;客户说“你帮我选”时,它会推荐一个时段;只有听到明确的“是”才预约;也能取消已有预约,同样要明确同意。预约数据库显示在另一个页面上,只 由代码写入,模型碰不到。
工作原理
三个通道,每个都用在模型训练时就熟悉的用途上:
- 音频只属于客户。 没有合成语音,也不注入音频。模型开口说话的自然触发就是声学信号。
- 视觉是操作员屏幕。 屏幕上是两三行字,每句都是操作员能原样对客户说的话(
BOOK: APRIL/WHICH DAY?;APRIL 20, 2 PM/FREE/SHALL I BOOK IT?;BOOKED/ANYTHING ELSE?)。绝不写面向操作员的指令:强制开口时,模型会逐字念屏幕,于是SAY: AVAILABLE就变成了 “I'm going to say available”。屏幕是状态,不是消息:模型说话时它也在更新,模型决定开口的那一刻读到什么,就说什么。 - 控制 token 决定“何时”。 上游有
force_listen;作者加了它的镜像force_speak(MiniCPMO45/modeling_minicpmo_unified.py):在一个已关闭轮次的单元首次决策处,把<|listen|>换成<|speak|>,然后照常解码。实测:12 次里有 11 次在 0.5–1.2 s 内开口。
每一句客户话语的处理循环:
- 浏览器 VAD 在 300 ms 静音后结束这一句 → Whisper large-v3-turbo 转写 → 提取器(一个小型云端 LLM,
google/gemini-3.5-flash-lite,OpenAI 兼容接口)输出一个事件:set( 说出的值,从不写入)、yes(唯一会写入的事件,且必须引用表示同意的原话)、no、cancel。 - 状态机(
gateway.py)合并记录、校验字段、查库里的日期或月份、在“你帮我选”时推荐时段、只在yes时预约或取消,并且是幂等的(记录相同就不产生事件)。 - 屏幕变了;模型根据语音作答并读屏幕,时间在客户说完后 1.5–3 s。
- 每个模型轮次结束时(以及客户说完 3 s 还没回应时),裁判(同一个 LLM,输入整段对话 + 屏幕 + 状态 + 时间信息 + 前台的约定)返回
ok或stuck(沉默、跑题、重复、无视屏幕、不实陈述、无法完成),加一句能说的提示语,以及它听到的陈述。 - 陈述由代码在数据库上验证,不交给 LLM:在得到 yes 之前说“我已经帮您订好了” → 红色
NOTHING BOOKED YET;说“下午 3 点有空”但其实已被占用 → 红色3 PM IS TAKEN。出现 stuck 或红色提示时,提示语被放到屏幕上,下一个分块带上force_speak,让模型开口说出来。 - 防护规则(都在真实运行里测过):两句客户话语之间最多提示一次,每个状态最多两次,两次强制之间隔 6 s,期间状态机变了就丢掉过期裁决,前 3 个分块内或轮次进行中绝不强制。裁判在轮次中也会检查:一个罗列日期、陷入循环的轮次会在约 11 s 处被
force_listen截断,然后纠正。 - KV cache 按会话加窗(基础版,4000 → 3500 tokens,保留系统提示词):上游会让它一直涨到模型上限,几分钟后对话就崩了。
