Los cinco ejemplos que nadie revisa desde marzo
Todo prompt de producción acaba con una sección de ejemplos. Alguien pegó cinco casos buenos el día del lanzamiento, el clasificador pasó de 71 % a 84 % de acierto y nadie volvió a abrir esa sección. Seis meses después, el producto tiene tipos de consulta que no existían entonces y los ejemplos siguen siendo los mismos cinco.
El problema no es que los ejemplos sean malos. Es que son estáticos: el modelo ve los mismos casos para una factura, para una queja y para una pregunta técnica. Un ejemplo ayuda cuando se parece a la entrada. Cuando no se parece, ocupa tokens y, en el peor caso, empuja al modelo hacia un patrón equivocado.
La alternativa es vieja y barata: guardar un banco de ejemplos y recuperar, en cada llamada, los más parecidos a la entrada actual. Es RAG aplicado al prompt.
Cómo funciona
El mecanismo tiene tres piezas:
- Un banco de ejemplos con pares entrada → salida esperada, revisados por una persona.
- Un índice de embeddings sobre la parte de entrada de cada par.
- Una selección en tiempo de ejecución: se embebe la entrada nueva, se buscan los k vecinos más cercanos y se montan como ejemplos del prompt.
import numpy as np
# Banco de ejemplos. La clave es la entrada; el valor, la salida validada.
# 'vec' se calcula una vez al cargar, no en cada petición.
BANCO = [
{"entrada": "No me llega el correo de confirmación", "salida": "soporte_tecnico"},
{"entrada": "Me habéis cobrado dos veces este mes", "salida": "facturacion"},
# ... cientos de pares revisados
]
def indexar(embed):
vecs = np.array([embed(e["entrada"]) for e in BANCO], dtype="float32")
# Normalizamos: así el producto escalar equivale a la similitud coseno.
return vecs / np.linalg.norm(vecs, axis=1, keepdims=True)
def elegir_ejemplos(consulta, embed, indice, k=4):
q = np.array(embed(consulta), dtype="float32")
q /= np.linalg.norm(q)
puntuaciones = indice @ q # similitud con cada ejemplo
top = np.argsort(-puntuaciones)[:k]
return [BANCO[i] for i in top]
def montar_prompt(consulta, ejemplos):
bloque = "\n\n".join(
f"Entrada: {e['entrada']}\nCategoría: {e['salida']}" for e in ejemplos
)
return f"Clasifica la consulta.\n\n{bloque}\n\nEntrada: {consulta}\nCategoría:"
Son treinta líneas. No hace falta una base vectorial: hasta unos miles de ejemplos, una matriz en memoria responde en menos de un milisegundo.
Lo que cambia frente a los ejemplos fijos
| Aspecto | Ejemplos fijos | Ejemplos dinámicos |
|---|---|---|
| Cobertura | Los casos que se te ocurrieron | Tu banco entero, filtrado por relevancia |
| Coste por llamada | Constante | Constante (mismo k) más un embebido |
| Mantenimiento | Editar el prompt | Añadir filas al banco |
| Caché de prompt | El prefijo se cachea entero | Los ejemplos van tras el prefijo, no se cachean |
| Riesgo | Se queda obsoleto | Un mal ejemplo en el banco contamina llamadas concretas |
La fila de la caché es la que más sorprende. Si usas caché de prompt, las instrucciones y el esquema van primero y se cachean; los ejemplos recuperados van después, porque cambian en cada petición. Invertir el orden rompe la caché de todo lo que venga detrás.
Lo que sale mal
Los vecinos más cercanos no son los más útiles. Si el banco tiene veinte variantes de “no me llega el correo”, los cuatro vecinos serán casi idénticos y el modelo no verá ningún caso de contraste. Deduplica el banco y, si la tarea lo pide, reserva un hueco para un ejemplo de la clase más confundible (un contraejemplo).
El orden importa. Los modelos pesan más lo que está cerca de la pregunta. Coloca el ejemplo más similar el último, justo antes de la entrada.
Fugas con el conjunto de evaluación. Si evalúas sobre casos que también están en el banco, el modelo copia la respuesta y tu métrica mide memoria. Separa banco y conjunto de prueba desde el primer día, y excluye de la recuperación cualquier ejemplo idéntico a la consulta cuando estés midiendo.
Ejemplos con errores. Un par mal etiquetado en un prompt fijo es un fallo constante y visible. En un banco dinámico es un fallo intermitente que solo aparece con ciertas entradas. Registra qué ejemplos se usaron en cada llamada: sin eso, depurar es adivinar.
Cómo saber si merece la pena
No lo des por hecho: mide. Un experimento de una tarde basta.
- Toma 200 entradas reales con su respuesta correcta, fuera del banco.
- Ejecuta el mismo prompt con los ejemplos fijos actuales y con k = 4 dinámicos.
- Compara acierto por categoría, no solo la media: la ganancia suele concentrarse en las clases raras, donde los ejemplos fijos nunca llegaron.
Si la mejora es menor de dos o tres puntos, quédate con los fijos. La complejidad extra no se paga sola.
Qué hacer el lunes
Abre el prompt que más tráfico mueve y mira la sección de ejemplos. Si tiene más de seis meses, empieza por lo barato: extrae esos ejemplos a un fichero, añade los fallos reales de la última semana (con su etiqueta correcta) y monta el selector de arriba con k = 3. El banco crece con cada error que corriges, y esa es la ventaja de fondo: el prompt deja de ser un texto que se reescribe y pasa a ser un dato que se cura.