--- name: extract_relations_glirel kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def extract_relations_glirel(text: str, entities: list[EntityCandidate], relation_types: list[str], model: Any, threshold: float = 0.5, max_pairs: int = 0) -> list[RelationCandidate]" description: "Extrae relaciones zero-shot con GLiREL. Drop-in del contrato de extract_relations_llm pero sin coste por token y mas rapido para corpus grandes. Tokeniza por whitespace, mapea spans de entidades (de attributes['start'/'end'] o fallback text.find) a indices de tokens, y devuelve RelationCandidate cuyos from_name/to_name siempre coinciden con entidades del input." tags: [glirel, relation, nlp, extract, zero-shot, knowledge-graph, fuzzygraph, graph, datascience, python] uses_functions: [glirel_load_model_py_datascience] uses_types: - entity_candidate_py_datascience - relation_candidate_py_datascience returns: - relation_candidate_py_datascience returns_optional: false error_type: "error_go_core" imports: [warnings, re] params: - name: text desc: "mismo chunk de texto que se uso para extraer las entidades (parrafo, doc corto)" - name: entities desc: "lista de EntityCandidate ya extraidas (de extract_entities_gliner, extract_entities_llm o regex). Si tienen attributes['start'/'end'] se usan; si no, fallback a text.find(name) con warning." - name: relation_types desc: "tipos de relacion permitidos, ej: ['works_for','owns','communicated_with']. Vacio lanza ValueError." - name: model desc: "instancia GLiREL cargada con glirel_load_model. Inyectar para evitar penalty de carga en batch." - name: threshold desc: "score minimo para aceptar una relacion (0.0-1.0). Defecto 0.5." - name: max_pairs desc: "0 = todas las relaciones encontradas. >0 = top N por score (descarta el resto)." output: "lista de RelationCandidate(from_name, to_name, relation_type, description='', confidence). from_name/to_name siempre coinciden con entidades del input." tested: true tests: - "Schema basico y modelo stub retorna RelationCandidate triplets validos" - "Threshold se propaga al modelo" - "relation_types vacio lanza ValueError" - "Menos de 2 entidades retorna vacio" - "Entidad sin offsets usa fallback text.find con warning" - "Entidad cuyo nombre no aparece en el texto se descarta" - "Excepcion del modelo se captura y retorna vacio" - "Relation_type fuera del set permitido se descarta" - "max_pairs=N limita el output a top N por score" - "head_pos/tail_pos resuelven entidades por posicion de token" - "Fallback por head_text/tail_text si head_pos no esta presente" test_file_path: "python/functions/datascience/tests/test_extract_relations_glirel.py" file_path: "python/functions/datascience/extract_relations_glirel.py" --- ## Ejemplo ```python from python.functions.datascience import ( glirel_load_model, extract_relations_glirel, ) from python.types.datascience.entity_candidate import EntityCandidate model = glirel_load_model(device="auto") text = "Alice Johnson works at OpenAI in San Francisco." entities = [ EntityCandidate(name="Alice Johnson", type_label="Person", attributes={"start": 0, "end": 13}, confidence=0.92), EntityCandidate(name="OpenAI", type_label="Organization", attributes={"start": 23, "end": 29}, confidence=0.87), EntityCandidate(name="San Francisco", type_label="Location", attributes={"start": 33, "end": 46}, confidence=0.81), ] relations = extract_relations_glirel( text=text, entities=entities, relation_types=["works_for", "located_in", "owns"], model=model, threshold=0.5, ) # [RelationCandidate(from_name='Alice Johnson', to_name='OpenAI', # relation_type='works_for', confidence=0.91), ...] ``` ## Drop-in con extract_relations_llm El retorno es identico (`list[RelationCandidate]`) y `from_name`/`to_name` siempre coinciden con entidades del input — `deduplicate_relations_py_datascience` lo acepta sin cambios. Diferencias: - **Coste**: GLiREL = 0 USD/token. LLM = depende del modelo. - **Latencia**: GLiREL es mucho mas rapido en GPU; en CPU depende del numero de pares (entidades x relation_types). - **Razonamiento implicito**: el LLM lo deduce ("CEO de la empresa" -> persona works_for empresa); GLiREL solo extrae lo explicito en el texto. - **Esquemas grandes**: GLiREL escala bien con muchos relation_types; el LLM pierde foco con esquemas muy largos. - **Idiomas**: GLiREL-large-v0 esta entrenado principalmente en ingles. Para ES evaluar precision/recall caso a caso o caer al LLM. ## Spans de entidades GLiREL necesita los spans (token indices) de cada entidad en el texto. Esta funcion: 1. Lee `attributes["start"]` y `attributes["end"]` (offsets de caracteres) si existen — el output natural de `extract_entities_gliner` y `extract_iocs`. 2. Si faltan, usa `text.find(entity.name)` como fallback (con warning). 3. Tokeniza por whitespace y mapea cada char span a un span de tokens (`[start_token, end_token]`). 4. Pasa todo a `model.predict_relations(tokens, labels=..., ner=...)`. Si la entidad no se puede localizar en el texto, se descarta (no se le pueden buscar relaciones sin saber donde esta). ## Notas - impure: el modelo es estado externo. `error_type: error_go_core` segun la regla de pureza del registry. - Si dos entidades tienen el mismo nombre, GLiREL podria mezclarlas; el matcheo por `head_pos`/`tail_pos` (token start) las distingue mejor que `head_text`. - Una `relation_type` que no aparece en el output NO es un error — solo significa que GLiREL no encontro evidencia. - Combinar con LLM para razonamiento implicito: ver issue 0040 (pipeline hibrido). - Para precision maxima, ajustar `threshold` por dominio: 0.3-0.4 = recall alto; 0.6-0.8 = precision alta. ## Limitacion GLiREL es bueno para relaciones explicitas en el texto (`X trabaja en Y`, `A llamo a B`), malo para razonamiento implicito (`la nueva CEO`, `su empresa`). Para razonamiento implicito seguir usando `extract_relations_llm`. El pipeline hibrido (issue 0040) compone GLiREL para extraccion masiva + LLM para los casos implicitos que GLiREL no cubre.