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

7. Decision Framework: Qué Técnica Usar

Descripción

Conocer las técnicas es solo la mitad del trabajo. La otra mitad es saber cuándo aplicar cada una. Este framework de decisión te da un proceso sistemático para elegir la técnica correcta basándote en las características de tu tarea, tus restricciones de presupuesto y los requisitos de calidad.


El Árbol de Decisión Principal

¿Tu tarea necesita datos en tiempo real o herramientas externas?
├── SÍ → ReAct (+ Structured Output si el output debe ser parseable)
│         └── ¿Accuracy crítica? → ReAct + verificación adicional
│
└── NO → ¿Es una tarea de razonamiento (math, lógica, multi-paso)?
    ├── SÍ → ¿Accuracy crítica (errores tienen alto costo)?
    │   ├── SÍ → CoT + Self-Consistency (N=3-5)
    │   └── NO → CoT solo (suficiente en la mayoría de casos)
    │
    └── NO → ¿Hay múltiples estrategias válidas posibles?
        ├── SÍ → Tree-of-Thought (solo si CoT falla sistemáticamente)
        └── NO → ¿Es clasificación, extracción, o respuesta directa?
            ├── SÍ → Zero-shot o Few-shot
            └── NO → ¿Necesitas optimizar el prompt o la calidad de la respuesta?
                ├── Prompt subóptimo → Meta-prompting
                └── Calidad mejorable → Self-Refine

Tabla de Referencia Rápida

TareaTécnica recomendadaLlamadas APILatenciaCosto relativo
Clasificación de textoFew-shot1Baja1x
Extracción de entidadesZero-shot + JSON mode1Baja1x
TraducciónZero-shot o Few-shot1Baja1x
Resumen de textoZero-shot1Baja1x
Math word problemCoT1Media1.5x
Math crítico (no errores)CoT + Self-Consistency N=55Alta5x
QA con búsqueda webReAct3-8Alta5-10x
QA con base de datosReAct2-5Media-Alta3-7x
Planificación con alternativasTree-of-Thought10-30Muy alta10-30x
Código con alta calidadSelf-Refine (2-3 iter)4-8Alta4-8x
Optimizar prompt en producciónMeta-prompting + evaluación10-20Alta10-20x

Framework Detallado por Dimensiones

Dimensión 1: Tipo de tarea

from openai import OpenAI
from enum import Enum
from dataclasses import dataclass
from typing import Optional

client = OpenAI()

class TipoTarea(Enum):
    CLASIFICACION = "clasificacion"       # Asignar a categoría
    EXTRACCION = "extraccion"             # Extraer información estructurada
    RAZONAMIENTO_MATH = "math"           # Operaciones numéricas/lógica
    RAZONAMIENTO_LOGICO = "logico"       # Deducciones, inferencias
    GENERACION_TEXTO = "generacion"      # Escritura creativa o técnica
    QA_FACTUAL = "qa_factual"            # Preguntas sobre hechos conocidos
    QA_TIEMPO_REAL = "qa_tiempo_real"    # Preguntas que necesitan datos actuales
    PLANIFICACION = "planificacion"      # Diseñar estrategias, roadmaps
    CODIGO = "codigo"                    # Escribir o revisar código
    MULTIMODAL = "multimodal"            # Combina varios tipos

@dataclass
class AnalisisTarea:
    tipo: TipoTarea
    accuracy_critica: bool          # ¿Los errores tienen alto costo?
    necesita_datos_externos: bool   # ¿Necesita APIs, base de datos?
    output_estructurado: bool       # ¿El output debe ser JSON/parseable?
    presupuesto_llamadas: int       # Máximo de llamadas API permitidas
    latencia_maxima_seg: float      # Máximo tiempo de respuesta aceptable

def analizar_tarea_automatico(descripcion: str) -> AnalisisTarea:
    """
    Analiza automáticamente una descripción de tarea para clasificarla.
    """
    prompt = f"""Analiza esta tarea y clasifícala. Responde en JSON:
    
Tarea: {descripcion}

{{
    "tipo": "clasificacion|extraccion|math|logico|generacion|qa_factual|qa_tiempo_real|planificacion|codigo",
    "accuracy_critica": true/false,
    "necesita_datos_externos": true/false,
    "output_estructurado": true/false,
    "presupuesto_llamadas": 1-20,
    "latencia_maxima_seg": 5-60
}}

Reglas para accuracy_critica: true si errores implican pérdida económica, riesgos legales, o impacto en usuarios.
Reglas para necesita_datos_externos: true si necesita precios, datos en tiempo real, consultas a bases de datos."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        response_format={"type": "json_object"}
    )
    
    import json
    datos = json.loads(response.choices[0].message.content)
    
    tipo_map = {
        "clasificacion": TipoTarea.CLASIFICACION,
        "extraccion": TipoTarea.EXTRACCION,
        "math": TipoTarea.RAZONAMIENTO_MATH,
        "logico": TipoTarea.RAZONAMIENTO_LOGICO,
        "generacion": TipoTarea.GENERACION_TEXTO,
        "qa_factual": TipoTarea.QA_FACTUAL,
        "qa_tiempo_real": TipoTarea.QA_TIEMPO_REAL,
        "planificacion": TipoTarea.PLANIFICACION,
        "codigo": TipoTarea.CODIGO
    }
    
    return AnalisisTarea(
        tipo=tipo_map.get(datos.get("tipo", "logico"), TipoTarea.RAZONAMIENTO_LOGICO),
        accuracy_critica=datos.get("accuracy_critica", False),
        necesita_datos_externos=datos.get("necesita_datos_externos", False),
        output_estructurado=datos.get("output_estructurado", False),
        presupuesto_llamadas=datos.get("presupuesto_llamadas", 5),
        latencia_maxima_seg=datos.get("latencia_maxima_seg", 30.0)
    )

La Función de Selección de Técnica

@dataclass
class RecomendacionTecnica:
    tecnica_principal: str
    tecnica_secundaria: Optional[str]
    parametros_sugeridos: dict
    justificacion: str
    llamadas_estimadas: int
    costo_relativo: float
    alternativas: list[str]

def seleccionar_tecnica(analisis: AnalisisTarea) -> RecomendacionTecnica:
    """
    Selecciona la técnica óptima basándose en el análisis de la tarea.
    
    Args:
        analisis: Resultado de analizar_tarea_automatico o creado manualmente
    
    Returns:
        Recomendación con técnica, parámetros y justificación
    """
    tipo = analisis.tipo
    accuracy = analisis.accuracy_critica
    externos = analisis.necesita_datos_externos
    estructurado = analisis.output_estructurado
    budget = analisis.presupuesto_llamadas
    
    # Regla 1: Datos externos → ReAct
    if externos:
        if accuracy and budget >= 8:
            return RecomendacionTecnica(
                tecnica_principal="ReAct",
                tecnica_secundaria="verificacion_adicional",
                parametros_sugeridos={"max_steps": 6, "output_format": "json" if estructurado else "text"},
                justificacion="Necesita datos externos. Con accuracy crítica, añadir verificación del resultado.",
                llamadas_estimadas=8,
                costo_relativo=8.0,
                alternativas=["react_con_fallback"]
            )
        return RecomendacionTecnica(
            tecnica_principal="ReAct",
            tecnica_secundaria="structured_output" if estructurado else None,
            parametros_sugeridos={"max_steps": 5},
            justificacion="Datos externos requieren ReAct para herramientas.",
            llamadas_estimadas=5,
            costo_relativo=5.0,
            alternativas=["react_simplificado"]
        )
    
    # Regla 2: Math/Lógica con accuracy crítica → CoT + SC
    if tipo in [TipoTarea.RAZONAMIENTO_MATH, TipoTarea.RAZONAMIENTO_LOGICO]:
        if accuracy and budget >= 5:
            n = min(5, budget)
            return RecomendacionTecnica(
                tecnica_principal="CoT + Self-Consistency",
                tecnica_secundaria=None,
                parametros_sugeridos={"n": n, "temperatura": 0.7},
                justificacion=f"Razonamiento con accuracy crítica. N={n} muestras para consenso.",
                llamadas_estimadas=n,
                costo_relativo=float(n),
                alternativas=["cot_solo", "react" if accuracy else None]
            )
        return RecomendacionTecnica(
            tecnica_principal="CoT",
            tecnica_secundaria=None,
            parametros_sugeridos={"temperatura": 0},
            justificacion="Razonamiento matemático/lógico. CoT con step-by-step.",
            llamadas_estimadas=1,
            costo_relativo=1.5,
            alternativas=["cot_sc_n3"]
        )
    
    # Regla 3: Planificación → ToT
    if tipo == TipoTarea.PLANIFICACION:
        if budget >= 15:
            return RecomendacionTecnica(
                tecnica_principal="Tree-of-Thought",
                tecnica_secundaria="Self-Consistency" if accuracy else None,
                parametros_sugeridos={"breadth": 3, "depth": 2},
                justificacion="Planificación tiene múltiples estrategias. ToT explora alternativas.",
                llamadas_estimadas=15,
                costo_relativo=15.0,
                alternativas=["cot", "tot_simplificado"]
            )
        return RecomendacionTecnica(
            tecnica_principal="ToT simplificado",
            tecnica_secundaria=None,
            parametros_sugeridos={"breadth": 2, "depth": 1},
            justificacion="Budget limitado. ToT simplificado: 2 enfoques, 1 nivel.",
            llamadas_estimadas=6,
            costo_relativo=6.0,
            alternativas=["cot"]
        )
    
    # Regla 4: Código → Self-Refine
    if tipo == TipoTarea.CODIGO:
        iter_max = 2 if budget >= 4 else 1
        return RecomendacionTecnica(
            tecnica_principal="Self-Refine",
            tecnica_secundaria=None,
            parametros_sugeridos={"max_iteraciones": iter_max, "criterios": ["correctitud", "eficiencia", "legibilidad"]},
            justificacion=f"Código requiere revisión iterativa. {iter_max} iteraciones de refine.",
            llamadas_estimadas=iter_max * 2,
            costo_relativo=float(iter_max * 2),
            alternativas=["cot_con_tests"]
        )
    
    # Regla 5: Clasificación/Extracción → Few-shot/Zero-shot
    if tipo in [TipoTarea.CLASIFICACION, TipoTarea.EXTRACCION]:
        return RecomendacionTecnica(
            tecnica_principal="Few-shot",
            tecnica_secundaria="structured_output" if estructurado else None,
            parametros_sugeridos={"n_ejemplos": 2 if tipo == TipoTarea.CLASIFICACION else 1, "temperatura": 0},
            justificacion="Clasificación/extracción simple no necesita técnicas complejas.",
            llamadas_estimadas=1,
            costo_relativo=1.2,
            alternativas=["zero_shot", "cot_para_casos_ambiguos"]
        )
    
    # Regla 6: QA Factual → Zero-shot
    if tipo == TipoTarea.QA_FACTUAL:
        return RecomendacionTecnica(
            tecnica_principal="Zero-shot",
            tecnica_secundaria="few_shot" if accuracy else None,
            parametros_sugeridos={"temperatura": 0},
            justificacion="QA factual con conocimiento del modelo. Zero-shot suficiente.",
            llamadas_estimadas=1,
            costo_relativo=1.0,
            alternativas=["rag" if externos else "cot"]
        )
    
    # Default: CoT
    return RecomendacionTecnica(
        tecnica_principal="CoT",
        tecnica_secundaria=None,
        parametros_sugeridos={"temperatura": 0},
        justificacion="Técnica general de razonamiento aplicable a la mayoría de tareas.",
        llamadas_estimadas=1,
        costo_relativo=1.5,
        alternativas=["few_shot", "zero_shot"]
    )


# Función principal para uso fácil:
def recomendar_tecnica(descripcion_tarea: str, verbose: bool = True) -> RecomendacionTecnica:
    """
    Dado la descripción de una tarea, recomienda la técnica óptima.
    """
    analisis = analizar_tarea_automatico(descripcion_tarea)
    recomendacion = seleccionar_tecnica(analisis)
    
    if verbose:
        print(f"=== ANÁLISIS DE TAREA ===")
        print(f"Tipo: {analisis.tipo.value}")
        print(f"Accuracy crítica: {analisis.accuracy_critica}")
        print(f"Datos externos: {analisis.necesita_datos_externos}")
        print(f"\n=== RECOMENDACIÓN ===")
        print(f"Técnica: {recomendacion.tecnica_principal}")
        if recomendacion.tecnica_secundaria:
            print(f"Combinada con: {recomendacion.tecnica_secundaria}")
        print(f"Parámetros: {recomendacion.parametros_sugeridos}")
        print(f"Justificación: {recomendacion.justificacion}")
        print(f"Llamadas estimadas: {recomendacion.llamadas_estimadas}")
        print(f"Costo relativo: {recomendacion.costo_relativo}x")
        print(f"Alternativas: {[a for a in recomendacion.alternativas if a]}")
    
    return recomendacion

Análisis Cost vs Accuracy vs Latency

import time

def benchmark_tecnicas_en_tarea(
    problema: str,
    respuesta_correcta: Optional[str] = None
) -> dict:
    """
    Benchmarks múltiples técnicas en un problema específico.
    Útil para calibrar qué técnica usar en un caso de uso particular.
    """
    resultados = {}
    
    # Zero-shot
    t0 = time.time()
    resp_zero = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problema}],
        temperature=0, max_tokens=200
    ).choices[0].message.content
    resultados["zero_shot"] = {
        "respuesta": resp_zero[:100],
        "latencia": time.time() - t0,
        "llamadas": 1,
        "costo_relativo": 1.0
    }
    
    # CoT
    t0 = time.time()
    resp_cot = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso."}],
        temperature=0, max_tokens=400
    ).choices[0].message.content
    resultados["cot"] = {
        "respuesta": resp_cot[:100],
        "latencia": time.time() - t0,
        "llamadas": 1,
        "costo_relativo": 1.5
    }
    
    # Self-Consistency N=3
    t0 = time.time()
    from collections import Counter
    import re
    def extraer_num(t):
        nums = re.findall(r'-?\d+\.?\d*', t)
        return nums[-1] if nums else t.strip()[-20:]
    
    resps_sc = []
    for _ in range(3):
        r = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso. Respuesta: [valor]"}],
            temperature=0.7, max_tokens=300
        ).choices[0].message.content
        resps_sc.append(extraer_num(r))
    ganadora_sc = Counter(resps_sc).most_common(1)[0][0]
    resultados["sc_n3"] = {
        "respuesta": ganadora_sc,
        "latencia": time.time() - t0,
        "llamadas": 3,
        "costo_relativo": 3.0
    }
    
    # Agregar evaluación si se provee respuesta correcta
    if respuesta_correcta:
        def es_correcto(pred, correcta):
            try:
                return abs(float(re.sub(r'[^\d.-]', '', pred)) - float(correcta)) < 0.01
            except Exception:
                return pred.strip().lower() == correcta.strip().lower()
        
        for tecnica in resultados:
            resultados[tecnica]["correcto"] = es_correcto(
                resultados[tecnica]["respuesta"], respuesta_correcta
            )
    
    return resultados


# Uso en caso real:
problema_test = "Si invierto $1000 al 7% anual durante 10 años con interés compuesto, ¿cuánto tendré?"
resultados = benchmark_tecnicas_en_tarea(problema_test, respuesta_correcta="1967.15")

print("Técnica         | Respuesta        | Latencia | Llamadas | ¿Correcto?")
print("-" * 75)
for t, datos in resultados.items():
    correcto = datos.get("correcto", "N/A")
    print(f"{t:<15} | {datos['respuesta'][:15]:<15} | {datos['latencia']:.2f}s  | {datos['llamadas']}        | {correcto}")

Guías Prácticas por Industria

Fintech / Análisis Financiero

GUIA_FINTECH = {
    "calculos_riesgo": {
        "tecnica": "CoT + Self-Consistency N=5",
        "razon": "Errores en cálculos de riesgo tienen alto impacto económico",
        "ejemplo": "Value at Risk, opciones financieras, scoring crediticio"
    },
    "datos_mercado": {
        "tecnica": "ReAct + Structured Output",
        "razon": "Precios, tipos de cambio, indices requieren datos en tiempo real",
        "ejemplo": "Precios de acciones, volatilidad, correlaciones"
    },
    "clasificacion_transacciones": {
        "tecnica": "Few-shot",
        "razon": "Clasificación de fraude/normal no requiere técnicas avanzadas",
        "ejemplo": "Detectar transacciones sospechosas por categoría"
    },
    "reportes_regulatorios": {
        "tecnica": "Self-Refine (3 iteraciones)",
        "razon": "La calidad del lenguaje y precisión son críticas en reportes regulatorios",
        "ejemplo": "Basel III, MIFID II compliance reports"
    }
}

Legal / Compliance

GUIA_LEGAL = {
    "analisis_contratos": {
        "tecnica": "Self-Refine + criterios legales",
        "razon": "Precisión y exhaustividad son críticas",
        "ejemplo": "Identificar cláusulas problemáticas"
    },
    "busqueda_jurisprudencia": {
        "tecnica": "ReAct",
        "razon": "Necesita buscar en bases de datos de casos",
        "ejemplo": "Encontrar precedentes relevantes"
    },
    "clasificacion_documentos": {
        "tecnica": "Few-shot con ejemplos legales",
        "razon": "Clasificación por tipo de documento es relativamente directa",
        "ejemplo": "Distinguir contratos de facturas, resoluciones"
    }
}

E-commerce / Customer Support

GUIA_ECOMMERCE = {
    "clasificacion_tickets": {
        "tecnica": "Zero-shot o Few-shot",
        "razon": "Alta velocidad necesaria; clasificación simple",
        "ejemplo": "Devolver vs. consulta vs. queja vs. información"
    },
    "consulta_estado_pedido": {
        "tecnica": "ReAct",
        "razon": "Requiere consultar el sistema de pedidos",
        "ejemplo": "¿Dónde está mi paquete?"
    },
    "respuestas_complejas": {
        "tecnica": "Self-Refine",
        "razon": "Respuestas a quejas complejas deben ser empáticas y precisas",
        "ejemplo": "Gestión de devoluciones complejas, reclamaciones"
    }
}

Diagrama de Flujo Completo con Código

def framework_decision_completo(
    descripcion_tarea: str,
    budget_usd_por_request: float = 0.005,
    latencia_max_seg: float = 30.0,
    accuracy_minima: float = 0.80
) -> dict:
    """
    Framework de decisión completo que considera presupuesto, latencia y accuracy.
    
    Returns:
        Configuración completa: técnica, parámetros, costo estimado, estrategia de fallback
    """
    # Clasificar tarea
    analisis = analizar_tarea_automatico(descripcion_tarea)
    
    # Costo estimado por técnica (simplificado)
    COSTOS_ESTIMADOS = {
        "zero_shot": 0.0002,
        "few_shot": 0.0003,
        "cot": 0.0004,
        "cot_sc_n3": 0.0012,
        "cot_sc_n5": 0.002,
        "react": 0.002,
        "tot_simplificado": 0.003,
        "self_refine_n2": 0.0008,
        "self_refine_n3": 0.0012
    }
    
    # Seleccionar recomendación base
    recom = seleccionar_tecnica(analisis)
    
    # Verificar si la técnica recomendada cabe en el presupuesto
    tecnica = recom.tecnica_principal.lower().replace(" + ", "_").replace(" ", "_")
    costo_estimado = COSTOS_ESTIMADOS.get(tecnica, COSTOS_ESTIMADOS["cot"])
    
    dentro_presupuesto = costo_estimado <= budget_usd_por_request
    
    if not dentro_presupuesto:
        # Buscar alternativa más barata
        alternativas_por_costo = sorted(
            [(t, c) for t, c in COSTOS_ESTIMADOS.items() if c <= budget_usd_por_request],
            key=lambda x: x[1], reverse=True
        )
        if alternativas_por_costo:
            tecnica_alternativa = alternativas_por_costo[0][0]
            advertencia = f"Técnica óptima ({recom.tecnica_principal}) supera presupuesto. Usando {tecnica_alternativa}"
        else:
            tecnica_alternativa = "zero_shot"
            advertencia = "Budget muy ajustado. Usando zero-shot."
    else:
        tecnica_alternativa = tecnica
        advertencia = None
    
    return {
        "tecnica_recomendada": recom.tecnica_principal,
        "tecnica_dentro_presupuesto": tecnica_alternativa,
        "parametros": recom.parametros_sugeridos,
        "justificacion": recom.justificacion,
        "costo_estimado_usd": costo_estimado,
        "dentro_presupuesto": dentro_presupuesto,
        "advertencia": advertencia,
        "fallback": recom.alternativas[0] if recom.alternativas else "zero_shot",
        "analisis_tarea": {
            "tipo": analisis.tipo.value,
            "accuracy_critica": analisis.accuracy_critica,
            "necesita_externos": analisis.necesita_datos_externos
        }
    }

Casos de Estudio

Caso 1: Sistema de tutoring adaptativo

Tarea: Sistema de tutoring de matemáticas para bachillerato.
Requisitos:
- Respuestas siempre correctas (accuracy crítica)
- Explicaciones paso a paso
- Detectar si el estudiante cometió un error específico

Análisis:
- Tipo: RAZONAMIENTO_MATH
- Accuracy crítica: TRUE (no puedes enseñar matemáticas incorrectas)
- Datos externos: FALSE
- Budget por consulta: $0.01

→ RECOMENDACIÓN: CoT + Self-Consistency N=5
  Parámetros: n=5, temperatura=0.7
  Justificación: Accuracy crítica, math → SC para validar, CoT para explicaciones
  Costo estimado: $0.002 (dentro del presupuesto)
  
IMPLEMENTACIÓN:
```python
def tutoring_matematicas(ejercicio: str, respuesta_estudiante: str) -> dict:
    """
    Evalúa la respuesta del estudiante y genera retroalimentación.
    """
    from collections import Counter
    import re
    
    # Resolver correctamente con CoT + SC
    respuestas = []
    for _ in range(5):
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{ejercicio}\nResuelve paso a paso. Respuesta: [número]"}],
            temperature=0.7, max_tokens=400
        ).choices[0].message.content
        nums = re.findall(r'-?\d+\.?\d*', resp)
        respuestas.append(nums[-1] if nums else resp.strip()[-10:])
    
    respuesta_correcta = Counter(respuestas).most_common(1)[0][0]
    
    # Evaluar respuesta del estudiante
    try:
        es_correcto = abs(float(respuesta_estudiante) - float(respuesta_correcta)) < 0.01
    except ValueError:
        es_correcto = respuesta_estudiante.strip() == respuesta_correcta.strip()
    
    # Generar retroalimentación
    feedback = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"""
Ejercicio: {ejercicio}
Respuesta correcta: {respuesta_correcta}
Respuesta del estudiante: {respuesta_estudiante}
¿Correcto?: {es_correcto}

{'¡Correcto! Explica por qué la solución es correcta en 2-3 oraciones.' if es_correcto else 'Incorrecto. Identifica el error probable del estudiante y explica el proceso correcto paso a paso.'}
"""}],
        temperature=0
    ).choices[0].message.content
    
    return {
        "es_correcto": es_correcto,
        "respuesta_correcta": respuesta_correcta,
        "retroalimentacion": feedback
    }

Caso 2: Análisis de sentimiento para app de reviews

Tarea: Clasificar reviews de productos como POSITIVO/NEGATIVO/MIXTO/NEUTRAL
Volumen: 10,000 reviews/día
Requisitos:
- Alta velocidad (<2 seg por review)
- Bajo costo (presupuesto limitado)
- 85%+ accuracy (errores ocasionales aceptables)

→ RECOMENDACIÓN: Few-shot con 2-3 ejemplos
  Justificación: Clasificación simple, alto volumen → economizar
  Costo: 1x (vs 5x si usáramos SC)
  
NO usar: CoT + Self-Consistency (sería 5x más caro sin mejora significativa para clasificación)

Troubleshooting del Framework

Problema 1: La técnica recomendada da malos resultados

Síntoma: El framework recomienda CoT para una tarea que el modelo falla consistentemente.

Diagnóstico: La tarea requiere conocimiento fuera del rango de entrenamiento, o el problema es de planificación compleja.

Solución:

def diagnóstico_rápido(problema: str) -> str:
    """
    Ejecuta CoT simple. Si falla, sugiere técnica alternativa.
    """
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso."}],
        temperature=0, max_tokens=400
    ).choices[0].message.content
    
    # Señales de que CoT está fallando:
    señales_fallo = [
        "no sé", "no puedo", "not sure", "I don't know",
        "necesito más información", "requires more context",
        "impossible", "imposible"
    ]
    
    falla_cot = any(s in resp.lower() for s in señales_fallo)
    
    if falla_cot:
        print("⚠ CoT falla. Posibles soluciones:")
        print("  1. ReAct: si el problema necesita datos externos")
        print("  2. ToT: si hay múltiples estrategias posibles")
        print("  3. Few-shot con ejemplos: si el modelo no entiende el formato")
        print("  4. Reformular el problema: hacerlo más explícito")
    
    return resp

Problema 2: El presupuesto es muy ajustado

Síntoma: Cada técnica avanzada supera el presupuesto permitido.

Solución: Usar routing para aplicar técnicas avanzadas solo cuando sea necesario:

def routing_presupuesto(
    problema: str,
    presupuesto_extra: bool = False  # True si el cliente/caso lo justifica
) -> str:
    """
    Aplicar técnicas avanzadas solo para casos que lo necesitan.
    """
    # Primero, intentar con la técnica más barata
    respuesta_simple = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problema}],
        temperature=0, max_tokens=200
    ).choices[0].message.content
    
    # Detectar si la respuesta simple es de baja calidad
    necesita_mas = any(palabra in respuesta_simple.lower() for palabra in 
                       ["no sé", "unclear", "depends", "complex", "multiple"])
    
    if necesita_mas and presupuesto_extra:
        # Escalar a técnica más avanzada
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\nPiensa muy cuidadosamente paso a paso."}],
            temperature=0, max_tokens=600
        ).choices[0].message.content
    
    return respuesta_simple

Ejercicios

Ejercicio 1: Clasificar 10 tareas

Para cada una de las siguientes tareas, usa el árbol de decisión para clasificar qué técnica usarías:

  1. Traducir un contrato legal del español al inglés
  2. Responder "¿Cuánto vale Bitcoin ahora?"
  3. Generar variantes de un email de marketing
  4. Verificar si un número es primo
  5. Analizar el tono de un email de soporte
  6. Calcular la ruta más eficiente entre 5 ciudades
  7. Resumir un informe de 50 páginas
  8. Escribir código para ordenar un array
  9. Responder preguntas sobre un documento específico cargado
  10. Detectar si un tweet viola las políticas de uso
Ver solución
  1. Traducción: Few-shot con ejemplos del dominio legal → Zero-shot/Few-shot (1x)
  2. Precio Bitcoin: Datos en tiempo real → ReAct con tool de precio (5x)
  3. Variantes de email: Generativo, sin respuesta única → Zero-shot con temperature 0.8 (1x)
  4. ¿Es primo?: Math puro → CoT (no necesita SC para número simple) (1.5x)
  5. Tono de email: Clasificación → Few-shot (1x)
  6. Ruta entre ciudades: Planificación con múltiples estrategias → ToT o CoT (depende de complejidad)
  7. Resumen 50 páginas: Texto largo → Map-Reduce (chunking) + Zero-shot por chunk (N llamadas)
  8. Código ordenar array: Código → Self-Refine con criterios de correctitud/eficiencia (3-5x)
  9. QA sobre documento: RAG (búsqueda + generación) → ReAct con tool de búsqueda en doc (3-6x)
  10. Moderation: Clasificación → Few-shot con ejemplos de políticas (1x)

Ejercicio 2: Diseñar el sistema completo

Tienes un sistema de análisis de contratos donde:

  • Recibes 500 contratos/día
  • Cada contrato tiene ~20 páginas (~15K tokens)
  • Necesitas: resumen ejecutivo, cláusulas de riesgo, partes involucradas
  • Budget: $0.10 por contrato

Diseña la arquitectura completa indicando qué técnica usas en cada etapa.

Ver solución
Arquitectura para análisis de contratos:

Etapa 1: Preprocessing (no LLM)
  - Dividir en chunks de 4K tokens (manteniendo contexto de sección)
  - Identificar secciones por headers

Etapa 2: Extracción por chunk (Few-shot, 1 llamada por chunk)
  - ~4 chunks por contrato = 4 llamadas
  - Extraer: partes, fechas, obligaciones por sección
  - Costo: ~4 × $0.0002 = $0.0008

Etapa 3: Análisis de riesgo (CoT, 1 llamada)
  - Input: outputs concatenados de etapa 2
  - Identificar cláusulas problemáticas con razonamiento
  - Costo: ~$0.0006

Etapa 4: Resumen ejecutivo (Self-Refine, 2 iteraciones)
  - Input: análisis de riesgo + extracción
  - 2 iteraciones para calidad
  - Costo: ~2 × $0.0004 = $0.0008

Total estimado: ~$0.002 por contrato (muy por debajo de $0.10)
→ Puedes permitirte más iteraciones en Self-Refine o usar Self-Consistency N=3 para análisis de riesgo

Ejercicio 3: Construir el router

Implementa una función router_automatico(tarea: str, texto: str) -> str que:

  1. Clasifica la tarea
  2. Selecciona la técnica
  3. Ejecuta la técnica
  4. Retorna la respuesta
Ver solución
def router_automatico(tarea: str, texto: str) -> str:
    """Router que selecciona y ejecuta la técnica correcta automáticamente."""
    problema = f"{tarea}\n\nTexto/Input:\n{texto}"
    
    # Clasificar tarea
    analisis = analizar_tarea_automatico(tarea)
    recom = seleccionar_tecnica(analisis)
    tecnica = recom.tecnica_principal.lower()
    
    print(f"[Router] Técnica seleccionada: {tecnica}")
    
    # Ejecutar según técnica
    if "self-consistency" in tecnica or "sc" in tecnica:
        from collections import Counter
        import re
        resps = []
        for _ in range(3):
            r = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso."}],
                temperature=0.7, max_tokens=400
            ).choices[0].message.content
            nums = re.findall(r'-?\d+\.?\d*', r)
            resps.append(nums[-1] if nums else r.strip()[-20:])
        return Counter(resps).most_common(1)[0][0]
    
    elif "few-shot" in tecnica or "clasificacion" in analisis.tipo.value:
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": problema}],
            temperature=0, max_tokens=200
        ).choices[0].message.content
    
    else:  # CoT default
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso."}],
            temperature=0, max_tokens=500
        ).choices[0].message.content

Resumen

El framework de decisión se resume en estas preguntas clave:

  1. ¿Necesitas datos externos? → Sí: ReAct. No: continúa.
  2. ¿Es razonamiento (math/lógica)? → Sí: CoT. Si accuracy crítica: CoT + SC.
  3. ¿Hay múltiples estrategias posibles? → Sí: ToT (si budget lo permite).
  4. ¿Es clasificación/extracción? → Few-shot o Zero-shot.
  5. ¿La calidad del prompt o respuesta es subóptima? → Meta-prompting / Self-Refine.

La regla de oro: empieza con la técnica más simple que funcione para tu caso de uso. Escala a técnicas más complejas solo cuando sea necesario.


Recursos adicionales

  1. Prompt Engineering Guide - Techniques Overview
  2. Chain-of-Thought Prompting (Wei et al.)
  3. ReAct (Yao et al., 2022)
  4. Self-Consistency (Wang et al., 2022)
  5. Tree of Thoughts (Yao et al., 2023)
  6. LLM evaluation best practices - OpenAI
  7. Cost optimization strategies - Anthropic