El prompt que funcionaba un martes
Conoces la escena. Pasas una tarde puliendo un prompt, le das a la frase justa y el modelo responde exactamente lo que querías. Lo pegas en el código, lo subes y te vas a casa contento.
Tres semanas después alguien cambia el modelo, o llega un caso que no habías probado, o el proveedor actualiza la versión por debajo. El prompt sigue ahí, intacto, y las respuestas ahora son otra cosa. Nadie tocó nada y todo se rompió.
El problema no es el prompt. Es que lo trataste como una frase mágica en lugar de como una pieza de software. Y el software que no se versiona, no se prueba y no se mide se degrada solo. El prompt engineering que escala no consiste en encontrar mejores frases, sino en construir el sistema que rodea a esas frases.
”Escalar” no es lo que crees
Escalar un prompt no significa que aguante más usuarios. Para eso ya tienes la infraestructura. Significa otra cosa: que el prompt siga funcionando cuando cambie todo lo que está a su alrededor.
Un prompt escala cuando puedes cambiar de modelo sin rezar, cuando un compañero lo modifica sin romper tres casos que tú nunca documentaste, cuando sabes en cinco minutos si la última edición mejoró o empeoró las respuestas. El prompt mágico falla en las tres. Funciona hoy, contigo, en tu máquina, con tu ejemplo favorito. Es una anécdota, no un sistema.
Trata el prompt como código, porque lo es
Lo primero es sacar el prompt de donde no debería estar: incrustado a pelo en medio de una función, con f-strings que concatenan variables a mano. Esa cadena es lógica de negocio. Merece el mismo trato que el resto.
Eso implica tres cosas concretas. El prompt vive en su propio fichero, no perdido entre el código. Tiene una versión, para que cuando cambie sepas qué cambió y puedas volver atrás. Y separa la plantilla fija de los datos que se inyectan, para no mezclar nunca la instrucción con el contenido del usuario.
from dataclasses import dataclass
from string import Template
@dataclass
class Prompt:
version: str
plantilla: Template
def render(self, **datos) -> str:
# safe_substitute no revienta si falta o sobra una variable:
# en producción prefieres una respuesta rara a una excepción.
return self.plantilla.safe_substitute(**datos)
# El prompt es un dato versionado, no una cadena suelta en el código.
RESUMEN = Prompt(
version="resumen-v3",
plantilla=Template(
"Resume el texto en tres frases para un lector no técnico.\n"
"Responde solo con el resumen, sin preámbulos.\n\n"
"Texto:\n$texto"
),
)
mensaje = RESUMEN.render(texto=documento)
No es elegancia por la elegancia. Cuando una respuesta salga mal en producción, vas a querer saber exactamente qué versión del prompt la generó. Si el prompt es una cadena anónima dispersa por el código, esa pregunta no tiene respuesta.
Separa la instrucción de los datos
La mayoría de los fallos raros vienen de mezclar lo que el sistema ordena con lo que el usuario aporta. Si construyes el prompt pegando texto del usuario directamente en la instrucción, le estás dando permiso para reescribir tus reglas. Eso es la puerta de entrada de la inyección de prompts.
La regla práctica es marcar fronteras claras. La instrucción va en el mensaje de sistema. El contenido del usuario va en su propio bloque, delimitado, y el prompt deja claro que ese bloque son datos a procesar, no órdenes a obedecer. No es infalible, pero convierte un agujero abierto en una rendija pequeña.
Evalúa, no intuyas
Aquí está el salto que separa al aficionado del ingeniero. “A mí me funciona” no es una métrica, es una sensación. Y las sensaciones no sobreviven a un cambio de modelo.
Antes de tocar un prompt en serio, monta un conjunto de evaluación: 20 o 30 casos reales con la respuesta que esperas de cada uno. No hace falta nada sofisticado; un fichero basta. Cuando edites el prompt, lo pasas entero y comparas. Así dejas de discutir opiniones y empiezas a ver números.
casos = [
{"texto": "...", "espera": "menciona el plazo de 30 días"},
{"texto": "...", "espera": "no inventa cifras"},
# ...
]
def evaluar(prompt, casos, juez):
aciertos = 0
for caso in casos:
salida = llm.generar(prompt.render(texto=caso["texto"]))
# El "juez" puede ser una regla, una comprobación o
# un segundo LLM con criterios explícitos.
if juez(salida, caso["espera"]):
aciertos += 1
return aciertos / len(casos)
Con esto, “mejorar el prompt” deja de ser una corazonada nocturna y pasa a ser una cifra que sube o baja. Cuando llegue el día de cambiar de modelo, pasas el mismo conjunto y sabes en minutos si la migración te sale gratis o te va a costar una semana.
Prompt mágico contra sistema de prompts
| Dimensión | Prompt mágico | Sistema de prompts |
|---|---|---|
| Dónde vive | Incrustado en el código | Fichero propio, versionado |
| Datos del usuario | Concatenados en la instrucción | En su bloque, separados |
| Cambiar de modelo | A ciegas, rezando | Pasas el conjunto de evaluación |
| Saber si mejoró | Por intuición | Por una cifra |
| Cuando algo falla | ”¿Qué prompt era?” | Versión exacta en el log |
La columna de la derecha no es más trabajo por gusto. Es la diferencia entre un sistema que puedes mantener y uno que reza cada despliegue.
Por dónde empezar mañana
No reescribas todo de golpe. Coge el prompt que más te importe en producción y hazle tres cosas. Sácalo a su fichero con una versión. Separa la instrucción de los datos del usuario. Y escríbele diez casos de evaluación, aunque sea a mano en un rato.
Con esos tres pasos, ese prompt deja de ser un accidente que aún no se ha roto y empieza a ser una pieza que puedes tocar sin miedo. El resto del sistema vendrá solo, prompt a prompt, cuando notes lo tranquilo que se trabaja cuando los cambios se miden en vez de adivinarse.