====== 21. Evaluación de skills ====== Una //skill// se prueba como el código: primero que **está bien hecha** (los checks) y después que **funciona** (los //evals//), repitiendo cada prueba varias veces porque el modelo no responde siempre igual. Cómo se construye la //skill// está en [[cursos:sdd:19-creando-skills|Estructura de un skill]]. Más información: * [[https://github.com/anthropics/skills/tree/main/skills/skill-creator|skill-creator]]: la //skill// de Anthropic para crear y evaluar //skills//. * [[https://www.anthropic.com/research/statistical-approach-to-model-evals|A statistical approach to model evaluations]]: por qué un resultado de //eval// sin su intervalo de confianza no dice nada. * [[https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval|Wilson score interval]]: el intervalo que se usa aquí para la tasa de aprobado. ---- ===== 1. Los checks: la forma ===== Antes de probar si una //skill// funciona, se comprueba que **está bien hecha**: una lista de comprobaciones que tiene que pasar entera antes de darla por terminada. Casi todo lo del [[cursos:sdd:19-creando-skills|tema 20]] se puede comprobar con un script, sin gastar tokens: * La carpeta y el H1 se llaman igual que el ''name'', y hay un solo ''SKILL.md''. * La ''description'' existe y no pasa de 1.024 caracteres. * La tabla índice está antes de cualquier otra sección y no hay huérfanos ni rutas a ficheros que no existen. * Nombres en //kebab-case//, sin repetir la carpeta y sin carpetas fuera de la tabla. * Todo ''.md'' de más de 100 líneas tiene índice; ninguno pasa de 2.500 palabras. * Las de procedimiento tienen ''User Input'', ''Outline'', ''STOP conditions'' y ''Quick Guidelines'' con 10 puntos como máximo. Lo demás (que la ''description'' diga el cuándo, que no haya teoría que el modelo ya sabe, que cada ❌ lleve su porqué) lo revisa un modelo o una persona. Esa lista y su script se guardan en una //skill// de conocimiento, ''k-skill'', con todas las reglas del tema 20. Así el agente las aplica al crear o revisar cualquier //skill// del proyecto. ===== 2. evals/: el comportamiento ===== Una batería de casos que se repite **al editar la //skill// y al cambiar de modelo**. Cada caso responde a dos preguntas: * **¿Se activa?** Que se cargue solo dice que el modelo la encontró, no que la siguiera. * **¿Aporta?** El caso se ejecuta **con la //skill// y sin ella**, y la diferencia de nota es lo que aporta. Si sin ella también aprueba, sobra para ese caso. .claude/skills/k-servicios/evals/ ├── update-sin-filtrar/ <- un caso, con el nombre de lo que prueba │ ├── prompt.md <- lo que escribiría el usuario │ ├── case.yaml <- opcional: dice qué script prepara los ficheros │ ├── fixture.sh <- opcional: crea los ficheros de partida │ └── graders/ │ ├── filtra-campos.md <- un evaluador por fichero │ └── skill-activada.md └── results/ <- las notas de cada evaluación; va en .gitignore ==== El caso ==== * **''prompt.md''**: lo que pediría un usuario, **sin nombrar la //skill//**: si la nombra, se prueba que obedece, no que se activa. En el frontmatter, cuántas vueltas puede dar el agente (''max_turns'') y cuántas veces se ejecuta el caso (''runs''). --- max_turns: 30 runs: 10 --- Añade a PedidoServiceImpl un método update que permita cambiar la dirección y el teléfono del pedido. * **''fixture.sh''**: deja los ficheros mínimos de partida, aquí un ''PedidoServiceImpl.java'' sin ''update''. Lo enlaza ''case.yaml'': schema_version: "1.1" name: update-sin-filtrar context: scaffold_script: fixture.sh ==== Los evaluadores ==== En ''graders/'', un evaluador por fichero, con su ''type'' en el frontmatter. Sobre el resultado, con un modelo que hace de juez: --- type: llm focus: { source: file, path: PedidoServiceImpl.java } --- PASS si el update solo deja cambiar la dirección y el teléfono. FAIL si copia sobre la entidad todo lo que recibe, o si el cliente puede cambiar el estado o el importe. Y sobre el proceso, con un script: --- type: tool_used tool: Skill input_match: '"skill"\s*:\s*"k-servicios"' --- Los tipos de evaluador: ^ Tipo ^ Aprueba cuando ^ Lo aplica ^ | ''regex'' | Un patrón aparece (o no) en la respuesta o en un fichero | Un script | | ''tool_used'' | Se llamó a una herramienta, con ''min'' y ''max'' veces | Un script | | ''tool_order'' | Una herramienta se llamó antes que otra | Un script | | ''file_exists'' | Se creó un fichero que cumple el patrón | Un script | | ''llm'' | Un juez vota PASS en dos de tres votos | Un modelo juez | Cómo se hacen bien: * **Al menos 3 casos**, sacados de **fallos reales** del modelo sin la //skill//, y **uno negativo**: un prompt parecido en el que la //skill// no debe activarse. * **Al menos 2 evaluadores por caso**: uno sobre el resultado y otro sobre el proceso. * **Deterministas primero**: ''regex'' sobre ficheros largos; ''llm'' solo para salidas cortas y con un PASS y un FAIL concretos. * **El juez, de otro modelo** que el evaluado: un modelo tiende a aprobar lo suyo. **''skill-activada'' no cuenta en la nota.** Sin la //skill// siempre suspende, así que inflaría la diferencia. Claude Code lo sabe: un ''tool_used'' sobre ''Skill'' solo dice si se activó. **El modelo evaluado no puede leer ''evals/''.** Si ve los criterios, aprueba por imitación. Ni el prompt ni ''fixture.sh'' apuntan a nada de ''evals/''. ==== Ejecutarlos ==== En Claude Code, con ''claude plugin eval'' y la carpeta de la //skill//. Por defecto ejecuta cada caso con la //skill// y sin ella, y da la diferencia: claude plugin eval .claude/skills/k-servicios --scaffold --runs 10 --judge-model haiku * ''--scaffold'': ejecuta el ''fixture.sh'' de cada caso. Sin él, el caso empieza sin ficheros. * ''--runs 10'': veces que se ejecuta cada caso (por defecto, 3; ver la sección siguiente). * ''--judge-model haiku'': el modelo de los evaluadores ''llm''. * ''--case update-sin-filtrar'': solo ese caso. Al terminar saca una tabla por caso con su nota y su ''PASS%'', y deja el detalle en ''evals/results//''. ---- ===== 3. Repetir la prueba: intervalos de confianza ===== El modelo no responde siempre igual, así que cada caso se ejecuta $n$ veces y lo que se mide es la **tasa de aprobado**: $k$ aprobados de $n$. Esa tasa es una estimación de la de verdad, y con pocas ejecuciones, muy imprecisa. Lo que dice cuánto te puedes fiar es su **intervalo de confianza al 95 %**: el rango en el que está la tasa real. **3 de 3 no es un 100 %.** Con 3 ejecuciones, todo aprobado solo dice que la tasa real está entre el 44 % y el 100 %: la //skill// podría fallar más de la mitad de las veces. Y 3 es lo que hace ''claude plugin eval'' por defecto. ==== El intervalo de Wilson ==== Con $\hat p = k/n$ y $z = 1{,}96$ para el 95 %: $$\frac{\hat p + \frac{z^2}{2n} \pm z\sqrt{\frac{\hat p(1-\hat p)}{n} + \frac{z^2}{4n^2}}}{1 + \frac{z^2}{n}}$$ No uses la fórmula de clase de estadística, $\hat p \pm z\sqrt{\hat p(1-\hat p)/n}$: con todo aprobado o todo suspendido da un intervalo de anchura cero, justo en los casos más frecuentes en un //eval//. Con este script se calcula a partir de lo que da ''claude plugin eval'': import sys from math import sqrt def wilson(k, n, z=1.96): p = k / n centro = (p + z*z/(2*n)) / (1 + z*z/n) margen = z * sqrt(p*(1-p)/n + z*z/(4*n*n)) / (1 + z*z/n) return centro - margen, centro + margen k, n = int(sys.argv[1]), int(sys.argv[2]) bajo, alto = wilson(k, n) print(f"{k}/{n}: {bajo:.0%} – {alto:.0%}") python3 intervalo.py 9 10 9/10: 60% – 98% Lo que da según el número de ejecuciones: ^ Resultado ^ Intervalo al 95 % ^ | 3/3 | 44 % – 100 % | | 5/5 | 57 % – 100 % | | 10/10 | 72 % – 100 % | | 20/20 | 84 % – 100 % | | 9/10 | 60 % – 98 % | | 5/10 | 24 % – 76 % | ==== ¿Aporta la skill? Que no se solapen ==== La //skill// aporta en un caso cuando el intervalo **con** ella y el intervalo **sin** ella **no se solapan**. Si se solapan, la diferencia puede ser suerte: ^ Ejecuciones ^ Con la //skill// ^ Sin la //skill// ^ ¿Aporta? ^ | 10 | 9/10 (60 % – 98 %) | 5/10 (24 % – 76 %) | No se sabe: se solapan | | 30 | 27/30 (74 % – 97 %) | 15/30 (33 % – 67 %) | Sí | Cuántas veces ejecutar: * **10 por caso** para empezar: ''--runs 10'' o ''runs: 10'' en el ''prompt.md''. * **30 en los casos que se solapan**, solo esos, con ''--case'': claude plugin eval .claude/skills/k-servicios --scaffold --runs 30 --case update-sin-filtrar Si con 30 ejecuciones los intervalos siguen solapándose, la //skill// no aporta en ese caso, o aporta tan poco que no compensa los tokens que añade. ---- ===== Ejercicios ===== ==== Ejercicio 1 ==== Crea la carpeta ''evals/'' de la //skill// ''do-crear-test'' del [[cursos:sdd:19-creando-skills#ejercicio_3|ejercicio 3 del tema 20]] con tres casos: dos que salgan de fallos que hayas visto al pedir tests sin la //skill// y uno negativo. Rellena la tabla: ^ Caso ^ Prompt ^ Evaluador del resultado ^ Evaluador del proceso ^ | | | | | | | | | | | | | | | ==== Ejercicio 2 ==== Calcula el intervalo de confianza al 95 % de cada resultado y di si la //skill// aporta en cada caso: ^ Caso ^ Con la //skill// ^ Intervalo ^ Sin la //skill// ^ Intervalo ^ ¿Aporta? ^ | ''test-con-id'' | 10/10 | | 6/10 | | | | ''test-sin-mock'' | 8/10 | | 7/10 | | | | ''test-negativo'' | 3/3 | | 3/3 | | | ==== Ejercicio 3 ==== Ejecuta los casos del ejercicio 1 con ''--runs 10''. Rellena la tabla y repite con ''--runs 30'' los casos en los que los intervalos se solapen: ^ Caso ^ Ejecuciones ^ Con la //skill// ^ Intervalo ^ Sin la //skill// ^ Intervalo ^ ¿Aporta? ^ | | | | | | | | | | | | | | | | | | | | | | | |