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