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 tarea | Verbos efectivos | Verbos a evitar |
|---|---|---|
| Extracción | Extrae, identifica, localiza, detecta | Encuentra, haz algo con |
| Clasificación | Clasifica, categoriza, etiqueta, asigna | Analiza, revisa, evalúa |
| Transformación | Traduce, convierte, reformula, adapta | Cambia, modifica |
| Generación | Genera, crea, escribe, produce | Haz, prepara |
| Análisis | Analiza específicamente X, evalúa criterio Y | Analiza (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
| Formato | Usar cuando | Ejemplo |
|---|---|---|
| JSON | Output procesado por código, estructuras complejas | {"key": "value", "list": [...]} |
| Lista numerada | Pasos secuenciales, ranking | 1. Primer paso\n2. Segundo paso |
| Bullets | Ítems sin orden específico | - Item 1\n- Item 2 |
| Tabla Markdown | Comparaciones, múltiples atributos | | Col1 | Col2 | |
| Texto plano | Lectura humana directa | Párrafos normales |
| String exacto | Clasificación, sí/no | Una 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ón | Mejor para | Evitar en |
|---|---|---|
| Instrucciones directas | Extracción, clasificación, transformación | Tareas abiertas creativas |
| Role-playing | Análisis, recomendaciones, tono específico | Tareas técnicas donde el rol no importa |
| Format specification | Output parseable por código | Respuestas conversacionales |
| Constraint-based | Control fino, edge cases, producción | Prototipado 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
- OpenAI Prompt Engineering — Tactics — Las tácticas oficiales de OpenAI se alinean exactamente con estos 4 patrones
- Anthropic Prompt Engineering — Guía específica de Claude con énfasis en role-playing y constraints
- Prompt Engineering Guide (DAIR.AI) — Zero-Shot — Comparativa de técnicas con benchmarks de accuracy en distintas tareas
- Learn Prompting: Structuring Prompts — Ejemplos de format specification con distintos formatos de output
- OpenAI JSON Mode Documentation — Cómo usar
response_formatpara garantizar JSON válido (base del Módulo 3) - OpenAI Cookbook: Techniques to improve reliability — Colección de técnicas reales con código para mejorar consistencia de prompts