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

7. Zero-Shot vs Few-Shot: Decision Framework

Descripción

¿Cuándo usar zero-shot y cuándo few-shot? Esta cápsula presenta un framework de decisión basado en tipo de tarea, requisitos de consistencia, costo y latencia. Incluye tabla comparativa con métricas, benchmarks en tareas comunes, árbol de decisión, y una función de decisión implementable.

Por qué importa: La decisión incorrecta tiene costos reales: few-shot innecesario aumenta tokens y latencia en 2-3x; zero-shot cuando few-shot es necesario produce outputs inconsistentes que rompen tu pipeline downstream. Este framework te permite tomar la decisión en segundos con criterios objetivos.


Tabla Comparativa Completa

CriterioZero-ShotFew-Shot (3 ej)Few-Shot (5 ej)
Tokens por request~100-200~300-500~500-800
Latencia relativa1x~1.3x~1.6x
Costo relativo1x~1.5-2x~2-3x
Setup inicialNingunoRequiere 3 ejemplos etiquetadosRequiere 5 ejemplos
MantenimientoBajoMedio (gestionar ejemplo bank)Medio-alto
Consistencia de formatoMediaAltaMuy alta
Accuracy en tareas comunesAlta (85-92%)Similar o +3-5%+3-8%
Accuracy en dominios nichoMedia (65-75%)Alta (80-90%)Alta (82-92%)
GeneralizaciónAlta (usa conocimiento del modelo)Media (depende de ejemplos)Media
Edge cases no cubiertosComportamiento inciertoDepende de coverage de ejemplosMejor coverage

Árbol de Decisión

Paso 1: ¿La tarea es estándar o de dominio nicho?
│
├── ESTÁNDAR (traducción, resumen, sentimiento, NER básico)
│   │
│   ├── ¿Necesitas formato muy específico (schema custom, campos internos)?
│   │   ├── NO → Zero-shot ✅
│   │   └── SÍ → Few-shot con 2-3 ejemplos de formato
│   │
│   └── ¿Ya probaste zero-shot y tiene >88% accuracy en tu dataset?
│       ├── SÍ → Quédate con zero-shot ✅
│       └── NO → Prueba few-shot con 3 ejemplos
│
└── NICHO (categorías internas, jerga, formato custom, clasificación especializada)
    │
    ├── ¿Tienes ejemplos etiquetados disponibles?
    │   ├── SÍ → Few-shot (3-5 ejemplos) ✅
    │   └── NO → Genera sintéticos con LLM → valida 10 manualmente → usa few-shot
    │
    └── ¿El costo/latencia es crítico?
        ├── SÍ → Prueba zero-shot primero; si accuracy < 80%, few-shot
        └── NO → Few-shot directamente

Paso 2: Si decides few-shot, ¿cuántos ejemplos?
│
├── 2-3 ejemplos: Cuando el patrón es simple y el formato es lo principal
├── 4-5 ejemplos: Cuando hay variedad en los casos o múltiples categorías
└── 6-10 ejemplos: Cuando las categorías son muy similares entre sí (ambigüedad alta)

Benchmark: 10 Tareas Comunes

Estos valores son promedios documentados en la literatura y validados en la práctica:

TareaZero-ShotFew-Shot (3 ej)RecomendaciónNota
Clasificación sentimiento (Pos/Neg/Neu)85-92%88-94%Zero-shotTarea bien conocida
Extracción NER estándar80-88%88-95%Few-shot si formato custom
Traducción EN→ES90-95%92-96%Zero-shotMejora marginal
Resumen libre75-85%80-88%Zero-shotFew-shot si longitud fija
Clasificación intención (5 cats custom)70-80%85-92%Few-shot obligatorio
Extracción de fechas (formato ISO)60-75%80-90%Few-shotFormato específico
QA sobre documentos70-80%75-85%SimilarFew-shot si schema de respuesta
Generación de código78-85%82-88%Zero-shot suele bastar
Análisis de tono (formal/informal)70-80%82-90%Few-shot
Clasificación tickets soporte (12 cats)65-75%85-92%Few-shot obligatorio

Nota: Los valores varían según el modelo, la calidad del prompt, y el dominio. Usa estos como referencia para saber qué esperar, no como verdad absoluta.


Implementación: Sistema de Decisión

from openai import OpenAI
from enum import Enum
from typing import Literal
import time

client = OpenAI()

class TipoTarea(str, Enum):
    ESTANDAR = "estandar"         # traducción, resumen, sentimiento
    DOMINIO_NICHO = "nicho"       # categorías custom, jerga interna
    FORMATO_CRITICO = "formato"   # schema muy específico

def recomendar_tecnica(
    tipo_tarea: TipoTarea,
    tiene_ejemplos: bool,
    costo_critico: bool = False,
    accuracy_requerida: float = 0.80
) -> tuple[Literal["zero-shot", "few-shot"], int, str]:
    """
    Recomienda técnica, número de ejemplos y justificación.
    
    Returns:
        (tecnica, n_ejemplos, justificacion)
    """
    if tipo_tarea == TipoTarea.ESTANDAR:
        if costo_critico:
            return "zero-shot", 0, "Tarea estándar + costo crítico → zero-shot siempre primero"
        return "zero-shot", 0, "Tarea estándar → zero-shot es suficiente en 85%+ de casos"
    
    if tipo_tarea == TipoTarea.DOMINIO_NICHO:
        if not tiene_ejemplos:
            return "zero-shot", 0, "Sin ejemplos: prueba zero-shot, si accuracy < 80% genera sintéticos"
        n = 5 if accuracy_requerida > 0.85 else 3
        return "few-shot", n, f"Dominio nicho con ejemplos: few-shot ({n} ej) para {accuracy_requerida:.0%} accuracy"
    
    if tipo_tarea == TipoTarea.FORMATO_CRITICO:
        return "few-shot", 3, "Formato crítico: 2-3 ejemplos anclan el schema exacto"
    
    # Default: few-shot si hay ejemplos, zero-shot si no
    if tiene_ejemplos:
        return "few-shot", 3, "Default: few-shot disponible → usar"
    return "zero-shot", 0, "Sin ejemplos disponibles → zero-shot"

# Función que ejecuta y mide ambas técnicas
def medir_tecnicas(
    textos_test: list[tuple[str, str]],  # (texto, etiqueta_real)
    categorias: list[str],
    ejemplos_few_shot: list[tuple[str, str]],
    k: int = 3
) -> dict:
    """
    Ejecuta zero-shot y few-shot en un test set y compara métricas.
    """
    stats = {
        "zero_shot": {"correctos": 0, "total_tokens": 0, "total_latencia": 0},
        "few_shot": {"correctos": 0, "total_tokens": 0, "total_latencia": 0}
    }
    
    cats_str = ", ".join(categorias)
    
    # Construir prompt few-shot base
    ejemplos_str = "\n".join([f"Ejemplo: '{t}' → {c}" for t, c in ejemplos_few_shot[:k]])
    
    for texto, etiqueta_real in textos_test:
        
        # === ZERO-SHOT ===
        t0 = time.time()
        r_zs = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "system",
                    "content": f"Clasifica en: {cats_str}. Responde solo con la categoría."
                },
                {"role": "user", "content": texto}
            ],
            temperature=0,
            max_tokens=20
        )
        latencia_zs = (time.time() - t0) * 1000
        
        pred_zs = r_zs.choices[0].message.content.strip().upper()
        if etiqueta_real.upper() in pred_zs or pred_zs in etiqueta_real.upper():
            stats["zero_shot"]["correctos"] += 1
        stats["zero_shot"]["total_tokens"] += r_zs.usage.total_tokens
        stats["zero_shot"]["total_latencia"] += latencia_zs
        
        # === FEW-SHOT ===
        t0 = time.time()
        prompt_fs = f"""
Clasifica en: {cats_str}.

{ejemplos_str}

Texto: '{texto}'
Categoría:"""
        
        r_fs = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt_fs}],
            temperature=0,
            max_tokens=20
        )
        latencia_fs = (time.time() - t0) * 1000
        
        pred_fs = r_fs.choices[0].message.content.strip().upper()
        if etiqueta_real.upper() in pred_fs or pred_fs in etiqueta_real.upper():
            stats["few_shot"]["correctos"] += 1
        stats["few_shot"]["total_tokens"] += r_fs.usage.total_tokens
        stats["few_shot"]["total_latencia"] += latencia_fs
    
    n = len(textos_test)
    return {
        "n_test": n,
        "zero_shot": {
            "accuracy": stats["zero_shot"]["correctos"] / n,
            "tokens_promedio": stats["zero_shot"]["total_tokens"] / n,
            "latencia_promedio_ms": stats["zero_shot"]["total_latencia"] / n
        },
        "few_shot": {
            "accuracy": stats["few_shot"]["correctos"] / n,
            "tokens_promedio": stats["few_shot"]["total_tokens"] / n,
            "latencia_promedio_ms": stats["few_shot"]["total_latencia"] / n
        }
    }

Cost vs Accuracy Trade-Off: El Cálculo Real

Costo por request aproximado (GPT-4o-mini, Marzo 2026)

# Costo simplificado para análisis
COSTO_POR_1M_TOKENS = 0.15  # USD, gpt-4o-mini input

def calcular_costo_mensual(
    requests_por_dia: int,
    tokens_por_request_zs: int,    # Zero-shot
    tokens_por_request_fs: int,    # Few-shot
    accuracy_zs: float,
    accuracy_fs: float,
    costo_por_error: float = 0.0   # Costo en USD de manejar un output incorrecto
) -> dict:
    """
    Calcula y compara el costo total (API + errores) de cada técnica.
    """
    dias_mes = 30
    total_requests = requests_por_dia * dias_mes
    
    # Costo API
    costo_api_zs = total_requests * tokens_por_request_zs * COSTO_POR_1M_TOKENS / 1_000_000
    costo_api_fs = total_requests * tokens_por_request_fs * COSTO_POR_1M_TOKENS / 1_000_000
    
    # Costo de errores
    errores_zs = total_requests * (1 - accuracy_zs)
    errores_fs = total_requests * (1 - accuracy_fs)
    costo_errores_zs = errores_zs * costo_por_error
    costo_errores_fs = errores_fs * costo_por_error
    
    return {
        "zero_shot": {
            "costo_api_usd": round(costo_api_zs, 2),
            "costo_errores_usd": round(costo_errores_zs, 2),
            "costo_total_usd": round(costo_api_zs + costo_errores_zs, 2)
        },
        "few_shot": {
            "costo_api_usd": round(costo_api_fs, 2),
            "costo_errores_usd": round(costo_errores_fs, 2),
            "costo_total_usd": round(costo_api_fs + costo_errores_fs, 2)
        },
        "recomendacion": "few-shot" if (costo_api_fs + costo_errores_fs) < (costo_api_zs + costo_errores_zs) else "zero-shot"
    }

# Ejemplo: chatbot de soporte con 1000 requests/día
resultado = calcular_costo_mensual(
    requests_por_dia=1000,
    tokens_por_request_zs=200,
    tokens_por_request_fs=500,
    accuracy_zs=0.75,      # 75% en dominio nicho
    accuracy_fs=0.90,      # 90% con few-shot
    costo_por_error=0.50   # $0.50 por error (ruta incorrecta al agente)
)

print("Costo mensual (30 días, 1000 req/día):")
print(f"  Zero-shot: ${resultado['zero_shot']['costo_total_usd']} (API: ${resultado['zero_shot']['costo_api_usd']} + Errores: ${resultado['zero_shot']['costo_errores_usd']})")
print(f"  Few-shot:  ${resultado['few_shot']['costo_total_usd']} (API: ${resultado['few_shot']['costo_api_usd']} + Errores: ${resultado['few_shot']['costo_errores_usd']})")
print(f"  Recomendación: {resultado['recomendacion']}")

# Output esperado:
# Costo mensual (30 días, 1000 req/día):
#   Zero-shot: $3375.90 (API: $0.90 + Errores: $3375.00)
#   Few-shot:  $4502.25 (API: $2.25 + Errores: $4500.00)
# Espera — verifiquemos con los números...
# Zero-shot errores: 1000*30*0.25 = 7500 errores * $0.50 = $3750
# Few-shot errores: 1000*30*0.10 = 3000 errores * $0.50 = $1500
# Few-shot total: $2.25 + $1500 = $1502.25 → MUCHO MENOR
# Recomendación: few-shot ✅

Lección clave: El costo del error downstream suele superar ampliamente el costo adicional de tokens del few-shot. Siempre incluye el costo de errores en tu análisis.


Cuándo Few-Shot Vale la Pena: Guía Rápida

Few-shot VALE la pena cuando...Few-shot NO vale la pena cuando...
Accuracy mejora >10% vs zero-shotAccuracy mejora <3% (estadísticamente insignificante)
El error downstream tiene costo real (redireccionamiento incorrecto, parsing falla)Errores son revisados manualmente de todas formas
El dominio es nicho o las categorías son internasLas categorías son genéricas (sentimiento, NER estándar)
Tienes ejemplos etiquetados de calidadNo tienes ejemplos y el dominio es nuevo
El formato de salida es específicoEl formato es flexible (texto libre)
Volumen < 100K requests/día (costo manejable)Volumen muy alto donde costo de tokens domina

Comparación con Código: Benchmark Completo

from openai import OpenAI
import time
from collections import Counter

client = OpenAI()

# Dataset de evaluación: clasificación de tickets en 4 categorías internas
CATEGORIAS = ["ACCESO", "FACTURA", "INTEGRACION", "OTRO"]

TEST_SET = [
    ("No puedo entrar al sistema desde ayer", "ACCESO"),
    ("Mi contraseña expiró y no puedo resetearla", "ACCESO"),
    ("La factura de marzo tiene un cargo duplicado", "FACTURA"),
    ("¿Puedo ver mi historial de pagos?", "FACTURA"),
    ("Necesito conectar con Salesforce", "INTEGRACION"),
    ("¿Tienen API para integrar con nuestro ERP?", "INTEGRACION"),
    ("¿Tienen soporte en fin de semana?", "OTRO"),
    ("¿Cuáles son los horarios de atención?", "OTRO"),
]

EJEMPLOS_TRAINING = [
    ("Error de login con usuario correcto", "ACCESO"),
    ("Cobro doble en mi último estado de cuenta", "FACTURA"),
    ("Webhook no está llegando a nuestro servidor", "INTEGRACION"),
]

def run_benchmark() -> dict:
    correctos_zs = 0
    correctos_fs = 0
    tokens_zs = []
    tokens_fs = []
    
    ejemplos_texto = "\n".join([f"'{t}' → {c}" for t, c in EJEMPLOS_TRAINING])
    cats_str = ", ".join(CATEGORIAS)
    
    for texto, etiqueta_real in TEST_SET:
        # Zero-shot
        r_zs = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"Clasifica en: {cats_str}. Solo la categoría."},
                {"role": "user", "content": texto}
            ],
            temperature=0, max_tokens=15
        )
        pred_zs = r_zs.choices[0].message.content.strip().upper()
        if etiqueta_real in pred_zs:
            correctos_zs += 1
        tokens_zs.append(r_zs.usage.total_tokens)
        
        # Few-shot
        r_fs = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"""
Clasifica en: {cats_str}.

Ejemplos:
{ejemplos_texto}

Texto: '{texto}'
Categoría:"""}],
            temperature=0, max_tokens=15
        )
        pred_fs = r_fs.choices[0].message.content.strip().upper()
        if etiqueta_real in pred_fs:
            correctos_fs += 1
        tokens_fs.append(r_fs.usage.total_tokens)
    
    n = len(TEST_SET)
    return {
        "zero_shot": {
            "accuracy": f"{correctos_zs/n:.0%}",
            "tokens_promedio": sum(tokens_zs) // n
        },
        "few_shot": {
            "accuracy": f"{correctos_fs/n:.0%}",
            "tokens_promedio": sum(tokens_fs) // n
        }
    }

resultados = run_benchmark()
print("BENCHMARK RESULTADOS:")
for tecnica, data in resultados.items():
    print(f"  {tecnica}: accuracy={data['accuracy']}, tokens/req={data['tokens_promedio']}")

Conexión con el Proyecto

En el Few-Shot Classification System (cápsula 08) implementarás este decision framework:

  • La función recomendar_tecnica() como parte del engine
  • El benchmark como herramienta de evaluación del sistema
  • El cálculo de costo como parte del reporte de comparación

Troubleshooting

Problema 1: Zero-shot da formatos inconsistentes en tareas estándar

Causa: Tarea "estándar" para el modelo pero con output no estándar para tu caso.

Solución: Zero-shot + output format explícito con ejemplo. Si sigue fallando, entonces sí few-shot.


Problema 2: Few-shot no mejora accuracy sobre zero-shot

Causas posibles:

  • Los ejemplos son redundantes entre sí (todos del mismo patrón)
  • La tarea ya está bien resuelta por zero-shot
  • Los ejemplos no son representativos de los casos que fallan

Diagnóstico:

# Medir accuracy en casos específicos donde zero-shot falla
fallos_zs = [(t, e) for t, e in TEST_SET if classify_zs(t) != e]
print(f"Zero-shot falla en {len(fallos_zs)}/{len(TEST_SET)} casos")
# ¿Few-shot los resuelve?
for texto, etiqueta in fallos_zs:
    pred_fs = classify_fs(texto)
    print(f"  '{texto[:40]}' → esperado: {etiqueta}, few-shot: {pred_fs}")

Problema 3: Sin ejemplos etiquetados para dominio nuevo

Solución paso a paso:

  1. Genera 10 ejemplos sintéticos con LLM: "Dame 10 ejemplos de tickets para categoría ACCESO"
  2. Valida manualmente que sean correctos (5 minutos)
  3. Úsalos como few-shot inicial
  4. Añade ejemplos reales conforme llegan del sistema

Problema 4: Costo de few-shot prohibitivo en alto volumen

Solución: Dynamic few-shot — solo incluir los K más relevantes para cada input específico:

# En lugar de siempre usar los mismos 5 ejemplos,
# selecciona los 3 más similares al input actual
ejemplos_relevantes = bank.k_nearest(texto_actual, k=3)
# Reduce tokens promedio y puede mejorar accuracy

Ejercicios

Ejercicio 1: Aplicar el árbol de decisión

Para cada tarea, aplica el árbol y justifica: (a) Traducir tweets al español, (b) Clasificar tickets en 12 categorías internas, (c) Extraer fechas en formato YYYY-MM-DD, (d) Generar respuestas de soporte en tono formal.

Ver solución

(a) Traducir tweets: Zero-shot. Tarea estándar, formato flexible, el modelo traduce perfectamente.

(b) 12 categorías internas: Few-shot obligatorio. Dominio nicho con categorías internas específicas de la empresa. Zero-shot tendrá 65-75% accuracy como mucho.

(c) Fechas en YYYY-MM-DD: Few-shot. Formato muy específico. 2-3 ejemplos anclan el patrón exacto: "el martes 15 de julio" → "2025-07-15".

(d) Respuestas formales de soporte: Zero-shot con Role + Personality bien definidos. La formalidad se controla con el system prompt, no con ejemplos.


Ejercicio 2: Calcular break-even de costos

Si zero-shot tiene 75% accuracy y few-shot 90% en clasificación de intención, y cada error de clasificación cuesta $1 en soporte manual adicional, ¿cuántos requests/día justifican el costo extra de few-shot (asumiendo +400 tokens/request y costo $0.15 por millón)?

Ver solución
# Costo adicional de few-shot (solo API) por request
tokens_extra = 400
costo_por_token_extra = 0.15 / 1_000_000
costo_api_extra_por_req = tokens_extra * costo_por_token_extra  # $0.00006

# Reducción de errores por request (15% menos errores)
reduccion_errores = 0.90 - 0.75  # 15%
ahorro_por_req = reduccion_errores * 1.0  # $1 por error ahorrado = $0.15 por request

# Break-even: costo_api_extra == ahorro_errores → siempre positive!
# $0.00006 de costo extra vs $0.15 de ahorro por req
# El few-shot se justifica desde el PRIMER request en este escenario

print(f"Costo extra API por request: ${costo_api_extra_por_req:.6f}")
print(f"Ahorro en errores por request: ${ahorro_por_req:.4f}")
print(f"ROI del few-shot: {ahorro_por_req/costo_api_extra_por_req:.0f}x")
# ROI: 2500x — few-shot es la decisión obvia

Ejercicio 3: Benchmark propio

Implementa el benchmark run_benchmark() con tu propio dataset de 10 ejemplos en una tarea que te interese. Reporta accuracy y tokens para ambas técnicas.

Ver solución
# Template de benchmark personalizable
def mi_benchmark(
    mi_test_set: list[tuple[str, str]],
    mis_categorias: list[str],
    mis_ejemplos: list[tuple[str, str]],
    k: int = 3
) -> None:
    """
    Ejecuta el benchmark y muestra resultados formateados.
    """
    resultado = medir_tecnicas(mi_test_set, mis_categorias, mis_ejemplos, k)
    
    print(f"\n=== BENCHMARK ({resultado['n_test']} ejemplos) ===")
    print(f"{'Técnica':<12} {'Accuracy':<12} {'Tokens/req':<12}")
    print("-" * 36)
    for tecnica in ["zero_shot", "few_shot"]:
        data = resultado[tecnica]
        print(f"{tecnica:<12} {data['accuracy']:.0%}{'':<9} {data['tokens_promedio']:.0f}")
    
    # Diferencia
    zs = resultado["zero_shot"]["accuracy"]
    fs = resultado["few_shot"]["accuracy"]
    diff = (fs - zs) * 100
    print(f"\nMejora few-shot: +{diff:.1f}%")
    if diff > 5:
        print("→ Few-shot JUSTIFICADO")
    else:
        print("→ Zero-shot puede ser suficiente")

Ejercicio 4: Documentar tu decisión

Para un proyecto real o hipotético, documenta: tipo de tarea, técnica elegida, justificación con métricas esperadas, y plan de validación.

Ver solución

Template de decisión documentada:

## Decisión de Técnica: [Nombre del sistema]

### Tarea
Clasificar [descripción] en categorías: [lista]

### Análisis
- Tipo de tarea: estándar / nicho / formato crítico
- Ejemplos disponibles: Sí (N ejemplos) / No
- Accuracy requerida: X%
- Costo crítico: Sí / No
- Volumen estimado: N requests/día

### Decisión
Técnica elegida: zero-shot / few-shot (N ejemplos)

### Justificación
- [Razón principal basada en el árbol de decisión]
- Accuracy esperada: X-Y%
- Costo estimado: $X/mes

### Plan de validación
- Dataset de test: N ejemplos etiquetados
- Métricas a medir: accuracy, tokens/req, latencia
- Criterio de éxito: accuracy > X%
- Si falla: [plan de contingencia]

Resumen

  • Zero-shot: Tareas estándar (traducción, resumen, sentimiento), formato flexible, costo crítico
  • Few-shot: Dominio nicho, categorías custom, formato específico, accuracy > 85% requerida
  • Número de ejemplos: 2-3 para formato simple, 4-5 para múltiples categorías, 6-10 para alta ambigüedad
  • El costo real: Incluir costo de errores downstream. Few-shot con 15% mejora puede tener ROI de 2500x
  • Benchmark primero: Mide en tu dataset antes de decidir. Las tablas son referencias, no verdades
  • Árbol de decisión: Estándar + sin formato → zero-shot. Nicho/formato/categorías custom → few-shot

Casos Especiales: Cuándo las Reglas No Aplican

1. Modelos con capacidades de reasoning (o1, o3)

Los modelos con razonamiento interno (OpenAI o1/o3, Gemini Flash Thinking) tienden a funcionar mejor con zero-shot incluso en tareas nicho:

# o1 y o3 realizan razonamiento interno — menos dependientes de ejemplos
def clasificar_con_o1(texto: str, categorias: list[str]) -> str:
    """
    Para modelos reasoning, zero-shot más elaborado puede superar few-shot.
    Estos modelos no soportan parámetro 'temperature' — se ignora.
    """
    response = client.chat.completions.create(
        model="o1-mini",
        messages=[
            {
                "role": "user",
                "content": f"""
Clasifica el siguiente texto en una de estas categorías: {', '.join(categorias)}.

Razona brevemente sobre el contenido del texto y su mejor categoría.
Finaliza con: Categoría: [NOMBRE_CATEGORIA]

Texto: {texto}
"""
            }
        ],
        max_completion_tokens=200
    )
    
    content = response.choices[0].message.content
    # Extraer la categoría de la respuesta estructurada
    import re
    match = re.search(r"Categoría:\s*(\w+)", content)
    if match:
        cat = match.group(1).upper()
        for c in categorias:
            if c.upper() == cat:
                return c
    return content.strip()

# Regla práctica: si usas o1/o3, empieza con zero-shot siempre.
# Si accuracy < umbral, añade instrucciones de razonamiento, no ejemplos.

2. Tareas de extracción vs clasificación

La distinción extracción/clasificación cambia el equilibrio:

TareaZero-shotFew-shotPor qué
Clasificar sentimiento88%92%El modelo "sabe" qué es positivo/negativo
Extraer entidades estándar (fechas, nombres)82%90%El modelo sabe el concepto, pero format es crítico
Extraer entidades custom ("código de proyecto interno")45%85%El modelo no sabe qué buscar sin ejemplos
Extraer con schema propio60%92%Schema desconocido para el modelo

Regla para extracción: Si la entidad es desconocida para el modelo, few-shot es obligatorio. Si solo es el formato, basta con un ejemplo.


Función de Decisión Automatizada

Esta función toma parámetros medibles y devuelve una recomendación:

from dataclasses import dataclass
from typing import Optional

@dataclass
class ContextoDecision:
    tipo_tarea: str              # "clasificacion", "extraccion", "generacion", "traduccion"
    dominio: str                 # "estandar", "nicho", "propietario"
    accuracy_requerida: float    # ej: 0.85
    tiene_ejemplos: bool
    n_categorias: Optional[int]  # Solo para clasificación
    formato_critico: bool        # ¿Necesitas schema exacto?
    requests_por_dia: int
    costo_por_error_usd: float   # Costo downstream de un error
    modelo: str                  # "gpt-4o-mini", "gpt-4o", "o1-mini", etc.

@dataclass
class Recomendacion:
    tecnica: str                     # "zero-shot" | "few-shot"
    n_ejemplos: int
    justificacion: list[str]
    riesgos: list[str]
    pasos_siguientes: list[str]

def decision_framework(ctx: ContextoDecision) -> Recomendacion:
    """
    Decision framework automatizado para zero-shot vs few-shot.
    """
    tecnica = "zero-shot"
    n_ejemplos = 0
    justificacion = []
    riesgos = []
    pasos = []
    
    # 1. Modelos reasoning — zero-shot por defecto
    if ctx.modelo in ("o1", "o1-mini", "o3", "o3-mini"):
        justificacion.append("Modelo con reasoning interno: zero-shot primero")
        return Recomendacion(
            tecnica="zero-shot", n_ejemplos=0,
            justificacion=justificacion,
            riesgos=["accuracy variable en dominios muy nicho"],
            pasos_siguientes=["Mide accuracy", "Si < umbral, refina instrucciones de razonamiento (no ejemplos)"]
        )
    
    # 2. Tarea estándar + dominio estándar
    if ctx.tipo_tarea in ("traduccion", "resumen") and ctx.dominio == "estandar":
        justificacion.append(f"Tarea '{ctx.tipo_tarea}' estándar: zero-shot es suficiente")
        if ctx.formato_critico:
            tecnica = "few-shot"
            n_ejemplos = 2
            justificacion.append("Pero formato crítico: 2 ejemplos para anclar schema")
        return Recomendacion(
            tecnica=tecnica, n_ejemplos=n_ejemplos,
            justificacion=justificacion,
            riesgos=["Formato puede ser inconsistente"] if not ctx.formato_critico else [],
            pasos_siguientes=["Probar con 5 inputs representativos", "Medir consistencia de formato"]
        )
    
    # 3. Clasificación
    if ctx.tipo_tarea == "clasificacion":
        n_cats = ctx.n_categorias or 3
        
        if ctx.dominio == "propietario":
            # Dominio propietario: few-shot siempre
            n_ejemplos = min(max(3, n_cats), 10)  # 1 por categoría, mín 3
            justificacion.append(f"Dominio propietario con {n_cats} categorías: few-shot con {n_ejemplos} ejemplos")
            
            if not ctx.tiene_ejemplos:
                riesgos.append("Sin ejemplos: necesitas generarlos (sintéticos o manual)")
                pasos.append("Genera 2-3 ejemplos sintéticos por categoría con LLM")
                pasos.append("Valida manualmente 20 minutos")
            
            tecnica = "few-shot"
        
        elif ctx.dominio == "nicho" and n_cats > 5:
            tecnica = "few-shot"
            n_ejemplos = 5
            justificacion.append(f"{n_cats} categorías nicho: few-shot obligatorio")
        
        elif ctx.accuracy_requerida > 0.90:
            tecnica = "few-shot"
            n_ejemplos = 5
            justificacion.append(f"Accuracy requerida {ctx.accuracy_requerida:.0%}: few-shot para margen extra")
        
        else:
            justificacion.append("Clasificación estándar: zero-shot como baseline")
            pasos.append("Mide accuracy zero-shot primero. Si < 80%, vuelve con few-shot")
    
    # 4. Extracción
    elif ctx.tipo_tarea == "extraccion":
        if ctx.dominio == "propietario" or ctx.formato_critico:
            tecnica = "few-shot"
            n_ejemplos = 3
            justificacion.append("Extracción con entidades o formato custom: few-shot esencial")
        else:
            tecnica = "zero-shot"
            justificacion.append("Extracción de entidades estándar: zero-shot + formato explícito")
            pasos.append("Especifica explícitamente el schema en el prompt (aunque no des ejemplos)")
    
    # 5. Cálculo de ROI
    if tecnica == "few-shot" and ctx.requests_por_dia > 0 and ctx.costo_por_error_usd > 0:
        tokens_extra_estimado = n_ejemplos * 30  # ~30 tokens por ejemplo
        costo_api_extra_por_mes = (
            ctx.requests_por_dia * 30 * tokens_extra_estimado * 0.15 / 1_000_000
        )
        mejora_estimada = 0.10  # 10% mejora conservadora
        ahorro_errores_por_mes = (
            ctx.requests_por_dia * 30 * mejora_estimada * ctx.costo_por_error_usd
        )
        
        if ahorro_errores_por_mes > costo_api_extra_por_mes * 5:
            justificacion.append(
                f"ROI positivo: ahorro estimado ${ahorro_errores_por_mes:.0f}/mes "
                f"vs costo API extra ${costo_api_extra_por_mes:.2f}/mes"
            )
    
    return Recomendacion(
        tecnica=tecnica, n_ejemplos=n_ejemplos,
        justificacion=justificacion,
        riesgos=riesgos,
        pasos_siguientes=pasos or ["Probar en dataset de 20+ ejemplos antes de producción"]
    )

# Ejemplo de uso
ctx_tickets = ContextoDecision(
    tipo_tarea="clasificacion",
    dominio="propietario",
    accuracy_requerida=0.88,
    tiene_ejemplos=True,
    n_categorias=12,
    formato_critico=False,
    requests_por_dia=500,
    costo_por_error_usd=2.00,
    modelo="gpt-4o-mini"
)

rec = decision_framework(ctx_tickets)
print(f"\nTécnica: {rec.tecnica} ({rec.n_ejemplos} ejemplos)")
print("Justificación:")
for j in rec.justificacion:
    print(f"  • {j}")
if rec.pasos_siguientes:
    print("Próximos pasos:")
    for p in rec.pasos_siguientes:
        print(f"  → {p}")

Patrones Anti-Few-Shot (Cuándo NO Usarlo)

Aunque few-shot mejora en muchos casos, hay patrones donde activamente daña:

Anti-patrón 1: Ejemplos desactualizados

# ❌ Si los ejemplos reflejan el comportamiento antiguo, few-shot fija el error
EJEMPLOS_MALOS = [
    ("Código: ERR-4001", "ACCESO"),        # Antes ERR-4001 era acceso
    ("Código: ERR-5003", "FACTURACION"),   # Ahora ERR-5003 fue redefinido
]

# ✅ Mantén el example bank actualizado o usa zero-shot si los ejemplos pueden quedar obsoletos

Anti-patrón 2: Ejemplos no representativos

# ❌ 3 ejemplos idénticos no enseñan nada nuevo
EJEMPLOS_REDUNDANTES = [
    ("No puedo entrar al sistema", "ACCESO"),
    ("No puedo iniciar sesión", "ACCESO"),
    ("Tengo problemas para acceder", "ACCESO"),
]
# El modelo ya sabe clasificar acceso. Estos tokens son desperdicio.

# ✅ Diversidad de categorías + diversidad dentro de cada categoría
EJEMPLOS_DIVERSOS = [
    ("No puedo entrar al sistema", "ACCESO"),      # Acceso
    ("Mi factura tiene un cargo extra", "FACTURA"), # Factura
    ("Quiero cancelar la suscripción", "CUENTA"),   # Cuenta
]

Anti-patrón 3: Demasiados ejemplos con baja diversidad

# ❌ 10 ejemplos donde 8 son de ACCESO
EJEMPLOS_DESBALANCEADOS = [
    ("No puedo entrar", "ACCESO"),
    ("Error de login", "ACCESO"),
    ("Contraseña incorrecta", "ACCESO"),
    ("No me deja acceder", "ACCESO"),
    ("Error al iniciar sesión", "ACCESO"),
    ("Acceso denegado", "ACCESO"),
    ("Sesión expirada", "ACCESO"),
    ("Bloqueado por intentos fallidos", "ACCESO"),
    ("Factura duplicada", "FACTURA"),
    ("Cargo incorrecto", "FACTURA"),
]
# El modelo aprende a predecir ACCESO para casi todo

# ✅ Balancear ejemplos por categoría

Recursos adicionales

  1. OpenAI Prompt Engineering Guide — Estrategias recomendadas para zero-shot y few-shot
  2. Scaling Laws for Few-Shot Learning (Zhao et al., 2021) — Análisis empírico de cuándo funciona few-shot
  3. Calibrate Before Use: Improving Few-Shot Performance (Zhao et al., 2021) — Impacto del orden de ejemplos en few-shot
  4. Prompt Engineering Guide (DAIR.AI) - Few-Shot — Resumen técnico con benchmarks comparativos
  5. LangSmith Evaluation — Herramienta para hacer benchmarks de prompts de forma sistemática