给豆包语音输入法加上专属热词,顺手修了一个自己埋的配置坑

AI 工作日志 作者头像野生码农 阅读 8 min
返回板块 GEO 源码全文 ✓
给豆包语音输入法加上专属热词,顺手修了一个自己埋的配置坑

2026-08-19 给豆包语音输入法加上专属热词,顺手修了一个自己埋的配置坑

背景与目标

豆包语音输入法(按住空格说话、松开出字的 Windows 小工具,走火山引擎 seedasr WebSocket 协议)用了一段时间,整体好用,但专属词汇识别不准。用户原话:

"有些时候识别准确率方面不是很准确,有些专属的词汇可能没那么准确。这个专属词汇功能能不能加上去?"

技术栈:Python + websockets + 火山引擎 SAUC 二进制协议 + PyInstaller 打包。

过程回顾

第一步:先查官方文档,不猜协议字段

翻火山引擎大模型流式语音识别 API 文档,确认 request.corpus.context 支持热词直传,格式是 JSON 字符串:

"corpus": {"context": "{\"hotwords\":[{\"word\":\"热词1\"},{\"word\":\"热词2\"}]}"}

关键信息:本项目用的流式输入模式(bigmodel_nostream)热词直传上限 5000 个词,完全够用,不用去控制台配词表。这一步避免了凭印象写字段名的风险。

第二步:最小侵入实现

  • asr_client.py:构造 full client request 时,有热词才加 corpus.context,没有就不加(保持原行为)
  • main.py:新增 load_hotwords(),合并 config.json 的 hotwords 数组和 hotwords.txt 文件(每行一词、# 注释、自动去重);托盘菜单加「重新加载热词」,改词表不用重启
  • 新建 hotwords.txt 模板,更新 config.example.json 和 README

自测三条全过:无热词不带 corpus 字段、有热词格式正确、数组合并文件去重正常。

第三步:打包与部署

PyInstaller 用现有 spec 重新打包(约 28MB),把用户编辑好的 hotwords.txt(119 个词,大量东阳本地地名——这类词正是通用 ASR 最容易错的)复制到 exe 同目录。

然后发现用户实际用的是桌面上的一份旧 exe 拷贝。用户自己提出了更优方案:

"还是我把 Dist 里面的 EXE 文件发送到桌面快捷方式?"

对,快捷方式指向 dist/ 里的 exe,热词文件固定在 dist/ 维护一份,以后重新打包桌面自动指向最新版。删旧拷贝、建快捷方式、验证 TargetPath,一次到位。

第四步:bug 来了——空格失效

用户反馈:

"好像有 bug,我在终端里面按空格键,无法触发语音输入了?"

排查过程很关键:先看日志,不猜。dist/voice_input.log 里一行直接破案:

已启动:按住【f10】说话,轻点仍为原按键。

根因:项目根目录 config.json 之前被改成 f10(适配 Linux),而打包 spec 会把这个配置内嵌进 exe。这次重新打包,就把 f10 带进了 Windows 版,空格自然失效。旧桌面 exe 内嵌的是老配置(space),所以之前一直正常。

中间还有个插曲:进程列表里看到两个同名进程,一度以为重复运行,后来确认是 PyInstaller onefile 的引导进程 + 主程序,正常现象。

用户随后拍板定方向:

"按 F10 的版本是适配 linux 版本的,你 Windows 版本另外区别开来。"

关键踩坑

  • 坑 1:内嵌配置导致"按键漂移"。现象:重新打包后空格不触发。根因:spec 里 datas = [('config.json', '.')] 把当前 config 打进 exe,而 config 不知何时被改成了 Linux 的 f10。解决:先用外挂 dist/config.json(外部配置优先)应急恢复 space,再做正式的双平台分离。
  • 坑 2:单配置服务两个平台。根因:Windows 要 space(按住说话 + 轻点穿透),Linux 要 f10(keyboard 库在 Linux 不支持拦截,只能切换模式)。一份配置必然打架。

最终方案

双平台配置分离:

  • config.json → Windows 专用,热键 space(也是 Windows exe 的内嵌默认)
  • config.linux.json → 新建,热键 f10,Linux 专用;已加入 .gitignore(含 API Key)
  • build_linux.sh → 打包前临时交换配置,trap EXIT 保证打完恢复:
restore_config() {
    if [ -f config.json.linuxbak ]; then mv -f config.json.linuxbak config.json; fi
}
trap restore_config EXIT
cp config.json config.json.linuxbak
cp config.linux.json config.json
pyinstaller --clean --noconfirm doubao-voice-input-linux.spec
  • 删掉应急用的 dist/config.json,恢复单一配置来源,重新打包验证:日志显示「已加载 119 个热词」+「按住【space】说话」

做得好的地方

  • 先查文档再动手:热词字段写法、5000 词上限都是从官方文档确认的,没靠猜
  • 排查先看日志:一行日志直接定位根因,全程没有瞎改代码
  • 采纳用户的方案:快捷方式替代 exe 拷贝是用户自己提的,比"复制新 exe 到桌面 + 桌面放热词文件"更干净,直接采纳
  • 热词热加载:托盘菜单「重新加载热词」让调词表的迭代成本降到零

复盘与改进

  • 打包即快照,内嵌配置是隐患。任何"打进去就看不见"的东西,改的时候不会有人记得它。这次如果一开始就把平台相关配置外置或分离,bug 不会发生。
  • 应急修复要留收尾动作。外挂 dist/config.json 当时是对的(不用等几分钟打包),但它本身又会成为下一个"配置漂移"源——事后必须删掉并重新打包闭环,这次做到了。
  • 跨平台项目的配置要显式分平台。config.linux.json + 构建脚本临时交换,比"记得打包前改回来"可靠得多。
  • 下次可以考虑:spec 里根据平台自动选配置文件,连交换脚本都省掉。

素材点

  • "一行日志破案":空格失效 → 日志显示 f10 → 内嵌配置串台,典型的"先看日志再动手"教学案例
  • PyInstaller 内嵌配置导致按键漂移:打包即快照,看不见的配置最危险
  • 119 个东阳本土地名热词:通用 ASR 的死角正是个人词表的价值点(村名、街道名识别)
  • 用户自己提出快捷方式方案:人机协作里"用户拍板、AI 执行"的好样本
  • onefile 双进程虚惊:两个同名进程不是重复运行,是引导进程 + 主程序
野生码农头像

关于作者 · 野生码农

把 AI 真放进生产环境的独立开发者。自研本地知识库、数字员工团队、软考备考系统、量化交易四套系统,全程公开复盘。

看他的平台账号 →
相关

继续阅读