最近我刷到 Nomily 这个开源项目时,第一反应是“这不就是我一直想要的东西吗”。它把 Apple Watch 的录音能力和 AI 总结捏在了一条链路上,而且用了 BYOK(Bring Your Own Key)模式:你带上自己的大模型 API Key,录音音频在本机完成转写与提炼,不依赖任何第三方订阅平台。整条链路打通之后,我的使用习惯被彻底改掉了——现在开会、通勤、遛狗时想到什么,抬一下手腕说几句,过几分钟手机上就会多一条结构化笔记,而不是躺在录音列表里再也不会打开的回放文件。
这篇文章我会把这个项目的设计思路完整拆开,从 wearable 端的录音实现,到传输机制、转写选型、提示词工程,再到部署和排坑,讲清楚每个环节为什么这么设计、有什么可替换方案、实操时要注意什么。适合三类人:Apple Watch 用户想搭一套高定制度 AI 录音流,watchOS/开源开发者想学习完整链路设计,以及单纯对“随身 AI Agent”工作流感兴趣的人。
1. 项目拆解:Apple Watch 到 AI 总结的完整链路
1.1 这个项目解决的核心痛点
先聊痛点。手机录音功能其实已经非常成熟,但“打开录音 App → 解锁屏幕 → 找到录制按钮 → 点击 → 开始讲话 → 之后再打开回放整理重点”,这个流程里每一步都在消耗耐心。真正常用录音的人是少数,不是因为不想记,而是入口太重。Apple Watch 的出现本来是想解决“抬手即得”的问题,但系统自带的录音机只能录,不能转写更不能总结;第三方 App 不是订阅制就是闭源,数据还得经过别人的服务器。
Nomily 解决的正是这个断层:把“采集”放到最自然的入口(手腕),把“理解”交给最合适的新工具(大模型),中间通过苹果自家的 WatchConnectivity 传输,整体形成一条从“随口说”到“结构化总结”的完整闭环。它不是一个花哨功能堆砌的 App,而是把每一环都做干净,再用开源方式把选择权交给使用者。
1.2 完整数据链路与各环节职责
把整个链路拆开来看,一共是五六个接力环节,每个环节都有独立的职责和技术选型:
- 录音采集:Apple Watch 通过系统录音能力捕获麦克风输入,生成 m4a/aac 编码的音频文件。
- 本地暂存:watchOS 端 App 沙盒内保存录音文件,同时记录时间戳、时长、触发来源等元数据。
- 跨设备传输:通过 WatchConnectivity 框架,把录音文件从 Apple Watch 可靠地传给已配对的 iPhone。
- 语音转写:iPhone 端调用 ASR 引擎(云端或本地),把音频转成带标点的文本。
- 模型总结:把转写文本送入 LLM(用你的 BYOK Key),生成结构化总结、待办提取、要点归纳。
- 结果落盘:保存 Markdown 格式的总结文本,并保留原始音频与转写文本作为可回溯资料。
这个链路里最容易翻车的地方是第 3 步和第 4 步的衔接:watchOS 的文件传输不像消息发送那么快,转写引擎又有自己的格式和上下文要求。项目在这两环都做了不少工程化处理,后面我会单独展开。
1.3 为什么是 BYOK 模式
BYOK 是整项目里我最认可的设计决策。传统做法是开发者自己接入大模型、封装成付费订阅,用户的录音要经过平台服务器再转回,数据流动链路长、隐私风险高、价格还贵。BYOK 直接把“模型能力”和“应用逻辑”解耦:应用只负责采集、调度、展示,模型能力由用户自己的 API Key 直连厂商接口提供。
带来的好处非常实际。第一,没有订阅费用,按量付费,用得少花得少,每天几条录音一个月下来可能就几杯奶茶钱,比年费订阅划算不少。第二,数据路径短,音频和文本直接从 iPhone 发到模型服务商,不经过任何第三方中转服务器,隐私边界清晰可解释。第三,模型可替换,只要兼容 OpenAI 接口,你可以随时把默认模型换成更便宜或者更懂特定领域的模型,这是闭源产品给不了的自由。当然代价也明显:使用者得自己申请 API Key、管理配额,需要一点动手门槛,这个项目本来就是给愿意折腾的人准备的。
2. Apple Watch 端:录音能力与传输机制
2.1 watchOS 录音的工程基础
在手腕上做录音,第一件事是理解 watchOS 的音频能力边界。我最早以为只需要把 iPhone 那套 AVAudioRecorder 代码搬过去就行,实际跑起来发现差别很大。
核心实现通常是用 AVFoundation 的 AVAudioRecorder 或更底层的 AudioQueue,配置一个 AVAudioSession 处理录音会话。这里有几个必选项:
- 在 Info.plist 里声明 NSMicrophoneUsageDescription,否则首次录音会直接崩溃或静默失败。
- 配置音频会话类别为 playAndRecord,默认端口选听筒或扬声器,避免录音音量低不可闻。
- 录音格式建议用 AAC-LC 编码的 .m4a,兼容性最好,也利于后续传输和转写。
watchOS 端的硬件和系统策略比 iPhone 激进得多:屏幕会频繁休眠,App 不进后台太久就会被挂起,音频会话也可能被其他声音打断。我第一次真机测试时,录到一半屏幕熄灭,再点亮发现录音已经停了——这就是没有处理好 session 激活时机导致的。可行的做法是录音开始时用 beginReceivingRemoteControlEvents 这类接口保持会话活跃,同时把录音状态写入 UserDefaults,App 重新激活时可以自动恢复或提示用户。
参数选择上,对语音识别来说 16kHz 采样率已经够用,不需要用 44.1kHz 的高保真规格。我实际对比过 Whisper API 对 16kHz 单声道 AAC 和 48kHz 立体声的识别结果,差距微乎其微,但文件体积差了好几倍,传输速度也天差地别。所以这个项目默认配置走的是 16kHz、单声道、AAC-LC,每分钟音频体积大概在 300KB 到 500KB 量级,一小时也只有二十兆左右,对一次几秒到几分钟的灵感录音来说毫无压力。
2.2 音频文件怎么传输到 iPhone
录音完成后,文件要高效、可靠地传到 iPhone。watchOS 和 iOS 之间的通信基本都得走 WatchConnectivity 框架,它有几种传输类型,针对这个场景需要分清主次:
- transferFile:适合传输完整文件,系统会把文件放入后台传输队列,支持 WiFi 和蓝牙,可靠性好。录音结束应该优先走这条路。
- sendMessage:适合传递小指令,比如“开始处理第几条录音”“请求新的转写”,响应快,但大文件不适合。
- transferUserInfo:适合同步少量元数据,比如录音时长、时间戳,比 sendMessage 更省资源。
工程实现上,Watch 端 App 在 init 里创建 WCSession 并调用 activate(),然后实现 delegate 的回调。发送侧调用 transferFile(:metadata:),iOS 端在 session(:didReceive:metadata:) 回调里拿到临时文件 URL,立刻拷贝到自己的 Documents 目录。
提示:WatchConnectivity 的 didReceive 回调给的是临时目录,必须第一时间 copy 到自己的沙盒目录,否则文件随时会被系统清理。
另一个实际经验是传输时机。watchOS 设备转瞬间进入低功耗模式后,系统会暂停后台传输,如果你录音完立刻锁屏,文件可能要等到下次点亮屏幕才传过去。最稳的策略是录音结束时保持 Watch App 在前台几秒,立即发起 transferFile,这样基本都能秒传。要是在户外没有手机在旁,文件就会留在 Watch 本地,等项目下次打开时再补传,所以本地缓存与清理策略也要配套。
2.3 录音侧的参数选择与资源控制
再补充几个属于“细节决定成败”的录音参数控制点。
首先是时长管理。Apple Watch 不是为连续几小时录音设计的,长时间录音会导致机身发热、电池健康下降,系统也会在资源紧张时杀掉录音进程。我在测试中会把单条录音限制在 10 到 15 分钟以内,超过就自动分段。分段还有个好处:转写引擎处理短音频的准确率和稳定性都更高,音频太长容易触发云端服务的上下文截断,长会议录音拆成几段后逐段转写,再合并总结,效果反而更好。
其次是麦克风方向问题。Watch 的麦克风在抬腕姿势下自然朝向说话人,这个设计很聪明,但你如果把手垂下来走路时说话,收音效果会大打折扣。项目没法改变物理拾音特性,但可以在转写阶段对特别低音量或高噪声的录音做预处理。我给 iPhone 端加了一个可选的“自动增益补偿”步骤,用简单波形分析把过低的音频振幅拉高,亲测对 whisper 识别率有明显帮助。
最后是磁盘清理。Watch 端存储宝贵,录完并确认 iPhone 收到后,本地文件就应该主动删除;iPhone 端则保留原始音频和最终总结,转写中间文本可以选择 7 天自动清理。这样整条链路既不会撑爆小容量 Watch,也保留了可回溯的原始素材。
3. iPhone 端:转写、总结与落盘
3.1 语音转写引擎的选择
转写是录音到总结的“翻译官”,这一步的质量直接决定后面 AI 总结的天花板。这个项目在实际选型中主要比较过两条路线:本地 ASR 和云端 ASR。
本地方案以 whisper.cpp、whisperkit 或 faster-whisper 为代表,模型放在 iPhone 上跑。优点非常突出:完全离线、隐私最好、转写成本为零,适合处理敏感内容或飞行模式场景。缺点也同样明显:对中文支持相比云端模型偏弱,时延高,十几分钟音频可能要等上一阵,而且连续转写会让机身发热、掉电明显。适合偶尔录几条的轻度用户。
云端方案以 OpenAI Whisper API 这类按分钟计费的服务为代表,识别精度高、中文标点完整、响应快,而且不用在手机上维护模型文件。代价是按量付费,目前 Whisper API 大致是每分钟 0.006 美元的量级,对每天几条灵感录音来说成本可以忽略,但对重度会议记录用户就得算一笔账。
我个人的建议是:项目保持这两种后端可切换的设计,日常默认走云端,信噪比高、要求严格的场景切本地。实现上用协议抽象一个 Transcriber 接口,云端和本地各实现一份,配置里填一个后端选择字段就够。代码结构不复杂,但换来的是很实用的灵活性。
3.2 AI 总结环节的提示词设计
转写文本出来后,调用大模型做总结是最有意思、也最需要调优的部分。很多人以为把文字丢给模型就行,实际效果往往很泛。Nomily 的做法是把提示词分场景管理。
如果是轻量灵感类,比如“明天下午三点和晓明对一下项目排期”,模型应该输出简洁的待办条目,不需要长篇分析。如果是会议录音,则要输出“主题 + 关键决策 + 待办事项 + 责任人与时间点”的结构。我给项目里预设了两套提示词模板,用户也可以在设置页自己改:
系统提示词示例:
你是一个高效的个人助理。用户提供了一段语音转写文本,请提取其中的主题、关键信息、待办事项和明确的时间节点。要求: 1. 只输出用户确实提到的内容,不要补充任何不存在的信息; 2. 不确定的信息用[不确定]标注; 3. 最终以Markdown列表输出,控制在300字以内。这里有两个关键参数需要调:temperature 要低一点,0.2 到 0.3 比较合适,避免模型发散;max_tokens 也要设,防止长文本总结被硬截断在中间。还有一个细节是“不要编造”这条声明,几乎每次实测都能减少幻觉。用过 LLM 的都知道,模型在信息不全时特别容易“脑补”,声明越明确,输出越老实。
对超长音频,我建议先分段总结再合并,而不是一次性把所有转写文本都怼进上下文。分段总结保留每段核心句,合并阶段再把若干段“摘要句子”汇总,这样既绕开上下文窗口限制,又能把核心观点最大化保留,效果比硬塞完整文本好得多。
3.3 输出格式与本地存储
总结完成后,怎么落盘也是个没有标准答案的设计点。项目选择生成标准 Markdown 文件,目录结构类似:
Documents/ recordings/ 原始音频 .m4a transcripts/ 转写文本 .txt summaries/ 最终总结 .md文件命名用“日期_时间_主题关键词”的方式,比如 2025-06-11_0930_项目排期.md。这样在文件 App、备忘录或任何支持 Markdown 的工具里都能直接检索和二次编辑。
除了落盘,iPhone 端还应该提供“分享”和“继续追问”两个入口。分享可以直接把总结发到备忘录、飞书、邮件;继续追问是很多 AI 笔记产品容易忽略但价值极高的功能:用户对刚生成的总结有疑问,比如“这里面提到的最晚交付时间到底是哪天”,可以直接对这条录音的上下文进行追加提问,而不是重新跑一遍完整总结。从使用体验来说,这两个功能让一条静态笔记活了起来,我实际使用中几乎每天都会用到追问功能。
4. BYOK 的成本、安全与隐私设计
4.1 API Key 的保存与保护
BYOK 模式绕不开 API Key 的安全问题。作为开源项目,代码都公开了,Key 如果写死在代码里等于把信用卡贴在门口。正确的做法要分两层:构建期和运行期。
构建期通过配置文件注入,仓库里只放 config.example.plist,包含键名和示例值,真实 Key 由使用者本地填写。同时把包含真实配置的文件加入 .gitignore,避免误提交。运行期最稳的保存位置是 iOS 的 Keychain,而不是 UserDefaults。Keychain 里的数据经过系统级加密,App 删除后仍可能保留(适合重装快速恢复),读取时要用 Security 框架的 SecItemCopyMatching。设置页放一个输入框 + 显隐开关就够了,不建议做联网校验,因为校验调用本身就会让 Key 多一次暴露。
我见过太多 Open Source 项目因为 Key 泄露被刷爆账单的案例。所以项目里还做了一层防护:默认关闭“自动转录新录音”开关,必须手动确认开启;每次调用前弹出“本次将消耗约多少算力费用”的估算提示。这样即使 Key 被误用,用户能尽早发现。
提示:任何时候都不要把真实 API Key 提交进 Git 历史。已经提交过的不是删文件就能解决,必须去服务商后台轮换 Key。
4.2 成本估算与额度控制
既然 BYOK 意味着用户自己掏模型费用,成本感知就必须做清楚。这里给一个很粗略的量级参考:每天录 5 条、每条 1 分钟的灵感录音,转写费用大约每天 0.03 美元量级,加上总结输出的 token 费用,一个月通常不会超过 3 到 5 美元。重度用户如果每天录 1 小时会议,成本会明显上升,就值得考虑本地模型或改用更经济的供应商。
控制成本我建议做三件事。第一,在模型供应商后台设置月度预算上限和告警阈值,这是兜底。第二,在 App 设置里提供“处理策略”:可以只处理最近 7 天的录音、可以对录音时长超过 15 分钟的分段转写、跳过草稿。第三,模型选择上下功夫:总结任务用便宜的小模型(比如 gpt-4o-mini 这类轻量级)完全够用,转写走专用的 ASR 服务,各司其职,比所有环节都用同一个高规格模型省钱得多。项目配置里把“转写模型”和“总结模型”分开设置,也是出于这个考虑。
4.3 数据隐私边界
隐私是这个项目最不需要怀疑的部分。因为 BYOK 的天然架构优势,录音和文本从你的 iPhone 直接发送给你指定的模型服务商,不经过项目开发者或任何第三方中转服务器。项目没有账号体系、没有云同步、没有遥测埋点,代码开源可审计,这比绝大多数商业录音软件要干净。
需要提醒的是,即使直连模型厂商,数据也并非“不存在”。主流模型服务商默认会对 API 请求做短期留存用于排障和模型改进,如果你处理的是机密信息,需要到服务商控制台关闭数据留存或开启合规模式;更稳妥的做法是直接切换本地模型,一切数据不出本机。Nomily 把这种选择权放在用户手里,本身就是 BYOK 模式的精髓。
5. 开源项目部署与二次开发
5.1 项目结构与核心代码模块
一个这种规模的开源项目,仓库结构通常值得仔细看,因为它代表作者的架构取舍。Nomily 大致分为三个模块:
- watchos/:录音入口、会话管理、文件传输、本地缓存。
- ios/:接收文件、ASR 调度、提示词管理、LLM 调用、结果展示与分享。
- shared/:数据结构定义、配置模型、通用常量,两端共同引用。
核心文件职责可以用一张表说明:
| 文件/模块 | 职责 |
|---|---|
| WatchRecorder.swift | 配置 AVAudioSession、AVAudioRecorder,启停录音 |
| WatchTransferService.swift | 封装 WCSession,transferFile 与元数据同步 |
| FileReceiver.swift | iOS 端接收音频并完成沙盒拷贝 |
| Transcriber.swift | 定义 ASR 后端协议,云端/本地实现 |
| PromptBuilder.swift | 按场景组装提示词模板 |
| Summarizer.swift | 调用 LLM,处理重试与分段总结 |
| SettingsView.swift | BYOK Key 配置、模型选择、预算开关 |
如果想改造成自己的项目,不要动 shared 层,优先在 ios 端扩展功能即可,两端依赖关系简单清晰。
5.2 从源码到真机的完整部署步骤
把一个双端开源项目跑起来,比单纯跑 iOS Demo 要多几步。我按实际顺序整理一遍:
第一步,克隆仓库,用 Xcode 打开 ios 工程(watchos 工程一般会作为 target 一起管理)。确认电脑上安装的 Xcode 版本满足项目要求,太老的版本编译 SwiftUI 双端工程会报错。
第二步,修改 Bundle Identifier 为你的唯一前缀,并在 Signing 面板选择 Team。注意 Watch App 的 Bundle ID 需要与 iPhone App 形成关联,一般格式是 iPhone 的 Bundle ID 后加 .watchkitapp,这个不要自己乱改,Xcode 在创建 target 时已经配好,你只需要同步修改前缀。
第三步,准备配置文件。复制 config.example.plist 为 config.plist,填入你的模型 API Key 相关占位或运行期提示文案。记住不要提交这个文件。
第四步,选择 iPhone 作为部署目标,Xcode 会自动尝试把 Watch App 安装到已配对的 Apple Watch 上。确保 Watch 解锁、亮屏,且和 iPhone 保持在蓝牙或同一 WiFi 范围内。第一次配对安装需要输入 Apple Watch 密码,等待进度条走完就好。
第五步,在 iPhone 上打开 App,进入设置填入 API Key(如果配置走的是运行期输入)。这时去 Watch 端打开录音界面,第一次录会弹麦克风权限请求,一定选允许,否则后面所有录音都会静默失败。录一段、传一首、看总结是否生成,跑通一次完整流程后,再按自己的使用习惯调整提示词和模型。
这套流程对第一次搞双端开发的初学者来说,最大的门槛反而是各种小环境问题。比如开发者证书过期、Watch 系统版本比 iOS 旧导致 WatchConnectivity 接口缺失、iPhone 与 Watch 登录不同 Apple ID 导致配对异常。遇到问题先看控制台日志,多数情况输出都会直接指向原因。
5.3 可扩展方向
这个项目最让我喜欢的地方是留白够大,几乎就是给 AI 录音工作流搭了一个基础框架,任何方向都能继续长。
我自己已经在计划中的扩展方向包括:结果自动同步到备忘录或 Notion,把总结文本通过 Share Sheet 或 URL Scheme 推给第三方笔记工具,顺手还能把链接转成稍后读清单;录音触发源从“Watch 录音键”扩展到 AirPods 双击唤起,让入口更自然;把 iPhone 端的本地模型换成 ollama 支持的接口,用家里那台闲置机器当转写服务器。如果你对“口头待办自动变成任务清单”感兴趣,也可以在这个基础上对接待办事项的标准接口。
这些都是想清楚数据链路后可以很自然延伸出去的功能。开源项目的价值也正在于此:你不是拿它当终点,而是拿它当起点。
6. 常见问题与排查技巧实录
6.1 录音侧问题
我在测试阶段遇到最多的是“录了一段但回放没声音”。排查顺序是这样:第一步看 Watch 端是否真的给了麦克风权限,设置里没开的话直接授权;第二步看 AVAudioSession 的配置,如果 category 设置成 playback 而不是 playAndRecord,录音会无声甚至失败;第三步看录音文件属性,用文件 App 导出后用播放器确认编码格式是否为 m4a/AAC,如果录成了特殊格式,播放器和 ASR 都不认识。
还有一个比较隐蔽的问题:Apple Watch 抬腕时如果检测到手腕上没有佩戴,部分系统版本会暂停录音会话。测试时建议按真实佩戴姿态验证,而不是把表摘下来对着麦克风吹气。
6.2 传输与同步问题
iPhone 收不到 Watch 文件,九成原因是 WCSession 没有正确激活或 delegate 没有实现。检查这几个点:
- iPhone 端和 Watch 端都要创建 WCSession 并调用 activate,两端缺一不可。
- iOS 端要在 AppDelegate 或 SwiftUI 生命周期里尽早完成激活,回调里先拷贝临时文件再处理别的。
- 文件大、传输慢时可以选择让 iPhone 和 Watch 同时处于同一个 WiFi 网络,速度会快很多。
如果发现只有部分文件能收到,看看 Watch 端是否因为低功耗模式挂起了传输。此时不要频繁重发,否则系统会堆积大量重复请求。正确做法是在 Watch 端增加“待传文件”列表,等 Watch 唤醒后再批量补传,而且每次只传未完成的那部分。
6.3 转写与总结质量问题
转写乱码或者识别不准,第一排查对象是音频采样率。如果录音被配置成了 8kHz 或更低,很多 ASR 引擎的预处理会直接失效,英文还行,中文标点和口语词基本全崩。第二是环境噪声,户外风声、咖啡馆背景音都会显著拉低识别率。第三是方言,我拿普通话模型直接转粤语,效果惨不忍睹,但换成支持多语言的 Whisper 大模型后明显改善。
总结太啰嗦或者信息缺失,一般不是模型笨,而是提示词没给到约束。把字数上限写死、把输出格式明确列出、把“只提取用户提到的内容”写进 system prompt,三个小改动通常能立竿见影。至于模型偶尔编造时间或人名,我的做法是提示词里加一条“不确定的信息用[不确定]标注”,不要试图让模型猜。实测这条声明能明显减少“一本正经地胡说八道”。
6.4 费用与额度异常问题
账单比预期高,先看是不是有两个环节都在计费并且重复运行。比如转写和总结各调用一次没问题,但如果你把“分段转写后的文本”又整体送进同一个模型做第二次总结,费用就会叠加。这类重复调用在日志里都能看到,建议开一个“调用记录”页面,每次请求消耗的分钟数/token 数都列出来,一眼就能定位异常。
Key 泄露是最坏的情况。一旦发现账单异常或代码仓库疑似暴露了 Key,第一时间到服务商控制台吊销并重新生成,同时开启用量告警。开源项目使用者要养成一个习惯:任何 Key 都不要提交到 Git 历史,已经提交过的要轮换而不是只删文件,因为 Git 历史删不干净。
最后分享几条我实际用了三周后觉得最值的小经验:第一,录的时候把关键信息用短句说清楚,说成“明天下午三点和张三开会评审排期”,AI 总结的信息密度会高很多,意识流长句反而容易被提炼掉重点;第二,每天录完睡前瞄一眼总结,随手分个类,一周后就是很好的周报素材;第三,周末批量回顾总结文件时,你会发现那些“随口说”的想法,才是最能反映你真实想法的东西,单凭这一点,这套链路就很值得搭。