基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-12 15:04

语音 Agent 为何把格式符号读出声

dev.to · AI#语音#Agent#文本处理
Editor brief · 编辑速览

LLM 输出的 Markdown、金额、日期和电话号码,直接交给 TTS 可能产生错误读法。文章指出应在合成前做文本规范化,并提醒逐条追加的正则规则会相互干扰。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

语音 Agent 把「星号、星号」念出来,未必是模型差,也未必是语音合成出了错。作者遇到的问题在两者之间:LLM 输出适合阅读的文本,TTS 却需要适合朗读的句子。金额、日期、电话号码,都不能只靠去掉 Markdown 来处理。

这篇文章最有用的地方,是拆开了文本规范化里的规则冲突。作者还介绍了自己写的 speakable,但比工具更值得带走的,是处理顺序和听觉验收这两条经验。

为什么文字没错,电话里却念错了

原文的例子很直观:模型返回 **Invoice #42**: $1,234.56 due 2026-11-11.,放在聊天窗口里没有问题,到了电话里,星号、井号、日期中的横线却可能被逐个念出。

作者的解释是,LLM 接触了大量文档、Markdown、代码和文章,自然倾向于生成书面表达。TTS(语音合成)对这些表达的支持则不一致:有些能识别金额,有些能处理缩写,但作者认为,不能指望任何一家覆盖所有情况。更换供应商或版本后,读音也可能变化。

因此,合成前需要一个文本规范化阶段,把书面形式转换成可朗读形式。

这里的难点不只是清理符号。(555) 123-4567 应该按电话号码逐位读,不能当成几个大整数;Dr. Rao 里的 Dr. 是称谓;14:00 需要按时间理解。数字长得相似,承担的语义却不同。

年份也有自己的习惯:2026 可以读成「twenty twenty-six」,2005 是「two thousand five」,1905 则是「nineteen oh five」。一个通用的整数转英文函数,覆盖不了这些差别。

规则为什么必须有明确的执行顺序

最初的修补通常是 text = text.replace("*", "").replace("#", "")。它能消除部分杂音,却没有解释金额、日期和编号该怎么读。

继续补正则后,更麻烦的问题来了:规则会抢同一段文本。

作者提出,Markdown 清理要先执行,避免 **$5** 干扰后续金额匹配;日期要先于电话号码,否则 2026-11-11 可能被识别成带连字符的号码;邮箱和 URL 要先于修改句点的规则,否则 john.doe@example.com 可能被拆坏;金额要先于普通整数,否则 $1,234.56 可能只剩一个流畅的数字读法,货币单位却丢了。

规范化规则的执行顺序,就是这层模块的设计。

作者花了一个下午排查的时间正则,很能说明问题。下面这段代码试图识别时间和可选的上午、下午标记:

re.sub(r"\b(\d{1,2}):(\d{2})\s*([ap]\.?m\.?)?", say_time, text)

要看的是末尾的 \.?:处理 Call at 3:30 PM. 时,它会把句末句点一起吞掉。另一个问题是 \s* 放在可选组外,即使没匹配到 PM,也会消耗后面的空格,让 Departs at 14:00 sharp 出现单词粘连。

作者给出的修正同时收紧了结尾,并把空白移进可选组:

re.sub(r"\b(\d{1,2}):(\d{2})(?:\s*([ap]\.?m))?", say_time, text)

关键是 (?:\s*([ap]\.?m))? 这一段:只有匹配到上午、下午标记时,才一并消费前面的空白。这处修改解决的是原文列出的两个问题,不能据此认定时间解析已经覆盖全部写法。

电话号码也有边界陷阱。原文模式 r"(?<![\w$.])\+?\d[\d\-.\s()]{6,18}\d(?![\w])" 从首个数字开始匹配,处理 (555) 123-4567 时会留下开括号。作者指出,开括号也必须被包含在匹配范围内,但没有给出修正后的完整表达式。

speakable 能接在哪,边界又在哪

流式输出还有一类特殊输入:模型输出到一半,或达到输出上限,只留下 **Your total is。只匹配成对标记的清理规则拿它没办法,两个星号仍会进入 TTS。

作者的处理思路是,先执行成对规则,再清理残留标记。截断的回答已经不完整,不该再让格式符号增加听觉噪声。不过,原文没有说明如何跨流式分块保存状态,不能把清理残留标记直接等同于完整的流式解析方案。

作者将这层逻辑整理成了 speakable。安装命令是 pip install speakable,通过 from speakable import normalize 导入;在文中 LiveKit agent 的示例里,合成前调用 await tts.say(normalize(llm_response))。

按作者介绍,它支持 Markdown 和 emoji 清理、五种货币、ISO/美式/文字日期、12 和 24 小时制时间,以及电话、百分比、序数、单位、缩写、邮箱和 URL,还能选择逐字母朗读首字母缩略词。

作者称其核心零依赖,每条话语处理约 1.5 ms,并具有幂等性,也就是同一内容处理两次不会继续变化。项目采用 MIT 许可证。当前不支持非英语地区格式、印度 lakh/crore 数位分组、罗马数字和分数;原文也没有列出五种货币的具体名单。

我的判断

我认可把规范化独立成模块。它有清楚的输入输出,也有规则之间的依赖,适合单独维护和回归验证。相比在业务代码里不断追加 .replace(),明确的规则顺序更容易解释问题出在哪里。

但工具效果和性能,目前只能按作者自述理解。原文没有提供测试语料规模、运行环境,也没有给出不同 TTS 供应商的对照数据。约 1.5 ms 不能直接当作所有团队的性能承诺,「所有 TTS 都处理不全」也缺少逐家验证。

另一种常见做法,是在提示词里要求模型输出纯文本、使用口语表达。这能减少格式噪声,但具体金额、日期和号码仍需要稳定的处理规则。两种方法可以配合,原文没有做效果对比。

对国内团队,最大的限制是语言:作者明确说还不支持非英语地区格式,不能默认套到中文金额、中文日期或中英混读场景里。

能拿来做什么

  • 如果正在做语音 Agent,就在 LLM 输出与 TTS 之间建立独立的规范化模块,并写清日期、电话、金额和普通数字的执行顺序。
  • 用原文里的金额、年份、邮箱、括号电话号码和未闭合 ** 建立回归样例,同时检查转换结果与实际录音,尤其留意单位丢失、标点消失和单词粘连。
  • 英文语音项目可以评估 speakable,重点验证业务常见格式与重复处理是否稳定;中文项目应先梳理自己的朗读规则,再判断需要实现多少语言相关逻辑。
  • 更换 TTS 供应商或版本时,用同一批业务文本重新试听,把原本正确的读法是否发生变化纳入验收。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
从安装到断点重跑的 AI 工具排障清单
下一篇
聊天任务截止日期如何解析才可靠

资讯速览

dev.to · AI · 2026-11-12 15:04

AI 评分 8.0 / 10

相关标签

#语音#Agent#文本处理

继续阅读

  • 57 个 MCP 服务探测揭示匿名访问边界
  • 从安装到断点重跑的 AI 工具排障清单
  • 聊天任务截止日期如何解析才可靠
  • 架构修复时要不要给模型低级别提示
  • 让智能体选对工具,先补好接口描述