开始使用
novme 是什么?
听懂当下,带走一份完整记录。
novme 是一款面向 macOS 的会议字幕与复盘工具。它同时采集本机麦克风和指定会议应用的音频, 优先发布源语言字幕,再异步补充中英文翻译;会议结束后,它会保留完整逐字稿并生成摘要、决策、 行动项和风险。
- 低延迟:源字幕先显示,翻译不会阻塞下一条源字幕。
- 双路采集:区分“我”和“会议”两类音频来源,关键帧带有可追踪的序号与时间信息。
- 完整复盘:实时路径可以降级或延后,但会议资料会持久化并在结束阶段补齐。
- 两种推理方式:支持 Apple Silicon 本地 MLX,也支持通过 Bearer 认证连接 DGX Spark。
从声音到会议记录
novme 将“实时可读”和“会后完整”拆成两条策略不同的处理路径。
快速开始
首次使用本地 MLX
首次安装后,按顺序下载模型、启动本地 MLX,再填写连接地址。模型缺失或预热失败时,novme 会直接显示错误,不会静默切换到其他后端。
环境要求
首次安装:3 步完成本地配置
下载三个所选模型
打开 App 的“设置 → 模型”,依次下载语音识别、实时翻译和会议总结所选模型。等待三项都显示为已下载后再继续;下载中的模型不能用于启动。
启动本地 MLX
在“模型”页底部点击“启动并使用”。novme 会加载并预热所选模型;状态变为“本地推理已就绪”前不要开始会议。失败时按界面提供的日志路径检查实际错误。
填写本地连接地址
进入“设置 → 连接”,在后端地址中填写 http://127.0.0.1:18080,网络路由选择“直连”,API Key 留空,然后点击“保存并检查”。连接状态变为“可用”后即可开始会议。
授予系统权限
- 允许 novme 使用麦克风。
- 允许 novme 进行屏幕录制,以便 ScreenCaptureKit 读取会议应用音频。
- 授权后完全退出并重新打开 novme。
从源码运行(开发者)
./script/build_and_run.sh --verify
正式发布包不需要执行此命令。仓库开发者使用验证模式检查应用签名、启动状态和权限连续性;普通重建不应重置系统权限。
开始使用
连接与认证
novme 没有用户名、密码、Cookie 或 OAuth 登录。连接身份是后端共享的 API Key,原生客户端会把它作为 Bearer 凭证发送给 HTTP 和 WebSocket。
本地后端
App 内置的本地 MLX 后端只监听 http://127.0.0.1:18080。首次安装时后端地址为空;下载三个所选模型并启动本地 MLX 后,请在“连接”页手动填写该地址,网络路由选择“直连”,API Key 留空,再点击“保存并检查”。严格健康检查通过后才允许开始会议。
远端 Spark
原生客户端不预置远端地址;请在设置中输入明确的后端原点。远端地址必须填写有效的 sk- API Key,Key 至少包含 20 个密钥字符;Spark 的 llama-server 和可选 MiMo 服务仍只监听远端回环地址。
在程序中保存连接
- 打开 macOS“设置”(
Command-,),进入“连接”页。 - 填写后端原点地址和 API Key,地址只能包含协议、主机和端口,不能附带路径、查询参数或内嵌账号密码。
- 点击“保存并检查”。客户端先校验格式,再请求
GET /health;严格模式、模型身份和音频契约不匹配时会显示失败。
- 后端地址保存在 macOS 应用偏好设置中。
- API Key 只保存在 Keychain,不写入应用偏好设置;网络请求使用
Authorization: Bearer <API Key>。 - HTTP 与两个 WebSocket 路由共用同一个 Key。缺少或错误的 HTTP 凭证返回
401;WebSocket 会在握手阶段被拒绝。 - 远端使用明文 HTTP 时,程序会在保存前显示风险警告,因为 Bearer Key、音频、字幕和会议数据都没有传输加密。当前直连只适合可信网络,不能视为 TLS 登录方案。
使用指南
一次会议的三个阶段
novme 把界面收敛为设置、实时字幕和会议复盘,结束处理期间会保留已有逐字稿。
设置
选择麦克风、会议应用、源语言和翻译方向。后端与本地权限未就绪时,“开始”不可用。
实时字幕
阅读源字幕和翻译,观察“我 / 会议”两路活动状态,并随时显式结束会议。
会议复盘
等待最终处理完成,查看摘要和逐字稿,导出 Markdown,或开始一场新会议。
核心能力
实时翻译字幕
每个音频帧先归档,再进入识别。源文本先到达客户端,翻译随后更新同一个字幕段。
源字幕优先
翻译任务具有独立的并发与排队边界。翻译繁忙或失败时,源字幕仍然保留并继续发布;
系统会发送 translation_delayed 或 translation_error 事件,并在会议结束阶段重试。
音频帧协商
| 运行配置 | 客户端帧 | 后端处理 |
|---|---|---|
| 固定兼容模式 | 1000 ms | 每个固定帧形成一个实时推理单元 |
| 当前 Spark 自适应 VAD | 80 ms | 常规边界为 800 ms 静音 / 15 秒硬上限;语音超过 4 秒后,出现 160 ms 低于来源阈值 75% 的停顿即可提前释放 |
| 历史 400 ms / 4 秒方案 | 80 ms | 仅保留客户端兼容和反例验证;真实回放出现切句、请求/算力放大与无效翻译,不再作为生产部署配置 |
客户端必须以 GET /health 返回的 client_audio_chunk_ms 为准,不应硬编码帧长。
上传滑动窗口也随帧长缩放:1000 ms 模式允许 4 帧在途,80 ms 模式允许 50 帧在途,
始终保留 4000 ms 的合计来源音频预算。每帧仍使用精确的 (source, seq) 归档确认与重试语义。
背压与完整性
客户端上传和后端推理是两条独立的背压边界。客户端会保留已采集帧,直到收到服务端
archived 确认;如果上传长期追不上双路采集,客户端会显示积压错误并提前停止采集,
保护已经连续归档的会议记录,而不是静默丢弃新音频。后端每个音频来源的实时识别任务和翻译任务
也有明确上限,超出实时推理容量的内容会转为会后处理。
核心能力
会议复盘
结束会议不是立即清空现场,而是进入可见的收尾阶段,等待已接收音频和待处理任务完成。
复盘内容
- 按时间排序的源语言逐字稿与翻译。
- 最终说话人标签。
- 会议摘要、决策、行动项和风险(有结果时显示)。
- 由后端记录生成的 Markdown 导出文件。
失败时保留什么
摘要或最终处理失败时,界面保留已经存在的逐字稿,并允许对同一个会议 ID 重试。 novme 不会静默创建替代会议,也不会自动返回设置页掩盖失败。
核心能力
音频采集
原生客户端分别管理麦克风与会议应用音频,任何来源的异常都必须可见且可追踪。
麦克风
使用 AVAudioEngine 采集本机输入,并在设置和实时阶段显示音量活动。
会议应用音频
macOS 13+ 使用 ScreenCaptureKit 读取用户明确选择的会议应用。
如果选中的应用退出,客户端会显示失败,不会自动换成另一个应用。
开发与部署
后端 API
FastAPI 后端同时提供会议生命周期 REST 接口和实时音频、字幕 WebSocket。
REST
GET/health严格运行身份、引擎、模型与音频帧契约POST/meetings创建会议并设置源语言与目标语言POST/meetings/{id}/finish等待接收屏障并完成复盘GET/meetings/{id}/segments读取最终字幕段GET/meetings/{id}/summary读取或生成会议摘要GET/meetings/{id}/export.md导出后端拥有的 Markdown 记录创建会议
POST /meetings
Content-Type: application/json
{
"source_lang": "en",
"target_lang": "zh"
}
WebSocket
| 路径 | 方向 | 用途 |
|---|---|---|
/ws/audio | 双向 | 客户端发送带来源、序号和时间信息的 PCM 音频帧;后端返回 archived、deferred 或 error 状态 |
/ws/subtitles/{id} | 后端 → 客户端 | 接收源字幕、翻译更新和类型化状态事件 |
真实实现
后端处理顺序与有效优先级
novme 没有一个统一的数字优先级队列。当前“优先级”由持久化顺序、每来源串行 ASR、 独立翻译信号量和结束屏障共同形成。
一帧音频的实际处理顺序
| 顺序 | 实现行为 | 失败或拥塞时 |
|---|---|---|
| 1 | 解码 PCM,追加到会议音频归档;有序客户端还会写入 SQLite audio_chunks 与连续序号。 | 未完成持久化就不发送 archived;错误直接返回给客户端。 |
| 2 | 固定模式直接形成推理单元;VAD 模式按 /health 的完整 tuple,把 80 ms 帧按 RMS 组装成至少 1 秒、最多 4 秒或 15 秒的语音段。 | 纯静音段标记完成但不创建字幕;格式、时间线或混合边界 tuple 错误直接失败。 |
| 3 | ASR 按 (meeting_id, source) 串行执行,同一来源保持输入顺序。 | 每来源最多积压 4 个任务;达到上限后转入会后恢复,不无限排队。 |
| 4 | ASR 结果先写入数据库并立即发布源字幕。 | ASR 失败时保留已归档音频,发送错误并留给结束阶段重跑。 |
| 5 | 翻译作为独立任务提交,完成后更新同一个 segment_id 并再次发布。 | 队列满发送 translation_delayed;提供方失败发送 translation_error,源字幕不回滚。 |
当前队列与并发参数
| 控制项 | 当前值 | 作用范围 |
|---|---|---|
AudioUploadPolicy.inFlightAudioBudgetMs | 4000 ms | 原生客户端合计来源音频预算;1000 ms 为 4 帧,80 ms 为 50 帧。 |
pendingAudioStopThreshold / HardLimit | 48 s / 60 s | 原生客户端合计来源音频缓存;80 ms 模式对应 600 / 750 帧,双路采集的墙钟时长约为一半。 |
NOVME_REALTIME_MAX_PENDING_PER_SOURCE | 4 | 每个会议、每个音频来源独立计算;同一来源 ASR 串行,麦克风与会议音频可以并行。 |
NOVME_TRANSLATION_MAX_CONCURRENCY | 10 | 整个后端进程共享的翻译提供方并发信号量,与 Spark 的 10 个模型槽对齐。 |
NOVME_TRANSLATION_MAX_PENDING | 32 | 整个进程中尚未完成的翻译任务总数,不按会议预留配额。 |
FINISH_BARRIER_TIMEOUT_SECONDS | 15 | 一次结束请求等待接收、实时 ASR 与翻译收敛的总时限。 |
翻译和会后总结谁优先
对同一场会议,代码保证的有效顺序如下:
- 已确认音频接收:等待客户端上报的最后序号全部进入持久化序列。
- 实时工作收敛:等待已登记的音频接收、ASR 和翻译任务。
- 补齐翻译:扫描所有有源文本但无译文的字幕段并重新提交翻译。
- 翻译硬门禁:仍有缺失译文时,
finish返回 HTTP 502,不生成成功复盘。 - 会后处理:重跑延后 ASR、重建顺序音频、执行说话人处理,然后调用总结模型。
- 提交最终状态:先保存 summary,再把会议标记为
finalized。
翻译请求细节
- 只翻译当前字幕段,并带上同一音频来源之前最多 2 条字幕作为术语与指代上下文。
- Spark 请求发送到
/v1/chat/completions,温度0.1,最多生成80tokens,HTTP 超时 30 秒。 - 提示词要求只返回一行译文,并使用
/no_think;代码会移除<think>内容和常见“Translation:”标签。 - 兼容
/completion的回退在 Spark 上关闭:NOVME_OPENAI_COMPLETION_FALLBACK=0。 - 翻译成功必须持久化到 SQLite;持久化失败与模型失败同样会进入错误和结束重试路径。
会后总结细节
- 总结输入使用最终字幕列表,每行格式为
说话人: 译文或原文。 - 当前会把整场逐字稿一次性发送给模型,没有 token 预算计算、分块总结或 map/reduce。
- 模型必须返回 JSON:
summary为字符串,decisions、action_items、risks为字符串数组。 - 正式
finalize路径严格验证 JSON 类型;解析或字段类型错误会让最终处理失败。 citations不是模型生成的,后端固定附上前 5 个字幕段作为引用材料。- Spark 总结最多生成
1024tokens,本地 MLX 也是同一默认上限。
关键可观测字段
关键日志包含 meeting_id、source、seq、队列深度、模型 ID、
asr_ms、queue_ms、provider_ms、结束屏障任务数和总耗时。
这些字段用于判断瓶颈是在采集、传输、ASR、翻译排队、模型推理还是最终处理。
真实实现
当前模型与运行参数
下面区分“健康接口模型 ID”“llama-server 服务别名”和“实际模型文件”,三者不是同一概念。
模型选择不是自动切换
启动脚本通过显式的引擎配置选择本地或 Spark profile。严格模式不会因为模型不可用而偷偷换成另一个模型;MiMo 只作为单独的未验收 ASR POC。
| Profile | 选择配置 | 模型职责 |
|---|---|---|
| Apple Silicon 本地 | NOVME_SPEECH_ENGINE=mlx-whisperNOVME_TRANSLATION_ENGINE=mlxNOVME_SUMMARY_ENGINE=mlx | Whisper Small MLX 做 ASR;共享 Qwen2.5 1.5B MLX 做翻译和总结。 |
| DGX Spark 当前 | NOVME_SPEECH_ENGINE=qwen3-asrNOVME_TRANSLATION_ENGINE=openai-compatibleNOVME_SUMMARY_ENGINE=openai-compatible | Qwen3-ASR 做 ASR;Qwen2.5 1.5B 独立负责实时翻译;DeepSeek Chat Q5 独立负责会后复核。 |
| MiMo POC | NOVME_SPEECH_ENGINE=mimo-asr | 只替换 Spark ASR,必须单独部署 loopback sidecar;未完成 GB10 负载、质量和回滚验收,不是默认选项。 |
当前 Spark 实际部署
以下值在 2026-07-21 通过远端非密钥配置、发布元数据和 /health 严格核对,
当前认证发布为 20260721T162100Z-adaptive-vad;实时翻译与会后复核使用两个独立的 loopback 模型服务,说话人复盘使用独立的 CPU CampPlus 模型,三者不会互相静默回退。
| 能力 | 引擎 / 模型 | 当前参数与边界 |
|---|---|---|
| 实时 ASR | Qwen3 ASRQwen/Qwen3-ASR-1.7B | 固定 revision 7278e1e…dd6e5;BF16;Transformers 离线推理;CUDA 0;单模型锁串行;最多 256 tokens;模型不提供置信概率。 |
| 说话人标签 | CampPlusiic/speech_campplus_sv_zh-cn_16k-common@v2.0.2 | 实时阶段仍按来源显示;会议结束后读取完整系统 WAV,在 CPU 上按音色聚类并持久化 对话人1/2/...,模型权重 SHA 和 0.31 阈值由严格健康检查锁定。 |
| 实时翻译 | OpenAI-compatibleqwen2.5-1.5b-realtime-local | 80 tokens;前序同来源上下文 2 条;后端并发 10。真实持久字幕顺序测试 P50/P95 为 220/322 ms;英文译中文结果若没有中文会直接报错。 |
| 会后复核 | OpenAI-compatibledeepseek-llm-7b-chat-q5-local | 独立端口和两路槽位;分层 map/reduce;1024 tokens 最终输出;135 段真实会议已完成复核与总结。 |
Qwen3-ASR 使用 qwen-asr==0.0.6、transformers==4.57.6 和现有 torch==2.11.0。
模型不返回置信概率,因此字幕记录中的 confidence=0.0 是明确的“不可用”哨兵,
/health 同时返回 confidence_available=false,不会伪造固定高置信度。
Spark 实际 GGUF 与 llama.cpp
/opt/novme/shared/models/qwen2.5-1.5b-instruct-q4_k_m.gguf6a1a2eb6d15622bf3c96857206351ba97e1af16c30d7a74ee38970e434e9407eqwen2.5-1.5b-realtime-local @ 127.0.0.1:1808140960 / 10 slots / 4096 each/opt/novme/shared/models/deepseek-llm-7b-chat-q5_k_m.gguf5ab7fade2ec4efe8793a1b557694a8e23a573ccccbadd2ef75d706a04a66f6c6deepseek-llm-7b-chat-q5-local @ 127.0.0.1:180838192 / 2 slots / 4096 each
两个 GGUF 文件都与发布元数据中的 SHA-256 一致,部署门禁分别验证路径、哈希、别名、端口与容量。
各自的启动脚本要求总上下文能被槽数整除,并保证每槽至少 4096。历史 DeepSeek R1 Q5 评估因空译文而结束,不是活动配置,也不是失败时的自动回退。
Spark 并发实测
下表是 2026-07-13 的 Qwen 历史容量基线。当前实时 Qwen 对 20 条真实持久字幕顺序测试 P50/P95 为 220/322 ms,十路执行为 775/1056 ms;并发运行 135 段 DeepSeek 会后复核时,实时 Qwen P95 为 982 ms。
| 并发 | P50 | P95 | 最大 |
|---|---|---|---|
1 | 38 ms | 39 ms | 104 ms |
4 | 56 ms | 197 ms | 197 ms |
10 | 87 ms | 393 ms | 394 ms |
Qwen3-ASR 在 GB10 上的固定 revision 权重峰值分配约 4.376 GiB,缓存加载约 26.8 s。
真实后端回放的四个 1 秒 ASR 请求耗时为 90–215 ms,对应翻译均成功;
当前 Spark 将实时 Qwen 翻译与 DeepSeek 会后复核隔离。短字幕翻译和 135 段会议复核已通过窄范围实机门禁,但持续混合负载、广泛质量以及 4 秒 VAD 候选仍未完成验收。
Apple Silicon 本地 MLX
| 能力 | 模型 | 运行方式 |
|---|---|---|
| ASR | mlx-community/whisper-small-mlx | 16 kHz PCM;温度 0;不继承上一段文本;启动时执行 1 秒静音预热。 |
| 翻译 | mlx-community/Qwen2.5-1.5B-Instruct-4bit | 80 tokens;温度 0;带中英 few-shot 示例。 |
| 总结 | mlx-community/Qwen2.5-1.5B-Instruct-4bit | 1024 tokens;严格 JSON;与翻译共享同一个已加载运行时。 |
MLX 翻译和总结按精确模型 ID 共享模型、tokenizer 和一把生成锁,因此同一进程内不会并发操作该运行时。 当前自适应配置使用 80 ms 传输帧,并由后端按来源 RMS 组装语音段:常规 800 ms 静音释放、15 秒硬上限;超过 4 秒后遇到 160 ms 低能量停顿可提前释放。666.56 秒生产模型回放得到 107 段,仅 2 段触发硬上限,ASR 与翻译均无失败。
模型就绪与失败策略
- 严格模式下,ASR、翻译、说话人和总结引擎任一不可用都会阻止后端启动;模型不会在运行中自动互换。
- MLX 模型和 Spark Qwen3-ASR 都必须在启动阶段完成加载与一次真实预热推理;失败会阻止严格模式启动。
- Spark 的 OpenAI-compatible 引擎在启动时探测回环
/health;当前就绪状态是构造时快照,不是持续容量监控。 /health暴露精确引擎名称、配置模型 ID、部署修订和音频/VAD 契约;Spark 部署验证另外核对 GGUF 指纹。
开发与部署
运行模式
严格模式
默认模式。必需引擎不可用、模型身份不匹配或健康契约缺失时,后端和原生客户端都会拒绝继续。
export NOVME_ENGINE_MODE=strict
scripts/run-backend.sh
开发模式
仅用于 UI、存储和传输开发。替代引擎会在健康状态中明确标记为降级,不会伪装成正式提供方。
NOVME_ENGINE_MODE=development scripts/run-backend.sh
健康契约 v2
/health 必须返回部署修订、配置档、引擎模式、精确模型 ID、协商帧长、RMS 指标、来源专属阈值和完整 VAD 边界。
原生客户端和回放工具会拒绝缺失或不匹配的契约;单纯的 HTTP 200 不是可接受的部署验证。
开发与部署
DGX Spark 部署
当前发布使用受 Bearer 认证保护的直连 HTTP 配置。FastAPI 对外映射到 Spark 的 8080,推理服务仍只绑定远端回环地址。
部署顺序
scripts/spark-deploy.sh --execute --maintenance-window
scripts/spark-verify.sh --execute
必须先完成发布验证,再打开客户端。原生客户端不会自动启动 SSH 隧道,也不会预填远端 origin;隧道脚本仍保留为运维访问路径。
Spark 直连
批准的直连 profile 是 direct-http-8080:运维者明确配置的 origin 转发到 Spark 的 FastAPI 0.0.0.0:8080。它要求 Bearer 认证,但 HTTP 不加密 Key、音频、字幕或会议数据,只适合可信网络。实时翻译 18081、会后复核 18083 和可选 MiMo 18082 仍必须保持回环绑定。
python3 -c 'import secrets; print("sk-novme-" + secrets.token_urlsafe(32))'
# 将生成的 Key 写入 Spark 的受限 secrets 文件后执行
scripts/spark-deploy.sh --execute --maintenance-window
scripts/spark-verify.sh --execute
专用 Key 写入 Spark 的 /etc/novme/novme.secrets,认证发布验证了缺失/错误 Key 的 HTTP 拒绝和 WebSocket 握手拒绝;同一 Key 由客户端用户在 macOS“设置”的“连接”页保存到 Keychain。HTTPS/WSS 是后续替代明文直连的安全目标,当前没有用它假装已经完成。
当前 Spark 配置
- 当前发布:
20260721T162100Z-adaptive-vad,direct-http-8080、严格模式、强制 Bearer 认证并使用UMask=0077。 - 实时 ASR:固定 revision 的 BF16
Qwen/Qwen3-ASR-1.7B,Transformers 离线推理,单模型锁串行。 - 实时翻译:
qwen2.5-1.5b-instruct-q4_k_m.gguf,别名qwen2.5-1.5b-realtime-local,回环端口18081,十路槽位。 - 会后复核:非 reasoning 的
deepseek-llm-7b-chat-q5_k_m.gguf,别名deepseek-llm-7b-chat-q5-local,回环端口18083,两路槽位。 - 实时音频:VAD 协商 80 ms 帧,按来源 RMS 阈值分类,常规 800 ms 静音释放并保留 15 秒硬上限;超过 4 秒后,160 ms 低于来源阈值 75% 的停顿会触发软边界。
- 历史方案:固定 400 ms 静音 / 4 秒硬切已被真实回放拒绝,仅保留兼容测试;当前生产已切到自适应边界。
- 服务:单用户、单进程、可信运维边界;FastAPI 使用 Spark
0.0.0.0:8080直连映射,llama-server 和 MiMo 保持回环。
其他
故障排查
客户端提示后端不可用
先访问当前配置对应的 /health:App 内置本地运行时是 http://127.0.0.1:18080/health,仓库开发脚本通常使用 127.0.0.1:8000,远端则使用运维者明确配置的 origin 并带上 Bearer Key。不要只验证端口是否打开,还要核对健康契约中的模型、部署身份和 VAD 参数。
麦克风或会议音频没有活动
检查 macOS 麦克风和屏幕录制权限,确认本地签名证书没有变化,并完全退出后重新打开应用。持续零系统音频会在 5 秒后显示警告。
源字幕正常,但翻译延迟
这通常表示翻译队列饱和或提供方失败。源字幕不会被丢弃;查看类型化事件与后端日志,结束会议后系统会尝试补齐翻译。
提示“音频连接积压达到上限”
这表示双路采集速度持续高于服务端归档确认速度。先检查 Spark 隧道往返延迟和后端归档日志,
再查看客户端的 audio upload protocol configured、audio archive latency high
与 audio backlog stopping session 日志。不要只调大缓存阈值来延后失败。
会议结束后复盘失败
保留当前会议 ID 和逐字稿,在复盘界面对同一会议重试。检查结束屏障、待处理翻译和摘要提供方日志,不要新建会议掩盖原始失败。
其他
当前边界
以下能力仍是明确边界,不应从现有实现推断为已完成。
- 原生 macOS 客户端尚未完成分发签名、公证和正式发布自动化。
- Spark 的全新主机搭建、真实维护窗口回滚和断电恢复尚未完成实机验收。
- 后端当前是可信、单用户、单进程运行边界;远端 Bearer 认证已实现,但明文 HTTP、WebSocket Origin、按用户授权、数据保留/删除、备份恢复和全局容量控制仍未完成。
- 新的 80 ms 上传窗口已通过双路高延迟模拟,但真实 Spark 连接下至少 5 分钟的双路采集验收仍未完成。
- 实时说话人标签不是最终精确分离结果;会后复盘负责更完整的整理。