Módulo 5: ReAct, Self-Consistency y Patrones Avanzados

5. Meta-Prompting y Self-Refine

Descripción

Las técnicas anteriores (ReAct, Self-Consistency, ToT) se enfocan en cómo el modelo razona para resolver un problema. Meta-prompting y Self-Refine abordan otro problema fundamental: ¿cómo mejoramos los prompts y las respuestas de forma sistemática?

Meta-prompting usa un LLM para generar, optimizar o evaluar prompts para otra tarea. Es como contratar a un experto en comunicación para que escriba las instrucciones de tu sistema.

Self-Refine (Madaan et al., 2023) implementa un ciclo iterativo: el modelo genera una respuesta, luego la critica desde la perspectiva del problema, y finalmente la mejora. Es el equivalente de que un estudiante escriba un ensayo, lo revise con ojos críticos, y lo reescriba con esa perspectiva.


Meta-Prompting: El LLM que Escribe Prompts

La Intuición

Escribir buenos prompts es difícil. Requiere entender:

  • Cómo se comporta el modelo ante ciertos patrones de lenguaje
  • Qué nivel de detalle es necesario
  • Qué ejemplos son más útiles
  • Cómo formular la salida esperada

¿Y si en lugar de hacerlo manualmente, le pedimos al propio LLM que diseñe el prompt?

Implementación Base

from openai import OpenAI
import json

client = OpenAI()

def meta_prompt_generator(
    tarea: str,
    contexto: str = "",
    formato_salida: str = "",
    restricciones: list[str] = None,
    ejemplos: list[dict] = None
) -> str:
    """
    Genera un prompt optimizado para una tarea específica.
    
    Args:
        tarea: Descripción de la tarea (ej: "clasificar sentimiento de tweets")
        contexto: Contexto adicional sobre el dominio o usuarios
        formato_salida: Cómo debe formatearse la salida
        restricciones: Lista de restricciones o requisitos
        ejemplos: Ejemplos de input/output deseados
    
    Returns:
        Un prompt optimizado para la tarea
    """
    restricciones_str = "\n".join([f"- {r}" for r in (restricciones or [])])
    ejemplos_str = ""
    if ejemplos:
        ejemplos_str = "\nEjemplos de input/output deseados:\n"
        for ej in ejemplos:
            ejemplos_str += f"Input: {ej.get('input', '')}\nOutput: {ej.get('output', '')}\n\n"
    
    meta_prompt = f"""Eres un experto en prompt engineering. Tu tarea es crear el prompt ÓPTIMO para el siguiente caso de uso.

TAREA A RESOLVER: {tarea}

CONTEXTO ADICIONAL:
{contexto if contexto else "Ninguno"}

FORMATO DE SALIDA ESPERADO:
{formato_salida if formato_salida else "No especificado"}

RESTRICCIONES/REQUISITOS:
{restricciones_str if restricciones_str else "Ninguna"}
{ejemplos_str}

Crea un prompt que:
1. Sea claro y específico sobre la tarea
2. Incluya formato de salida explícito
3. Anticipe y maneje edge cases
4. Use el nivel correcto de detalle (ni muy vago ni sobrespecificado)
5. Si aplica, incluya 1-2 ejemplos (few-shot)

IMPORTANTE: Devuelve SOLO el prompt, sin explicaciones ni metadatos adicionales.
El prompt debe estar listo para ser usado directamente."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": meta_prompt}],
        temperature=0.3,
        max_tokens=800
    )
    
    return response.choices[0].message.content.strip()


# Ejemplo de uso:
if __name__ == "__main__":
    prompt_generado = meta_prompt_generator(
        tarea="Clasificar el sentimiento de reviews de restaurantes",
        contexto="Los reviews pueden mezclar aspectos positivos y negativos. Son en español.",
        formato_salida="JSON con: sentiment (POSITIVO/NEGATIVO/MIXTO/NEUTRAL), score (0-1), aspecto_principal",
        restricciones=[
            "Considerar tanto comida como servicio y ambiente",
            "Detectar sarcasmo e ironía",
            "Manejar reviews muy cortos (1-2 palabras)"
        ],
        ejemplos=[
            {"input": "La comida estaba buenísima pero tardaron 45 minutos en traerla.", 
             "output": '{"sentiment": "MIXTO", "score": 0.5, "aspecto_principal": "comida+servicio"}'},
            {"input": "Normalito.",
             "output": '{"sentiment": "NEUTRAL", "score": 0.5, "aspecto_principal": "general"}'}
        ]
    )
    
    print("Prompt generado por meta-prompting:")
    print("=" * 50)
    print(prompt_generado)

Meta-Prompting Avanzado: Múltiples Candidatos

def meta_prompt_con_evaluacion(
    tarea: str,
    n_candidatos: int = 3,
    golden_set: list[dict] = None
) -> dict:
    """
    Genera N prompts candidatos y evalúa cuál es el mejor
    usando un golden set de ejemplos con respuestas correctas.
    
    Args:
        tarea: La tarea a resolver
        n_candidatos: Número de prompts candidatos a generar
        golden_set: Lista de {"input": ..., "expected": ...}
    
    Returns:
        dict con el mejor prompt y sus métricas
    """
    # Paso 1: Generar N candidatos con diferentes estilos
    estilos = [
        "muy conciso y directo",
        "detallado con ejemplos few-shot",
        "con instrucciones de razonamiento paso a paso"
    ]
    
    candidatos = []
    for i, estilo in enumerate(estilos[:n_candidatos]):
        meta = f"""Crea un prompt para esta tarea: {tarea}
        
El prompt debe ser: {estilo}
Devuelve SOLO el prompt."""
        
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": meta}],
            temperature=0.4,
            max_tokens=500
        )
        candidatos.append({
            "estilo": estilo,
            "prompt": resp.choices[0].message.content.strip()
        })
    
    # Si no hay golden set, retornar todos los candidatos
    if not golden_set:
        return {"candidatos": candidatos, "mejor": candidatos[0], "sin_evaluacion": True}
    
    # Paso 2: Evaluar cada candidato en el golden set
    for candidato in candidatos:
        aciertos = 0
        for ejemplo in golden_set:
            # Usar el prompt candidato para resolver el ejemplo
            resp = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{
                    "role": "user",
                    "content": f"{candidato['prompt']}\n\nInput: {ejemplo['input']}"
                }],
                temperature=0,
                max_tokens=200
            )
            prediccion = resp.choices[0].message.content.strip()
            
            # Evaluación simplificada: comparar si expected está en prediccion
            if ejemplo.get("expected", "").lower() in prediccion.lower():
                aciertos += 1
        
        candidato["accuracy"] = aciertos / len(golden_set)
        candidato["aciertos"] = aciertos
    
    # Paso 3: Seleccionar el mejor
    mejor = max(candidatos, key=lambda c: c["accuracy"])
    
    return {
        "mejor_prompt": mejor["prompt"],
        "mejor_accuracy": mejor["accuracy"],
        "todos_candidatos": candidatos
    }

Self-Refine: Generar → Criticar → Mejorar

El Ciclo de Refinamiento

┌─────────────────────────────────────────────────────┐
│                  SELF-REFINE LOOP                   │
│                                                     │
│  Input → [GENERATE] → Respuesta inicial             │
│              ↓                                      │
│         [CRITIQUE] → "Problemas: X, Y, Z"           │
│              ↓                                      │
│          [REFINE] → Respuesta mejorada              │
│              ↓                                      │
│         ¿Suficiente? ─── SÍ ──→ Output final        │
│              │                                      │
│              NO                                     │
│              └──→ [CRITIQUE] → ...                  │
└─────────────────────────────────────────────────────┘

Implementación Completa

from dataclasses import dataclass
from typing import Callable, Optional

@dataclass
class IteracionRefine:
    """Registra una iteración del ciclo self-refine."""
    numero: int
    respuesta: str
    critica: str
    score_antes: float
    score_despues: float

def criticar_respuesta(
    problema: str,
    respuesta: str,
    criterios: list[str] = None
) -> dict:
    """
    Critica una respuesta identificando problemas y áreas de mejora.
    
    Args:
        problema: El problema original
        respuesta: La respuesta a criticar
        criterios: Lista de criterios específicos para evaluar
    
    Returns:
        dict con 'critica', 'score', 'problemas', 'puede_mejorar'
    """
    criterios_default = [
        "precisión factual",
        "completitud (¿responde todo lo pedido?)",
        "claridad y estructura",
        "relevancia al problema"
    ]
    criterios_usados = criterios or criterios_default
    criterios_str = "\n".join([f"- {c}" for c in criterios_usados])
    
    prompt = f"""Problema original: {problema}

Respuesta a evaluar:
{respuesta}

Evalúa esta respuesta según estos criterios:
{criterios_str}

Responde en JSON:
{{
    "score": 0.0-1.0,
    "problemas": ["problema1", "problema2", ...],
    "fortalezas": ["fortaleza1", ...],
    "mejoras_concretas": ["mejora específica 1", "mejora específica 2", ...],
    "puede_mejorar": true/false
}}

Sé específico en las mejoras. Si score >= 0.85, puede_mejorar = false."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=400,
        response_format={"type": "json_object"}
    )
    
    try:
        critica = json.loads(response.choices[0].message.content)
        return {
            "score": float(critica.get("score", 0.5)),
            "problemas": critica.get("problemas", []),
            "fortalezas": critica.get("fortalezas", []),
            "mejoras": critica.get("mejoras_concretas", []),
            "puede_mejorar": bool(critica.get("puede_mejorar", True))
        }
    except (json.JSONDecodeError, KeyError) as e:
        return {
            "score": 0.5,
            "problemas": ["Error al parsear crítica"],
            "fortalezas": [],
            "mejoras": ["Reformatear la respuesta"],
            "puede_mejorar": True
        }

def refinar_respuesta(
    problema: str,
    respuesta_actual: str,
    critica: dict
) -> str:
    """
    Mejora una respuesta basándose en una crítica específica.
    """
    mejoras_str = "\n".join([f"- {m}" for m in critica.get("mejoras", [])])
    problemas_str = "\n".join([f"- {p}" for p in critica.get("problemas", [])])
    
    prompt = f"""Problema original: {problema}

Tu respuesta anterior:
{respuesta_actual}

Problemas identificados:
{problemas_str if problemas_str else "Ninguno crítico"}

Mejoras específicas requeridas:
{mejoras_str if mejoras_str else "Mejorar claridad general"}

Genera una respuesta MEJORADA que:
1. Corrija todos los problemas identificados
2. Implemente las mejoras sugeridas
3. Mantenga las fortalezas de la respuesta anterior
4. Sea más completa y precisa

Respuesta mejorada:"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.3,
        max_tokens=600
    )
    
    return response.choices[0].message.content.strip()

def self_refine(
    problema: str,
    max_iteraciones: int = 3,
    umbral_score: float = 0.85,
    criterios: list[str] = None,
    verbose: bool = True
) -> dict:
    """
    Ciclo completo de Self-Refine.
    
    Args:
        problema: El problema a resolver
        max_iteraciones: Máximo número de ciclos refinar
        umbral_score: Si score >= umbral, parar (ya es suficientemente bueno)
        criterios: Criterios de evaluación específicos
        verbose: Si True, imprime el proceso
    
    Returns:
        dict con respuesta_final, iteraciones, score_final
    """
    # Paso 1: Generar respuesta inicial
    resp_inicial = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"Resuelve este problema de forma detallada:\n\n{problema}"}],
        temperature=0.3,
        max_tokens=600
    ).choices[0].message.content.strip()
    
    respuesta_actual = resp_inicial
    iteraciones = []
    
    if verbose:
        print(f"[Iteración 0 - Inicial]\n{respuesta_actual[:200]}...\n")
    
    # Ciclo de refinamiento
    for i in range(max_iteraciones):
        # Criticar la respuesta actual
        critica = criticar_respuesta(problema, respuesta_actual, criterios)
        score_actual = critica["score"]
        
        if verbose:
            print(f"[Crítica iteración {i+1}]")
            print(f"  Score: {score_actual:.2f}")
            print(f"  Problemas: {critica['problemas']}")
            print(f"  Mejoras: {critica['mejoras'][:2]}")
        
        # Si ya es suficientemente bueno, parar
        if not critica["puede_mejorar"] or score_actual >= umbral_score:
            if verbose:
                print(f"  ✓ Score suficientemente alto ({score_actual:.2f}). Parando.")
            iteraciones.append(IteracionRefine(
                numero=i+1,
                respuesta=respuesta_actual,
                critica=str(critica),
                score_antes=score_actual,
                score_despues=score_actual
            ))
            break
        
        # Refinar la respuesta
        respuesta_mejorada = refinar_respuesta(problema, respuesta_actual, critica)
        critica_post = criticar_respuesta(problema, respuesta_mejorada, criterios)
        score_nuevo = critica_post["score"]
        
        if verbose:
            print(f"[Refinada iteración {i+1}]")
            print(f"  Score: {score_actual:.2f}{score_nuevo:.2f}")
            print(f"  {respuesta_mejorada[:150]}...\n")
        
        iteraciones.append(IteracionRefine(
            numero=i+1,
            respuesta=respuesta_mejorada,
            critica=str(critica),
            score_antes=score_actual,
            score_despues=score_nuevo
        ))
        
        # Si el refinamiento empeoró, quedarse con la versión anterior
        if score_nuevo < score_actual - 0.05:
            if verbose:
                print("  ⚠ El refinamiento empeoró la respuesta. Revirtiendo.")
            break
        
        respuesta_actual = respuesta_mejorada
    
    return {
        "respuesta_final": respuesta_actual,
        "respuesta_inicial": resp_inicial,
        "iteraciones": iteraciones,
        "n_iteraciones": len(iteraciones),
        "score_final": critica["score"] if iteraciones else 0.0,
        "mejora_total": critica["score"] - 0.5  # vs baseline asumido 0.5
    }

Meta-Prompting + Self-Refine: La Combinación

def meta_refine_pipeline(
    tarea_descripcion: str,
    inputs_prueba: list[str],
    n_iteraciones_meta: int = 2
) -> dict:
    """
    Pipeline completo: generar prompt con meta-prompting,
    luego evaluar y refinar el prompt usando self-refine.
    """
    # Paso 1: Generar prompt inicial con meta-prompting
    prompt_actual = meta_prompt_generator(tarea=tarea_descripcion)
    
    print(f"Prompt inicial:\n{prompt_actual[:200]}...\n")
    
    historial_mejoras = []
    
    for iteracion in range(n_iteraciones_meta):
        # Paso 2: Evaluar el prompt en inputs de prueba
        resultados = []
        for input_prueba in inputs_prueba[:3]:  # Limitar a 3 para ahorrar llamadas
            resp = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": f"{prompt_actual}\n\nInput: {input_prueba}"}],
                temperature=0,
                max_tokens=300
            ).choices[0].message.content
            resultados.append({"input": input_prueba, "output": resp})
        
        # Paso 3: Usar meta-prompting para mejorar el prompt
        critica_prompt = f"""Tarea: {tarea_descripcion}

Prompt actual:
{prompt_actual}

Resultados de usar este prompt en ejemplos reales:
{json.dumps(resultados, ensure_ascii=False, indent=2)}

Identifica problemas en el prompt y genera una versión MEJORADA.
¿El prompt produce resultados consistentes? ¿El formato es correcto? ¿Hay ambigüedad?

Devuelve SOLO el prompt mejorado, sin explicaciones."""
        
        resp_mejora = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": critica_prompt}],
            temperature=0.3,
            max_tokens=600
        )
        
        prompt_mejorado = resp_mejora.choices[0].message.content.strip()
        historial_mejoras.append({
            "iteracion": iteracion + 1,
            "prompt_anterior": prompt_actual,
            "prompt_nuevo": prompt_mejorado,
            "resultados_evaluacion": resultados
        })
        
        prompt_actual = prompt_mejorado
        print(f"Iteración {iteracion+1}: Prompt mejorado")
    
    return {
        "prompt_final": prompt_actual,
        "historial": historial_mejoras,
        "n_iteraciones": n_iteraciones_meta
    }

Casos de Uso Reales

Caso 1: Mejorar prompts de extracción de entidades

tarea = "Extraer entidades nombradas (personas, organizaciones, lugares, fechas) de texto en español"
texts = [
    "El CEO de Google, Sundar Pichai, visitó Madrid el 15 de marzo de 2026.",
    "La reunión entre Apple y Samsung fue en Seúl ayer.",
    "Elon Musk fundó SpaceX en 2002 en California."
]

resultado = meta_refine_pipeline(
    tarea_descripcion=tarea,
    inputs_prueba=texts,
    n_iteraciones_meta=2
)

print(f"\nPrompt final optimizado:\n{resultado['prompt_final']}")

Caso 2: Self-Refine para Código

problema_codigo = """
Escribe una función Python que:
1. Reciba una lista de diccionarios con 'nombre', 'edad', 'salario'
2. Filtre empleados con salario > 50000 y edad > 30
3. Retorne la lista ordenada por salario descendente
4. Maneje el caso de lista vacía y claves faltantes
"""

resultado = self_refine(
    problema=problema_codigo,
    max_iteraciones=2,
    umbral_score=0.9,
    criterios=[
        "correctitud del código (sin bugs)",
        "manejo de casos edge (lista vacía, claves faltantes)",
        "eficiencia (evitar operaciones innecesarias)",
        "legibilidad y documentación"
    ],
    verbose=True
)

print(f"\n=== Código Final ===")
print(resultado["respuesta_final"])
print(f"\nMejora de score: +{resultado['mejora_total']:.2f}")

Caso 3: Self-Refine para Contenido Educativo

problema_educativo = """
Explica el concepto de 'transformer architecture' para alguien que sabe Python 
básico pero nunca ha trabajado con ML/DL. Incluye una analogía y ejemplo de código simple.
"""

resultado = self_refine(
    problema=problema_educativo,
    max_iteraciones=3,
    criterios=[
        "claridad para audiencia no técnica en ML",
        "analogía comprensible y precisa",
        "código ejecutable y simple",
        "progresión lógica del concepto"
    ],
    verbose=True
)

Integración con Anthropic

import anthropic

client_anthropic = anthropic.Anthropic()

def self_refine_claude(problema: str, max_iter: int = 2) -> str:
    """
    Self-Refine usando Claude. Claude tiende a ser más autocrítico.
    """
    # Generación inicial
    mensaje_inicial = client_anthropic.messages.create(
        model="claude-3-5-haiku-20241022",
        max_tokens=600,
        messages=[{"role": "user", "content": f"Resuelve: {problema}"}]
    )
    respuesta = mensaje_inicial.content[0].text
    
    for i in range(max_iter):
        # Crítica
        critica_msg = client_anthropic.messages.create(
            model="claude-3-5-haiku-20241022",
            max_tokens=300,
            messages=[{
                "role": "user",
                "content": f"""Problema: {problema}
                
Tu respuesta: {respuesta}

Identifica 2-3 mejoras específicas que harían esta respuesta más precisa y completa.
Si ya es excelente (nada significativo que mejorar), di solo "ÓPTIMO"."""
            }]
        )
        critica = critica_msg.content[0].text
        
        if "ÓPTIMO" in critica.upper():
            break
        
        # Refinamiento
        refine_msg = client_anthropic.messages.create(
            model="claude-3-5-haiku-20241022",
            max_tokens=600,
            messages=[{
                "role": "user",
                "content": f"""Problema: {problema}
                
Respuesta anterior: {respuesta}
Crítica: {critica}

Genera una versión mejorada que incorpore las críticas."""
            }]
        )
        respuesta = refine_msg.content[0].text
    
    return respuesta

Troubleshooting

Problema 1: El meta-prompt genera prompts vagos o genéricos

Síntoma: El prompt generado podría aplicar a cualquier tarea, no es específico.

Causas:

  • Descripción de tarea muy ambigua
  • No se proveen restricciones ni ejemplos

Solución:

# MAL: descripción vaga
prompt_malo = meta_prompt_generator("clasificar texto")

# BIEN: descripción específica con contexto
prompt_bueno = meta_prompt_generator(
    tarea="Clasificar tickets de soporte técnico en: BUG_CRITICO, BUG_MENOR, FEATURE_REQUEST, PREGUNTA, DUPLICADO",
    contexto="Los tickets son de usuarios de software B2B, escritos en español o inglés mezclado (Spanglish)",
    formato_salida="JSON: {categoria: str, confianza: float 0-1, razon: str corta}",
    restricciones=[
        "BUG_CRITICO: solo si menciona pérdida de datos o caída del sistema",
        "Detectar si hay urgencia implícita en el lenguaje",
        "Si es ambiguo, preferir PREGUNTA sobre otras categorías"
    ]
)

Problema 2: Self-Refine no mejora (la crítica es demasiado permisiva)

Síntoma: El crítico siempre da score > 0.8 y dice "puede_mejorar = false" aunque la respuesta sea mediocre.

Causas:

  • El crítico usa el mismo modelo que es "demasiado amable consigo mismo"
  • Los criterios de evaluación son vagos

Solución:

# Usar un crítico más estricto con criterios específicos y ejemplos de qué cuenta como "mal"
critica_estricta = f"""Evalúa CRÍTICAMENTE esta respuesta.
Sé exigente: busca activamente errores, omisiones e imprecisiones.

Criterio: Una respuesta de 0.9+ debe cumplir TODO:
- Sin ningún error factual
- Completamente responde TODAS las partes de la pregunta
- Clara y bien estructurada
- Sin redundancias innecesarias

Problema: {problema}
Respuesta: {respuesta}

Si hay CUALQUIER problema, incluso pequeño, score debe ser < 0.85."""

Problema 3: Cada iteración de Self-Refine empeora la respuesta

Síntoma: La respuesta se va haciendo más larga y confusa con cada iteración.

Solución:

def self_refine_con_rollback(problema: str, max_iter: int = 3) -> dict:
    """Self-Refine que revierte si una iteración empeora."""
    respuesta_actual = generar_inicial(problema)
    score_actual = criticar_respuesta(problema, respuesta_actual)["score"]
    mejor = {"respuesta": respuesta_actual, "score": score_actual}
    
    for _ in range(max_iter):
        critica = criticar_respuesta(problema, respuesta_actual)
        if not critica["puede_mejorar"]:
            break
        
        nueva = refinar_respuesta(problema, respuesta_actual, critica)
        nuevo_score = criticar_respuesta(problema, nueva)["score"]
        
        if nuevo_score > mejor["score"]:
            mejor = {"respuesta": nueva, "score": nuevo_score}
        
        if nuevo_score < score_actual - 0.05:
            # Empeoró: revertir
            break
        
        respuesta_actual = nueva
        score_actual = nuevo_score
    
    return mejor

def generar_inicial(problema: str) -> str:
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problema}],
        temperature=0.3, max_tokens=500
    ).choices[0].message.content

Ejercicios

Ejercicio 1: Meta-prompt para un clasificador de spam

Usa meta-prompting para generar un prompt que clasifique emails como SPAM o NO_SPAM. Luego evalúa el prompt generado con 10 ejemplos conocidos. ¿Supera el 85% de accuracy?

Ver solución
emails_test = [
    ("¡GANASTE $1,000,000! Haz clic aquí para reclamar tu premio.", "SPAM"),
    ("Reunión del equipo mañana a las 10am.", "NO_SPAM"),
    ("Oferta especial: Viagra al 90% de descuento", "SPAM"),
    ("Tu factura de enero está disponible.", "NO_SPAM"),
    ("Estimado usuario, su cuenta será bloqueada en 24h. Verifique ahora.", "SPAM"),
    ("¿Puedes revisar el PR que subí ayer?", "NO_SPAM"),
    ("Crypto trading bot - gana $500 diarios sin esfuerzo", "SPAM"),
    ("Adjunto el informe trimestral del Q4.", "NO_SPAM"),
    ("Usted ha sido seleccionado para una oferta exclusiva.", "SPAM"),
    ("¿Tienes disponibilidad para una llamada el viernes?", "NO_SPAM"),
]

# Generar prompt con meta-prompting
prompt_spam = meta_prompt_generator(
    tarea="Clasificar emails como SPAM o NO_SPAM",
    contexto="Emails pueden ser en español o inglés",
    formato_salida="Solo responde: SPAM o NO_SPAM",
    restricciones=[
        "Detectar urgencia artificial ('AHORA', '24h', 'INMEDIATAMENTE')",
        "Detectar promesas de dinero fácil",
        "Ignorar emails de trabajo legítimos"
    ]
)

# Evaluar
aciertos = 0
for email, esperado in emails_test:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{prompt_spam}\n\nEmail: {email}"}],
        temperature=0
    ).choices[0].message.content.strip().upper()
    
    if esperado in resp:
        aciertos += 1

print(f"Accuracy del prompt generado: {aciertos/len(emails_test):.0%}")

Ejercicio 2: Self-Refine para un ensayo

Aplica Self-Refine para mejorar un ensayo corto (200 palabras) sobre "el impacto de la IA en el empleo". Define 3 criterios de calidad relevantes para un ensayo académico. ¿En cuántas iteraciones converge?

Ver solución
problema_ensayo = """
Escribe un ensayo corto (200 palabras) sobre el impacto de la IA en el empleo.
Debe incluir: argumentos a favor y en contra, datos si los conoces, y una conclusión equilibrada.
"""

criterios_ensayo = [
    "Argumentación balanceada: presenta ambas perspectivas con igual peso",
    "Datos y ejemplos concretos que respaldan las afirmaciones",
    "Estructura clara: introducción, desarrollo con datos, conclusión",
    "Conclusión matizada: no excesivamente optimista ni alarmista"
]

resultado = self_refine(
    problema=problema_ensayo,
    max_iteraciones=3,
    umbral_score=0.88,
    criterios=criterios_ensayo,
    verbose=True
)

print(f"\n=== Ensayo Final (iter {resultado['n_iteraciones']}) ===")
print(resultado["respuesta_final"])
print(f"\nScore final: {resultado['score_final']:.2f}")

Ejercicio 3: Comparar Meta-Prompting Manual vs Automático

Escribe manualmente el mejor prompt que puedas para "resumir artículos de noticias en 3 puntos clave". Luego genera uno con meta-prompting. Evalúa ambos en 5 artículos reales. ¿Cuál tiene mejor calidad percibida?

Ver solución
# Prompt manual
prompt_manual = """Resume el siguiente artículo de noticias en exactamente 3 puntos clave.
Formato:
1. [Hecho principal]
2. [Contexto o causa]
3. [Impacto o consecuencia]
Sé conciso (máx 20 palabras por punto)."""

# Prompt con meta-prompting
prompt_auto = meta_prompt_generator(
    tarea="Resumir artículos de noticias en 3 puntos clave",
    formato_salida="Lista numerada con 3 puntos, máx 20 palabras cada uno",
    restricciones=["No incluir opiniones del autor", "Incluir datos numéricos si están disponibles"]
)

articulo_test = """La empresa Tesla anunció hoy la construcción de una nueva Gigafactory 
en España, específicamente en Valencia, con una inversión de 4 mil millones de euros. 
La planta dará empleo a 3,000 trabajadores directos y se espera que empiece a operar 
en 2028. El Gobierno español ha ofrecido incentivos fiscales de 500 millones de euros."""

def evaluar_resumen(prompt: str, articulo: str) -> str:
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{prompt}\n\nArtículo: {articulo}"}],
        temperature=0
    ).choices[0].message.content

print("=== MANUAL ===")
print(evaluar_resumen(prompt_manual, articulo_test))
print("\n=== META-PROMPTING ===")
print(evaluar_resumen(prompt_auto, articulo_test))

Resumen

  • Meta-prompting: Usar un LLM para generar el prompt óptimo de otra tarea. Útil cuando el espacio de prompts es grande o el dominio es complejo.
  • Meta-prompting + evaluación: Generar N candidatos, evaluar en golden set, elegir el mejor. Más robusto que un solo candidato.
  • Self-Refine: Ciclo iterativo Generar → Criticar → Mejorar. 1-3 iteraciones suelen ser suficientes antes de rendimientos decrecientes.
  • Crítico estricto: La clave del Self-Refine es tener un crítico que sea genuinamente exigente. Un crítico demasiado permisivo no mejorará nada.
  • Rollback: Si una iteración empeora la respuesta, revertir a la mejor versión hasta ahora.
  • Cuándo usar: Meta-prompting para optimización de sistemas, Self-Refine para respuestas críticas que deben tener alta calidad.

Recursos adicionales

  1. Self-Refine: Iterative Refinement with Self-Feedback (Madaan et al., 2023) - Paper original
  2. Large Language Models as Optimizers (Yang et al., 2023) - Prompts como optimización
  3. Automatic Prompt Engineer (APE) (Zhou et al., 2022) - Meta-prompting sistemático
  4. Constitutional AI (Anthropic) - Self-critique para alineación
  5. OpenAI Prompt Engineering Guide
  6. Reflexion: Language Agents with Verbal Reinforcement - Variante de self-refine