Módulo 1: Fundamentos de Prompt Engineering

5. Mental Models para Diseñar Prompts: CRISPE

Descripción de la cápsula

Diseñar prompts por intuición no escala. Sin un framework, cada prompt es un experimento aislado: pruebas algo, ves si funciona, lo ajustas, y repites sin método. Eso funciona para exploración personal, pero en producción necesitas prompts reproducibles que cualquier miembro del equipo pueda entender, modificar y versionar.

CRISPE (Capacity, Role, Insight, Statement, Personality, Experiment) es el mental model que convierte el diseño de prompts en un proceso sistemático. En lugar de preguntarte "¿cómo escribo este prompt?", te pregunta seis cosas concretas sobre tu tarea. Cada respuesta es un componente del prompt. Cuando tienes los seis, tienes un prompt completo.

Por qué importa: Los módulos siguientes de esta guía — zero-shot, few-shot, CoT, ReAct, composition — todos se benefician de tener un framework de referencia. Cuando un prompt de CoT falla, CRISPE te ayuda a diagnosticar cuál componente es el problema. Cuando diseñes un system prompt para un agente en el módulo 8, CRISPE es la estructura implícita detrás del diseño.


Framework CRISPE

CRISPE es un acrónimo que cubre las seis dimensiones clave de un prompt profesional:

LetraDimensiónPregunta que responde
CCapacity¿Qué acción/capacidad debe ejecutar el modelo?
RRole¿Qué rol, expertise o persona adopta?
IInsight¿Qué contexto o información de fondo necesita?
SStatement¿Cuál es la tarea concreta e instrucción específica?
PPersonality¿Qué tono, estilo, restricciones y guardrails?
EExperiment¿Qué formato de salida? ¿Ejemplos de few-shot?

Analogía: Si un prompt es una especificación de software, CRISPE es la plantilla de requisitos. C es el tipo de operación, R es el contexto del actor, I son las precondiciones, S son los requisitos funcionales, P los no funcionales, y E el contrato de input/output.


Desglose de CRISPE con Ejemplos Progresivos

C — Capacity

Qué es: La capacidad o acción principal que el modelo debe ejecutar. Define el verbo de la tarea.

Verbos de Capacity más comunes:

  • Clasificar, categorizar, etiquetar
  • Extraer, identificar, detectar
  • Resumir, condensar, sintetizar
  • Traducir, adaptar, reformular
  • Generar, crear, producir
  • Analizar, evaluar, comparar
  • Transformar, convertir, procesar
  • Verificar, validar, corregir

Por qué importa: Sin Capacity clara, el modelo puede hacer algo relacionado pero diferente. "Algo con este texto" vs "Clasifica y extrae" son instrucciones completamente distintas.

Progresión:

❌ Vago:    "Algo con este texto de reseña"
⚠️ Mejor:   "Analiza este texto"
✅ Preciso: "Clasifica el sentimiento y extrae los aspectos del producto mencionados"

Regla: Capacity debe ser un verbo de acción en infinitivo. Si usas "algo" o "procesa", Capacity está incompleto.


R — Role

Qué es: El rol, expertise, o persona que adopta el modelo. Afecta vocabulario, nivel de detalle, suposiciones implícitas y estilo de respuesta.

Cómo el Role cambia el output:

from openai import OpenAI
client = OpenAI()

pregunta = "¿Cómo mejoro la performance de esta función Python?"

# Role 1: Ingeniero senior
r_senior = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Eres un ingeniero senior de Python especializado en optimización de performance."},
        {"role": "user", "content": pregunta}
    ],
    temperature=0
)

# Role 2: Maestro para principiantes
r_maestro = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Eres un maestro que explica programación a estudiantes sin experiencia previa."},
        {"role": "user", "content": pregunta}
    ],
    temperature=0
)

# r_senior → Habla de profiling, time complexity, async, caching
# r_maestro → Habla de loops básicos, simplicidad, evita jargon

Roles por dominio:

  • Técnico: "Eres un ingeniero senior de Python con 10 años de experiencia en sistemas distribuidos"
  • Legal: "Eres un abogado especializado en contratos internacionales bajo ley española"
  • Educación: "Eres un profesor que explica conceptos complejos usando analogías del mundo real"
  • Análisis: "Eres un analista de datos con experiencia en métricas de e-commerce"
  • Soporte: "Eres un agente de soporte técnico de nivel 2 para software enterprise"

Regla: El Role debe ser específico, no genérico. "Eres un asistente útil" no es un Role — es el default del modelo. Un Role real restringe y calibra el comportamiento.


I — Insight

Qué es: Contexto, información de fondo, definiciones de dominio, o restricciones que el modelo necesita para ejecutar bien la tarea pero que NO puede inferir del Statement o del Input.

Tipos de Insight:

  • Definiciones de dominio: "Un ticket 'crítico' se define como cualquier problema que afecta a más de 100 usuarios simultáneamente"
  • Restricciones del negocio: "Solo aplica para usuarios con plan Enterprise (no Free ni Pro)"
  • Contexto del sistema: "Este prompt opera en un sistema de inventario. Los códigos de producto tienen formato SKU-XXXX donde X son dígitos"
  • Reglas especiales: "Si el total supera $1,000, el nivel de descuento cambia a Tier-2"
  • Background del usuario: "El usuario ya reinició el dispositivo 3 veces y tiene conexión estable a internet"

Lo que NO debe ir en Insight:

  • Cosas que el modelo puede inferir del Input
  • Información que repite el Statement
  • Contexto irrelevante que aumenta tokens sin valor
# Insight bien calibrado vs sobrecargado

# ❌ Insight sobrecargado (repite lo obvio)
INSIGHT_MALO = """
Este es un sistema de clasificación de tickets de soporte.
Los tickets son mensajes de usuarios.
Los usuarios escriben cuando tienen problemas.
Los problemas pueden ser técnicos o no técnicos.
Las categorías son: Técnico, Facturación, General.
"""

# ✅ Insight preciso (solo lo no inferible)
INSIGHT_BUENO = """
Categorías disponibles y sus criterios:
- TECNICO: Error de software, fallo de funcionalidad, rendimiento degradado
- FACTURACION: Cobros, facturas, cambios de plan, reembolsos
- GENERAL: Preguntas de uso, solicitudes de features, feedback

Si el mensaje menciona múltiples categorías, elige la que requiere atención más urgente.
Tickets de FACTURACION con palabras "fraude" o "cargo incorrecto" → siempre FACTURACION.
"""

Regla: Aplica el principio de la pregunta "¿puede el modelo inferir esto del contexto?" Si SÍ → no lo pongas en Insight. Si NO → ponlo.


S — Statement

Qué es: La instrucción concreta y específica. La tarea exacta que el modelo debe ejecutar.

Diferencia con Capacity:

  • Capacity es el verbo general: "Clasificar"
  • Statement es la instrucción completa: "Clasifica el siguiente ticket de soporte en exactamente una de las categorías definidas, eligiendo la más específica cuando haya ambigüedad"

Progresión de Statement:

Nivel 1 (débil):    "Analiza este texto"
Nivel 2 (mejor):    "Identifica el sentimiento de este texto"
Nivel 3 (bueno):    "Clasifica el sentimiento de este texto como POSITIVO, NEGATIVO, o NEUTRO"
Nivel 4 (completo): "Clasifica el sentimiento del siguiente texto en EXACTAMENTE una de estas categorías: 
                     POSITIVO, NEGATIVO, NEUTRO. Si hay sentimientos mixtos, elige el dominante."

Verbos de Statement más efectivos:

  • "Identifica exactamente..."
  • "Clasifica en uno de estos valores: [lista]"
  • "Extrae los siguientes campos: [lista]"
  • "Devuelve únicamente..."
  • "Si [condición], entonces [acción]; de lo contrario [acción alternativa]"

P — Personality

Qué es: Tono, estilo de respuesta, restricciones de comportamiento, y guardrails. Controla el cómo, no el qué.

Componentes de Personality:

# Ejemplo completo de Personality
PERSONALITY = """
- Idioma: Responde siempre en español, independientemente del idioma del input
- Tono: Formal y técnico. Sin tuteos. Sin coloquialismos
- Longitud: Respuesta concisa. Máximo 3 oraciones a menos que el usuario pida más
- Restricciones: 
  * No inventes información. Si no tienes certeza, di "No tengo información suficiente para responder esto con certeza"
  * No hagas suposiciones sobre intenciones maliciosas
- Guardrails: Si el input pide generar contenido dañino, responde: "No puedo ayudar con eso"
- Formato: Sin markdown a menos que explícitamente se pida
"""

Personality minimal vs completo:

# Personality minimal (para tareas simples)
personality_minimal = "Responde SOLO con la categoría. Sin texto adicional."

# Personality completo (para agentes con más comportamiento)
personality_completo = """
- Solo la categoría en mayúsculas, sin puntuación
- Sin explicaciones ni justificaciones
- Sin saludos ni despedidas
- Si el input es ambiguo: DESCONOCIDO
- Si el input está vacío: ERROR_INPUT_VACIO
"""

E — Experiment

Qué es: El formato de salida esperado, ejemplos de few-shot, y cualquier especificación de cómo debe estructurarse la respuesta.

Opciones de formato en E:

  • JSON con schema específico
  • Lista de bullets con estructura definida
  • Tabla Markdown
  • String exacto (una palabra, un número)
  • Texto estructurado con secciones nombradas
  • Código en un lenguaje específico

Few-shot en E — patrón común:

Experiment:
Input: "Excelente producto, muy recomendable" → Output: POSITIVO
Input: "Pésima atención al cliente, no volvería" → Output: NEGATIVO
Input: "Es un producto normal, ni bueno ni malo" → Output: NEUTRO

Schema explícito en E:

Experiment:
Formato exacto (JSON):
{
  "categoria": "TECNICO|FACTURACION|GENERAL",
  "confianza": 0.0-1.0,
  "razon": "string corto de máximo 10 palabras"
}

Ejemplo Completo: CRISPE Aplicado

Tarea: Clasificar intención de usuario en un chatbot de soporte técnico B2B.

from openai import OpenAI
import json

client = OpenAI()

# Los 6 componentes de CRISPE explícitos
CAPACITY = "Clasificar la intención del usuario en una categoría predefinida."

ROLE = "Eres un clasificador de intenciones para un chatbot de soporte técnico B2B."

INSIGHT = """
Categorías disponibles:
- SALUDO: El usuario saluda sin hacer pregunta específica
- PREGUNTA_TECNICA: Problema técnico, error, fallo, no funciona algo
- QUEJA: Insatisfacción con el servicio o experiencia (puede incluir componente técnico, pero el tono es de frustración/insatisfacción emocional)
- PEDIDO_INFO: Solicitud de información sobre producto, precios, features
- DESPEDIDA: El usuario se despide o cierra la conversación
- OTRO: Cualquier cosa que no encaje claramente en las anteriores

Si hay múltiples intenciones, elige la DOMINANTE (la que requiere más atención inmediata).
Distingue QUEJA de PREGUNTA_TECNICA por el tono: frustración emocional → QUEJA; pregunta técnica neutral → PREGUNTA_TECNICA.
"""

STATEMENT = "Clasifica el siguiente mensaje del usuario en exactamente UNA de las categorías definidas."

PERSONALITY = """
- Responde SOLO con el nombre de la categoría (mayúsculas, sin puntuación adicional)
- Sin explicaciones
- Sin texto antes o después de la categoría
"""

EXPERIMENT = """
Formato de respuesta: una sola palabra en mayúsculas.

Ejemplos:
- "Hola buenas tardes" → SALUDO
- "No puedo acceder a mi cuenta desde el viernes" → PREGUNTA_TECNICA  
- "Llevo 3 días esperando respuesta y nadie me atiende, esto es inaceptable" → QUEJA
- "¿Cuánto cuesta el plan Enterprise?" → PEDIDO_INFO
- "Muchas gracias, hasta pronto" → DESPEDIDA
"""

# Ensamblar en system prompt
SYSTEM = f"""## Capacity
{CAPACITY}

## Role
{ROLE}

## Insight
{INSIGHT}

## Statement
{STATEMENT}

## Personality
{PERSONALITY}

## Experiment
{EXPERIMENT}
"""

# Test con múltiples inputs representativos
test_inputs = [
    "Buenos días, necesito ayuda",
    "El sistema está caído desde las 9am y no puedo trabajar",
    "¿Tienen integración con Salesforce?",
    "Harto de esperar, esto es un desastre y mi empresa está pagando mucho dinero",
    "Hasta luego, gracias por la ayuda",
    "Me llega error 500 al hacer login",
    "Quiero explorar opciones de upgrade"
]

print("=== Test de clasificación con CRISPE ===\n")
for msg in test_inputs:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": msg}
        ],
        temperature=0,
        max_tokens=15
    )
    clasificacion = response.choices[0].message.content.strip()
    print(f"Input:  {msg[:60]}")
    print(f"Output: {clasificacion}\n")

Output esperado:

=== Test de clasificación con CRISPE ===

Input:  Buenos días, necesito ayuda
Output: SALUDO

Input:  El sistema está caído desde las 9am y no puedo trabajar
Output: PREGUNTA_TECNICA

Input:  ¿Tienen integración con Salesforce?
Output: PEDIDO_INFO

Input:  Harto de esperar, esto es un desastre y mi empresa está pagando mucho dinero
Output: QUEJA

Input:  Hasta luego, gracias por la ayuda
Output: DESPEDIDA

Input:  Me llega error 500 al hacer login
Output: PREGUNTA_TECNICA

Input:  Quiero explorar opciones de upgrade
Output: PEDIDO_INFO

Prompt como Especificación de Software

Pensar en el prompt como una especificación de software cambia la forma en que lo diseñas. No estás "escribiendo instrucciones" — estás definiendo un contrato de comportamiento.

Elemento de software specEquivalente en prompt (CRISPE)
Tipo de operaciónCapacity (verbo de acción)
Dominio y actorRole
Precondiciones y contextoInsight
Requisitos funcionalesStatement
Requisitos no funcionalesPersonality
Contrato de I/OExperiment

Anti-pattern: "Haz algo útil con esto" — sin especificación, el modelo usa sus defaults, que pueden no coincidir con tu expectativa.

Pattern: "Dado texto en cualquier idioma (I), extrae entidades nombradas (C+S) como ingeniero de NLP (R), sin inventar, en JSON (P+E)."


Pensar en Constraints, No en Deseos

La diferencia más importante entre un prompt débil y uno fuerte está en los constraints.

Deseos (vagos, difíciles de evaluar):

  • "Que sea bueno"
  • "Que sea preciso"
  • "Que sea útil y completo"

Constraints (específicos, verificables):

  • "Solo responde con una de estas 5 categorías: [lista]"
  • "Máximo 100 palabras"
  • "No incluyas información que no esté en el texto de entrada"
  • "Formato JSON con exactamente estas keys: a, b, c"
  • "Si el input está vacío, devuelve: {"error": "input_vacio"}"
# Deseo vs Constraint en código

# ❌ Deseo: Vago, no evaluable
system_deseo = """
Eres un asistente útil que da información precisa y completa sobre productos.
"""

# ✅ Constraint: Específico, evaluable, reproducible
system_constraint = """
Eres un extractor de información de productos.

CONSTRAINTS:
- Extrae SOLO estos campos: nombre, precio, disponibilidad
- Formato: JSON con keys exactas: {"nombre": str, "precio": float|null, "disponibilidad": bool}
- Si un campo no está en el texto: usa null (nunca inventes valores)
- Si el texto no describe un producto: {"error": "no_es_producto"}
- Sin texto fuera del JSON
"""

# Test
def extract_product(text: str) -> dict:
    from openai import OpenAI
    import json
    client = OpenAI()
    
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": system_constraint},
            {"role": "user", "content": text}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(r.choices[0].message.content)

# Casos de prueba
tests = [
    "Laptop Dell XPS 13, precio: $1,299, disponible en stock",
    "Este artículo está agotado temporalmente",
    "El cielo está despejado hoy"
]

for t in tests:
    print(f"Input: {t}")
    print(f"Output: {extract_product(t)}\n")

Output esperado:

Input: Laptop Dell XPS 13, precio: $1,299, disponible en stock
Output: {'nombre': 'Laptop Dell XPS 13', 'precio': 1299.0, 'disponibilidad': True}

Input: Este artículo está agotado temporalmente
Output: {'nombre': None, 'precio': None, 'disponibilidad': False}

Input: El cielo está despejado hoy
Output: {'error': 'no_es_producto'}

Regla: Cada constraint reduce el espacio de posibles respuestas → mayor predictibilidad.


Iterative Refinement con CRISPE

CRISPE no es un formulario que llenas una vez. Es un ciclo de refinamiento:

1. Draft inicial: Escribe prompt con los 6 componentes
                  ↓
2. Test con 10+ inputs representativos (happy path + edge cases)
                  ↓
3. Analiza fallos: ¿Qué tipo de inputs fallan?
                  ↓
4. Diagnóstico: ¿Cuál componente de CRISPE es el responsable del fallo?
                  ↓
5. Refina ese componente específicamente
                  ↓
6. Re-test. Repite hasta consistencia aceptable (>90%)

Diagnóstico por componente — ejemplos reales:

# Fallo observado: QUEJA clasificada como PREGUNTA_TECNICA cuando hay frustración técnica
# Diagnóstico: Insight no diferencia claramente las dos categorías
# Acción: Mejorar el Insight

INSIGHT_V1 = """
- QUEJA: Insatisfacción con el servicio
- PREGUNTA_TECNICA: Problema técnico
"""

# Después de observar fallos...
INSIGHT_V2 = """
- QUEJA: Frustración o insatisfacción EMOCIONAL hacia el servicio. 
  Puede mencionar problema técnico pero el tono dominante es emocional/confrontacional.
  Señales: "harto", "inaceptable", "decepcionante", "nunca más", "desastre"
- PREGUNTA_TECNICA: Descripción NEUTRAL o informativa de un fallo técnico.
  Señales: "error", "no funciona", "no puedo", preguntas sobre causa del problema
"""

# Fallo observado: El modelo a veces incluye texto adicional antes de la categoría
# Diagnóstico: Personality no es lo suficientemente restrictivo
# Acción: Mejorar el Personality y el Experiment

PERSONALITY_V1 = "Responde solo con la categoría."
PERSONALITY_V2 = """
- Responde ÚNICAMENTE con el nombre de la categoría (ej: SALUDO)
- Sin puntuación al final
- Sin espacio antes o después
- Sin justificación, explicación, ni texto adicional
"""

CRISPE Simplificado para Tareas Simples

Para tareas simples, no siempre necesitas los 6 componentes. Prioriza según el tipo de tarea:

Tipo de tareaComponentes mínimos necesarios
Clasificación simpleC + I (categorías) + S + E (formato)
Extracción de datosC + S + E (schema)
Conversación con personaR + P + S
Generación creativaR + C + S + E (ejemplos)
Análisis complejoTodos los 6
# Ejemplo: CRISPE parcial para detección de idioma

# Justificación de omisiones:
# - Role: Tarea tan específica que no hay ambigüedad de perspectiva
# - Insight: No hay lógica de dominio compleja
# - Personality: No hay restricciones de tono relevantes (solo brevedad, cubierta en E)

SYSTEM_SIMPLE = """
## Capacity
Detectar el idioma del texto.

## Statement
Identifica el idioma del siguiente texto.

## Experiment
Responde solo con el código ISO 639-1 del idioma (2 letras en minúsculas).
Ejemplos: es, en, fr, pt, de, it, zh, ja, ar
Si el texto es demasiado corto o ambiguo: "und" (undetermined)
"""

from openai import OpenAI
client = OpenAI()

textos = [
    "El prompt engineering es fundamental para sistemas de IA",
    "Prompt engineering is fundamental for AI systems",
    "Le prompt engineering est fondamental pour les systèmes d'IA",
    "OK"  # Ambiguo: demasiado corto
]

for texto in textos:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM_SIMPLE},
            {"role": "user", "content": texto}
        ],
        temperature=0,
        max_tokens=5
    )
    print(f"'{texto[:45]}' → {r.choices[0].message.content.strip()}")

Output esperado:

'El prompt engineering es fundamental para si' → es
'Prompt engineering is fundamental for AI sys' → en
'Le prompt engineering est fondamental pour l' → fr
'OK' → und

Lección: CRISPE es una guía, no un template rígido. Para tareas simples, 3-4 componentes pueden ser suficientes. Para sistemas en producción con edge cases, úsalos todos.


Comparación: Con vs Sin CRISPE

import time

# Prompt sin CRISPE
PROMPT_SIN = "Clasifica este ticket de soporte."

# Prompt con CRISPE (condensado para comparación)
PROMPT_CON = """
Clasifica este ticket de soporte de empresa B2B.

Categorías (elige exactamente una):
- TECNICO: Error de software, fallo de funcionalidad
- FACTURACION: Cobros, facturas, planes, reembolsos
- GENERAL: Preguntas de uso, features, feedback

Responde SOLO con el nombre de la categoría en mayúsculas. Nada más.
"""

ticket = "Mi factura de este mes tiene un cargo incorrecto"

# Test de consistencia (5 llamadas cada uno)
resultados_sin = []
resultados_con = []

client = OpenAI()

for i in range(5):
    r_sin = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{PROMPT_SIN}\n\nTicket: {ticket}"}],
        temperature=0.5
    )
    resultados_sin.append(r_sin.choices[0].message.content.strip()[:50])
    
    r_con = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": PROMPT_CON},
            {"role": "user", "content": ticket}
        ],
        temperature=0,
        max_tokens=10
    )
    resultados_con.append(r_con.choices[0].message.content.strip())

print("Sin CRISPE (5 ejecuciones):")
for r in resultados_sin:
    print(f"  '{r}'")

print("\nCon CRISPE (5 ejecuciones):")
for r in resultados_con:
    print(f"  '{r}'")

Output típico:

Sin CRISPE (5 ejecuciones):
  'El ticket se clasifica como un problema de Facturación.'
  'Facturación'
  'Este ticket corresponde a un problema de facturación o cobros.'
  'FACTURACIÓN'
  'Facturacion (billing issue)'

Con CRISPE (5 ejecuciones):
  'FACTURACION'
  'FACTURACION'
  'FACTURACION'
  'FACTURACION'
  'FACTURACION'

Consistencia: 20% vs 100%. Parseabilidad por código: imposible vs trivial.


Conexión con el Proyecto

En el Prompt Analyzer (cápsula 08) usarás CRISPE para:

  • Identificar qué dimensiones tiene un prompt dado (presentes/ausentes/implícitas)
  • Detectar las dimensiones faltantes con sugerencias específicas por cada una
  • Puntuar la "completitud" del prompt (ej: 4/6 componentes CRISPE presentes)
  • Generar sugerencias concretas: "Falta Personality: añade restricciones de formato y guardrails"

En el Módulo 8 (Production Prompt System), cuando registres prompts en el registry, el metadata incluirá cuáles componentes CRISPE tiene cada versión — útil para evolución y debugging.


Troubleshooting

Problema 1: El modelo ignora el Role

Causa: El Role está enterrado al final del system prompt o es contradictorio con el Statement.

Solución:

# ✅ Role al inicio, claro y específico
Eres un abogado especializado en derecho laboral mexicano.
Solo respondes sobre temas laborales bajo ley mexicana.
[Resto del prompt]

# ❌ Role enterrado y débil
Analiza este contrato. Considera aspectos legales. El análisis debe ser profesional. Eres un abogado.

Problema 2: Demasiados componentes generan conflictos

Causa: CRISPE con texto muy largo o Personality que contradice Statement puede confundir al modelo.

Solución: Sé conciso en cada sección. Si el Insight supera 300 palabras, considera si toda esa información es necesaria. Para tareas simples, usa CRISPE parcial (C + S + E mínimo).

Problema 3: El Experiment (formato) no se cumple consistentemente

Causa: El formato no tiene ejemplos concretos o hay ambigüedad en el schema.

Solución:

# ✅ Formato con ejemplo explícito y contra-ejemplo
Formato de respuesta (solo esto, nada más):
{"sentimiento": "POSITIVO|NEGATIVO|NEUTRO", "confianza": 0.0-1.0}

Ejemplo correcto: {"sentimiento": "POSITIVO", "confianza": 0.92}
Ejemplo incorrecto: "El sentimiento es positivo con confianza alta"

Problema 4: El Insight hace el prompt demasiado largo y caro

Causa: Incluir demasiado contexto que el modelo puede inferir o que no es relevante para la tarea.

Solución: Principio MECE (Mutually Exclusive, Collectively Exhaustive): el Insight cubre todo lo necesario, sin solaparse con el Statement, sin repetir lo obvio. Mide tokens con tiktoken antes de deployar: len(tiktoken.encoding_for_model("gpt-4o-mini").encode(prompt)).

Problema 5: CRISPE completo para tarea simple — overkill

Causa: Aplicar los 6 componentes a tareas que no los necesitan aumenta latencia y costo sin beneficio.

Solución: Usa el criterio del tipo de tarea (tabla de CRISPE Simplificado). Para tareas de clasificación simple, C + I + S + E suele ser suficiente.


Ejercicios

Ejercicio 1: CRISPE para extractor de fechas (Fácil)

Aplica CRISPE completo para diseñar un prompt que extraiga fechas de textos y las devuelva en formato ISO YYYY-MM-DD.

Ver solución
from openai import OpenAI
import json

client = OpenAI()

SYSTEM = """
## Capacity
Extraer todas las fechas explícitas de un texto en lenguaje natural.

## Role
Eres un extractor de datos estructurados especializado en información temporal.

## Insight
- Solo fechas explícitas (no "ayer", "la semana pasada", ni fechas inferidas del contexto)
- Formato ISO: YYYY-MM-DD
- Para fechas ambiguas (ej: "03/04/2024"), usa la interpretación del idioma del texto
  (español: DD/MM/YYYY; inglés: MM/DD/YYYY)
- Año de referencia si no se especifica: 2025

## Statement
Del siguiente texto, identifica y extrae todas las fechas presentes.

## Personality
- Solo JSON válido, sin texto adicional
- Si no hay fechas, devuelve lista vacía (no null)

## Experiment
Formato: {"fechas": ["YYYY-MM-DD", ...]}

Ejemplos:
- "Reunión el 15 de marzo de 2025" → {"fechas": ["2025-03-15"]}
- "No hay fechas aquí" → {"fechas": []}
- "Vence el 03/04/2025 y el 15/04/2025" → {"fechas": ["2025-04-03", "2025-04-15"]}
"""

test_cases = [
    "La reunión es el 15 de marzo de 2025 a las 3pm",
    "El contrato vence el 31/12/2025 y la renovación debe hacerse antes del 01/12/2025",
    "No tenemos fechas confirmadas todavía",
    "Nos vemos mañana para revisar el proyecto"  # "mañana" es relativo → no se extrae
]

for texto in test_cases:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": texto}
        ],
        temperature=0
    )
    result = json.loads(r.choices[0].message.content)
    print(f"Input:  {texto}")
    print(f"Output: {result}\n")

Output esperado:

Input:  La reunión es el 15 de marzo de 2025 a las 3pm
Output: {'fechas': ['2025-03-15']}

Input:  El contrato vence el 31/12/2025 y la renovación debe hacerse antes del 01/12/2025
Output: {'fechas': ['2025-12-31', '2025-12-01']}

Input:  No tenemos fechas confirmadas todavía
Output: {'fechas': []}

Input:  Nos vemos mañana para revisar el proyecto
Output: {'fechas': []}

Explicación: El Insight especifica explícitamente que "mañana" no se extrae porque es una fecha relativa. Esto es exactamente el tipo de regla de dominio que va en Insight: no es inferible del Statement.


Ejercicio 2: Identificar dimensión faltante (Fácil)

Analiza este prompt. ¿Qué dimensiones de CRISPE faltan? Corrígelo para hacerlo production-ready.

"Resume este artículo en 3 puntos."
Ver solución

Componentes presentes:

  • Statement: ✅ "Resume en 3 puntos" (instrucción clara aunque incompleta)

Componentes faltantes:

  • Capacity: ¿Resumir cómo? ¿Extractivo, abstractivo, ejecutivo?
  • Role: ¿Desde qué perspectiva? ¿Editor técnico? ¿Generalista? Afecta profundidad y vocabulario
  • Insight: ¿Qué aspectos priorizar? ¿Longitud de cada punto? ¿Hay términos técnicos que mantener?
  • Personality: ¿Tono? ¿Longitud máxima por punto? ¿Idioma?
  • Experiment: ¿Formato exacto? ¿Bullets? ¿Numerados? ¿Solo texto? ¿JSON?

Prompt corregido:

SYSTEM = """
## Capacity
Resumir artículos técnicos en puntos clave accionables.

## Role
Eres un editor técnico que crea resúmenes ejecutivos para ingenieros de software.

## Insight
- Prioriza: hallazgos principales, metodología aplicable, conclusiones con impacto práctico
- Mantén términos técnicos específicos del dominio (no los simplifiques)
- Ignora introducción genérica y agradecimientos

## Statement
Resume el siguiente artículo en exactamente 3 puntos clave.

## Personality
- Conciso: máximo 30 palabras por punto
- Tono neutral y técnico
- Sin interpretaciones subjetivas
- Sin relleno introductorio ("Este artículo habla de...")

## Experiment
Formato:
1. [Primer punto clave — qué hace o demuestra]
2. [Segundo punto clave — cómo funciona o se implementa]
3. [Tercer punto clave — resultado, limitación, o implicación práctica]
"""

Mejora cuantificable: El prompt original puede producir bullets de cualquier longitud, en cualquier idioma, con o sin formato, con texto introductorio o no. El corregido tiene constraints que lo hacen determinístico.


Ejercicio 3: CRISPE completo para análisis de reseñas (Medio)

Diseña un prompt con CRISPE completo para esta tarea: analizar reseñas de restaurantes y extraer sentimiento (1-5 estrellas), aspectos mencionados (comida/servicio/ambiente/precio), y si el reviewer recomienda el lugar.

Ver solución
from openai import OpenAI
import json

client = OpenAI()

SYSTEM = """
## Capacity
Analizar reseñas de restaurantes y extraer información estructurada.

## Role
Eres un analista de experiencia de cliente especializado en el sector gastronómico.

## Insight
Aspectos y sus criterios:
- comida: calidad, sabor, presentación, temperatura, variedad del menú
- servicio: atención, velocidad, amabilidad, profesionalismo del staff
- ambiente: decoración, nivel de ruido, limpieza, comodidad, ubicación
- precio: relación calidad-precio, valor percibido, comparado con expectativas

Escala de estrellas:
1 = Muy negativo / No volvería nunca
2 = Negativo / Por debajo de expectativas
3 = Neutro / Aceptable pero sin destacar
4 = Positivo / Buena experiencia
5 = Muy positivo / Excelente, lo recomendaría activamente

## Statement
Analiza la siguiente reseña de restaurante y extrae los campos especificados.

## Personality
- Solo JSON válido, sin texto adicional
- Si un aspecto no se menciona en la reseña, usa null (no lo inventes)
- La recomendación se infiere del tono general y de si el reviewer indica que volvería

## Experiment
Formato exacto:
{
  "estrellas": 1-5,
  "aspectos": {
    "comida": "positivo|negativo|mixto|null",
    "servicio": "positivo|negativo|mixto|null",
    "ambiente": "positivo|negativo|mixto|null",
    "precio": "positivo|negativo|mixto|null"
  },
  "recomienda": true|false,
  "frase_clave": "cita directa de máximo 10 palabras que captura el sentimiento principal"
}
"""

reseñas = [
    "La comida estaba deliciosa pero el servicio fue lentísimo. Volveré solo por la pasta.",
    "Horrible. Esperamos 40 minutos, la comida llegó fría y los meseros muy groseros. Jamás regreso.",
    "Ambiente increíble para una cena romántica. Los precios son altos pero vale la pena cada peso."
]

for reseña in reseñas:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": reseña}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    result = json.loads(r.choices[0].message.content)
    print(f"Reseña: {reseña[:60]}...")
    print(f"Análisis: {json.dumps(result, ensure_ascii=False, indent=2)}\n")

Output esperado:

{
  "estrellas": 3,
  "aspectos": {"comida": "positivo", "servicio": "negativo", "ambiente": null, "precio": null},
  "recomienda": true,
  "frase_clave": "comida deliciosa pero servicio fue lentísimo"
}

Ejercicio 4: CRISPE simplificado con justificación (Medio)

Para una tarea simple (detectar idioma de un texto), implementa CRISPE parcial. Escribe la justificación de cuáles componentes omitiste.

Ver solución
# CRISPE Parcial para detección de idioma
# Componentes usados: Capacity, Statement, Experiment
# Omitidos (con justificación):
#   - Role: La tarea es tan específica que no hay ambigüedad de perspectiva posible
#   - Insight: No hay lógica de dominio compleja más allá del ISO 639-1
#   - Personality: Las restricciones de comportamiento están cubiertas en Experiment

SYSTEM = """
## Capacity
Detectar el idioma del texto.

## Statement
Identifica el idioma del siguiente texto.

## Experiment
Responde solo con el código ISO 639-1 del idioma (2 letras en minúsculas).
Ejemplos: es, en, fr, pt, de, it, zh, ja, ar
Si el texto es ambiguo o demasiado corto para determinar: "und" (undetermined)
"""

from openai import OpenAI
client = OpenAI()

textos = [
    "El prompt engineering es fundamental para sistemas de IA",
    "Prompt engineering is fundamental for AI systems",
    "Le prompt engineering est fondamental pour les systèmes d'IA",
    "OK",   # Ambiguo
    "Ciao"  # Podría ser italiano o español informal
]

for texto in textos:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": texto}
        ],
        temperature=0,
        max_tokens=5
    )
    print(f"'{texto[:45]}' → {r.choices[0].message.content.strip()}")

Lección: CRISPE es una guía, no un template rígido. Para tareas simples, 3 componentes pueden ser suficientes. Para sistemas en producción con edge cases múltiples, úsalos todos.


Ejercicio 5: Diagnóstico de fallo con CRISPE (Difícil)

Tienes este prompt que falla en ~30% de los casos: el modelo a veces incluye "El ticket es de tipo:" antes de la categoría. Diagnostica cuál componente de CRISPE es el problema y corrígelo.

SYSTEM = """
Clasifica este ticket de soporte:
- TECNICO: problemas con el software
- FACTURACION: temas de dinero y pagos
- GENERAL: todo lo demás

Responde con la categoría.
"""
Ver solución

Diagnóstico:

  • Fallo: El modelo a veces incluye texto introductorio antes de la categoría
  • Componente problemático: Personality (demasiado vago en restricciones de formato) y Experiment (no especifica formato exacto ni da ejemplos)

"Responde con la categoría" es un deseo, no un constraint. No especifica:

  • Si la categoría va sola o con texto
  • En qué caso (mayúsculas, minúsculas)
  • Sin puntuación al final o no

Corrección:

SYSTEM_CORREGIDO = """
## Capacity
Clasificar tickets de soporte en una categoría predefinida.

## Insight
Categorías:
- TECNICO: error de software, fallo de funcionalidad, crash, rendimiento degradado
- FACTURACION: cobros, facturas, cambios de plan, reembolsos, cargos incorrectos
- GENERAL: preguntas de uso, solicitudes de features, feedback, dudas generales

## Statement
Clasifica el siguiente ticket en exactamente UNA de las categorías.

## Personality
- Responde ÚNICAMENTE con el nombre de la categoría en mayúsculas
- Sin texto antes ni después
- Sin puntuación al final
- Sin "El ticket es:", "Categoría:", ni ningún prefijo

## Experiment
Formato: una sola palabra.
Ejemplos:
- "El login no funciona desde esta mañana" → TECNICO
- "Me cobran el doble este mes" → FACTURACION
- "¿Cómo exporto mis datos?" → GENERAL
"""

Clave: El Experiment con ejemplos explícitos y el Personality con "sin ningún prefijo" eliminan el 30% de fallos.


Resumen

En esta cápsula aprendiste:

  • CRISPE: Capacity (qué hace), Role (desde qué perspectiva), Insight (qué necesita saber del dominio), Statement (instrucción exacta), Personality (cómo se comporta), Experiment (formato + ejemplos)
  • Prompt como especificación: No estás "escribiendo instrucciones" — estás definiendo un contrato de comportamiento con requisitos funcionales y no funcionales
  • Constraints > Deseos: "Una sola palabra" es un constraint. "Sé conciso" es un deseo. Los constraints son verificables y producen prompts determinísticos
  • Iterative refinement: Draft → Test 10+ inputs → Diagnóstico por componente CRISPE → Refinar ese componente específico → Repetir
  • CRISPE parcial: Para tareas simples (clasificación directa, extracción obvia), C + S + E puede ser suficiente. Para sistemas complejos o en producción, úsalos todos
  • Conexión con el resto de la guía: CRISPE es el framework implícito en zero-shot (M2), few-shot (M2), structured output (M3), CoT (M4), y ReAct (M5)

Próxima cápsula: Comparación side-by-side entre prompt casual y prompt engineered con métricas cuantitativas — verás con datos la diferencia que hace CRISPE.


Recursos adicionales

  1. CRISPE Framework Overview — Descripción detallada del framework con más ejemplos de aplicación
  2. Prompt Engineering Guide — OpenAI — Técnicas complementarias de la perspectiva de OpenAI, incluyendo strategy patterns
  3. Anthropic Prompt Design — Enfoque de Anthropic para diseño efectivo, especialmente útil para entender Role y Personality
  4. Chain-of-Thought Prompting (Wei et al., 2022) — Paper original donde los componentes de CRISPE (especialmente CoT en Experiment) mejoran razonamiento
  5. Prompt Engineering for Developers (DeepLearning.AI) — Curso gratuito de Andrew Ng con perspectiva práctica sobre iterative refinement
  6. tiktoken — Librería de OpenAI para contar tokens en prompts, útil para optimizar Insight sin sobrepasar presupuesto de tokens