Creación de un arnés de evaluación LLM personalizado en Python desde cero

En este artículo, aprenderá cómo crear un arnés de evaluación LLM personalizado en Python desde cero, estructurado en torno a cuatro componentes modulares que le brindan control total sobre cómo se prueban y califican sus modelos.

Los temas que cubriremos incluyen:

Cómo construir un conjunto de datos valioso de casos de prueba basados ​​en entradas de usuarios reales y modos de falla conocidos. Cómo construir un corredor de inferencia asíncrono y una capa de jueces de dos modos utilizando puntuación determinista y basada en LLM. Cómo registrar resultados en un formato a prueba de fallos que desacopla la puntuación de la inferencia, para que pueda volver a puntuar sin volver a ejecutar costosas llamadas API.

Los puntos de referencia públicos como MMLU o GSM8K miden la capacidad general, pero no le dirán si su agente de atención al cliente sigue su política de rechazo o si su canal RAG obtiene el contexto correcto para su dominio específico. Para eso, necesitas algo que tengas.

Ahí es donde entra en juego un instrumento de evaluación personalizado: un proceso repetible creado en torno a sus casos de prueba y sus criterios de puntuación. En ingeniería de software, un arnés de prueba es el andamiaje que alimenta entradas a un sistema, captura salidas y las califica con respecto a una línea de base conocida. Aplicado a modelos de lenguaje grandes, hace lo mismo en un conjunto de indicaciones, rastreando tasas de aprobación, latencia y costos a lo largo del tiempo.

Esta guía explica cómo crear uno desde cero en Python. Cubriremos cuatro componentes modulares: un conjunto de datos dorado, un corredor de inferencia, una capa de juez y un registrador. Cada uno se mantiene deliberadamente al mínimo para que tengas una base sobre la cual construir, no una caja negra para copiar y pegar.

Requisitos previos

Necesitará Python 3.10 o posterior, principalmente para el soporte nativo de asyncio que le permite ejecutar evaluaciones simultáneamente en lugar de esperar cada llamada API de una en una. Más allá de eso, la lista de dependencia es corta a propósito.

Instale lo siguiente:

LiteLLM le brinda una interfaz unificada para llamar a OpenAI, Anthropic, Google y otros backends de modelos con la misma firma de función. Intercambiar el modelo bajo prueba significa cambiar una cadena, no reescribir su corredor. Pydantic aplica una salida estructurada desde su capa de jueces para que las puntuaciones regresen como campos escritos en lugar de texto no analizable. La biblioteca estándar se encarga del resto: asyncio para concurrencia y json para registro.

Construyendo el conjunto de datos dorado

Un conjunto de datos de oro es una lista estructurada de casos de prueba, cada uno de los cuales combina un mensaje de entrada con una descripción del resultado esperado. Todo lo demás en el arnés depende de esto.

Comience con entre 20 y 50 casos extraídos de registros de usuarios reales o modos de falla conocidos, no ejemplos que haya inventado en su escritorio. Los casos sintéticos tienden a agruparse en torno al camino feliz. Las entradas reales del usuario son más confusas, y ese desorden es exactamente lo que su arnés necesita captar.

Cada caso tiene tres campos: una identificación única para rastrear los resultados entre ejecuciones, la entrada que se envía al modelo y un concepto_esperado con el que el evaluador verifica. Mantenga los valores de concepto esperado breves e inequívocos. Las expectativas vagas conducen a puntuaciones poco fiables.

Este es un buen lugar para personalizar el conjunto de datos. Puede agregar campos para etiquetas de categoría, niveles de dificultad o metadatos sobre dónde se originó el caso de prueba. La estructura aquí es un punto de partida, no un techo.

Escribir el corredor de inferencia

El corredor itera a través de su conjunto de datos, envía cada entrada al modelo y devuelve el resultado sin procesar. Dado que utiliza LiteLLM, puede apuntar a GPT-4o, Claude 3.5 Sonnet o un modelo alojado localmente simplemente cambiando un único argumento.

num_retries=3 y timeout=30 son las configuraciones de confiabilidad mínimas viables para cualquier ejecución de evaluación de producción. Para ejecutar el conjunto de datos completo al mismo tiempo, llame a await asyncio.gather(*[run_case(c, model) for c in cases]), que activa todos los casos en paralelo y reduce considerablemente el tiempo de reloj de pared en grandes conjuntos de datos.

Tenga en cuenta que la función solo devuelve resultados sin procesar, sin ninguna lógica de puntuación adjunta. Esa separación es intencional y verás por qué en el siguiente paso.

Calificar el resultado con una capa de juez

El juez toma cada resultado del corredor y decide: pasa o falla. Dos enfoques funcionan bien aquí y la elección correcta depende de lo que estés probando.

Un juez determinista es rápido, barato y consistente. Úselo cuando esté buscando una cadena, formato o patrón específico.

Un LLM como juez es más lento y agrega costos de API, pero maneja casos en los que la corrección semántica importa más que la coincidencia exacta. Una respuesta que diga “esta operación eliminaría permanentemente sus datos” debe pasar una verificación de rechazo aunque no contenga la palabra “rechazo”.

Pydantic exige que el modelo de juez devuelva un booleano aprobado válido en lugar de un párrafo. Sin él, eventualmente obtendrá una respuesta como “Creo que pasó” y su analizador fallará.

Ambos jueces aquí son simples por diseño. En la práctica, probablemente querrás personalizar la rúbrica de calificación, agregar puntuación parcial o dirigir diferentes categorías de prueba a diferentes jueces. Ese es el tipo de adaptación que hace que un arnés de evaluación sea útil para su proyecto específico.

Registro de resultados en el disco

Escriba cada resultado en un archivo .jsonl inmediatamente después de calificar. JSON delimitado por líneas de solo agregar es el formato estándar para los registros de evaluación porque es seguro contra fallas: si el ejecutor se detiene a mitad del conjunto de datos, usted mantiene todos los resultados escritos hasta el momento.

He aquí por qué es importante mantener la inferencia y la puntuación por separado: registre el campo raw_output junto con la puntuación, y cuando refine la rúbrica de calificación de su juez más adelante, podrá volver a calificar esos mismos resultados sin procesar sin volver a ejecutar la inferencia. En un conjunto de datos de 500 casos frente a una API paga, eso ahorra tiempo y dinero real.

Solución de problemas

Puntuaciones inconsistentes entre ejecuciones: los jueces de LLM no son deterministas por defecto. Establezca la temperatura = 0 en las llamadas del modelo de juez para reducir la variación. Para jueces deterministas, verifique que sus cadenas de concepto_esperado no contengan subcadenas ambiguas que coincidan con resultados no deseados. Errores de límite de tasa: num_retries de LiteLLM maneja fallas transitorias, pero los límites de tasa sostenidos necesitan un límite de concurrencia. Envuelva asyncio.gather con asyncio.Semaphore(10) para limitar las solicitudes simultáneas. Los costos del modelo de juez se suman: ejecute la puntuación determinista primero y solo envíe las fallas al juez de LLM. Los casos más sencillos se resuelven sin una llamada a la API y usted ahorra la calificación más costosa para resultados genuinamente ambiguos.

Conclusión

Cuatro módulos, dos dependencias y aproximadamente 60 líneas de Python le brindan un instrumento de evaluación funcional. El conjunto de datos dorado define cómo es “bueno”. El corredor recopila resultados brutos sin sesgos. El juez los califica utilizando el método que se ajuste al caso de prueba. El registrador desacopla la inferencia de la puntuación para que pueda iterar en la calificación sin volver a ejecutar inferencias costosas.

A partir de aquí, hay mucho espacio para crecer. Agregue campos de latencia y costo de token a cada registro. Conecte el arnés a una canalización de CI/CD para detectar regresiones en cada actualización del modelo. Cree un panel de agregación sobre sus archivos .jsonl. Cambie por un juez más sofisticado que califique según una rúbrica en lugar de aprobar/reprobar. La arquitectura se mantiene a medida que los requisitos se vuelven más complejos, sin necesidad de reescribirla.