evolutia 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,22 @@
1
- """
2
- Generador de variaciones de ejercicios con mayor complejidad.
3
- Utiliza APIs de IA para generar variaciones inteligentes.
4
- """
5
- import os
6
- import logging
7
- from typing import Dict, Optional
8
- from dotenv import load_dotenv
9
- from pathlib import Path
10
-
11
- # Imports for new Provider system
12
- from .llm_providers import get_provider
13
- from .utils.json_parser import extract_and_parse_json
14
-
15
- # Cargar variables de entorno explícitamente desde el directorio del script
16
- env_path = Path(__file__).parent / '.env'
17
- load_dotenv(dotenv_path=env_path)
18
-
19
- logger = logging.getLogger(__name__)
1
+ """
2
+ Generador de variaciones de ejercicios con mayor complejidad.
3
+ Utiliza APIs de IA para generar variaciones inteligentes.
4
+ """
5
+ import os
6
+ import logging
7
+ from typing import Dict, Optional, List, Union, Any
8
+ from dotenv import load_dotenv
9
+ from pathlib import Path
10
+
11
+ # Imports for new Provider system
12
+ from .llm_providers import get_provider, LLMProvider
13
+ from .utils.json_parser import extract_and_parse_json
14
+
15
+ # Cargar variables de entorno explícitamente desde el directorio del script
16
+ env_path = Path(__file__).parent / '.env'
17
+ load_dotenv(dotenv_path=env_path)
18
+
19
+ logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
22
 
@@ -37,28 +37,234 @@ class VariationGenerator:
37
37
 
38
38
  self._provider_instance = None
39
39
 
40
- def _get_provider(self):
41
- """Lazy loader para el proveedor, permitiendo configuración tardía de props."""
42
- if self._provider_instance:
43
- return self._provider_instance
44
-
45
- kwargs = {}
46
- if self.model_name:
47
- kwargs['model_name'] = self.model_name
48
- elif self.local_model and self.api_provider == 'local':
49
- kwargs['model_name'] = self.local_model
40
+ def _get_provider(self):
41
+ """
42
+ Lazy loader para el proveedor, permitiendo configuración tardía de props.
43
+
44
+ Returns:
45
+ Instancia de LLMProvider si la inicialización fue exitosa
46
+ None si hubo un error de configuración
47
+ """
48
+ if self._provider_instance:
49
+ return self._provider_instance
50
+
51
+ kwargs = {}
52
+ if self.model_name:
53
+ kwargs['model_name'] = self.model_name
54
+ elif self.local_model and self.api_provider == 'local':
55
+ kwargs['model_name'] = self.local_model
56
+
57
+ if self.base_url:
58
+ kwargs['base_url'] = self.base_url
59
+
60
+ try:
61
+ self._provider_instance = get_provider(self.api_provider, **kwargs)
62
+ logger.info(f"[VariationGenerator] Proveedor inicializado: {self.api_provider}")
63
+ return self._provider_instance
64
+ except ValueError as e:
65
+ logger.error(f"[VariationGenerator] Error inicializando proveedor '{self.api_provider}': {e}")
66
+ return None
67
+ except Exception as e:
68
+ logger.error(f"[VariationGenerator] Error inesperado inicializando proveedor '{self.api_provider}': {e}")
69
+ return None
70
+
71
+ def _create_prompt(self, exercise: Dict, analysis: Dict) -> str:
72
+ """Crea el prompt para generar una variación."""
73
+
74
+ content = exercise.get('content', '')
75
+ complexity = analysis.get('total_complexity', 0)
76
+ concepts = ", ".join(analysis.get('concepts', []))
77
+
78
+ prompt = f"""Actúa como un profesor experto de física y matemáticas universitarias.
79
+ Tu tarea es crear una VARIACIÓN de un ejercicio existente.
80
+
81
+ EJERCICIO ORIGINAL:
82
+ {content}
83
+
84
+ ANÁLISIS DE COMPLEJIDAD ORIGINAL:
85
+ - Complejidad: {complexity:.2f}
86
+ - Conceptos: {concepts}
87
+
88
+ OBJETIVO:
89
+ Generar una nueva versión del ejercicio que sea MÁS COMPLEJA y DESAFIANTE, pero evaluando los mismos principios fundamentales.
90
+
91
+ ESTRATEGIAS PARA AUMENTAR COMPLEJIDAD:
92
+ 1. Cambia las variables numéricas por parámetros simbólicos (a, b, R, etc.)
93
+ 2. Introduce sistemas de coordenadas diferentes (cilíndricas/esféricas) si aplica
94
+ 3. Combina múltiples conceptos en un solo problema
95
+ 4. Agrega una restricción o condición de borde adicional
96
+ 5. Pide una generalización del resultado
97
+
98
+ REGLAS DE FORMATO:
99
+ 1. Usa Markdown estándar
100
+ 2. Usa LaTeX para matemáticas (bloques :::math o $$...$$)
101
+ 3. La salida debe contener DOS PARTES separadas por "SOLUCIÓN REQUERIDA:"
102
+ - Parte 1: El enunciado del nuevo ejercicio (encabezado con "EJERCICIO VARIADO:")
103
+ - Parte 2: La solución paso a paso
104
+
105
+ Genera solo el contenido solicitado."""
106
+ return prompt
107
+
108
+ def _create_quiz_prompt(self, context_info: Dict[str, Any]) -> str:
109
+ """Crea prompt para ejercicios de selección única."""
110
+ content = context_info.get('content', '')
111
+
112
+ prompt = f"""Actúa como un profesor experto. Genera una pregunta de examen de tipo SELECCIÓN ÚNICA (Quiz) basada en el siguiente material:
113
+
114
+ MATERIAL BASE:
115
+ {content}
116
+
117
+ REQUISITOS:
118
+ 1. La pregunta debe ser conceptual y desafiante.
119
+ 2. Genera 4 opciones (A, B, C, D).
120
+ 3. Solo una opción debe ser correcta, las otras deben ser distractores plausibles.
121
+ 4. Devuelve la respuesta EXCLUSIVAMENTE en formato JSON válido:
122
+ {{
123
+ "question": "Enunciado de la pregunta en Markdown...",
124
+ "options": {{
125
+ "A": "Texto opción A",
126
+ "B": "Texto opción B",
127
+ "C": "Texto opción C",
128
+ "D": "Texto opción D"
129
+ }},
130
+ "correct_option": "A",
131
+ "explanation": "Explicación detallada de por qué es la correcta..."
132
+ }}
133
+ """
134
+ return prompt
135
+
136
+ def generate_variation(self, exercise: Dict[str, Any], analysis: Dict[str, Any], exercise_type: str = "development") -> Optional[Dict]:
137
+ """
138
+ Genera una variación de un ejercicio existente.
139
+ """
140
+ # 1. Crear prompt según tipo
141
+ if exercise_type == 'multiple_choice':
142
+ context_info = {
143
+ 'content': f"Ejercicio Original:\n{exercise.get('content')}"
144
+ }
145
+ prompt = self._create_quiz_prompt(context_info)
146
+ else:
147
+ prompt = self._create_prompt(exercise, analysis)
148
+
149
+ # 2. Get Provider
150
+ provider = self._get_provider()
151
+ if not provider:
152
+ logger.warning("[VariationGenerator] Proveedor no inicializado, no se puede generar variación")
153
+ return None
154
+
155
+ # 3. Generar
156
+ content = provider.generate_content(prompt, system_prompt="Eres un experto en diseño de exámenes de ingeniería.")
157
+
158
+ if not content:
159
+ logger.warning("[VariationGenerator] Proveedor retornó contenido vacío")
160
+ return None
161
+
162
+ # 4. Parsear respuesta
163
+ variation_content = ""
164
+ variation_solution = ""
165
+
166
+ if exercise_type == 'multiple_choice':
167
+ data = extract_and_parse_json(content)
168
+ if data and 'question' in data:
169
+ variation_content = f"{data['question']}\n\n"
170
+ for opt, text in data.get('options', {}).items():
171
+ variation_content += f"- **{opt})** {text}\n"
172
+ variation_solution = f"**Respuesta Correcta: {data.get('correct_option', '?')}**\n\n{data.get('explanation', '')}"
173
+ else:
174
+ variation_content = content
175
+ variation_solution = "Error parseando JSON de quiz."
176
+ else:
177
+ # Parseo texto plano
178
+ parts = content.split("SOLUCIÓN REQUERIDA:")
179
+ if len(parts) == 2:
180
+ variation_content = parts[0].replace("EJERCICIO VARIADO:", "").strip()
181
+ variation_solution = parts[1].strip()
182
+ else:
183
+ variation_content = content
184
+ variation_solution = ""
185
+
186
+ return {
187
+ 'variation_content': variation_content,
188
+ 'variation_solution': variation_solution,
189
+ 'original_frontmatter': exercise.get('frontmatter', {}),
190
+ 'original_label': exercise.get('label'),
191
+ 'type': exercise_type
192
+ }
193
+
194
+ def _create_new_exercise_prompt(self, topic: str, tags: list, context: Dict, difficulty: str) -> str:
195
+ """Crea prompt para ejercicio nuevo desde cero."""
196
+ tags_str = ", ".join(tags)
197
+
198
+ prompt = f"""Diseña un NUEVO ejercicio de examen universitario para:
199
+ Asignatura/Tema: {topic}
200
+ Conceptos Clave (Tags): {tags_str}
201
+ Nivel de Dificultad: {difficulty.upper()} (donde ALTA implica demostraciones o conexiones no triviales).
202
+
203
+ INSTRUCCIONES:
204
+ 1. Crea un problema original que evalúe comprensión profunda.
205
+ 2. No copies ejercicios de libros de texto.
206
+ 3. Formato de salida:
207
+ EJERCICIO NUEVO:
208
+ [Enunciado en Markdown con LaTeX]
209
+
210
+ SOLUCIÓN REQUERIDA:
211
+ [Solución paso a paso]
212
+ """
213
+ return prompt
214
+
215
+ def generate_new_exercise_from_topic(self, topic: str, tags: Optional[List[str]] = None, difficulty: str = "alta", exercise_type: str = "development") -> Optional[Dict]:
216
+ """
217
+ Genera un ejercicio nuevo desde cero.
218
+ """
219
+ tags = tags or []
220
+ context = {} # Base implementations doesn't use context
221
+
222
+ # 1. Crear prompt
223
+ if exercise_type == 'multiple_choice':
224
+ context_info = {
225
+ 'content': f"Tema: {topic}\nTags: {', '.join(tags)}\nDificultad: {difficulty}"
226
+ }
227
+ prompt = self._create_quiz_prompt(context_info)
228
+ else:
229
+ prompt = self._create_new_exercise_prompt(topic, tags, context, difficulty)
50
230
 
51
- if self.base_url and self.api_provider == 'local':
52
- kwargs['base_url'] = self.base_url
231
+ # 2. Get Provider
232
+ provider = self._get_provider()
233
+ if not provider: return None
234
+
235
+ # 3. Generar
236
+ content = provider.generate_content(prompt)
237
+ if not content: return None
238
+
239
+ # 4. Parsear
240
+ # Reutilizamos lógica simple de parseo
241
+ if exercise_type == 'multiple_choice':
242
+ data = extract_and_parse_json(content)
243
+ if data and 'question' in data:
244
+ var_content = f"{data['question']}\n\n"
245
+ # ... (simplificado, igual que arriba)
246
+ for k, v in data.get('options',{}).items():
247
+ var_content += f"- **{k})** {v}\n"
248
+ var_sol = f"R: {data.get('correct_option')}. {data.get('explanation')}"
249
+ else:
250
+ var_content = content
251
+ var_sol = ""
252
+ else:
253
+ parts = content.split("SOLUCIÓN REQUERIDA:")
254
+ if len(parts) == 2:
255
+ var_content = parts[0].replace("EJERCICIO NUEVO:", "").strip()
256
+ var_sol = parts[1].strip()
257
+ else:
258
+ var_content = content
259
+ var_sol = ""
260
+
261
+ return {
262
+ 'variation_content': var_content,
263
+ 'variation_solution': var_sol,
264
+ 'original_frontmatter': {'topic': topic, 'tags': tags},
265
+ 'type': exercise_type
266
+ }
53
267
 
54
- try:
55
- self._provider_instance = get_provider(self.api_provider, **kwargs)
56
- except ValueError as e:
57
- logger.error(f"Error inicializando proveedor: {e}")
58
- return None
59
-
60
- return self._provider_instance
61
-
62
268
  def generate_variation_with_solution(self, exercise: Dict, analysis: Dict) -> Optional[Dict]:
63
269
  """
64
270
  Genera una variación con su solución.
@@ -69,23 +275,19 @@ class VariationGenerator:
69
275
  if not variation:
70
276
  return None
71
277
 
278
+ # Si ya tiene solución (porque el prompt único la pidió), retornarla
279
+ if variation.get('variation_solution'):
280
+ return variation
281
+
72
282
  provider = self._get_provider()
73
283
  if not provider: return None
74
284
 
75
- # Luego generar la solución
76
- solution_prompt = f"""Eres un experto en métodos matemáticos para física e ingeniería. Resuelve el siguiente ejercicio paso a paso, mostrando todos los cálculos y procedimientos.
77
-
285
+ # Si no, generar la solución por separado (fallback legacy)
286
+ solution_prompt = f"""Eres un experto en métodos matemáticos. Resuelve el siguiente ejercicio paso a paso:
287
+
78
288
  EJERCICIO:
79
289
  {variation['variation_content']}
80
290
 
81
- INSTRUCCIONES:
82
- 1. Resuelve el ejercicio de forma completa y detallada
83
- 2. Muestra todos los pasos intermedios
84
- 3. Usa notación matemática LaTeX correcta
85
- 4. Explica el razonamiento cuando sea necesario
86
- 5. Usa bloques :::{{math}} para ecuaciones display y $...$ para inline
87
- 6. Escribe en español
88
-
89
291
  GENERA LA SOLUCIÓN COMPLETA:"""
90
292
 
91
293
  solution_content = provider.generate_content(solution_prompt)