LifeOS 发音覆盖系统实战指南:用 PRONUNCIATIONS.json 让数字助理把每个词读对
【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
导读
本文讲解 LifeOS 内置的 TTS 发音覆盖(Pronunciation Overrides)机制:它通过一份扁平化的PRONUNCIDATIONS.json映射表,精确控制 ElevenLabs 语音合成对姓名、专业术语、缩写等词汇的读音,同时配套一份仅供人类阅读的PRONUNCIATIONS.md笔记文件用于记录"为什么要有这条覆盖"。读完本文,你将掌握这套双层配置的设计意图、JSON 词条的编写规则与字面匹配陷阱、它在语音合成管线中的底层执行逻辑(正则编译、同形异义词消歧、词边界锚定),以及如何通过/interview工作流系统化地采集和维护发音词条。
一、职责边界:为什么要有两个 Pronunciations 文件
在 LifeOS 的用户目录中存在一对配套文件:
- LifeOS/install/USER/PRINCIPAL/PRONUNCIATIONS.md —— 人类笔记,解释每条覆盖存在的原因;
- LifeOS/install/USER/PRINCIPAL/PRONUNCIATIONS.json —— 机器读取,TTS 语音层实际消费的事实来源。
PRONUNCIATIONS.md开头用一段引用块把这条边界讲得很清楚:
The voice layer does NOT read this file.It reads the sibling
PRONUNCIATIONS.json, a flat"text": "spoken form"map. This markdown file is for your own notes onwhyan override exists; every entry you actually want spoken must also exist in the.json.
也就是说,Markdown 文件里写了什么不会对语音输出产生任何影响,你真正想让助理读对的每个词条,都必须同步写进 JSON。这一设计把"说明文档"与"机器配置"分离:Markdown 用于维护者可读的备注与追溯,JSON 用于程序的高效解析。PRONUNCIATIONS.json文件头部的_comment字段也明确标注了同样的信息,并提示"Delete these examples and add your own"(删除示例、填入你自己的词条)。
二、核心配置格式:扁平映射表(Flat Map)
PRONUNCIATIONS.json是一个扁平的"原文" → "口语形式"映射:
{ "_comment": "Pronunciation overrides for the TTS layer. Flat map: exact text to match -> how it should be spoken. Matching is literal, so add each inflection you actually say ('is live', 'went live'). The VoiceServer reads THIS file (LIFEOS/USER/PRINCIPAL/PRONUNCIATIONS.json); the sibling PRONUNCIATIONS.md is human notes only. Delete these examples and add your own.", "LifeOS": "LIFE-ohess", "is live": "is lyve", "went live": "went lyve" }三个示例词条恰好覆盖了三种典型场景:
| 词条(匹配文本) | 口语形式(发音) | 场景说明 |
|---|---|---|
LifeOS | LIFE-ohess | 产品名,默认 TTS 可能读成"Life-Oh-Es"逐字母拼读,需要固化为一个整体读音 |
is live | is lyve | 短语级词条,"live"读作 /laɪv/(广播/上线语义) |
went live | went lyve | 同一单词的另一种屈折变体,需要独立成条 |
2.1 字面匹配:必须补齐每个实际说出口的屈折形式
JSON 的_comment和 Markdown 文档都反复强调同一条规则:匹配是字面的(literal)。TTS 输入文本中的词条必须与 JSON 键逐字符完全一致才会命中,因此不要只写基础词形,而是要把你实际会说的每种变化都补上:
- 只写
"live": "lyve"并不能保证is live、went live被正确改写; - 文档给出的原话是 "add each inflection you really say (
is live,went live), not just the base word",即按真实口语输入补齐变体。
这也是为什么示例 JSON 里同时存在is live与went live两条——它们分别对应现在时与过去时两种真实用法。
三、源码级机制:词条如何变成正则并作用于语音
3.1 文件路径解析
语音模块从固定位置读取发音文件。在 VoiceServer/voice.ts 的loadPronunciations中,默认路径被解析为:
~/.claude/LIFEOS/USER/PRINCIPAL/PRONUNCIATIONS.json即用户主目录下.claude/LIFEOS/USER/PRINCIPAL/目录中的 JSON。该函数同时接受一个customPath参数,允许通过VoiceConfig.pronunciations_path覆盖默认位置。若文件不存在,会记录一条 warning:TTS will use default pronunciations(将使用默认发音);若 JSON 解析失败,则记录 error,两种失败都不会让语音模块崩溃,而是静默回退到默认读音。
3.2 词条编译:正则转义与词边界锚定
每条 JSON 词条在加载时都会被编译成一个正则规则(CompiledRule { regex, phonetic })。loadPronunciations中的核心逻辑如下:
pronunciationRules = Object.entries(flat).map(([term, phonetic]) => { // \b only exists next to a word char: a leading \b before "." (".env") // or a trailing \b after "." ("Live.") never matches, silently killing // the rule. Anchor with \b only where the term boundary is a word char. const lead = /^\w/.test(term) ? "\\b" : "" const tail = /\w$/.test(term) ? "\\b" : "" return { regex: new RegExp(`${lead}${escapeRegex(term)}${tail}`, "g"), phonetic, } })这里有三个值得注意的实现细节:
- 正则转义:词条会先经过
escapeRegex(voice.ts#L165-L167),把.*+?^${}()|[]\等正则元字符全部转义,因此词条中的标点、点号等都能被安全地当作普通文本匹配; - 词边界条件锚定:
\b(单词边界)只在相邻字符是单词字符时才存在。源码注释特别举例:词条若以.开头(如.env)或结尾是.,直接加\b会永远匹配不到。因此实现里用/^\w/与/\w$/探测首尾字符,仅在首尾是单词字符时才加\b锚定——这保证了PRONUNCIATIONS.json里可以安全地写入带点号的词条; - 全局替换:正则带
g标志,意味着同一词条在文本中多次出现会被全部替换。
3.3 应用顺序与 TTS 管线集成
替换动作在applyPronunciations(voice.ts#L199-L205)中完成:按规则数组顺序,逐条对文本做String.replace。
真正发语音时,generateSpeech(voice.ts#L322-L357)在把文本交给 ElevenLabs API 之前会先做预处理:
const pronouncedText = applyPronunciations(disambiguateHomographs(text)) if (pronouncedText !== text) { log("info", `Voice pronunciation: "${text}" -> "${pronouncedText}"`) }可以看到完整的读音管线是同形异义词消歧(homographs)→ 自定义发音规则(PRONUNCIATIONS.json)→ 请求 ElevenLabs。如果改写后的文本与原文不同,日志会记录Voice pronunciation: "..." -> "...",方便排查词条是否命中。随后请求https://api.elevenlabs.io/v1/text-to-speech/{voiceId},模型固定为eleven_turbo_v2_5,并携带voice_settings参数(stability、similarity_boost、style、speed、use_speaker_boost)。
四、同形异义词消歧:live问题的内建解法
PRONUNCIATIONS.json里两条live词条并不是孤立的存在,它与语音模块内置的 homographs.ts 消歧器形成互补。
homographs.ts的注释解释了背景:有些词拼写相同、词义不同读音就不同,ElevenLabs 偶尔会猜错。最典型的例子是live:
- 动词义(/lɪv/,"live freely"、"where you live")是 ElevenLabs 的默认读法,通常读得对;
- 广播/上线义(/laɪv/,"the site is live"、"go live")经常被误读成动词音。
因此消歧器采用高精度上下文匹配而非全量替换——它内置了多组上下文正则(homographs.ts#L31-L47),只在明确的上线语境中把live改写为lyve:
go / goes / going / gonna go / went / stay / stays / staying + live(如 "went live");is / are / am / was / were / be / been / it's / now / then / currently + live(如 "is live");live on <域名>、live in production、live site/deploy/stream/...;watch / stream / air / broadcast ... live;- 连字符形式
live-verified、live-tested等; deploy/ship/push/launch/roll out + ... + live等动词搭配。
disambiguateHomographs(homographs.ts#L62-L73)对每个上下文正则匹配到的片段,只改写其中的live标记本身,并通过matchCase保持原始大小写(如 "Live" → "Lyve")。这样"live freely"这类动词义句子完全不受影响。
这正好解释了为什么PRONUNCIATIONS.json的示例仍要写is live与went live两条词条:homographs.ts的内建规则虽然覆盖了这两种语境,但它是共享的、写死的;用户自定义词条则用于内建规则覆盖不到、或你个人习惯的特殊读法,两者是"内置兜底 + 用户扩展"的关系。voice.ts的调用顺序(先disambiguateHomographs再applyPronunciations)保证用户词条拥有最终决定权。
五、词条采集:用 /interview 工作流建立发音清单
Markdown 文档明确给出了词条采集入口:运行/interview让数字助理记录它必须读对的词汇,或者直接手动编辑 JSON。
这条指引对应着仓库中的实际实现:
- Workflows/Interview.md 在第 2 步把 "name, pronunciation, timezone, hometown" 归入 Principal identity 采集范畴;
- InterviewScan.ts 的采集提示包含
"Your name (with pronunciation if uncommon)?"(你的名字,若不常见请附发音),说明访谈流程会主动询问用户姓名的非常规读音; - 身份层数据结构 identity.ts 中,
DEFAULT_PRINCIPAL与合并逻辑都带有pronunciation: string字段,用户的发音偏好会随 Principal 身份一起持久化。
此外,仓库还提供了面向语音系统的结构化参考样例 pronunciations.reference.json,其 entries 示例为:
{ "key": "Schmidt", "value": "shmit", "notes": "silent c, soft-t" }, { "key": "LifeOS", "value": "P-A-I", "notes": "letters not 'pie'" }并带category: "voice"、kind: "reference"等元数据(含 schemaVersion、pageId、sourceHashes 指向LIFEOS/USER/PRINCIPAL/PRONUNCIATIONS.json、adapterVersion 等),说明发音词条已纳入 PULSE 的 Schema 数据模型,可作为你设计自己词条表时的参考格式。
六、配置生效、健康检查与验证
6.1 启动加载与配置覆盖
语音模块通过startVoice(config)初始化(voice.ts#L623-L658):先解析 ElevenLabs API Key(config →ELEVENLABS_API_KEY环境变量),再调用loadPronunciations(config.pronunciations_path)加载发音规则,随后加载 settings.json 中的声音配置。启动日志会输出pronunciationRules: N,直接显示已加载的词条数量。
VoiceConfig接口(voice.ts#L25-L30)定义如下:
export interface VoiceConfig { enabled: boolean elevenlabs_api_key?: string default_voice_id?: string pronunciations_path?: string }其中pronunciations_path即自定义发音文件路径;default_voice_id用于指定默认声音(未配置时回退到 ElevenLabs 预置声音 "Rachel")。
6.2 健康检查接口
语音模块暴露GET /voice/health路由(voice.ts#L713-L715),voiceHealth()(voice.ts#L663-L674)返回的字段中包含pronunciation_rules: pronunciationRules.length,可直接确认词条是否成功加载。一个典型响应包含:initialized、enabled、voice_system: "ElevenLabs"、default_voice_id、api_key_configured、pronunciation_rules、configured_voices、desktop_notifications。
注意:该模块不创建自己的 HTTP 服务,而是由父进程 pulse 在匹配路由上调用
handleVoiceRequest()(见 voice.ts#L700-L811)。POST 路由(/notify、/notify/personality、/voice)受 60 秒窗口内 10 次的速率限制。
七、最佳实践:维护一套可靠的发音词表
综合文档规则与源码实现,整理出如下实践清单:
- JSON 是唯一事实来源:任何你想让 TTS 读对的词条必须写入
PRONUNCIATIONS.json,仅在PRONUNCIATIONS.md记录"为什么"(来源、语境、特殊说明),二者保持同步; - 按真实口语补齐变体:字面匹配意味着要写全你实际会说的形式——
is live与went live各成一条,而不是只写live; - 善用短语级词条:JSON 支持任意长度的键,不只是单词;
is live这类短语级词条比单词级更精准,误伤其他语境的风险更低; - 注意首尾标点:虽然源码对词首/词尾是非单词字符的词条(如
.env)做了智能边界处理,但常规词条仍建议保持普通单词或短语形态; - 用 /interview 采集:让访谈流程询问姓名等非常规读音,并核对
identity.ts中pronunciation字段与 JSON 是否一致; - 验证命中:启动日志中的
pronunciationRules: N与/voice/health的pronunciation_rules字段可确认加载数量;TTS 请求前的Voice pronunciation: "..." -> "..."日志可确认具体词条是否在真实文本中命中改写。
八、小结
LifeOS 的发音覆盖体系是一个典型的"双层配置 + 三级管线"设计:PRONUNCIATIONS.md负责人类可读的原因记录,PRONUNCIATIONS.json负责机器可读的扁平映射;运行时文本依次经过内建同形异义词消歧、用户自定义正则替换两道改写,最终才送入 ElevenLabs 合成。理解字面匹配的规则、词边界锚定的细节、以及/interview采集链路,你就能为数字助理建立一套精准、可追溯、可持续维护的个性化发音词表。
【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考