Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -4,73 +4,65 @@ import gradio as gr
|
|
| 4 |
import os
|
| 5 |
from openai import OpenAI
|
| 6 |
import json
|
|
|
|
| 7 |
|
| 8 |
# --- 1. Configurar el Cliente de OpenAI ---
|
| 9 |
-
# La clave de API se cargará de forma segura desde los "Secrets" de Hugging Face
|
| 10 |
try:
|
| 11 |
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 12 |
api_key_found = True
|
| 13 |
except TypeError:
|
| 14 |
api_key_found = False
|
| 15 |
|
| 16 |
-
# --- 2. El Prompt:
|
| 17 |
-
# Este prompt le dice a GPT-4o cómo actuar y qué analizar.
|
| 18 |
SYSTEM_PROMPT = """
|
| 19 |
-
Eres un experto
|
| 20 |
-
Tu tarea es
|
| 21 |
-
Debes calificar la pronunciación general en una escala de 0 a 100.
|
| 22 |
-
Tu análisis debe ser profundo, considerando:
|
| 23 |
-
1. **Precisión (Accuracy):** Compara la transcripción del usuario con la de Whisper para detectar palabras omitidas o incorrectas.
|
| 24 |
-
2. **Fluidez (Fluency):** Analiza el ritmo, la cadencia y la presencia de pausas o muletillas (uh, um).
|
| 25 |
-
3. **Prosodia (Prosody):** Evalúa la entonación y el acento de la frase. ¿Suena natural o monótono?
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
{
|
| 29 |
"calificacion_general_100": integer,
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
"
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
]
|
| 36 |
}
|
| 37 |
"""
|
| 38 |
|
| 39 |
-
# --- 3. La Función Principal
|
| 40 |
def evaluate_pronunciation_openai(audio_input, user_transcript):
|
| 41 |
-
"""
|
| 42 |
-
Toma un audio y un texto, los envía a la API de OpenAI y formatea la respuesta.
|
| 43 |
-
"""
|
| 44 |
if not api_key_found:
|
| 45 |
-
raise gr.Error("Clave de API de OpenAI no encontrada.
|
| 46 |
|
| 47 |
if audio_input is None or not user_transcript:
|
| 48 |
-
|
|
|
|
| 49 |
|
| 50 |
-
sr, waveform = audio_input
|
| 51 |
-
|
| 52 |
-
# Guardar temporalmente el audio para enviarlo a la API
|
| 53 |
temp_audio_path = "temp_audio.wav"
|
| 54 |
-
import soundfile as sf
|
| 55 |
sf.write(temp_audio_path, waveform, sr)
|
| 56 |
|
| 57 |
-
|
| 58 |
-
print("Transcribiendo audio con Whisper API...")
|
| 59 |
with open(temp_audio_path, "rb") as audio_file:
|
| 60 |
-
ai_transcript = client.audio.transcriptions.create(
|
| 61 |
-
model="whisper-1",
|
| 62 |
-
file=audio_file
|
| 63 |
-
).text
|
| 64 |
|
| 65 |
-
# 2. Construir el prompt final para el modelo de lenguaje
|
| 66 |
user_prompt = f"""
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
Transcripción generada por
|
| 70 |
"""
|
| 71 |
|
| 72 |
-
|
| 73 |
-
print("Enviando a GPT-4o para evaluación...")
|
| 74 |
response = client.chat.completions.create(
|
| 75 |
model="gpt-4o",
|
| 76 |
response_format={"type": "json_object"},
|
|
@@ -80,58 +72,62 @@ def evaluate_pronunciation_openai(audio_input, user_transcript):
|
|
| 80 |
]
|
| 81 |
)
|
| 82 |
|
| 83 |
-
# 4. Procesar y formatear la respuesta JSON
|
| 84 |
try:
|
| 85 |
result = json.loads(response.choices[0].message.content)
|
| 86 |
-
score = result.get("calificacion_general_100", 0)
|
| 87 |
-
level = result.get("nivel_mcerl_estimado", "N/A")
|
| 88 |
-
fluency = result.get("analisis_fluidez", "")
|
| 89 |
-
accuracy = result.get("analisis_precision", "")
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
return score, level, fluency, accuracy, highlighted_feedback
|
| 101 |
except (json.JSONDecodeError, KeyError) as e:
|
| 102 |
-
print(f"Error al
|
| 103 |
-
|
|
|
|
| 104 |
|
| 105 |
|
| 106 |
-
# ---
|
| 107 |
description = """
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
y una retroalimentación detallada.
|
| 111 |
"""
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
demo = gr.Interface(
|
| 114 |
fn=evaluate_pronunciation_openai,
|
| 115 |
inputs=[
|
| 116 |
gr.Audio(type="numpy", label="Sube tu Audio (.wav o .mp3)"),
|
| 117 |
-
gr.Textbox(lines=
|
| 118 |
],
|
| 119 |
outputs=[
|
| 120 |
gr.Number(label="Calificación General (0-100)"),
|
| 121 |
-
gr.
|
| 122 |
-
gr.Textbox(label="Análisis
|
| 123 |
-
gr.
|
| 124 |
-
gr.HighlightedText(
|
| 125 |
-
label="Retroalimentación por Palabra",
|
| 126 |
-
color_map={"Mejorar": "yellow"}
|
| 127 |
-
)
|
| 128 |
],
|
| 129 |
-
title="🤖 Evaluador de Pronunciación
|
| 130 |
description=description,
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
examples=[["mark_is_going_to_see_elephant.wav", "MARK IS GOING TO SEE ELEPHANT"]]
|
| 135 |
)
|
| 136 |
|
| 137 |
if __name__ == "__main__":
|
|
|
|
| 4 |
import os
|
| 5 |
from openai import OpenAI
|
| 6 |
import json
|
| 7 |
+
import soundfile as sf
|
| 8 |
|
| 9 |
# --- 1. Configurar el Cliente de OpenAI ---
|
|
|
|
| 10 |
try:
|
| 11 |
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 12 |
api_key_found = True
|
| 13 |
except TypeError:
|
| 14 |
api_key_found = False
|
| 15 |
|
| 16 |
+
# --- 2. El NUEVO Prompt: Más Técnico y Preciso ---
|
|
|
|
| 17 |
SYSTEM_PROMPT = """
|
| 18 |
+
Eres un lingüista computacional y experto en fonética inglesa, especializado en la evaluación de acentos para hablantes de inglés como segundo idioma (ESL).
|
| 19 |
+
Tu tarea es realizar un análisis fonético detallado de un audio, comparando la pronunciación del hablante con un modelo de Inglés Americano General (General American English).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
Debes basar tu análisis en estos tres pilares:
|
| 22 |
+
1. **Precisión Fonética (Phonetic Accuracy):** Compara la transcripción de Whisper con la transcripción de referencia. Para cada palabra, analiza si los fonemas clave fueron producidos correctamente.
|
| 23 |
+
2. **Prosodia (Prosody):** Evalúa el ritmo, la entonación y el acento de la frase. ¿La entonación sube y baja de forma natural? ¿Se acentúan las palabras correctas?
|
| 24 |
+
3. **Vinculación (Linking/Liaison):** Observa si el hablante conecta las palabras de manera fluida (ej. "is a" suena como /ɪzə/).
|
| 25 |
+
|
| 26 |
+
Tu respuesta DEBE ser un objeto JSON válido, sin texto adicional antes o después. La estructura es la siguiente:
|
| 27 |
{
|
| 28 |
"calificacion_general_100": integer,
|
| 29 |
+
"calificacion_precision_fonetica_100": integer,
|
| 30 |
+
"analisis_general": "string (Un resumen de dos o tres líneas sobre los puntos fuertes y las áreas de mejora principales del hablante.)",
|
| 31 |
+
"feedback_fonetico_por_palabra": [
|
| 32 |
+
{
|
| 33 |
+
"palabra": "string",
|
| 34 |
+
"ipa_correcta": "string (La transcripción fonética correcta en IPA, ej. /ˈreɪn.boʊ/)",
|
| 35 |
+
"error_detectado": "string (Describe el error fonético, ej. 'La vocal /eɪ/ se pronunció como /e/ de forma monoptonga.')",
|
| 36 |
+
"sugerencia": "string (Un consejo práctico para corregirlo, ej. 'Asegúrate de deslizar la lengua de la posición de 'e' a la de 'i' para crear el diptongo.')"
|
| 37 |
+
}
|
| 38 |
]
|
| 39 |
}
|
| 40 |
"""
|
| 41 |
|
| 42 |
+
# --- 3. La Función Principal (Actualizada para el nuevo JSON) ---
|
| 43 |
def evaluate_pronunciation_openai(audio_input, user_transcript):
|
|
|
|
|
|
|
|
|
|
| 44 |
if not api_key_found:
|
| 45 |
+
raise gr.Error("Clave de API de OpenAI no encontrada.")
|
| 46 |
|
| 47 |
if audio_input is None or not user_transcript:
|
| 48 |
+
# Devuelve valores por defecto para todos los outputs
|
| 49 |
+
return 0, 0, "Por favor, proporciona un audio y una transcripción.", "### Retroalimentación Detallada\nEsperando análisis..."
|
| 50 |
|
| 51 |
+
sr, waveform = audio_input
|
|
|
|
|
|
|
| 52 |
temp_audio_path = "temp_audio.wav"
|
|
|
|
| 53 |
sf.write(temp_audio_path, waveform, sr)
|
| 54 |
|
| 55 |
+
print("Transcribiendo audio...")
|
|
|
|
| 56 |
with open(temp_audio_path, "rb") as audio_file:
|
| 57 |
+
ai_transcript = client.audio.transcriptions.create(model="whisper-1", file=audio_file).text
|
|
|
|
|
|
|
|
|
|
| 58 |
|
|
|
|
| 59 |
user_prompt = f"""
|
| 60 |
+
Realiza el análisis fonético del audio proporcionado.
|
| 61 |
+
Frase de referencia (lo que el usuario intentaba decir): "{user_transcript}"
|
| 62 |
+
Transcripción generada por Whisper (lo que realmente se dijo): "{ai_transcript}"
|
| 63 |
"""
|
| 64 |
|
| 65 |
+
print("Enviando a GPT-4o para evaluación fonética...")
|
|
|
|
| 66 |
response = client.chat.completions.create(
|
| 67 |
model="gpt-4o",
|
| 68 |
response_format={"type": "json_object"},
|
|
|
|
| 72 |
]
|
| 73 |
)
|
| 74 |
|
|
|
|
| 75 |
try:
|
| 76 |
result = json.loads(response.choices[0].message.content)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
# Extraer datos del nuevo formato JSON
|
| 79 |
+
score_general = result.get("calificacion_general_100", 0)
|
| 80 |
+
score_phonetics = result.get("calificacion_precision_fonetica_100", 0)
|
| 81 |
+
analysis = result.get("analisis_general", "No se proporcionó análisis.")
|
| 82 |
+
feedback_list = result.get("feedback_fonetico_por_palabra", [])
|
| 83 |
+
|
| 84 |
+
# Formatear el feedback detallado como Markdown para una mejor visualización
|
| 85 |
+
markdown_feedback = "### Retroalimentación Fonética Detallada\n---\n"
|
| 86 |
+
if not feedback_list:
|
| 87 |
+
markdown_feedback += "¡Excelente pronunciación! No se detectaron errores específicos."
|
| 88 |
+
else:
|
| 89 |
+
for item in feedback_list:
|
| 90 |
+
markdown_feedback += f"**Palabra:** `{item.get('palabra', 'N/A')}`\n"
|
| 91 |
+
markdown_feedback += f"- **IPA Correcta:** `{item.get('ipa_correcta', 'N/A')}`\n"
|
| 92 |
+
markdown_feedback += f"- **Error Detectado:** {item.get('error_detectado', 'N/A')}\n"
|
| 93 |
+
markdown_feedback += f"- **Sugerencia:** {item.get('sugerencia', 'N/A')}\n\n"
|
| 94 |
+
|
| 95 |
+
return score_general, score_phonetics, analysis, markdown_feedback
|
| 96 |
|
|
|
|
| 97 |
except (json.JSONDecodeError, KeyError) as e:
|
| 98 |
+
print(f"Error al procesar la respuesta de la API: {e}")
|
| 99 |
+
error_message = "La respuesta de la API no tuvo el formato JSON esperado. Inténtalo de nuevo."
|
| 100 |
+
return 0, 0, error_message, f"### Error\n---\n{error_message}"
|
| 101 |
|
| 102 |
|
| 103 |
+
# --- 4. Definir y Lanzar la Interfaz de Gradio (Actualizada) ---
|
| 104 |
description = """
|
| 105 |
+
Usa la frase estándar para una evaluación completa o prueba con tus propias frases.
|
| 106 |
+
Sube tu audio y la transcripción. La IA analizará tu pronunciación fonema por fonema.
|
|
|
|
| 107 |
"""
|
| 108 |
|
| 109 |
+
# IMPORTANTE: Graba un audio diciendo la frase de ejemplo y guárdalo
|
| 110 |
+
# con este nombre en la misma carpeta que tu app.py
|
| 111 |
+
audio_ejemplo_path = "mark_is_going_to_see_elephant.wav"
|
| 112 |
+
frase_ejemplo_texto = "MARK IS GOING TO SEE ELEPHANT"
|
| 113 |
+
|
| 114 |
demo = gr.Interface(
|
| 115 |
fn=evaluate_pronunciation_openai,
|
| 116 |
inputs=[
|
| 117 |
gr.Audio(type="numpy", label="Sube tu Audio (.wav o .mp3)"),
|
| 118 |
+
gr.Textbox(lines=3, label="Escribe la Transcripción de Referencia", value=frase_ejemplo_texto)
|
| 119 |
],
|
| 120 |
outputs=[
|
| 121 |
gr.Number(label="Calificación General (0-100)"),
|
| 122 |
+
gr.Number(label="Precisión Fonética (0-100)"),
|
| 123 |
+
gr.Textbox(label="Análisis General"),
|
| 124 |
+
gr.Markdown(label="Feedback Detallado por Palabra") # CAMBIO: Markdown para mejor visualización
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
],
|
| 126 |
+
title="🤖 Evaluador Fonético de Pronunciación (v2)",
|
| 127 |
description=description,
|
| 128 |
+
examples=[
|
| 129 |
+
[audio_ejemplo_path, frase_ejemplo_texto]
|
| 130 |
+
]
|
|
|
|
| 131 |
)
|
| 132 |
|
| 133 |
if __name__ == "__main__":
|