5e6023f639
Cierra el ciclo del analysis gliner_glirel_tuning: documenta en app.md el pipeline NER+RE disponible en el registry y abre los dos issues que faltan para cablearlo en extract_graph_hybrid + panel paste_extract. Archiva el 0042 original (mREBEL) tras la decision a favor de GLiNER2 (Apache 2.0, joint NER+RE, 20-30x mas rapido en CPU). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
175 lines
7.6 KiB
Plaintext
175 lines
7.6 KiB
Plaintext
---
|
||
id: 0042
|
||
title: Sustituir GLiREL por mREBEL como extractor de relaciones (uso no comercial)
|
||
status: pending
|
||
priority: high
|
||
created: 2026-05-04
|
||
parent: 0013
|
||
depends_on: [0041]
|
||
---
|
||
|
||
## Contexto
|
||
|
||
Tras integrar GLiREL en `extract_graph_hybrid` (issue 0013), el analisis empirico (`projects/osint_graph/analysis/gliner_glirel_tuning/`) revelo que GLiREL falla estrepitosamente sobre narrativa empresarial castellana: 51 falsos positivos a `t=0.15`, 1 unica relacion (tambien falsa) a `t=0.30`. **No hay sweet spot**.
|
||
|
||
Probado el sustituto **`Babelscape/mrebel-large`** (mBART seq2seq que genera tripletas directamente del texto) sobre el mismo corpus: **8 tripletas crudas, 5 alineables con entidades GLiNER, 4 inequivocamente correctas, cero falsos absurdos**.
|
||
|
||
Decision documentada en `vaults/osint_nlp_models/decisions/2026-05-04-mrebel-over-glirel.md`.
|
||
|
||
## Restriccion de licencia
|
||
|
||
mREBEL es **CC BY-NC-SA 4.0 — uso no comercial**. Compatible con `graph_explorer` actual (uso personal de investigacion). Bloqueante si pasa a producto comercial. Plan de contingencia documentado en `vaults/osint_nlp_models/decisions/2026-05-04-license-constraint.md`. **Hay que marcar la restriccion en el codigo y en el `app.md`.**
|
||
|
||
## Objetivo
|
||
|
||
Anadir mREBEL como capa 4 alternativa en `extract_graph_hybrid`, configurable por el usuario en el panel `paste_extract`. GLiREL no se borra — queda disponible para textos en ingles donde su comportamiento puede ser razonable.
|
||
|
||
## Diseño
|
||
|
||
### 1. Funciones nuevas en el registry
|
||
|
||
```
|
||
python/functions/datascience/
|
||
├── mrebel_load_model.py # carga + cache (mBART, src_lang, tgt_lang)
|
||
├── extract_relations_mrebel.py # frase a frase, devuelve list[RelationCandidate]
|
||
└── _mrebel_parser.py # parser del wire format <triplet>...</s>
|
||
```
|
||
|
||
Plantilla `mrebel_load_model.py`:
|
||
|
||
```python
|
||
"""LICENSE: CC BY-NC-SA 4.0 — non-commercial use only.
|
||
|
||
Carga (y cachea) Babelscape/mrebel-large para extraccion de relaciones.
|
||
"""
|
||
from typing import Any
|
||
_MODEL_CACHE: dict[tuple[str, str], Any] = {}
|
||
|
||
def mrebel_load_model(
|
||
model_name: str = "Babelscape/mrebel-large",
|
||
src_lang: str = "es_XX",
|
||
tgt_lang: str = "tp_XX",
|
||
) -> tuple[Any, Any]:
|
||
key = (model_name, src_lang)
|
||
if key in _MODEL_CACHE: return _MODEL_CACHE[key]
|
||
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
||
tok = AutoTokenizer.from_pretrained(model_name, src_lang=src_lang, tgt_lang=tgt_lang)
|
||
mdl = AutoModelForSeq2SeqLM.from_pretrained(model_name)
|
||
_MODEL_CACHE[key] = (tok, mdl)
|
||
return tok, mdl
|
||
```
|
||
|
||
`extract_relations_mrebel.py` toma `text`, `entities` (de GLiNER), trocea por frases con `re.split`, llama a mREBEL frase a frase, parsea tripletas, hace **string-match con `entity.name`**, y devuelve `list[RelationCandidate]` con `relation_type` igual al `type` que emite mREBEL (vocabulario Wikidata). Las tripletas sin match se descartan silenciosamente.
|
||
|
||
### 2. Cambio en `extract_graph_hybrid.py`
|
||
|
||
Anadir parametro `relation_engine` con tres valores:
|
||
|
||
```python
|
||
def extract_graph_hybrid(
|
||
chunks, entity_schema, relation_types,
|
||
gliner_model,
|
||
relation_engine: str = "mrebel", # 'glirel' | 'mrebel' | 'none'
|
||
glirel_model=None, # solo si engine == 'glirel'
|
||
mrebel_models=None, # tuple (tok, mdl) si engine == 'mrebel'
|
||
llm_chat_json=None,
|
||
entity_threshold=0.5,
|
||
relation_threshold=0.15,
|
||
):
|
||
...
|
||
if relation_engine == "glirel":
|
||
rels = extract_relations_glirel(chunk, ents, relation_types, glirel_model, threshold=relation_threshold)
|
||
elif relation_engine == "mrebel":
|
||
rels = extract_relations_mrebel(chunk, ents, mrebel_models)
|
||
elif relation_engine == "none":
|
||
rels = []
|
||
...
|
||
```
|
||
|
||
**`relation_types` es ignorado** cuando `engine == 'mrebel'` (el modelo emite vocab Wikidata cerrado). Quiza convenga dejar un parametro opcional `accept_relation_types` que filtre la salida si el usuario quiere taxonomia controlada.
|
||
|
||
### 3. Cambio en `enrichers/paste_extract/run.py`
|
||
|
||
```yaml
|
||
# manifest
|
||
params:
|
||
text: { type: string, required: true }
|
||
use_hybrid: { type: bool, default: true }
|
||
relation_engine: { type: enum, values: [glirel, mrebel, none], default: mrebel }
|
||
entity_threshold: { type: float, default: 0.5 }
|
||
relation_threshold: { type: float, default: 0.15 } # solo aplicable si engine=glirel
|
||
```
|
||
|
||
### 4. Cambios en el panel C++ (`extract_panel.cpp`)
|
||
|
||
- Combo `Relation engine: [GLiREL | mREBEL | None]` debajo del toggle hybrid.
|
||
- **Banner de licencia** cuando `engine == mREBEL`: amarillo, texto fijo `"mREBEL: CC BY-NC-SA — non-commercial use only"`.
|
||
- **Barra de progreso por frase** cuando engine=mREBEL (latencia ~3s/frase). El subprocess emite `PROGRESS:0.X frase_N` cada frase, el panel lo lee y refresca un `ImGui::ProgressBar`.
|
||
|
||
### 5. `app.md` — declarar la restriccion
|
||
|
||
```yaml
|
||
---
|
||
...
|
||
notes: |
|
||
El motor de relaciones por defecto es mREBEL (Babelscape/mrebel-large),
|
||
bajo licencia CC BY-NC-SA 4.0. NO usar en contextos comerciales sin
|
||
cambiar el motor a GLiREL (Apache 2.0) o LLM externo.
|
||
optional_dependencies:
|
||
- "transformers (mREBEL/GLiREL)"
|
||
- "sentencepiece (mBART tokenizer)"
|
||
---
|
||
```
|
||
|
||
### 6. Tests pytest
|
||
|
||
`tests/test_extract_relations_mrebel.py`:
|
||
|
||
- Test parser de output mREBEL (formato `<triplet> ... </s>`).
|
||
- Test string-match con entidades fuzzy (substring + exact).
|
||
- Test que tripletas sin match desaparecen.
|
||
- Test smoke con stub de modelo (mock que retorna decoded canónico).
|
||
- Test integracion: pasar texto ES → ents GLiNER fake → tripletas alineadas.
|
||
|
||
`tests/test_paste_extract.py`: añadir test del path `relation_engine=mrebel`.
|
||
|
||
### 7. Documentacion
|
||
|
||
- `app.md` — seccion de relaciones explica los 3 engines + licencia.
|
||
- `python/functions/datascience/mrebel_load_model.md` — frontmatter completo + restriccion de licencia.
|
||
|
||
## Definicion de hecho
|
||
|
||
- Pego el texto del corpus es_corporate, selecciono `engine=mREBEL`, pulso Extract.
|
||
- Tras ~25s veo las 4 relaciones limpias en la tabla (Pablo Isla→Inditex, Endesa→Marina Serrano, BBVA→Carlos Torres, Arteixo→A Coruna).
|
||
- Cambio a `engine=GLiREL` y veo las 51 espurias (validacion contraria).
|
||
- Apply solo guarda las marcadas; dedupe sigue funcionando.
|
||
- Banner de licencia amarillo es visible cuando engine=mREBEL.
|
||
- Tests verdes en WSL.
|
||
|
||
## Latencia / UX
|
||
|
||
- mREBEL es ~50× mas lento que GLiREL (3s/frase vs 50ms/grafo entero).
|
||
- Para texto largo (20+ frases) son ~60s. **Barra de progreso es obligatoria.**
|
||
- Plan: emitir `PROGRESS:f frase=N/M` cada frase; panel C++ refresca ProgressBar.
|
||
- Cancelacion: boton "Cancel" mata el subprocess limpiamente (issue futuro si no es trivial).
|
||
|
||
## Out of scope (issues separados)
|
||
|
||
- LLM como validator semantico de tripletas mREBEL — futuro.
|
||
- mREBEL en GPU (issue de infra, requires CUDA). En CPU es usable.
|
||
- Mapeo del vocabulario Wikidata a una taxonomia OSINT custom — issue futuro.
|
||
- Cancelacion del subprocess (Cancel button) — micro-issue.
|
||
|
||
## Riesgos
|
||
|
||
- **Latencia.** El usuario puede sentir que la app esta colgada sin la barra de progreso. Mitigacion: progress por frase + tip "modelo grande, primer texto descarga 2.4 GB".
|
||
- **Cobertura del vocabulario Wikidata** en dominios fuera de business/journalism. Para narrativa funciona; para OSINT, drama tipico, puede no encajar — habra que probar y posiblemente añadir mapeo custom (issue futuro).
|
||
- **Licencia.** Reducible solo via diseño claro de UI (banner) + documentacion (app.md, vault).
|
||
|
||
## Relacionado
|
||
|
||
- `vaults/osint_nlp_models/models/mrebel.md` — observaciones empiricas
|
||
- `vaults/osint_nlp_models/decisions/2026-05-04-mrebel-over-glirel.md` — ADR
|
||
- Notebooks 01, 02, 03 en `analysis/gliner_glirel_tuning/`
|