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.
- evolutia/__init__.py +10 -1
- evolutia/async_llm_providers.py +157 -0
- evolutia/cache/__init__.py +9 -0
- evolutia/cache/exercise_cache.py +226 -0
- evolutia/cache/llm_cache.py +487 -0
- evolutia/complexity_validator.py +33 -31
- evolutia/config_manager.py +60 -41
- evolutia/evolutia_engine.py +341 -66
- evolutia/exam_generator.py +44 -43
- evolutia/exceptions.py +38 -0
- evolutia/exercise_analyzer.py +54 -91
- evolutia/imports.py +175 -0
- evolutia/llm_providers.py +224 -60
- evolutia/material_extractor.py +166 -88
- evolutia/rag/rag_indexer.py +107 -90
- evolutia/rag/rag_retriever.py +130 -103
- evolutia/retry_utils.py +280 -0
- evolutia/utils/json_parser.py +29 -19
- evolutia/utils/markdown_parser.py +185 -159
- evolutia/utils/math_extractor.py +153 -144
- evolutia/validation/__init__.py +1 -0
- evolutia/validation/args_validator.py +253 -0
- evolutia/validation/config_validator.py +502 -0
- evolutia/variation_generator.py +252 -50
- evolutia-0.1.2.dist-info/METADATA +536 -0
- evolutia-0.1.2.dist-info/RECORD +37 -0
- {evolutia-0.1.0.dist-info → evolutia-0.1.2.dist-info}/WHEEL +1 -1
- {evolutia-0.1.0.dist-info → evolutia-0.1.2.dist-info}/licenses/LICENSE +1 -1
- evolutia_cli.py +30 -7
- evolutia-0.1.0.dist-info/METADATA +0 -723
- evolutia-0.1.0.dist-info/RECORD +0 -27
- {evolutia-0.1.0.dist-info → evolutia-0.1.2.dist-info}/entry_points.txt +0 -0
- {evolutia-0.1.0.dist-info → evolutia-0.1.2.dist-info}/top_level.txt +0 -0
evolutia/exercise_analyzer.py
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Analizador de complejidad de ejercicios.
|
|
3
|
-
Identifica tipo, pasos, conceptos y variables de ejercicios.
|
|
4
|
-
"""
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
|
-
from
|
|
1
|
+
"""
|
|
2
|
+
Analizador de complejidad de ejercicios.
|
|
3
|
+
Identifica tipo, pasos, conceptos y variables de ejercicios.
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, List, Set, Optional, TYPE_CHECKING
|
|
8
|
+
from collections import Counter
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from evolutia.cache.exercise_cache import ExerciseAnalysisCache
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
8
14
|
|
|
9
15
|
try:
|
|
10
16
|
from utils.math_extractor import (
|
|
@@ -22,73 +28,19 @@ except ImportError:
|
|
|
22
28
|
)
|
|
23
29
|
|
|
24
30
|
|
|
25
|
-
class ExerciseAnalyzer:
|
|
26
|
-
"""Analiza la complejidad y estructura de ejercicios."""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'considere', 'suponga', 'modelo', 'sistema físico', 'aplicación',
|
|
39
|
-
'dispositivo', 'campo', 'potencial'
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
STEP_KEYWORDS = [
|
|
43
|
-
'primero', 'luego', 'finalmente', 'ahora', 'a continuación',
|
|
44
|
-
'por tanto', 'por lo tanto', 'en consecuencia', 'así',
|
|
45
|
-
'por otro lado', 'además', 'también'
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
# Patrones compilados para búsqueda eficiente
|
|
49
|
-
TYPE_PATTERNS = {
|
|
50
|
-
'demostracion': re.compile('|'.join(map(re.escape, DEMOSTRACION_KEYWORDS)), re.IGNORECASE),
|
|
51
|
-
'calculo': re.compile('|'.join(map(re.escape, CALCULO_KEYWORDS)), re.IGNORECASE),
|
|
52
|
-
'aplicacion': re.compile('|'.join(map(re.escape, APLICACION_KEYWORDS)), re.IGNORECASE)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
STEP_KEYWORDS_PATTERN = re.compile('|'.join(map(re.escape, STEP_KEYWORDS)), re.IGNORECASE)
|
|
56
|
-
|
|
57
|
-
# Conceptos matemáticos comunes
|
|
58
|
-
CONCEPT_PATTERNS = {
|
|
59
|
-
'vector_operations': [
|
|
60
|
-
r'\\vec', r'\\cdot', r'\\times', r'\\nabla',
|
|
61
|
-
r'producto\s+(escalar|vectorial)', r'gradiente', r'divergencia', r'rotacional'
|
|
62
|
-
],
|
|
63
|
-
'coordinate_systems': [
|
|
64
|
-
r'coordenadas?\s+(cartesianas?|polares?|cilíndricas?|esféricas?|toroidales?)',
|
|
65
|
-
r'\\rho', r'\\theta', r'\\phi', r'\\hat\{e\}_'
|
|
66
|
-
],
|
|
67
|
-
'integrals': [
|
|
68
|
-
r'\\int', r'\\oint', r'integral', r'teorema\s+(de\s+)?(Green|Stokes|Gauss)',
|
|
69
|
-
r'divergencia', r'rotacional'
|
|
70
|
-
],
|
|
71
|
-
'differential_equations': [
|
|
72
|
-
r'\\frac\{d', r'\\partial', r'ecuaci[óo]n\s+diferencial',
|
|
73
|
-
r'EDP', r'EDO'
|
|
74
|
-
],
|
|
75
|
-
'linear_algebra': [
|
|
76
|
-
r'\\begin\{matrix\}', r'\\begin\{pmatrix\}', r'\\begin\{bmatrix\}',
|
|
77
|
-
r'matriz', r'\\mathbf', r'autovalor', r'autovector'
|
|
78
|
-
],
|
|
79
|
-
'complex_numbers': [
|
|
80
|
-
r'\\mathbb\{C\}', r'z\s*=', r'n[úu]mero\s+complejo',
|
|
81
|
-
r'\\Re', r'\\Im', r'\\arg'
|
|
82
|
-
],
|
|
83
|
-
'series_expansions': [
|
|
84
|
-
r'\\sum', r'serie', r'expansi[óo]n', r'Fourier',
|
|
85
|
-
r'Taylor', r'\\sum_\{n=0\}'
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
def __init__(self):
|
|
90
|
-
"""Inicializa el analizador."""
|
|
91
|
-
pass
|
|
31
|
+
class ExerciseAnalyzer:
|
|
32
|
+
"""Analiza la complejidad y estructura de ejercicios."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, cache: Optional['ExerciseAnalysisCache'] = None):
|
|
35
|
+
"""
|
|
36
|
+
Inicializa el analizador.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cache: Instancia opcional de ExerciseAnalysisCache para cachear análisis
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
self.cache = cache
|
|
92
44
|
|
|
93
45
|
def identify_exercise_type(self, content: str) -> str:
|
|
94
46
|
"""
|
|
@@ -183,23 +135,34 @@ class ExerciseAnalyzer:
|
|
|
183
135
|
|
|
184
136
|
return concepts
|
|
185
137
|
|
|
186
|
-
def analyze(self, exercise: Dict) -> Dict:
|
|
187
|
-
"""
|
|
188
|
-
Analiza un ejercicio completo y retorna metadatos de complejidad.
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
exercise: Diccionario con información del ejercicio
|
|
192
|
-
- 'content': Contenido del ejercicio
|
|
193
|
-
- 'solution': Contenido de la solución (opcional)
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
Diccionario con análisis de complejidad
|
|
197
|
-
"""
|
|
198
|
-
content = exercise.get('content', '')
|
|
199
|
-
solution = exercise.get('solution', '')
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
|
|
138
|
+
def analyze(self, exercise: Dict[str, Optional[str]]) -> Dict[str, Optional[str | int | float | List[str]]]:
|
|
139
|
+
"""
|
|
140
|
+
Analiza un ejercicio completo y retorna metadatos de complejidad.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
exercise: Diccionario con información del ejercicio
|
|
144
|
+
- 'content': Contenido del ejercicio
|
|
145
|
+
- 'solution': Contenido de la solución (opcional)
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Diccionario con análisis de complejidad
|
|
149
|
+
"""
|
|
150
|
+
content = exercise.get('content', '')
|
|
151
|
+
solution = exercise.get('solution', '')
|
|
152
|
+
|
|
153
|
+
# Intentar caché primero
|
|
154
|
+
if self.cache:
|
|
155
|
+
cached_analysis = self.cache.get(exercise)
|
|
156
|
+
if cached_analysis:
|
|
157
|
+
logger.info(f"[ExerciseAnalyzer] Análisis obtenido del caché para exercise={exercise.get('label', 'unknown')}")
|
|
158
|
+
return cached_analysis['analysis']
|
|
159
|
+
|
|
160
|
+
# Análisis normal (cache miss)
|
|
161
|
+
if not content:
|
|
162
|
+
return {}
|
|
163
|
+
|
|
164
|
+
# Extraer expresiones matemáticas
|
|
165
|
+
math_expressions = extract_math_expressions(content)
|
|
203
166
|
if solution:
|
|
204
167
|
math_expressions.extend(extract_math_expressions(solution))
|
|
205
168
|
|
evolutia/imports.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Módulo de imports centralizados y condicionales para EvolutIA.
|
|
3
|
+
Gestiona imports de dependencias opcionales (RAG, ML, etc.) de forma centralizada.
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OptionalImports:
|
|
12
|
+
"""
|
|
13
|
+
Gestor de imports opcionales para EvolutIA.
|
|
14
|
+
|
|
15
|
+
Permite importar dependencias opcionales de forma controlada,
|
|
16
|
+
con mensajes de error claros cuando no están disponibles.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_imported_modules = {}
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_chromadb(cls):
|
|
23
|
+
"""Importa ChromaDB si está disponible."""
|
|
24
|
+
if 'chromadb' in cls._imported_modules:
|
|
25
|
+
return cls._imported_modules['chromadb']
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import chromadb
|
|
29
|
+
from chromadb.config import Settings
|
|
30
|
+
cls._imported_modules['chromadb'] = (chromadb, Settings)
|
|
31
|
+
logger.debug("[OptionalImports] ChromaDB importado exitosamente")
|
|
32
|
+
return chromadb, Settings
|
|
33
|
+
except ImportError:
|
|
34
|
+
logger.warning(
|
|
35
|
+
"[OptionalImports] chromadb no está instalado. "
|
|
36
|
+
"Instala con: pip install -e '.[rag]'"
|
|
37
|
+
)
|
|
38
|
+
return None, None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_sentence_transformers(cls):
|
|
42
|
+
"""Importa sentence-transformers si está disponible."""
|
|
43
|
+
if 'sentence_transformers' in cls._imported_modules:
|
|
44
|
+
return cls._imported_modules['sentence_transformers']
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from sentence_transformers import SentenceTransformer
|
|
48
|
+
cls._imported_modules['sentence_transformers'] = SentenceTransformer
|
|
49
|
+
logger.debug("[OptionalImports] sentence-transformers importado exitosamente")
|
|
50
|
+
return SentenceTransformer
|
|
51
|
+
except ImportError:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"[OptionalImports] sentence-transformers no está instalado. "
|
|
54
|
+
"Instala con: pip install -e '.[rag]'"
|
|
55
|
+
)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def get_openai(cls):
|
|
60
|
+
"""Importa OpenAI si está disponible."""
|
|
61
|
+
if 'openai' in cls._imported_modules:
|
|
62
|
+
return cls._imported_modules['openai']
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from openai import OpenAI
|
|
66
|
+
cls._imported_modules['openai'] = OpenAI
|
|
67
|
+
logger.debug("[OptionalImports] OpenAI importado exitosamente")
|
|
68
|
+
return OpenAI
|
|
69
|
+
except ImportError:
|
|
70
|
+
logger.warning(
|
|
71
|
+
"[OptionalImports] openai no está instalado. "
|
|
72
|
+
"Instala con: pip install openai"
|
|
73
|
+
)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get_anthropic(cls):
|
|
78
|
+
"""Importa Anthropic si está disponible."""
|
|
79
|
+
if 'anthropic' in cls._imported_modules:
|
|
80
|
+
return cls._imported_modules['anthropic']
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
import anthropic
|
|
84
|
+
cls._imported_modules['anthropic'] = anthropic
|
|
85
|
+
logger.debug("[OptionalImports] Anthropic importado exitosamente")
|
|
86
|
+
return anthropic
|
|
87
|
+
except ImportError:
|
|
88
|
+
logger.warning(
|
|
89
|
+
"[OptionalImports] anthropic no está instalado. "
|
|
90
|
+
"Instala con: pip install anthropic"
|
|
91
|
+
)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def get_google_generativeai(cls):
|
|
96
|
+
"""Importa google-generativeai si está disponible."""
|
|
97
|
+
if 'google_generativeai' in cls._imported_modules:
|
|
98
|
+
return cls._imported_modules['google_generativeai']
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
import google.generativeai as genai
|
|
102
|
+
cls._imported_modules['google_generativeai'] = genai
|
|
103
|
+
logger.debug("[OptionalImports] google-generativeai importado exitosamente")
|
|
104
|
+
return genai
|
|
105
|
+
except ImportError:
|
|
106
|
+
logger.warning(
|
|
107
|
+
"[OptionalImports] google-generativeai no está instalado. "
|
|
108
|
+
"Instala con: pip install google-generativeai"
|
|
109
|
+
)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def check_rag_available(cls) -> bool:
|
|
114
|
+
"""Verifica si todas las dependencias de RAG están disponibles."""
|
|
115
|
+
chromadb, _ = cls.get_chromadb()
|
|
116
|
+
sentence_transformers = cls.get_sentence_transformers()
|
|
117
|
+
|
|
118
|
+
if chromadb is None or sentence_transformers is None:
|
|
119
|
+
return False
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def get_module(cls, module_name: str):
|
|
124
|
+
"""
|
|
125
|
+
Importa un módulo específico por nombre.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
module_name: Nombre del módulo a importar
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
El módulo importado o None si no está disponible
|
|
132
|
+
"""
|
|
133
|
+
if module_name in cls._imported_modules:
|
|
134
|
+
return cls._imported_modules[module_name]
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
module = __import__(module_name)
|
|
138
|
+
cls._imported_modules[module_name] = module
|
|
139
|
+
logger.debug(f"[OptionalImports] {module_name} importado exitosamente")
|
|
140
|
+
return module
|
|
141
|
+
except ImportError:
|
|
142
|
+
logger.warning(f"[OptionalImports] {module_name} no está instalado.")
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Funciones de conveniencia para compatibilidad con código existente
|
|
147
|
+
def get_chromadb():
|
|
148
|
+
"""Importa ChromaDB si está disponible."""
|
|
149
|
+
chromadb_module, settings = OptionalImports.get_chromadb()
|
|
150
|
+
return chromadb_module, settings
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def get_sentence_transformers():
|
|
154
|
+
"""Importa sentence-transformers si está disponible."""
|
|
155
|
+
return OptionalImports.get_sentence_transformers()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_openai():
|
|
159
|
+
"""Importa OpenAI si está disponible."""
|
|
160
|
+
return OptionalImports.get_openai()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_anthropic():
|
|
164
|
+
"""Importa Anthropic si está disponible."""
|
|
165
|
+
return OptionalImports.get_anthropic()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_google_generativeai():
|
|
169
|
+
"""Importa google-generativeai si está disponible."""
|
|
170
|
+
return OptionalImports.get_google_generativeai()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_rag_available() -> bool:
|
|
174
|
+
"""Verifica si todas las dependencias de RAG están disponibles."""
|
|
175
|
+
return OptionalImports.check_rag_available()
|