Spaces:
Running
Running
Download processing/embeddings.py from successjourney/arginfluence: direct link, hf CLI and curl.
- Browser
- Download file 5.89 kB
-
https://huggingface.co/spaces/successjourney/arginfluence/resolve/main/processing/embeddings.py
- Command line
-
hf download hf://spaces/successjourney/arginfluence/processing/embeddings.py
-
curl -L -o embeddings.py https://huggingface.co/spaces/successjourney/arginfluence/resolve/main/processing/embeddings.py
5.89 kB
| """ | |
| embeddings.py — Vectorización de textos via HuggingFace Inference API. | |
| Convierte textos en vectores numéricos (embeddings) usando el modelo | |
| paraphrase-multilingual-MiniLM-L12-v2 corriendo en la nube de HuggingFace. | |
| No requiere GPU ni modelos locales — todo corre remotamente. | |
| Manejo de errores: | |
| - 503 (modelo cargando): espera HF_RETRY_DELAY segundos, reintenta hasta 3 veces | |
| - 429 (rate limit): espera HF_RATELIMIT_DELAY segundos, reintenta | |
| - Otros errores: loguea y devuelve lista vacía para ese batch | |
| """ | |
| import logging | |
| import time | |
| from typing import Any | |
| import requests | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from config import ( | |
| HUGGINGFACE_API_KEY, | |
| HF_API_URL_EMBEDDINGS, | |
| HF_BATCH_SIZE, | |
| HF_RETRY_DELAY, | |
| HF_RATELIMIT_DELAY, | |
| SCRAPING_MAX_RETRIES, | |
| LOG_FORMAT, | |
| LOG_DATE_FORMAT, | |
| LOG_LEVEL, | |
| ) | |
| # ── Logging ─────────────────────────────────────────────────────────────────── | |
| logging.basicConfig(format=LOG_FORMAT, datefmt=LOG_DATE_FORMAT) | |
| logger = logging.getLogger("embeddings") | |
| logger.setLevel(getattr(logging, LOG_LEVEL, logging.INFO)) | |
| def _headers() -> dict: | |
| """Construye los headers HTTP para la API de HuggingFace.""" | |
| headers = {"Content-Type": "application/json"} | |
| if HUGGINGFACE_API_KEY: | |
| headers["Authorization"] = f"Bearer {HUGGINGFACE_API_KEY}" | |
| return headers | |
| def _fetch_batch(textos: list[str]) -> list[list[float]]: | |
| """ | |
| Envía un batch de textos a HuggingFace y devuelve sus embeddings. | |
| Maneja reintentos automáticos para errores 503 y 429. | |
| Devuelve lista vacía si todos los intentos fallan. | |
| """ | |
| payload = {"inputs": textos, "options": {"wait_for_model": True}} | |
| for intento in range(1, SCRAPING_MAX_RETRIES + 1): | |
| try: | |
| t0 = time.time() | |
| resp = requests.post( | |
| HF_API_URL_EMBEDDINGS, | |
| headers=_headers(), | |
| json=payload, | |
| timeout=60, | |
| ) | |
| elapsed = time.time() - t0 | |
| if resp.status_code == 200: | |
| resultado = resp.json() | |
| logger.info( | |
| f"[embeddings] batch {len(textos)} textos — " | |
| f"{elapsed:.1f}s — dim={len(resultado[0]) if resultado else 0}" | |
| ) | |
| return resultado | |
| elif resp.status_code == 503: | |
| logger.warning( | |
| f"[embeddings] 503 modelo cargando (intento {intento}/" | |
| f"{SCRAPING_MAX_RETRIES}) — esperando {HF_RETRY_DELAY}s" | |
| ) | |
| time.sleep(HF_RETRY_DELAY) | |
| elif resp.status_code == 429: | |
| logger.warning( | |
| f"[embeddings] 429 rate limit — esperando {HF_RATELIMIT_DELAY}s" | |
| ) | |
| time.sleep(HF_RATELIMIT_DELAY) | |
| else: | |
| logger.error( | |
| f"[embeddings] error {resp.status_code}: {resp.text[:200]}" | |
| ) | |
| return [] | |
| except requests.exceptions.Timeout: | |
| logger.warning(f"[embeddings] timeout en intento {intento}/{SCRAPING_MAX_RETRIES}") | |
| if intento < SCRAPING_MAX_RETRIES: | |
| time.sleep(HF_RETRY_DELAY) | |
| except Exception as e: | |
| logger.error(f"[embeddings] error inesperado: {e}") | |
| return [] | |
| logger.error(f"[embeddings] todos los reintentos fallaron para batch de {len(textos)} textos") | |
| return [] | |
| def get_embeddings(texts: list[str]) -> list[list[float]]: | |
| """ | |
| Vectoriza una lista de textos usando HuggingFace Inference API. | |
| Procesa en batches de HF_BATCH_SIZE para respetar los límites del free tier. | |
| Los textos vacíos se saltan y se devuelve lista vacía en su posición. | |
| Parámetros: | |
| texts: lista de strings a vectorizar | |
| Devuelve: | |
| Lista de embeddings (lista de floats). Mismo orden que la entrada. | |
| Las posiciones con error devuelven lista vacía []. | |
| """ | |
| if not texts: | |
| return [] | |
| todos_los_embeddings: list[list[float]] = [] | |
| for i in range(0, len(texts), HF_BATCH_SIZE): | |
| batch = texts[i: i + HF_BATCH_SIZE] | |
| # Filtrar textos vacíos, preservar posiciones | |
| indices_validos = [j for j, t in enumerate(batch) if t and t.strip()] | |
| textos_validos = [batch[j] for j in indices_validos] | |
| if not textos_validos: | |
| todos_los_embeddings.extend([[] for _ in batch]) | |
| continue | |
| embeddings_batch = _fetch_batch(textos_validos) | |
| # Validar que la API devolvió el mismo número de embeddings que textos | |
| if embeddings_batch and len(embeddings_batch) != len(textos_validos): | |
| logger.warning( | |
| f"[embeddings] mismatch: enviados {len(textos_validos)} textos, " | |
| f"recibidos {len(embeddings_batch)} embeddings — se completan con []" | |
| ) | |
| # Completar o recortar para que el zip funcione correctamente | |
| if len(embeddings_batch) < len(textos_validos): | |
| embeddings_batch.extend([] for _ in range(len(textos_validos) - len(embeddings_batch))) | |
| else: | |
| embeddings_batch = embeddings_batch[:len(textos_validos)] | |
| # Reconstruir con posiciones originales | |
| resultado_batch: list[list[float]] = [[] for _ in batch] | |
| for pos, emb in zip(indices_validos, embeddings_batch): | |
| resultado_batch[pos] = emb | |
| todos_los_embeddings.extend(resultado_batch) | |
| logger.info( | |
| f"[embeddings] completado — {len(texts)} textos, " | |
| f"{sum(1 for e in todos_los_embeddings if e)} con embedding válido" | |
| ) | |
| return todos_los_embeddings | |