Módulo 2: Zero-Shot y Few-Shot Prompting

2. Zero-Shot Prompting Patterns

Descripción de la cápsula

Zero-shot prompting no es "escribir cualquier cosa y esperar que funcione". Existen cuatro patrones probados que maximizan la consistencia y calidad de las respuestas sin añadir un solo ejemplo. En esta cápsula aprenderás cada patrón con código ejecutable, cuándo usarlo, cómo combinarlo con otros, y cómo diagnosticar cuándo falla.

Los cuatro patrones son: instrucciones directas (verbos de acción explícitos), role-playing (anclar perspectiva y tono), format specification (controlar el output), y constraint-based prompting (restricciones verificables). Cada uno resuelve un tipo diferente de problema de consistencia. Saber cuál aplicar — o cómo combinar varios — es la diferencia entre un prompt que funciona el 60% del tiempo y uno que funciona el 95%.

Por qué importa: Estos patrones son la base de todo lo demás en la guía. Los prompts de CoT en el Módulo 4 usan instrucciones directas + format specification. Los system prompts para agentes en el Módulo 8 usan role-playing + constraint-based. Dominarlos ahora te da vocabulario y herramientas para los módulos siguientes.


Patrón 1: Instrucciones Directas

Concepto

La instrucción es explícita, imperativa y sin ambigüedad. Responde "qué hacer" con verbos de acción concretos. El modelo no tiene que inferir la tarea — le dices exactamente qué ejecutar.

Principio: Cada grado de ambigüedad en la instrucción es un grado de variabilidad en el output.

Anatomía de una buena instrucción directa

[VERBO DE ACCIÓN] [OBJETO] [RESTRICCIONES] [FORMATO]

Ejemplo:
EXTRAE    [personas y organizaciones]  [solo las explícitas]  [en JSON]
CLASIFICA [el sentimiento]             [en POSITIVO/NEGATIVO] [una palabra]
RESUME    [el artículo]                [en 3 puntos]          [bullets]

Ejemplos progresivos

from openai import OpenAI
import json

client = OpenAI()

# Nivel 1: Básico — acción clara, sin restricciones adicionales
def ner_basico(texto: str) -> str:
    """Named Entity Recognition — versión básica."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "Extrae personas y organizaciones del texto."
            },
            {"role": "user", "content": texto}
        ],
        temperature=0
    )
    return response.choices[0].message.content

# Nivel 2: Con formato — acción + formato de salida
def ner_con_formato(texto: str) -> dict:
    """NER con output JSON estructurado."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": """
Extrae personas y organizaciones del texto.
Devuelve JSON: {"personas": ["nombre1", ...], "organizaciones": ["org1", ...]}
Si no hay, usa listas vacías. Solo JSON válido, sin texto adicional.
"""
            },
            {"role": "user", "content": texto}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

# Nivel 3: Con restricciones explícitas — acción + formato + reglas
def ner_completo(texto: str) -> dict:
    """NER con reglas de dominio y manejo de edge cases."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": """
Extrae personas y organizaciones del texto.

REGLAS:
- Solo entidades que aparezcan EXPLÍCITAMENTE (no las inferas del contexto)
- Nombres completos cuando estén disponibles (no apodos)
- Organizaciones incluyen empresas, ONGs, gobiernos, instituciones académicas
- Si un nombre es ambiguo (persona u organización), incluye en ambos con nota [ambiguo]
- Si el texto no tiene entidades: {"personas": [], "organizaciones": []}

FORMATO: JSON con keys exactas: personas, organizaciones
SOLO JSON VÁLIDO. Sin explicaciones.
"""
            },
            {"role": "user", "content": texto}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

# Test con texto real
texto = "María García, directora de Google España, se reunió con el CEO de Telefónica en Madrid. El evento fue organizado por el MIT Media Lab."

print("Básico:")
print(ner_basico(texto))
print("\nCon formato:")
print(ner_con_formato(texto))
print("\nCompleto:")
print(json.dumps(ner_completo(texto), ensure_ascii=False, indent=2))

Output esperado:

Básico:
Personas: María García
Organizaciones: Google España, Telefónica, MIT Media Lab

Con formato:
{'personas': ['María García'], 'organizaciones': ['Google España', 'Telefónica', 'MIT Media Lab']}

Completo:
{
  "personas": ["María García"],
  "organizaciones": ["Google España", "Telefónica", "MIT Media Lab"]
}

Diferencia entre niveles: El básico funciona pero es imparseable por código. El con formato ya es estructurado. El completo maneja edge cases que los otros ignoran.

Verbos de acción más efectivos para instrucciones directas

Tipo de tareaVerbos efectivosVerbos a evitar
ExtracciónExtrae, identifica, localiza, detectaEncuentra, haz algo con
ClasificaciónClasifica, categoriza, etiqueta, asignaAnaliza, revisa, evalúa
TransformaciónTraduce, convierte, reformula, adaptaCambia, modifica
GeneraciónGenera, crea, escribe, produceHaz, prepara
AnálisisAnaliza específicamente X, evalúa criterio YAnaliza (sin criterio)

Patrón 2: Role-Playing

Concepto

Asignas un rol al modelo ("Eres un experto en X") para anclar tono, nivel de detalle, perspectiva y suposiciones implícitas. Un rol específico crea un modelo mental consistente del "tipo de respuesta" esperado.

Por qué funciona: El pre-entrenamiento incluye texto escrito por expertos de distintos dominios. Al decir "Eres un abogado especializado en contratos", el modelo accede a patrones de escritura legal que aprendió durante el entrenamiento.

Impacto del role en el output

from openai import OpenAI

client = OpenAI()

pregunta = "¿Cuáles son los riesgos de usar async/await en Python?"

# El mismo statement, tres roles diferentes
roles = {
    "senior_engineer": "Eres un ingeniero senior de Python con 10 años en sistemas distribuidos.",
    "teacher_beginners": "Eres un profesor que explica programación a estudiantes sin experiencia.",
    "technical_writer": "Eres un redactor técnico que crea documentación para APIs."
}

print(f"Pregunta: {pregunta}\n")
print("=" * 60)

for nombre, role in roles.items():
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": role},
            {"role": "user", "content": pregunta}
        ],
        temperature=0,
        max_tokens=150
    )
    print(f"\n[{nombre}]")
    print(response.choices[0].message.content[:300])
    print("...")

Output típico:

[senior_engineer]
Los riesgos principales son: (1) Event loop blocking — si tienes una operación CPU-bound
en un coroutine, bloqueas el event loop entero. Usa asyncio.run_in_executor() para CPU-bound.
(2) Error handling complejo — las excepciones en tasks concurrentes requieren careful
handling con asyncio.gather(return_exceptions=True)...

[teacher_beginners]
¡Buena pregunta! async/await es útil pero puede confundirse con threads (que es diferente).
El riesgo principal es este: si accidentalmente haces algo que "se tarda mucho" dentro
de una función async, otros procesos esperarán. Es como si en una cafetería, el barista
se queda dormido mientras hace tu café — todos esperan...

[technical_writer]
## Riesgos de async/await en Python

**Risk 1: CPU-bound blocking**
*Issue:* Blocking operations inside coroutines block the event loop.
*Mitigation:* Use `asyncio.run_in_executor()` for CPU-intensive tasks...

Misma pregunta, tres audiencias, tres estilos completamente distintos. Sin role-playing, el modelo usa su default (generalmente el estilo de technical_writer o similar).

Cómo construir un role efectivo

Un role efectivo tiene tres componentes:

# Componentes de un role effectivo
ROLE_TEMPLATE = """
Eres {quien}                    # Identidad y expertise
especializado en {dominio}      # Área de especialización  
con experiencia en {contexto}   # Contexto específico relevante
"""

# Ejemplos bien construidos
ROLES_EFECTIVOS = {
    "analista_financiero": "Eres un analista financiero especializado en startups SaaS, con experiencia evaluando métricas de crecimiento y unit economics.",
    
    "agente_soporte": "Eres un agente de soporte técnico de nivel 2 para software enterprise. Tus usuarios son administradores de sistemas con conocimiento técnico.",
    
    "editor_tecnico": "Eres un editor técnico que escribe para desarrolladores backend. Tu estilo es conciso, preciso y orientado a ejemplos de código.",
    
    "clasificador": "Eres un sistema de clasificación automática para {dominio}. Solo produces la categoría, sin explicaciones.",
}

# Role anti-patterns
ROLES_INEFECTIVOS = {
    "vago": "Eres un asistente útil.",  # No define nada específico
    "contradictorio": "Eres un experto técnico que explica para principiantes.",  # Ambiguo
    "sin_dominio": "Eres un profesional.",  # Sin especialización
}

Role + Instrucción directa: combinación frecuente

from openai import OpenAI
import json

client = OpenAI()

def analizar_reseña(texto: str) -> dict:
    """Role-playing + instrucción directa + format specification combinados."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": """
Eres un analista de experiencia de cliente especializado en e-commerce.
Tu función es extraer insights accionables de reseñas de productos.

EXTRAE del texto:
1. Sentimiento: POSITIVO, NEGATIVO, MIXTO
2. Aspecto_principal: el tema más importante mencionado (producto/servicio/envío/precio)
3. Acción_sugerida: qué debería hacer la empresa (1 oración)

FORMATO: {"sentimiento": "...", "aspecto": "...", "accion": "..."}
Solo JSON válido.
"""
            },
            {"role": "user", "content": texto}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

reseñas = [
    "El producto es excelente pero tardó 3 semanas en llegar. Decepcionado con el envío.",
    "Calidad increíble y precio justo. El servicio al cliente fue muy amable. Volvería a comprar.",
    "No funciona como se describe. Devuelto."
]

for r in reseñas:
    result = analizar_reseña(r)
    print(f"Reseña: {r[:60]}...")
    print(f"  Análisis: {result}\n")

Output:

Reseña: El producto es excelente pero tardó 3 semanas en llegar...
  Análisis: {'sentimiento': 'MIXTO', 'aspecto': 'envío', 'accion': 'Mejorar tiempos de envío y actualizar estimaciones al cliente'}

Reseña: Calidad increíble y precio justo. El servicio al cliente f...
  Análisis: {'sentimiento': 'POSITIVO', 'aspecto': 'producto', 'accion': 'Destacar calidad y servicio en campañas de marketing'}

Reseña: No funciona como se describe. Devuelto....
  Análisis: {'sentimiento': 'NEGATIVO', 'aspecto': 'producto', 'accion': 'Revisar descripción del producto y verificar control de calidad'}

Patrón 3: Format Specification

Concepto

Especificas explícitamente el formato de salida: JSON, XML, Markdown, lista numerada, tabla, etc. Sin format specification, el modelo elige cómo responder — lo que lleva a variabilidad de formato entre llamadas.

Regla: Si tu código necesita procesar el output, debes especificar el formato. El modelo no adivina que necesitas JSON a menos que lo pidas.

Formatos y cuándo usar cada uno

FormatoUsar cuandoEjemplo
JSONOutput procesado por código, estructuras complejas{"key": "value", "list": [...]}
Lista numeradaPasos secuenciales, ranking1. Primer paso\n2. Segundo paso
BulletsÍtems sin orden específico- Item 1\n- Item 2
Tabla MarkdownComparaciones, múltiples atributos| Col1 | Col2 |
Texto planoLectura humana directaPárrafos normales
String exactoClasificación, sí/noUna sola palabra

Progresión de format specification

from openai import OpenAI
import json

client = OpenAI()

articulo = """
Los modelos de lenguaje de gran escala (LLMs) han revolucionado el procesamiento del lenguaje natural.
GPT-4 y Claude 3 lideran el mercado con capacidades de razonamiento avanzadas.
Sin embargo, los costos de API y las preocupaciones de privacidad son barreras para la adopción masiva.
El fine-tuning y los prompts cuidadosamente diseñados pueden mejorar significativamente los resultados.
"""

# Nivel 1: Bullets simples
def resumir_bullets(texto: str) -> str:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"Resume en 3 bullets:\n{texto}"
        }],
        temperature=0
    )
    return r.choices[0].message.content

# Nivel 2: JSON con schema
def resumir_json(texto: str) -> dict:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": """
Resume el texto con exactamente este JSON:
{
  "puntos_clave": ["punto1", "punto2", "punto3"],
  "palabras_clave": ["kw1", "kw2", "kw3"],
  "sentimiento_general": "positivo|negativo|neutro"
}
Solo JSON válido.
"""
            },
            {"role": "user", "content": texto}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(r.choices[0].message.content)

# Nivel 3: Tabla Markdown para comparaciones
def comparar_en_tabla(items: list[str], criterios: list[str]) -> str:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": f"""
Compara los items en base a los criterios dados.
FORMATO: tabla Markdown con columnas: Item | {" | ".join(criterios)}
Solo la tabla, sin texto adicional.
"""
            },
            {
                "role": "user",
                "content": f"Compara: {', '.join(items)}"
            }
        ],
        temperature=0
    )
    return r.choices[0].message.content

# Nivel 4: Output exacto para clasificación
def clasificar_string_exacto(texto: str, categorias: list[str]) -> str:
    cats = ", ".join(categorias)
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": f"Clasifica en una de: {cats}. Solo el nombre exacto de la categoría. Nada más."
            },
            {"role": "user", "content": texto}
        ],
        temperature=0,
        max_tokens=10
    )
    return r.choices[0].message.content.strip()

# Tests
print("=== Bullets ===")
print(resumir_bullets(articulo))

print("\n=== JSON ===")
print(json.dumps(resumir_json(articulo), ensure_ascii=False, indent=2))

print("\n=== Tabla ===")
print(comparar_en_tabla(
    ["GPT-4o", "Claude 3.5", "Gemini 1.5"],
    ["Velocidad", "Calidad razonamiento", "Precio"]
))

print("\n=== String exacto ===")
print(clasificar_string_exacto(
    "Error al conectar con la base de datos",
    ["TECNICO", "FACTURACION", "GENERAL"]
))

Output esperado:

=== Bullets ===
• Los LLMs han transformado el procesamiento del lenguaje natural
• GPT-4 y Claude 3 lideran con capacidades avanzadas de razonamiento
• Costos y privacidad son barreras para adopción masiva

=== JSON ===
{
  "puntos_clave": [
    "Los LLMs han revolucionado el NLP",
    "GPT-4 y Claude 3 lideran el mercado con costos como barrera",
    "Fine-tuning y prompts mejoran resultados significativamente"
  ],
  "palabras_clave": ["LLMs", "GPT-4", "fine-tuning"],
  "sentimiento_general": "neutro"
}

=== Tabla ===
| Item | Velocidad | Calidad razonamiento | Precio |
|------|-----------|---------------------|--------|
| GPT-4o | Alta | Excelente | Alto |
| Claude 3.5 | Alta | Excelente | Medio |
| Gemini 1.5 | Alta | Muy buena | Bajo |

=== String exacto ===
TECNICO

Patrón 4: Constraint-Based Prompting

Concepto

Añades restricciones explícitas: longitud máxima, idioma, qué evitar, valores permitidos, manejo de edge cases. Los constraints son verificables — puedes comprobar programáticamente si el output los cumple.

La diferencia entre deseos y constraints:

  • Deseo: "Que sea conciso" → El modelo decide qué es conciso
  • Constraint: "Máximo 50 palabras" → Verificable: len(output.split()) <= 50

Tipos de constraints y ejemplos

from openai import OpenAI

client = OpenAI()

# Constraint de longitud
prompt_longitud = """
Traduce al inglés (US English).
CONSTRAINTS:
- Máximo 100 palabras en la traducción
- Términos técnicos (API, endpoint, JSON) no se traducen
- Sin notas del traductor ni explicaciones
"""

# Constraint de valores permitidos
prompt_valores = """
Clasifica la urgencia del ticket.
CONSTRAINTS:
- Solo estos valores: ALTA, MEDIA, BAJA, CRÍTICA
- Si no puedes determinar la urgencia: MEDIA (default)
- Sin texto adicional
"""

# Constraint de edge cases
prompt_edge_cases = """
Extrae el email del texto.
CONSTRAINTS:
- Si hay múltiples emails: devuelve todos en lista
- Si no hay email: devuelve lista vacía []
- Solo emails que aparezcan explícitamente, no los inferidos
- Formato: {"emails": ["email1@...", "email2@..."]}
"""

# Constraint de comportamiento
prompt_comportamiento = """
Responde a la pregunta del usuario.
CONSTRAINTS:
- Responde SOLO basándote en el contexto proporcionado
- Si la respuesta no está en el contexto: "No tengo esa información en el contexto"
- Sin inventar datos, estadísticas, o afirmaciones no respaldadas por el contexto
- Máximo 3 oraciones
"""

# Ejemplo completo con múltiples constraints
def traducir_documentacion(texto: str, idioma_destino: str = "inglés") -> str:
    """
    Traduce documentación técnica con constraints estrictos.
    """
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": f"""
Eres un traductor técnico especializado en documentación de software.
Traduce al {idioma_destino}.

CONSTRAINTS OBLIGATORIOS:
1. Términos técnicos: NO traducir (API, endpoint, JSON, HTTP, REST, SDK, CLI, etc.)
2. Código: NO modificar — todo lo que esté en backticks o bloques de código permanece igual
3. Nombres de funciones/variables: NO traducir
4. Longitud: mantén una longitud similar al original (±20%)
5. Tono: técnico y formal
6. Si hay ambigüedad de traducción: usa la más común en documentación oficial
"""
            },
            {"role": "user", "content": texto}
        ],
        temperature=0
    )
    return r.choices[0].message.content

texto_tecnico = """
Crea un endpoint REST usando FastAPI. El endpoint debe aceptar un JSON body con el campo `user_id` 
y devolver los datos del usuario desde la base de datos. Usa `async/await` para las consultas.
"""

traduccion = traducir_documentacion(texto_tecnico)
print(traduccion)

Output esperado:

Create a REST endpoint using FastAPI. The endpoint should accept a JSON body with the `user_id` 
field and return the user's data from the database. Use `async/await` for queries.

Nota: API, FastAPI, JSON, user_id, async/await permanecen sin traducir — los constraints funcionan.


Combinando los 4 Patrones

En prompts de producción, los cuatro patrones trabajan juntos:

from openai import OpenAI
import json

client = OpenAI()

# Ejemplo: system prompt completo para clasificador de soporte
SYSTEM_CLASIFICADOR_COMPLETO = """
## Role (Role-Playing)
Eres un clasificador automático de tickets de soporte técnico para empresa SaaS B2B.

## Task (Instrucción Directa)
Clasifica cada ticket en UNA categoría y asigna una prioridad.

## Categories (Instrucción Directa + Format)
Categorías disponibles:
- ACCESO: problemas para entrar al sistema
- ERROR: crashes, errores, funcionalidad rota
- RENDIMIENTO: lentitud, timeouts
- INTEGRACION: conexión con APIs o servicios externos
- FACTURACION: cobros, facturas, planes
- DATOS: exportación, importación, pérdida de datos
- OTRO: cualquier cosa que no encaje

Prioridades: CRITICA (sistema caído), ALTA (funcionalidad bloqueada), MEDIA (inconveniente), BAJA (pregunta)

## Constraints (Constraint-Based)
- Si hay duda entre dos categorías: elige la más específica
- Prioridad CRITICA: solo si el usuario indica que el sistema está caído para múltiples usuarios
- Si el ticket es vago o incompleto: categoría OTRO, prioridad MEDIA
- Si menciona fecha límite de negocio: sube prioridad un nivel

## Output Format (Format Specification)
JSON exacto:
{
  "categoria": "CATEGORIA_EN_MAYUSCULAS",
  "prioridad": "CRITICA|ALTA|MEDIA|BAJA",
  "razon": "máximo 10 palabras"
}
Solo JSON. Sin texto adicional.
"""

def clasificar_ticket(texto: str) -> dict:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM_CLASIFICADOR_COMPLETO},
            {"role": "user", "content": texto}
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    return json.loads(r.choices[0].message.content)

tickets = [
    "El dashboard no carga para nadie desde hace 2 horas",
    "¿Cómo exporto los reportes a PDF?",
    "Error 403 al intentar conectar con la API de Salesforce",
    "Necesito el informe de ventas para mañana — reunión con directivos",
]

for ticket in tickets:
    result = clasificar_ticket(ticket)
    print(f"Ticket: {ticket[:60]}")
    print(f"  {result}\n")

Output:

Ticket: El dashboard no carga para nadie desde hace 2 horas
  {'categoria': 'RENDIMIENTO', 'prioridad': 'CRITICA', 'razon': 'sistema caído para múltiples usuarios'}

Ticket: ¿Cómo exporto los reportes a PDF?
  {'categoria': 'DATOS', 'prioridad': 'BAJA', 'razon': 'consulta de uso de funcionalidad'}

Ticket: Error 403 al intentar conectar con la API de Salesforce
  {'categoria': 'INTEGRACION', 'prioridad': 'ALTA', 'razon': 'integración bloqueada por error de autenticación'}

Ticket: Necesito el informe de ventas para mañana — reunión con directivos
  {'categoria': 'DATOS', 'prioridad': 'ALTA', 'razon': 'fecha límite de negocio detectada, prioridad subida'}

Comparación de Patrones por Caso de Uso

PatrónMejor paraEvitar en
Instrucciones directasExtracción, clasificación, transformaciónTareas abiertas creativas
Role-playingAnálisis, recomendaciones, tono específicoTareas técnicas donde el rol no importa
Format specificationOutput parseable por códigoRespuestas conversacionales
Constraint-basedControl fino, edge cases, producciónPrototipado rápido

Regla de combinación: En producción, usa los cuatro. En prototipado, empieza con instrucciones directas + format specification y añade los demás según necesidad.


Conexión con el Proyecto

En el Few-Shot Classification System (cápsula 08), el componente de zero-shot usa:

  • Instrucciones directas: "Clasifica en una de: [categorías]. Solo la categoría."
  • Format specification: Output como string exacto para parsear programáticamente
  • Constraint-based: "Si no puedes clasificar con certeza, devuelve OTRO"
  • Role-playing: "Eres un clasificador automático de [dominio]"

El sistema compara este zero-shot contra few-shot y mide cuándo cada patrón es suficiente.


Troubleshooting

Problema 1: El modelo ignora el formato especificado

Causa: El formato está enterrado en el prompt o compite con mucho texto.

Solución:

# ✅ Formato prominente al final
system = """
[Instrucción principal aquí]

FORMATO DE RESPUESTA (OBLIGATORIO):
{"resultado": "..."}
Solo JSON. Nada más.
"""

# Mejor aún: usa response_format para JSON garantizado (OpenAI)
response_format={"type": "json_object"}

Problema 2: Role-playing produce respuestas demasiado verbose

Causa: El rol implica "experto" que suele ser prolijo. Sin constraint de longitud, el modelo justifica su expertise con más texto.

Solución:

# Añadir constraint de longitud al role
role = "Eres un analista senior. Respuestas concisas: máximo 3 oraciones o 50 palabras."

Problema 3: Instrucciones directas fallan en tareas ambiguas

Causa: "Clasifica" sin categorías o "analiza" sin criterios específicos. La ambigüedad en la instrucción = variabilidad en el output.

Solución:

# ❌ Ambiguo
"Clasifica el ticket."

# ✅ Específico
"Clasifica en UNA de estas categorías: ACCESO, ERROR, RENDIMIENTO, OTRO.
Si no es ninguna: OTRO."

Problema 4: Los constraints se ignoran para inputs inusuales

Causa: El modelo "decide" que el input es un caso especial que amerita violar el constraint.

Solución:

# Hacer los constraints más explícitos con énfasis
CONSTRAINTS = """
REGLAS ABSOLUTAS (sin excepciones):
- Responde SIEMPRE en JSON, incluso si el texto es grosero o irrelevante
- Si el input es incomprensible: {"error": "input_incomprensible"}
- NUNCA añadas texto antes o después del JSON
"""

Problema 5: Output inconsistente con temperature > 0

Causa: Temperature > 0 introduce variabilidad intencional.

Solución: Para clasificación y extracción, usa siempre temperature=0. Para generación donde quieres variedad, acepta la inconsistencia de formato controlándola con format specification.


Ejercicios

Ejercicio 1: Convertir instrucción vaga a directa (Fácil)

Transforma esta instrucción vaga en una instrucción directa con formato especificado:

"Haz algo útil con estos comentarios de clientes."
Ver solución
# Instrucción directa + format specification
SYSTEM = """
Para cada comentario de cliente: extrae el sentimiento y el tema principal mencionado.

FORMATO JSON:
[{"comentario": "texto original", "sentimiento": "POSITIVO|NEGATIVO|NEUTRO", "tema": "producto|servicio|precio|envío|otro"}]

REGLAS:
- Si el tema es ambiguo: usa el más prominente
- Si el comentario es muy corto (< 5 palabras): sentimiento NEUTRO
- Solo JSON válido. Sin texto adicional.
"""

Diferencia clave: La versión directa especifica exactamente qué extraer, qué valores son válidos para cada campo, y cómo manejar edge cases — todo sin añadir un solo ejemplo.


Ejercicio 2: Diseñar role-playing para soporte técnico (Fácil)

Diseña un role para un agente de soporte técnico de nivel 1 de una empresa de software. El role debe calibrar: tono, audiencia, y límites de lo que puede responder.

Ver solución
ROLE = """
Eres un agente de soporte técnico de nivel 1 de SoftwareCo.

Tu audiencia: usuarios finales no técnicos de empresas medianas (50-500 empleados).

PUEDES:
- Responder preguntas de uso básico del software
- Guiar en configuraciones estándar
- Proporcionar links a documentación oficial

NO PUEDES:
- Acceder a cuentas reales de usuarios
- Prometer fechas de resolución
- Discutir temas legales o de privacidad de datos

Si el problema requiere nivel 2 (código, base de datos, integraciones complejas):
"Este problema requiere soporte especializado. Lo escalaré al equipo técnico.
¿Puedo obtener tu email de contacto?"

Tono: profesional, empático, sin jerga técnica.
"""

Este role define identidad, audiencia, capacidades, limitaciones, y comportamiento de escalado — todos los componentes de un role de producción.


Ejercicio 3: Añadir constraints a un prompt de extracción (Medio)

Dado este prompt básico, añade al menos 4 constraints que lo hagan production-ready:

"Extrae el email del texto."
Ver solución
SYSTEM = """
Extrae el email del texto.

CONSTRAINTS:
1. Si hay múltiples emails: devuelve todos en lista
2. Si no hay email: devuelve lista vacía []
3. Solo emails que aparezcan EXPLÍCITAMENTE (no los inferidos del contexto)
4. Formato obligatorio: {"emails": ["email@dominio.com", ...]}
5. Solo el dominio es case-insensitive; el local part (antes de @) mantiene el case original
6. Si el texto está vacío o es solo whitespace: {"emails": [], "error": "input_vacio"}

SOLO JSON. Sin texto adicional.
"""

Los 4 constraints mínimos resuelven: múltiples emails, ausencia de email, emails implícitos, y el formato exacto. Los constraints 5 y 6 son mejoras adicionales para edge cases de producción.


Ejercicio 4: Implementar clasificador con los 4 patrones (Medio)

Implementa una función Python que use los cuatro patrones (role-playing, instrucción directa, format specification, constraint-based) para clasificar reseñas de apps móviles en: BUG_REPORT, FEATURE_REQUEST, GENERAL_FEEDBACK.

Ver solución
import json
from openai import OpenAI

client = OpenAI()

SYSTEM = """
## Role
Eres un clasificador automático de reseñas de apps móviles para el equipo de producto.

## Instrucción
Clasifica cada reseña en UNA de las categorías según su contenido principal.

## Categorías
- BUG_REPORT: El usuario reporta un error, crash, mal funcionamiento o comportamiento inesperado
- FEATURE_REQUEST: El usuario pide una función nueva o mejora a una existente
- GENERAL_FEEDBACK: Opinión general, satisfacción/insatisfacción sin reportar error ni pedir feature

## Constraints
- Si la reseña tiene bug + feature: clasifica como BUG_REPORT (más urgente)
- Si la reseña es muy corta (< 5 palabras): GENERAL_FEEDBACK
- Si no puedes clasificar con certeza: GENERAL_FEEDBACK
- Ignora el idioma (puede ser cualquiera)

## Formato
{"categoria": "BUG_REPORT|FEATURE_REQUEST|GENERAL_FEEDBACK", "confianza": 0.0-1.0}
Solo JSON válido.
"""

def clasificar_reseña(reseña: str) -> dict:
    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"}
    )
    return json.loads(r.choices[0].message.content)

reseñas = [
    "La app crashea cada vez que intento adjuntar una foto",
    "Sería genial poder exportar los datos a Excel directamente",
    "5 estrellas, la mejor app que he usado",
    "Excelente app pero necesita modo oscuro",  # Mixto: feedback + feature
    "No"  # Muy corto
]

for r in reseñas:
    result = clasificar_reseña(r)
    print(f"Reseña: {r}")
    print(f"  {result}\n")

Nota pedagógica: Este ejercicio integra los 4 patrones. El role ancla la perspectiva (equipo de producto). La instrucción directa define qué hacer. Las categorías con descripción reducen ambigüedad. Los constraints manejan casos edge. El formato garantiza output parseable.


Ejercicio 5: Diagnóstico de prompt fallido (Difícil)

Este prompt falla ~40% de las veces — el modelo a veces incluye explicaciones, usa minúsculas, o da múltiples categorías. Identifica cuál(es) patrón(es) le falta(n) y corrígelo.

"¿Cuál es el sentimiento de este comentario: positivo, negativo o neutro?"
Ver solución

Diagnóstico:

  • Falta Format specification: la pregunta permite respuestas como "Es positivo", "Positivo (aunque con matiz...)", "podría ser positivo o neutro"
  • Falta Constraint-based: sin "una sola palabra", "sin explicaciones", "sin puntuación"
  • Falta Role (menor): sin role, el modelo puede ser conversacional

Prompt corregido:

SYSTEM = """
Clasifica el sentimiento del comentario.
Responde ÚNICAMENTE con una de estas palabras exactas: POSITIVO, NEGATIVO, NEUTRO.
Sin puntuación, sin explicaciones, sin texto adicional.
Para sentimientos mixtos: usa el dominante.
"""

Por qué falla el original: Es una pregunta abierta, no una instrucción. Las preguntas invitan respuestas conversacionales. Las instrucciones directas en imperativo ("clasifica", "responde con") producen outputs más controlados.


Resumen

En esta cápsula aprendiste:

  • Instrucciones directas: Verbos de acción en imperativo (extrae, clasifica, resume), restricciones explícitas. Elimina ambigüedad = elimina variabilidad
  • Role-playing: "Eres un X especializado en Y" ancla tono, vocabulario y perspectiva. Específico > genérico
  • Format specification: Define el formato de output explícitamente. JSON mode para output parseable. El formato va al final o como constraint prominente
  • Constraint-based: Restricciones verificables (longitud, valores permitidos, edge cases). La diferencia entre deseos y constraints: los constraints son verificables programáticamente
  • Combinación: En producción, los 4 patrones trabajan juntos. El system prompt del Módulo 8 usa los cuatro simultáneamente

Próxima cápsula: Few-shot prompting — selección de ejemplos: cuántos usar, cómo elegirlos, en qué orden ponerlos, y cuándo un ejemplo hace más daño que bien.


Recursos adicionales

  1. OpenAI Prompt Engineering — Tactics — Las tácticas oficiales de OpenAI se alinean exactamente con estos 4 patrones
  2. Anthropic Prompt Engineering — Guía específica de Claude con énfasis en role-playing y constraints
  3. Prompt Engineering Guide (DAIR.AI) — Zero-Shot — Comparativa de técnicas con benchmarks de accuracy en distintas tareas
  4. Learn Prompting: Structuring Prompts — Ejemplos de format specification con distintos formatos de output
  5. OpenAI JSON Mode Documentation — Cómo usar response_format para garantizar JSON válido (base del Módulo 3)
  6. OpenAI Cookbook: Techniques to improve reliability — Colección de técnicas reales con código para mejorar consistencia de prompts