使用API调用扣子语音合成TTS智能体实现文本转语音功能

☞ ░ 前往老猿Python博客 ░ https://blog.youkuaiyun.com/LaoYuanPython

一、引言

在老猿的博文《扣子智能体实现非流式对话的过程及API详解(https://blog.youkuaiyun.com/LaoYuanPython/article/details/154023567)》中,老猿介绍了基调用扣子智能体平台智能体实现非流式对话的过程,并介绍了实现中需要涉及的API相关请求和应答消息内容。

本文将以语音合成智能体的调用为例,将上述介绍内容以案例形式具体化。
在这里插入在这里插入图片描述
图片描述

二、调用TTS服务智能体过程详解

2.1、概述

本案例将实现将输入文本转换成语音、并将语音以MP3形式保存到文件,主函数名text2Voice。

2.2、主函数:text2Voice

text2Voice实现文本转语音的功能,函数的参数取决于调用的智能体TTS插件所带的参数,以及开发者想要施加控制的需求。

本案例的主函数text2Voice声明如下:

def text2Voice(inputText,voiceSpeed=1,voiceType='爽快思思/Skye')

该函数的参数如下:

  1. inputText:需要转换成语音的文本内容;
  2. voiceSpeed:语速,取值范围是[0.2,3],默认为1,通常保留一位小数即可,对应智能体所使用的语音合成插件的参数speed_ratio;
  3. voiceType:声音类别,对应智能体所使用的语音合成插件的参数speaker_id,默认值:爽快思思/Skye,经老猿验证,可能支持的取值如下:
voiceType :  ['爽快思思/Skye', '温暖阿虎/Alvin', '少年梓辛/Brayan', 
 '邻家女孩', '渊博小叔', '阳光青年', '甜美小源', '清澈梓梓', '解说小明', '开朗姐姐', '邻家男孩', '甜美悦悦', '心灵鸡汤', 
 '京腔侃爷/Harmony', '湾湾小何', '湾区大叔', '呆萌川妹', '广州德哥', '北京小爷', '浩宇小哥', '广西远舟', '妹坨洁儿', 
 '豫州子轩', '奶气萌娃', '高冷御姐', '傲娇霸总', '魅力女友', '深夜播客', '柔美女友', '撒娇学妹', '病弱少女', '活泼女孩',
 '东方浩然', '和蔼奶奶', '邻居阿姨', '温柔小雅', '儒雅青年', '擎苍', '古风少御']

2.3、主函数实现思路

2.3.1、根据参数构建请求信息体
2.3.1.1、关于参数的补充介绍

API调用的参数老猿没仔细研究过,从这个案例来说,包括API本身定义的参数、以及调用插件需要的参数,API的参数在老猿的博文《扣子智能体实现非流式对话的过程及API详解(https://blog.youkuaiyun.com/LaoYuanPython/article/details/154023567)》中已经介绍。

下面基于老猿另一篇博文《基于新版本扣子(coze)平台的TTS智能体创建发布过程及开放API信息查阅方法(https://blog.youkuaiyun.com/LaoYuanPython/article/details/153961814)》使用的语音合成插件语音合成/speech_synthesis来介绍。

在智能体的插件栏可以查看对应插件需要的参数,如图:
在这里插入图片描述
根据这个弹窗插件参数来看,相关插件参数如下:

  • text:string,必填,要合成音频的文本内容
  • emotion:string,语音情感,老猿没有使用这个参数,也没研究,可以忽略
  • voice_id:扣子音色 ID,支持选择扣子系统预置的音色或资源库中复刻的音色。 可以在操作页面直接选择音色,或通过系统音色列表查看音色 ID。老猿没有使用这个参数,也没研究,可以忽略
  • language:string,音色的语种,非必填,所有中文音色支持中英文混合场景。可参考系统音色列表查看各音色支持的语种
  • speaker_id:string,音色ID,默认为爽快思思/Skye。详细音色列表参考系统音色列表 , 老猿在主函数text2Voice的参数部分已经列出了经验证的可用音色ID
  • speed_ratio:number,语速,范围是[0.2,3],默认为1,通常保留一位小数即可
  • emotion_scale:number,调用emotion设置情感参数后可使用emotion_scale进一步设置情绪值,范围1~5,不设置时默认值为4。 注:理论上情绪值越大,情感越明显。但情绪值1~5实际为非线性增长,可能存在超过某个值后,情绪增加不明显,例如设置3和5时情绪值可能接近。

注意
除了以上参数外,经老猿验证还要注意有个特殊参数cluster,这个参数用于设置插件调用的集群,但官方文档没有这个参数的说明,只是在部分插件参数中看到这个参数,默认值是"volcano_tts",表示调火山引擎集群,也没看到其他的取值,因此就直接使用这个值。

2.3.1.2、请求参数设置的代码

在开始进入主函数体前,导入了相关库,并定义了如下全局变量和函数:

import requests
import json,time

import os,tempfile
import subprocess


api_key = "pat_xxxxxxxxxxxxxxxxxxxxxxxxxz" 

bootid = "7565727230333337636"  #智能体ID
baseUrl =  'https://api.coze.cn/v3/chat' 
headers = {  #http请求头
    "Authorization": f"Bearer {api_key}",
    'Content-Type': 'application/json'
}

def getMp3UrlFromText:  #自定义函数,从智能体返回文本中提取URL地址
    match = re.search(r'https?://[^\s\]\)]+', textInf)

    # 如果找到匹配的链接,则提取并打印
    if match:
        first_link = match.group(0)
        print("****提取的HTTP链接是:\n", first_link)
        first_link = match.group(0)
        return first_link
    else:
        print("提取HTTP链接失败")
        return None

在主函数text2Voice定义了3个参数,分别是:

  • inputText:输入的文本
  • voiceSpeed:语速
  • voiceType:音色ID

构建请求参数:

ttsReqObj = {"cluster":"volcano_tts", "speaker_id": voiceType, "speed_ratio": voiceSpeed, "text": inputText, "language":"中文"}
    ttsReqObjStr = json.dumps(ttsReqObj, ensure_ascii=False)
    data = {
        "bot_id": bootid,
        "user_id": "jiangwp",
        "stream": False,
        "auto_save_history": True,
        "additional_messages": [
            {
                "role": "user",
                "content": ttsReqObjStr,#inputText,
                "content_type": "text",
                "type":"question"
            }
            ]
        }
2.3.2、向智能体提交对话请求并获取应答
response = requests.post(baseUrl, headers=headers, data=json.dumps(data))
if response.status_code == 200:
        # 解析响应内容
        response_data = response.json()
        print("响应内容:", json.dumps(response_data,indent=4, ensure_ascii=False))
        try:
            chatid = response_data['data']['id']
        except Exception as e:
            print(f"从智能体返回消息中取数据异常,错误信息:\n{e}\n原始返回信息:\n{response_data}")
            return
        #answer = response_data.get("code")
        conversation_id = response_data['data']['conversation_id']
        print(f"获得服务端应答:应答状态={response_data['data']['status']},chatid={chatid},voiceSpeed={voiceSpeed}")#智能体应答: {answer}")

        getQuesyionAnswer(conversation_id,chatid)
2.3.3、轮询智能体处理任务情况并获取返回消息

查询智能体是否任务处理完成,如果处理未完成,则继续轮询,直到处理完成,再读取智能体返回消息:

def getQuesyionAnswer(conversationID,chatID):
    #查看对话详情,如果对话完成,则获取智能体应答

    #构建对话详情查看请求
    params = { "bot_id": bootid,"task_id": chatID }
    getChatStatusUrl = baseUrl+f'/retrieve?conversation_id={conversationID}&chat_id={chatID}&'
    
    #循环查看对话是否结束
    while True:
        response = requests.get(getChatStatusUrl, headers=headers, params=None)
        if response.status_code == 200:
            response_data = response.json()
            responseText = json.dumps(response_data,indent=4, ensure_ascii=False)
            #print(f"response_data:\n{responseText}")
            status = response_data['data']['status'] #取对话状态
            if status == 'completed':#对话结束
                #构建查看对话消息详情请求
                getChatAnswerUrl = baseUrl+f'/message/list?chat_id={chatID}&conversation_id={conversationID}'

                #提交查看对话消息详情
                response = requests.get(getChatAnswerUrl, headers=headers, params=params)
                if response.status_code == 200:
                    #解析对话数据
                    response_data = response.json()
                    #print("模型返回数据:\n", json.dumps(response_data,indent=4, ensure_ascii=False))
                    processQuestionAnswer(response_data)
                    return True
                break
            else:
                print(f"任务仍在智能体处理中,状态: {status}")
                time.sleep(2)  # 等待1秒后再次检查
        else:
            print(f"请求失败,状态码: {response.status_code}")
            break
    return False

三、完整代码

import requests
import json,time,re

import os,tempfile
import subprocess


api_key = "pat_XXXXXXXXX"

bootid = "7565727230333337636"
baseUrl =  'https://api.coze.cn/v3/chat'
headers = {
    "Authorization": f"Bearer {api_key}",
    'Content-Type': 'application/json'
}

def getMp3UrlFromText(textInf):
    match = re.search(r'https?://[^\s\]\)]+', textInf)

    # 如果找到匹配的链接,则提取并打印
    if match:
        first_link = match.group(0)
        print("****提取的HTTP链接是:\n", first_link)
        first_link = match.group(0)
        return first_link
    else:
        print("提取HTTP链接失败")
        return None
    
def getMp3AndPlay(mp3URL):
    _,FName = tempfile.mkstemp(suffix='', dir=None, text=False)

    mp3FName=FName+".mp3"
    if os.path.exists(mp3FName):os.remove(mp3FName)
    response = requests.get(mp3URL)
    if response.status_code == 200:
        with open(mp3FName, 'wb') as file:
            file.write(response.content)
        print(f'文件已成功保存为 {mp3FName}')
        subprocess.call(f"xdg-open {mp3FName}",shell=True)

def processQuestionAnswer(response_data):#解析智能体对话应答信息
    if response_data['code']:
        print("应答异常:",response_data['msg'])
    else:
        data = response_data['data']
        count = 0
        for item in data:#逐条处理返回消息
            if item['type']=='answer':
                print("大模型应答:",item['content'])
                mp3URL = getMp3UrlFromText(item['content'])
                getMp3AndPlay(mp3URL)
            elif item['type'] in['follow_up','function_call','tool_response','verbose']:#此几类消息忽略
                continue
                """ if count==0:
                    print("您可以参考如下方式提问:")
                print(f"☆ 问题{count+1}:",item['content'])
                count += 1 """


def getQuesyionAnswer(conversationID,chatID):
    #查看对话详情,如果对话完成,则获取智能体应答

    #构建对话详情查看请求
    params = { "bot_id": bootid,"task_id": chatID }
    getChatStatusUrl = baseUrl+f'/retrieve?conversation_id={conversationID}&chat_id={chatID}&'
    
    #循环查看对话是否结束
    while True:
        response = requests.get(getChatStatusUrl, headers=headers, params=None)
        if response.status_code == 200:
            response_data = response.json()
            responseText = json.dumps(response_data,indent=4, ensure_ascii=False)
            #print(f"response_data:\n{responseText}")
            status = response_data['data']['status'] #取对话状态
            if status == 'completed':#对话结束
                #构建查看对话消息详情请求
                getChatAnswerUrl = baseUrl+f'/message/list?chat_id={chatID}&conversation_id={conversationID}'

                #提交查看对话消息详情
                response = requests.get(getChatAnswerUrl, headers=headers, params=params)
                if response.status_code == 200:
                    #解析对话数据
                    response_data = response.json()
                    #print("模型返回数据:\n", json.dumps(response_data,indent=4, ensure_ascii=False))
                    processQuestionAnswer(response_data)
                    return True
                break
            else:
                print(f"任务仍在智能体处理中,状态: {status}")
                time.sleep(2)  # 等待1秒后再次检查
        else:
            print(f"请求失败,状态码: {response.status_code}")
            break
    return False
        
def text2Voice(inputText,voiceSpeed=0.8,voiceType='爽快思思/Skye'):
     
    # 定义API的URL和授权令牌

    ttsReqObj = {"cluster":"volcano_tts", "speaker_id": voiceType, "speed_ratio": voiceSpeed, "text": inputText, "language":"中文"}
    #ttsReqObj = {"speaker_id": voiceType, "speed_ratio": voiceSpeed, "text": inputText, "language":"中文"}
    ttsReqObjStr = json.dumps(ttsReqObj, ensure_ascii=False)
    dataDict = {
        "bot_id": bootid,
        "user_id": "jiangwp",
        "stream": False,
        "auto_save_history": True,
        "additional_messages": [
            {
                "role": "user",
                "content": ttsReqObjStr,#inputText,
                "content_type": "text",
                "type":"question"
            }
            ]
        }
        

    # 发送POST请求
    data = json.dumps(dataDict)#,indent=4, ensure_ascii=False)
    print(f"请求信息:{data}")
    print(f"请求信息: 文本={inputText},voiceType={voiceType},voiceSpeed={voiceSpeed}")
    response = requests.post(baseUrl, headers=headers, data=data)

    # 检查响应状态码
    if response.status_code == 200:
        # 解析响应内容
        response_data = response.json()
        #print("响应内容:", json.dumps(response_data,indent=4, ensure_ascii=False))
        try:
            chatid = response_data['data']['id']
        except Exception as e:
            print(f"从智能体返回消息中取数据异常,错误信息:\n{e}\n原始返回信息:\n{response_data}")
            return
        #answer = response_data.get("code")
        conversation_id = response_data['data']['conversation_id']
        print(f"获得服务端应答:应答状态={response_data['data']['status']},chatid={chatid},voiceSpeed={voiceSpeed}")#智能体应答: {answer}")

        getQuesyionAnswer(conversation_id,chatid)

    else:
        print("请求失败,状态码:", response.status_code)
        print("错误信息:", response.text)



text2Voice("娥曼",1)

程序运行输出:

请求信息:{"bot_id": "7565727230333337636", "user_id": "jiangwp", "stream": false, "auto_save_history": true, "additional_messages": [{"role": "user", "content": "{\"cluster\": \"volcano_tts\", \"speaker_id\": \"\u723d\u5feb\u601d\u601d/Skye\", \"speed_ratio\": 1, \"text\": \"\u5a25\u66fc\", \"language\": \"\u4e2d\u6587\"}", "content_type": "text", "type": "question"}]}
请求信息: 文本=娥曼,voiceType=爽快思思/Skye,voiceSpeed=1
获得服务端应答:应答状态=in_progress,chatid=7569155642948534324,voiceSpeed=1
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
任务仍在智能体处理中,状态: in_progress
大模型应答: 已将文本转换为语音,你可以点击下面的链接下载语音文件:
[speech_7426720361753903141_1041d2cb-3a9a-4fe5-a0bf-bd78f0390624.mp3](https://lf9-appstore-sign.oceancloudapi.com/ocean-cloud-tos/VolcanoUserVoice/speech_7426720361753903141_1041d2cb-3a9a-4fe5-a0bf-bd78f0390624.mp3?lk3s=da27ec82&x-expires=1762590585&x-signature=%2BRGdcedku84Gqfzo%2B1kZpELDn%2FU%3D)
本次使用的音色为:爽快思思/Skye
****提取的HTTP链接是:
 https://lf9-appstore-sign.oceancloudapi.com/ocean-cloud-tos/VolcanoUserVoice/speech_7426720361753903141_1041d2cb-3a9a-4fe5-a0bf-bd78f0390624.mp3?lk3s=da27ec82&x-expires=1762590585&x-signature=%2BRGdcedku84Gqfzo%2B1kZpELDn%2FU%3D
文件已成功保存为 /tmp/tmp0c74eiii.mp3

四、小结

本文介绍了调用扣子智能体实现文本转语音的具体实现,通过对智能体发起非流式对话,然后判断智能体对话任务是否处理完成,在任务处理完成后调用查看智能体对话消息详情来获取智能体应答,根据应答提供的生成语音的URL地址下载音频到本地。这个案例过程有助于理解扣子智能体的非流式对话服务的整体过程。

更多人工智能知识学习过程中可能遇到的疑难问题及解决办法请关注专栏《零基础机器学习入门》及付费专栏《机器学习疑难问题集》后续的文章。

写博不易,敬请支持:

如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!

关于老猿的付费专栏

  1. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_9607725.html 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 https://blog.youkuaiyun.com/LaoYuanPython/article/details/107580932 使用PyQt开发图形界面Python应用专栏目录》;
  2. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_10232926.html moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《https://blog.youkuaiyun.com/LaoYuanPython/article/details/107574583 moviepy音视频开发专栏文章目录》;
  3. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》为《https://blog.youkuaiyun.com/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《https://blog.youkuaiyun.com/LaoYuanPython/article/details/109713407 OpenCV-Python初学者疑难问题集专栏目录
  4. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_10762553.html Python爬虫入门 》站在一个互联网前端开发小白的角度介绍爬虫开发应知应会内容,包括爬虫入门的基础知识,以及爬取优快云文章信息、博主信息、给文章点赞、评论等实战内容。

前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.youkuaiyun.com/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

老猿Python,跟老猿学Python!

☞ ░ 前往老猿Python博文目录 https://blog.youkuaiyun.com/LaoYuanPython
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LaoYuanPython

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值