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
| Criterio | Zero-Shot | Few-Shot (3 ej) | Few-Shot (5 ej) |
|---|---|---|---|
| Tokens por request | ~100-200 | ~300-500 | ~500-800 |
| Latencia relativa | 1x | ~1.3x | ~1.6x |
| Costo relativo | 1x | ~1.5-2x | ~2-3x |
| Setup inicial | Ninguno | Requiere 3 ejemplos etiquetados | Requiere 5 ejemplos |
| Mantenimiento | Bajo | Medio (gestionar ejemplo bank) | Medio-alto |
| Consistencia de formato | Media | Alta | Muy alta |
| Accuracy en tareas comunes | Alta (85-92%) | Similar o +3-5% | +3-8% |
| Accuracy en dominios nicho | Media (65-75%) | Alta (80-90%) | Alta (82-92%) |
| Generalización | Alta (usa conocimiento del modelo) | Media (depende de ejemplos) | Media |
| Edge cases no cubiertos | Comportamiento incierto | Depende de coverage de ejemplos | Mejor 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:
| Tarea | Zero-Shot | Few-Shot (3 ej) | Recomendación | Nota |
|---|---|---|---|---|
| Clasificación sentimiento (Pos/Neg/Neu) | 85-92% | 88-94% | Zero-shot | Tarea bien conocida |
| Extracción NER estándar | 80-88% | 88-95% | Few-shot si formato custom | |
| Traducción EN→ES | 90-95% | 92-96% | Zero-shot | Mejora marginal |
| Resumen libre | 75-85% | 80-88% | Zero-shot | Few-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-shot | Formato específico |
| QA sobre documentos | 70-80% | 75-85% | Similar | Few-shot si schema de respuesta |
| Generación de código | 78-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-shot | Accuracy 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 internas | Las categorías son genéricas (sentimiento, NER estándar) |
| Tienes ejemplos etiquetados de calidad | No tienes ejemplos y el dominio es nuevo |
| El formato de salida es específico | El 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:
- Genera 10 ejemplos sintéticos con LLM:
"Dame 10 ejemplos de tickets para categoría ACCESO" - Valida manualmente que sean correctos (5 minutos)
- Úsalos como few-shot inicial
- 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:
| Tarea | Zero-shot | Few-shot | Por qué |
|---|---|---|---|
| Clasificar sentimiento | 88% | 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 propio | 60% | 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
- OpenAI Prompt Engineering Guide — Estrategias recomendadas para zero-shot y few-shot
- Scaling Laws for Few-Shot Learning (Zhao et al., 2021) — Análisis empírico de cuándo funciona few-shot
- Calibrate Before Use: Improving Few-Shot Performance (Zhao et al., 2021) — Impacto del orden de ejemplos en few-shot
- Prompt Engineering Guide (DAIR.AI) - Few-Shot — Resumen técnico con benchmarks comparativos
- LangSmith Evaluation — Herramienta para hacer benchmarks de prompts de forma sistemática