GPT_Chat_Audio / app.py
nekoniii3's picture
Update app.py
dbc6787
raw
history blame
16.2 kB
import gradio as gr
import time
import datetime
from zoneinfo import ZoneInfo
import os
from openai import (
OpenAI, AuthenticationError, NotFoundError, BadRequestError
)
# 各種設定値
MAX_TRIAL = int(os.environ["MAX_TRIAL"]) # メッセージ取得最大試行数
INTER_SEC = int(os.environ["INTER_SEC"]) # 試行間隔(秒)
# sys_prompt_default = "あなたは優秀なアシスタントです。日本語で質問に回答してください。"
# lang_code = {'Japanese': "ja", 'English': "en"}
auto_play_bl = {'ON': True, 'OFF': False}
voice_list = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
def set_state(state, openai_key, voice, auto_play, speed):
state["openai_key"] = openai_key
state["voice"] = voice
state["auto_play"] = auto_play_bl[auto_play]
state["speed"] = speed
return state
def add_history(history, text_msg):
""" Chat履歴"history"に追加を行う関数 """
print(text_msg)
print(history)
# ユーザテキストをチャットに追加
history = history + [(text_msg, None)]
# テキスト・オーディオ初期化
return history, gr.Textbox(value="", interactive=False), gr.components.Audio(value=None, interactive=False)
def init(state, mode, text_msg, voice_msg):
""" 初期処理(入力チェック・テキスト変換) """
print(state)
err_msg = ""
text = ""
if text_msg == "" and voice_msg is None:
# 何も入力がないならエラーメッセージを返す
err_msg = "テキストまたは音声を入力してください。"
return state, "", err_msg
try:
if state["client"] is None:
# 初回起動時は初期処理をする
os.environ["OPENAI_API_KEY"] = os.environ["TEST_OPENAI_KEY"] # テスト時
# os.environ["OPENAI_API_KEY"] = state["openai_key"]
# クライアント新規作成
client = OpenAI()
# client作成後は消す
os.environ["OPENAI_API_KEY"] = ""
# セッションにセット
state["client"] = client
# IDとして現在時刻をセット
dt = datetime.datetime.now(ZoneInfo("Asia/Tokyo"))
state["user_id"] = dt.strftime("%Y%m%d%H%M%S")
else:
# 既存のクライアントをセット
client = state["client"]
# モードが翻訳以外はアシスタント・スレッドセット
if mode != 2:
if state["thread_id"] == "":
# スレッド作成・セット
thread = client.beta.threads.create()
state["thread_id"] = thread.id
if state["assistant_id"] == "":
# アシスタント作成
# assistant = client.beta.assistants.create(
# name="codeinter_test",
# instructions=state["system_prompt"],
# # model="gpt-4-1106-preview",
# model="gpt-3.5-turbo-1106",
# tools=[{"type": "code_interpreter"}]
# )
# state["assistant_id"] = assistant.id
if mode == 0:
# 日本語アシスタントをセット
state["assistant_id"] = os.environ["ASSIST_JA"]
elif mode == 1:
# 英語教師アシスタントをセット
state["assistant_id"] = os.environ["ASSIST_EN"]
else:
# アシスタント確認(IDが存在しないならエラーとなる)
# assistant = client.beta.assistants.retrieve(state["assistant_id"])
pass
# ユーザIDでフォルダ作成
os.makedirs(state["user_id"], exist_ok=True)
if voice_msg is None:
# 音声がないならテキストを返す
text = text_msg
else:
# 音声があるならwhisperでテキストに変換
text = exec_transcript(client, voice_msg)
pass
except NotFoundError as e:
err_msg = "アシスタントIDが間違っています。新しく作成する場合はアシスタントIDを空欄にして下さい。"
print(e)
except AuthenticationError as e:
err_msg = "認証エラーとなりました。OpenAPIKeyが正しいか、支払い方法などが設定されているか確認して下さい。"
print(e)
except Exception as e:
err_msg = "その他のエラーが発生しました。"
print(e)
finally:
return state, text, err_msg
def raise_exception(err_msg):
""" エラーの場合例外を起こす関数 """
if err_msg != "":
raise Exception()
return
def exec_transcript(client, voice_msg):
""" whisperで文字に起こす関数 """
audio_file= open(voice_msg, "rb")
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language = "ja",
response_format="text"
)
return transcript
def exec_translation(client, text):
""" GPTで英語に翻訳する関数 """
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": "You are a translator. Please translate the Japanese you received into English."},
{"role": "user", "content": text},
]
)
return response.choices[0].message.content
def exec_text_to_speech(client, voice , speed, folder, text):
""" テキストを音声にする """
response = client.audio.speech.create(
model= "tts-1", # "tts-1-hd",
voice=voice,
input=text,
speed=speed
)
# ファイル名は現在時刻
dt = datetime.datetime.now(ZoneInfo("Asia/Tokyo"))
file_name = dt.strftime("%Y%m%d%H%M%S") + ".mp3"
file_path = folder + "/" + file_name
# 音声ファイルに出力
response.stream_to_file(file_path)
return file_path
def bot(state, mode, history):
""" Chat返答処理 """
err_msg = ""
file_path = None
# セッション情報取得
client = state["client"]
asist_id = state["assistant_id"]
thread_id = state["thread_id"]
last_msg_id = state["last_msg_id"]
user_id = state["user_id"]
voice = state["voice"]
speed = state["speed"]
# 最新のユーザからのメッセージ
user_msg = history[-1][0]
print(history)
print(user_msg)
if mode in (0, 1): # 日本語会話モードの場合
# メッセージ作成
message = client.beta.threads.messages.create(
thread_id=thread_id,
role="user",
content=user_msg
)
# RUNスタート
run = client.beta.threads.runs.create(
thread_id=thread_id,
assistant_id=asist_id
)
# "completed"となるまで繰り返す(指定秒おき)
for i in range(0, MAX_TRIAL, 1):
if i > 0:
time.sleep(INTER_SEC)
# メッセージ受け取り
run = client.beta.threads.runs.retrieve(
thread_id=thread_id,
run_id=run.id
)
# 前回のメッセージより後を昇順で取り出す
messages = client.beta.threads.messages.list(
thread_id=thread_id,
after=last_msg_id,
order="asc"
)
# messageを取り出す
for msg in messages:
if msg.role == "assistant":
assist_msg = msg.content[0].text.value
if assist_msg != "":
history[-1][1] = assist_msg
# 音声を作成
file_path = exec_text_to_speech(client, voice, speed, user_id, assist_msg)
if file_path is None:
err_msg = "音声作成でエラーが発生しました。"
history = history + [(None, err_msg)]
else:
history = history + [(None, (file_path,))]
yield gr.Chatbot(label=run.status ,value=history), err_msg
# 最終メッセージID更新
last_msg_id = msg.id
# セッション更新
state["last_msg_id"] = last_msg_id
# 完了なら終了
if run.status == "completed":
yield gr.Chatbot(label=run.status ,value=history), err_msg
break
elif run.status == "failed":
# エラーとして終了
err_msg = "※メッセージ取得に失敗しました。"
yield gr.Chatbot(label=run.status ,value=history), err_msg
break
elif i == MAX_TRIAL:
# エラーとして終了
err_msg = "※メッセージ取得の際にタイムアウトしました。"
yield gr.Chatbot(label=run.status ,value=history), err_msg
break
elif mode == 2: # 英訳モードの場合
# GPTで英文にする
bot_message = exec_translation(client, user_msg)
file_path = exec_text_to_speech(client, voice, speed, user_id, bot_message)
# bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
# bot_message = "まず、新宿駅南口に出ます。"
# voice_path = "nova.mp3"
history[-1][1] = bot_message
# history[-1][1] = [None, (voice_path,)]
history = history + [(None, (file_path,))]
# history[-1][1] = (voice_path,)
yield history, err_msg
def finally_proc(state, history, err_msg):
""" 最終処理 """
# テキスト・オーディオを使えるように
interactive = gr.update(interactive = True)
if err_msg == "":
print(history[-1][1][0])
# 出力オーディオをセット
audio = gr.update(value=history[-1][1][0], autoplay=state["auto_play"])
else:
audio = None
return interactive, interactive, audio
def reset_chat(state):
state["assistant_id"] = ""
state["thread_id"] = ""
state["last_msg_id"] = ""
# Chatも初期化
return state, None
with gr.Blocks() as demo:
title = "<h2>GPT音声対応チャット</h2>"
message = "<h3>・WhisperとText to speechによりチャットを行います。<br>"
message += "・テキストまたは音声を入力して下さい。モードの違いは「利用上の注意」タブをご覧ください。<br>"
message += "・動画での紹介はこちら→https://www.youtube.com/watch?v=wMoAORg0Y5Q<br>"
message += "※自動再生の音声は送信ボタンの下の”出力音声”から自動再生で流れています。(設定でOFFにできます)<br>"
# message += "・テスト中でAPIKEY無しで動きます。フィードバックもお待ちしております。<br>"
message += "</h3>"
gr.Markdown(title + message)
# セッションの宣言
state = gr.State({
# "system_prompt": SYS_PROMPT_DEFAULT,
"openai_key" : "",
"client" : None,
"assistant_id" : "",
"thread_id" : "",
"last_msg_id" : "",
"user_id" : "",
"auto_play" : True,
"speed" : 0.8,
"voice" : "nova"
})
with gr.Tab("GPT-4V 音声入力対応チャット") as maintab:
# モード選択
mode = gr.Radio(["通常(日本語)", "英会話", "英訳"], label="Mode", value="通常(日本語)", interactive=True, type="index")
# 各コンポーネント定義
chatbot = gr.Chatbot(label="チャット画面")
text_msg = gr.Textbox(label="テキスト")
voice_msg=gr.components.Audio(label="音声入力", sources="microphone", type="filepath")
with gr.Row():
btn = gr.Button("送信")
clear = gr.ClearButton([chatbot, text_msg, voice_msg])
sys_msg = gr.Text(label="システムメッセージ", interactive = False)
out_voice=gr.Audio(label="出力音声", type="filepath", interactive = False, autoplay = True)
# 英訳保存用(非表示)
# text_en = gr.Textbox(visible=False)
# メッセージ送信時の処理
btn.click(init, [state, mode, text_msg, voice_msg],[state, text_msg, sys_msg], queue=False).then(
raise_exception, sys_msg, None).success(
add_history, [chatbot, text_msg], [chatbot, text_msg, voice_msg], queue=False).then(
bot, [state, mode, chatbot], [chatbot, sys_msg]).then(
finally_proc, [state, chatbot, sys_msg], [text_msg, voice_msg, out_voice], queue=False)
# 録音終了時の処理
# voice_msg.stop_recording(init, [state, mode, text_msg, voice_msg],[state, text_msg, sys_msg], queue=False).then(
# raise_exception, sys_msg, None).success(
# add_history, [chatbot, text_msg], [chatbot, text_msg, voice_msg], queue=False).then(
# bot, [state, mode, chatbot], [chatbot, sys_msg]).then(
# finally_proc, [state, chatbot, sys_msg], [text_msg, voice_msg, out_voice], queue=False)
with gr.Tab("設定"):
openai_key = gr.Textbox(label="OpenAI API Key" ,visible=False)
voice = gr.Dropdown(choices=voice_list, value = "nova", label="Voice", interactive = True)
auto_play = gr.Dropdown(choices=["ON", "OFF"], value = "ON", label="Auto Play", interactive = True)
speed = gr.Slider(0, 1, value=0.8, label="Speed", info="1に近づけるほど読むスピードが速くなります。", interactive = True)
# sys_prompt = gr.Textbox(value = sys_prompt_default,lines = 5, label="Custom instructions", interactive = True)
# モード変更時
mode.change(reset_chat, [state], [state, chatbot])
# 設定変更時
maintab.select(set_state, [state, openai_key, voice, auto_play, speed], state)
with gr.Tab("利用上の注意"):
caution = "・通常モードは日本人アシスタントとしてGPTが日本語で回答します。<br>"
caution += "・英会話モードは英語の教師としてGPTが回答します。<br>"
caution += "(英語で返答するように設定していますが、まれに日本語で回答するかもしれません。)<br>"
caution += "・英訳モードはテキスト・音声ともに入力した日本語をそのまま英語に翻訳します。<br>"
caution += "(GPTでの英訳となり、余計な文章などが含まれる可能性があります、)<br>"
caution += "・モードを変更すると会話はリセットされます。<br>"
caution += "・設定タブで声の種類(デフォルトはnova)、回答を自動再生にするか決めることができます。<br>"
gr.Markdown("<h4>" + caution + "</h4>")
with gr.Tab("声サンプル") as voice_chk:
gr.Markdown("<h3>Text to speechの声のサンプルです。(速度は0.8です)</h3>")
with gr.Row():
btn_alloy = gr.Button(value="alloy")
btn_echo = gr.Button(value="echo")
btn_fable = gr.Button(value="fable")
with gr.Row():
btn_onyx = gr.Button(value="onyx")
btn_nova = gr.Button(value="nova")
btn_shimmer = gr.Button(value="shimmer")
sample_voice=gr.Audio(type="filepath", interactive = False, autoplay = True)
btn_alloy.click(lambda:"voice_sample/alloy.mp3", None, sample_voice)
btn_echo.click(lambda:"voice_sample/echo.mp3", None, sample_voice)
btn_fable.click(lambda:"voice_sample/fable.mp3", None, sample_voice)
btn_onyx.click(lambda:"voice_sample/onyx.mp3", None, sample_voice)
btn_nova.click(lambda:"voice_sample/nova.mp3", None, sample_voice)
btn_shimmer.click(lambda:"voice_sample/shimmer.mp3", None, sample_voice)
if __name__ == '__main__':
demo.queue()
demo.launch(debug=True)