feat(datascience): GLiNER entity extractor (zero-shot NER) drop-in con LLM
Funciones nuevas en python/functions/datascience/: - gliner_load_model: carga + cachea modelo GLiNER por (name, device). device='auto' resuelve a cuda/cpu segun torch.cuda.is_available, sin fallar si torch no esta instalado. ImportError claro si falta gliner. - extract_entities_gliner: contrato drop-in de extract_entities_llm (mismo entity_schema, mismo list[EntityCandidate]). El caller inyecta el modelo (cargado UNA vez por proceso). Anota offsets start/end en attributes para reconciliar con extract_iocs (issue 0040). Diferencias vs LLM extractor: - 50-200x mas rapido en GPU, 0 USD/token. - Malo con IoCs tecnicos (lo cubre 0037). - Threshold y flat_ner ajustables por dominio. pyproject.toml: gliner como extra opcional `[nlp]` para no inflar el .venv de quien no use NER. Instalacion: `uv pip install -e '.[nlp]'`. Refs #0038 — Desbloquea 0039 (GLiREL) y 0040 (pipeline hibrido). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Carga (y cachea) un modelo GLiNER en el device deseado."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Cache global: (model_name, device) -> modelo cargado.
|
||||
_MODEL_CACHE: dict[tuple[str, str], Any] = {}
|
||||
|
||||
|
||||
def _resolve_device(device: str) -> str:
|
||||
"""Resuelve `device='auto'` a `cuda` o `cpu` segun disponibilidad."""
|
||||
if device != "auto":
|
||||
return device
|
||||
try:
|
||||
import torch
|
||||
except ImportError:
|
||||
return "cpu"
|
||||
return "cuda" if torch.cuda.is_available() else "cpu"
|
||||
|
||||
|
||||
def gliner_load_model(
|
||||
model_name: str = "urchade/gliner_multi-v2.1",
|
||||
device: str = "auto",
|
||||
) -> Any:
|
||||
"""Carga un modelo GLiNER con cache por (model_name, device).
|
||||
|
||||
La primera llamada descarga el modelo desde HuggingFace (~200 MB para
|
||||
`gliner_multi-v2.1`). Llamadas sucesivas con los mismos parametros
|
||||
devuelven la instancia cacheada.
|
||||
|
||||
Args:
|
||||
model_name: ID del modelo en HuggingFace Hub.
|
||||
device: 'auto' usa CUDA si esta disponible, o 'cpu'/'cuda'/'cuda:N'
|
||||
de forma explicita.
|
||||
|
||||
Returns:
|
||||
Instancia del modelo GLiNER lista para `predict_entities`.
|
||||
|
||||
Raises:
|
||||
ImportError: si la dependencia `gliner` no esta instalada.
|
||||
Solucion: `uv pip install gliner` o instalar el extra `nlp`
|
||||
del proyecto (`uv pip install -e '.[nlp]'`).
|
||||
"""
|
||||
resolved_device = _resolve_device(device)
|
||||
cache_key = (model_name, resolved_device)
|
||||
cached = _MODEL_CACHE.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
try:
|
||||
from gliner import GLiNER
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"gliner no esta instalado. Instalalo con "
|
||||
"`uv pip install gliner` o `uv pip install -e '.[nlp]'`."
|
||||
) from exc
|
||||
|
||||
model = GLiNER.from_pretrained(model_name)
|
||||
if hasattr(model, "to"):
|
||||
model.to(resolved_device)
|
||||
_MODEL_CACHE[cache_key] = model
|
||||
return model
|
||||
Reference in New Issue
Block a user