# 记录问诊 · 本地 SenseVoice 转写接入 > 「记录问诊」录完整段录音后,在本机用 **SenseVoice**(经 **sherpa-mnn** 跑在 **MNN 后端**)离线转写成文字, > 再交本地 LLM 整理成问诊小结。全程本机、无网络。 > > 代码已全部就位且**默认编译通过**:未接入 sherpa-mnn 时 `SenseVoiceBridge` 走桩,问诊自动回退系统端侧识别(SFSpeech)。 > 本文档是把真实 SenseVoice 跑起来的「构建 + 设备验证」步骤。 ## 架构(按本项目模块边界 §3.1) ``` ConsultationSheet(UI) → 录音(ConsultationRecorder,m4a 落 Vault) → SenseVoiceASRService.transcribe(file) // 离线整段转写,无实时字幕 → AIRuntime.runExclusiveForASR { … } // 进推理闸门 + 卸常驻 LLM/VL 腾内存(防 OOM) → SenseVoiceBridge(ObjC++) // sherpa-mnn C-API → sherpa-mnn → MNN 后端(CPU/SME2) → DiaryAssistService.organizeConsultation(text) // 本地 LLM 整理成问诊小结 → 存为带「问诊」tag 的 DiaryEntry(录音挂 audio Asset) ``` - 引擎/模型未就绪 → 自动回退 SFSpeech;再不行 → 手动文字录入。任何一步都不卡死(红线 #5)。 - SenseVoice 是**非流式**:录音中只显示声纹动效,不显示实时字幕,结束后整段转写。 ## 一、构建 sherpa-mnn.xcframework ```sh MNN_SRC=/Users/xuhuayong/apps/MNN-src sh scripts/build-sherpa-mnn-xcframework.sh ``` - 复用 MNN 源码自带的 `apps/frameworks/sherpa-mnn/build-ios.sh`,产出 `Frameworks/sherpa-mnn.xcframework` (已含 device arm64 + simulator,libtool 合并好的静态库 + `Headers/`)。 - 若 sherpa cmake 报找不到 MNN:先 `sh scripts/build-mnn-xcframework.sh` 构建 MNN, 再 `export MNN_LIB_DIR=<含 libMNN.a + include 的目录>` 重跑。 - Apple Silicon 上若不需要 Intel 模拟器,可在 `build-ios.sh` 里去掉 `simulator_x86_64` 段加速。 ## 二、加进 Xcode 工程 1. 把 `Frameworks/sherpa-mnn.xcframework` 拖进 target → **Frameworks, Libraries, and Embedded Content**, 选 **Do Not Embed**(静态库)。 2. **Build Settings → Header Search Paths** 追加(recursive,Debug + Release): ``` $(PROJECT_DIR)/Frameworks/sherpa-mnn.xcframework/Headers ``` 这样 `SenseVoiceBridge.mm` 里的 `#if __has_include()` 命中,自动切到真实实现。 3. sherpa-mnn 用到 C++ 标准库,确保 target 的 **Other Linker Flags** 含 `-lc++`(通常已隐式链接)。 4. `MNN.xcframework` 仍需在工程里(sherpa-mnn 依赖它)。即便主 LLM 已切 Gemma-3n/MLX, **不要从工程移除 MNN.xcframework**,否则问诊转写退回 SFSpeech。 > 工程用 PBXFileSystemSynchronizedRootGroup:`SenseVoiceBridge.{h,mm}` 放在 `康康/AI/MNN/` 下已自动参与编译, > 桥接已在 `康康/康康-Bridging-Header.h` 暴露给 Swift,无需手改 pbxproj。 ## 三、转换并安装 SenseVoice 模型 ```sh MNN_SRC=/Users/xuhuayong/apps/MNN-src sh scripts/convert-sensevoice-mnn.sh ``` 产出 `build/SenseVoice/{model.mnn, tokens.txt}`(转的是 **fp32** `model.onnx`,8bit 权重量化;勿转 int8 版)。 安装到沙盒 `Application Support/Models/SenseVoice/`: - **模拟器**:拷到 `~/Library/Developer/CoreSimulator/Devices//data/Containers/Data/Application//Library/Application Support/Models/SenseVoice/` - **真机**:经「我的 · 模型管理」旁路导入,或用调试构建预拷进沙盒。 就位判定:`SenseVoiceASRService.isModelInstalled`(model.mnn + tokens.txt 都在)。 引擎+模型都就绪后 `SenseVoiceASRService.isAvailable == true`,问诊即走 SenseVoice。 ## 四、设备验证 1. 真机打开「记一笔 → 记录问诊 → 开始录音」,说几句(含数值/药名,验数字保真)。 2. 「结束并整理」后应看到「正在转写录音 · 本地 SenseVoice」,而非「本机识别」。 3. 转写稿交本地 LLM 整理成问诊小结,保存后在记录详情里能回放原声、看小结。 4. 故意不装模型 → 应自动回退「本机识别」(SFSpeech),功能仍可用。 ## 备注:与 Gemma-3n/MLX 主线的关系 - 转写(SenseVoice/MNN)与文本生成(Gemma-3n/MLX)互相独立:ASR 用 MNN 后端,LLM 整理用 MLX。 二者经 `AIRuntime` 闸门**串行**,不会同时常驻内存(§3.1)。 - 这是目前工程里**唯一**仍依赖 MNN 的路径。若决定彻底移除 MNN,问诊转写会退回 SFSpeech; 桥接的 `__has_include` 守卫保证那时仍能编译。 - 若希望改走 sherpa-onnx(onnxruntime,不依赖 MNN),只需把 `SenseVoiceBridge.mm` 里的 `SherpaMnn*` C-API 换成对应的 `SherpaOnnx*`(结构同名),其余 Swift 层不动。