Files
fn_registry/dev/issues/completed/0038-gliner-entity-extractor.md
T

3.9 KiB
Raw Blame History

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0038 GLiNER entity extractor (zero-shot NER) completado feature
multi-app alta
2026-05-17 2026-05-17

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.