expression-trainer 拆解:确定性词库打底,AI 只做判断层

fxy2311-youyou/expression-trainer,中文名叫"宇宙无敌表达训练系统"。一句话说清它是什么:一个跑在你电脑上的 Electron 应用,你对着麦克风说话,它把语音实时转成全屏大字,把你说的每个填充词(嗯、那个、然后)、每个犹豫词(可能、我觉得)标成不同颜色,每 50 字弹一条不超过 8 个字的教练提示,说完了再给你一份六个维度的表达分析报告。语音识别和词库分析完全离线,AI 反馈可选 DeepSeek、OpenAI、Ollama 或任何 OpenAI 兼容接口。

仓库 2026 年 7 月 6 日建,7 月 18 日最后一次推送,12 天 12 个 commit,然后停更;到现在 1309 star、234 fork——fork/star 比 17.9%,远高于"随手收藏"的水平,说明真有人在拿去改。MIT 协议,主语言 JavaScript,整个仓库只有两个依赖:Electron 和 sherpa-onnx-node。

一句话结论:如果你有当众讲话、面试、汇报的明确场景,想在说话的当下(而不是事后)看见自己的口癖,这东西值得装一次,成本几乎为零;它真正值得学的不是功能,是"确定性检测归词库、判断性反馈才给 AI"这个两层切法——以及它把 prompt 写成规则机的做法。 顺带,它也是"一个人 + AI 用 12 天写完一个能用的本地 AI 应用"的又一样本,和上周 Karpathy 的 HackerTube 恰好构成对照组:一个证明生产侧在塌缩,一个证明本地侧在塌缩。

本文提纲

  • 定位:它给谁用、你装上后看到什么
  • 两层架构:确定性归词库,判断性才给 AI
  • 语音识别层:streaming paraformer 的真实配置
  • 词库层:手写分词、密度指标和 146 个词的真相
  • AI 反馈层:8 字规则机 prompt 与六维报告
  • 怎么用:三步安装、模型下载、后端选择
  • 真正的创新点
  • 横向比较:跟三种练法差在哪
  • 该怀疑的地方
  • 成本

定位:它给谁用、你装上后看到什么

先排除一个误解:它不是又一个 AI 陪聊。它不跟你对话,只听你说——更像驾校的副驾教练:你开车,它实时指出你压线。目标用户很具体:有明确"说得更准"需求的人。面试前练自我介绍、汇报前练方案陈述、演讲前过稿子,甚至吵架前复盘自己的表达能力。

装上之后的主界面:黑底白字的全屏字幕区,你说的每个字实时滚出来;带颜色标记——按 README 的表,填充词是红色波浪下划线、犹豫词橙色、笼统词黄色虚线、有力表达绿色。左侧面板实时统计三类词的次数,右侧面板弹 AI 教练提示(3 秒消失)。说完点"结束",可以让 AI 生成完整报告,六个维度:逻辑、直接性、填充词、密度、词汇、亮点。

另外它有两个入口:README 里还挂了一个在线版 exprtrain.online,浏览器即开即用、支持中英双语——桌面版才是本仓库的全部内容,在线版是后续的衍生形态。

两层架构:确定性归词库,判断性才给 AI

整个项目最值得抄的设计决策只有一个,先把它讲透:它没有把所有分析都塞给大模型。

拆开音频进来之后的路径,检测被切成了性质完全不同的两层:

MERMAID_BLOCK_0

字幕高亮、词频统计、笼统词替代建议——这些全部由 lib/lexicon.js 在本地用词表匹配完成,零 API 调用、零延迟、零费用;只有"这段话有没有跑题、有没有说完结论、有没有前后矛盾"这类需要理解的判断,才走 lib/ai-feedback.js 发给大模型。lib/prompts.js 文件头的注释把这个切分写得非常明白:

视觉层(字幕高亮,由前端词库处理,不经过AI) / 提示层(AI判断,弹一句话3秒消失)

为什么要这么切?因为两类任务的性质不同。"那个"是不是填充词是个确定性事实,词表匹配的正确率是 100%,而让大模型判断每次都要花几百毫秒和真金白银,还会漏判;"你铺垫太久没给结论"是个判断题,词表永远做不了,只能靠模型。把确定性的工作从 AI 请求里抠出来,是这个时代做 AI 应用最基础也最容易偷懒的一刀——多数同类产品把这刀省了,把整个字幕原文一股脑丢给模型,让它"分析一下",然后为每个字的高亮付费。

代价是对应的:词表层的表达能力有上限("那个"在"那个方案"里是限定词、在"那个……我觉得"里才是填充词,词表分不出来),分词误差会在下一节细说。

语音识别层:streaming paraformer 的真实配置

ASR 用的是 k2-fsa 的 sherpa-onnx runtime + 阿里达摩院开源的 streaming paraformer 中英双语模型,lib/asr.js 里的初始化配置可以直接读出它的取舍:

const config = {
  featConfig: { sampleRate: 16000, featureDim: 80 },
  modelConfig: {
    paraformer: {
      encoder: path.join(modelDir, 'encoder.int8.onnx'),
      decoder: path.join(modelDir, 'decoder.int8.onnx'),
    },
    tokens: path.join(modelDir, 'tokens.txt'),
    numThreads: 2,
    provider: 'cpu',
  },
  decodingMethod: 'greedy_search',
  maxActivePaths: 4,
  enableEndpoint: true,
  rule1MinTrailingSilence: 2.4,
  rule2MinTrailingSilence: 1.2,
  rule3MinUtteranceLength: 20,
};

几个参数值得点名。int8 量化 + provider: 'cpu' + numThreads: 2:不要求 GPU,普通笔记本就能实时跑,这是"本地优先"能成立的前提。enableEndpoint + 三条 endpoint 规则(说话后静音 2.4 秒、句中静音 1.2 秒、最长语音段 20 秒)负责把音频流切成一句一句——字幕能逐句上屏靠的就是这个。greedy_search 解码:不做 beam search,牺牲一点准确率换延迟,训练场景里完全划算。

音频链路也有个务实的处理:录音不在主进程做,渲染进程用 Web Audio API 采 16kHz PCM,音频帧通过 IPC 送进主进程喂给识别器。main.js 里的 Electron 安全配置是规范的——三个窗口全部 contextIsolation: true、nodeIntegration: false、走 preload。这在随手写的 Electron 项目里不算多数,值得记一笔。

词库层:手写分词、密度指标和 146 个词的真相

词库层是这项目最有意思、也水分最足的一层。先把真话说完。

仓库描述写的是"27000词情感词库",实际随包发行的 data/emotion-lexicon.json 里是 146 个情绪词。 27000 指的是它的来源——大连理工情感词汇本体库(约 2.7 万词条)的规模;这个项目从里面精筛了 146 个词,每个带七大类目(喜怒哀惧恶惊)、子类、1–9 强度值和极性。README 自己写的是"130+ 情绪词",是诚实的;挂 27000 在仓库描述里,就属于营销话术了。买椟还珠的比例是 186:1。

然后是两个真正值得警惕的工程细节。

第一,主检测路径根本没用 JSON 里那套词表。 lib/lexicon.js 加载了 emotion-lexicon.json,但 loadLexicon() 之后只用了里面的 emotions(146 个情绪词);填充词、犹豫词、笼统词映射用的是文件里硬编码的另一套 const 数组。而 JSON 里其实躺着更大的一套:fillerWords 25 个、hedgeWords 19 个、笼统词映射 25 组——README 里"24 个口头禅、19 个犹豫词、25 组映射"的数字对应的是 JSON,不是代码。同一个项目里存在两套已经漂移的词表:数据文件里一套(大),检测代码里一套(小,16 个填充词、15 个犹豫词、20 组映射)。 顺带一个低级但致命的细节:JSON 的键名拼错了,vagueToPrecise 写成了 vagueToPresice。这类漂移和拼错在 12 天赶出来的项目里正常,但你要改词库,必须先弄清改哪一份才生效——改 JSON 不改 const,检测行为纹丝不动。

第二,中文分词是手写的最大正向匹配,没有用任何分词库。 核心函数 segmentText 只有十几行:

function segmentText(text) {
  const words = [];
  let i = 0;
  const maxLen = 6;
  const dict = new Set([
    ...FILLER_WORDS, ...HEDGE_WORDS,
    ...Object.keys(VAGUE_TO_PRECISE),
    ...Object.keys(lexiconData.emotions || {})
  ]);
  while (i < text.length) {
    let matched = false;
    for (let len = Math.min(maxLen, text.length - i); len >= 2; len--) {
      const word = text.substring(i, i + len);
      if (dict.has(word)) { words.push(word); i += len; matched = true; break; }
    }
    if (!matched) { words.push(text[i]); i++; }
  }
  return words;
}

它只认识词表里的词(最长 6 字,从长到短贪心匹配),其余全部切成单字。这个设计对它的场景其实够用——检测目标词全部在词表里,不需要通用分词——但误差也真实存在:"就是"在"这就是我要说的"里是强调结构、在"我觉得就是……"里是填充词,词表匹配分不出来,一律高亮。误伤率取决于你说话的习惯。

analyzeText 最后算一个表达密度指标:(总词数 - 填充词 - 犹豫词) / 总词数,四舍五入成百分比。简单,但给了六维报告里唯一一个不依赖 AI 判断的硬数字。

AI 反馈层:8 字规则机 prompt 与六维报告

AI 层是这个项目里 prompt 工程做得最认真的一块。实时反馈的 system prompt 开头三行把约束钉死:

你是中文口语表达的实时教练。每次只输出1条提示,不超过8个字,不加标点,不解释。

然后是 11 条按优先级排序的触发规则——重复检测→「说过一遍」、结论缺失→「说结论」、自问自答→「✓ 好结构」、连续抽象→「太抽象,给个数字」、主题漂移→「跑题」、立场模糊→「你到底觉得呢?」……只输出第一个命中的,全没命中输出空行。最后一段硬性约束还要求正负反馈混着来,"不要偏向某一种"。

这个 prompt 本质上是用自然语言写了一个状态机:输入约束(取末尾 500 字)、输出约束(≤8 字、无标点、空行语义)、优先级仲裁(11 条规则定序)、正负均衡约束。把"输出空行"定义成一种合法输出,是整个设计里最见功力的一笔——没有触发就安静,不硬找话说,教练才不烦人。对比市面上大量"请对用户的表达给出反馈"式的松散 prompt,这是拿 prompt 当规格写的范例。

用户还有四个注入点,可以在设置里往 system prompt 末尾追加:训练目标(goals)、自定义规则(customRules)、想要的风格范本(styleRef)、自己的口癖词表(customWords)。仓库里甚至有一个独立的 prompt 编辑器页面(src/prompt-editor.html)。这四个注入点把"通用教练"变成了"你的教练"——表达训练恰恰是高度个人化的需求,这个设计是产品层面的正确答案。

完整报告是另一个 prompt(getReportPrompt),六维分析,文件头注释自述血统:"融合 meeting-insights-analyzer + content-research-writer",版本号 v6。也就是说作者把两个现成的分析类 skill 的 prompt 谱系揉在了一起,迭代了六版。这是"prompt 资产复用"的一个真实样本:分析框架(冲突回避、填充词爆发情境、主导性 vs 被动性)是可迁移的,作者没重造轮子。

后端层 lib/ai-feedback.js 很薄:OpenAI 兼容的 chat completions 调用,四个 provider 预设(DeepSeek、OpenAI、Ollama 本地、自定义 baseURL),实时反馈限 max_tokens: 200。没有任何 SDK,用的是 Node 18+ 自带的 fetch——所以 package.json 里才能只有两个依赖。

怎么用:三步安装、模型下载、后端选择

三步,全部来自 README,可复现:

git clone https://github.com/fxy2311-youyou/expression-trainer
cd expression-trainer && npm install

cd models
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-paraformer-bilingual-zh-en.tar.bz2
tar xvf sherpa-onnx-streaming-paraformer-bilingual-zh-en.tar.bz2
cd ..

npm start

启动后点右上角 ⚙️ 进设置,填 AI 后端。README 给的推荐表:DeepSeek(费用极低、快)、OpenAI(中等)、Ollama(免费、取决于硬件),README 明确推荐 DeepSeek。填好就能用。不填也行——词库高亮和统计不依赖网络,只是没有 AI 提示和报告。

唯一的坑是模型目录:models/ 下要解出 encoder.int8.onnx、decoder.int8.onnx、tokens.txt 三个文件,路径错了 lib/asr.js 的 checkModels() 会直接抛错并告诉你缺哪个文件,报错信息写得不错。

真正的创新点

功能清单不重复了,讲三个机制层面的东西。

第一,"确定性/判断性"的任务切分。 前面已经拆透,不展开。这是整个项目对做 AI 应用的人最有迁移价值的一条:先问"这件事词表/规则能不能做",能做的不进 prompt。

第二,prompt 写成带仲裁的规则机,且把沉默定义为合法输出。 11 条优先级规则 + 空行语义 + 正负均衡,这比大多数"AI 助手"的 prompt 更像一份状态机规格。实时场景里,模型话多就是产品事故,这个 prompt 的克制是设计出来的,不是靠抽卡抽出来的。

第三,极简依赖栈证明了一条本地 AI 应用的最低配路线。 一个 Electron 壳 + 一个 sherpa-onnx-node 依赖 + Node 自带 fetch,没有框架、没有构建步骤、没有 TypeScript,24 个文件、全部源码约 150KB,跑起来是一个完整可用的本地语音 AI 应用。搭配 Ollama 后端,整条链路(转写、检测、反馈)可以完全离线。这在"AI 应用必须上云"的默认叙事里,是一个反例样本:语音 AI 的本地化门槛已经被 sherpa-onnx 这类 runtime 压到了"一个 npm 依赖"的水平。

横向比较:跟三种练法差在哪

跟直接用 ChatGPT 语音模式练比。 ChatGPT 能给你高质量的复盘建议,但它是对话方,不是镜子:你看不到自己的话被可视化,反馈是事后的、成段的、要读完的。expression-trainer 的价值全在"当下"——字正打出来、颜色正亮着、提示正弹着,训练发生在说话的瞬间。这是"事后看报告"和"实时照镜子"的区别。

跟 Yoodli 这类演讲教练 SaaS 比。 Yoodli 做得更重:完整演讲录像、多维度评分、长期追踪,面向的是正式演讲训练。代价是数据全上云、按订阅收费。expression-trainer 反向:功能糙得多,但本地离线、零订阅、代码 MIT 全开。练面试自述这种半私密场景,"语音不出本机"本身就有价值——它的推荐后端虽然是云端 API,但 Ollama 路径下连反馈都是本地的。

跟自己写脚本比。 "录音 + whisper 转写 + 让 GPT 分析"这套流程技术上任何人都能拼出来,成本更低。但拼出来的东西不会在你说"那个"的当下标红它——而表达训练的神经科学常识是,即时反馈对习惯纠正的效果远大于事后复盘。这个项目证明的正是这一点:技术上没有任何一环是新的,但把各环拼成"实时"这个产品形态,就是别人没做的部分。

该怀疑的地方

一、检测层的天花板是词表,不是智能。 手写正向匹配分词 + 硬编码词表,误判("就是"作为正常结构被标红)和漏判(词表外的口癖检测不到)都是常态。好在 customWords 注入点给了自救通道,把自己的高频口癖手动加进去,能补一大部分。

二、"27000 词库"的描述与实物比例是 186:1。 前面已拆。加上两套词表漂移(README 数对不上代码)、JSON 键名拼错,这个项目的数据层卫生状况,决定了你 fork 它之后的第一件事是统一词表,而不是加功能。

三、12 个 commit、12 天、然后停更。 最后一次推送停在 2026-07-18,11 个 open issue 无一关闭。好消息是这项目没有依赖会腐烂的部分——模型文件来自 sherpa-onnx 官方 release,runtime 依赖只有一个。fork/star 17.9% 的高比例说明社区在自救式使用,但你别期待上游修 issue。

四、"表达=去掉填充词"是一个意识形态假设。 这个项目的价值判断非常明确:直接、精准、少铺垫才是好表达。对汇报、面试、技术陈述,这个假设基本成立。但填充词在真实人类对话里承担着思考缓冲、礼貌缓和、关系润滑的功能——把它训练没了,在某些场景(安慰人、头脑风暴、跨文化沟通)是减分项。它是"表达健身房",不是"表达真理"。

五、AI 反馈的质量完全取决于你接的后端。 README 推荐 DeepSeek 是成本考量,但 8 字提示这种高压缩输出格式对弱模型很不友好——规则触发判断错误时你只有 3 秒窗口去消化一条错误提示,且没有上下文。用 Ollama 本地小模型跑这个 prompt 的实际效果,需要自己试,README 没给任何模型推荐的下限。

成本

  • 钱:软件免费,MIT。AI 反馈走 DeepSeek 的话,实时提示单次输入约几百 token、输出上限 200 token,练一小时(按每 50 字一次提示、语速 200 字/分钟)约 240 次调用,DeepSeek 计价下是几分钱量级;报告一次几厘钱。走 Ollama 为零。
  • 机器:int8 模型 + 2 线程 CPU 推理,普通笔记本无压力;Electron 常驻内存百多 MB。模型包需从 sherpa-onnx releases 下载解压,一次性动作。
  • 注意力:装好即用,无账号无云服务。真正的成本是你愿意每天对着它说几分钟话——表达训练工具的死法从来不是技术故障,是吃灰。

参考文档与链接

你最想戒掉自己的哪个口癖?我的答案是"就是"——评论区聊聊你打算把哪个词交给它管。


作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI 人工智能时代,转载请注明出处。

posted @ 2026-11-05 07:56  iTech  阅读(18)  评论(0)    收藏  举报