mramirez2001 commited on
Commit
cc1c0a9
·
verified ·
1 Parent(s): b4d513c

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +137 -88
  2. requirements.txt +5 -1
app.py CHANGED
@@ -4,65 +4,115 @@ import gradio as gr
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"},
@@ -71,64 +121,63 @@ def evaluate_pronunciation_openai(audio_input, user_transcript):
71
  {"role": "user", "content": user_prompt}
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__":
134
- demo.launch()
 
 
 
 
4
  import os
5
  from openai import OpenAI
6
  import json
7
+ import librosa
8
+ import numpy as np
9
  import soundfile as sf
10
+ import whisper
11
 
12
+ # --- 0. CONFIGURACIÓN INICIAL ---
13
  try:
14
  client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
15
  api_key_found = True
16
  except TypeError:
17
  api_key_found = False
18
 
19
+ print("Cargando modelo de Whisper...")
20
+ whisper_model = whisper.load_model("base")
21
+ print("Modelo de Whisper cargado.")
22
+
23
+ # --- NUEVO SÚPER-PROMPT ---
24
  SYSTEM_PROMPT = """
25
+ Eres un examinador de inglés de Cambridge con un doctorado en fonética. Tu tarea es realizar una evaluación integral de la pronunciación y fluidez de un hablante no nativo.
26
+
27
+ **Recibirás:**
28
+ 1. La **frase de referencia** que el usuario intentaba decir.
29
+ 2. La **transcripción literal** de su audio, generada por Whisper.
30
+ 3. **Métricas de fluidez**: WPM (palabras por minuto) y número de pausas.
31
 
32
+ **Tu proceso de análisis debe ser el siguiente:**
 
 
 
33
 
34
+ 1. **Análisis Palabra por Palabra**: Compara la "frase de referencia" con la "transcripción de Whisper". Para cada palabra en la frase de referencia, determina si fue:
35
+ * **Correcta (100%)**: Pronunciada de forma clara e inteligible.
36
+ * **Incorrecta (0-50%)**: Pronunciada de forma que cambia el significado o es difícil de entender.
37
+ * **Omitida (0%)**: No se dijo.
38
+ 2. **Calcular Accuracy Total**: Calcula el promedio de la puntuación de todas las palabras para obtener una calificación global de 0 a 100.
39
+ 3. **Evaluar Fluidez**: Usa las métricas de WPM y pausas para escribir un breve análisis sobre el ritmo y la cadencia del hablante. (Ej. WPM < 100 es lento, WPM > 140 es fluido).
40
+ 4. **Asignar Nivel MCERL**: Basándote en el accuracy, la fluidez y la complejidad de los errores, asigna un nivel de Marco Común Europeo (A1, A2, B1, B2, C1).
41
+ 5. **Generar Feedback Adaptativo**: Crea una respuesta en inglés para el usuario. La respuesta debe ser retadora pero comprensible según el nivel que le asignaste.
42
+
43
+ **Tu respuesta DEBE ser únicamente un objeto JSON con esta estructura exacta:**
44
  {
45
+ "calificacion_general_100": integer,
46
+ "nivel_mcerl_estimado": "string (ej. B1)",
47
+ "analisis_por_palabra": [
48
+ {
49
+ "palabra": "string",
50
+ "accuracy": integer,
51
+ "feedback": "string (Feedback específico si hay un error, ej. 'La vocal 'a' sonó como 'e'.')"
52
+ }
53
+ ],
54
+ "feedback_general_html": "string (Un párrafo en HTML con tu análisis sobre fluidez, prosodia y consejos generales.)",
55
+ "respuesta_adaptada_al_usuario": "string (El feedback final para el usuario, escrito en un inglés apropiado para su nivel.)"
56
  }
57
  """
58
 
59
+
60
+ # --- 1. FUNCIÓN DE EXTRACCIÓN DE CARACTERÍSTICAS (SIMPLIFICADA) ---
61
+ def extract_audio_metrics(audio_path):
62
+ try:
63
+ y, sr = librosa.load(audio_path, sr=16000)
64
+ duration = librosa.get_duration(y=y, sr=sr)
65
+ if duration < 0.2: return {}
66
+
67
+ result = whisper_model.transcribe(audio_path, word_timestamps=True, fp16=False)
68
+ if not result["segments"] or not result["segments"][0]["words"]: return {"text": result.get("text", "")}
69
+
70
+ words = result["segments"][0]["words"]
71
+ num_words = len(words)
72
+ wpm = (num_words / duration) * 60 if duration > 0 else 0
73
+
74
+ pauses = 0
75
+ for i in range(len(words) - 1):
76
+ if words[i+1]['start'] - words[i]['end'] > 0.5:
77
+ pauses += 1
78
+
79
+ return {
80
+ "text": result.get("text", ""),
81
+ "wpm": round(wpm, 2),
82
+ "num_pauses": pauses
83
+ }
84
+ except Exception as e:
85
+ print(f"Error en extracción de métricas: {e}")
86
+ return {}
87
+
88
+
89
+ # --- 2. FUNCIÓN PRINCIPAL DE EVALUACIÓN (TODO CON LA API) ---
90
+ def evaluate_with_openai(audio_input, reference_transcript):
91
  if not api_key_found:
92
  raise gr.Error("Clave de API de OpenAI no encontrada.")
93
+ if audio_input is None or not reference_transcript:
94
+ return 0, "N/A", "Proporciona un audio y una transcripción.", "Esperando análisis...", "Esperando análisis..."
 
 
95
 
96
+ sr, y = audio_input
97
  temp_audio_path = "temp_audio.wav"
98
+ sf.write(temp_audio_path, y, sr)
99
 
100
+ # 1. Extraer métricas básicas del audio
101
+ audio_metrics = extract_audio_metrics(temp_audio_path)
102
+ if not audio_metrics:
103
+ return 0, "Error", "No se pudo procesar el audio.", "Error", "Error"
104
+
105
+ # 2. Construir el prompt para la API
106
  user_prompt = f"""
107
+ **Frase de referencia:** "{reference_transcript}"
108
+ **Transcripción de Whisper:** "{audio_metrics.get('text', '')}"
109
+ **Métricas de fluidez:**
110
+ - WPM: {audio_metrics.get('wpm', 'N/A')}
111
+ - Pausas (>0.5s): {audio_metrics.get('num_pauses', 'N/A')}
112
  """
113
+
114
+ # 3. Llamar a la API de OpenAI
115
+ print("Enviando a GPT-4o para evaluación completa...")
116
  response = client.chat.completions.create(
117
  model="gpt-4o",
118
  response_format={"type": "json_object"},
 
121
  {"role": "user", "content": user_prompt}
122
  ]
123
  )
124
+
125
+ # 4. Procesar y formatear la respuesta JSON
126
  try:
127
  result = json.loads(response.choices[0].message.content)
128
 
129
+ # Formatear el análisis por palabra en Markdown
130
+ word_analysis_md = "### Análisis por Palabra\n| Palabra | Puntuación | Feedback |\n| :--- | :--- | :--- |\n"
131
+ for item in result.get("analisis_por_palabra", []):
132
+ word_analysis_md += f"| {item.get('palabra')} | {item.get('accuracy')}% | {item.get('feedback')} |\n"
133
+
134
+ return (
135
+ result.get("calificacion_general_100", 0),
136
+ result.get("nivel_mcerl_estimado", "N/A"),
137
+ gr.HTML(result.get("feedback_general_html", "")),
138
+ word_analysis_md,
139
+ result.get("respuesta_adaptada_al_usuario", "")
140
+ )
 
 
 
 
 
 
141
 
142
  except (json.JSONDecodeError, KeyError) as e:
143
+ print(f"Error procesando la respuesta de la API: {e}")
144
+ error_msg = "La respuesta de la API no tuvo el formato esperado. Inténtalo de nuevo."
145
+ return 0, "Error", error_msg, error_msg, error_msg
146
 
147
 
148
+ # --- 3. INTERFAZ DE GRADIO ---
149
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
150
+ gr.Markdown("# 🤖 Evaluador de Pronunciación Impulsado por IA (v4)")
151
+ gr.Markdown("Graba tu voz diciendo la frase de referencia. La IA analizará cada palabra, tu fluidez, y te dará una calificación y feedback adaptado a tu nivel.")
 
152
 
153
+ frase_ejemplo = "The rainbow is a division of white light into many beautiful colors."
154
+
155
+ with gr.Row():
156
+ with gr.Column(scale=1):
157
+ audio_in = gr.Audio(sources=["microphone"], type="numpy", label="1. Graba tu voz aquí")
158
+ text_in = gr.Textbox(lines=3, label="2. Frase de Referencia", value=frase_ejemplo)
159
+ submit_btn = gr.Button("Evaluar Pronunciación", variant="primary")
160
+
161
+ with gr.Column(scale=2):
162
+ gr.Markdown("### Resultados de la Evaluación")
163
+ with gr.Row():
164
+ score_out = gr.Number(label="Calificación General (0-100)", scale=1)
165
+ level_out = gr.Textbox(label="Nivel MCERL Estimado", scale=1)
166
+
167
+ feedback_html_out = gr.HTML(label="Análisis General de Fluidez y Prosodia")
168
+ word_analysis_out = gr.Markdown(label="Detalle por Palabra")
169
+
170
+ gr.Markdown("--- \n ### Mensaje de tu Tutor de IA")
171
+ adaptive_response_out = gr.Textbox(label="Feedback Adaptado a tu Nivel", lines=5)
172
+
173
+ submit_btn.click(
174
+ fn=evaluate_with_openai,
175
+ inputs=[audio_in, text_in],
176
+ outputs=[score_out, level_out, feedback_html_out, word_analysis_out, adaptive_response_out]
177
+ )
178
 
179
  if __name__ == "__main__":
180
+ if not api_key_found:
181
+ print("\nFATAL: Clave de API de OpenAI no encontrada.")
182
+ else:
183
+ demo.launch(debug=True)
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
  gradio
2
  openai
3
- soundfile
 
 
 
 
 
1
  gradio
2
  openai
3
+ librosa
4
+ numpy
5
+ soundfile
6
+ # Para Whisper con soporte de marcas de tiempo, se instala de forma especial:
7
+ git+https://github.com/openai/whisper.git