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,89 @@
|
||||
---
|
||||
name: extract_entities_gliner
|
||||
kind: function
|
||||
lang: py
|
||||
domain: datascience
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def extract_entities_gliner(text: str, entity_schema: list[dict], model: Any, threshold: float = 0.5, flat_ner: bool = True) -> list[EntityCandidate]"
|
||||
description: "Extrae entidades zero-shot con GLiNER. Drop-in del contrato de extract_entities_llm pero 50-200x mas rapido y sin coste por token. El caller inyecta el modelo cargado con gliner_load_model. Anota offsets start/end en attributes para reconciliar con extract_iocs."
|
||||
tags: [gliner, ner, nlp, entity, extract, zero-shot, osint, graph, datascience, python]
|
||||
uses_functions: [gliner_load_model_py_datascience]
|
||||
uses_types: [entity_candidate_py_datascience]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [warnings]
|
||||
params:
|
||||
- name: text
|
||||
desc: "chunk de texto a analizar (parrafo, documento corto, output de OCR)"
|
||||
- name: entity_schema
|
||||
desc: "lista de dicts con 'type_ref' y 'label'. Mismo formato que extract_entities_llm. El 'label' se usa como label de GLiNER."
|
||||
- name: model
|
||||
desc: "instancia GLiNER cargada con gliner_load_model. Inyectar para evitar penalty de carga en batch."
|
||||
- name: threshold
|
||||
desc: "score minimo para aceptar una entidad (0.0-1.0). Defecto 0.5 — ajustable segun precision/recall objetivo."
|
||||
- name: flat_ner
|
||||
desc: "True (defecto) sin entidades anidadas; False permite spans solapados (ej. 'Universidad de Madrid' como ORG y 'Madrid' como LOC en simultaneo)"
|
||||
output: "lista de EntityCandidate con name, type_ref, type_label, confidence y attributes={'start': int, 'end': int}"
|
||||
tested: true
|
||||
tests:
|
||||
- "Schema basico y modelo stub retorna EntityCandidate con offsets"
|
||||
- "Threshold filtra spans con score bajo"
|
||||
- "Schema vacio lanza ValueError"
|
||||
- "Schema sin label+type_ref validos retorna vacio con warning"
|
||||
- "Excepcion del modelo se captura y retorna vacio"
|
||||
- "Label desconocido se descarta"
|
||||
- "flat_ner se propaga al modelo"
|
||||
test_file_path: "python/functions/datascience/tests/test_extract_entities_gliner.py"
|
||||
file_path: "python/functions/datascience/extract_entities_gliner.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
from python.functions.datascience import (
|
||||
gliner_load_model,
|
||||
extract_entities_gliner,
|
||||
)
|
||||
|
||||
model = gliner_load_model(device="auto")
|
||||
|
||||
schema = [
|
||||
{"type_ref": "osint_person_go_cybersecurity", "label": "Person"},
|
||||
{"type_ref": "osint_organization_go_cybersecurity", "label": "Organization"},
|
||||
{"type_ref": "osint_location_go_cybersecurity", "label": "Location"},
|
||||
]
|
||||
|
||||
text = "Alice Johnson works at OpenAI in San Francisco."
|
||||
entities = extract_entities_gliner(text, schema, model, threshold=0.4)
|
||||
# [EntityCandidate(name='Alice Johnson', type_ref='osint_person_go_cybersecurity',
|
||||
# attributes={'start': 0, 'end': 13}, confidence=0.92), ...]
|
||||
```
|
||||
|
||||
## Drop-in con extract_entities_llm
|
||||
|
||||
El retorno es identico (`list[EntityCandidate]`), por lo que se puede sustituir
|
||||
sin tocar el resto del pipeline (`deduplicate_entities`, `merge_entity_attributes`,
|
||||
etc). Diferencias:
|
||||
|
||||
- **Coste**: GLiNER = 0 USD/token. LLM = depende de modelo.
|
||||
- **Latencia**: GLiNER 50-200x mas rapido en GPU.
|
||||
- **IoCs tecnicos** (IPs, hashes, wallets, CVEs): GLiNER es malo — usar
|
||||
`extract_iocs_py_cybersecurity` para esos. Combinar regex + GLiNER en
|
||||
el pipeline hibrido (issue 0040).
|
||||
- **Schemas con muchos tipos**: GLiNER pierde precision con >20 labels;
|
||||
LLM la mantiene. Para esquemas grandes, dividir en bloques.
|
||||
- **Razonamiento implicito** ("CEO de la empresa"): el LLM lo deduce, GLiNER
|
||||
solo extrae lo explicito.
|
||||
|
||||
## Notas
|
||||
|
||||
- El modelo se carga UNA vez por proceso. No cargarlo aqui dentro: penalty fatal
|
||||
en batch. Inyeccion explicita por contrato.
|
||||
- impure: el modelo es estado externo (memoria, GPU si aplica). `error_type:
|
||||
error_go_core` segun la regla de pureza del registry.
|
||||
- Si `flat_ner=False`, validar que el caller dedupica/normaliza spans solapados
|
||||
— `EntityCandidate.attributes['start'/'end']` permite hacerlo facilmente.
|
||||
- Para precision maxima, ajustar `threshold` por dominio: 0.3-0.4 para recall
|
||||
alto, 0.6-0.8 para precision alta.
|
||||
Reference in New Issue
Block a user