morphos / evals /README.md
Jose Salazar
Ajustes varios al engine y al prompt basado en resultados de los evals
bf9f7d1
|
Raw
History Blame Contribute Delete
6.12 kB
# 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`.