|
from typing import Dict, List, Optional, Union |
|
import spacy |
|
from transformers import AutoTokenizer, AutoModel |
|
import torch |
|
import numpy as np |
|
import re |
|
from patterns import ( |
|
PATRONES_AMBIGUEDAD_LEXICA, |
|
PATRONES_AMBIGUEDAD_SINTACTICA, |
|
SUGERENCIAS_MEJORA |
|
) |
|
|
|
class SemanticAnalyzer: |
|
""" |
|
Analizador semántico que utiliza embeddings para comparar textos. |
|
""" |
|
def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"): |
|
""" |
|
Inicializa el analizador semántico. |
|
|
|
Args: |
|
model_name (str): Nombre del modelo de HuggingFace a utilizar |
|
""" |
|
try: |
|
self.tokenizer = AutoTokenizer.from_pretrained(model_name) |
|
self.model = AutoModel.from_pretrained(model_name) |
|
except Exception as e: |
|
raise RuntimeError(f"Error cargando el modelo {model_name}: {str(e)}") |
|
|
|
def get_embedding(self, texto: str) -> np.ndarray: |
|
""" |
|
Obtiene el embedding de un texto usando el modelo de transformers. |
|
|
|
Args: |
|
texto (str): Texto a procesar |
|
|
|
Returns: |
|
np.ndarray: Vector de embedding |
|
""" |
|
inputs = self.tokenizer(texto, return_tensors="pt", padding=True, truncation=True) |
|
with torch.no_grad(): |
|
outputs = self.model(**inputs) |
|
return outputs.last_hidden_state.mean(dim=1).numpy()[0] |
|
|
|
def calcular_similitud(self, texto1: str, texto2: str) -> float: |
|
""" |
|
Compara la similitud semántica entre dos textos. |
|
|
|
Args: |
|
texto1 (str): Primer texto |
|
texto2 (str): Segundo texto |
|
|
|
Returns: |
|
float: Score de similitud entre 0 y 1 |
|
""" |
|
emb1 = self.get_embedding(texto1) |
|
emb2 = self.get_embedding(texto2) |
|
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2)) |
|
return float(similarity) |
|
|
|
class AmbiguityClassifier: |
|
""" |
|
Clasificador de ambigüedades en historias de usuario. |
|
Detecta ambigüedades léxicas y sintácticas, y proporciona sugerencias de mejora. |
|
""" |
|
|
|
def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"): |
|
""" |
|
Inicializa el clasificador de ambigüedades. |
|
|
|
Args: |
|
model_name (str): Nombre del modelo de HuggingFace a utilizar |
|
""" |
|
try: |
|
self.nlp = spacy.load("es_core_news_sm") |
|
except OSError: |
|
raise RuntimeError("Es necesario instalar el modelo es_core_news_sm. Ejecute: python -m spacy download es_core_news_sm") |
|
|
|
self.semantic_analyzer = SemanticAnalyzer(model_name) |
|
|
|
def __call__(self, texto: str) -> Dict[str, Union[bool, List[str], float]]: |
|
""" |
|
Analiza una historia de usuario en busca de ambigüedades. |
|
|
|
Args: |
|
texto (str): Historia de usuario a analizar |
|
|
|
Returns: |
|
Dict: Resultado del análisis con tipos de ambigüedad y sugerencias |
|
""" |
|
if not texto or not isinstance(texto, str): |
|
return { |
|
"tiene_ambiguedad": False, |
|
"ambiguedad_lexica": [], |
|
"ambiguedad_sintactica": [], |
|
"sugerencias": ["El texto está vacío o no es válido"], |
|
"score_ambiguedad": 0.0 |
|
} |
|
|
|
|
|
doc = self.nlp(texto.strip()) |
|
|
|
|
|
ambiguedades_lexicas = [] |
|
for patron in PATRONES_AMBIGUEDAD_LEXICA: |
|
if re.search(patron["patron"], texto, re.IGNORECASE): |
|
ambiguedades_lexicas.append({ |
|
"tipo": patron["tipo"], |
|
"descripcion": patron["descripcion"] |
|
}) |
|
|
|
|
|
ambiguedades_sintacticas = [] |
|
for patron in PATRONES_AMBIGUEDAD_SINTACTICA: |
|
if re.search(patron["patron"], texto, re.IGNORECASE): |
|
ambiguedades_sintacticas.append({ |
|
"tipo": patron["tipo"], |
|
"descripcion": patron["descripcion"] |
|
}) |
|
|
|
|
|
sugerencias = [] |
|
if ambiguedades_lexicas or ambiguedades_sintacticas: |
|
for ambiguedad in ambiguedades_lexicas + ambiguedades_sintacticas: |
|
tipo = ambiguedad["tipo"] |
|
if tipo in SUGERENCIAS_MEJORA: |
|
sugerencias.extend(SUGERENCIAS_MEJORA[tipo]) |
|
|
|
|
|
score = len(ambiguedades_lexicas) * 0.4 + len(ambiguedades_sintacticas) * 0.6 |
|
score_normalizado = min(1.0, score / 5.0) |
|
|
|
return { |
|
"tiene_ambiguedad": bool(ambiguedades_lexicas or ambiguedades_sintacticas), |
|
"ambiguedad_lexica": [amb["descripcion"] for amb in ambiguedades_lexicas], |
|
"ambiguedad_sintactica": [amb["descripcion"] for amb in ambiguedades_sintacticas], |
|
"sugerencias": sugerencias if sugerencias else ["No se encontraron ambigüedades"], |
|
"score_ambiguedad": round(score_normalizado, 2) |
|
} |
|
|
|
def analizar_similitud_semantica(self, texto1: str, texto2: str) -> float: |
|
""" |
|
Compara la similitud semántica entre dos textos. |
|
|
|
Args: |
|
texto1 (str): Primer texto |
|
texto2 (str): Segundo texto |
|
|
|
Returns: |
|
float: Score de similitud entre 0 y 1 |
|
""" |
|
return self.semantic_analyzer.calcular_similitud(texto1, texto2) |