--- 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.