Módulo 1: Fundamentos de Prompt Engineering
6. Comparación: Prompt Casual vs Prompt Engineered
Descripción de la cápsula
En esta cápsula verás side-by-side los resultados de prompts naive versus prompts diseñados con los principios de este módulo. Medirás la diferencia con datos concretos: consistencia de formato, parseabilidad por código, tokens consumidos y costo estimado. Sin esta evidencia, es fácil subestimar el valor del prompt engineering — parece un esfuerzo extra para algo que "ya funciona".
El objetivo no es que abandones prompts simples para siempre, sino que entiendas en qué contextos la diferencia es crítica y en qué contextos no importa. Aprenderás a elegir el nivel de ingeniería apropiado para cada situación y a medir la mejora de forma objetiva, no por intuición.
Por qué importa: En el Módulo 7 aprenderás a evaluar prompts sistemáticamente. Esta cápsula es el puente entre "sé que mejoré" y "tengo datos que demuestran cuánto mejoré". El framework de comparación que verás aquí es la versión simplificada de lo que usarás con métricas formales en producción.
Fundamentos: Por Qué los Prompts Casuales Son Inconsistentes
Un prompt casual falla porque no especifica ninguno de estos elementos:
- Sin system prompt: El modelo usa su comportamiento default, que varía por modelo y versión
- Sin formato de salida: El modelo elige cómo responder (puede ser una palabra o tres párrafos)
- Sin temperatura controlada: El default varía entre proveedores (0.7-1.0 típicamente), añadiendo variabilidad
- Sin restricciones: El modelo puede añadir explicaciones, advertencias, sugerencias no pedidas
- Sin manejo de edge cases: Inputs atípicos producen outputs impredecibles
Analogía: Un prompt casual es como una función sin type hints, sin validación de inputs, y sin valor de retorno definido. Funciona a veces, falla silenciosamente otras.
Experimento 1: Clasificación de Sentimiento
Setup del experimento
from openai import OpenAI
import json
client = OpenAI()
test_inputs = [
"El servicio fue terrible, no volveré.",
"El producto llegó en perfectas condiciones, muy contento.",
"Más o menos, podría mejorar pero tampoco está mal.",
"INCREÍBLE! Mejor compra que hice este año!!",
"No funciona como esperaba. Decepcionante."
]
Prompt casual — ejecución y análisis
def clasificar_casual(texto: str) -> str:
"""Prompt sin estructura, sin system, temperature default."""
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": f"¿Este texto es positivo o negativo? \"{texto}\""}
]
# Sin temperature (default del modelo)
# Sin max_tokens
# Sin system prompt
)
return r.choices[0].message.content.strip()
print("=== Resultados Prompt Casual ===")
resultados_casual = []
for texto in test_inputs:
resultado = clasificar_casual(texto)
resultados_casual.append(resultado)
print(f"Input: {texto[:50]}")
print(f"Output: {resultado}\n")
Output típico (varía entre ejecuciones):
Input: El servicio fue terrible, no volveré.
Output: Este texto es claramente negativo. El autor expresa insatisfacción con el servicio y declara que no regresará.
Input: El producto llegó en perfectas condiciones, muy contento.
Output: Positivo
Input: Más o menos, podría mejorar pero tampoco está mal.
Output: El texto tiene un tono neutral o mixto. No es claramente positivo ni negativo.
Input: INCREÍBLE! Mejor compra que hice este año!!
Output: Es positivo.
Input: No funciona como esperaba. Decepcionante.
Output: Negativo. El texto expresa decepción.
Problema: Cinco formatos diferentes en cinco respuestas. Imposible de parsear con código.
Prompt engineered — ejecución y análisis
SYSTEM_CLASIFICADOR = """
Eres un clasificador de sentimiento de texto.
TAREA: Clasifica el sentimiento del texto en exactamente una de estas categorías:
- POSITIVO
- NEGATIVO
- NEUTRO
REGLAS:
- Responde ÚNICAMENTE con la categoría (una palabra en mayúsculas)
- Sin explicaciones, sin puntuación, sin texto adicional
- Para textos mixtos, elige el sentimiento DOMINANTE
"""
def clasificar_engineered(texto: str) -> str:
"""Prompt estructurado, system prompt, temperature=0."""
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEM_CLASIFICADOR},
{"role": "user", "content": texto}
],
temperature=0, # Determinístico
max_tokens=10 # Solo necesitas una palabra
)
return r.choices[0].message.content.strip()
print("=== Resultados Prompt Engineered ===")
resultados_eng = []
for texto in test_inputs:
resultado = clasificar_engineered(texto)
resultados_eng.append(resultado)
print(f"Input: {texto[:50]}")
print(f"Output: {resultado}\n")
Output consistente:
Input: El servicio fue terrible, no volveré.
Output: NEGATIVO
Input: El producto llegó en perfectas condiciones, muy contento.
Output: POSITIVO
Input: Más o menos, podría mejorar pero tampoco está mal.
Output: NEUTRO
Input: INCREÍBLE! Mejor compra que hice este año!!
Output: POSITIVO
Input: No funciona como esperaba. Decepcionante.
Output: NEGATIVO
Cinco respuestas, cinco formatos idénticos. Parseable directamente: resultado in ["POSITIVO", "NEGATIVO", "NEUTRO"].
Métricas Cuantitativas: Comparación Directa
def medir_consistencia_formato(resultados: list[str],
formatos_validos: set[str]) -> float:
"""Qué % de respuestas tiene formato válido y parseable."""
validos = sum(1 for r in resultados if r in formatos_validos)
return validos / len(resultados)
def medir_tokens_promedio(resultados: list[str]) -> float:
"""Tokens promedio por respuesta (aproximado)."""
return sum(len(r.split()) for r in resultados) / len(resultados)
formatos_validos = {"POSITIVO", "NEGATIVO", "NEUTRO"}
consistencia_casual = medir_consistencia_formato(resultados_casual, formatos_validos)
consistencia_eng = medir_consistencia_formato(resultados_eng, formatos_validos)
tokens_casual = medir_tokens_promedio(resultados_casual)
tokens_eng = medir_tokens_promedio(resultados_eng)
print("=== Comparación de Métricas ===")
print(f"Consistencia casual: {consistencia_casual:.0%}")
print(f"Consistencia engineered:{consistencia_eng:.0%}")
print(f"Tokens/respuesta casual: {tokens_casual:.1f}")
print(f"Tokens/respuesta eng: {tokens_eng:.1f}")
Output esperado:
=== Comparación de Métricas ===
Consistencia casual: 20%
Consistencia engineered:100%
Tokens/respuesta casual: 18.4
Tokens/respuesta eng: 1.0
Tabla de métricas completa
| Métrica | Prompt casual | Prompt engineered | Mejora |
|---|---|---|---|
| Consistencia de formato | ~20% (1/5) | 100% (5/5) | +5x |
| Parseable directamente | No (requiere regex/NLP) | Sí (in valid_set) | — |
| Tokens por respuesta | 15-50 | 1-2 | ~10-25x menos |
| Costo relativo por call | 10-15x mayor | Base | -90% |
| Latencia relativa | Mayor | Menor | ~20-30% más rápido |
| Reproducibilidad | Variable | Determinístico (temp=0) | — |
Experimento 2: Extracción de Datos Estructurados
La diferencia es aún más pronunciada cuando el output debe ser estructurado.
Prompt casual para extracción
def extraer_casual(texto: str) -> str:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Extrae el nombre y email de este texto: \"{texto}\""
}]
)
return r.choices[0].message.content.strip()
textos = [
"Contactar a María García en mgarcia@empresa.com para la reunión",
"El responsable es Juan (juan.perez@gmail.com) y su backup Carlos (carlos@mail.com)",
"No hay información de contacto en este mensaje"
]
print("=== Extracción Casual ===")
for t in textos:
print(f"Input: {t[:60]}")
print(f"Output: {extraer_casual(t)}\n")
Output típico (problemático):
Input: Contactar a María García en mgarcia@empresa.com para la reunión
Output: Nombre: María García
Email: mgarcia@empresa.com
Input: El responsable es Juan (juan.perez@gmail.com) y su backup Carlos (carlos@mail.com)
Output: En el texto se mencionan dos personas con sus correos:
1. Juan - juan.perez@gmail.com
2. Carlos - carlos@mail.com
Input: No hay información de contacto en este mensaje
Output: No se encontró nombre ni email en el texto proporcionado.
Problema: Tres formatos completamente diferentes. ¿Cómo parseas esto con código?
Prompt engineered para extracción
SYSTEM_EXTRACTOR = """
Extrae nombres y emails de textos.
FORMATO DE RESPUESTA (JSON estricto, sin nada más):
{"nombres": ["nombre1", "nombre2"], "emails": ["email1", "email2"]}
REGLAS:
- Si hay múltiples personas, incluye todos
- Si no hay nombres: {"nombres": [], "emails": [...]}
- Si no hay emails: {"nombres": [...], "emails": []}
- Si no hay nada: {"nombres": [], "emails": []}
- Solo JSON válido. Sin texto adicional.
"""
def extraer_engineered(texto: str) -> dict:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEM_EXTRACTOR},
{"role": "user", "content": texto}
],
temperature=0,
response_format={"type": "json_object"}
)
return json.loads(r.choices[0].message.content)
print("=== Extracción Engineered ===")
for t in textos:
resultado = extraer_engineered(t)
print(f"Input: {t[:60]}")
print(f"Output: {resultado}")
# Verificar que es parseable
assert isinstance(resultado["nombres"], list)
assert isinstance(resultado["emails"], list)
print(" ✅ Parseable y válido\n")
Output consistente:
Input: Contactar a María García en mgarcia@empresa.com para la reunión
Output: {'nombres': ['María García'], 'emails': ['mgarcia@empresa.com']}
✅ Parseable y válido
Input: El responsable es Juan (juan.perez@gmail.com) y su backup Carlos (carlos@mail.com)
Output: {'nombres': ['Juan', 'Carlos'], 'emails': ['juan.perez@gmail.com', 'carlos@mail.com']}
✅ Parseable y válido
Input: No hay información de contacto en este mensaje
Output: {'nombres': [], 'emails': []}
✅ Parseable y válido
Cuándo Basta el Prompt Casual
No siempre necesitas ingeniería completa. La decisión depende del contexto de uso:
Basta con prompt casual cuando:
-
Exploración personal: Estás probando ideas, generando borradores, explorando posibilidades. El output va a tus ojos, no a un sistema.
-
One-off manual: Ejecutas el prompt una sola vez, tú revisas el output manualmente, y no lo repetirás.
-
Prototipado rápido: Estás validando si un concepto funciona antes de invertir tiempo en diseño. Ciclo de validación de 30 minutos.
-
Tarea con resultado humano: El output pasa por revisión humana antes de usarse. La persona puede corregir inconsistencias.
-
Tarea trivialmente simple: "Traduce 'hello' al español." No hay ambigüedad posible; el formato natural del modelo ya es el correcto.
Necesitas prompt engineering cuando:
-
Producción: El output va a un sistema, a usuarios reales, o a código que lo procesa. Los fallos tienen consecuencia.
-
Output parseable: Tu código espera JSON, una categoría específica, un número, un formato concreto. Un output mal formateado rompe el sistema.
-
Consistencia requerida: El mismo input debe producir el mismo output (o un rango predecible). Multi-user, multi-region, multi-proveedor.
-
Evaluación y mejora: Necesitas comparar versiones del prompt con métricas objetivas. Sin formato consistente, no puedes medir.
-
Escala: El prompt se ejecuta 1,000+ veces al día. Cada token de más es costo real. Cada formato inconsistente es un fallo de parsing.
-
Multi-proveedor: Tu prompt debe funcionar en OpenAI, Anthropic o cualquier otro modelo. Sin estructura explícita, el comportamiento varía.
# Regla de decisión práctica
def necesita_engineering(contexto: dict) -> tuple[bool, str]:
"""
contexto: dict con flags del caso de uso.
Retorna (necesita, razón).
"""
if contexto.get("produccion"):
return True, "Producción: consistencia crítica"
if contexto.get("output_parseable"):
return True, "Output parseable: formato estricto requerido"
if contexto.get("llamadas_por_dia", 0) >= 100:
return True, f"Escala: {contexto['llamadas_por_dia']} llamadas/día → optimizar tokens"
if contexto.get("multi_proveedor"):
return True, "Multi-proveedor: necesita instrucciones explícitas portables"
if contexto.get("necesita_evaluacion"):
return True, "Evaluación: sin formato consistente no puedes medir mejoras"
return False, "Exploración/one-off: prompt casual suficiente"
# Ejemplos
casos = [
{"descripcion": "Chatbot de soporte productivo", "produccion": True, "output_parseable": True},
{"descripcion": "Script one-off para traducir 10 títulos", "llamadas_por_dia": 1},
{"descripcion": "Extractor de datos que corre 500 veces/día", "llamadas_por_dia": 500, "output_parseable": True},
{"descripcion": "Prototipo rápido para demo interna", "llamadas_por_dia": 5}
]
for caso in casos:
necesita, razon = necesita_engineering(caso)
tipo = "ENGINEERED" if necesita else "CASUAL"
print(f"{caso['descripcion']}: [{tipo}] — {razon}")
Output:
Chatbot de soporte productivo: [ENGINEERED] — Producción: consistencia crítica
Script one-off para traducir 10 títulos: [CASUAL] — Exploración/one-off: prompt casual suficiente
Extractor de datos que corre 500 veces/día: [ENGINEERED] — Escala: 500 llamadas/día → optimizar tokens
Prototipo rápido para demo interna: [CASUAL] — Exploración/one-off: prompt casual suficiente
Framework de Comparación de Prompts
Cuando quieras medir si una versión nueva de un prompt es mejor que la anterior, necesitas un benchmark básico:
from typing import Callable
import time
def benchmark_prompts(
fn_casual: Callable[[str], str],
fn_engineered: Callable[[str], str],
test_inputs: list[str],
ground_truth: list[str],
valid_formats: set[str]
) -> dict:
"""
Compara dos versiones de prompt con métricas cuantitativas.
Returns:
dict con métricas de cada versión
"""
results = {"casual": {}, "engineered": {}}
for name, fn in [("casual", fn_casual), ("engineered", fn_engineered)]:
outputs = []
times = []
for inp in test_inputs:
start = time.time()
output = fn(inp)
elapsed = time.time() - start
outputs.append(output)
times.append(elapsed)
# Métricas
formato_valido = sum(1 for o in outputs if o in valid_formats)
tokens_promedio = sum(len(o.split()) for o in outputs) / len(outputs)
# Accuracy vs ground truth (si aplica)
accuracy = None
if ground_truth:
correcto = sum(1 for o, g in zip(outputs, ground_truth) if o == g)
accuracy = correcto / len(ground_truth)
results[name] = {
"consistencia_formato": formato_valido / len(test_inputs),
"tokens_promedio": tokens_promedio,
"latencia_promedio_s": sum(times) / len(times),
"accuracy": accuracy,
"outputs": outputs
}
return results
# Uso
ground_truth = ["NEGATIVO", "POSITIVO", "NEUTRO", "POSITIVO", "NEGATIVO"]
metricas = benchmark_prompts(
fn_casual=clasificar_casual,
fn_engineered=clasificar_engineered,
test_inputs=test_inputs,
ground_truth=ground_truth,
valid_formats={"POSITIVO", "NEGATIVO", "NEUTRO"}
)
print("=== Benchmark Resultados ===\n")
for version, m in metricas.items():
print(f"[{version.upper()}]")
print(f" Consistencia de formato: {m['consistencia_formato']:.0%}")
print(f" Tokens promedio: {m['tokens_promedio']:.1f}")
print(f" Latencia promedio: {m['latencia_promedio_s']:.2f}s")
if m['accuracy'] is not None:
print(f" Accuracy vs ground truth: {m['accuracy']:.0%}")
print()
Output esperado:
=== Benchmark Resultados ===
[CASUAL]
Consistencia de formato: 20%
Tokens promedio: 18.4
Latencia promedio: 0.38s
Accuracy vs ground truth: 60%
[ENGINEERED]
Consistencia de formato: 100%
Tokens promedio: 1.0
Latencia promedio: 0.31s
Accuracy vs ground truth: 100%
Impacto a Escala
Las diferencias parecen pequeñas en un solo prompt. A escala, cambian el costo operativo:
def calcular_costo_escala(
tokens_promedio: float,
llamadas_por_dia: int,
precio_por_1k_tokens: float = 0.00015 # gpt-4o-mini output
) -> dict:
"""Calcula costo mensual estimado."""
tokens_diarios = tokens_promedio * llamadas_por_dia
costo_diario = (tokens_diarios / 1000) * precio_por_1k_tokens
costo_mensual = costo_diario * 30
return {
"tokens_diarios": tokens_diarios,
"costo_diario_usd": costo_diario,
"costo_mensual_usd": costo_mensual
}
llamadas_por_dia = 10_000 # Sistema real mediano
costo_casual = calcular_costo_escala(18.4, llamadas_por_dia)
costo_eng = calcular_costo_escala(1.0, llamadas_por_dia)
print(f"=== Impacto de tokens a {llamadas_por_dia:,} llamadas/día ===\n")
print(f"Prompt casual: ${costo_casual['costo_mensual_usd']:.2f}/mes")
print(f"Prompt engineered: ${costo_eng['costo_mensual_usd']:.2f}/mes")
ahorro = costo_casual['costo_mensual_usd'] - costo_eng['costo_mensual_usd']
print(f"Ahorro mensual: ${ahorro:.2f} (-{ahorro/costo_casual['costo_mensual_usd']:.0%})")
Output:
=== Impacto de tokens a 10,000 llamadas/día ===
Prompt casual: $0.83/mes
Prompt engineered: $0.05/mes
Ahorro mensual: $0.78 (-95%)
Nota: con gpt-4o-mini los costos son bajos, pero con gpt-4o o con modelos de pago los valores se multiplican 10-20x. El principio de minimizar tokens innecesarios aplica en todos los casos.
Conexión con el Proyecto
En el Prompt Analyzer (cápsula 08) implementarás una función que detecta si un prompt tiene características de "casual" o "engineered" según:
- Presencia de system prompt (sí/no)
- Componentes explícitos detectados (instrucción, formato, restricciones)
- Temperatura especificada (sí/no)
- Manejo de edge cases documentado
El score resultante es una de las "sugerencias de mejora" que generará el analyzer: "Este prompt es casual (2/5 características engineered). Añade system prompt y formato explícito para aumentar consistencia."
Troubleshooting
Problema 1: El prompt engineered a veces también falla
Causa: Edge cases no cubiertos en el diseño. Ningún prompt es 100% perfecto al primer intento.
Solución: Documenta los inputs que fallan, identifica el patrón, y añade al prompt:
- En Insight: regla específica para ese tipo de input
- En Experiment: ejemplo del edge case con su output correcto
# Agregar edge case al Experiment
EXPERIMENT_MEJORADO = """
...ejemplos previos...
Edge cases:
- Input vacío o solo espacios → NEUTRO
- Input con solo emojis positivos (😊👍) → POSITIVO
- Input en otro idioma → clasifica igualmente (el sentiment es universal)
"""
Problema 2: No sé qué métricas usar para comparar
Causa: Falta definición de "éxito" antes de comparar.
Solución: Define antes de ejecutar:
- Para clasificación: accuracy vs ground truth (labels verificados por humanos)
- Para extracción: precision y recall de campos (¿extrae todos? ¿solo los correctos?)
- Para formato: % de respuestas que parsean sin error
- Para costo: tokens promedio por llamada exitosa
Problema 3: Mi "ground truth" es difícil de construir
Causa: No tienes ejemplos etiquetados para medir accuracy.
Solución: Empieza con un conjunto pequeño pero representativo (20-50 ejemplos):
- Selecciona casos que cubran happy path, edge cases, y casos ambiguos
- Etiqueta manualmente (o con un modelo de alta calidad como "juez")
- Usa esos mismos casos en cada versión del prompt
Problema 4: La comparación no muestra diferencia notable
Causa: Los inputs de test son demasiado simples (no prueban edge cases).
Solución: Añade inputs que fuercen al modelo a elegir:
- Textos mixtos (positivo y negativo en la misma oración)
- Inputs con ironía o sarcasmo
- Inputs muy cortos ("OK", "no")
- Inputs con typos o informalidad
Ejercicios
Ejercicio 1: Diseñar tu comparación (Fácil)
Elige una tarea de tu preferencia (ej: extraer URLs de un texto, clasificar tipo de email, detectar idioma) y escribe (a) prompt casual y (b) prompt engineered. Ejecuta ambos 5 veces con el mismo input y compara consistencia de formato.
Ver solución
from openai import OpenAI
import json
client = OpenAI()
# Tarea: Extraer URLs de un texto
# (a) Prompt casual
def casual_urls(texto: str) -> str:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Extrae las URLs de este texto: {texto}"}]
)
return r.choices[0].message.content
# (b) Prompt engineered
SYSTEM_URLS = """
Extrae todas las URLs de un texto.
Responde SOLO con JSON: {"urls": ["url1", "url2"]}
Si no hay URLs: {"urls": []}
Sin texto adicional.
"""
def engineered_urls(texto: str) -> dict:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEM_URLS},
{"role": "user", "content": texto}
],
temperature=0,
response_format={"type": "json_object"}
)
return json.loads(r.choices[0].message.content)
# Test
texto = "Visita https://openai.com y también https://anthropic.com para más info"
print("Casual (5 ejecuciones):")
for _ in range(5):
print(f" {casual_urls(texto)[:80]}")
print("\nEngineered (5 ejecuciones):")
for _ in range(5):
result = engineered_urls(texto)
print(f" {result}")
# Verificar parseabilidad
assert isinstance(result["urls"], list)
Métrica clave: ¿Cuántas de las 5 respuestas casuales puedes procesar directamente con código? ¿Cuántas de las engineered?
Ejercicio 2: Decidir el nivel apropiado (Fácil)
Para cada escenario, decide si prompt casual basta o si necesitas ingeniería, y justifica:
a) Herramienta interna para que un dev extraiga datos de logs manualmente
b) Chatbot de soporte que clasifica intención antes de rutear a equipo
c) Script one-off para traducir 10 títulos de marketing
d) API pública que procesa texto de usuarios y devuelve análisis JSON
Ver solución
-
(a) Herramienta interna — DEPENDE. Si el dev revisa manualmente: casual basta. Si el output alimenta otro sistema automáticamente: engineered.
-
(b) Chatbot de soporte — ENGINEERED. Criterios: producción, output parseable para routing, escala (muchas llamadas), consistencia crítica para UX. Fallar el routing afecta directamente al cliente.
-
(c) Script one-off — CASUAL. Criterios: una sola ejecución, revisión humana antes de usar, resultado no critico. Invertir en diseño aquí sería overkill.
-
(d) API pública — ENGINEERED. Criterios: producción, output JSON que usuarios parsearan en su código, multi-usuario, SLA de consistencia implícito, necesidad de evaluación y versioning.
Ejercicio 3: Construir el benchmark básico (Medio)
Extiende el benchmark_prompts del ejemplo para añadir una métrica de "tokens de input" (no solo output). ¿Por qué es relevante medir también los tokens del prompt en sí?
Ver solución
def benchmark_con_input_tokens(
fn_casual: Callable,
fn_engineered: Callable,
system_casual: str,
system_eng: str,
test_inputs: list[str]
) -> dict:
"""
Incluye tokens de input (system + user) en la comparación.
Los tokens de input también se cobran y afectan el costo total.
"""
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o-mini")
results = {}
for name, fn, system in [
("casual", fn_casual, system_casual),
("engineered", fn_engineered, system_eng)
]:
outputs = [fn(inp) for inp in test_inputs]
# Tokens de input = sistema + cada mensaje de usuario
tokens_input_total = 0
for inp in test_inputs:
tokens_input_total += len(enc.encode(system + inp))
tokens_output_total = sum(len(enc.encode(o)) for o in outputs)
tokens_totales_promedio = (tokens_input_total + tokens_output_total) / len(test_inputs)
results[name] = {
"tokens_input_promedio": tokens_input_total / len(test_inputs),
"tokens_output_promedio": tokens_output_total / len(test_inputs),
"tokens_totales_promedio": tokens_totales_promedio
}
return results
Por qué medir tokens de input: Un system prompt de 500 tokens se cobra en CADA llamada. Si el prompt casual tiene 10 tokens de system y el engineered tiene 200 tokens de system, el engineered puede ser más caro en input aunque sea más barato en output. El costo total = input tokens + output tokens.
La optimización real es: minimizar output tokens (están bien) + mantener system prompt lo más conciso posible sin perder precisión.
Ejercicio 4: Medir impacto de temperature en consistencia (Medio)
Ejecuta el mismo prompt casual con temperature 0, 0.5, y 1.0 (5 veces cada uno). ¿Cómo afecta temperature a la consistencia del formato?
Ver solución
from openai import OpenAI
client = OpenAI()
texto = "El producto es bueno pero el precio es muy alto"
prompt_simple = "¿Este texto es positivo o negativo?"
print("=== Impacto de temperature en consistencia ===\n")
for temp in [0.0, 0.5, 1.0]:
outputs = []
for _ in range(5):
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{prompt_simple}\n\n{texto}"}],
temperature=temp
)
outputs.append(r.choices[0].message.content.strip()[:60])
formatos_unicos = len(set(outputs))
print(f"temperature={temp}:")
for o in outputs:
print(f" '{o}'")
print(f" Formatos únicos: {formatos_unicos}/5 (menor = más consistente)\n")
Resultado típico:
temperature=0: 1-2 formatos únicos (alta consistencia)temperature=0.5: 3-4 formatos únicos (media consistencia)temperature=1.0: 4-5 formatos únicos (baja consistencia, máxima variabilidad)
Conclusión: Para clasificación en producción → temperature=0. Para generación creativa → temperature=0.7-1.0. El system prompt mejora la consistencia en todos los casos, pero temperature es multiplicador de variabilidad.
Resumen
En esta cápsula aprendiste:
- Prompt casual: Sin system prompt, sin formato explícito, sin temperatura controlada → resultados variables, difícilmente parseables, costosos a escala
- Prompt engineered: System prompt + formato explícito + temperature=0 + max_tokens controlado → resultados consistentes, parseables directamente, optimizados en costo
- Métricas de comparación: Consistencia de formato (%), tokens promedio, accuracy vs ground truth, latencia, parseabilidad directa
- Cuándo basta lo casual: Exploración personal, one-off con revisión humana, prototipado rápido
- Cuándo necesitas ingeniería: Producción, output parseable por código, escala, evaluación sistemática, multi-proveedor
- Impacto a escala: 18 tokens promedio vs 1 token = diferencia de 95% en costo a 10k llamadas/día
- Framework de benchmark:
benchmark_prompts()mide consistencia, tokens, latencia y accuracy — la base del Módulo 7 (Evaluation)
Próxima cápsula: Diferencias de comportamiento entre proveedores — por qué el prompt que funciona en OpenAI puede necesitar ajustes en Anthropic, y cómo diseñar prompts portables.
Recursos adicionales
- OpenAI Evals — Framework oficial para evaluar prompts con datasets y métricas, base para lo que harás en el Módulo 7
- OpenAI Pricing — Precios actuales por token para calcular impacto real a escala
- Prompt Engineering Best Practices — OpenAI — Guía oficial con estrategias para mejorar consistencia y reducir tokens
- tiktoken — Librería de OpenAI para contar tokens exactamente antes de ejecutar (evita sorpresas de costo)
- Anthropic Prompt Caching — Técnica para reducir costos en prompts con system prompts largos repetidos (preview del Módulo 8)
- BLEU, ROUGE y métricas de evaluación — Hugging Face — Métricas más formales que usarás en el Módulo 7 para evaluar calidad de outputs de texto libre