evolutia 0.1.3__py3-none-any.whl → 0.1.5__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.
- evolutia/evolutia_engine.py +249 -237
- evolutia/llm_providers.py +14 -2
- evolutia/schemas/config.schema.json +210 -0
- evolutia/validation/__init__.py +2 -1
- evolutia/variation_generator.py +86 -86
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/METADATA +1 -1
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/RECORD +12 -11
- evolutia_cli.py +20 -19
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/WHEEL +0 -0
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/entry_points.txt +0 -0
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {evolutia-0.1.3.dist-info → evolutia-0.1.5.dist-info}/top_level.txt +0 -0
evolutia/evolutia_engine.py
CHANGED
|
@@ -11,40 +11,40 @@ import time
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import List, Dict, Optional, Tuple, Any, Union
|
|
13
13
|
from tqdm import tqdm
|
|
14
|
-
|
|
15
|
-
# Imports from internal modules
|
|
16
|
-
from .material_extractor import MaterialExtractor
|
|
17
|
-
from .exercise_analyzer import ExerciseAnalyzer
|
|
18
|
-
from .variation_generator import VariationGenerator
|
|
19
|
-
from .complexity_validator import ComplexityValidator
|
|
20
|
-
from .exam_generator import ExamGenerator
|
|
21
|
-
from .config_manager import ConfigManager
|
|
22
|
-
|
|
23
|
-
# Conditional RAG imports
|
|
24
|
-
try:
|
|
25
|
-
from .rag.rag_manager import RAGManager
|
|
26
|
-
from .rag.enhanced_variation_generator import EnhancedVariationGenerator
|
|
27
|
-
from .rag.consistency_validator import ConsistencyValidator
|
|
28
|
-
RAG_AVAILABLE = True
|
|
29
|
-
except ImportError:
|
|
30
|
-
RAG_AVAILABLE = False
|
|
31
|
-
|
|
32
|
-
logger = logging.getLogger(__name__)
|
|
33
|
-
|
|
34
|
-
class EvolutiaEngine:
|
|
35
|
-
"""
|
|
36
|
-
Motor central que coordina el flujo de trabajo de EvolutIA.
|
|
37
|
-
"""
|
|
38
|
-
|
|
14
|
+
|
|
15
|
+
# Imports from internal modules
|
|
16
|
+
from .material_extractor import MaterialExtractor
|
|
17
|
+
from .exercise_analyzer import ExerciseAnalyzer
|
|
18
|
+
from .variation_generator import VariationGenerator
|
|
19
|
+
from .complexity_validator import ComplexityValidator
|
|
20
|
+
from .exam_generator import ExamGenerator
|
|
21
|
+
from .config_manager import ConfigManager
|
|
22
|
+
|
|
23
|
+
# Conditional RAG imports
|
|
24
|
+
try:
|
|
25
|
+
from .rag.rag_manager import RAGManager
|
|
26
|
+
from .rag.enhanced_variation_generator import EnhancedVariationGenerator
|
|
27
|
+
from .rag.consistency_validator import ConsistencyValidator
|
|
28
|
+
RAG_AVAILABLE = True
|
|
29
|
+
except ImportError:
|
|
30
|
+
RAG_AVAILABLE = False
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
class EvolutiaEngine:
|
|
35
|
+
"""
|
|
36
|
+
Motor central que coordina el flujo de trabajo de EvolutIA.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
39
|
def __init__(self, base_path: Union[Path, str], config_path: Optional[Union[Path, str]] = None):
|
|
40
40
|
self.base_path = Path(base_path)
|
|
41
41
|
self.config_path = Path(config_path) if config_path else None
|
|
42
42
|
self.rag_manager = None
|
|
43
|
-
|
|
44
|
-
# Load configuration manager
|
|
45
|
-
self.config_manager = ConfigManager(base_path, config_path)
|
|
46
|
-
self.full_config = self.config_manager.load_current_config()
|
|
47
|
-
|
|
43
|
+
|
|
44
|
+
# Load configuration manager
|
|
45
|
+
self.config_manager = ConfigManager(base_path, config_path)
|
|
46
|
+
self.full_config = self.config_manager.load_current_config()
|
|
47
|
+
|
|
48
48
|
def initialize_rag(self, force_reindex: bool = False) -> bool:
|
|
49
49
|
"""
|
|
50
50
|
Inicializa el subsistema RAG si está disponible.
|
|
@@ -65,70 +65,70 @@ class EvolutiaEngine:
|
|
|
65
65
|
except Exception as e:
|
|
66
66
|
logger.error(f"[EvolutiaEngine] Error inicializando RAG (force_reindex={force_reindex}): {e}")
|
|
67
67
|
return False
|
|
68
|
-
|
|
69
|
-
def get_api_config(self, provider: str) -> Dict[str, Any]:
|
|
70
|
-
"""Obtiene la configuración específica para una API."""
|
|
71
|
-
return self.full_config.get('api', {}).get(provider, {})
|
|
72
|
-
|
|
73
|
-
def extract_materials_and_exercises(self, topics: List[str], label_filter: Optional[List[str]] = None) -> Tuple[List[Dict], List[Dict]]:
|
|
74
|
-
"""
|
|
75
|
-
Paso 1 & 2: Extrae materiales y lista todos los ejercicios disponibles.
|
|
76
|
-
"""
|
|
77
|
-
logger.info("Paso 1: Extrayendo materiales didácticos...")
|
|
78
|
-
extractor = MaterialExtractor(self.base_path)
|
|
79
|
-
materials = []
|
|
80
|
-
|
|
81
|
-
# 1. Extract by topic
|
|
82
|
-
if topics:
|
|
83
|
-
for topic in topics:
|
|
84
|
-
topic_materials = extractor.extract_by_topic(topic)
|
|
85
|
-
if topic_materials:
|
|
86
|
-
materials.extend(topic_materials)
|
|
87
|
-
else:
|
|
88
|
-
logger.warning(f"No se encontraron materiales para el tema: {topic}")
|
|
89
|
-
|
|
90
|
-
# 2. Fallback: Search all if no materials found yet or topics were empty (e.g., list mode)
|
|
91
|
-
if not materials:
|
|
92
|
-
logger.info("Buscando en todos los directorios...")
|
|
93
|
-
for topic_dir in self.base_path.iterdir():
|
|
94
|
-
if topic_dir.is_dir() and topic_dir.name not in ['_build', 'evolutia', 'proyecto', '.git']:
|
|
95
|
-
materials.extend(extractor.extract_from_directory(topic_dir))
|
|
96
|
-
|
|
97
|
-
if not materials:
|
|
98
|
-
return [], []
|
|
99
|
-
|
|
100
|
-
logger.info(f"Encontrados {len(materials)} archivos con materiales")
|
|
101
|
-
|
|
102
|
-
# Get exercises
|
|
103
|
-
logger.info("Paso 2: Obteniendo ejercicios...")
|
|
104
|
-
all_exercises = extractor.get_all_exercises(materials)
|
|
105
|
-
|
|
106
|
-
# Filter by label if requested
|
|
107
|
-
if label_filter:
|
|
108
|
-
logger.info(f"Filtrando por labels: {label_filter}")
|
|
109
|
-
filtered = [ex for ex in all_exercises if ex.get('label') in label_filter]
|
|
110
|
-
if not filtered:
|
|
111
|
-
available = [ex.get('label') for ex in all_exercises if ex.get('label')]
|
|
112
|
-
logger.warning(f"No se encontraron ejercicios con los labels solicitados. Disponibles: {available[:10]}...")
|
|
113
|
-
all_exercises = filtered
|
|
114
|
-
|
|
115
|
-
logger.info(f"Encontrados {len(all_exercises)} ejercicios")
|
|
116
|
-
return materials, all_exercises
|
|
117
|
-
|
|
118
|
-
def analyze_exercises(self, exercises: List[Dict]) -> List[Tuple[Dict, Dict]]:
|
|
119
|
-
"""Paso 3: Analiza la complejidad de los ejercicios."""
|
|
120
|
-
logger.info("Paso 3: Analizando complejidad de ejercicios...")
|
|
121
|
-
analyzer = ExerciseAnalyzer()
|
|
122
|
-
exercises_with_analysis = []
|
|
123
|
-
|
|
124
|
-
for exercise in exercises:
|
|
125
|
-
analysis = analyzer.analyze(exercise)
|
|
126
|
-
exercises_with_analysis.append((exercise, analysis))
|
|
127
|
-
|
|
128
|
-
# Sort by total complexity descending
|
|
129
|
-
exercises_with_analysis.sort(key=lambda x: x[1]['total_complexity'], reverse=True)
|
|
130
|
-
return exercises_with_analysis
|
|
131
|
-
|
|
68
|
+
|
|
69
|
+
def get_api_config(self, provider: str) -> Dict[str, Any]:
|
|
70
|
+
"""Obtiene la configuración específica para una API."""
|
|
71
|
+
return self.full_config.get('api', {}).get(provider, {})
|
|
72
|
+
|
|
73
|
+
def extract_materials_and_exercises(self, topics: List[str], label_filter: Optional[List[str]] = None) -> Tuple[List[Dict], List[Dict]]:
|
|
74
|
+
"""
|
|
75
|
+
Paso 1 & 2: Extrae materiales y lista todos los ejercicios disponibles.
|
|
76
|
+
"""
|
|
77
|
+
logger.info("Paso 1: Extrayendo materiales didácticos...")
|
|
78
|
+
extractor = MaterialExtractor(self.base_path)
|
|
79
|
+
materials = []
|
|
80
|
+
|
|
81
|
+
# 1. Extract by topic
|
|
82
|
+
if topics:
|
|
83
|
+
for topic in topics:
|
|
84
|
+
topic_materials = extractor.extract_by_topic(topic)
|
|
85
|
+
if topic_materials:
|
|
86
|
+
materials.extend(topic_materials)
|
|
87
|
+
else:
|
|
88
|
+
logger.warning(f"No se encontraron materiales para el tema: {topic}")
|
|
89
|
+
|
|
90
|
+
# 2. Fallback: Search all if no materials found yet or topics were empty (e.g., list mode)
|
|
91
|
+
if not materials:
|
|
92
|
+
logger.info("Buscando en todos los directorios...")
|
|
93
|
+
for topic_dir in self.base_path.iterdir():
|
|
94
|
+
if topic_dir.is_dir() and topic_dir.name not in ['_build', 'evolutia', 'proyecto', '.git']:
|
|
95
|
+
materials.extend(extractor.extract_from_directory(topic_dir))
|
|
96
|
+
|
|
97
|
+
if not materials:
|
|
98
|
+
return [], []
|
|
99
|
+
|
|
100
|
+
logger.info(f"Encontrados {len(materials)} archivos con materiales")
|
|
101
|
+
|
|
102
|
+
# Get exercises
|
|
103
|
+
logger.info("Paso 2: Obteniendo ejercicios...")
|
|
104
|
+
all_exercises = extractor.get_all_exercises(materials)
|
|
105
|
+
|
|
106
|
+
# Filter by label if requested
|
|
107
|
+
if label_filter:
|
|
108
|
+
logger.info(f"Filtrando por labels: {label_filter}")
|
|
109
|
+
filtered = [ex for ex in all_exercises if ex.get('label') in label_filter]
|
|
110
|
+
if not filtered:
|
|
111
|
+
available = [ex.get('label') for ex in all_exercises if ex.get('label')]
|
|
112
|
+
logger.warning(f"No se encontraron ejercicios con los labels solicitados. Disponibles: {available[:10]}...")
|
|
113
|
+
all_exercises = filtered
|
|
114
|
+
|
|
115
|
+
logger.info(f"Encontrados {len(all_exercises)} ejercicios")
|
|
116
|
+
return materials, all_exercises
|
|
117
|
+
|
|
118
|
+
def analyze_exercises(self, exercises: List[Dict]) -> List[Tuple[Dict, Dict]]:
|
|
119
|
+
"""Paso 3: Analiza la complejidad de los ejercicios."""
|
|
120
|
+
logger.info("Paso 3: Analizando complejidad de ejercicios...")
|
|
121
|
+
analyzer = ExerciseAnalyzer()
|
|
122
|
+
exercises_with_analysis = []
|
|
123
|
+
|
|
124
|
+
for exercise in exercises:
|
|
125
|
+
analysis = analyzer.analyze(exercise)
|
|
126
|
+
exercises_with_analysis.append((exercise, analysis))
|
|
127
|
+
|
|
128
|
+
# Sort by total complexity descending
|
|
129
|
+
exercises_with_analysis.sort(key=lambda x: x[1]['total_complexity'], reverse=True)
|
|
130
|
+
return exercises_with_analysis
|
|
131
|
+
|
|
132
132
|
def _generate_single_variation(
|
|
133
133
|
self,
|
|
134
134
|
generator: Union['VariationGenerator', 'EnhancedVariationGenerator'],
|
|
@@ -138,43 +138,46 @@ class EvolutiaEngine:
|
|
|
138
138
|
args: argparse.Namespace
|
|
139
139
|
) -> Optional[Dict]:
|
|
140
140
|
"""Helper para generar una única variación (thread-safe logic)."""
|
|
141
|
-
attempt_count = 0
|
|
142
|
-
while attempt_count < 3:
|
|
143
|
-
try:
|
|
144
|
-
# Generate
|
|
145
|
-
if args.type == 'multiple_choice':
|
|
146
|
-
variation = generator.generate_variation(
|
|
147
|
-
exercise_base,
|
|
148
|
-
analysis,
|
|
149
|
-
exercise_type=args.type
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
validation = validator.validate(exercise_base, analysis, variation)
|
|
173
|
-
is_valid = validation['is_valid']
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
141
|
+
attempt_count = 0
|
|
142
|
+
while attempt_count < 3:
|
|
143
|
+
try:
|
|
144
|
+
# Generate
|
|
145
|
+
if args.type == 'multiple_choice':
|
|
146
|
+
variation = generator.generate_variation(
|
|
147
|
+
exercise_base,
|
|
148
|
+
analysis,
|
|
149
|
+
exercise_type=args.type,
|
|
150
|
+
max_tokens=args.max_tokens
|
|
151
|
+
)
|
|
152
|
+
elif not args.no_generar_soluciones:
|
|
153
|
+
variation = generator.generate_variation_with_solution(
|
|
154
|
+
exercise_base,
|
|
155
|
+
analysis,
|
|
156
|
+
max_tokens=args.max_tokens
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
variation = generator.generate_variation(
|
|
160
|
+
exercise_base,
|
|
161
|
+
analysis,
|
|
162
|
+
exercise_type=args.type,
|
|
163
|
+
max_tokens=args.max_tokens
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if not variation:
|
|
167
|
+
attempt_count += 1
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Validate
|
|
171
|
+
if args.use_rag:
|
|
172
|
+
validation = validator.validate(exercise_base, analysis, variation)
|
|
173
|
+
is_valid = validation['is_valid']
|
|
174
|
+
else:
|
|
175
|
+
validation = validator.validate(exercise_base, analysis, variation)
|
|
176
|
+
is_valid = validation['is_valid']
|
|
177
|
+
|
|
178
|
+
if is_valid:
|
|
179
|
+
return variation
|
|
180
|
+
|
|
178
181
|
except Exception as e:
|
|
179
182
|
logger.error(f"[EvolutiaEngine] Error en hilo de generación (intento {attempt_count + 1}/3): {e}")
|
|
180
183
|
|
|
@@ -188,7 +191,8 @@ class EvolutiaEngine:
|
|
|
188
191
|
topic: str,
|
|
189
192
|
tags: List[str],
|
|
190
193
|
complexity: str,
|
|
191
|
-
ex_type: str
|
|
194
|
+
ex_type: str,
|
|
195
|
+
args: argparse.Namespace
|
|
192
196
|
) -> Optional[Dict]:
|
|
193
197
|
"""
|
|
194
198
|
Helper para modo creación.
|
|
@@ -202,12 +206,13 @@ class EvolutiaEngine:
|
|
|
202
206
|
topic,
|
|
203
207
|
tags,
|
|
204
208
|
difficulty=complexity,
|
|
205
|
-
exercise_type=ex_type
|
|
209
|
+
exercise_type=ex_type,
|
|
210
|
+
max_tokens=args.max_tokens
|
|
206
211
|
)
|
|
207
212
|
except Exception as e:
|
|
208
213
|
logger.error(f"[EvolutiaEngine] Error en creación de ejercicio nuevo (topic={topic}): {e}")
|
|
209
214
|
return None
|
|
210
|
-
|
|
215
|
+
|
|
211
216
|
def generate_variations_parallel(
|
|
212
217
|
self,
|
|
213
218
|
selected_exercises: List[Tuple[Dict, Dict]],
|
|
@@ -217,83 +222,83 @@ class EvolutiaEngine:
|
|
|
217
222
|
"""
|
|
218
223
|
Paso 4: Genera variaciones en paralelo.
|
|
219
224
|
"""
|
|
220
|
-
logger.info(f"Paso 4: Generando variaciones en paralelo (Workers: {max_workers})...")
|
|
221
|
-
|
|
222
|
-
# Setup Generator
|
|
223
|
-
api_config = self.get_api_config(args.api)
|
|
224
|
-
|
|
225
|
-
if (args.use_rag and self.rag_manager) or args.mode == 'creation':
|
|
226
|
-
retriever = self.rag_manager.get_retriever() if (args.use_rag and self.rag_manager) else None
|
|
227
|
-
generator = EnhancedVariationGenerator(api_provider=args.api, retriever=retriever)
|
|
228
|
-
validator = ConsistencyValidator(retriever=retriever) if retriever else ComplexityValidator()
|
|
229
|
-
else:
|
|
230
|
-
generator = VariationGenerator(api_provider=args.api)
|
|
231
|
-
validator = ComplexityValidator()
|
|
232
|
-
|
|
233
|
-
# Configure model
|
|
234
|
-
if args.api == 'local':
|
|
235
|
-
generator.base_url = args.base_url or api_config.get('base_url', "http://localhost:11434/v1")
|
|
236
|
-
generator.local_model = args.model or api_config.get('model', "llama3")
|
|
237
|
-
elif args.api == 'generic':
|
|
238
|
-
generator.base_url = args.base_url or api_config.get('base_url')
|
|
239
|
-
generator.model_name = args.model or api_config.get('model')
|
|
240
|
-
elif args.api in ['openai', 'anthropic', 'deepseek', 'gemini']:
|
|
241
|
-
if args.model:
|
|
242
|
-
generator.model_name = args.model
|
|
243
|
-
elif 'model' in api_config:
|
|
244
|
-
generator.model_name = api_config['model']
|
|
245
|
-
|
|
246
|
-
# Determine tasks based on mode
|
|
247
|
-
tasks = []
|
|
248
|
-
|
|
249
|
-
if args.mode == 'creation':
|
|
250
|
-
# Creation Mode Logic
|
|
251
|
-
for i in range(args.num_ejercicios):
|
|
252
|
-
current_topic = args.tema[i % len(args.tema)]
|
|
253
|
-
current_tags = [args.tags[i % len(args.tags)]] if args.tags else [current_topic]
|
|
254
|
-
|
|
255
|
-
tasks.append({
|
|
256
|
-
'func': self._generate_creation_mode,
|
|
257
|
-
'args': (generator, current_topic, current_tags, args.complejidad, args.type)
|
|
258
|
-
})
|
|
259
|
-
else:
|
|
260
|
-
# Variation Mode Logic
|
|
261
|
-
|
|
262
|
-
# If explicit lables, use exactly those
|
|
263
|
-
if args.label:
|
|
264
|
-
target_exercises = list(selected_exercises)
|
|
265
|
-
else:
|
|
266
|
-
# Random selection to fill num_ejercicios
|
|
267
|
-
target_exercises = []
|
|
268
|
-
candidates = selected_exercises[:max(5, len(selected_exercises)//2)]
|
|
269
|
-
for _ in range(args.num_ejercicios):
|
|
270
|
-
if candidates:
|
|
271
|
-
target_exercises.append(random.choice(candidates))
|
|
272
|
-
|
|
273
|
-
for ex_base, analysis in target_exercises:
|
|
274
|
-
tasks.append({
|
|
275
|
-
'func': self._generate_single_variation,
|
|
276
|
-
'args': (generator, validator, ex_base, analysis, args)
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
# Execute Parallel
|
|
280
|
-
valid_variations = []
|
|
281
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
282
|
-
future_to_task = {}
|
|
283
|
-
for t in tasks:
|
|
284
|
-
future = executor.submit(t['func'], *t['args'])
|
|
285
|
-
future_to_task[future] = t
|
|
286
|
-
# Stagger requests to avoid hitting rate limits instantly
|
|
287
|
-
time.sleep(1.0)
|
|
288
|
-
|
|
289
|
-
for future in tqdm(concurrent.futures.as_completed(future_to_task), total=len(tasks), desc="Generando"):
|
|
290
|
-
try:
|
|
291
|
-
result = future.result()
|
|
292
|
-
if result:
|
|
293
|
-
valid_variations.append(result)
|
|
294
|
-
except Exception as e:
|
|
295
|
-
logger.error(f"Excepción no manejada en worker: {e}")
|
|
296
|
-
|
|
225
|
+
logger.info(f"Paso 4: Generando variaciones en paralelo (Workers: {max_workers})...")
|
|
226
|
+
|
|
227
|
+
# Setup Generator
|
|
228
|
+
api_config = self.get_api_config(args.api)
|
|
229
|
+
|
|
230
|
+
if (args.use_rag and self.rag_manager) or args.mode == 'creation':
|
|
231
|
+
retriever = self.rag_manager.get_retriever() if (args.use_rag and self.rag_manager) else None
|
|
232
|
+
generator = EnhancedVariationGenerator(api_provider=args.api, retriever=retriever)
|
|
233
|
+
validator = ConsistencyValidator(retriever=retriever) if retriever else ComplexityValidator()
|
|
234
|
+
else:
|
|
235
|
+
generator = VariationGenerator(api_provider=args.api)
|
|
236
|
+
validator = ComplexityValidator()
|
|
237
|
+
|
|
238
|
+
# Configure model
|
|
239
|
+
if args.api == 'local':
|
|
240
|
+
generator.base_url = args.base_url or api_config.get('base_url', "http://localhost:11434/v1")
|
|
241
|
+
generator.local_model = args.model or api_config.get('model', "llama3")
|
|
242
|
+
elif args.api == 'generic':
|
|
243
|
+
generator.base_url = args.base_url or api_config.get('base_url')
|
|
244
|
+
generator.model_name = args.model or api_config.get('model')
|
|
245
|
+
elif args.api in ['openai', 'anthropic', 'deepseek', 'gemini']:
|
|
246
|
+
if args.model:
|
|
247
|
+
generator.model_name = args.model
|
|
248
|
+
elif 'model' in api_config:
|
|
249
|
+
generator.model_name = api_config['model']
|
|
250
|
+
|
|
251
|
+
# Determine tasks based on mode
|
|
252
|
+
tasks = []
|
|
253
|
+
|
|
254
|
+
if args.mode == 'creation':
|
|
255
|
+
# Creation Mode Logic
|
|
256
|
+
for i in range(args.num_ejercicios):
|
|
257
|
+
current_topic = args.tema[i % len(args.tema)]
|
|
258
|
+
current_tags = [args.tags[i % len(args.tags)]] if args.tags else [current_topic]
|
|
259
|
+
|
|
260
|
+
tasks.append({
|
|
261
|
+
'func': self._generate_creation_mode,
|
|
262
|
+
'args': (generator, current_topic, current_tags, args.complejidad, args.type, args)
|
|
263
|
+
})
|
|
264
|
+
else:
|
|
265
|
+
# Variation Mode Logic
|
|
266
|
+
|
|
267
|
+
# If explicit lables, use exactly those
|
|
268
|
+
if args.label:
|
|
269
|
+
target_exercises = list(selected_exercises)
|
|
270
|
+
else:
|
|
271
|
+
# Random selection to fill num_ejercicios
|
|
272
|
+
target_exercises = []
|
|
273
|
+
candidates = selected_exercises[:max(5, len(selected_exercises)//2)]
|
|
274
|
+
for _ in range(args.num_ejercicios):
|
|
275
|
+
if candidates:
|
|
276
|
+
target_exercises.append(random.choice(candidates))
|
|
277
|
+
|
|
278
|
+
for ex_base, analysis in target_exercises:
|
|
279
|
+
tasks.append({
|
|
280
|
+
'func': self._generate_single_variation,
|
|
281
|
+
'args': (generator, validator, ex_base, analysis, args)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
# Execute Parallel
|
|
285
|
+
valid_variations = []
|
|
286
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
287
|
+
future_to_task = {}
|
|
288
|
+
for t in tasks:
|
|
289
|
+
future = executor.submit(t['func'], *t['args'])
|
|
290
|
+
future_to_task[future] = t
|
|
291
|
+
# Stagger requests to avoid hitting rate limits instantly
|
|
292
|
+
time.sleep(1.0)
|
|
293
|
+
|
|
294
|
+
for future in tqdm(concurrent.futures.as_completed(future_to_task), total=len(tasks), desc="Generando"):
|
|
295
|
+
try:
|
|
296
|
+
result = future.result()
|
|
297
|
+
if result:
|
|
298
|
+
valid_variations.append(result)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"Excepción no manejada en worker: {e}")
|
|
301
|
+
|
|
297
302
|
logger.info(f"Generación completada. {len(valid_variations)} variaciones exitosas.")
|
|
298
303
|
return valid_variations
|
|
299
304
|
|
|
@@ -332,14 +337,16 @@ class EvolutiaEngine:
|
|
|
332
337
|
generator.generate_variation,
|
|
333
338
|
exercise_base,
|
|
334
339
|
analysis,
|
|
335
|
-
args.type
|
|
340
|
+
exercise_type=args.type,
|
|
341
|
+
max_tokens=args.max_tokens
|
|
336
342
|
)
|
|
337
343
|
elif not args.no_generar_soluciones:
|
|
338
344
|
variation = await loop.run_in_executor(
|
|
339
345
|
None,
|
|
340
346
|
generator.generate_variation_with_solution,
|
|
341
347
|
exercise_base,
|
|
342
|
-
analysis
|
|
348
|
+
analysis,
|
|
349
|
+
max_tokens=args.max_tokens
|
|
343
350
|
)
|
|
344
351
|
else:
|
|
345
352
|
variation = await loop.run_in_executor(
|
|
@@ -347,7 +354,8 @@ class EvolutiaEngine:
|
|
|
347
354
|
generator.generate_variation,
|
|
348
355
|
exercise_base,
|
|
349
356
|
analysis,
|
|
350
|
-
args.type
|
|
357
|
+
exercise_type=args.type,
|
|
358
|
+
max_tokens=args.max_tokens
|
|
351
359
|
)
|
|
352
360
|
|
|
353
361
|
if not variation:
|
|
@@ -381,6 +389,7 @@ class EvolutiaEngine:
|
|
|
381
389
|
tags: List[str],
|
|
382
390
|
complexity: str,
|
|
383
391
|
ex_type: str,
|
|
392
|
+
args: argparse.Namespace,
|
|
384
393
|
semaphore: asyncio.Semaphore
|
|
385
394
|
) -> Optional[Dict]:
|
|
386
395
|
"""
|
|
@@ -399,7 +408,8 @@ class EvolutiaEngine:
|
|
|
399
408
|
topic,
|
|
400
409
|
tags,
|
|
401
410
|
complexity,
|
|
402
|
-
ex_type
|
|
411
|
+
ex_type,
|
|
412
|
+
args.max_tokens
|
|
403
413
|
)
|
|
404
414
|
except Exception as e:
|
|
405
415
|
logger.error(f"[EvolutiaEngine] Error en async creación (topic={topic}): {e}")
|
|
@@ -463,7 +473,8 @@ class EvolutiaEngine:
|
|
|
463
473
|
current_topic,
|
|
464
474
|
current_tags,
|
|
465
475
|
args.complejidad,
|
|
466
|
-
args.type
|
|
476
|
+
args.type,
|
|
477
|
+
args.max_tokens
|
|
467
478
|
))
|
|
468
479
|
else:
|
|
469
480
|
if args.label:
|
|
@@ -498,6 +509,7 @@ class EvolutiaEngine:
|
|
|
498
509
|
task_info[3], # tags
|
|
499
510
|
task_info[4], # complexity
|
|
500
511
|
task_info[5], # ex_type
|
|
512
|
+
task_info[6], # max_tokens
|
|
501
513
|
semaphore
|
|
502
514
|
))
|
|
503
515
|
else:
|
|
@@ -528,7 +540,7 @@ class EvolutiaEngine:
|
|
|
528
540
|
|
|
529
541
|
logger.info(f"Generación async completada. {len(valid_variations)} variaciones exitosas.")
|
|
530
542
|
return valid_variations
|
|
531
|
-
|
|
543
|
+
|
|
532
544
|
def generate_exam_files(
|
|
533
545
|
self,
|
|
534
546
|
variations: List[Dict],
|
|
@@ -537,23 +549,23 @@ class EvolutiaEngine:
|
|
|
537
549
|
exam_number: int
|
|
538
550
|
) -> bool:
|
|
539
551
|
"""Paso 5: Genera los archivos finales del examen."""
|
|
540
|
-
logger.info("Paso 5: Generando archivos de examen...")
|
|
541
|
-
exam_gen = ExamGenerator(self.base_path)
|
|
542
|
-
|
|
543
|
-
keywords = args.keywords or []
|
|
544
|
-
metadata = {
|
|
545
|
-
'model': args.api, # Simplified, internal details hidden
|
|
546
|
-
'provider': args.api,
|
|
547
|
-
'rag_enabled': args.use_rag,
|
|
548
|
-
'mode': args.mode,
|
|
549
|
-
'target_difficulty': args.complejidad
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return exam_gen.generate_exam(
|
|
553
|
-
variations,
|
|
554
|
-
exam_number,
|
|
555
|
-
output_dir,
|
|
556
|
-
args.subject,
|
|
557
|
-
keywords,
|
|
558
|
-
metadata=metadata
|
|
559
|
-
)
|
|
552
|
+
logger.info("Paso 5: Generando archivos de examen...")
|
|
553
|
+
exam_gen = ExamGenerator(self.base_path)
|
|
554
|
+
|
|
555
|
+
keywords = args.keywords or []
|
|
556
|
+
metadata = {
|
|
557
|
+
'model': args.api, # Simplified, internal details hidden
|
|
558
|
+
'provider': args.api,
|
|
559
|
+
'rag_enabled': args.use_rag,
|
|
560
|
+
'mode': args.mode,
|
|
561
|
+
'target_difficulty': args.complejidad
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return exam_gen.generate_exam(
|
|
565
|
+
variations,
|
|
566
|
+
exam_number,
|
|
567
|
+
output_dir,
|
|
568
|
+
args.subject,
|
|
569
|
+
keywords,
|
|
570
|
+
metadata=metadata
|
|
571
|
+
)
|
evolutia/llm_providers.py
CHANGED
|
@@ -142,8 +142,20 @@ class OpenAICompatibleProvider(LLMProvider):
|
|
|
142
142
|
temperature=kwargs.get("temperature", self.DEFAULT_TEMPERATURE),
|
|
143
143
|
max_tokens=kwargs.get("max_tokens", self.DEFAULT_MAX_TOKENS)
|
|
144
144
|
)
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
message = response.choices[0].message
|
|
146
|
+
finish_reason = response.choices[0].finish_reason
|
|
147
|
+
|
|
148
|
+
if hasattr(message, 'content') and message.content is not None:
|
|
149
|
+
content = message.content.strip()
|
|
150
|
+
else:
|
|
151
|
+
content = ""
|
|
152
|
+
logger.warning(f"[{provider_name}] Message content is None")
|
|
153
|
+
|
|
154
|
+
if not content:
|
|
155
|
+
logger.warning(f"[{provider_name}] Contenido vacío recibido. Finish reason: {finish_reason}")
|
|
156
|
+
logger.debug(f"[{provider_name}] Raw response: {response}")
|
|
157
|
+
|
|
158
|
+
logger.info(f"[{provider_name}] Contenido generado exitosamente (modelo={model}, longitud={len(content)}, reason={finish_reason})")
|
|
147
159
|
|
|
148
160
|
# Guardar en caché
|
|
149
161
|
if self.cache:
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Evolutia Configuration",
|
|
4
|
+
"description": "Configuration schema for Evolutia AI tool",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"api": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"default_provider": {
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
"openai": {
|
|
14
|
+
"$ref": "#/definitions/modelConfig"
|
|
15
|
+
},
|
|
16
|
+
"anthropic": {
|
|
17
|
+
"$ref": "#/definitions/modelConfig"
|
|
18
|
+
},
|
|
19
|
+
"gemini": {
|
|
20
|
+
"$ref": "#/definitions/modelConfig"
|
|
21
|
+
},
|
|
22
|
+
"local": {
|
|
23
|
+
"allOf": [
|
|
24
|
+
{
|
|
25
|
+
"$ref": "#/definitions/modelConfig"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"properties": {
|
|
29
|
+
"base_url": {
|
|
30
|
+
"type": "string"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": [
|
|
38
|
+
"default_provider"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"paths": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"base_path": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"materials_directories": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"required": [
|
|
55
|
+
"materials_directories"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"complexity": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"properties": {
|
|
61
|
+
"min_complexity_increase": {
|
|
62
|
+
"type": "number"
|
|
63
|
+
},
|
|
64
|
+
"min_improvements": {
|
|
65
|
+
"type": "integer"
|
|
66
|
+
},
|
|
67
|
+
"weights": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"patternProperties": {
|
|
70
|
+
"^.*$": {
|
|
71
|
+
"type": "number"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"exam": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"default": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {
|
|
83
|
+
"subject": {
|
|
84
|
+
"type": "string"
|
|
85
|
+
},
|
|
86
|
+
"points_per_exercise": {
|
|
87
|
+
"type": "integer"
|
|
88
|
+
},
|
|
89
|
+
"duration_hours": {
|
|
90
|
+
"type": "number"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"keywords": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"patternProperties": {
|
|
97
|
+
"^.*$": {
|
|
98
|
+
"type": "array",
|
|
99
|
+
"items": {
|
|
100
|
+
"type": "string"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"logging": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {
|
|
110
|
+
"level": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
},
|
|
113
|
+
"format": {
|
|
114
|
+
"type": "string"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"rag": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"properties": {
|
|
121
|
+
"vector_store": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"type": {
|
|
125
|
+
"type": "string"
|
|
126
|
+
},
|
|
127
|
+
"persist_directory": {
|
|
128
|
+
"type": "string"
|
|
129
|
+
},
|
|
130
|
+
"collection_name": {
|
|
131
|
+
"type": "string"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"required": [
|
|
135
|
+
"type"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
"embeddings": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": {
|
|
141
|
+
"provider": {
|
|
142
|
+
"type": "string"
|
|
143
|
+
},
|
|
144
|
+
"model": {
|
|
145
|
+
"type": "string"
|
|
146
|
+
},
|
|
147
|
+
"batch_size": {
|
|
148
|
+
"type": "integer"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"required": [
|
|
152
|
+
"provider",
|
|
153
|
+
"model"
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"retrieval": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"properties": {
|
|
159
|
+
"top_k": {
|
|
160
|
+
"type": "integer"
|
|
161
|
+
},
|
|
162
|
+
"similarity_threshold": {
|
|
163
|
+
"type": "number"
|
|
164
|
+
},
|
|
165
|
+
"use_metadata_filters": {
|
|
166
|
+
"type": "boolean"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"chunking": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {
|
|
173
|
+
"chunk_size": {
|
|
174
|
+
"type": "integer"
|
|
175
|
+
},
|
|
176
|
+
"chunk_overlap": {
|
|
177
|
+
"type": "integer"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"required": [
|
|
185
|
+
"api",
|
|
186
|
+
"paths"
|
|
187
|
+
],
|
|
188
|
+
"definitions": {
|
|
189
|
+
"modelConfig": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {
|
|
192
|
+
"model": {
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
"temperature": {
|
|
196
|
+
"type": "number"
|
|
197
|
+
},
|
|
198
|
+
"max_tokens": {
|
|
199
|
+
"type": "integer"
|
|
200
|
+
},
|
|
201
|
+
"api_key": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"required": [
|
|
206
|
+
"model"
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
evolutia/validation/__init__.py
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
from .config_validator import ConfigValidator, ConfigValidationError
|
|
2
|
+
from .args_validator import ArgsValidator
|
evolutia/variation_generator.py
CHANGED
|
@@ -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, 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__)
|
|
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,36 +37,36 @@ class VariationGenerator:
|
|
|
37
37
|
|
|
38
38
|
self._provider_instance = None
|
|
39
39
|
|
|
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
|
|
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
70
|
|
|
71
71
|
def _create_prompt(self, exercise: Dict, analysis: Dict) -> str:
|
|
72
72
|
"""Crea el prompt para generar una variación."""
|
|
@@ -105,8 +105,8 @@ REGLAS DE FORMATO:
|
|
|
105
105
|
Genera solo el contenido solicitado."""
|
|
106
106
|
return prompt
|
|
107
107
|
|
|
108
|
-
def _create_quiz_prompt(self, context_info: Dict[str, Any]) -> str:
|
|
109
|
-
"""Crea prompt para ejercicios de selección única."""
|
|
108
|
+
def _create_quiz_prompt(self, context_info: Dict[str, Any]) -> str:
|
|
109
|
+
"""Crea prompt para ejercicios de selección única."""
|
|
110
110
|
content = context_info.get('content', '')
|
|
111
111
|
|
|
112
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:
|
|
@@ -133,33 +133,33 @@ REQUISITOS:
|
|
|
133
133
|
"""
|
|
134
134
|
return prompt
|
|
135
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
|
|
136
|
+
def generate_variation(self, exercise: Dict[str, Any], analysis: Dict[str, Any], exercise_type: str = "development", max_tokens: int = 2000) -> 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.", max_tokens=max_tokens)
|
|
157
|
+
|
|
158
|
+
if not content:
|
|
159
|
+
logger.warning("[VariationGenerator] Proveedor retornó contenido vacío")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
# 4. Parsear respuesta
|
|
163
163
|
variation_content = ""
|
|
164
164
|
variation_solution = ""
|
|
165
165
|
|
|
@@ -212,10 +212,10 @@ INSTRUCCIONES:
|
|
|
212
212
|
"""
|
|
213
213
|
return prompt
|
|
214
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
|
-
"""
|
|
215
|
+
def generate_new_exercise_from_topic(self, topic: str, tags: Optional[List[str]] = None, difficulty: str = "alta", exercise_type: str = "development", max_tokens: int = 2000) -> Optional[Dict]:
|
|
216
|
+
"""
|
|
217
|
+
Genera un ejercicio nuevo desde cero.
|
|
218
|
+
"""
|
|
219
219
|
tags = tags or []
|
|
220
220
|
context = {} # Base implementations doesn't use context
|
|
221
221
|
|
|
@@ -233,7 +233,7 @@ INSTRUCCIONES:
|
|
|
233
233
|
if not provider: return None
|
|
234
234
|
|
|
235
235
|
# 3. Generar
|
|
236
|
-
content = provider.generate_content(prompt)
|
|
236
|
+
content = provider.generate_content(prompt, max_tokens=max_tokens)
|
|
237
237
|
if not content: return None
|
|
238
238
|
|
|
239
239
|
# 4. Parsear
|
|
@@ -265,12 +265,12 @@ INSTRUCCIONES:
|
|
|
265
265
|
'type': exercise_type
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
def generate_variation_with_solution(self, exercise: Dict, analysis: Dict) -> Optional[Dict]:
|
|
268
|
+
def generate_variation_with_solution(self, exercise: Dict, analysis: Dict, max_tokens: int = 2000) -> Optional[Dict]:
|
|
269
269
|
"""
|
|
270
270
|
Genera una variación con su solución.
|
|
271
271
|
"""
|
|
272
272
|
# Primero generar el ejercicio
|
|
273
|
-
variation = self.generate_variation(exercise, analysis)
|
|
273
|
+
variation = self.generate_variation(exercise, analysis, max_tokens=max_tokens)
|
|
274
274
|
|
|
275
275
|
if not variation:
|
|
276
276
|
return None
|
|
@@ -290,7 +290,7 @@ EJERCICIO:
|
|
|
290
290
|
|
|
291
291
|
GENERA LA SOLUCIÓN COMPLETA:"""
|
|
292
292
|
|
|
293
|
-
solution_content = provider.generate_content(solution_prompt)
|
|
293
|
+
solution_content = provider.generate_content(solution_prompt, max_tokens=max_tokens)
|
|
294
294
|
|
|
295
295
|
if solution_content:
|
|
296
296
|
variation['variation_solution'] = solution_content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evolutia
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Sistema automatizado para generar preguntas de examen desafiantes basadas en materiales didácticos existentes
|
|
5
5
|
Home-page: https://github.com/glacy/evolutIA
|
|
6
6
|
Author: Gerardo Lacy-Mora
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
evolutia_cli.py,sha256
|
|
1
|
+
evolutia_cli.py,sha256=-B9615kK6DjGB8RckVvG_o6S_88fE56_RFv4GcRHqx8,8522
|
|
2
2
|
evolutia/__init__.py,sha256=Fne5WLrmdjIxOSSL6z92G2alAoGRzwGnvpejFqksTiY,350
|
|
3
3
|
evolutia/async_llm_providers.py,sha256=HVbKmWhg9rGe3FphsLjeil0SD85tk83j8xtw3W5HjZU,5465
|
|
4
4
|
evolutia/complexity_validator.py,sha256=4BtRvTDVfQUweaz-4QabO-wiEtr5S7_3n0AuAd1Bn5Y,7490
|
|
5
5
|
evolutia/config_manager.py,sha256=Cf_MQzUq-g5rQuJN_csKt9gJSwPJ6kYg-Dw8uYp1um0,9183
|
|
6
|
-
evolutia/evolutia_engine.py,sha256
|
|
6
|
+
evolutia/evolutia_engine.py,sha256=-UPEOHydeZCjipmcqHpiJmcCFEiQYGDMO4rx3_KIO2c,22713
|
|
7
7
|
evolutia/exam_generator.py,sha256=7CKdo86u8bC6yYJY7I3WITcyV_4L8lMRETngGYf2WbU,13837
|
|
8
8
|
evolutia/exceptions.py,sha256=A1xnYmPlySYEeTHR12ct7Apet2n6JhGugjOYX4mmobw,779
|
|
9
9
|
evolutia/exercise_analyzer.py,sha256=bY4c-hqeef_xrDTVC9X4MzZlglrhNejWtwEgI_NJ-Mw,8666
|
|
10
10
|
evolutia/imports.py,sha256=CTsHJ5FukEKMh5GM_O9EsoH61F6xMGj_A3gV0H869Hc,5914
|
|
11
|
-
evolutia/llm_providers.py,sha256=
|
|
11
|
+
evolutia/llm_providers.py,sha256=y3g8n-VMRAn3X0JMt2Ky5h5UEqImXfvYCou7wiuU17c,15505
|
|
12
12
|
evolutia/material_extractor.py,sha256=CQGgCS9sRBNIhKeR2ya4V7TYBz47yWcEQdjpGEduZRY,12429
|
|
13
13
|
evolutia/retry_utils.py,sha256=j-DS7MtQaNq2i1Rp788mPVRa_-xs_pEM6PGMzXpICfw,9830
|
|
14
|
-
evolutia/variation_generator.py,sha256=
|
|
14
|
+
evolutia/variation_generator.py,sha256=bmH17aeatQFYOqF5C2R9U2220imQhcvELSGAqPtEgr0,11629
|
|
15
15
|
evolutia/cache/__init__.py,sha256=XAI2hwXc7SMrvs3n8VxgmbVuB7Ma4euQsqdoTuhPRQ0,263
|
|
16
16
|
evolutia/cache/exercise_cache.py,sha256=eVd0DmcBxoq3eiuOCjiWAZUCtQNIdZBaO6qKqAQ9vjU,7010
|
|
17
17
|
evolutia/cache/llm_cache.py,sha256=BdUcVeJ8GvAtIZ1s4rEgklcqaXPwORl8VoZp6tdzA_g,16608
|
|
@@ -22,16 +22,17 @@ evolutia/rag/enhanced_variation_generator.py,sha256=hFA60OW6-02d8HqluiDTgh7Apwkj
|
|
|
22
22
|
evolutia/rag/rag_indexer.py,sha256=Rg-PtvEJXW5IGo-nLh3h1TT51YXnbz9mwoifUTwUiZM,16732
|
|
23
23
|
evolutia/rag/rag_manager.py,sha256=ODfOiREv8OyQ7WfCdIOPmynJ6grckN6uLYjuECrt3LU,8071
|
|
24
24
|
evolutia/rag/rag_retriever.py,sha256=It8gB-dL0Ip2r2zCD5Oq_Hmc-C8fbCyrCLaNOghqhLw,14370
|
|
25
|
+
evolutia/schemas/config.schema.json,sha256=GzQxWpJeog5BMYlkEFkzzwk3UNVX0ndDyKRh6tvOafs,6481
|
|
25
26
|
evolutia/utils/__init__.py,sha256=rD3hl92fM5L3_7QCRrCjyR91bx_bOsw0baVQrCjy6vg,56
|
|
26
27
|
evolutia/utils/json_parser.py,sha256=71yA15bYCGkZ7pAhLoqoGuKTrFyAsjWDNEgG7rhsloo,3308
|
|
27
28
|
evolutia/utils/markdown_parser.py,sha256=LjMFMuq4liXaRVXBd6FsH1ZlfiFlO8Hryg-CNAgaYYk,5743
|
|
28
29
|
evolutia/utils/math_extractor.py,sha256=tKfzwnmj8Puw9w624bBAnRjgxr3my-O3a9UcIyi8XTk,5125
|
|
29
|
-
evolutia/validation/__init__.py,sha256=
|
|
30
|
+
evolutia/validation/__init__.py,sha256=TxFTXvjfbDaD26gIDy3XE7SEGdzwMV-aocX-OMs_wxE,111
|
|
30
31
|
evolutia/validation/args_validator.py,sha256=lhlQ5_swUBYTO_7MXUgTgtwd_JLCCEjvh4YxM7KbtkE,10051
|
|
31
32
|
evolutia/validation/config_validator.py,sha256=ATTI0--zZIG85WzASU59M2nW-nZbNSxWOe82QJ1vIMQ,20968
|
|
32
|
-
evolutia-0.1.
|
|
33
|
-
evolutia-0.1.
|
|
34
|
-
evolutia-0.1.
|
|
35
|
-
evolutia-0.1.
|
|
36
|
-
evolutia-0.1.
|
|
37
|
-
evolutia-0.1.
|
|
33
|
+
evolutia-0.1.5.dist-info/licenses/LICENSE,sha256=jyOI5zt59oNBGE33KX2L1py7ZwWKqVFNcgEePR6o6YA,11564
|
|
34
|
+
evolutia-0.1.5.dist-info/METADATA,sha256=VUgcMYe8hYZmGneid3GEjcveiijXnTW6zrvOWIxwYz8,16955
|
|
35
|
+
evolutia-0.1.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
36
|
+
evolutia-0.1.5.dist-info/entry_points.txt,sha256=BMYhya9XcbrfsjGfp4lMVQF0TK3Bd_Kj6mZtFhVJ7zo,47
|
|
37
|
+
evolutia-0.1.5.dist-info/top_level.txt,sha256=GrIvEW8qAI8CIW5XTPnCqC7o06qJIL8p9CGwVhCEC4s,22
|
|
38
|
+
evolutia-0.1.5.dist-info/RECORD,,
|
evolutia_cli.py
CHANGED
|
@@ -12,12 +12,12 @@ from dotenv import load_dotenv
|
|
|
12
12
|
# Cargar variables de entorno desde el directorio actual (donde el usuario ejecuta el comando)
|
|
13
13
|
load_dotenv()
|
|
14
14
|
|
|
15
|
-
# Add current directory to path so we can import 'evolutia' package if running locally without install
|
|
16
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
17
|
-
|
|
18
|
-
from evolutia.evolutia_engine import EvolutiaEngine
|
|
19
|
-
from evolutia.config_manager import ConfigManager
|
|
20
|
-
from evolutia.validation.args_validator import ArgsValidator, ValidationError
|
|
15
|
+
# Add current directory to path so we can import 'evolutia' package if running locally without install
|
|
16
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
17
|
+
|
|
18
|
+
from evolutia.evolutia_engine import EvolutiaEngine
|
|
19
|
+
from evolutia.config_manager import ConfigManager
|
|
20
|
+
from evolutia.validation.args_validator import ArgsValidator, ValidationError
|
|
21
21
|
|
|
22
22
|
# Configurar logging
|
|
23
23
|
logging.basicConfig(
|
|
@@ -64,20 +64,21 @@ Ejemplos:
|
|
|
64
64
|
parser.add_argument('--query', type=str, help='Consulta RAG')
|
|
65
65
|
parser.add_argument('--workers', type=int, default=5, help='Número de hilos para generación paralela')
|
|
66
66
|
parser.add_argument('--analyze', action='store_true', help='Analizar estructura y generar config.yaml')
|
|
67
|
+
parser.add_argument('--max_tokens', type=int, default=2000, help='Máximo de tokens para generación')
|
|
68
|
+
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
|
|
71
|
+
# Validar argumentos con ArgsValidator
|
|
72
|
+
validator = ArgsValidator()
|
|
73
|
+
is_valid, errors = validator.validate_args(args)
|
|
74
|
+
|
|
75
|
+
if not is_valid:
|
|
76
|
+
logger.error("Errores de validación en argumentos:")
|
|
77
|
+
for error in errors:
|
|
78
|
+
logger.error(f" - {error}")
|
|
79
|
+
parser.error("\n".join(errors))
|
|
67
80
|
|
|
68
|
-
args
|
|
69
|
-
|
|
70
|
-
# Validar argumentos con ArgsValidator
|
|
71
|
-
validator = ArgsValidator()
|
|
72
|
-
is_valid, errors = validator.validate_args(args)
|
|
73
|
-
|
|
74
|
-
if not is_valid:
|
|
75
|
-
logger.error("Errores de validación en argumentos:")
|
|
76
|
-
for error in errors:
|
|
77
|
-
logger.error(f" - {error}")
|
|
78
|
-
parser.error("\n".join(errors))
|
|
79
|
-
|
|
80
|
-
if args.analyze:
|
|
81
|
+
if args.analyze:
|
|
81
82
|
print("Analizando estructura del proyecto...")
|
|
82
83
|
ConfigManager(str(Path.cwd())).update_config_from_structure()
|
|
83
84
|
return 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|