Merge pull request #101 from wugren/MVP

TTS and ASR function implemented based on openai api
This commit is contained in:
Liu Zhicong
2023-11-22 09:23:26 -08:00
committed by GitHub
11 changed files with 554 additions and 139 deletions
+1 -1
View File
@@ -6,6 +6,7 @@ from .compute_kernel import ComputeKernel,ComputeTask,ComputeTaskResult,ComputeT
from .compute_node import ComputeNode,LocalComputeNode
from .open_ai_node import OpenAI_ComputeNode
from .role import AIRole,AIRoleGroup
from .storage import ResourceLocation,AIStorage,UserConfig,UserConfigItem
from .workflow import Workflow
from .bus import AIBus
from .workflow_env import WorkflowEnvironment,CalenderEnvironment,CalenderEvent,PaintEnvironment
@@ -15,7 +16,6 @@ from .google_text_to_speech_node import GoogleTextToSpeechNode
from .tunnel import AgentTunnel
from .tg_tunnel import TelegramTunnel
from .email_tunnel import EmailTunnel
from .storage import ResourceLocation,AIStorage,UserConfig,UserConfigItem
from .contact_manager import ContactManager,Contact,FamilyMember
from .text_to_speech_function import TextToSpeechFunction
from .image_2_text_function import Image2TextFunction
+55
View File
@@ -0,0 +1,55 @@
import logging
from typing import Dict
from aios_kernel import ComputeKernel
from aios_kernel.ai_function import AIFunction
logger = logging.getLogger(__name__)
class AsrFunction(AIFunction):
def __init__(self):
self.func_id = "speech_to_text"
self.description = "语音识别,将语音转换为文字"
def get_name(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"audio_file": {"type": "string", "description": "音频文件路径"},
"model": {"type": "string", "description": "识别模型", "enum": ["openai-whisper"]},
"prompt": {"type": "string", "description": "提示语句,可以为None"},
"response_format": {"type": "string", "description": "返回格式", "enum": ["text", "json", "srt", "verbose_json", "vtt"]},
}
}
async def execute(self, **kwargs) -> str:
logger.info(f"execute asr function: {kwargs}")
audio_file = kwargs.get("audio_file")
model = kwargs.get("model")
prompt = kwargs.get("prompt")
response_format = kwargs.get("response_format")
if response_format is None:
response_format = "text"
result = await ComputeKernel.get_instance().do_speech_to_text(audio_file, model, prompt, response_format)
if result is not None:
return f"exec speech_to_text Ok. {response_format} is\n```\n{result.result_str}\n```"
else:
return "exec speech_to_text failed"
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+21 -1
View File
@@ -197,7 +197,8 @@ class ComputeKernel:
gender: Optional[str] = None,
age: Optional[str] = None,
voice_name: Optional[str] = None,
tone: Optional[str] = None):
tone: Optional[str] = None,
model_name: Optional[str] = None):
task_req = ComputeTask()
task_req.params["text"] = input
task_req.params["language_code"] = language_code
@@ -205,6 +206,7 @@ class ComputeKernel:
task_req.params["age"] = age
task_req.params["voice_name"] = voice_name
task_req.params["tone"] = tone
task_req.params["model_name"] = model_name
task_req.task_type = ComputeTaskType.TEXT_2_VOICE
self.run(task_req)
@@ -213,6 +215,24 @@ class ComputeKernel:
if task_req.state == ComputeTaskState.DONE:
return task_result.result
async def do_speech_to_text(self,
audio: str,
model: str,
prompt: Optional[str],
response_format: Optional[str]):
task_req = ComputeTask()
task_req.params["file"] = audio
task_req.params["model_name"] = model
task_req.params["prompt"] = prompt
task_req.params["response_format"] = response_format
task_req.task_type = ComputeTaskType.VOICE_2_TEXT
self.run(task_req)
task_result = await self._wait_task(task_req)
if task_req.state == ComputeTaskState.DONE:
return task_result
def text_2_image(self, prompt:str, model_name:Optional[str] = None, negative_prompt = None):
task = ComputeTask()
@@ -117,9 +117,18 @@ class GoogleTextToSpeechNode(ComputeNode):
def _run_task(self, task: ComputeTask):
task.state = ComputeTaskState.RUNNING
language_code = task.params["language_code"]
if language_code is None:
language_code = "en"
text = task.params["text"]
voice_name = task.params["voice_name"]
if voice_name is None:
voice_name = "default"
gender = task.params["gender"]
if gender is None:
gender = "female"
age = task.params["age"]
if language_code == "zh":
+120
View File
@@ -0,0 +1,120 @@
import asyncio
import io
import logging
import os
from asyncio import Queue
from openai import AsyncOpenAI
from aios_kernel import ComputeNode, ComputeTask, ComputeTaskState, ComputeTaskResult, ComputeTaskType, AIStorage
logger = logging.getLogger(__name__)
class OpenAITTSComputeNode(ComputeNode):
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
super().__init__()
self.is_start = False
self.node_id = "openai_tts_node"
self.task_queue = Queue()
self.voice_list = {
"female": ["nova", "shimmer"],
"man": ["alloy", "echo", "fable", "onyx"]
}
if os.getenv("OPENAI_API_KEY") is not None:
self.openai_api_key = os.getenv("OPENAI_API_KEY")
else:
self.openai_api_key = AIStorage.get_instance().get_user_config().get_value("openai_api_key")
self.start()
def start(self):
if self.is_start is True:
logger.warn("OpenAITTSComputeNode is already start")
return
self.is_start = True
async def _run_task_loop():
while True:
task = await self.task_queue.get()
try:
result = await self._run_task(task)
if result is not None:
task.state = ComputeTaskState.DONE
task.result = result
except Exception as e:
logger.error(f"openai_tts_node run task error: {e}")
task.state = ComputeTaskState.ERROR
task.result = ComputeTaskResult()
task.result.set_from_task(task)
task.result.worker_id = self.node_id
task.result.result_str = str(e)
asyncio.create_task(_run_task_loop())
async def _run_task(self,task: ComputeTask):
task.state = ComputeTaskState.RUNNING
text = task.params["text"]
voice_name = task.params["voice_name"]
if voice_name is None:
voice_name = "default"
gender = task.params["gender"]
if gender is None:
gender = "female"
voice_list = self.voice_list[gender]
voice = voice_list[hash(voice_name)%len(voice_list)]
model_name = task.params['model_name']
if model_name is None:
model_name = 'tts-1'
client = AsyncOpenAI(api_key=self.openai_api_key)
response = await client.audio.speech.create(model=model_name, voice=voice, input=text)
cache = io.BytesIO()
async for data in await response.aiter_bytes():
cache.write(data)
cache.seek(0)
result = ComputeTaskResult()
result.set_from_task(task)
result.worker_id = self.node_id
result.result = cache.read()
return result
async def push_task(self, task: ComputeTask, proiority: int = 0):
logger.info(f"openai_tts_node push task: {task.display()}")
self.task_queue.put_nowait(task)
async def remove_task(self, task_id: str):
pass
def get_task_state(self, task_id: str):
pass
def display(self) -> str:
return f"OpenAITTSComputeNode: {self.node_id}"
def get_capacity(self):
return 0
def is_support(self, task: ComputeTask) -> bool:
if task.task_type == ComputeTaskType.TEXT_2_VOICE:
if task.params['model_name'] is None or task.params['model_name'] == 'tts-1' or task.params['model_name'] == 'tts-1-hd':
return True
return False
def is_local(self) -> bool:
return False
@@ -0,0 +1,107 @@
import io
import logging
import os
import random
from pathlib import Path
from typing import Dict
from aios_kernel import ComputeKernel, AIStorage
from aios_kernel.ai_function import AIFunction
from pydub import AudioSegment
logger = logging.getLogger(__name__)
class ScriptToSpeechFunction(AIFunction):
def __init__(self):
self.func_id = "script_to_speech"
self.description = "根据输入的剧本生成音频文件,成功时会返回音频文件路径"
self.speech_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "tts")
Path(self.speech_path).mkdir(exist_ok=True)
def get_name(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"language": {"type": "string", "description": "演播语言", "enum": ["zh", "en"]},
"model": {"type": "string", "description": "演播模型", "enum": ["tts-1", "tts-1-hd"]},
"roles": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"gender": {"type": "string", "description": "角色性别", "enum": ["man", "female"]},
"age": {"type": "string", "description": "年龄", "enum": ["child", "adult"]},
}}},
"lines": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"tone": {"type": "string", "description": "演播情感",
"enum": ["happy", "sad", "angry", "fear", "disgust", "surprise", "neutral"]},
"text": {"type": "string", "description": "台词"},
}
}}
}
}
async def execute(self, **kwargs) -> str:
logger.info(f"execute text_to_speech function: {kwargs}")
language = kwargs.get("language")
if language is None:
language = "zh"
model = kwargs.get("model")
roles = kwargs.get("roles")
lines = kwargs.get("lines")
audio = None
for line in lines:
name = line.get("name")
tone = line.get("tone")
text = line.get("text")
gender = None
age = None
for role in roles:
role_name = role.get("name")
if role_name == name:
gender = role.get("gender")
age = role.get("age")
break
i = 0
while i < 3:
try:
data = await ComputeKernel.get_instance().do_text_to_speech(text, language, gender, age, name, tone, model_name=model)
if audio is None:
audio = AudioSegment.from_mp3(io.BytesIO(data))
else:
audio = audio + AudioSegment.from_mp3(io.BytesIO(data))
break
except Exception as e:
logger.error(f"do_text_to_speech failed: {e}")
i += 1
continue
if audio is not None:
path = os.path.join(self.speech_path, "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10))))
audio.export(path, format="mp3")
return "exec text_to_speech OKspeech file store at ```{}```".format(path)
else:
return "exec text_to_speech failed"
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+25 -49
View File
@@ -2,19 +2,23 @@ import io
import logging
import os
import random
from pathlib import Path
from typing import Dict
from aios_kernel import ComputeKernel
from aios_kernel import ComputeKernel, AIStorage
from aios_kernel.ai_function import AIFunction
from pydub import AudioSegment
logger = logging.getLogger(__name__)
class TextToSpeechFunction(AIFunction):
def __init__(self):
self.func_id = "text_to_speech"
self.description = "根据输入的本生成音频文件,成功时会返回音频文件路径"
self.description = "根据输入的本生成音频文件,成功时会返回音频文件路径"
self.speech_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "tts")
Path(self.speech_path).mkdir(exist_ok=True)
def get_name(self) -> str:
return self.func_id
@@ -27,22 +31,8 @@ class TextToSpeechFunction(AIFunction):
"type": "object",
"properties": {
"language": {"type": "string", "description": "演播语言", "enum": ["zh", "en"]},
"roles": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"gender": {"type": "string", "description": "角色性别", "enum": ["man", "female"]},
"age": {"type": "string", "description": "年龄", "enum": ["child", "adult"]},
}}},
"lines": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"tone": {"type": "string", "description": "演播情感",
"enum": ["happy", "sad", "angry", "fear", "disgust", "surprise", "neutral"]},
"text": {"type": "string", "description": "台词"},
}
}}
"model": {"type": "string", "description": "演播模型", "enum": ["tts-1", "tts-1-hd"]},
"text": {"type": "string", "description": "文本内容"}
}
}
@@ -51,41 +41,27 @@ class TextToSpeechFunction(AIFunction):
language = kwargs.get("language")
if language is None:
language = "zh"
roles = kwargs.get("roles")
lines = kwargs.get("lines")
language = "en"
model = kwargs.get("model")
text = kwargs.get("text")
audio = None
for line in lines:
name = line.get("name")
tone = line.get("tone")
text = line.get("text")
gender = None
age = None
for role in roles:
role_name = role.get("name")
if role_name == name:
gender = role.get("gender")
age = role.get("age")
break
i = 0
while i < 3:
try:
data = await ComputeKernel.get_instance().do_text_to_speech(text, language, gender, age, name, tone)
if audio is None:
audio = AudioSegment.from_mp3(io.BytesIO(data))
else:
audio = audio + AudioSegment.from_mp3(io.BytesIO(data))
break
except Exception as e:
logger.error(f"do_text_to_speech failed: {e}")
i += 1
continue
i = 0
while i < 3:
try:
data = await ComputeKernel.get_instance().do_text_to_speech(text, language, None, None, None, None,
model_name=model)
if data is not None:
audio = AudioSegment.from_mp3(io.BytesIO(data))
break
except Exception as e:
logger.error(f"do_text_to_speech failed: {e}")
i += 1
continue
if audio is not None:
path = os.path.join(os.path.realpath(os.curdir), "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10))))
path = os.path.join(self.speech_path, "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10))))
audio.export(path, format="mp3")
return "exec text_to_speech OKspeech file store at {}".format(path)
return "exec text_to_speech OKspeech file store at ```{}```".format(path)
else:
return "exec text_to_speech failed"
+150 -32
View File
@@ -1,55 +1,77 @@
import io
import json
from asyncio import Queue
import asyncio
import openai
import os
import logging
import srt
import webvtt
from openai import AsyncOpenAI
from openai.cli._progress import BufferReader
from pydub import AudioSegment
from datetime import timedelta
from . import AIStorage
from .compute_node import ComputeNode
from .compute_task import ComputeTask, ComputeTaskResult, ComputeTaskState, ComputeTaskType
logger = logging.getLogger(__name__)
SECONDS_IN_HOUR = 3600
SECONDS_IN_MINUTE = 60
HOURS_IN_DAY = 24
MICROSECONDS_IN_MILLISECOND = 1000
def timedelta_to_vtt_timestamp(timedelta_timestamp):
hrs, secs_remainder = divmod(timedelta_timestamp.seconds, SECONDS_IN_HOUR)
hrs += timedelta_timestamp.days * HOURS_IN_DAY
mins, secs = divmod(secs_remainder, SECONDS_IN_MINUTE)
msecs = timedelta_timestamp.microseconds // MICROSECONDS_IN_MILLISECOND
return "%02d:%02d:%02d.%03d" % (hrs, mins, secs, msecs)
class WhisperComputeNode(ComputeNode):
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.is_start = False
cls._instance = cls()
return cls._instance
def __init__(self) -> None:
super().__init__()
if self.is_start is True:
logger.warn("WhisperComputeNode is already start")
return
self.is_start = True
self.is_start = False
self.node_id = "whisper_node"
self.enable = True
self.task_queue = Queue()
self.open_api_key = None
if self.open_api_key is None and os.getenv("OPENAI_API_KEY") is not None:
self.open_api_key = os.getenv("OPENAI_API_KEY")
if self.open_api_key is None:
raise Exception("WhisperComputeNode open_api_key is None")
if os.getenv("OPENAI_API_KEY") is not None:
self.openai_api_key = os.getenv("OPENAI_API_KEY")
else:
self.openai_api_key = AIStorage.get_instance().get_user_config().get_value("openai_api_key")
self.start()
def start(self):
if self.is_start is True:
logger.warn("WhisperComputeNode is already start")
return
self.is_start = True
async def _run_task_loop():
while True:
task = await self.task_queue.get()
try:
result = self._run_task(task)
result = await self._run_task(task)
if result is not None:
task.state = ComputeTaskState.DONE
task.result = result
except Exception as e:
logger.error(f"whisper_node run task error: {e}")
logger.exception(e)
task.state = ComputeTaskState.ERROR
task.result = ComputeTaskResult()
task.result.set_from_task(task)
@@ -58,7 +80,7 @@ class WhisperComputeNode(ComputeNode):
asyncio.create_task(_run_task_loop())
def _run_task(self, task: ComputeTask):
async def _run_task(self, task: ComputeTask):
task.state = ComputeTaskState.RUNNING
prompt = task.params["prompt"]
response_format = None
@@ -72,19 +94,114 @@ class WhisperComputeNode(ComputeNode):
language = task.params["language"]
file = task.params["file"]
resp = openai.Audio.transcribe("whisper-1",
file,
self.open_api_key,
prompt=prompt,
response_format=response_format,
temperature=temperature,
language=language)
result = ComputeTaskResult()
result.set_from_task(task)
result.worker_id = self.node_id
result.result_str = resp["text"]
result.result = resp
return result
client = AsyncOpenAI(api_key=self.openai_api_key)
if os.path.getsize(file) > 25 * 1024 * 1024:
audio = AudioSegment.from_file(file)
text = ""
results = []
latest_resp = None
step = 10 * 60 * 1000
for i in range(0, len(audio), step):
if i + step < len(audio):
chunk = audio[i:i + step]
else:
chunk = audio[i:]
seg_file = io.BytesIO()
chunk.export(seg_file, format="mp3")
seg_file.seek(0)
resp = await client.audio.transcriptions.create(model="whisper-1",
file = ("test.mp3", seg_file),
language=language,
temperature=temperature,
prompt=prompt,
response_format=response_format)
if response_format == "json":
if text == "":
text = resp.text
else:
text += "," + resp.text
elif response_format == "text":
if text == "":
text = resp
else:
text += "," + resp
elif response_format == "verbose_json":
if text == "":
text = resp.text
else:
text += "," + resp.text
results.extend(resp.segments)
elif response_format == "srt":
srt_list = list(srt.parse(resp))
for item in srt_list:
item.start += timedelta(milliseconds=i)
item.end += timedelta(milliseconds=i)
results.append(item)
elif response_format == "vtt":
vtt = webvtt.read_buffer(io.StringIO(resp))
for caption in vtt.captions:
start = timedelta_to_vtt_timestamp(
srt.srt_timestamp_to_timedelta(caption.start) + timedelta(milliseconds=i))
end = timedelta_to_vtt_timestamp(
srt.srt_timestamp_to_timedelta(caption.end) + timedelta(milliseconds=i))
results.append(webvtt.Caption(start, end, caption.text))
else:
raise Exception(f"not support response_format: {response_format}")
latest_resp = resp
result = ComputeTaskResult()
result.set_from_task(task)
result.worker_id = self.node_id
if response_format == "text":
result.result_str = text
result.result = text
elif response_format == "json":
result.result_str = json.dumps({"text": text})
resp.text = text
result.result = resp
elif response_format == "verbose_json":
result.result_str = json.dumps({"text": text, "segments": results})
latest_resp.text = text
latest_resp.segments = results
result.result = latest_resp
elif response_format == "srt":
result.result_str = srt.compose(results)
result.result = result.result_str
elif response_format == "vtt":
vtt = webvtt.WebVTT()
vtt.captions.extend(results)
f = io.StringIO()
vtt.write(f)
f.seek(0)
result.result_str = f.read()
result.result = result.result_str
return result
else:
with open(file, "rb") as file_reader:
buffer_reader = BufferReader(file_reader.read(), desc="Upload progress")
resp = await client.audio.transcriptions.create(model="whisper-1",
file = (file, buffer_reader),
language=language,
temperature=temperature,
prompt=prompt,
response_format=response_format)
result = ComputeTaskResult()
result.set_from_task(task)
result.worker_id = self.node_id
if response_format == "json":
result.result_str = json.dumps({"text": resp.text})
elif response_format == "verbose_json":
result.result_str = json.dumps({"text": resp.text, "segments": resp.segments})
elif response_format == "srt" or response_format == "vtt" or response_format == "text":
result.result_str = resp
else:
raise Exception(f"not support response_format: {response_format}")
result.result = resp
return result
async def push_task(self, task: ComputeTask, proiority: int = 0):
logger.info(f"whisper_node push task: {task.display()}")
@@ -102,9 +219,10 @@ class WhisperComputeNode(ComputeNode):
def get_capacity(self):
return 0
def is_support(self, task_type: ComputeTaskType) -> bool:
if task_type == ComputeTaskType.VOICE_2_TEXT:
return True
def is_support(self, task: ComputeTask) -> bool:
if task.task_type == ComputeTaskType.VOICE_2_TEXT:
if task.params['model_name'] is None or task.params['model_name'] == 'openai-whisper':
return True
return False
def is_local(self) -> bool:
+2 -2
View File
@@ -8,7 +8,7 @@ import threading
import logging
from typing import Optional
from .text_to_speech_function import TextToSpeechFunction
from .script_to_speech_function import ScriptToSpeechFunction
from .image_2_text_function import Image2TextFunction
from .compute_kernel import ComputeKernel, ComputeTaskResultCode
from .environment import Environment,EnvironmentEvent
@@ -344,7 +344,7 @@ class WorkflowEnvironment(Environment):
self.db_file = db_file
self.local = threading.local()
self.table_name = "WorkflowEnv_" + env_id
self.add_ai_function(TextToSpeechFunction())
self.add_ai_function(ScriptToSpeechFunction())
self.add_ai_function(Image2TextFunction())
+2
View File
@@ -140,3 +140,5 @@ sentence-transformers==2.2.2
tiktoken
markdown
PyPDF2
srt==3.5.3
webvtt-py==0.4.6
+19 -11
View File
@@ -20,6 +20,8 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.styles import Style
from aios_kernel.openai_tts_node import OpenAITTSComputeNode
directory = os.path.dirname(__file__)
sys.path.append(directory + '/../../')
@@ -74,8 +76,8 @@ class AIOS_Shell:
user_config.add_user_config("shell.current","last opened target and topic",True,"default@Jarvis")
proxy.declare_user_config()
google_text_to_speech = GoogleTextToSpeechNode.get_instance()
google_text_to_speech.declare_user_config()
# google_text_to_speech = GoogleTextToSpeechNode.get_instance()
# google_text_to_speech.declare_user_config()
Local_Stability_ComputeNode.declare_user_config()
@@ -94,7 +96,7 @@ class AIOS_Shell:
bus.register_message_handler(target_id,a_workflow._process_msg)
return True
a_contact = await ContactManager.get_instance().get_contact(target_id)
a_contact = ContactManager.get_instance().find_contact_by_name(target_id)
if a_contact is not None:
bus.register_message_handler(target_id,a_contact._process_msg)
return True
@@ -143,6 +145,12 @@ class AIOS_Shell:
return False
ComputeKernel.get_instance().add_compute_node(open_ai_node)
whisper_node = WhisperComputeNode.get_instance()
ComputeKernel.get_instance().add_compute_node(whisper_node);
openai_tts_node = OpenAITTSComputeNode.get_instance()
ComputeKernel.get_instance().add_compute_node(openai_tts_node)
llama_nodes = ComputeNodeConfig.get_instance().initial()
for llama_node in llama_nodes:
llama_node.start()
@@ -158,14 +166,14 @@ class AIOS_Shell:
await AIStorage.get_instance().set_feature_init_result("llama",False)
if await AIStorage.get_instance().is_feature_enable("aigc"):
try:
google_text_to_speech_node = GoogleTextToSpeechNode.get_instance()
google_text_to_speech_node.init()
ComputeKernel.get_instance().add_compute_node(google_text_to_speech_node)
except Exception as e:
logger.error(f"google text to speech node initial failed! {e}")
await AIStorage.get_instance.set_feature_init_result("aigc",False)
# if await AIStorage.get_instance().is_feature_enable("aigc"):
# try:
# google_text_to_speech_node = GoogleTextToSpeechNode.get_instance()
# google_text_to_speech_node.init()
# ComputeKernel.get_instance().add_compute_node(google_text_to_speech_node)
# except Exception as e:
# logger.error(f"google text to speech node initial failed! {e}")
# await AIStorage.get_instance.set_feature_init_result("aigc",False)
# stability_api_node = Stability_ComputeNode()
# if await stability_api_node.initial() is not True: