DEV Community

Moon Robert
Moon Robert

Posted on

Prompt Engineering avanzado: lo que aprendí después de dos semanas rompiendo producciones

Hace tres meses, un compañero de trabajo me mandó un Slack diciendo que el extractor de datos que yo había construido estaba devolviendo JSON malformado en producción. El modelo era GPT-4o, el prompt era "razonablemente bueno" (eso pensaba yo), y el sistema había fallado silenciosamente durante cuatro horas antes de que alguien se diera cuenta. Ese fue el momento en el que decidí tomarme el prompt engineering en serio.

Pasé las dos semanas siguientes probando sistemáticamente cada técnica que pude encontrar. Lo que sigue es lo que realmente funciona, qué no funciona como dicen los tutoriales, y dónde perdí tiempo que no voy a recuperar.


Few-Shot no es solo "poner ejemplos ahí"

La mayoría de los tutoriales que leí sobre Few-Shot prompting lo presentan como: pon tres ejemplos y el modelo lo entiende. Técnicamente es verdad. Pero hay mucho espacio entre "el modelo lo entiende" y "el modelo hace exactamente lo que necesitas de forma consistente".

El problema con el que me topé primero fue la calidad de los ejemplos. Para un clasificador de intenciones que estaba construyendo para un chatbot de soporte, mis primeros ejemplos eran demasiado "limpios" — casos perfectos que no reflejaban cómo los usuarios reales escriben. Resultado: 78% de precisión en producción contra el 94% que veía en mis pruebas manuales.

El ajuste fue específico: usé ejemplos reales de logs de producción, con errores ortográficos incluidos, con frases incompletas, con mezcla de idiomas (nuestros usuarios a veces mezclan español e inglés). Eso subió la precisión a 91%. No es perfecto, pero es honesto.

Otro detalle que marca diferencia: el orden de los ejemplos importa más de lo que parece. Los modelos tienen un sesgo hacia los últimos ejemplos que vieron (primacy/recency bias). Si tu tarea tiene clases desbalanceadas, pon los ejemplos de la clase minoritaria al final. Lo descubrí por accidente cuando reorganicé los ejemplos para "que se vieran más limpios" y el rendimiento cayó 6 puntos.

# Esto es lo que terminé usando para el clasificador de intenciones
# Fíjate en los ejemplos: no son perfectos, reflejan inputs reales

SYSTEM_PROMPT = """Clasifica la intención del usuario en: REEMBOLSO, SOPORTE_TECNICO, CONSULTA_PRODUCTO, OTRO.
Responde solo con la etiqueta, nada más.

Ejemplos:
Usuario: "quier devolver el producto que compre ayer"
Intención: REEMBOLSO

Usuario: "la app no abre desde ayer, intente reinstalar y nada"
Intención: SOPORTE_TECNICO

Usuario: "cuanto cuesta el plan premium? tiene descuento para equipos?"
Intención: CONSULTA_PRODUCTO

Usuario: "ok gracias"
Intención: OTRO

Usuario: "necesito help con mi cuenta no puedo login"
Intención: SOPORTE_TECNICO"""

# El último ejemplo mezcla idiomas — porque nuestros usuarios lo hacen
Enter fullscreen mode Exit fullscreen mode

So, la regla práctica que sigo ahora: construye tus ejemplos Few-Shot a partir de datos reales, no de lo que tú crees que debería escribir un usuario ideal. Si no tienes datos reales todavía, al menos varía intencionalmente el formato, longitud y estilo.


Chain-of-Thought: cuándo funciona y cuándo es puro overhead

Chain-of-Thought (CoT) es la técnica donde le pides al modelo que "piense paso a paso" antes de dar una respuesta. El paper original de Wei et al. (2022) mostró mejoras dramáticas en tareas de razonamiento matemático. Lo que no siempre mencionan es que tiene un costo real en tokens y latencia, y que para muchas tareas simplemente no ayuda.

Lo probé durante una semana completa en cuatro tipos de tareas distintas:

Extracción de datos estructurados: CoT no ayudó. El modelo ya sabía qué hacer, solo necesitaba instrucciones claras sobre el formato de salida. Añadir "piensa paso a paso" aumentó el costo de tokens un 40% sin mejorar la precisión.

Clasificación de sentimientos en reseñas largas: CoT ayudó moderadamente (+4% en casos ambiguos). No justificaba el costo en mi caso, pero podría ser diferente si la precisión fuera crítica.

Resolución de problemas de lógica / reglas de negocio complejas: aquí CoT brilló. Tenía un sistema que verificaba si ciertos descuentos eran aplicables basándose en 12 reglas de negocio con dependencias entre ellas. Sin CoT, el modelo fallaba en un 23% de los casos complejos. Con CoT, ese número bajó a 8%.

Generación de código: depende completamente de la complejidad. Para funciones simples, no. Para arquitecturas que requieren considerar múltiples tradeoffs, sí.

La implementación que me funcionó mejor no es el simplista "piensa paso a paso" — es lo que algunos llaman Zero-Shot CoT con estructura forzada:

# En vez de esto (demasiado abierto):
prompt_malo = "Piensa paso a paso y determina si el descuento aplica."

# Uso esto (estructura forzada):
prompt_bueno = """Determina si el descuento aplica. Sigue este proceso exacto:

1. ELEGIBILIDAD_USUARIO: [analiza si el usuario cumple los requisitos de membresía]
2. ELEGIBILIDAD_PRODUCTO: [analiza si el producto está incluido en la promoción]  
3. RESTRICCIONES: [verifica si hay restricciones temporales o de cantidad]
4. CONFLICTOS: [verifica si hay otro descuento ya aplicado que sea incompatible]
5. DECISIÓN: [SÍ/NO con razón en una línea]

Responde con exactamente esta estructura."""

# La diferencia clave: le digo QUÉ pensar, no solo que piense
# Esto reduce variabilidad y hace el output más parseable
Enter fullscreen mode Exit fullscreen mode

One thing I noticed: cuando el CoT está bien estructurado, el "razonamiento" que produce el modelo también te sirve como log de auditoría. En mi caso, lo guardo en base de datos porque si alguien pregunta "¿por qué no me aplicaron el descuento?", tengo una explicación generada automáticamente. Eso solo ya pagó el costo extra de tokens.


El error que me costó más caro: confundir complejidad con efectividad

Aquí va el gotcha que prometí.

Después de ver resultados con CoT, me fui por las ramas. Empecé a implementar Tree-of-Thought (ToT) — una extensión donde el modelo explora múltiples "ramas" de razonamiento antes de elegir la mejor. Hay papers impresionantes, implementaciones en GitHub con miles de estrellas. Me convencí de que era lo que necesitaba para mi caso de uso.

Pasé cinco días implementándolo. El resultado: peor rendimiento que CoT simple, cuatro veces más caro en tokens, y latencia de 8-12 segundos por request (inaceptable para un chatbot interactivo). El problema no era la técnica — ToT tiene sentido para problemas donde el espacio de búsqueda es realmente amplio. Pero yo lo apliqué a clasificación de intenciones, que simplemente no es ese tipo de problema.

La lección que me quedo: antes de implementar una técnica avanzada, pregúntate si tu problema es fundamentalmente un problema de razonamiento complejo o si es un problema de instrucciones claras + buenos ejemplos. La mayoría de los casos que encuentro en aplicaciones web reales son lo segundo.

Self-Consistency sí fue útil, aunque más modesta. La idea: genera múltiples respuestas con temperatura > 0 y toma la respuesta más frecuente (mayoría de votos). Lo implementé para decisiones binarias de alto impacto (¿este mensaje viola los términos de servicio?) con tres muestras y mayoría simple. Subió la precisión ~5% en casos límite. El tradeoff es que triplica el costo y la latencia, así que solo lo uso donde los falsos negativos tienen consecuencias reales.


Structured Outputs y por qué cambió mi flujo de trabajo

Esto no es exactamente una técnica de prompting, pero tiene tanto impacto en la confiabilidad que no puedo no mencionarlo.

Desde que OpenAI lanzó Structured Outputs (disponible en gpt-4o-2024-08-06 en adelante) con garantía de adherencia al schema JSON, dejé de usar prompting para controlar el formato de output en la mayoría de mis casos. Antes tenía instrucciones como "responde SOLO con JSON válido, sin texto adicional, sin markdown" y aun así tenía que manejar excepciones regularmente.

Con Structured Outputs defines un schema Pydantic (o JSON Schema directo) y el modelo garantiza que la respuesta lo cumple. No al 99%. Al 100%. El modelo puede seguir "equivocarse" en el contenido — eso sigue siendo tu problema de prompting — pero el formato está garantizado.

El cambio en mi pipeline fue eliminar casi toda la lógica defensiva de parsing que había acumulado. Menos código, menos bugs, menos sorpresas a las 3am.

Lo que todavía requiere prompting cuidadoso es la semántica del contenido. Structured Outputs te garantiza que el campo confidence es un float entre 0 y 1, pero no te garantiza que el valor sea correcto. Para eso sigues necesitando buenos ejemplos, instrucciones claras, y en casos críticos, CoT.


Lo que recomendaría si empezaras desde cero hoy

Sin rodeos: empieza por lo básico bien hecho antes de ir a técnicas avanzadas. El 80% de los problemas de rendimiento que he visto en prompts de producción son instrucciones ambiguas o ejemplos de baja calidad, no ausencia de técnicas sofisticadas.

El orden en el que yo probaría las cosas:

Primero, System prompt claro con rol, tarea, y restricciones explícitas. No "eres un asistente útil" — eso no le dice nada al modelo. Algo como "eres un clasificador de intenciones para el servicio de soporte de [empresa]. Tu única tarea es...".

Segundo, Few-Shot con 3-5 ejemplos reales, no ideales. Si tienes producción corriendo, mina tus logs para los casos donde el modelo falla y úsalos como ejemplos negativos/positivos.

Tercero, Structured Outputs si estás en la API de OpenAI (o el equivalente en Anthropic con tool use forzado). Elimina toda la lógica defensiva de parsing.

Cuarto, Chain-of-Thought estructurado solo si tu tarea involucra razonamiento multi-paso con dependencias entre condiciones. Si no estás seguro de si aplica, haz una prueba A/B real con 100 casos antes de comprometerte.

Quinto, Self-Consistency solo para decisiones de alto impacto donde puedes asumir el costo de latencia triplicada.

Tree-of-Thought y técnicas más experimentales las dejaría para cuando tengas casos de uso realmente difíciles y tiempo para experimentar — no son punto de partida.

No estoy 100% seguro de que esto escale igual para todos los casos de uso. Trabajo principalmente con aplicaciones de soporte y clasificación de contenido; si tu dominio es generación de código complejo o razonamiento matemático, el balance puede ser diferente. Pero la heurística de "empieza simple, mide, complejiza solo donde el dato lo justifica" es bastante universal.

El prompt engineering tiene algo de arte y algo de ingeniería. La parte de ingeniería — métricas, pruebas A/B, iteración basada en datos — es donde más tiempo debería ir. Intuición y técnicas avanzadas llegan después.

Top comments (2)

Collapse
 
nyrok profile image
Hamza KONTE

Two weeks of breaking prod sounds familiar. The pattern I kept hitting: the prompt "worked" in testing but was too ambiguous to be reliable under different inputs.

Ended up building a visual tool to force myself to be explicit about every component — role, constraints, examples, output format separately. Dramatic improvement in consistency. flompt.dev / github.com/Nyrok/flompt if anyone wants to try it.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.