让对话与执行真正并行:Qwen-Audio-Agent 架构解析

语音助理长期存在一处根本性割裂:当它转身去检索资料、调用工具或推进一段多步任务时,整场对话往往被迫悬停。用户只能干等,直到那句「好了,我查到了」姗姗来迟。Qwen-Audio-Agent 是一个开源的实时语音运行时(Voice Runtime),主张交流应当连续、Agent 应当始终在场——用户在说,任务在跑,二者互不阻塞。本文只谈核心设计:两层运行时的骨架、支撑「边聊边干」的关键机制,以及藏在边界处的取舍。项目地址:https://github.com/QwenAudio/qwen-audio-agent

架构与实现

对外一个助理,对内两层职责

在用户侧,交互对象始终是唯一的一个 Qwen-Audio 助理;在内部,系统被解耦为两层:实时前台承担全双工语音、可即时作答的轻量请求与本地工具,追求低延迟与持续在场;后台 Agent 是一个持久会话,接管一切需要工具、实时信息、文件或多步推进的请求。一条分界线贯穿全局——可即时回答者当即应答,需要动手推进者一律下沉后台,而前台的语音通道从不被任何后台任务占用。正是这条约束,把「边聊边干」从口号变成了可工程化的边界。

让对话与执行真正并行:Qwen-Audio-Agent 架构解析
图1:参考架构总览:前台直接回答,或把更深入的工作委派给 Agent Runtime

展开便是三层接入参考架构:实时前台、承担调度与协调的 Gateway,以及可选的、真正落地执行的后台会话与工具工作区。贯穿其间的是一条单向依赖铁律:依赖只向内收敛,前台工具不引用后台适配器,Gateway 既不持有 UI 组件,也不直接执行终端操作。

让对话与执行真正并行:Qwen-Audio-Agent 架构解析
图2:三层接入参考架构

全双工语音,与一个被刻意收窄的前台

最直观的一层是连续、可自然打断的语音交互:默认启用带回声消除的全双工模式,用户直接开口即可打断播报。但决定对话体验上限的并非双工本身,而是一项近乎苛刻的取舍——整个 Realtime 前台默认仅暴露八个工具:

server/src/voice/frontend-tools.mjs
export const TOOLS = [
  spawn_thinking,           // 把可执行的工作交付后台
  schedule_reminder,        // 定时提醒 / 定时任务
  cancel_agent_task,        // 取消仍可取消的工作
  get_agent_task_status,    // 查状态 / 进度
  get_current_time,         // 本地时区当前时间
  memory,                   // 读写 USER.md / MEMORY.md
  notes,                    // 命名清单增删查改
  respond_agent_permission, // 转达权限决定
]

更能说明问题的是这份清单里刻意缺席的能力:前台无权选择、创建、延续或取消后台会话,无权决定同步/异步的执行形态,也无权挑选具体工具或子 Agent。它的职责被收敛到极致——倾听、转达、开口,而「究竟如何完成」完整下沉给后台。前台越轻,语音通道越不易被复杂决策拖慢。

「智能中究竟藏着什么神奇的诀窍?诀窍就是根本没有诀窍——智能的力量来自多样性,而非任何单一而完美的原则」(Marvin Minsky,《心智社会》)。把实时前台交给双工,把办事后台交给 harness,正是这份取舍的注脚。

非阻塞并发,多任务有序汇入同一会话

这是整套体验的中枢:对话绝不因后台任务而停顿,用户还能并行发起多个任务、随时追问或取消。支点在于 spawn_thinking 从不等待它派发的工作——请求一经受理便立即把控制权交还实时语音:

非阻塞请求流
用户指令 ─┬─ 可即时作答 ─────────────────► Realtime 当即开口
         │
         └─ 需要执行
         │
         ├─ spawn_thinking(objective) → 立即返回 accepted
         ▼
 owner 级 FIFO 队列(同一时刻仅一项进入后台)
         ▼
 固定的后台 Agent 会话(自主决定执行策略)
         ▼
      最终呈现 → 等安全的插入窗口 → 自然播报

「不等待」被推得很彻底:受理路径上已不再 await 任何东西——原话解析改为在受理瞬间挂上等待器、留到派发时再取用,后台可用性探测则换成一份带过期时间的同步快照,探测未回时先乐观放行。判断「该不该等」的标准始终一致:只有等待本身构成语义的操作才保留同步,例如取消——它必须如实报出后台停下了没有。

多任务不做语义「合并」,而是更稳健的串行汇入:每位 owner 一条 FIFO 队列,任一时刻只有一项工作进入后台会话;由于会话固定且持久,后一个任务能自然承接前一个的结果。每个任务被建模成状态机,但它只是一张投递回执,而非后台内部任务图的镜像——对外仅保留请求、时间戳、最终结果与通用工具活动,不含执行模式、子 Agent 状态或后台拓扑。克制的接口,同时也是经济的接口。

固定的后台身份,与结果的择时回流

为什么重开一段全新语音对话,助理仍记得此前正在推进的事?因为每个 owner 与后台的组合都被赋予一个稳定不变的会话身份(形如 openclaw:personal:backend),原生会话 ID 隐藏其后,每轮以 resume 恢复。请求经由结构化的协调信封递交,前台把用户原话与保守整理后的意图分置两栏:

coordinator.mjs · 协调信封
<qwen_audio_agent_request>
{ "input": { "final_asr": "用户本轮原话",    // 唯一事实来源
             "objective": "前台的保守整理" } // 意图交接,而非执行计划
}
</qwen_audio_agent_request>

后台执行完毕只回传唯一的一份最终「呈现」:speech 是供语音重新组织的语义材料而非逐字台词,inline 承载注入时间线的 Markdown 或代码。最见功力之处在于结果何时开口——系统不在就绪的瞬间贸然打断,而是遵循一套择时回流的投递纪律:

  • 播完才算数。结果先注入 Realtime 上下文,只有真正播报完毕才标记为已投递——以播放完成为准,而非生成完成。
  • 让路当前对话。若用户正在说话或已有另一条响应挂起,投递主动退让、稍后重试且不重复注入;一枚可续租的 claim 确保两个同时在线的前台不会争抢同一结果。

协调层不亲手,异步委派第三层

有些任务漫长而独立——要在某个工程目录里跑完一整套改动与验证。此时协调层不亲手在本轮硬扛,而是异步创建一个独立的第三层任务:session_start 一旦返回 started,协调层就必须按约定返回一枚「已委派」响应并结束本轮,绝不在同一轮里轮询它;且一轮协调只能派生一个第三层任务,适配器用一道单飞闸守住这条规矩。

派生成功的那一刻,适配器立即把原始 Work 置为 delegated,并同时释放两把锁——后台会话的串行化锁与调度轨道,于是第三层独自运行期间,其他语音请求依旧能用上协调层。跑完之后,唯有与 delegation_id 严格对应的那次目标完成才能了结它:忙碌的目标、空结果、无关的会话更新或陈旧的旧结果,都无法冒名顶替。超时只作用于协调轮与呈现轮,第三层独自运行的等待并不计时。

四层上下文,与可精确改写的本地 Markdown

连续性不止于会话,也体现在记忆。前台的人格与记忆被拆成四份互不越权的文本:PROMPT.md 是随包分发的核心规则,ASSISTANT.md 是助理画像,USER.md 存长期偏好,MEMORY.md 存长期事实。四者拼进同一段前台指令,权限却不同:画像只影响名称、人格与表达风格,凡涉及工具、路由、权限或事实判断一概无效;长期记忆更被排除在指令之外,只作事实依据。可写的只有两份,接口因此收得极紧:

frontend-tools.mjs · memory 工具
memory({ action: 'read' | 'append' | 'replace',
         document: 'user' | 'memory' | 'all',
         old_text, new_text })
// ASSISTANT.md 不在枚举内:助理无权改写人设

「精确改写」是核心:replace 要求 old_text 在目标文档中恰好出现一次,多一处或一处不落都直接失败,绝不模糊匹配;提交前还要核对版本号,期间被人手改过便安全失败。这些失败都不以异常示人,而是降级成一条带上最新文档的可重试回执,请模型重读后再改。

更值一提的是无感沉淀:用户不必特意开口说「记住」。语音连接关闭时,系统在后台顺手把值得长期留存的内容蒸馏出来分投两份文档,却被要求绝不拖慢、更绝不弄坏断连本身。抽取器处处设闸:防抖、发言过少不跑、敏感内容整批丢弃、归档边界双向校验;凡改动长期偏好,必须在转写里找到一句用户亲口说过的长期指令。‍

开放的后台生态:复用,而非重建

后台并不锁定某一家。目前接入的十家后台共用同一份后台目录作为唯一事实来源——启动命令、服务地址与能力开关都在条目里声明,运行期 driver 由条目直接合成;进程既可由 Gateway 托管,也可连接用户自管的服务。生态真正的价值在于复用而非重建:直接沿用后台已有的模型、工具、MCP 与认证,只递交一枚信封,绝不指挥后台如何调度自身能力。

设计取舍

以上勾勒了系统「能做什么」。真正拉开体验差距的,往往是那些藏于边界处的取舍——每一处都在回答一个极易被做错的细节问题。

意图交接:保守解读,而非执行计划

前台交付给后台的 objective 约束近乎严苛:必须忠实保留本轮诉求,不得以占位内容启动空转,更不得臆测或编造事实。最终 ASR 始终是唯一事实来源,objective 只承担「意图交接」之职——既让后台理解要做什么,又绝不让前台越权替用户规划如何做。把无法处理的问题坦诚交给下一环节,错误因此可见,而非潜伏。

追问进度:一条隐形的高优先控制流

用户在委派任务运行时追问一句「到哪了」,看似寻常实则棘手:协调器可能正被另一轮占用。解法是为 delegated 工作创建一条隐藏的、高优先级的控制查询,排在正在运行的协调轮之后、普通排队工作之前,结果沿常规异步通告回流,且不作为用户任务暴露。查询与执行分离,系统于是不必在「打断」与「沉默」之间二选一。

确认式取消,重启时诚实失败

取消绝非乐观假设——任务持续停留于 cancelling,直至某条路径确认真正停止才落定为 cancelled。至于 Gateway 重启,进行中的工作无法安全续跑,系统宁可如实转为 failed 并附原因,也决不假装无缝恢复;唯一的例外是提醒——它的执行体只是把既定文本念出来,重放没有副作用,于是已触发未播报的提醒会还原成 scheduled,作为逾期项补说一次。

「不确定性是通讯系统的本质属性,无法也不应被完全消除;可靠性的真正来源,不是避免所有错误,而是在行动上优化性价比」(俞凯,2025 国际工程智能大会主旨演讲)。不确定不等于不可靠:系统坦诚以待,用户知情决策。

说到就要做到:只请模型重想一次,绝不代为执行

还有一类失当出自模型自己:嘴上应了一句「我来查一下」,本轮却没有调用任何工具。为此系统在响应结束处加了一层响应守卫:它不参与执行,也不改动任何任务状态,只在识别到这种情形时请模型就同一轮重新判断一次。识别规则刻意做窄——只认四十字以内、以「我来/马上/这就」起头再接执行类动词的短句,句末带问号或出现「要不要」这类征询一律排除。措辞也留了台阶:只说「重新判断」,允许模型认定确实不必执行。

没有确认弹窗,就把记忆摊开成人能读的文件

图形界面里,自动记忆通常配一枚「要不要记下来」的确认弹窗;纯语音场景没有这块画布。系统于是换了一条路:事前把口子收窄,事后把东西摊开——不必再造一套回溯界面,因为两份记忆本身就是普通 Markdown,用户随时可打开、读懂并直接改掉;审计流水只记改了哪份文档、前后版本号与改动处数,正文一字不落进日志。这是典型的等价替换:交互形态拿不出「事前确认」,就用「事前收窄 + 事后可改」凑出同等强度的约束。

结语

纵观全局,Qwen-Audio-Agent 始终围绕一个执念:让交流与执行真正并行,而非彼此阻塞。两层运行时撑起了「能做什么」,边界处的取舍决定了「做得干不干净」。并发、委派、取消、投递中固有的复杂度被牢牢封存于系统内部;用户所面对的,自始至终只是一个会倾听、会回应、并在任务完成时自然道一声「已经好了」的助理。

如果这套设计带来了些许启发,不妨移步仓库读一读源码、跑一跑,或者直接换上你自己的后台 Agent 一探究竟。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐