File size: 9,796 Bytes
fd22ae3
6e771f6
fd22ae3
862d34a
8ca0441
6e771f6
 
57315a6
8ca0441
 
fd22ae3
b2a1255
 
fd22ae3
8ca0441
862d34a
b2a1255
 
 
 
8ca0441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2a1255
 
862d34a
8ca0441
 
fd22ae3
 
 
 
8ca0441
fd22ae3
 
 
 
8ca0441
 
fd22ae3
862d34a
8ca0441
 
862d34a
 
 
 
ce12d21
 
862d34a
 
8ca0441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
862d34a
 
 
 
8ca0441
 
862d34a
8ca0441
 
 
 
862d34a
8ca0441
 
 
 
 
 
 
 
 
 
 
 
 
862d34a
 
 
 
 
 
 
 
b2a1255
862d34a
 
 
 
 
 
 
 
 
8ca0441
b2a1255
fd22ae3
 
8ca0441
 
 
 
fd22ae3
8ca0441
 
 
 
fd22ae3
8ca0441
 
 
 
fd22ae3
 
b2a1255
 
fd22ae3
 
 
 
b2a1255
 
 
fd22ae3
 
 
 
b2a1255
 
fd22ae3
 
 
 
 
 
8ca0441
 
 
 
 
 
 
 
 
 
 
fd22ae3
 
 
 
 
 
 
 
 
 
 
862d34a
 
 
8ca0441
862d34a
fd22ae3
 
 
 
ce12d21
8ca0441
862d34a
fd22ae3
862d34a
fd22ae3
 
 
 
8ca0441
fd22ae3
 
8ca0441
 
 
fd22ae3
8ca0441
fd22ae3
8ca0441
fd22ae3
 
 
 
 
 
 
 
862d34a
fd22ae3
862d34a
8ca0441
fd22ae3
 
862d34a
6e771f6
fd22ae3
6e771f6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import os

import gradio as gr
from openai import OpenAI
import google.generativeai as genai


LOAD_KEYS_FROM_FILES = False
USE_LOCAL_ASR_PIPELINE = True


if LOAD_KEYS_FROM_FILES:
    # Load OpenAI API key
    with open('KEY_OPENAI', 'r') as file:
        OPENAI_API_KEY = file.read().replace('\n', '')

    # Hugging Face API key, used for the serverless access to ASR model
    with open('KEY_HF', 'r') as file:  
        os.environ['HUGGINGFACE_API_KEY'] = file.read().replace('\n', '')

    # Google AI API key, to acces Gemini model
    with open('KEY_GOOGLE_AI', 'r') as file:
        GOOGLE_API_KEY = file.read().replace('\n', '')
else:
    # testar se existe a variável OPENAI_API_KEY
    OPENAI_API_KEY = "" if 'OPENAI_API_KEY' not in os.environ else os.environ['OPENAI_API_KEY']
    GOOGLE_API_KEY = "" if 'GOOGLE_API_KEY' not in os.environ else os.environ['GOOGLE_API_KEY']
    assert 'HUUGINGFACE_API_KEY' in os.environ, "Hugging Face API key not found in environment variables"


USE_OPENAI_LLM = (GOOGLE_API_KEY == "")

OPENAI_CLIENT = None
if USE_OPENAI_LLM:
    OPENAI_CLIENT = OpenAI(api_key=OPENAI_API_KEY)
else:
    genai.configure(api_key=GOOGLE_API_KEY)

GOOGLE_GEN_CONFIG = genai.types.GenerationConfig(
            candidate_count=1,
            temperature=0.5)

AUDIO_OUT_FILE_PREFIX = "output"   # prefixo do nome do arquivo de áudio .wav

TEMPLATE_SYSTEM_MESSAGE = """Você é assistente virtual com a função é entreter uma criança de idade entre 6 e 8 anos que adora futebol. Diretrizes para a conversa: 
- Você é {GENRE}, seu nome é {NAME}.
- Pergunte o nome da criança. 
- Fale sobre futebol, times, jogadores, seleções e grandes jogos. 
- Em especial, foque no futebol do Brasil, Inglaterra e Espanha. 
- {PERSONALITY}
- Fale, no máximo, três frases por mensagem.
"""

# Mapeia a personalidade no template e na temperatura
PERSONALITIES = {
    "nova": ("Sua personalidade é bastante amigável e alegre, como uma criança. Tente iniciar novos assuntos, quando a conversa estiver repetitiva. Conte piadas de futebol, de vez em quando.", 0.5, "F"),
    "echo": ("Sua personalidade é amigável, mas objetivo. Tente manter-se no mesmo assunto. Conte alguma curiosidade sobre um grande craque, de vez em quando.", 0.2, "M")
}

INITIAL_PERSON = "nova"


# Função para converter o histórico de chat para o formato esperado pela API do OpenAI
def to_openai_chat_history(system_prompt, curr_message, chat_history):
    prompt = [ { 'role': 'system', 'content': system_prompt } ]
    if len(chat_history) > 10:
        chat_history = chat_history[0:3] + chat_history[-5:]
    for turn in chat_history:
        user_message, bot_message = turn
        prompt.append( {'role': 'user', 'content': user_message} )
        prompt.append( {'role': 'assistant', 'content': bot_message} )
    prompt.append( {'role': 'user', 'content': curr_message } )
    return prompt


# Função para converter o histórico de chat para o formato esperado pela API do Google AI
def to_google_history(curr_message, chat_history):
    prompt = []
    for turn in chat_history:
        user_message, bot_message = turn
        prompt.append( {'role':'user', 'parts': [user_message]} )
        prompt.append( {'role': 'model', 'parts': [bot_message]} )
    prompt.append( {'role': 'user', 'parts': [curr_message]} )
    
    return prompt


def respond(system_prompt, user_message, chat_history, temperature, voice="echo"):
        if USE_OPENAI_LLM:
            openai_history = to_openai_chat_history(system_prompt, user_message, chat_history)

            bot_response = OPENAI_CLIENT.chat.completions.create(
                messages=openai_history,
                temperature=temperature,
                model="gpt-3.5-turbo")

            assistant_msg = bot_response.choices[0].message.content
        
        else:
            GOOGLE_GEN_CONFIG.temperature = temperature
            model = genai.GenerativeModel('gemini-1.5-pro-latest', 
                               system_instruction=system_prompt,
                               generation_config=GOOGLE_GEN_CONFIG)
            
            google_history = to_google_history(user_message, chat_history)
            
            bot_response = model.generate_content(google_history, generation_config=GOOGLE_GEN_CONFIG)

            assistant_msg = bot_response.text

        # salva o audio 
        response = OPENAI_CLIENT.audio.speech.create(
            model="tts-1",
            voice=voice,
            input=assistant_msg
        )

        output_audio_file = f"{AUDIO_OUT_FILE_PREFIX}-{len(chat_history)+1:03}.wav"
        #response.stream_to_file(output_audio_file)
        response.write_to_file(output_audio_file)

        # adiciona ao chat, com o tipo de dado esperado pelo Gradio
        chat_history.append( (user_message, assistant_msg) )
        
        return "", chat_history, output_audio_file


def reset_and_apply(voice):
    return [("", "Olá, vamos falar de futebol?")], AUDIO_OUT_FILE_PREFIX + f"-001-{voice}.wav"

def reset_openai_client(openai_key):
    global USE_OPENAI_LLM, OPENAI_CLIENT, OPENAI_API_KEY, GOOGLE_API_KEY
    USE_OPENAI_LLM = (GOOGLE_API_KEY != "")
    OPENAI_API_KEY = openai_key
    OPENAI_CLIENT = OpenAI(api_key=OPENAI_API_KEY)

def reset_google_client(google_key):
    global GOOGLE_API_KEY, USE_OPENAI_LLM
    GOOGLE_API_KEY = google_key
    USE_OPENAI_LLM = (google_key != "")

def on_voice_change(voice):
    persona_description, persona_temperature, sex = PERSONALITIES[voice]
    genre = "menina" if sex=="F" else "menino"
    return TEMPLATE_SYSTEM_MESSAGE.format(NAME=voice.upper(), PERSONALITY=persona_description, GENRE=genre), persona_temperature


# With pipeline (downloaded model)
if USE_LOCAL_ASR_PIPELINE:
    from transformers import pipeline
    import numpy as np

    global ASR_PIPELINE
    ASR_PIPELINE = pipeline(task="automatic-speech-recognition", 
                            model="openai/whisper-large-v3")
                            #model="distil-whisper/distil-small.en") # English only
else:
    import requests
    global ASR_API_URL, ASR_API_HEADERS
    
    HF_KEY = os.environ['HUGGINGFACE_API_KEY']

    # Serverless API endpoint for OpenAI's Whisper model
    ASR_API_URL = "https://api-inference.huggingface.co/models/openai/whisper-large-v3"
    ASR_API_HEADERS = {"Authorization": f"Bearer {HF_KEY}"}


def transcribe(audio_in):
    # from numpy data:
    #sr, y = audio_in  # sampling rate and audio data
    #y2 = y.astype(np.float32)
    #y2 /= np.max(np.abs(y))
    #response = ASR_PIPELINE({"sampling_rate": sr, "raw": y})

    # using serverless API
    with open(audio_in, "rb") as f:
        data = f.read()
    response = requests.post(ASR_API_URL, headers=ASR_API_HEADERS, data=data)
    response = response.json()
    
    #print(response)
    return response['text']


def transcribe_and_respond(audio_in, system_txtbox, user_msg_txb, *args):
    user_message = transcribe(audio_in)
    outputs = respond(system_txtbox, user_message, *args)
    return outputs



with gr.Blocks() as demo:
    # aqui, é resetado e instanciado o cliente
    initial_chat_history, initial_audio = reset_and_apply(INITIAL_PERSON)

    chatbot_area = gr.Chatbot(value=initial_chat_history)
    audio_out = gr.Audio(label="Escute a última mensagem", value=initial_audio, autoplay=True, interactive=False)
    
    user_msg_txb = gr.Textbox(label="Mensagem")

    #audio_in = gr.Audio(label="Mensagem de Áudio", sources=['microphone'], interactive=True, type='filepath')  # TODO: tentar type='numpy'
    
    submit_btn = gr.Button("Enviar")
    
    #clear_btn = gr.ClearButton(components=[user_msg, chatbot], value="Clear console")
    reset_btn = gr.Button("Reiniciar")
    
    with gr.Accordion(label="Configurações",open=False):
        openai_key = gr.Textbox(label="OpenAI API Key (GPT 3.5)", value="", placeholder="Insira a chave aqui")
        openai_key.change(reset_openai_client, inputs=[openai_key])

        openai_key = gr.Textbox(label="Google API Key (Gemini 1.5)", value="", placeholder="Insira a chave aqui")
        openai_key.change(reset_google_client, inputs=[openai_key])

        # opções de vozes e personalidades
        voice_ddown = gr.Dropdown(label="Personalidade (muda os dois abaixo)", choices=["nova", "echo"], value=INITIAL_PERSON)

        initial_system_message, initial_temperature = on_voice_change(INITIAL_PERSON)        
        temperature_sldr = gr.Slider(label="Diversidade de respostas", minimum=0.0, maximum=1.0, value=initial_temperature, step=0.1)
        
        with gr.Accordion(label="Avançado",open=False):
            # o valor inicial é dado pela system message com o nome e personalidade dados pelos controles acima
            system_txtbox = gr.Textbox(label="System message", lines=3, value=initial_system_message)

        voice_ddown.change(on_voice_change, inputs=[voice_ddown], outputs=[system_txtbox, temperature_sldr])
        #gr.Markdown("*Clique em 'Reiniciar' para aplicar as (a maior parte das) configurações.*")

    reset_btn.click(reset_and_apply, inputs=[openai_key, voice_ddown], outputs=[chatbot_area, audio_out])

    #audio_in.stop_recording( transcribe_and_respond, inputs=[audio_in, system_txtbox, user_msg_txb, chatbot_area, temperature_sldr, voice_ddown], outputs=[user_msg_txb, chatbot_area, audio_out] )
    submit_btn.click(respond, inputs=[system_txtbox, user_msg_txb, chatbot_area, temperature_sldr, voice_ddown], outputs=[user_msg_txb, chatbot_area, audio_out])    # Click on the button
    user_msg_txb.submit(respond, inputs=[system_txtbox, user_msg_txb, chatbot_area, temperature_sldr, voice_ddown], outputs=[user_msg_txb, chatbot_area, audio_out]) # Press enter to submit - same effect


demo.queue().launch(share=False)