Tecnología. Experimentos. Mis cosas

,

A/B Testing de prompts

Uno de los mayores problemas actuales en el desarrollo con Inteligencia Artificial es el «Vibes-based development» (desarrollo basado en vibras). Cambiamos una frase del system prompt, lanzamos un par de pruebas a mano, y si el texto nos suena mejor, lo subimos a producción.

Para un chatbot de atención al cliente, esto puede ser aceptable. Pero en GalopApp, donde el LLM se usa para predecir qué caballo va a ganar una carrera, no me sirve que el texto suene bonito. Necesito saber matemáticamente si la variante v3_reasoning acierta más ganadores que la v2_concise, o si el cambio a un nuevo modelo base justifica su coste en tokens.

Para solucionar esto, construí un sistema de Trazabilidad y A/B Testing integrado directamente en el backend de FastAPI.

La anatomía del prompt_tracking

El corazón del sistema es la tabla prompt_tracking. La regla de oro es: ninguna llamada al LLM se hace sin registrar su huella completa.

Cada vez que el servicio de predicción llama a Gemini, inserta un registro como este (definido en SQLAlchemy):

pythonclass PromptTracking(Base):    __tablename__ = "prompt_tracking"    id = Column(Integer, primary_key=True)    race_id = Column(Integer, ForeignKey("Races.race_id"))        # Metadatos del LLM    variant_name = Column(String(50))   # ej: "v3_reasoning"    model_name = Column(String(50))     # ej: "gemini-2.0-flash"        # Prompts completos íntegros    system_prompt = Column(Text)    user_prompt = Column(Text)    prediction_result = Column(Text)        # Rendimiento de API    tokens_used = Column(Integer)    latency_ms = Column(Integer)    # ... campos de evaluación (se rellenan a posteriori) ...

Guardar el prompt completo es costoso en almacenamiento, pero es un seguro de vida. Si dentro de un mes descubro que el modelo falló estrepitosamente en una carrera, puedo leer exactamente qué información (pesos, historial, meteorología) se le inyectó ese día concreto.

Cerrando el bucle: El script de Backfill

Generar el pronóstico es solo la mitad del trabajo. La magia ocurre días después, cuando la carrera real ya se ha corrido y tenemos los resultados oficiales.

Un script en background (backfill_prediction_results.py) recorre todos los registros de prompt_tracking de carreras pasadas. Extrae a los caballos que el LLM propuso como 1º, 2º y 3º, y los cruza con la clasificación real.

El sistema no solo evalúa si acertó el ganador, sino que rellena métricas puras de apuestas:

python# Módulo de actualización de resultados (simplificado)# ¿Acertó el ganador?tracking.was_correct = (pred_1 == act_1)# ¿El caballo que dio como ganador quedó al menos entre los 3 primeros?top3_actual = {act_1, act_2, act_3}tracking.was_colocado = (pred_1 in top3_actual)# Gemela exacta (1º y 2º en orden)tracking.was_gemelas = ({pred_1, pred_2} == {act_1, act_2})# Trío desordenado (acertó los 3 del podio, sin importar orden)tracking.was_trio_desord = ({pred_1, pred_2, pred_3} == {act_1, act_2, act_3})

El cuadro de mando: Comparando variantes

Con estos datos en MySQL, comparar prompts deja de ser un arte y pasa a ser pura ciencia de datos.

El servicio puede agregar los datos y darnos un win rate exacto agrupado por variante de prompt:

pythondef get_summary_stats(self, days: int = 30) -> Dict[str, Any]:    query = (        select(            PromptTracking.variant_name,            func.count(PromptTracking.id).label('total'),            func.sum(case((PromptTracking.was_correct == True, 1), else_=0)).label('correct'),            func.avg(PromptTracking.tokens_used).label('avg_tokens'),            func.avg(PromptTracking.latency_ms).label('avg_latency'),        )        # ... joins y agrupaciones por variant_name ...    )        # ... cálculo de porcentajes ...    # Devuelve:    # {    #   "variant": "v3_reasoning",    #   "win_rate": 28.5,    #   "avg_tokens": 1450,    #   "avg_latency_ms": 3200    # }

Esto me permite responder preguntas cruciales:

  1. ¿El prompt hiper-detallado (v3_reasoning) de verdad acierta más ganadores que el prompt simple (v2_concise)?
  2. Si el prompt v3 acierta lo mismo pero consume el doble de tokens, ¿compensa el sobrecoste en producción?
  3. Si migro de gemini-1.5-pro a gemini-2.0-flash, ¿cae el win rate? La latencia seguro que baja, pero con este sistema puedo medir el impacto exacto en el ROI (Retorno de Inversión) de mis apuestas simuladas.

Conclusión

No puedes mejorar lo que no puedes medir. En la era de la Inteligencia Artificial, donde los modelos son cajas negras no deterministas, la única forma de garantizar la calidad de un software en producción es medir empíricamente sus resultados frente al mundo real.

Gracias a la tabla prompt_tracking, puedo jugar con la ingeniería de prompts o cambiar de proveedor de LLM con total confianza, sabiendo que los números dictarán sentencia.

(En el último artículo de esta serie técnica, abandonaremos la IA generativa para meternos en el lado duro de las apuestas: cómo implementé un motor de Reinforcement Learning en Python sin dependencias pesadas).