Refactor the code directory structure to better suit the current complexity

This commit is contained in:
Liu Zhicong
2023-11-30 21:04:19 -08:00
parent 4955225ecd
commit adeca91e0a
99 changed files with 391 additions and 342 deletions
+3
View File
@@ -0,0 +1,3 @@
from .open_ai_node import *
from .openai_tts_node import *
from .whisper_node import *
+301
View File
@@ -0,0 +1,301 @@
import openai
from openai import AsyncOpenAI
import os
import asyncio
from asyncio import Queue
import logging
import json
import aiohttp
import base64
import requests
from aios import ComputeTask, ComputeTaskResult, ComputeTaskState, ComputeTaskType,ComputeTaskResultCode,ComputeNode,AIStorage,UserConfig
logger = logging.getLogger(__name__)
class OpenAI_ComputeNode(ComputeNode):
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = OpenAI_ComputeNode()
return cls._instance
@classmethod
def declare_user_config(cls):
if os.getenv("OPENAI_API_KEY_") is None:
user_config = AIStorage.get_instance().get_user_config()
user_config.add_user_config("openai_api_key","openai api key",False,None)
def __init__(self) -> None:
super().__init__()
self.is_start = False
# openai.organization = "org-AoKrOtF2myemvfiFfnsSU8rF" #buckycloud
self.openai_api_key = None
self.node_id = "openai_node"
self.task_queue = Queue()
async def initial(self):
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")
if self.openai_api_key is None:
logger.error("openai_api_key is None!")
return False
openai.api_key = self.openai_api_key
self.start()
return True
async def push_task(self, task: ComputeTask, proiority: int = 0):
logger.info(f"openai_node push task: {task.display()}")
self.task_queue.put_nowait(task)
async def remove_task(self, task_id: str):
pass
def message_to_dict(self, message)->dict:
result = message.dict()
# result_msg = {}
# #message.json()
# if message.content:
# result_msg["content"] = message.content
# result_msg["role"] = message.role
# if message.function_call:
# function_call = {}
# function_call["arguments"] = message.function_call.arguments
# function_call["name"] = message.function_call.name
# result_msg["function_call"] = function_call
# if message.tool_calls:
# tool_calls = []
# for tool_call in message.tool_calls:
# tool_call_dict = {}
# tool_call_dict["id"] = tool_call.id
# tool_call_dict["type"] = tool_call.type
# func_call_dict = {}
# func_call_dict["name"] = tool_call.function.name
# func_call_dict["arguments"] = tool_call.function.arguments
# tool_call_dict["function"] = func_call_dict
# tool_calls.append(tool_call_dict)
# result_msg["tool_calls"] = message.tool_calls
# result["message"] = result_msg
return result
def _image_2_text(self, task: ComputeTask):
logger.info('openai image_2_text')
# 本地图片处理
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.openai_api_key }"
}
model_name = task.params["model_name"]
base64_image = encode_image(task.params["image_path"])
payload = {
"model": model_name,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": task.params["prompt"]
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
],
"max_tokens": 300
}
logger.info('openai send image_2_text request ')
# openai 的库的Vision只支持传图片的url地址。本地图片得用request
response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload)
if response.status_code == 200:
logger.info('openai image_2_text success')
return response.json()
else:
logger.error('openai image_2_text error')
logger.error(response.json())
return None
async def _run_task(self, task: ComputeTask):
task.state = ComputeTaskState.RUNNING
result = ComputeTaskResult()
result.result_code = ComputeTaskResultCode.ERROR
result.set_from_task(task)
match task.task_type:
case ComputeTaskType.TEXT_EMBEDDING:
model_name = task.params["model_name"]
input = task.params["input"]
logger.info(f"call openai {model_name} input: {input}")
try:
resp = openai.Embedding.create(model=model_name,
input=input)
except Exception as e:
logger.error(f"openai run TEXT_EMBEDDING task error: {e}")
task.state = ComputeTaskState.ERROR
task.error_str = str(e)
result.error_str = str(e)
return result
# resp = {
# "object": "list",
# "data": [
# {
# "object": "embedding",
# "index": 0,
# "embedding": [
# -0.00930514745414257,
# 0.00765434792265296,
# -0.007167573552578688,
# -0.012373941019177437,
# -0.04884673282504082
# ]}]
# }
logger.info(f"openai response: {resp}")
task.state = ComputeTaskState.DONE
result.result_code = ComputeTaskResultCode.OK
result.worker_id = self.node_id
result.result_str = resp["data"][0]["embedding"]
return result
case ComputeTaskType.IMAGE_2_TEXT:
result.result_code = ComputeTaskResultCode.OK
result.worker_id = self.node_id
# result.result_str = resp["data"][0]["image_2_text"]
result.result["message"] = self._image_2_text(task)
return result
case ComputeTaskType.LLM_COMPLETION:
mode_name = task.params["model_name"]
prompts = task.params["prompts"]
resp_mode = task.params["resp_mode"]
if resp_mode == "json":
response_format = { "type": "json_object" }
else:
response_format = None
max_token_size = task.params.get("max_token_size")
llm_inner_functions = task.params.get("inner_functions")
if max_token_size is None:
max_token_size = 4000
result_token = max_token_size
client = AsyncOpenAI(api_key=self.openai_api_key)
try:
if llm_inner_functions is None:
logger.info(f"call openai {mode_name} prompts: {prompts}")
resp = await client.chat.completions.create(model=mode_name,
messages=prompts,
response_format = response_format,
#max_tokens=result_token,
)
else:
logger.info(f"call openai {mode_name} prompts: \n\t {prompts} \nfunctions: \n\t{json.dumps(llm_inner_functions)}")
resp = await client.chat.completions.create(model=mode_name,
messages=prompts,
response_format = response_format,
functions=llm_inner_functions,
# max_tokens=result_token,
) # TODO: add temperature to task params?
except Exception as e:
logger.error(f"openai run LLM_COMPLETION task error: {e}")
task.state = ComputeTaskState.ERROR
task.error_str = str(e)
result.error_str = str(e)
return result
logger.info(f"openai response: {resp}")
status_code = resp.choices[0].finish_reason
token_usage = resp.usage
match status_code:
case "function_call":
task.state = ComputeTaskState.DONE
case "stop":
task.state = ComputeTaskState.DONE
case _:
task.state = ComputeTaskState.ERROR
task.error_str = f"The status code was {status_code}."
result.error_str = f"The status code was {status_code}."
result.result_code = ComputeTaskResultCode.ERROR
return result
result.result_code = ComputeTaskResultCode.OK
result.worker_id = self.node_id
result.result_str = resp.choices[0].message.content
result.result["message"] = self.message_to_dict(resp.choices[0].message)
if token_usage:
result.result_refers["token_usage"] = token_usage
logger.info(f"openai success response: {result.result_str}")
return result
case _:
task.state = ComputeTaskState.ERROR
task.error_str = f"ComputeTask's TaskType : {task.task_type} not support!"
result.error_str = f"ComputeTask's TaskType : {task.task_type} not support!"
return None
def start(self):
if self.is_start is True:
return
self.is_start = True
async def _run_task_loop():
while True:
task = await self.task_queue.get()
logger.info(f"openai_node get task: {task.display()}")
result = await self._run_task(task)
if result is not None:
task.result = result
task.state = ComputeTaskState.DONE
asyncio.create_task(_run_task_loop())
def display(self) -> str:
return f"OpenAI_ComputeNode: {self.node_id}"
def get_task_state(self, task_id: str):
pass
def get_capacity(self):
pass
def is_support(self, task: ComputeTask) -> bool:
if task.task_type == ComputeTaskType.LLM_COMPLETION:
if not task.params["model_name"]:
return True
model_name : str = task.params["model_name"]
if model_name.startswith("gpt-"):
return True
if task.task_type == ComputeTaskType.IMAGE_2_TEXT:
model_name : str = task.params["model_name"]
if model_name.startswith("gpt-4"):
return True
#if task.task_type == ComputeTaskType.TEXT_EMBEDDING:
# if task.params["model_name"] == "text-embedding-ada-002":
# return True
return False
def is_local(self) -> bool:
return False
@@ -0,0 +1,118 @@
import asyncio
import io
import logging
import os
from asyncio import Queue
from aios 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
+226
View File
@@ -0,0 +1,226 @@
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 aios import AIStorage,ComputeNode,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
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self) -> None:
super().__init__()
self.is_start = False
self.node_id = "whisper_node"
self.enable = True
self.task_queue = Queue()
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 = 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)
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
prompt = task.params["prompt"]
response_format = None
if "response_format" in task.params:
response_format = task.params["response_format"]
temperature = None
if "temperature" in task.params:
temperature = task.params["temperature"]
language = None
if "language" in task.params:
language = task.params["language"]
file = task.params["file"]
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()}")
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"WhisperComputeNode: {self.node_id}"
def get_capacity(self):
return 0
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:
return False