docs: issues 0041 (split thresholds) + 0042 (GLiNER2), supersedes mREBEL
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>
This commit is contained in:
@@ -98,3 +98,34 @@ cmake --build build/linux --target graph_explorer -j$(nproc)
|
||||
filesystem propio para evitar lock con otras apps que esten escribiendo.
|
||||
- El `graph_hash` se calcula a partir del path canonico del input. Mismo path
|
||||
= mismo grafo a efectos de layout guardado.
|
||||
|
||||
### Pipeline NER+RE disponible en el registry (2026-05-04)
|
||||
|
||||
Tras la investigacion del analysis `gliner_glirel_tuning` (proyecto `osint_graph`), el stack completo de extraccion de entidades + relaciones desde texto / PDF esta listo como funciones del registry. **Esto desbloquea los issues 0041 y 0042**:
|
||||
|
||||
```python
|
||||
# Pipeline E2E recomendado (texto -> grafo)
|
||||
from pipelines.extract_graph_from_text import extract_graph_from_text
|
||||
from datascience.gliner2_load_model import gliner2_load_model
|
||||
|
||||
model = gliner2_load_model() # Apache 2.0, NER+RE joint, 340M params
|
||||
result = extract_graph_from_text(text, ENTITY_LABELS, RELATION_LABELS, ALLOWED, model)
|
||||
# result = {'nodes': [...], 'edges': [...], 'stats': {...}}
|
||||
```
|
||||
|
||||
Componentes (mira `python/functions/{core,datascience,pipelines}/`):
|
||||
- **core (puras):** `clean_pdf_text`, `chunk_with_overlap`, `merge_entity_aliases`, `filter_relations_by_entity_types`, `aggregate_extraction_results`.
|
||||
- **datascience (impuras):** `gliner2_load_model`, `extract_graph_gliner2`, `spacy_es_load_model`, `extract_triples_spacy_es` (OpenIE schema-less ES).
|
||||
- **pipelines:** `extract_graph_from_text` — composicion E2E.
|
||||
|
||||
Recetas validadas en notebooks 04-08 del analysis y vaultadas en `vaults/osint_nlp_models/`:
|
||||
- `threshold=0.3` (vs default 0.5) para GLiNER2.
|
||||
- snake_case verbal labels (`works_at`, `ceo_of`...).
|
||||
- `chunk_with_overlap` para texto > 1500 chars.
|
||||
- `filter_relations_by_entity_types` para descartar `Madrid president_of Persona`.
|
||||
- `merge_entity_aliases` para fusionar `BBVA` ⊂ `Banco Bilbao Vizcaya Argentaria, S.A.`.
|
||||
- spaCy ES dep-rules como capa OpenIE schema-less complementaria (predicado = verbo del texto).
|
||||
|
||||
Issues que desbloquea: `issues/0041-split-confidence-thresholds.md` y `issues/0042-gliner2-unified-extractor.md`. El registry tiene todas las funciones necesarias; solo falta cablearlas en `extract_graph_hybrid_py_pipelines` y el panel `paste_extract`.
|
||||
|
||||
Playground de referencia: `projects/osint_graph/analysis/gliner_glirel_tuning/playground/` (FastAPI + Sigma.js, sirviendo en `localhost:7878`).
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 0041
|
||||
title: Split confidence_threshold en entity_threshold + relation_threshold
|
||||
status: pending
|
||||
priority: medium
|
||||
created: 2026-05-04
|
||||
parent: 0013
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
`extract_graph_hybrid` y el panel `paste_extract` actualmente comparten un solo `confidence_threshold`. El analisis empirico (`projects/osint_graph/analysis/gliner_glirel_tuning/`) demuestra que las dos capas tienen distribuciones de score radicalmente distintas:
|
||||
|
||||
- **GLiNER** emite scores 0.92-0.99 en narrativa, 0.5-0.95 en OSINT. Threshold sano: **0.50-0.70**.
|
||||
- **GLiREL** emite scores 0.05-0.34. Threshold sano: **0.10-0.20**.
|
||||
- **mREBEL** (issue 0042) emite confianzas implicitas via `num_beams` — distinto modelo, distinto rango.
|
||||
|
||||
Un solo threshold global o filtra todas las relaciones (`t=0.6` mata GLiREL) o satura el grafo de entidades dudosas (`t=0.15` deja pasar entidades borderline).
|
||||
|
||||
## Cambios
|
||||
|
||||
### En `python/functions/pipelines/extract_graph_hybrid.py`
|
||||
|
||||
Aceptar dos thresholds opcionales con back-compat:
|
||||
|
||||
```python
|
||||
def extract_graph_hybrid(
|
||||
chunks, entity_schema, relation_types,
|
||||
gliner_model, glirel_model,
|
||||
llm_chat_json=None,
|
||||
confidence_threshold=0.6, # legacy, valor por defecto si los dos siguientes no se pasan
|
||||
entity_threshold=None, # nuevo — gobierna GLiNER + LLM-fallback de entidades
|
||||
relation_threshold=None, # nuevo — gobierna GLiREL + LLM-fallback de relaciones
|
||||
):
|
||||
if entity_threshold is None:
|
||||
entity_threshold = confidence_threshold
|
||||
if relation_threshold is None:
|
||||
relation_threshold = confidence_threshold
|
||||
...
|
||||
```
|
||||
|
||||
Pasar cada threshold a su capa respectiva (GLiNER `predict_entities(threshold=entity_threshold)`, GLiREL `predict_relations(threshold=relation_threshold)`).
|
||||
|
||||
### En `enrichers/paste_extract/run.py`
|
||||
|
||||
Aceptar `entity_threshold` y `relation_threshold` en `params`. Mantener `confidence_threshold` por compatibilidad. Defaults sugeridos en el manifest:
|
||||
|
||||
```yaml
|
||||
params:
|
||||
text: { type: string, required: true }
|
||||
use_hybrid: { type: bool, default: true }
|
||||
entity_threshold: { type: float, default: 0.50 }
|
||||
relation_threshold: { type: float, default: 0.15 }
|
||||
```
|
||||
|
||||
### En `extract_panel.cpp` / `extract_panel.h`
|
||||
|
||||
Reemplazar el slider unico por **dos sliders verticales** (mas el toggle `use_hybrid`):
|
||||
|
||||
```
|
||||
┌─ Extract config ─────────────────────────────┐
|
||||
│ ☑ Use hybrid (GLiNER + GLiREL) │
|
||||
│ │
|
||||
│ Entity threshold 0.50 [==== ] │
|
||||
│ Relation threshold 0.15 [== ] │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Defaults: `entity_threshold=0.50`, `relation_threshold=0.15`.
|
||||
|
||||
### Tests
|
||||
|
||||
`tests/test_paste_extract.py`:
|
||||
|
||||
- Test que pasar solo `confidence_threshold` mantiene comportamiento legacy.
|
||||
- Test que pasar `entity_threshold=0.5, relation_threshold=0.1` aplica thresholds distintos a cada capa.
|
||||
- Test que la UI envia los dos parametros correctamente al subprocess.
|
||||
|
||||
## Definicion de hecho
|
||||
|
||||
- Pego un texto y veo entidades con confianza 0.5+ Y relaciones con confianza 0.15+ por defecto.
|
||||
- Mover el slider de relaciones a 0.05 me muestra mas relaciones (potencialmente espurias) sin afectar las entidades.
|
||||
- Mover el slider de entidades a 0.9 reduce las entidades sin tocar las relaciones.
|
||||
- El test legacy (`confidence_threshold` solo) sigue pasando.
|
||||
|
||||
## Datos que respaldan los defaults
|
||||
|
||||
Notebook `01_gliner_glirel_tuning.ipynb`, tabla "GLiNER thresholds" (corpus es_corporate, en_corporate, es_journalism). Notebook `02_e2e_spanish_graph.ipynb`, comparacion recall (t=0.15) vs precision (t=0.30) — ambos modos producen grafos validos por separado, no hay un punto medio compartido bueno.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Optimizacion automatica de thresholds (issue futuro).
|
||||
- Threshold por tipo de entidad (`person` mas estricto que `location`) — issue futuro.
|
||||
- Calibracion automatica con feedback del usuario (al hacer Apply, ajustar thresholds aprendiendo).
|
||||
|
||||
## Reversibilidad
|
||||
|
||||
Si los nuevos defaults producen mas ruido del esperado, basta con cambiarlos en el manifest. El esquema de dos thresholds es estrictamente mas expresivo que el legacy.
|
||||
|
||||
## Relacionado
|
||||
|
||||
- Issue 0042 — sustituir GLiREL por mREBEL. Si 0042 va primero, este issue cambia: `relation_threshold` se vuelve menos relevante (mREBEL no usa threshold continuo del mismo modo) pero `entity_threshold` sigue siendo necesario.
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
id: 0042
|
||||
title: GLiNER2 — extractor unificado NER+RE (Apache 2.0) sustituye GLiREL en extract_graph_hybrid
|
||||
status: pending
|
||||
priority: high
|
||||
created: 2026-05-04
|
||||
parent: 0013
|
||||
depends_on: [0041]
|
||||
supersedes: 0042-mrebel-relation-extractor (archivado, ver decisions/2026-05-04-gliner2-over-mrebel.md)
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
El issue 0042 inicial (sustituir GLiREL por mREBEL) queda superado por el descubrimiento de **GLiNER2** (`fastino/gliner2-large-v1`):
|
||||
|
||||
- **Apache 2.0** — sin restriccion comercial.
|
||||
- **NER + RE en un solo modelo** — un solo forward pass.
|
||||
- **20-30× mas rapido** que mREBEL en CPU.
|
||||
- **Funciona en OSINT castellano** — captura IPs, dominios defanged, CVEs, malware.
|
||||
|
||||
Datos en `vaults/osint_nlp_models/models/gliner2.md`, decision en `vaults/osint_nlp_models/decisions/2026-05-04-gliner2-over-mrebel.md`, validacion empirica en `analysis/gliner_glirel_tuning/notebooks/04_gliner2_winner.ipynb`.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Anadir GLiNER2 como motor principal de NER+RE en el pipeline `extract_graph_hybrid`. GLiREL queda como engine secundario en el panel `paste_extract`. mREBEL queda disponible via las funciones del registry pero no por defecto.
|
||||
|
||||
## Diseño
|
||||
|
||||
### 1. Funciones nuevas en el registry
|
||||
|
||||
```
|
||||
python/functions/datascience/
|
||||
├── gliner2_load_model.py # carga + cache GLiNER2 (Apache 2.0)
|
||||
└── extract_graph_gliner2.py # ejecuta schema entity+relation, normaliza a EntityCandidate/RelationCandidate
|
||||
```
|
||||
|
||||
Plantilla `gliner2_load_model.py`:
|
||||
|
||||
```python
|
||||
"""Carga (y cachea) un modelo GLiNER2 que hace NER+RE conjunto.
|
||||
|
||||
LICENCIA: Apache 2.0 — uso libre incluido comercial.
|
||||
"""
|
||||
from typing import Any
|
||||
_MODEL_CACHE: dict[tuple[str, str], Any] = {}
|
||||
|
||||
def gliner2_load_model(
|
||||
model_name: str = "fastino/gliner2-large-v1",
|
||||
device: str = "auto",
|
||||
) -> Any:
|
||||
"""Returns GLiNER2 instance ready for .extract() with schemas."""
|
||||
resolved = _resolve_device(device)
|
||||
key = (model_name, resolved)
|
||||
if key in _MODEL_CACHE: return _MODEL_CACHE[key]
|
||||
from gliner2 import GLiNER2
|
||||
m = GLiNER2.from_pretrained(model_name)
|
||||
if hasattr(m, "to"): m.to(resolved)
|
||||
_MODEL_CACHE[key] = m
|
||||
return m
|
||||
```
|
||||
|
||||
Plantilla `extract_graph_gliner2.py`:
|
||||
|
||||
```python
|
||||
def extract_graph_gliner2(
|
||||
text: str,
|
||||
entity_labels: list[str],
|
||||
relation_labels: list[str],
|
||||
model,
|
||||
chunk_by_sentences: bool = False,
|
||||
) -> tuple[list, list]:
|
||||
"""Returns (entities, relations) as list[EntityCandidate], list[RelationCandidate].
|
||||
|
||||
If chunk_by_sentences=True, splits text by sentence and aggregates.
|
||||
Recommended for texts > 20 sentences (recall improves).
|
||||
"""
|
||||
```
|
||||
|
||||
### 2. Cambio en `extract_graph_hybrid.py`
|
||||
|
||||
Anadir parametro `relation_engine` con cuatro valores:
|
||||
|
||||
```python
|
||||
def extract_graph_hybrid(
|
||||
chunks, entity_schema, relation_types,
|
||||
relation_engine: str = "gliner2", # 'gliner2' | 'glirel' | 'mrebel' | 'none'
|
||||
gliner_model=None, # capa 1 NER (legacy stack)
|
||||
glirel_model=None, # capa 4 RE (legacy)
|
||||
mrebel_models=None, # tuple (tok, mdl) si engine=mrebel
|
||||
gliner2_model=None, # NUEVO — sustituye gliner+glirel
|
||||
llm_chat_json=None,
|
||||
entity_threshold=0.5,
|
||||
relation_threshold=0.15,
|
||||
chunk_by_sentences=False,
|
||||
):
|
||||
if relation_engine == "gliner2":
|
||||
ents, rels = extract_graph_gliner2(text=..., entity_labels=..., relation_labels=...,
|
||||
model=gliner2_model, chunk_by_sentences=chunk_by_sentences)
|
||||
# NO se llama a GLiNER ni GLiREL, GLiNER2 hace ambos.
|
||||
elif relation_engine == "glirel":
|
||||
# legacy: GLiNER + GLiREL como antes
|
||||
...
|
||||
elif relation_engine == "mrebel":
|
||||
# GLiNER + mREBEL via las funciones del registry (extract_relations_mrebel)
|
||||
...
|
||||
elif relation_engine == "none":
|
||||
rels = []
|
||||
```
|
||||
|
||||
### 3. `enrichers/paste_extract/run.py`
|
||||
|
||||
Manifest:
|
||||
|
||||
```yaml
|
||||
params:
|
||||
text: { type: string, required: true }
|
||||
use_hybrid: { type: bool, default: true }
|
||||
relation_engine: { type: enum, values: [gliner2, glirel, mrebel, none], default: gliner2 }
|
||||
entity_threshold: { type: float, default: 0.5 }
|
||||
relation_threshold: { type: float, default: 0.15 }
|
||||
chunk_by_sentences: { type: bool, default: false }
|
||||
```
|
||||
|
||||
### 4. Panel C++ (`extract_panel.cpp`)
|
||||
|
||||
- Combo: `Engine: [GLiNER2 (recomendado) | GLiNER+GLiREL (rapido EN) | GLiNER+mREBEL (no comercial) | None]`
|
||||
- Banner amarillo solo si `mREBEL` seleccionado (recordatorio de licencia CC BY-NC-SA).
|
||||
- Checkbox `chunk_by_sentences` (recomendado para texto largo).
|
||||
- Sliders del issue 0041 (entity_threshold, relation_threshold).
|
||||
|
||||
### 5. `app.md`
|
||||
|
||||
```yaml
|
||||
notes: |
|
||||
Motor de extraccion por defecto: GLiNER2 (Apache 2.0, NER+RE conjunto, ES/EN/FR).
|
||||
Engines alternativos: GLiNER+GLiREL (rapido EN), GLiNER+mREBEL (no comercial,
|
||||
18 idiomas). Ver vaults/osint_nlp_models/ para benchmarks.
|
||||
|
||||
dependencies:
|
||||
- "gliner2 >= 1.3.0 (Apache 2.0)"
|
||||
optional_dependencies:
|
||||
- "gliner + glirel (Apache 2.0, legacy)"
|
||||
- "transformers + sentencepiece (mREBEL/REBEL — non-commercial mREBEL)"
|
||||
```
|
||||
|
||||
### 6. Tests pytest
|
||||
|
||||
`tests/test_extract_graph_gliner2.py`:
|
||||
|
||||
- Test smoke con stub de modelo (mock que retorna shape oficial).
|
||||
- Test que normaliza output a `EntityCandidate` / `RelationCandidate` correctamente.
|
||||
- Test que `chunk_by_sentences=True` agrega correctamente.
|
||||
|
||||
`tests/test_paste_extract.py`: anadir test del path `relation_engine=gliner2` (default).
|
||||
|
||||
## Definicion de hecho
|
||||
|
||||
- Pego el corpus es_corporate_short, dejo engine=GLiNER2, pulso Extract.
|
||||
- Tras ~1.5s veo 14 entidades y 8 relaciones, mayoria correctas.
|
||||
- Cambio engine a GLiREL → resultado pobre conocido (validacion contraria).
|
||||
- Cambio engine a mREBEL → veo banner de licencia + 5 relaciones lentas pero limpias.
|
||||
- Pego corpus es_osint con label set OSINT → veo IPs, CVEs, malware, hashes correctamente.
|
||||
- Apply solo guarda las marcadas; dedupe sigue funcionando.
|
||||
- Tests verdes en WSL.
|
||||
|
||||
## Latencia / UX
|
||||
|
||||
- GLiNER2: ~1-5s para texto medio (4-30 frases). UX **fluida**, no necesita progress bar.
|
||||
- GLiNER2 chunked: ~150ms/frase. Para 30 frases = 4.5s, sigue fluido.
|
||||
- mREBEL queda con barra de progreso obligatoria (issue separado dentro de este).
|
||||
|
||||
## Out of scope (issues separados)
|
||||
|
||||
- Threshold tuning interno de GLiNER2 (si la API lo expone).
|
||||
- Mapeo de tipos de entidad GLiNER2 a vocabulario controlado custom (ej. `persona` → `Person`).
|
||||
- Extension a Frances (declarado pero sin probar).
|
||||
- Comparativa con `gliner2-base-v1` (mas pequeño) — issue de optimizacion.
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Recall bajo en texto largo** sin chunking. Mitigacion: `chunk_by_sentences=True` por defecto si texto > 20 frases.
|
||||
- **Errores semanticos puntuales** (e.g. `Inditex acquired Pablo Isla`). El panel los muestra con checkbox — el humano filtra antes de Apply.
|
||||
- **Spans sucios ocasionales** en OSINT. Mitigacion: aplicar `align_relations_to_entities_py_datascience` (funcion ya en el registry) post-extract para descartar relaciones cuyas puntas no son entidades reales.
|
||||
|
||||
## Relacionado
|
||||
|
||||
- `vaults/osint_nlp_models/models/gliner2.md`
|
||||
- `vaults/osint_nlp_models/decisions/2026-05-04-gliner2-over-mrebel.md`
|
||||
- `analysis/gliner_glirel_tuning/notebooks/04_gliner2_winner.ipynb`
|
||||
- Issue 0041 (split confidence_threshold) — sigue valido.
|
||||
- Issue 0042-mrebel-relation-extractor.md.superseded (archivado).
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
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/`
|
||||
Reference in New Issue
Block a user