Skip to content

Repository files navigation

ChineseASR

ChineseASR 是一个本地优先的中文语音转文字项目,目标是把中文录音转成可审计、低幻觉、可复现的文本。它面向 Windows + CUDA 工作站,默认 quick 使用 SenseVoiceSmall,strict 使用 Qwen3-ASR-1.7B + SenseVoiceSmall。FunASR 官方 GPU flagship Fun-ASR-Nano-2512 已作为显式 fun-asr-nano profile 提供,但不会因为安装完成而改变 quick 默认;可选的 FireRedASR2-LLM 是证据级词汇主引擎,也不会自动取代默认 strict 组合。

项目同时提供录音转写和 Windows 桌面语音输入。本文是使用入口;项目执行规则见 AGENTS.md,机器状态以 PCConfig 和 Windows 现场为准。

Win+H / Ctrl+Win+H 语音输入

主界面是 160×60 实际像素的白绿小胶囊:麦克风控制录音,旁边的小箭头展开设备菜单,×收起。先点输入框,再按 Win+HCtrl+Win+H:显示小窗并录音,再按则隐藏并暂停。

主屏与 VDD 各显示一个同步小窗,共用一次录音和识别;任一边暂停、收起或选择麦克风都会同步。屏幕接回或分辨率改变后自动更新位置。configs/dictation.yamlpanel_monitor_ids 用硬件型号 ID 选择目标屏,未接入的目标不显示;设为 null 则仅跟随 Windows 当前主屏。

麦克风按钮单击录音、再单击暂停,不改变窗口显示;拖动空白处可移动小窗。点小箭头或右键麦克风可选择、刷新设备或复制最近文字。DJI 是默认选择,设备和长说明不占主界面;悬停可看状态,错误以小点和提示保留。

设备菜单使用随 Windows DPI 缩放的字号,外框尺寸固定。插入新设备后点“刷新麦克风”即可列出;所选设备会在下次录音时重新枚举,不会自行切换。

× 隐藏并暂停,尾句继续完成,后台仍待命;Esc 取消尚未输入的部分,已输入文字保留。只有托盘“退出”或脚本 Stop 才退出程序并释放模型,文字不会自动按回车发送。

听写使用现有 Qwen3-ASR-1.7B,不做双模型复核或自动润色。模型登录后在后台加载到内存,录音期间使用 GPU;暂停或收起后保留内存中的模型并释放显存及 LocalGpuBroker 租约,下一次唤出不重复冷加载。真正退出时直接释放模型,界面先收起,资源清理不挡住窗口。登录后不会自行打开麦克风。

绿色麦克风表示已经开始采集声音;模型首次加载可以与录音并行,声音先留在内存,模型准备好后再识别。目前在约 600 ms 的语音停顿、最长 20 秒或手动暂停时提交整段结果,尚无逐字流式输出。轻量 WebRTC VAD 过滤无有效语音的输入,保留句内音频和前后缓冲;检测未通过或识别为空时不输入文字。默认术语提示留空,避免不清晰音频触发提示词复读。

.\scripts\dictation.ps1 -Mode Install  # 安装听写依赖、登录自启并立即启动
.\scripts\dictation.ps1 -Mode Status
.\scripts\dictation.ps1 -Mode Stop
.\scripts\dictation.ps1 -Mode Start
.\scripts\dictation.ps1 -Mode Uninstall  # 移除登录自启,保留项目和模型

运行时接管这两组快捷键,支持本机键盘及远程控制发送的按键;退出或从托盘暂停快捷键后恢复系统行为。键盘监听与界面处理分开,麦克风初始化时仍能接收按键。麦克风和技术词拼写在 configs/dictation.yaml 设置;input_device: null 表示跟随 Windows 默认麦克风。指定设备未连接时会提示连接,不切换到其他麦克风。焦点改变时停止自动输入,托盘“复制最近文本”可手动取回完整识别结果。管理员窗口可能拒绝普通权限程序输入,需手动复制。

录音和识别结果不保存为历史文件;outputs/dictation/runtime.log 只记录运行错误、耗时和字数。麦克风选择保存在本机 outputs/dictation/preferences.json,不修改 Windows 默认设备。公开短音频只能检查软件集成及速度,个人口音、麦克风与实际体验仍以本人试用为准。

更新原则:AI 可以自主更新兼容依赖;没有证据证明新模型明显更好时维持当前模型,有可靠的中文效果、速度和兼容证据后再新增或替换。不会因为“发布了新版本”就自动换模,也未设置定时下载服务。

录音转写优先解决三件事:

  • 中文准确性优先:strict 模式默认以 Qwen3-ASR-1.7B 为主引擎,SenseVoiceSmall 为对照锚点。
  • 低幻觉和可复核:双模型分歧、静音出字、模板废话、异常重复、繁体残留、超长无标点等都会进入 audit / metrics / review。
  • 适合 AI Agent 调用scripts\asr-smart.ps1 通过本地 API 提交任务,快速返回 job 状态,避免长时间卡住命令行或上层 Agent。

它是本地优先而不是云转写服务:默认路径不会上传音频。只有调用独立的云入口、明确标注本次是重要录音或已授权的存疑转写质量复核、并授权本次云上传时,才会把本机切片发送给阿里云百炼;模型、输出、wheelhouse 和私人评测数据仍保留在本机。

当前状态

personal-use v0.1 已完成收尾,进入维护态。关闭标准是:

  1. scripts\doctor.ps1 能确认无代理、CUDA、模型配置和依赖状态。
  2. 单元测试全通过。
  3. scripts\smoke-asr-smart.ps1 -Json 能完成默认 strict smart job;重要录音另用 scripts\smoke-evidence-asr.ps1 -Audio <path> -Json 验收 FireRed + Qwen 完整证据链。
  4. 公开仓库只包含源码、脚本、配置、测试和文档,不包含模型权重、用户音频、生成转写、模型收据或 wheelhouse 大文件。

后续真实录音 benchmark、模型组合微调、Ollama 仲裁启用、VAD 边界切片优化都属于使用阶段校准,不是当前版本的关闭阻塞项。

适合场景

  • 微信语音、会议录音、口述笔记、中文播客、课程录音的本地转写。
  • 对“幻觉低、能复查证据”要求高于“只要快”的中文 ASR。
  • 需要让 Codex、脚本或其他本地 Agent 调用 ASR,又不希望命令行长时间阻塞。
  • 需要保留 raw JSON、audit、metrics、manifest,方便以后追溯模型版本和输入 hash。

不适合场景

  • 不愿意本地安装模型权重和 Python 环境。
  • 希望普通录音或整个文件夹自动上传云端转写;云入口只接受明确的重要录音,或已授权且当前选定的存疑本地转写质量复核,两种用途都须显式授权本次上传。
  • 需要英文、多语种或字幕生产工具链作为主目标。

默认模型策略

模型注册在 configs\models.yaml,实现和模型选择解耦。

用途 默认引擎 说明
strict 主引擎 qwen3-asr-1.7b 准确率优先的中文主转写线,基于 Qwen3-ASR 权重和 qwen-asr runtime
strict 对照引擎 sensevoice 快速中文声学锚点,用于发现分歧和疑似幻觉
quick sensevoice 单模型快速转写
显式 GPU flagship fun-asr-nano FunAudioLLM/Fun-ASR-Nano-2512;需要 GPU,作为较重的 LLM-ASR 候选,不改变 quick 默认
可选证据级词汇主引擎 fireredasr2-llm 隔离在 WSL 中运行;仅在显式选择时作为 strict 主引擎
重要录音 / 存疑质量复核云入口 qwen-audio-3.0-asr-flash 独立脚本显式选择 -Important-QualityReview 并授权本次上传;Key 经 Password Center SecretRef 注入,普通模式无法触发
显式时间线/匿名说话人 baseline paraformer 固定 speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch@v2.0.4,输出逐句 sentence_info 时间和 CAM++ 匿名聚类;已知两方通话的调用方可传 --preset-spk-num 2,省略时自动聚类;不改变 quick/strict 默认
fallback/comparison whisper-large-v3 已注册为备用/对照,不作为中文 strict 默认路径

strict 模式即使一路模型失败,也会保留可用输出并生成审计包。正文会标记 [疑似]strict.audit.md 会记录失败引擎、异常摘要和复核理由。两路都失败时输出 [听不清]

一分钟使用

在仓库根目录打开 PowerShell:

cd <repo-root>
.\scripts\doctor.ps1
.\scripts\asr-smart.ps1 -Audio C:\path\to\audio.wav -Mode strict -WaitSec 15 -Json

推荐日常入口是 asr-smart.ps1,因为它会走本地 API/job 队列,不会把调用方长时间卡死。

常用模式:

# 严格双模型,推荐默认
.\scripts\asr-smart.ps1 -Audio C:\path\to\audio.wav -Mode strict -WaitSec 15 -Json

# 长音频,自适应引擎上限切片 + 断点续跑
.\scripts\asr-smart.ps1 -Audio C:\path\to\long.wav -Mode long-strict -WaitSec 15 -Json

# 重要录音的证据级组合;不会改变默认配置
.\scripts\asr-smart.ps1 -Audio C:\path\to\long.mp3 -Mode long-strict -PrimaryEngine fireredasr2-llm -SecondaryEngine qwen3-asr-1.7b -WaitSec 15 -Json

# 明确的重要/专业录音才允许使用最强云候选;两个开关缺一即在上传前阻断
.\scripts\asr-professional-cloud.ps1 -Audio C:\path\to\important.wav -Important -CloudUploadAuthorized -Json

# 已授权、当前选定的普通存疑本地转写可作质量复核;不会被标成重要录音
.\scripts\asr-professional-cloud.ps1 -Audio C:\path\to\uncertain.wav -QualityReview -CloudUploadAuthorized -Json

# 快速单模型,只在明确接受较少审计时使用
.\scripts\asr-smart.ps1 -Audio C:\path\to\audio.wav -Mode quick -WaitSec 15 -Json

# 显式使用 FunASR GPU flagship;不会改变 quick 默认
.\scripts\asr-smart.ps1 -Audio C:\path\to\audio.wav -Mode quick -Engine fun-asr-nano -Device cuda:0 -WaitSec 15 -Json

# 批量转写文件夹
.\scripts\transcribe-folder.ps1 -InputDir C:\path\to\audio-folder

已有转写的哈希回读

需要让媒体检索复用已经存在的转写和时间段时,可以按原音频 SHA-256 只读回读 owner(归属方)管理的本地任务快照和已保留云结果:

.\.venv\Scripts\python.exe -B -m zh_asr transcript-readback `
  --audio-sha256 <64-hex-source-sha256> `
  --jobs-snapshot .\outputs\api\jobs.json

该命令只读取 jobs.json 和其中明确引用的结果文件,以及 outputs\cloud-jobs 中已完成、哈希绑定的云结果;不读取原音频,不启动服务或模型,不创建数据库、缓存或新结果。 它同时核对 job.request.audio_sha256、objective sidecar(目标结果旁路文件)的 audio.raw_sha256idempotency_basis.source_audio_sha256,确认处理已完成,并按 sidecar 声明的路径、字节数和 SHA-256 核对实际解析的 raw artifact(原始结果文件)字节。

机器结果的顶层字段为 schemastatussource_audio_sha256segmentsartifactqualitycoverageevidence_statuslookup_scope。有可用时间段时,status=ok,每个 segments 项至少包含 start_msend_mstextspeakertimestamp_granularityraw_path;raw 结果明确提供的细粒度 timestamp 数组只有在有效且位于该片段内时才保留。qualitycoverageevidence_status 只做保守投影,不把 coverage.status=complete 自动改成完整覆盖。

没有保留任务、结果完整性校验失败或匹配转写没有有效时间段时,命令返回 status=not_foundstatus=gap, 并在 gap.code 给出原因,不猜测时间。仍在处理的任务会返回其 ID,应继续该任务而非重复提交。重复源哈希按时间段可用性、证据、质量、覆盖和段数确定性取优,任务新旧 本身不被当作质量证明。Paraformer 的 sentence_info 是当前明确支持的逐句时间和匿名说话人结构;其他引擎只有 在 raw 结构明确提供有效时间段时才会被回读。云结果只在 schema、用途、成功状态、原音哈希和每块范围都一致时可回读;其范围的 timestamp_granularity=chunk,不是句级时间戳,quality.status=unknown,且云端成功不构成逐字准确性或说话人归属证明。

固定端到端 smoke:

.\scripts\smoke-asr-smart.ps1 -Json

重要录音的完整证据链验收:

.\scripts\smoke-evidence-asr.ps1 -Audio C:\path\to\important.mp3 -Json

重要录音与存疑质量复核云入口

scripts\asr-professional-cloud.ps1 是唯一的云上传入口,当前 worker 固定调用阿里云百炼 qwen-audio-3.0-asr-flash 同步接口。它与 quickstrictlong-strict 隔离,普通调用、 批量文件夹和仅因录音较长都不会触发云端。阿里云当前对非实时长文件/说话人分离推荐 qwen-audio-3.0-asr-flash-filetrans,但该接口要求公网可访问的文件 URL;本项目坚持本地音频边界, 因此尚未把它接入本地 worker。现有入口会在本机切片后调用同步模型。

入口在创建任务和读取音频前要求一个且仅一个用途开关,以及上传授权:

  1. -Important:当前录音已被明确归类为重要或专业录音。请求仍写入 importance=important,回执仍为 important_only=true
  2. -QualityReview:仅用于已授权、当前选定的普通存疑本地转写质量复核。请求写入独立的 purpose=quality_review,没有 importance 字段,回执为 important_only=false
  3. -CloudUploadAuthorized:调用方确认这次可以把音频切片发送给阿里云百炼;
  4. Password Center 的受管目标 qwen-audio3-asr-important-once 完整性验证通过,并且其固定 worker 哈希与当前项目 worker 一致。

两个用途开关不能同时使用。普通质量复核不会自动启动 FireRed、Qwen 或其它本地双引擎;只有结果与上下文仍有影响理解的分歧时,才按实际需要回核原音和本地结果。重要证据录音仍适用下文的本地证据链与人工核听要求。

API Key 只由 Secret Broker 注入固定、哈希绑定的子进程环境,不进入命令行、请求文件、 转写结果或模型上下文。音频先在本机转为 16 kHz 单声道 WAV,再按最多 180 秒切片; 每段使用 HTTPS Base64 同步接口,结果保存到被 Git 忽略的 outputs\cloud-jobs。云调用失败会 明确返回失败原因,不会静默冒充本地结果。运行时重绑缺失时不上传,重绑后最多重试一次;网络、 限流、超时或供应商 5xx 也只建议有界重试一次。其它云失败不盲重试,明确建议改用本地 asr-smart,并保持云结果与本地证据链分开。对于法律、投诉、雇佣等证据录音,云结果是能力优先的 专业候选,同时仍应运行 FireRed + Qwen 本地证据链并人工核听,不能把云转写本身当作证据认证。

安装与模型下载

先安装 CUDA 版 PyTorch 和核心依赖:

.\scripts\install-torch-cu128-direct.ps1
.\scripts\setup-core.ps1

核心依赖文件 requirements-core.txt 固定 funasr==1.4.14。Fun-ASR-Nano 是面向 GPU 的较重模型,先完成 CUDA/PyTorch 与核心依赖安装,再按需下载;不需要 Nano 的机器无需额外安装模型。

下载 quick / secondary 默认需要的 SenseVoice:

.\scripts\download-models.ps1 -Engine sensevoice

按需下载官方 GPU flagship Fun-ASR-Nano-2512

.\scripts\download-models.ps1 -Engine fun-asr-nano

该命令只准备显式 profile 的模型,不会把它设为默认 quick 引擎;profile 固定 ModelScope revision 05201c46f1c38592b1567f857c0d56eab3d0d8ef 并启用官方 trust_remote_code 加载路径。没有可用 CUDA GPU 时继续使用 sensevoice

strict 主线需要 Qwen ASR runtime 和权重:

.\scripts\setup-qwen.ps1
.\scripts\download-models.ps1 -Engine qwen3-asr-1.7b

Qwen runtime 固定为 qwen-asr==0.0.6,模型固定为 revision a04930dbe5419bfee073f7cade734f572689a3a8。下载脚本会生成并验证 MODEL_RECEIPT.json,逐项绑定 13 个必要文件的规范路径、大小和 SHA-256;已有固定 缓存可用 -ReceiptOnly 只生成/核验收据,不下载也不加载模型。runtime 版本、revision、 收据或任一权重文件漂移时,adapter 会在模型 loader 运行前 fail-closed。

可选的 FireRedASR2-LLM 使用独立 WSL runtime,安装顺序如下:

.\scripts\setup-firered.ps1
.\scripts\download-models.ps1 -Engine fireredasr2-llm

默认 WSL 虚拟环境是 /opt/chineseasr/firered/.venv。源码和权重分别放在 Git 忽略的 models/firered/FireRedASR2Smodels/firered/FireRedASR2-LLM

  • 源码固定为 commit 4e7d9aaf4482a47cec1724807026b9b151926eb5
  • 模型固定为 revision 2c5e0f415b9afb8f67cb8b00ea4c54959f70e824
  • 下载完成后生成 MODEL_RECEIPT.json,记录 14 项必要权重文件的规范顺序、路径、大小和 SHA-256。运行时逐项校验 receipt、固定 revision、源码 HEAD 和干净工作树,任一不一致都会拒绝装载。

FireRed 输入契约是 16 kHz、16-bit、mono PCM WAV;MP3 等输入由前端经 ffmpeg 生成派生 WAV。单输入硬上限为 40 秒,长音频的推荐有效切片为 35 秒。

启用 use_half: true 且使用 CUDA 时,隔离 worker 会让 Qwen 基座首次装载即使用 BF16(GPU 不支持 BF16 时使用 FP16),避免官方实现先完整物化 FP32 权重、随后才转半精度造成的内存峰值。临时装载桥在成功或异常后都会恢复官方绑定,固定源码 checkout 不会被改写;raw 结果会记录 llm_initial_load_dtype

FireRed 的 CPU 装载峰值仍高于普通 ASR。本机 64GB Windows + RTX 5090 D 的已验证 WSL 配置是:

[wsl2]
memory=32GB
swap=8GB

[experimental]
autoMemoryReclaim=gradual

修改 %UserProfile%\.wslconfig 后需在没有重要 WSL/Docker 任务时执行 wsl --shutdown 再重新启动。隔离 worker 会在大权重哈希和装载前同时检查配置容量与当前可用容量,并在无法读取 Linux CUDA 内存信息时 fail-closed。半精度装载的最低配置门槛为 28 GiB RAM、34 GiB RAM+swap,启动当下还需至少 18 GiB MemAvailable、22 GiB MemAvailable+SwapFree;不满足时会区分“配置不足”和“当前占用过高”并给出可操作错误。这里的 32GB/8GB 是本机验证值,不是对所有硬件的统一承诺。

常规模型下载与 FireRed runtime setup 会清理代理环境;FireRed 的 Hugging Face 权重下载保留调用进程当前代理设置,以适应实际联网环境。常规模型缓存位于 models\modelscope;这些模型目录都不进入 Git。

输出文件

strict 模式会把最终稿和证据拆开:

文件 用途
*.strict.md 给人看的最终转写正文,尽量保持干净
*.strict.audit.md 模型文本、相似度、分歧、flags、候选文本和判断依据
*.strict.audit.json 机器可读审计报告
*.strict.review.json 面向机器和人工复核的结构化投影
*.strict.receipt.json 绑定本次全部严格产物的路径、大小、SHA-256、语义声明与 bundle SHA
*.qwen3-asr-1.7b.raw.json 主模型原始输出
*.sensevoice.raw.json 对照模型原始输出
*.fireredasr2-llm.raw.json 显式选择 FireRed 主引擎时的原始输出

长音频和评测流程还会写:

文件 用途
manifest.json 输入 hash、模型配置 hash、chunk 参数、chunk 状态
metrics.json 耗时、相似度、风险标记、模型和运行时信息
review.md 最值得人工复核的片段队列,按 P0/P1/P2 排序
benchmark.md / benchmark.json 和 truth 文本对齐后的评测结果

不要把 raw JSON 当最终稿。常规读取顺序是:先看 outputs.final*.strict.md,再看 audit.mdaudit_jsonmetrics.jsonreview.md

按需说话人归属

默认转写不是声纹识别:模型给出的 speaker 编号只是匿名分段/聚类,不能直接等同“本人”。只有真实问题需要判断一句话是谁说的时,才对已有的、带起止时间的转写 JSON做一次小投影。普通 attribute-speakers 不读原始音频、不加载模型;结果只下发逐句文本、匿名说话人、状态、角色、单句中文依据和回到原转写的 JSON pointer:

python -m zh_asr attribute-speakers C:\private\call.raw.json `
  --context C:\private\call.speaker-context.json `
  --out C:\private\call.speaker-attribution.json

私有 person:self 声纹锚

在本机私有边界内,可以显式保存唯一一个用户本人的 person:self CAM++ profile。它不是通用声纹库:没有第二数据库、服务、队列、他人 profile 或全库重跑。单参考兼容 profile 保存在 Git 忽略的 outputs\private(也可显式指定另一条私有路径),包含参考路径/哈希/片段/声道和一个可撤销向量。跨录音域 profile 只接收恰好 2–3 条不同源参考,将每条向量 L2 归一化后求质心并再次归一化;持久化文件只有一个质心、来源哈希/大小、精确片段与可回查选择绑定,不保存源路径、原音或各参考向量。

只对“本人候选”有清晰、无反证依据的有限片段建锚。每次必须写出一条 --inference-basis:profile 固定为可撤销的 inferred,不能冒充 confirmed,可以用更强或更新的参考显式替换。

python -m zh_asr speaker-enroll C:\private\known-self.wav `
  --start-ms 0 --end-ms 12000 --channel mix `
  --inference-basis "该片段有可回查的本人候选依据,且可由更强参考替换。" `
  --profile C:\private\person-self.voice-profile.json --device cpu

需要兼顾应用双声道与普通通话时,可用一个私有 manifest 指定 2–3 条来源天然明确、互不重复的有限参考;manifest 本身不进入 Git,profile 中也不保留其中的源路径。每条必须绑定现有证据 JSON 的 SHA-256 和 JSON pointer,不能只写“我觉得是本人”:

python -m zh_asr speaker-enroll `
  --references C:\private\person-self.reference-set.json `
  --profile C:\private\person-self.voice-profile.json --replace --device cpu

之后只在需要的一个目标片段上生成证据。目标向量只在内存中计算并丢弃;输出仅留下原始目标哈希、精确片段时间、声道提取方式、模型/文件哈希、profile 哈希和相似度/阈值:

python -m zh_asr speaker-evidence C:\private\call.m4a `
  --start-ms 18400 --end-ms 23100 --channel mix `
  --profile C:\private\person-self.voice-profile.json `
  --out C:\private\call.18400-23100.person-self-evidence.json `
  --require-held-out --device cpu

--require-held-out 会在目标原件曾参与当前 profile 时失败关闭,避免把 enrollment 片段回测成“验证”。模型固定阈值不会因质心或单次样本被调到刚好通过;相似度仍须与声道、联系人、对话角色和内容融合。

即使调用方没有要求 held-out,evidence 也会按源哈希标记 enrollment_sourceheld_out_source。单参考兼容 profile 与跨来源质心 profile 的同原件分数都只留作本地审计;归属投影会把它降为 unknown 声学线索,防止录音设备、声道或背景底色泄漏成身份判断。

显式替换或删除也只作用于这一份 person:self profile:

python -m zh_asr speaker-enroll C:\private\new-known-self.wav `
  --start-ms 0 --end-ms 12000 `
  --inference-basis "新的可回查本人候选依据;替换旧锚。" `
  --profile C:\private\person-self.voice-profile.json --replace
python -m zh_asr speaker-profile-delete `
  --profile C:\private\person-self.voice-profile.json --confirm-delete person:self

--channel mix 会明确记录为 mixed_not_channel_evidenceleft/right 只在原始输入确为双声道时由新入口精确提取;默认 quick/strict/long 的单声道准备产物不能倒推为左右声道证据。即使是小米录音,也只有同时满足已验证 cohort、原始右声道精确提取、源文件 SHA-256 和分段时间都匹配时,才有一个可撤销的“本人候选”声道线索。

把一份或多份上述证据传给投影时,context.recording_audio.sha256 必须绑定同一原始音频。CLI 会把转写 JSON、context、每份 voice evidence 的实际文件 SHA-256,以及该原始音频 SHA-256 写进顶层 input_binding;库调用没有文件时使用同一 JSON 的 canonical SHA-256:

{
  "schema": "chinese-asr.speaker-attribution-context.v2",
  "recording_kind": "mono_call",
  "recording_audio": {
    "sha256": "<原始音频 SHA-256>"
  },
  "segment_evidence": [
    {
      "index": 0,
      "dialogue_role": {
        "candidate_role": "self",
        "reason": "该句在快递员询问后回答了本人持有物的故障。"
      }
    }
  ]
}
python -m zh_asr attribute-speakers C:\private\call.raw.json `
  --context C:\private\call.speaker-context.json `
  --voice-evidence C:\private\call.18400-23100.person-self-evidence.json `
  --out C:\private\call.speaker-attribution.json

contact_roledialogue_rolesemantic_rolecross_recording_role 与来源上下文都是软线索,可单独形成可撤销 inferred,也会相互融合。当前项目没有独立可信 receipt adapter,因此即使 source_identity 带有 authority_ref 也只能形成 inferred,不能由 caller 自报升为 confirmed。输出逐段只保留匿名 speaker、结论、单句中文依据和原转写 JSON pointer;不会下发声纹分数、逐项内部线索或目标 embedding。

  • CAM++ 相似度、联系人、声道、对话角色、句义和跨录音相同声音都不能单独 confirmed;相似度本身只是 person:self 的正/负候选线索。
  • attribute-speakers 只让与当前唯一私有 person:self profile 哈希一致的声纹分数参与方向判断;profile 被删除或替换后,旧 evidence 自动失效并保留为 unknown 声纹线索,独立的声道、联系人、对话角色和句义依据不受影响。
  • 归因器不做固定加权、合成分数或 high/medium/low 等级。单一清晰且无反证的线索、或多项同向线索,都可以直接产生可撤销 inferred;声纹/声道只组织注意力,不是低智力终裁。
  • 若声纹或声道与有具体理由的来源、联系人、对话或句义判断相反,投影会在内部保留两边证据,并在对外单句依据中说明为何后者暂时压过前者;只有上下文判断本身冲突、或只剩无法解释的相反声学线索时,才输出 unknownspeaker_attribution_gap=true
  • recording_kind=mono_call 且目标来自 mix/mixed_not_channel_evidence 时,投影使用更保守的 ±0.04 声纹风险带;模型阈值仍固定为 0.31,不会拿单通电话重调模型。风险带内的分数只记为 unknown 声学线索,仍可由具体联系人、对话角色或句义依据作可撤销判断。其他录音类型继续使用模型证据的 ±0.02 常规歧义带。
  • 这只归属“这段语音可能是谁说的”,不证明照片、视频、微信媒体或消息由用户发送、拥有或持有;媒体来源/Owner 必须另有明确来源事实。
  • Paraformer 的可选 CAM++ diarization 仍只是匿名聚类,可能过拆/合并,不能替代 person:self enrollment,也不能把 cluster ID 解释成用户。

如果原转写没有可用时间戳,应只在真实问题需要时先补这一份录音的时间戳分句;不要批量把历史录音或每条消息转成“本人事件”。

顶层 job 的 succeeded 只表示流程产出了结果;机器消费者必须同时读取 evidence_status

  • verified:要求的双引擎链完整执行,且 final、audit、review、两路 raw 已通过收据哈希和语义交叉校验;不表示转写逐字准确,仍需看 status、分歧和人工复核项;
  • provisional:至少一路引擎失败,现有文本只是带失败证据的回退结果;
  • unavailable:证据链或必要产物不完整;
  • pending:仍在处理;
  • not_applicable:quick 单引擎任务不适用证据级双引擎状态。

显式使用 FireRed 时,若 FireRed 失败而对照引擎成功,流程仍会生成带 [疑似] 的回退文本,但 job、长音频 manifest 和对应 chunk 都会标为 provisional,并列出 evidence_failures。状态判定会重新验证收据覆盖的六项内容产物,核对路径、字节数、SHA-256、两路 raw 独立性、引擎身份、文本、执行状态、错误以及 final/audit/review 对 audit JSON 的投影;未同步重建收据的替换、损坏、缺失或语义错配会使 verified 降为 unavailable。该收据是自包含的一致性清单,不是数字签名或可信时间戳,不能单独证明外部真实性。证据级验收还必须确认每个 FireRed raw JSON 文本非空、error=null、运行时 dtype 正确,并且逐段审计不含 engine_failure;原始录音始终是权威来源,verified 不能替代人工核听。

空转写的客观结果

每个 quick/strict 结果都会旁写一个版本化的 *.objective-result.json(long-strict 为根目录的 objective-result.json,chunk 也各有一个)。它与旧 strict 两条 engine_evidence 和 receipt 分开,机器消费者必须读取 objective_outcome,不能把空字符串、空数组、零字节或 expect_empty 当作无语音:

  • speech_transcribed:至少有可观察转写文本;单引擎空文本会附带 quality_status=low_confidence,不能把双引擎链的 verified 当成准确性证明;
  • no_speech_detected:只在完整音频覆盖下取得规范 VAD 零区间,或完整有效 PCM 的全零负证据时使用,并绑定 raw SHA-256、处理器/配置/策略/request hash、区间和非空负证据 hash;
  • speech_detected_but_not_transcribable:检测到语音区间但文本为空,保持 deferred/unknown,可交给音频理解路线;
  • indeterminate:没有完整 VAD/负证据,或存在预处理、模型、子进程、格式、覆盖等不确定性。

sidecar 的正式正交字段是 execution.status ∈ {completed, failed, unsupported, corrupt}coverage.status ∈ {complete, partial, unknown}quality.status ∈ {sufficient, low_confidence, unknown};旧 *_status 名称只在 compatibility 中保留。media_kind 固定为 audio。调用方若有 caller-owned 绑定,可通过 API caller_binding 原样透传;服务到 CLI 子进程只经 ZH_ASR_CALLER_BINDING_JSON 环境变量传递,不进入命令行,ChineseASR 不解释或伪造这些治理字段。long-strict 允许配置的 chunk overlap,但必须从 0 连续覆盖到原音频时长、没有 gap/exclusion、首尾闭合,且所有 child 负证据、区间、raw 引用和幂等身份都绑定时才允许聚合为 no_speech_detected。旧 Markdown 或缺 sidecar 的缓存最多是未验证的历史产物,不得据此宣称无语音。

本地 API 与 Smart Wrapper

本地 API 只绑定 127.0.0.1

.\.venv\Scripts\python.exe -m zh_asr serve --host 127.0.0.1 --port 18666 --state-dir outputs\api

主要端点:

  • GET /health
  • GET /jobs
  • GET /jobs/{job_id}
  • GET /observer/jobs
  • GET /observer/jobs/{job_id}
  • POST /jobs/transcribe
  • POST /jobs/{job_id}/cancel

/observer/* 是只读安全投影,供本机统一观察台读取。它只返回任务状态、模式、逻辑模型名、时间、可用的长音频 chunk 计数与终态 RTF;ASR 不产生通用 LLM token 指标,因此 token 状态固定为 not_applicable。投影不返回音频/输出路径、PID、命令、stdout/stderr、识别正文、证据完整性状态或 GPU Broker 信息。证据消费者必须读取 /jobs/jobs/{job_id} 中的 evidence_statusevidence_failures,不能用 observer 投影替代证据验收。

asr-smart.ps1 会在需要时启动本地 API,提交 job,并在 WaitSec 内等待结果。如果任务仍在运行,它会返回 job id 和下一步查询命令,而不是无限等待。

API 会在 outputs\api\jobs.json 中以原子替换保存任务历史:服务重启不会自动重跑任务, 重启前仍处于 queuedrunning 的 job 会保留为可查询的 service_restarted 失败终态。 持久化快照最多保留最近 200 条终态记录,同时保留活动/排队任务;裁剪历史不会删除原有转写输出文件。 若任务历史落盘失败,GET /health 会报告 persistence.status=degradedready=false, 后续新任务会被拒绝,直到一次后续状态写入成功恢复;命令已完成但终态历史不可持久时, job 不报告为成功,且转写输出文件可能已经存在。

为了避免和受管 Ollama、LocalOCR 或其他 ChineseASR 任务抢 GPU,所有公开 CLI 和 smart/API 路径都必须先取得 LocalGpuBroker 租约;Broker 不可用时任务失败关闭,不会 退回到仅凭 nvidia-smi 判断后继续重型推理。

默认端口是 18666,刻意避开 LocalOCR 的 18665

RTX 5090D 32GB 显存较大时,这个 GPU 排他锁仍然是保守调度边界,不代表硬件不能并发。 旧客户端的 -AllowGpuConflicts / allow_gpu_conflicts=true 仍可被解析,但只影响没有 机器级 Broker 的嵌入式外部 CUDA 进程检测,不能绕过本机 LocalGpuBroker。正式 Broker 只协调已接入它的 Ollama、LocalOCR 与 ChineseASR,不声称管理 LM Studio 等未接入进程。

默认入口会向 http://127.0.0.1:32100/_gpu_broker/* 申请全机 GPU 租约。Broker 会在 ASR 启动前卸载空闲 Ollama/LocalOCR,并在 ASR 运行期间阻止新的 Ollama 或 OCR 重型推理。服务子进程启动时必须携带 opaque lease token 并向 Broker 验证当前 live owner,父进程随后持续续租;裸环境标记不能证明已持有租约。直接 CLI 同样采用“持租约 监督进程 → 可终止工作子进程”结构。 租约续期一旦失败,运行中的完整子进程树会被立即终止;服务任务以 gpu_broker_lost 失败,不能在失去排他性的情况下继续生成貌似成功的证据。

长音频与断点续跑

长音频入口:

.\scripts\asr-smart.ps1 -Audio C:\path\to\long.mp3 -Mode long-strict -WaitSec 15 -Json

ChunkSec 是请求值,不是无条件采用的固定值。实际 effective_chunk_sec 会取请求值与两路引擎能力上限中的最小值;选择 FireRed 时为 35 秒,并始终低于其 40 秒单输入硬上限。每个 chunk 都跑 strict 双引擎。

MP3 等非标准输入先统一为 16 kHz、16-bit、mono PCM WAV。schema 2 的 manifest.json 记录源文件与派生文件 SHA-256、转换 provenance、模型配置 hash、固定模型/runtime 收据、运行代码身份、请求/有效切片参数、已解析引擎和 chunk 状态。内容与运行身份共同形成 fingerprint。续跑时不会只相信 manifest 的缓存状态,而会重新验证每个 chunk 的收据和全部内容;一致时才跳过,残留 running、内容篡改或身份变化都会转为重跑。

待处理 chunk 按两路引擎最小 max_request_inputs 分成有界批。FireRed 配置为每批最多 16 个输入;每个批次中,每个引擎只加载一次,再处理该批所有 chunk,避免逐片重复加载模型。

strict audit v2 保留两路引擎的原始文本、raw JSON 引用、provenance、分歧和人工复核项。选择策略固定为保留主引擎证据:不做多数投票,也不做语义改写。 新生成的 strict bundle 使用包内相对引用,并由 receipt 绑定六项内容产物;完整目录复制 到归档盘后仍可复验。旧版绝对引用在原位置继续兼容,但不会伪装成可搬迁包。

LLM 仲裁

LLM 仲裁配置在 configs\models.yamlllm_arbitration,默认关闭。

当前设计是本地 Ollama evidence-only 仲裁:

  • 只读取 ASR audit 证据,不读取音频。
  • 只在 chunk 有 flagsneeds_review 或低相似度时触发。
  • 仲裁结果写入 merged audit / metrics。
  • 不覆盖 raw ASR JSON。
  • 默认 keep_alive=0,避免长期占用 GPU。

默认关闭是有意设计:基础转写链路必须在没有 Ollama、没有额外 GPU 驻留、没有 LLM 最终猜测时也能稳定工作。

评测与 Benchmark

生成隐私友好的本地合成/对抗评测集:

.\scripts\eval.ps1 -Generate -GenerateOnly

运行内置评测:

.\scripts\eval.ps1 -Generate -Force

用自己的音频和人工 truth 文本跑 benchmark:

.\scripts\benchmark.ps1 -AudioDir C:\path\to\audio -TruthDir C:\path\to\truth

benchmark 按文件 stem 匹配音频和 truth,写 _manifest\manifest.json,不会复制你的源音频或 truth 文件。真实私人录音 benchmark 是校准手段,不需要提交到公开仓库。

离线 Wheelhouse

冻结当前依赖:

.\scripts\export-lock.ps1

下载 wheelhouse:

.\scripts\build-wheelhouse.ps1
.\scripts\verify-wheelhouse.ps1

离线安装 smoke:

.\scripts\install-offline.ps1 -Venv .venv-offline-smoke

offline\wheelhouse\ 被 Git 忽略。小型 lock/checksum manifest 可以放在 offline\manifests\ 下追踪。

模型替换

同一 adapter 内换模型,优先只改 configs\models.yaml

  • defaults.engine:quick 默认引擎。
  • strict.primary_engine:strict 主引擎。
  • strict.secondary_engine:strict 对照引擎。
  • engines.*.adapter:运行时适配器,目前有 funasrqwen-asrfirered-worker
  • llm_arbitration:本地 Ollama 仲裁配置,默认关闭。

临时使用其他配置:

$env:ZH_ASR_MODEL_CONFIG='C:\path\to\models.yaml'

换模型后的基本验证:

.\scripts\download-models.ps1 -Engine <engine-name>
.\scripts\strict.ps1 -Audio C:\path\to\audio.wav
.\scripts\smoke-asr-smart.ps1 -Json

新增不同运行时,例如 Whisper 本地实现、其他 LLM 音频模型或云 API,应新增 adapter,并保持 pipeline 只依赖统一的 generate(...) 输出形状。

测试

单元测试不加载 ASR 模型:

.\.venv\Scripts\python.exe -m unittest discover -s tests

环境体检:

.\scripts\doctor.ps1

端到端 smoke:

.\scripts\smoke-asr-smart.ps1 -Json

公开发布前建议再跑:

git diff --check
git status --ignored=matching --short

另外按公开发布流程对 README.mddocssrctestsscriptsconfigs 做 source-only 秘密模式扫描,确认没有 token、私钥、完整 .env、OAuth JSON、原始日志或私人输出内容。

公开仓库边界

仓库追踪:

  • src\zh_asr\
  • scripts\
  • configs\models.yaml
  • tests\
  • docs\
  • offline\manifests\

仓库不追踪:

  • .venv\
  • models\
  • outputs\
  • eval\corpus\
  • offline\wheelhouse\
  • Python cache 和 build artifacts

这些目录可能包含模型权重、生成转写、私人音频路径、评测材料或大型 wheel 文件。

常见问题

为什么 strict 比 quick 慢?

strict 会跑两路模型并写审计文件,目标是低幻觉和可复核;quick 只跑单模型。

为什么 smart 返回 blocked

LocalGpuBroker 不可用、已有其他受管重型任务,或无法取得全机租约。等待现有任务结束并 检查 Broker 状态;-AllowGpuConflicts 不会绕过机器级 Broker。

为什么正文里有 [疑似][听不清]

这表示模型证据不足、两路分歧大、某一路失败或命中风险规则。正文不强装确定,细节看 strict.audit.mdstrict.audit.json

是否会自动使用 LLM 猜最终答案?

不会。Ollama 仲裁默认关闭,并且只读取 audit 证据。需要时在 configs\models.yaml 显式打开。

换更强模型要改代码吗?

通常不用。优先改 configs\models.yaml,只有新增运行时时才写 adapter。

更多文档

About

Local-first Chinese ASR pipeline with strict dual-model audit and smart local API.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages