evolutia 0.1.3__tar.gz → 0.1.5__tar.gz
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-0.1.5/MANIFEST.in +3 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/PKG-INFO +1 -1
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/evolutia_engine.py +249 -237
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/llm_providers.py +14 -2
- evolutia-0.1.5/evolutia/schemas/config.schema.json +210 -0
- evolutia-0.1.5/evolutia/validation/__init__.py +2 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/variation_generator.py +86 -86
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/PKG-INFO +1 -1
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/SOURCES.txt +2 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia_cli.py +20 -19
- {evolutia-0.1.3 → evolutia-0.1.5}/setup.py +2 -1
- evolutia-0.1.3/evolutia/validation/__init__.py +0 -1
- {evolutia-0.1.3 → evolutia-0.1.5}/LICENSE +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/README.md +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/__init__.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/async_llm_providers.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/cache/__init__.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/cache/exercise_cache.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/cache/llm_cache.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/complexity_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/config_manager.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/exam_generator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/exceptions.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/exercise_analyzer.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/imports.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/material_extractor.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/__init__.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/consistency_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/context_enricher.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/enhanced_variation_generator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/rag_indexer.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/rag_manager.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/rag/rag_retriever.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/retry_utils.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/utils/__init__.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/utils/json_parser.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/utils/markdown_parser.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/utils/math_extractor.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/validation/args_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia/validation/config_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/dependency_links.txt +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/entry_points.txt +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/requires.txt +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/evolutia.egg-info/top_level.txt +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/setup.cfg +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_args_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_cache.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_complexity_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_config_discovery.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_config_validator.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_exercise_analyzer.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_exercise_analyzer_cache.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_json_robustness.py +0 -0
- {evolutia-0.1.3 → evolutia-0.1.5}/tests/test_math_extractor.py +0 -0
|
@@ -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
|
|
@@ -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
|
+
)
|
|
@@ -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:
|