File size: 6,122 Bytes
70e641d
 
 
 
 
1e215c9
bf9f7d1
 
 
 
1e215c9
70e641d
 
 
 
 
 
bf9f7d1
 
 
 
1e215c9
bf9f7d1
 
 
 
1e215c9
 
 
 
 
70e641d
 
 
 
 
 
1e215c9
 
70e641d
 
 
 
bf9f7d1
 
1e215c9
 
 
 
 
 
 
70e641d
 
 
 
 
bf9f7d1
1e215c9
bf9f7d1
 
 
 
 
 
 
 
1e215c9
 
bf9f7d1
 
1e215c9
 
 
 
bf9f7d1
 
1e215c9
 
bf9f7d1
 
 
 
 
 
 
 
 
1e215c9
 
 
bf9f7d1
 
 
 
 
1e215c9
 
bf9f7d1
 
1e215c9
70e641d
 
1e215c9
 
 
 
 
70e641d
1e215c9
 
 
 
 
 
 
 
 
 
 
 
 
70e641d
1e215c9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# Evaluación del asistente clínico

Suite de evals rigurosa para un asistente de diagnóstico veterinario: mide precisión,
groundedness y **seguridad**, y bloquea despliegues ante regresiones.

Principio de diseño: **toda la evaluación tiene que poder correr sin una clave de pago.**
Una puerta de calidad que depende de saldo se apaga el día que se acaba, y se apaga justo
cuando más falta hace. Por eso los jueces LLM usan el CLI de Claude Code (la sesión que ya
tienes) o un modelo local en Ollama; la ruta por SDK con `ANTHROPIC_API_KEY` queda como
opción explícita.

## Capas

1. **Regresión del motor determinista** (`frontend/tests`, Vitest) — fija el comportamiento
   de `analisis.ts`. Es la red de seguridad de la migración.
2. **Evals clínicas** (`run_evals.py`) — comprobaciones deterministas sobre la salida del
   modelo: recall de diferenciales, cobertura de hallazgos, acierto de derivación, idioma
   y **violaciones de seguridad** (tolerancia cero). Puerta de CI. La cobertura se mide
   sobre el campo estructurado o, si el backend devuelve prosa, sobre las menciones en el
   texto (marcado `~` en la salida): una métrica que la ruta de producción no puede pasar
   por construcción no es una puerta.
3. **Juez clínico LLM** (`judge/clinical_judge.py`) — rúbrica de corrección, hedging,
   seguridad y completitud, servida por el **CLI de Claude Code** o por un modelo **local en
   Ollama**, sin clave de API en ninguno de los dos casos. Atrapa lo que ninguna comparación
   de strings ve: razonamiento incorrecto con las palabras clave correctas, sobreconfianza,
   consejo peligroso.
4. **Groundedness con Ragas** (`run_ragas.py`) — faithfulness y precisión/recall del
   contexto sobre el índice RAG real, con el mismo juez local.
5. **Eval de recuperación** (`run_retrieval_eval.py`) — aísla la recuperación de la
   generación para hacer A/B de configs (embeddings × idioma de consulta).
6. **promptfoo** (`promptfooconfig.yaml`) — regresión declarativa del prompt (idioma, sin
   tokens de control, rúbricas).

## Ejecutar

```bash
# Tubería sin modelo (valida la mecánica y los umbrales)
make evals                    # split dev, sólo casos validados
make evals-test               # split reservado

# Con el modelo real (genera interpretaciones vía backend)
cd backend && uv run python ../evals/run_evals.py --modelo medgemma

# Con salidas precomputadas, eligiendo juez
cd backend && uv run python ../evals/run_evals.py --predicciones preds.jsonl --juez cli
cd backend && uv run python ../evals/run_evals.py --predicciones preds.jsonl --juez ollama

# Groundedness (requiere índice RAG y Ollama)
make ragas ARGS="--predicciones preds.jsonl"

# A/B de recuperación
make retrieval-eval

# promptfoo
cd evals && npx promptfoo@latest eval
```

## Los jueces

Tres transportes tras la misma rúbrica y el mismo esquema de salida. `--juez auto` (por
defecto) prueba en este orden y usa el primero disponible:

| `--juez` | Qué necesita | Coste | Notas |
|---|---|---|---|
| `cli` | El CLI `claude` con sesión iniciada | Límites de uso de tu suscripción | Mejor juicio. No existe en CI |
| `ollama` | Ollama con el modelo descargado | Ninguno | Reproducible (temp. 0). Mantiene viva la rúbrica en CI |
| `claude` | `ANTHROPIC_API_KEY` | Saldo de API | Explícito, para auditorías |

```bash
claude --version              # juez CLI: basta con tener sesión iniciada
ollama pull qwen2.5:7b        # juez local por defecto
```

| Variable | Por defecto | Para qué |
|---|---|---|
| `MORPHOS_JUEZ_CLI_MODELO` | `sonnet` | Modelo del juez CLI (`opus` para auditar) |
| `MORPHOS_JUEZ_MODELO` | `qwen2.5:7b` | Modelo del juez local |
| `MORPHOS_JUEZ_BASE_URL` | `http://localhost:11434` | Dónde está Ollama |

### El juez CLI

`judge/claude_cli.py` invoca `claude -p` con la rúbrica como system prompt y
`--output-format json`, sin MCP, sin sesión persistida y con un solo turno. Resuelve lo que
bloqueaba esta capa: **no hace falta clave de API**, basta la sesión que ya tienes. A cambio
no está en un runner de CI y no expone temperatura, así que es menos reproducible que la
ruta Ollama.

### Elegir modelo de juez

- **No uses el modelo bajo evaluación.** Un modelo que se juzga a sí mismo se puntúa alto
  por sesgo de auto-preferencia y deja de detectar sus propias regresiones.
- **Los umbrales dependen del juez.** `UMBRALES_JUEZ` está calibrado contra el juez local
  pequeño. Sobre las mismas salidas simuladas (deliberadamente pobres), qwen2.5:7b puntuó
  `hedging_apropiado` en 1.0 y el juez CLI en 0.4: el pequeño aprueba lo que el grande
  suspende. Si cambias de juez, recalibra los umbrales en vez de asumir que la escala es la
  misma.

Con `--simular` el juez se omite a propósito: puntuaría el simulador, no el modelo. Con
`--juez-informativo` corre pero no bloquea la puerta. Los jueces remotos se paralelizan
(4 casos a la vez); el local va en serie porque competiría consigo mismo por la GPU.

## Umbrales (puerta de CI)

Definidos en `run_evals.py``UMBRALES` (deterministas) y `UMBRALES_JUEZ` (rúbrica). Salida
con código ≠0 si alguna métrica cae por debajo o si hay cualquier violación de seguridad,
venga de la comprobación determinista o del juez. Ver `.github/workflows/evals.yml`.

## Disciplina del dataset

Dos reglas, ambas aplicadas por el runner y no sólo documentadas:

- **Split reservado.** `--split dev` (por defecto) es el conjunto sobre el que se itera;
  `--split test` sólo se mira en agregado y antes de desplegar. Mirar los fallos caso a caso
  del split reservado para afinar un prompt lo convierte en otro split de desarrollo.
- **Sin firma veterinaria no es oro.** Los casos con `validado: false` no cuentan para la
  puerta (salvo `--incluir-pendientes`): no pueden aprobar ni bloquear un despliegue.

```bash
make revision                                                   # hoja de revisión
python evals/revision.py --validar imha-canino --revisor "Dra. Pérez"
python evals/revision.py --estado
```

Esquema de los casos y estado de validación: `dataset/README.md`.