大数跨境

一周时间,把「还得东拼西凑」改成「一条命令开聊」

一周时间,把「还得东拼西凑」改成「一条命令开聊」 dotNET跨平台
2026-10-31
4
上周发的那篇《有人用纯 C# 写了个语音识别引擎,README 比代码还好看》,底下有条评论我看了好几遍: > 感觉还不是开箱即用啊,还得东拼西凑。 这条评论说的是 FunAsrNet,但我看完脸上有点挂不住——因为我自己的 speech-to-speech-dotnet 也好不到哪去。 先交代一下背景。这个项目是把 huggingface/speech-to-speech 用 C# 做的行为等价重写:Silero VAD 切话、本地 STT 转写、GGUF 小模型生成、Qwen3-TTS 出声,整条语音智能体管线跑在自己机器上,不要 API key,对外讲 OpenAI Realtime 协议,写好的 Realtime 应用把地址指过来就能用。 管线是通的,协议覆盖 26/26,移植差异 75 条逐笔登记在账。但你要真跑起来,得先去 Hugging Face 上把五份模型权重一份一份找齐:VAD 一个仓库、Whisper 一个仓库、GGUF 一个仓库、TTS 二十几个文件、Smart Turn 又一个仓库。README 里我当年写的是「不下载,只指路」。
说得客气是指路,说得直接点,就是让读者自己东拼西凑。 那条评论之后我花了一周把这件事了结了。这篇文章记一下过程,也顺带说说改完之后是什么体验。 ## 先把「凑」这件事变成一张清单 模型来源分散是客观事实,消不掉,但可以不让用户感知到。 做法是在仓库里放一份钉死的清单 `tools/models/models.tsv`,每行四列:SHA-256、字节数、相对路径、带固定 commit 的下载地址。注意是固定 commit,运行时永远不解析 main 或 master——上游哪天换个权重,你的管线行为不会跟着漂移。 然后两个下载脚本,`download-models.ps1` 和 `download-models.sh`,干三件事:按清单下载、逐文件校验哈希和大小、断点续传。依赖只有 curl 和一个 sha256 工具,不需要 Python,不需要 Hugging Face CLI,不需要任何 key。Windows 上还修了个老坑:PowerShell 里 `curl` 默认是 `Invoke-WebRequest` 的别名,得显式解析 `curl.exe`,不然参数全是错的。 想省流量可以按组下: ```bash ./download-models.sh --groups vad,sense-voice ``` 这部分里跨平台下载器的基础是社区 PR #1(@vovilaljn)贡献的,致谢。 ## local 一条命令,但坑在 listen 之前 原来的 `local` 命令号称一条命令开聊,实际有个阴间行为:缺模型的时候服务照样起,客户端也照样连,就是永远收不到事件。看起来像客户端 bug,其实是服务端在连接构建期悄悄拒绝了。这笔账我在差异账本里登记为 E003,登记完就一直没修。 现在 `local` 在开始监听之前会先算一笔账:你选了哪些本地后端、每个后端需要哪组模型、哪些文件缺失或哈希不对,然后只拉这几组,校验全部通过才绑端口。自定义模型路径始终归用户自己管,缺了就在监听前明确报错,绝不静默拿默认值顶替。 另外一个细节:模型文件几个 GB,每次启动都重新哈希一遍不现实。校验通过的文件会把元数据记进 `models/.model-verification-cache.json`,下次启动对得上就直接跳过。这个缓存也是这轮加的。 ## Docker 不再要宿主机伺候 原来的 compose 文件有个我自己都不好意思的设计:LLM 指向 `host.docker.internal:8080`,也就是你得在宿主机上另外跑一个 llama.cpp 服务,容器才能干活。这叫什么容器化。 现在的形态:entrypoint 在启动服务前先跑下载器,五组默认模型(约 6.84 GB,全量清单 45 个文件 7.1 GB)落进 named volume,校验过了才 `exec` 启动 serve,下载失败容器直接非零退出,不起一个缺模型的服务。LLM 也改回容器内的本地 GGUF。首次启动等下载,之后重启复用卷,已验证的文件不重复拉。 ```docker docker compose up --build ``` 就这一条。 ## serve 补票,只差一行 上面这些做完后还剩一个逻辑漏洞:`local` 会准备模型,单独跑 `serve` 还是老样子。修法小得可笑,`Program.cs` 里一行: ```csharp if(line.Command is "local" or "serve") ``` 复用同一套准备流程,行为完全一致。改完记得把账本 E003 的口径翻过来——这种「代码改了、文档忘了」的事我自己抓到过别人好几次,这回轮到自己头上,README 的已知差异表、differences.jsonl、单元测试计数(现在 713 例)一起同步掉。 ## 顺手把默认 STT 换了 既然都在动模型清单,顺带解决另一个心病:默认的 whisper-base 识别中文只能说勉强。这轮把默认 STT 切成 SenseVoice(sherpa-onnx 的多语种导出,int8 约 228.5 MiB),中文质量好一截,Paraformer 也一并收进清单。想换回 Whisper 加一句 `--stt whisper` 就行。 这里多说一句许可:SenseVoice 的权重受 FunASR Model License 约束,不是 Apache-2.0,商用之前自己核对。清单里每个模型许可各不相同,下载不代表授权,README 里这句丑话我保留了。 ## 国内网络的最后一步 清单里 45 个文件,大头都在 Hugging Face 上。国内网络环境里「一键下载」能不能成立,取决于这一步。 做法是不改清单,加一个环境变量: ```bash S2S_MODEL_MIRROR=https://hf-mirror.com bash ./download-models.sh ``` 设了之后,所有 huggingface.co 的地址先走镜像,镜像拉不动自动回落源站,断点续传两边都保留,ModelScope 的地址不受影响。镜像地址完全是用户侧配置,清单本身的权威来源不变。 ## 改完之后 现在的上手路径是: ```bash git clone https://github.com/geffzhang/speech-to-speech-dotnet.git cd speech-to-speech-dotnet dotnet build SpeechToSpeech.slnx dotnet run --project src/SpeechToSpeech.Cli -- local ``` `local` 自己下模型、自己校验、自己起服务、自己开麦克风。第一条命令到开口说话之间,没有任何一步需要你去浏览器里找权重。 还没做到的也交代一下:NuGet 不打算发——这个项目是行为等价重写,定位不是对外包面,账本里记了;Qwen3-TTS 在 CPU 上是整句合成,被打断那一轮还是会合成完,这笔 D001 的账还挂着;真人麦克风的听感没系统测过。另外刚发现下载脚本的帮助里镜像说明打印了两遍,下轮修。 回到开头那条评论。「东拼西凑」这个事实没有消失——45 个文件、四个上游组织、两家托管平台,依然分散。区别只是这些分散现在由一张清单和两个脚本管着,用户不用知道了。 基础设施的价值就是这样:做好了,没人知道它存在。 感谢那位留言的读者。话糙,但指的方向是对的。 ## 相关链接 * 项目仓库:https://github.com/geffzhang/speech-to-speech-dotnet * 上游项目 huggingface/speech-to-speech:https://github.com/huggingface/speech-to-speech * 模型清单与校验值(45 个文件的固定 commit、SHA-256、字节数都在这):https://github.com/geffzhang/speech-to-speech-dotnet/blob/main/tools/models/models.tsv * 上篇文章《有人用纯 C# 写了个语音识别引擎,README 比代码还好看》(FunAsrNet,那条评论就在这下面):https://www.cnblogs.com/shanyou/p/23404953 * FunAsrNet 项目仓库:https://github.com/hnlyf168/FunAsrNet * 国内镜像 hf-mirror:https://hf-mirror.com * SenseVoice 许可(商用前务必核对):https://github.com/FunAudioLLM/SenseVoice/blob/main/LICENSE * 默认 SenseVoice 权重来源 sherpa-onnx 导出:https://huggingface.co/csukuangfj/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17 * Smart Turn 端点判定模型:https://huggingface.co/pipecat-ai/smart-turn-v3
【声明】内容源于网络
0
0
dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
内容 2382
粉丝 0
dotNET跨平台 专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
总阅读88.1k
粉丝0
内容2.4k