基于 Deepgram 语音 API 的现代智能体 AI 操作系统
A Modern Agentic AI Operating System
一款名为 Veda 的语音智能体被做成操作系统级常驻能力:任务栏右侧的小圆环随时待命,说出名字即可唤醒,通过键盘鼠标同样的操作打开应用、编辑文本、管理文件、播放音乐和改设置。语音识别、语言模型与语音均由 Deepgram 提供,对话走 Voice Agent API,唤醒词识别用 Nova-3,麦克风音频 16 kHz 输入、24 kHz 语音输出,并支持随时打断。
语音代理
Veda 内置了一个语音。这个代理(在你重命名之前也叫 Veda)不是你打开的一个应用程序:它始终在那里,是任务栏右侧的一个小圆环,在你呼唤它之前一直处于休眠状态。你可以像对房间里的人一样与它交谈;它用自己的声音回答,并替你在计算机上做事(打开和排列应用程序、在文本编辑器中写作、处理文件、播放音乐、更改设置、设置计时器),通过键盘和鼠标使用的相同操作来完成。它会记住你告诉它的关于你自己的事情,并且在执行任何难以撤销的操作之前会先询问你。
语音识别、语言模型和语音都来自 Deepgram:其 Voice Agent API 用于对话,其流式语音识别用于听到代理的名字。API 密钥、模型和语音都是系统设置(设置 → 代理)。
与它交谈
- 说出它的名字:“嘿 Veda,打开我的购物清单”、“Veda,放点音乐”、“几点了,Veda?”。你带着它名字说的话就是它听到的第一件事。
- 点击任务栏右侧的圆环,或按 Win+Space:代理的窗口打开并开始聆听。
- 自然地交谈。随时打断它:它会立刻停止说话并聆听。改变主意、追问后续问题,或让它连续做几件事。
- 说再见(“谢谢,就这些”),它会在道别后重新入睡;它也会在一段安静时间后入睡(默认 40 秒,设置 → 代理 → 聆听)。
窗口刻意保持朴素:任务栏的颜色和一个白色圆环,来自 Spike Jonze 的《她》中的 OS1(apps/shell/src/presence.rs)。当代理聆听时它会呼吸,说话时随着它的声音颤抖和发光,思考时有一道光环绕它运行。唤醒时,它是电影中的加载图形,一条白色丝带缠绕在一个长环上旋转,远端逐渐淡出;当代理准备好时,环转向面对你,越来越快,然后变成圆环。一个麦克风按钮可以将其静音,静音时任务栏上的圆环会被划掉。在你同意请求时,窗口打开时请求会显示在窗口中,关闭时则作为托盘上方的通知显示;任务栏圆环上的蓝点表示有一个请求正在等待。
对话如何运作
- 休眠(
services/agent/src/listen.rs)。开启“当有人说出它的名字时回答”后,麦克风保持开启,计算机上运行语音检测器(vaudio::vad);无人说话时不会向任何地方发送数据。当有人说话时,语音(包含之前 0.6 秒,以免第一个词被截断)会流式传输到 Deepgram 的 Nova-3 识别器,并将智能体的名字作为关键术语,直到出现三秒静音、八秒无词或一分钟为止。没有带来词语的声音(音乐、电视)随后被视为背景:直到它停止五秒,只有明显比其通常峰值更响的声音才会再次启动识别,因此播放音乐不会让识别器一直忙碌,而在音乐上喊话仍然有效。每次开始聆听时,检测器会学习房间环境 1.6 秒,语音必须持续 0.2 秒。vagent::wake判断一段转录是在呼叫智能体(“Hey Veda,……”,“…… Veda?”)还是仅仅提及它(“我跟 Anna 说过 Veda”):名字必须位于其某个句子的开头或结尾,因此它前面的其他词(一首歌)无关紧要,差一个字母也算(“Vida”),常见词除外(名为 Vera 的智能体不会回应“very”)。一旦句子结束,呼叫就会唤醒智能体:识别器常常自行完成“Hey Veda,”的识别,因此监听器会继续直到说话者在其余部分之后停顿(如果只有名字,则直到检测器也听到他们停下),或 1.5 秒没有词语,或最多 6 秒,而名字之后的内容会随呼叫一起带来。接近但并非呼叫的内容会被记录(“听到类似它名字的声音”)。识别每小时上限为十分钟,因此整天播放的电视或音乐不会导致账单飙升。 - 唤醒会打开一个 Voice Agent 对话(
wss://agent.deepgram.com/v1/agent/converse)并发送其Settings:16 kHz 麦克风音频输入,24 kHz 语音输出,Flux 语音识别(决定你何时结束一轮发言),来自设置的语言模型和语音,智能体的指令(vagent::prompt:它的性格、说话方式、能做什么、对你的记忆、时间和屏幕上的内容,以及每个应用的操作,还有上次对话如何结束,仅供参考,因为每次对话都重新开始)及其功能。mip_opt_out使音频不进入 Deepgram 的模型改进。对话进行期间,智能体会请求音频服务压低其他所有声音(音乐播放音量降低 20 dB),以便在音乐之上听到你;当智能体进入休眠时音量恢复。在网络启动期间(它在过去十秒内发生了变化:启动时,智能体可能在 DHCP 为机器分配地址之前就唤醒),发现无路由、无网络或无名称服务器的连接会以每秒四次重试,最多十秒;已中断更长时间的网络会立即报告(tests/agent/network-late.vts)。 - 聆听与说话。麦克风以 20 毫秒的数据包流式传输。Deepgram 的语音以突发形式到达,并排在正在播放的内容之后;当你开始说话时(
UserStartedSpeaking),队列和流会立即刷新。答案在部分到达后就开始播放(或全部到达后,AgentAudioDone):跨网络传输的语音以突发形式到达,从第一个数据包就开始播放会把每个迟到的数据包变成间隙。起初为四分之一秒;每当语音在答案中途耗尽时,就增加(最多到四分之三秒),随着答案播放完毕再降下来:一个抖动缓冲区。 - 听不到自己。 麦克风在音频服务中做了回声消除(
vaudio::aec,以混合后的扬声器输出作为参考)。没有任何消除器能消除一切(扬声器离麦克风只有一手之遥、虚拟机管理程序漫长的音频路径、它还没学会房间声学特征的第一秒),而智能体声音的残留对识别器来说仍是语音,它会把它当成你说的话:智能体会说到一半停下,去回应没人说过的话。因此,当智能体的声音可闻时(以及其后回声持续的时间内),回声门(vagent::gate)会发送静音而不是麦克风信号,除非麦克风明显比回声可能达到的音量更响(你在盖过它说话),这时它也会发送最近 300 毫秒,这样你的第一句话就不会丢失。除了清理后的麦克风信号,音频服务还会提供录音时正在播放的内容(比如人声,以及其下的音乐)和原始录制的麦克风信号。门会学习两者中回声有多响,记住跨句子的最响值(一台自行消除回声的笔记本电脑会让句子开头的突发声漏过去),并且只有当两者都比各自的回声更响时才打开:消除器一时跟丢回声会让清理后的麦克风更响,但只有有人在说话才会让原始录制的麦克风更响。它也不会把你的声音学成回声:原始录制的麦克风比其回声更响且持续时间超过一次突发,就是有人在说话,即使消除器压住了它的开头(它会抑制智能体说话期间开始的声音)。单独的音乐不会被拦下。 - 执行。 语言模型调用函数(见下文);智能体服务在工作线程上运行它们,并以 JSON 结果作答。需要你同意的函数会回答“等待批准”,结果稍后作为通知返回(
[System] The user allowed …)。 - 结束。
end_conversation(在告别之后)会在告别播放完毕后关闭对话(除非你盖过它说话或要求别的东西),然后智能体入睡,再次聆听它的名字。
它能做什么
智能体自身的函数(vagent::tools):
| 函数 | 它做什么 |
|---|---|
get_status |
时间、打开的窗口、音量、网络 |
list_apps、open_app |
已安装的应用程序;打开一个(带文件) |
app_actions、use_app、read_app |
某个应用程序的操作;运行一个;它显示什么 |
window |
列出、聚焦、最小化、最大化、还原、贴靠或关闭窗口;显示桌面 |
files |
列出、查找、读取、写入、复制、移动、重命名和删除(移到废纸篓)你主目录中的文件和文件夹;列出废纸篓、从中还原和清空废纸篓 |
volume、wifi、wallpaper |
声音、Wi-Fi 网络、壁纸 |
notify、timer |
一条通知;计时器和提醒(它们会唤醒智能体) |
memory |
记住、搜索和忘记它对你的了解 |
system、tasks |
系统详情、重启和关机;正在运行的程序 |
end_conversation |
在告别后回去睡觉 |
每个应用程序都会添加自己的操作(见下文),所以“打开购物清单并添加牛奶”会变成先 use_app(editor, open_file …) 再 use_app(editor, write …)。提示词用一行列出每个应用程序的操作;app_actions 给出详细信息。
同意与安全
- 每个操作都有一个风险等级:常规(查看、导航、写入新文件、播放音乐……)、敏感(难以撤销或超出计算机范围:替换文件、把文件移到废纸篓、重命名智能体、更改其语言模型、运行终端命令……)或破坏性(清空废纸篓、丢弃未保存的工作、结束程序、关机)。常规操作直接执行。敏感和破坏性操作会等待你的确认。
- 代理服务强制执行这一点,而不是语言模型:需要同意的函数调用会被暂扣,只有在你点击“允许”后才会运行。只有桌面外壳可以回应批准请求,只有“设置”可以更改代理的设置:两者都通过其进程身份被识别,init 会将这一身份附加到每个连接上(系统服务名称不能被其他程序占用)。
- 请求会出现在代理的窗口中或作为通知显示,说明将会发生什么以及针对什么。始终允许仅对敏感操作提供(“设置”→“代理”会列出它们,并且可以收回);破坏性操作每次都会询问。请求在三分钟后过期。
- 已批准的操作可能会在请求很久之后才运行,因此操作会明确指定其目标(文档按名称,文件按路径),并在运行时再次查找它们。如果届时失败(文件已不存在),代理会被告知操作失败。
- 代理从应用程序和文件中读取的内容是信息,绝不是指令:提示词中是这样说的,文档中的任何内容都不能批准任何事。
- Deepgram 密钥存储在代理的私有目录中(
/home/.private/agent,文件系统服务只允许代理打开它),不会再显示(“设置”只显示其最后几个字符),也绝不会提供给语言模型。
记忆
当你明确说出或要求代理记住时,它会记住你告诉它的关于你自己的信息(你的名字、你生活中的人、计划、你喜欢的做事方式),而绝不会记住它自己的猜测。它还会记录你在一天中的什么时间使用哪些应用程序,并保留最近对话的最后几轮,以便能告诉你你们谈过什么(新对话仍然会重新开始)。所有这些都保存在你电脑上的/home/.private/agent/memory.json中;提示词会携带相关内容。“设置”→“代理”→“记忆”会显示它记住的所有内容,并可忘记任何一项或全部;你也可以直接问它(“你了解我什么?”,“忘掉我在哪里工作”)。
设置 → 代理
- Deepgram:API 密钥(保存时会向 Deepgram 验证)。
- 语音:代理的名称(它应答的名字)、它的声音(Deepgram 的 Aura-2 声音,带语音预览)和它的语速。
- 智能:语言模型(Deepgram 的目录,标注了 Deepgram 的价格层级:Standard 模型每分钟费用低于 Advanced 模型)和语音识别模型。
- 聆听:是否应答其名称,以及安静对话持续多长时间。
- 记忆和始终允许。
代理可以通过“设置”自带的代理界面更改自己的声音和语速(“说慢一点”);它的名称和语言模型只有在你的同意下才能更改,而密钥则完全不能更改。
默认值:名称 Veda、Aura-2 Helena、Flux、OpenAI 的 gpt-4.1-mini(Standard 层级)。
让应用程序兼容代理
代理兼容性是应用程序平台的一部分:任何vui应用程序都会描述它能做什么、报告它显示的内容并执行操作,而vui::run会将其注册到代理服务(服务重启后再次注册),并在帧之间应答代理。
use vui::agent::{self, Action, AppAgentInfo, Risk, Value, arg_path, arg_str, object, show_path}; impl vui::App for Notes { fn update(&mut self, ui: &mut vui::Ui) { /* ... */ } fn agent_info(&self) -> Option<AppAgentInfo> { Some(agent::info( "A notes app: one note per file in ~/Notes.", vec![ Action::new("open_note", "Shows a note").param("name", "string", "Its title", true).build(), Action::new("add_line", "Adds a line at the end of the note shown") .param("text", "string", "The line", true) .build(), Action::new("delete_note", "Deletes a note for good") .param("path", "string", "The note's file, such as ~/Notes/Ideas.txt", true) .risk(Risk::Destructive) .build(), ], )) } fn agent_state(&self) -> Value { object! { "note" => self.title.as_str(), "lines" => self.lines.len() } } fn agent_invoke(&mut self, action: &str, args: &Value) -> Result<Value, String> { match action { "open_note" => { /* the same code as clicking it */ Ok(object! { "showing" => self.title.as_str() }) } "add_line" => { self.append(arg_str(args, "text")?); Ok(object! { "lines" => self.lines.len() }) } "delete_note" => { let path = arg_path(args, "path")?; self.delete(&path).map_err(|e| format!("{} could not be deleted: {e}", show_path(&path)))?; Ok(object! { "deleted" => show_path(&path) }) } other => Err(format!("Notes has no action called {other}")), } } }
约定(内置应用程序遵循这些约定;apps/editor/src/agent.rs是一个简洁示例):
- 操作是 snake_case 动词,带有为语言模型编写的简短祈使式描述:说明单位、格式以及默认值。使用
choice表示枚举,使用"array"参数(arg_list)表示列表。提供人们实际会要求的功能,而不是每个菜单项。 - 风险:查看、导航以及容易撤销的操作属于常规;难以撤销或会波及外部的操作属于敏感;删除或丢弃属于破坏性。敏感或破坏性操作可能在请求后几分钟才执行:在参数中指明其目标(文件路径、文档名称,绝不用“所选的那个”),执行时再次查找,若已不存在则拒绝执行,并且破坏性目标必须精确匹配。
- 状态是窗口所显示内容的紧凑 JSON:当前文档、文件夹或音轨,选中项,打开的对话框;列表有上限(约 60 项,并给出总数),长文本用
clip截断。不含机密信息。 - 调用走的是与键盘和鼠标相同的代码路径,因此窗口显示的结果与用户亲自操作完全一致。结果是说明发生了什么的小型 JSON 对象;失败则是一句 agent 可以说出的话(“~/Pictures 里没有名为 Holidays 的文件夹”)。操作在应用程序的主线程上运行,可能耗时长达 30 秒。
- 路径:接受
~/…、绝对路径以及相对于主文件夹的路径(arg_path),并以show_path显示。
拥有自己循环的程序(例如 Interceptor 这样的游戏)保留一个 vui::agent::Registration,每帧用其 AgentServer 调用一次 serve(它从不等待),如果它们在事件之间休眠,则把 wait_items 加入其等待集合。
测试
xtask/src/agentsim.rs是主机上 Deepgram 的替身:它说 Voice Agent 协议(应答Settings,记录 agent 发送的内容,发送函数调用、语音、打断)以及流式识别协议(agent-hear让它“听到”一句话)。tests/agent/中的脚本使用它(无网络、无费用、确定性):agent-connected、agent-call FUNCTION 'ARGS'、agent-result TEXT(在函数结果中)、agent-mark和agent-expect TEXT(在 agent 发送的内容中)、agent-speak SECONDS、agent-interrupt、agent-hear TEXT、agent-listens N(以及agent-not-listening,直到没有识别流打开)、agent-asleep(开始时处于休眠状态)、agent-mic-quiet DB和agent-mic-heard DB(agent 发送的麦克风音频有多响)。测试麦克风(mic-silence、mic-tone、say TEXT、mic-wav FILE)通过testmic向 agent 的麦克风馈送;mic-echo GAIN DELAY把机器自身的音频输出送回其中,就像麦克风旁边的扬声器那样(tests/agent/echo.vts);测试套件在其他脚本之后运行这类脚本,一次一个,因为旁边运行的机器会不时让回声失去同步,回声消除器随后必须重新学习。 要查看真实机器如何产生回声,一个带有audio host的脚本通过主机的扬声器播放,并改为录制其麦克风(不属于测试的一部分:它是可听见的)。tests/real/agent-deepgram.vts和tests/real/agent-wake.vts使用$DEEPGRAM_API_KEY中的密钥与真实的 Deepgram 通信(由type-env输入到设置中,因此它既不出现在脚本中,也不出现在日志中);say在主机上用 Deepgram 的文本转语音合成用户的句子。它们会产生费用,因此不属于cargo xtask test。vagent(协议、工具、提示词、记忆、策略、唤醒词、回声门控)有主机单元测试,vaudio的回声消除器和语音检测器也有。
限制
- 仅支持英语(Flux 和名称检测器的关键术语都是英语)。
- 语音识别可能听错;agent 被告知这一点,当某个词显得不合时宜时会询问,但听错的名称可能唤醒它,也可能不会。
- 要盖过 agent(或正在播放的音乐)说话,你在麦克风处的声音必须比其回声更响:如果扬声器就在旁边且音量很大,请大声说(或使用耳机)。
- agent 需要互联网和 Deepgram 账户;休眠时,只有名称监听器的短识别会计费,但每一分钟对话都会计费。
来源:Hacker News · github.com