Files
fn_registry/dev/issues/completed/0038-gliner-entity-extractor.md
T
egutierrez 20374a9220 docs(issues): cerrar 0038 — GLiNER entity extractor
- Move dev/issues/0038-gliner-entity-extractor.md a completed/
- Update README link y estado a completado

Closes #0038

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 16:41:30 +02:00

3.7 KiB
Raw Blame History

0038 — GLiNER entity extractor (zero-shot NER)

Metadata

Campo Valor
ID 0038
Estado pendiente
Prioridad alta
Tipo feature — Python (python/functions/datascience/)

Dependencias

Recomendado leer extract_entities_llm_py_datascience, entity_candidate_py_datascience, build_entity_schema_prompt_py_datascience. El contrato debe ser drop-in con la version LLM.

Desbloquea: 0039 (GLiREL), 0040 (pipeline hibrido). Permite extraccion masiva sin coste por token.


Objetivo

Wrapper sobre GLiNER (urchade/gliner_multi-v2.1 por defecto, multilingue ES/EN) que extrae entidades zero-shot a partir del mismo entity_schema: list[dict] que ya consume extract_entities_llm. Misma firma, mismo retorno (list[EntityCandidate]), 50200x mas rapido y sin coste por token.

Funciones a crear

Function ID Rol
gliner_load_model_py_datascience Carga + cachea el modelo. purity: impure, kind: function
extract_entities_gliner_py_datascience Extractor por chunk. purity: impure, mismo contrato que la version LLM

Contrato

# gliner_load_model.py
def gliner_load_model(
    model_name: str = "urchade/gliner_multi-v2.1",
    device: str = "auto",   # "auto" | "cpu" | "cuda" | "cuda:0"
) -> "GLiNER": ...

# extract_entities_gliner.py
def extract_entities_gliner(
    text: str,
    entity_schema: list[dict],          # mismo formato que extract_entities_llm
    model: "GLiNER",                    # inyectado, cargado una sola vez
    threshold: float = 0.5,
    flat_ner: bool = True,              # False = nested entities
) -> list[EntityCandidate]

entity_schema se traduce internamente: cada {"name": "person", "label": "Person", ...} se convierte al label que GLiNER espera. EntityCandidate se rellena con:

  • name = span text
  • type_ref = entity name del schema
  • type_label = label legible del schema
  • confidence = score de GLiNER
  • attributes["start"], attributes["end"] = offsets en el chunk
  • source_chunk_indices lo setea el caller (igual que en la version LLM)

Pureza

gliner_load_model: impure (lee disco/red la primera vez, descarga ~200 MB de HF). extract_entities_gliner: impure (modelo es estado externo) — error_type: error_go_core siguiendo la regla del registry.

Deliverables

  • python/functions/datascience/gliner_load_model.py + .md
  • python/functions/datascience/extract_entities_gliner.py + .md
  • Tests en python/functions/datascience/tests/test_extract_entities_gliner.py con un corpus minimo (3-5 textos cortos ES/EN, schema con 4-5 tipos).
  • pyproject.toml: añadir gliner>=0.2.13 como dep opcional bajo [project.optional-dependencies] nlp = ["gliner", ...]. NO añadir a deps principales para no inflar .venv de quien no use NER.
  • Documentar en el .md: pip install gliner (o uv pip install gliner), tamaño del modelo, latencia esperada CPU vs GPU.

Validacion

  1. ./fn run extract_entities_gliner_py_datascience corre los tests.
  2. Bench manual: medir tokens/seg en un texto de 10 KB con 8 labels, CPU y GPU si hay.
  3. Compararlo con extract_entities_llm sobre el mismo corpus pequeño y registrar precision/recall en el .md.

Notas

  • Mantener el contrato identico al de la version LLM permite usar deduplicate_entities, merge_entity_attributes y todo el resto del pipeline sin cambios.
  • GLiNER es malo con IoCs tecnicos (IPs, hashes, wallets) — eso lo cubre 0037. Documentar la limitacion.
  • El modelo se carga UNA vez por proceso: el caller lo inyecta. No cargarlo dentro de extract_entities_gliner — penalty fatal en batch.
  • Si la GPU no esta disponible, device="auto" debe caer a CPU sin error.