evolutia 0.1.1__py3-none-any.whl → 0.1.3__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 +9 -0
- 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 +53 -40
- evolutia/evolutia_engine.py +341 -66
- evolutia/exam_generator.py +44 -43
- evolutia/exceptions.py +38 -0
- evolutia/exercise_analyzer.py +42 -59
- evolutia/imports.py +175 -0
- evolutia/llm_providers.py +223 -61
- 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 +82 -70
- evolutia-0.1.3.dist-info/METADATA +536 -0
- evolutia-0.1.3.dist-info/RECORD +37 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/WHEEL +1 -1
- evolutia_cli.py +22 -9
- evolutia-0.1.1.dist-info/METADATA +0 -221
- evolutia-0.1.1.dist-info/RECORD +0 -27
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/entry_points.txt +0 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {evolutia-0.1.1.dist-info → evolutia-0.1.3.dist-info}/top_level.txt +0 -0
evolutia/exercise_analyzer.py
CHANGED
|
@@ -3,9 +3,15 @@ Analizador de complejidad de ejercicios.
|
|
|
3
3
|
Identifica tipo, pasos, conceptos y variables de ejercicios.
|
|
4
4
|
"""
|
|
5
5
|
import re
|
|
6
|
-
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, List, Set, Optional, TYPE_CHECKING
|
|
7
8
|
from collections import Counter
|
|
8
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from evolutia.cache.exercise_cache import ExerciseAnalysisCache
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
9
15
|
try:
|
|
10
16
|
from utils.math_extractor import (
|
|
11
17
|
extract_math_expressions,
|
|
@@ -25,70 +31,36 @@ except ImportError:
|
|
|
25
31
|
class ExerciseAnalyzer:
|
|
26
32
|
"""Analiza la complejidad y estructura de ejercicios."""
|
|
27
33
|
|
|
28
|
-
# Palabras clave para identificación de tipo
|
|
29
|
-
DEMOSTRACION_KEYWORDS = [
|
|
30
|
-
'demuestre', 'demuestre que', 'pruebe', 'verifique', 'muestre que'
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
CALCULO_KEYWORDS = [
|
|
34
|
-
'calcule', 'calcular', 'encuentre', 'determine', 'evalúe', 'evaluar'
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
APLICACION_KEYWORDS = [
|
|
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
34
|
TYPE_PATTERNS = {
|
|
50
|
-
'demostracion': re.compile('
|
|
51
|
-
'calculo': re.compile('
|
|
52
|
-
'aplicacion': re.compile('
|
|
35
|
+
'demostracion': re.compile(r'(?i)(demuestre|pruebe|verifique|muestre|justifique|demostraci[oó]n)'),
|
|
36
|
+
'calculo': re.compile(r'(?i)(calcule|halle|encuentre|resuelva|eval[uú]e|calcular|obtenga|determinar)'),
|
|
37
|
+
'aplicacion': re.compile(r'(?i)(aplicaci[oó]n|problema|vida real|modelo|f[íi]sic[ao]|ingenier[íi]a|econom[íi]a|contexto)')
|
|
53
38
|
}
|
|
54
39
|
|
|
55
|
-
STEP_KEYWORDS_PATTERN = re.compile(
|
|
40
|
+
STEP_KEYWORDS_PATTERN = re.compile(
|
|
41
|
+
r'(?i)(primero|luego|despu[ée]s|finalmente|entonces|por lo tanto|conclusi[oó]n|paso|seguidamente)',
|
|
42
|
+
re.MULTILINE
|
|
43
|
+
)
|
|
56
44
|
|
|
57
|
-
# Conceptos matemáticos comunes
|
|
58
45
|
CONCEPT_PATTERNS = {
|
|
59
|
-
'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
],
|
|
63
|
-
'
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
]
|
|
46
|
+
'integrals': [r'(?i)integral', r'\\int', r'\\iint', r'\\iiint', r'\\oint'],
|
|
47
|
+
'derivatives': [r'(?i)derivada', r'\\frac{d}{d', r'\\[dp]artial', r'\''],
|
|
48
|
+
'limits': [r'(?i)l[íi]mite', r'\\lim'],
|
|
49
|
+
'series': [r'(?i)serie', r'(?i)sucesi[oó]n', r'\\sum', r'convergencia'],
|
|
50
|
+
'vectors': [r'(?i)vector', r'\\vec', r'\\mathbf', r'producto punto', r'producto cruz'],
|
|
51
|
+
'matrices': [r'(?i)matriz', r'(?i)determinante', r'\\begin{pmatrix}', r'\\begin{bmatrix}', r'autovalor'],
|
|
52
|
+
'coordinate_systems': [r'(?i)coordenadas', r'(?i)polares', r'(?i)esf[ée]ricas', r'(?i)cil[íi]ndricas', r'jacobian[oa]'],
|
|
53
|
+
'vector_operations': [r'(?i)gradiente', r'(?i)divergencia', r'(?i)rotacional', r'\\nabla', r'teorema de stokes', r'teorema de green', r'teorema de la divergencia']
|
|
87
54
|
}
|
|
88
55
|
|
|
89
|
-
def __init__(self):
|
|
90
|
-
"""
|
|
91
|
-
|
|
56
|
+
def __init__(self, cache: Optional['ExerciseAnalysisCache'] = None):
|
|
57
|
+
"""
|
|
58
|
+
Inicializa el analizador.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
cache: Instancia opcional de ExerciseAnalysisCache para cachear análisis
|
|
62
|
+
"""
|
|
63
|
+
self.cache = cache
|
|
92
64
|
|
|
93
65
|
def identify_exercise_type(self, content: str) -> str:
|
|
94
66
|
"""
|
|
@@ -183,7 +155,7 @@ class ExerciseAnalyzer:
|
|
|
183
155
|
|
|
184
156
|
return concepts
|
|
185
157
|
|
|
186
|
-
def analyze(self, exercise: Dict) -> Dict:
|
|
158
|
+
def analyze(self, exercise: Dict[str, Optional[str]]) -> Dict[str, Optional[str | int | float | List[str]]]:
|
|
187
159
|
"""
|
|
188
160
|
Analiza un ejercicio completo y retorna metadatos de complejidad.
|
|
189
161
|
|
|
@@ -198,6 +170,17 @@ class ExerciseAnalyzer:
|
|
|
198
170
|
content = exercise.get('content', '')
|
|
199
171
|
solution = exercise.get('solution', '')
|
|
200
172
|
|
|
173
|
+
# Intentar caché primero
|
|
174
|
+
if self.cache:
|
|
175
|
+
cached_analysis = self.cache.get(exercise)
|
|
176
|
+
if cached_analysis:
|
|
177
|
+
logger.info(f"[ExerciseAnalyzer] Análisis obtenido del caché para exercise={exercise.get('label', 'unknown')}")
|
|
178
|
+
return cached_analysis['analysis']
|
|
179
|
+
|
|
180
|
+
# Análisis normal (cache miss)
|
|
181
|
+
if not content:
|
|
182
|
+
return {}
|
|
183
|
+
|
|
201
184
|
# Extraer expresiones matemáticas
|
|
202
185
|
math_expressions = extract_math_expressions(content)
|
|
203
186
|
if solution:
|
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()
|