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