ChatGPT 与 TTS 之间奇妙的反应

一种很新的智能语音助手

Mar 14, 2023
3007
#ChatGPT#TTS#语音助手

本文最近一次更新于 1 年前,其中的内容很可能已经有所发展或是发生改变。

https://cdn.blog.itswincer.net/img/chatgpt-and-tts-work-together.webp

其实早在去年底 ChatGPT 刚发布的时候,我就申请到了账号,不过当时的用户体验并不算好:ChatGPT 在说大段话的时候会突然断掉,然后还经常会出现请求量过多导致服务拒绝的情况,所以也就开始尝试了几天,新鲜感满足后就搁在一旁了。

谁知过了两个月之后,ChatGPT 在国内的概念突然又被炒了起来,它的更多用法也被挖掘了出来,同时它本身的用户体验也提升了不少:不会再出现大段话突然断掉的情况;拒绝服务的情况也越来越少了;还支持了历史对话等。于是这时候我开始在有意识地使用 ChatGPT 来处理一些日常的事物了:比如写 PPT、常识性的知识问答,以及重构代码等。虽然在重构代码这一块,错误还是不少,但也确实会给予我一些灵感。

当时就有一个想法,ChatGPT 什么都好,就是只有一点不好,只能通过打字交流(咳咳,要是能通过语音交流的话,面试啥的岂不是都可以用上了?)。可惜 ChatGPT 一直没有公开 API,想使用的话估计只能用无头浏览器来模拟了,不过这样一来还要破解 Cloudflare 的人机验证,工作量太大了…

终于,在三月的时候 OpenAI 发布了 ChatGPT 的 API 以及对应的语音转文字模型 Whisper 的 API,之前想与 ChatGPT 语音交流实现起来应该没多大难度了,不过我这人有点懒,还是缺一个契机或者动力来做这个事。直到前几天的时候,女朋友转给我她的一个学长在朋友圈发布的视频,视频内容正是通过语音与 ChatGPT 进行对话,她觉得很有趣,问我能不能给她做一个,我想这正巧和我的想法不谋而合,于是乎,我就开始研究了这件事起来。

模块拆分

很显然,要通过语音与 ChatGPT 对话,整体的功能可以分成 3 个模块完成,分别是:

  1. 语音识别模块:这部分将用户的语音转化成对应的文字,也即是 ChatGPT 的输入;
  2. ChatGPT 模块:这部分就是把文字发送给 ChatGPT,然后把ChatGPT 的回答保存下来;
  3. 语音合成模块:这部分就是把 ChatGPT 的回答转化成语音,然后播放。

以下是设计图:

image

下面我会分别阐述一下这三个模块的设计与实现。

语音识别模块

这个模块是我耗费时间最久的,我一开始并没有打算使用 Whisper API(白嫖惯了的人,总是想找个免费的),而是打算使用离线的方案,主要是考虑到用这种 API 会涉及到网络层面的开销,拖慢语音识别的速度,用户的体验也会相应地打折扣。

我相继尝试了 PocketSphinxDeepSpeech这两个离线的语音识别方案,可效果都不太理想。PocketSphinx 的准确率实在差劲,DeepSpeech 的准确率倒是还可以,就是识别的速度太慢了,而且离线的方案还需要我自己来维护一套训练模型,我这半吊子的机器学习水平,实在懒得维护这些模型。

于是兜兜转转我还是回到了找云服务商提供的语音识别方案上,找了半天,终于找到了一款 Python 的开源库 SpeechRecognition,虽然是个开源库,但它本身集成了市面上大部分服务商提供的语音识别方案,虽然大部分都是付费的(需要你自己去服务商购买然后输入 API KEY),但好在还是有 Google 家的方案还可以免费使用。

于是语音识别模块我基本上都是封装了 SpeechRecognition 这个库的一些操作,等后续如果发现有什么用得不爽的地方我再来自己定制。

ChatGPT

这个模块是最短时间完成的,也没啥好说的,直接调用 OpenAI 的 API 即可,不过我稍微研究了下 API 的参数,相比较默认的 API 进行了两个调整:

  1. temperature:按 OpenAI 的说法,较低的值会让回答更加稳定,比如用同样的语句问,temperature 越大,回答的答案也可能越会不同;
  2. messages:对于 messages 我更细化地进行了 2 个调整:
    1. 在所有会话的前面都加上 role=system 的指令,这一目的是让ChatGPT 的每一次回答都遵循 system 的指令,我目前设置的指令是:「Answer in concise language」,也就是用简洁的语言回答,如果不加这个指令,稍微长一点的话题就会反复说一些废话,这不利于对话的展开。这个指令在配置文件里是可更改的;
    2. 默认保留最近 3 次对话内容,ChatGPT API 默认并不会关联上下文的会话,这是与 Web 端最大的区别,如果想要关联上下文,那就只能把之前的会话内容一起发送,如果不限制一下保存的会话次数,那么越往后面消耗的 Token 就越恐怖;

我目前的调教方法是,普通聊天就保持默认配置不动;如果想要进行特殊的会话,比如让它当百科全书来回答问题,那么会话保留的条数可以更短,system 指令可以设置成:「尽可能详尽地回答」;如果想要练习英文口语,那么可以设置 system 指令为:「Play a English teacher, Point out grammatical errors and ask questions according to the context」;如果想要模拟面试,那么可以设置 system 指令为:「扮演一名 xx 岗位的面试官进行面试,简洁地回答和提问」。

语音合成模块

这个模块也颇费了我一番功夫,一开始我也是想找离线的方案,同样也是因为想节省一些网络的开销,让对话进行地更顺畅。我也找到了对应的离线方案:pyttsx3,这个方案是调用操作系统本身的 TTS 引擎来朗读:Windows 是 SAPI5,MacOS 是 NSSpeechSynthesizer,其他平台使用 eSpeak。

这个方案虽然免费、省事,但是 pyttsx3 这个库有一个很严重的 bug:它不支持多线程,它如果放在非主线程之外运行,Speak 的时候不能阻塞住,五年前就有人在 Github 提了这个 Bug,反正目前是还没有解决。

因此我不得已只能放弃了这个离线方案,转而白嫖起了 Azure 和 Google 的 API)——分别是由 edge-ttsgTTS开源库提供。

另外值得一提的是,我优化了语音合成播放的流程,将 ChatGPT 的交互模块与语音合成模块解耦成两个线程,之间用队列通信。如果是线性地执行,等 ChatGPT 的回答完全返回之后再进行朗读的话,在 ChatGPT 回答比较长的时候等待的时间太久了,因此每当 ChatGPT 的一句话说完的时候,就先把这一句话的内容发送到队列中,同时语音合成的模块会不断地从队列取数据开始朗读。

这样,用户体验会好不少。

链接

演示视频:https://www.bilibili.com/video/BV1rY411z7tA/

代码仓库:https://github.com/WincerChan/talkgpt

一些思考

高中的时候,我的数学老师挺有趣的,我在之前的文章里也有提到他。他并不是数学专业毕业,而是学机械的,后来因为找不到相关的工作就转行来当数学老师了。

在某天上课的时候,他给我们普及历史上的三次数学危机(忘记是什么话题引起的了):第一次是因为无理数、第二次是因为无穷小、第三次是因为罗素悖论,老师像讲故事一样讲完三次危机之后,问了我们一个问题:你们觉得还会不会有第四次数学危机的出现?当时一下班上都叽叽喳喳讨论起来了,无非都是说「应该不会有吧?」、「如果有的话那是什么呢?」之类的话,他在讲台上静静地看着我们讨论了一会,然后说:「看来你们都觉得再有数学危机发生了吧」

看着大家都没有反对,应该是默认了他的话。紧接着他说:「我不这么认为,我想接下来一定还会有第四次、第五次数学危机,我们研究数学的,不能总是按照常识来做判断,在前几次危机发生前,当时的大家也不认为还会有数学危机发生,可事实就是发生了,所以我断定只要人类文明还在发展,接下来一定还会有数学危机发生」(原话已经过了快 10 年了,我自然是无法复述,只能说个大概的意思)。当时的我虽然听不太懂,但是也给我弱小的心灵带来了很强的震撼,现在回过头来我想我有点能理解了。

我们是属于科技行业的人,在 ChatGPT 这波「浪潮」来临的时候,我们首先感受到了,至于是忽略这波浪潮还是利用这波浪潮,在写这个小工具的时候,我想我已经有了决断了。

总结

花费了几天的空余时间,也算是把这个工具初步完成了。虽然使用起来还比较粗糙,但剩下的打磨可以慢慢进行了。

最近一段时间人有些浮躁,没怎么专心研究技术,做完这个项目下来,感觉还挺好的。我会慢慢拾起对技术的热情。

ChatGPT 与 TTS 之间奇妙的反应

https://blog.itswincer.com/posts/chatgpt-and-tts-work-together/

作者

Wincer

更新于

Mar 14, 2023

许可协议

CC BY-NC-ND 4.0
  1. Mar 4, 2024

    基于 OpenCL 生成 Solana 虚荣地址
  2. Nov 12, 2023

    Quantumult X 的链式代理配置
  3. May 5, 2021

    Type-Length-Value Encoding Scheme Practice
  4. Jul 8, 2020

    基于 ETS 的漏斗限流
  5. Apr 22, 2020

    使用 OpenCore 引导黑苹果踩坑记录
  6. Mar 16, 2020

    Kubernetes 初探