陌生推销电话里有这样一个瞬间:对方已经接起,正在说话,你在点头。但你心里一片空白。一个字也没听懂。

尤其是电话那头的英国人带着某种特别的口音,或者印度人说得又快又自信。这两种情况,都是我刚在迪拜做经纪人那几周的现实。

我是设计师。我会设计复杂系统,懂产品。但和陌生口音的人通电话是另一项技能,不会因为你想要就自动出现。

本可以只管练习、慢慢等。我决定做一个工具。

什么是 CallCoach

一个用 Swift 编写、结合 Apple Intelligence 和 Live Translation 的原生 iOS 应用。

通话时,它实时识别对方的语音,将文字直接显示在屏幕上。分析对方说了什么,提供提示——怎么回答、如何接着聊、该问什么。

在 Xcode 中运行模拟器和测试的 CallCoach 应用

这不是为了转写而转写,而是一位话术助手,恰好在需要时出现——在真实对话中,毫无延迟。

为什么选择 Swift 和 Apple Intelligence

我本可以用任何 Web 技术栈。但 Web 无法提供所需的速度,以及与麦克风、系统 API 的原生集成。

Apple Intelligence 是直接在设备上运行的本地语言引擎。无需请求云端,没有延迟,没有对话数据泄露。一切都在手机上。

Live Translation 能以所需精度处理带口音的英语。正适合迪拜这样的多国籍城市。

在 Codex 里开发应用——五小时 token 窗口用完了,现在等下一个

它如何改变了工作

当你看见对方所说的话的文字,焦虑就消退了。不再把精力花在“他说了什么”,而是开始想“我该怎么回答”。

从求生模式,进入对话模式。根本性的转变。

我还在继续开发。下一步是针对不同场景建立话术库:首次通话、异议、澄清预算、约定见面。提示不只是转写,而是针对具体话语给出智能的上下文回应。

我为什么讲这件事

CallCoach 是我思考产品方式的一个例子。

不是“试什么技术”,而是“解决什么问题”。问题具体而切身。解决方案原生、快速,没有多余的依赖。

会写代码的设计师并不等于开发者。但有时正是这一点,让想法不至于死在等待实现的队列里。

技术实现

从技术角度,我把 CallCoach 当作真正的工作工具来做,而不是概念稿。

首先,我和 ChatGPT 一起梳理想法与架构:明确产品任务、用户场景,为 Codex 准备一份很长的初始提示词,让它创建基础 Swift 项目,配置依赖,搭出第一个可运行的应用骨架。

Codex 的 token 窗口用完后,我把项目交给 Claude Code。接下来开发中真正的“硬菜”都在那边完成,使用 Opus 4.7 模型。

它尤其擅长长线工程:思考很多,消耗很多 token,但能很好地守住架构,认真编写彼此衔接的代码,帮助把应用打磨到可用。

之后我开始定期切到 Xcode:构建、跑测试、捕捉崩溃,把错误交回 Claude Code,一起修复问题。有些修改直接在 Xcode 里完成,我也在那里接入 Codex 和 Claude,作为随处可用的助手。

应用逻辑如何组织

应用围绕设备上的本地语音处理构建。

普通模式下,CallCoach 可以通过麦克风监听音频流,显示实时转写。但最关键的技术决定是分离双方声音。

主要问题是,使用扬声器通话时,麦克风会同时听到你和对方。结果转写混入双方话语,无法再有效用于辅导。

为了解决这个问题,我做了两种模式。

模式 1——System Audio

这是主要的推荐模式。

应用通过 ScreenCaptureKit 捕获系统音频输出,也就是只捕获送往扬声器的声音。这样就能只转写对方的声音,不会录入我通过麦克风发出的声音。

此外还使用标志 excludesCurrentProcessAudio = true,以排除应用自身的声音。

首次启动时,macOS 会请求屏幕录制权限——没有这个权限,就无法捕获系统音频。

模式 2——Microphone + Push-to-Mute

这是备用方案。

它使用普通麦克风采集,但我自己说话时,可以按住按钮,暂时停止向转写器发送音频缓冲区。

逻辑很简单:我说话时按住按钮,说完松开,应用重新开始听对方。

代码里实现了什么

从架构上,应用由几个独立管理器组成,每个负责处理流水线的一部分。

AudioCaptureManager

负责通过以下组件采集音频: AVAudioEngine.

  • 使用 installTap 作用于 inputNode
  • startRecording() 返回 AsyncStream<AVAudioPCMBuffer>
  • 缓冲区流继续向下传递,不会遇到以下问题: 跨 actor 传递(actor crossing)
  • 同时通过 RMS 和 dB 计量计算信号电平
  • UI 更新被发送到 @MainActor 通过 Task

SystemAudioCaptureManager

这是我为捕获系统音频单独添加的类,通过 SCStream.

正是它让转写只接收扬声器里对方的声音,而不是周围全部的声学噪声。

TranscriptionManager

这一层接收音频缓冲区流,并将其传给 SpeechAnalyzer ,来自 Speech 框架。

随后遍历 AsyncThrowingStream<SpeechTranscriptionResult>,区分临时与最终转写,并显式管理切换到 @MainActor.

也就是说,已经不只是“识别声音”,而是把流式转写稳妥地整合进实时界面。

CoachingEngine

这是一个独立逻辑层,将普通转写变成有用的对话工具。

  • processNewTranscript(_:) 使用 1.5 秒防抖
  • 上一个 Task 在收到新的语音片段时取消
  • LanguageModelSession 每次会话只创建一次,之后复用
  • 保留上下文历史
  • 解析器按以下格式解析回答: [类型]:文本 ,回退到 .phrase
  • 通过关键词确定通话阶段
  • 向模型发送最后约 300 个单词,而非整份转写

我还额外加入了迪拜市场的上下文: Emaar, DAMAC, Binghatti, ROI、预订、见面、Zoom,以及其他对话中常见的信号。

ContentView

所有部分在这里汇合成用户界面。

  • .task 在启动时请求麦克风权限
  • .onChange(segments.count) 触发 coach.processNewTranscript
  • toggleSession() 将流和格式传给 startTranscription
  • 各管理器的错误通过独立的以下处理器显示: .onChange

正是这一层让整个应用有了实时运作的感觉:切换音源、开始和停止会话、静音逻辑、错误处理,以及实时更新提示。

我如何训练提示

我还额外给系统补充了实际业务上下文。

针对陌生推销电话,我使用真实销售话术:开场问候、保持对话、初步筛选客户、澄清预算、处理疑虑,最后约定见面或一次深入的 Zoom 沟通。

这些并非网上抽象的模板,而是 SPI Dubai 中介公司教给我们的实际逻辑。

因此,提示变得更实用。不是简单地“礼貌地回点什么”,而是按正确的结构、在正确的阶段继续对话。

第二个场景——产品设计师面试

同时,我为同一种方法做了第二个使用方向:参加产品设计师面试。

这里我用自己的项目、案例和作品集训练系统,让它帮助的不是陌生推销,而是另一类对话——需要快速、自信、条理清晰地回答关于自身经验的问题。

因此,CallCoach 实质上已经不只是语音转文字应用,而是一个通用对话助手,可以适配具体的交流场景。

真实的开发过程是什么样的

坦白说,对我来说,这也是一个重要例子,说明今天真实的产品开发是什么样。

想法源于自己的痛点。

借助语言模型快速搭建架构。

与智能体合作编写复杂代码。

最后的打磨在 Xcode 里亲手完成——构建、崩溃、调试、反复迭代。

不是“神经网络的魔法”,而是正常的工程流程,只是被新工具加速了。


更新日志 / CallCoach 是如何做出来的

1. 明确想法

  • 确认了具体问题:打陌生推销电话时,面对英国和印度口音,有些话靠耳朵根本来不及识别。
  • 明确产品任务:不只是转写对话,还要帮助继续聊下去。
  • 与 ChatGPT 一起梳理概念、用户流程和未来应用的结构。
  • 为 Codex 生成一份很长的初始提示词,快速搭起 Swift 项目骨架。

2. 项目初次搭建

  • Codex 创建了基础 Swift 项目。
  • 配置了主要依赖。
  • 准备了基础架构。
  • 搭出了第一个可运行原型。

3. 主要工程实现

  • Codex 的 token 窗口耗尽后,项目转交给 Claude Code。
  • 主要开发继续使用 Opus 4.7。
  • 通过 Claude Code 完成应用的核心部分:音频采集、转写、辅导逻辑、与界面的连接。

4. 解决核心产品问题——分离声音

  • 发现关键问题:麦克风同时听到用户和对方。
  • 实现了两种模式:
    • System Audio ——仅捕获系统音频输出,使用 ScreenCaptureKit
    • Microphone + Push-to-Mute ——备用模式,手动暂停向转写器发送缓冲区
  • 已添加 excludesCurrentProcessAudio = true,以排除应用自身的声音。

5. 已实现的组件

  • SystemAudioCaptureManager.swift ——新增的系统音频采集类,通过 SCStream
  • AudioCaptureManager.swift ——音频采集,通过 AVAudioEngine,已添加 isMuted
  • TranscriptionManager ——流式转写,通过 SpeechAnalyzer
  • CoachingEngine ——防抖、复用模型会话、解析短语、通话阶段逻辑
  • ContentView.swift ——会话管理、音源选择、错误、按钮与界面
  • Info.plist ——系统捕获权限
  • project.pbxproj ——接入 ScreenCaptureKit.framework

6. 调试与测试

  • 通过 Xcode 构建和运行。
  • 每次应用崩溃,都把错误交回 Claude Code。
  • 迭代修复:Xcode → 崩溃/错误 → Claude Code → 修复 → 新构建。
  • 部分小改动直接在接入 Codex 和 Claude 的 Xcode 中完成。

7. 针对真实场景训练

  • 针对陌生推销,在系统中加入真实销售话术:
    • 问候
    • 普通寒暄
    • 客户资格筛选
    • 了解兴趣
    • 约定见面或 Zoom
  • 话术以 SPI Dubai 的实际工作与培训为基础。
  • 同时创建第二个使用场景:辅助产品设计师岗位面试。
  • 为此,用我的项目、案例和作品集训练模型。

8. 最终成果

  • 本地转写,不向云端发送数据
  • 对话过程中实时提供提示
  • 销售与面试各有独立逻辑
  • 原生技术栈,无需 Web 补丁
  • 用 ChatGPT + Codex + Claude Code + Xcode 协同做出的可用产品