Módulo 5: ReAct, Self-Consistency y Patrones Avanzados
6. Combinación de Técnicas
Descripción
Las técnicas que hemos visto —CoT, Self-Consistency, ReAct, Tree-of-Thought, Meta-prompting y Self-Refine— no son excluyentes. Combinadas estratégicamente, pueden superar el rendimiento de cualquiera por separado. Pero combinar técnicas indiscriminadamente también puede aumentar el costo sin mejora real.
Esta cápsula explora cuándo y cómo combinar técnicas, con implementaciones prácticas y análisis de cuándo la combinación vale la pena.
Por Qué Combinar Técnicas
Cada técnica resuelve un problema diferente:
| Técnica | Problema que resuelve |
|---|---|
| CoT | Razonamiento interno deficiente |
| Self-Consistency | Un solo camino puede estar mal |
| ReAct | Falta de datos externos |
| ToT | Exploración de estrategias alternativas |
| Meta-prompting | Prompt subóptimo para la tarea |
| Self-Refine | Respuesta inicial de baja calidad |
Cuando una tarea tiene múltiples problemas simultáneos, combinar técnicas ataca cada uno.
Combinación 1: CoT + Self-Consistency (La más común)
La combinación más usada en producción. Cada una de las N respuestas de Self-Consistency usa CoT internamente, lo que mejora tanto la calidad individual de cada respuesta como el consenso final.
from openai import OpenAI
from collections import Counter
import re
client = OpenAI()
def extraer_respuesta_numerica(texto: str) -> str:
"""Extrae la respuesta numérica final de un texto CoT."""
patrones = [
r'(?:respuesta|answer|resultado)[\s:=]+(-?\d+\.?\d*)',
r'=\s*(-?\d+\.?\d*)\s*$',
r'\*\*(-?\d+\.?\d*)\*\*'
]
for patron in patrones:
m = re.search(patron, texto, re.IGNORECASE | re.MULTILINE)
if m:
return m.group(1)
numeros = re.findall(r'-?\d+\.?\d*', texto)
return numeros[-1] if numeros else texto.strip()[-20:]
def cot_self_consistency(
problema: str,
n: int = 5,
temperatura: float = 0.7,
incluir_reasoning: bool = True
) -> dict:
"""
CoT + Self-Consistency: N respuestas con CoT, majority vote.
La diferencia con Self-Consistency básico es que el prompt
explícitamente fuerza un razonamiento más detallado antes de la respuesta.
"""
prompt_cot_detallado = f"""{problema}
Instrucciones:
1. Identifica los datos conocidos y desconocidos
2. Establece qué fórmulas o conceptos aplican
3. Resuelve paso a paso, mostrando CADA operación
4. Verifica si la respuesta tiene sentido
5. Al final escribe: "Respuesta final: [valor]"
"""
respuestas = []
razonamientos = []
for _ in range(n):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt_cot_detallado}],
temperature=temperatura,
max_tokens=600
)
raw = response.choices[0].message.content
respuesta = extraer_respuesta_numerica(raw)
respuestas.append(respuesta)
razonamientos.append(raw)
conteo = Counter(respuestas)
ganadora, votos = conteo.most_common(1)[0]
# Encontrar el razonamiento que llevó a la respuesta ganadora
mejor_razonamiento = next(
(r for r, resp in zip(razonamientos, respuestas) if resp == ganadora),
razonamientos[0]
)
return {
"respuesta": ganadora,
"confianza": votos / n,
"distribucion": dict(conteo),
"mejor_razonamiento": mejor_razonamiento if incluir_reasoning else None
}
# Benchmark vs CoT solo:
problemas_math = [
"María tiene 5 veces más dinero que Juan. Si entre los dos tienen $720, ¿cuánto tiene cada uno?",
"Un tren sale de A a 80 km/h y otro de B a 60 km/h. Están separados por 420 km. ¿En cuánto tiempo se cruzan?",
"Si el IVA es 21%, ¿cuál es el precio sin IVA de un artículo que cuesta $242 con IVA?"
]
for problema in problemas_math:
resultado = cot_self_consistency(problema, n=5)
print(f"Problema: {problema[:60]}...")
print(f" Respuesta: {resultado['respuesta']} (confianza: {resultado['confianza']:.0%})")
Combinación 2: ReAct + Structured Output
ReAct para razonamiento con tools; el output final en formato JSON parseado con Pydantic. Esta combinación es ideal para sistemas de producción que necesitan tanto datos externos como outputs confiables.
from pydantic import BaseModel, Field
from typing import Optional
import json
class RespuestaReAct(BaseModel):
"""Schema para el output estructurado de ReAct."""
respuesta: str = Field(description="La respuesta al problema")
confianza: float = Field(ge=0, le=1, description="Confianza en la respuesta (0-1)")
fuentes_consultadas: list[str] = Field(default_factory=list)
pasos_razonamiento: list[str] = Field(default_factory=list)
requiere_aclaracion: bool = Field(default=False)
def react_con_output_estructurado(problema: str) -> RespuestaReAct:
"""
ReAct que finaliza con output JSON estructurado y validado con Pydantic.
"""
# Primero ejecutar ReAct normal para obtener el razonamiento
from .02_react_reasoning_acting import run_react # En producción, importar correctamente
# Adaptar el prompt final para pedir JSON
system_prompt = """Eres un asistente que resuelve problemas usando herramientas.
Al terminar, responde SIEMPRE en JSON con este formato:
{
"respuesta": "la respuesta al problema",
"confianza": 0.9,
"fuentes_consultadas": ["herramienta1", "herramienta2"],
"pasos_razonamiento": ["paso1", "paso2"],
"requiere_aclaracion": false
}"""
# Versión inline para no depender de imports externos
TOOLS_BASICAS = [
{
"type": "function",
"function": {
"name": "buscar",
"description": "Busca información factual",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calcular",
"description": "Evalúa expresiones matemáticas",
"parameters": {
"type": "object",
"properties": {"expresion": {"type": "string"}},
"required": ["expresion"]
}
}
}
]
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": problema}
]
herramientas_usadas = []
pasos = []
for _ in range(8):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOLS_BASICAS,
tool_choice="auto",
temperature=0
)
msg = response.choices[0].message
if not msg.tool_calls:
# Respuesta final - parsear como JSON
try:
# Intentar extraer JSON del texto
contenido = msg.content or "{}"
json_match = re.search(r'\{.*\}', contenido, re.DOTALL)
if json_match:
datos = json.loads(json_match.group())
datos["fuentes_consultadas"] = herramientas_usadas
datos["pasos_razonamiento"] = pasos
return RespuestaReAct(**datos)
except Exception:
pass
# Fallback
return RespuestaReAct(
respuesta=msg.content or "Sin respuesta",
confianza=0.5,
fuentes_consultadas=herramientas_usadas,
pasos_razonamiento=pasos
)
messages.append({
"role": "assistant",
"content": msg.content or "",
"tool_calls": [tc.model_dump() for tc in msg.tool_calls]
})
for tc in msg.tool_calls:
tool_name = tc.function.name
args = json.loads(tc.function.arguments)
# Simular herramientas
if tool_name == "buscar":
resultado = f"Resultado de búsqueda: {args.get('query', '')} - [datos simulados]"
elif tool_name == "calcular":
try:
resultado = str(eval(args.get('expresion', '0'), {"__builtins__": {}}))
except Exception:
resultado = "Error de cálculo"
else:
resultado = "Herramienta no disponible"
herramientas_usadas.append(tool_name)
pasos.append(f"{tool_name}({args})")
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": resultado
})
return RespuestaReAct(respuesta="Timeout", confianza=0.0)
# Uso:
resultado = react_con_output_estructurado("¿Cuánto es 15% de 1500 EUR en USD usando el tipo de cambio actual?")
print(f"Respuesta: {resultado.respuesta}")
print(f"Confianza: {resultado.confianza:.0%}")
print(f"Pasos: {resultado.pasos_razonamiento}")
Combinación 3: ToT + Self-Consistency
Para problemas donde hay múltiples estrategias válidas Y queremos verificar el resultado por consenso.
def tot_self_consistency(
problema: str,
n_arboles: int = 3,
breadth_por_arbol: int = 2
) -> dict:
"""
Ejecuta N árboles de ToT independientes y vota por la respuesta más común.
Cada árbol parte de un enfoque diferente (seed de temperatura),
y la respuesta final es por majority vote sobre los N árboles.
"""
respuestas_arboles = []
for i in range(n_arboles):
# Cada árbol usa temperatura diferente para diversidad
temperatura = 0.5 + (i * 0.2)
prompt_enfoques = f"""Problema: {problema}
Genera {breadth_por_arbol} enfoques distintos para resolverlo.
Elige los enfoques más DIFERENTES entre sí posibles.
Número de enfoque seguido de descripción del primer paso."""
resp_enfoques = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt_enfoques}],
temperature=temperatura,
max_tokens=300
)
lineas = [l.lstrip('0123456789.-) ').strip()
for l in resp_enfoques.choices[0].message.content.split('\n')
if l.strip() and l.strip()[0].isdigit()][:breadth_por_arbol]
# Evaluar y seleccionar mejor enfoque
if not lineas:
continue
scores = []
for enfoque in lineas:
eval_resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Problema: {problema}\nEnfoque: {enfoque}\n¿Prometedor? (0-1)"}],
temperature=0, max_tokens=10
)
try:
score = float(eval_resp.choices[0].message.content.strip()[:4])
except ValueError:
score = 0.5
scores.append((score, enfoque))
mejor_enfoque = max(scores, key=lambda x: x[0])[1]
# Resolver desde mejor enfoque
solucion_resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{problema}\n\nEnfoque: {mejor_enfoque}\n\nResuelve. Respuesta final: [valor]"}],
temperature=0,
max_tokens=400
)
respuesta = extraer_respuesta_numerica(solucion_resp.choices[0].message.content)
respuestas_arboles.append(respuesta)
if not respuestas_arboles:
return {"respuesta": "Sin resultado", "confianza": 0.0}
# Majority vote sobre resultados de los árboles
conteo = Counter(respuestas_arboles)
ganadora, votos = conteo.most_common(1)[0]
return {
"respuesta": ganadora,
"confianza": votos / len(respuestas_arboles),
"respuestas_por_arbol": respuestas_arboles,
"n_arboles": len(respuestas_arboles)
}
Combinación 4: Meta-Prompting + Self-Refine + Evaluation
El pipeline completo de optimización automática de prompts:
def pipeline_optimizacion_completo(
descripcion_tarea: str,
dataset_evaluacion: list[dict],
n_iteraciones: int = 3
) -> dict:
"""
Pipeline completo de optimización:
1. Meta-prompting genera prompt inicial
2. Se evalúa en dataset
3. Self-Refine mejora el prompt basado en errores
4. Repetir hasta convergencia o N iteraciones
Args:
descripcion_tarea: Descripción de la tarea
dataset_evaluacion: Lista de {input: str, expected: str}
n_iteraciones: Iteraciones de mejora
Returns:
dict con mejor_prompt, accuracy_historico, mejoras
"""
# Paso 1: Generar prompt inicial con meta-prompting
meta_prompt = f"""Crea un prompt óptimo para: {descripcion_tarea}
El prompt debe ser específico, incluir formato de salida claro, y manejar edge cases.
Devuelve solo el prompt."""
prompt_actual = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": meta_prompt}],
temperature=0.3,
max_tokens=500
).choices[0].message.content.strip()
historial = []
for iteracion in range(n_iteraciones):
# Paso 2: Evaluar prompt actual
errores = []
aciertos = 0
for ejemplo in dataset_evaluacion[:5]: # Limitar para ahorrar llamadas
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{prompt_actual}\n\nInput: {ejemplo['input']}"}],
temperature=0,
max_tokens=200
).choices[0].message.content
expected = ejemplo.get("expected", "")
correcto = expected.lower() in resp.lower() if expected else False
if correcto:
aciertos += 1
else:
errores.append({
"input": ejemplo["input"][:100],
"esperado": expected,
"obtenido": resp[:100]
})
accuracy = aciertos / min(len(dataset_evaluacion), 5)
historial.append({
"iteracion": iteracion,
"accuracy": accuracy,
"prompt": prompt_actual[:200]
})
if accuracy >= 0.9:
break
# Paso 3: Self-Refine del prompt basado en errores
if errores:
errores_str = json.dumps(errores[:3], ensure_ascii=False, indent=2)
refine_prompt = f"""Tarea: {descripcion_tarea}
Prompt actual:
{prompt_actual}
Errores cometidos al usar este prompt:
{errores_str}
Mejora el prompt para corregir estos errores sin romper los casos que funcionan.
Devuelve solo el prompt mejorado."""
prompt_mejorado = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": refine_prompt}],
temperature=0.2,
max_tokens=500
).choices[0].message.content.strip()
prompt_actual = prompt_mejorado
return {
"prompt_final": prompt_actual,
"accuracy_historico": [h["accuracy"] for h in historial],
"iteraciones": historial,
"mejora_total": historial[-1]["accuracy"] - historial[0]["accuracy"] if historial else 0
}
Cuándo Combinar vs Cuando No
Cuándo combinar ES útil
Tarea con MÚLTIPLES requisitos simultáneos:
├── Necesita datos externos + Accuracy crítica
│ └── → ReAct + Self-Consistency
├── Múltiples estrategias + Verificación
│ └── → ToT + Self-Consistency
├── Prompt subóptimo + Respuesta mejorable
│ └── → Meta-prompting + Self-Refine
└── Datos externos + Output estructurado
└── → ReAct + Structured Output
Cuándo combinar NO es útil (overkill)
# OVERKILL: Tarea simple de clasificación
def clasificar_sentimiento_overkill(texto: str) -> str:
# INCORRECTO: No necesita ToT ni Self-Consistency para clasificación simple
return tot_self_consistency(
f"Clasifica el sentimiento: {texto}",
n_arboles=5, breadth_por_arbol=3
)["respuesta"]
# CORRECTO: Few-shot es suficiente
def clasificar_sentimiento_optimo(texto: str) -> str:
return client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""
Clasifica el sentimiento como POSITIVO, NEGATIVO o NEUTRAL.
Ejemplos: "Me encantó" → POSITIVO | "Horrible" → NEGATIVO | "Normal" → NEUTRAL
Texto: {texto}
Responde solo con: POSITIVO, NEGATIVO, o NEUTRAL"""}],
temperature=0
).choices[0].message.content.strip()
Regla general: escala de complejidad
| Si la tarea es... | Técnica |
|---|---|
| Clasificación/extracción simple | Zero-shot o Few-shot |
| Razonamiento matemático | CoT |
| Razonamiento matemático crítico | CoT + Self-Consistency |
| Requiere datos externos | ReAct |
| Requiere datos externos + accuracy | ReAct + Self-Consistency |
| Múltiples estrategias posibles | ToT |
| Múltiples estrategias + verificación | ToT + Self-Consistency |
| Prompt subóptimo conocido | Meta-prompting |
| Calidad de respuesta crítica | Self-Refine |
| Sistema de producción completo | Meta-prompting + Self-Refine + evaluación |
Medición del Valor de Combinar
def benchmark_combinaciones(
problemas: list[dict], # [{"enunciado": str, "respuesta": str}]
tecnicas_a_evaluar: list[str] = None
) -> dict:
"""
Compara múltiples técnicas y combinaciones en un conjunto de problemas.
Args:
problemas: Lista de problemas con respuesta correcta conocida
tecnicas_a_evaluar: Lista de técnicas a comparar
"""
if tecnicas_a_evaluar is None:
tecnicas_a_evaluar = ["cot_solo", "cot_sc_n3", "cot_sc_n5"]
resultados = {t: {"aciertos": 0, "total": len(problemas), "llamadas": 0}
for t in tecnicas_a_evaluar}
def normalizar(texto: str) -> str:
try:
return str(round(float(re.sub(r'[^\d.-]', '', texto.split()[-1] if texto.split() else texto)), 1))
except ValueError:
return texto.strip().upper()
for problema in problemas:
enunciado = problema["enunciado"]
correcta = normalizar(str(problema["respuesta"]))
for tecnica in tecnicas_a_evaluar:
if tecnica == "cot_solo":
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{enunciado}\nPiensa paso a paso. Respuesta: [número]"}],
temperature=0, max_tokens=400
).choices[0].message.content
pred = normalizar(extraer_respuesta_numerica(resp))
resultados["cot_solo"]["llamadas"] += 1
elif tecnica == "cot_sc_n3":
r = cot_self_consistency(enunciado, n=3, incluir_reasoning=False)
pred = normalizar(r["respuesta"])
resultados["cot_sc_n3"]["llamadas"] += 3
elif tecnica == "cot_sc_n5":
r = cot_self_consistency(enunciado, n=5, incluir_reasoning=False)
pred = normalizar(r["respuesta"])
resultados["cot_sc_n5"]["llamadas"] += 5
else:
pred = "?"
if pred == correcta or (len(pred) > 0 and correcta in pred):
resultados[tecnica]["aciertos"] += 1
# Calcular métricas
for tecnica in resultados:
r = resultados[tecnica]
r["accuracy"] = r["aciertos"] / r["total"]
r["llamadas_por_problema"] = r["llamadas"] / r["total"]
return resultados
# Ejemplo:
benchmark_data = [
{"enunciado": "¿Cuánto es 15% de 480?", "respuesta": 72},
{"enunciado": "Si 3x + 5 = 17, ¿cuánto vale x?", "respuesta": 4},
{"enunciado": "Un tren recorre 240 km en 3 horas. ¿Cuál es su velocidad media en km/h?", "respuesta": 80},
]
metricas = benchmark_combinaciones(benchmark_data)
for tecnica, datos in metricas.items():
print(f"{tecnica}: accuracy={datos['accuracy']:.0%}, llamadas/problema={datos['llamadas_por_problema']:.1f}")
Combinación Práctica: Sistema de Q&A Robusto
Un sistema de preguntas y respuestas que combina múltiples técnicas según el tipo de pregunta:
def qa_robusto(pregunta: str) -> dict:
"""
Sistema de Q&A que selecciona automáticamente la combinación correcta.
"""
# Paso 1: Clasificar la pregunta
clasificacion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""
Clasifica esta pregunta. Responde JSON: {{"tipo": "...", "necesita_datos_externos": true/false, "accuracy_critica": true/false}}
Tipos: math (operaciones numéricas), logica (razonamiento), factual (hechos del mundo), creative (sin respuesta correcta única)
Pregunta: {pregunta}"""}],
temperature=0,
response_format={"type": "json_object"}
).choices[0].message.content
try:
clasif = json.loads(clasificacion)
except Exception:
clasif = {"tipo": "logica", "necesita_datos_externos": False, "accuracy_critica": False}
tipo = clasif.get("tipo", "logica")
necesita_externos = clasif.get("necesita_datos_externos", False)
accuracy_critica = clasif.get("accuracy_critica", False)
# Paso 2: Seleccionar técnica óptima
if necesita_externos and accuracy_critica:
# ReAct + Self-Consistency (más caro pero más preciso)
# Simplificado: usar CoT + Self-Consistency con nota de que faltan datos externos
resultado = cot_self_consistency(pregunta, n=5)
tecnica_usada = "cot_sc_n5 (idealmente: react+sc)"
elif necesita_externos:
# ReAct
from openai import OpenAI
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{pregunta}\nNota: si necesitas datos externos, indícalo explícitamente."}],
temperature=0, max_tokens=400
).choices[0].message.content
resultado = {"respuesta": resp, "confianza": 0.7}
tecnica_usada = "react_simplificado"
elif tipo == "math" and accuracy_critica:
# CoT + Self-Consistency
resultado = cot_self_consistency(pregunta, n=5)
tecnica_usada = "cot_sc_n5"
elif tipo in ["math", "logica"]:
# CoT solo
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{pregunta}\nPiensa paso a paso."}],
temperature=0, max_tokens=400
).choices[0].message.content
resultado = {"respuesta": resp, "confianza": 0.8}
tecnica_usada = "cot_solo"
else:
# Zero-shot/few-shot para factual y creative
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": pregunta}],
temperature=0.3, max_tokens=400
).choices[0].message.content
resultado = {"respuesta": resp, "confianza": 0.75}
tecnica_usada = "zero_shot"
return {
"respuesta": resultado.get("respuesta", ""),
"confianza": resultado.get("confianza", 0.5),
"clasificacion": clasif,
"tecnica_usada": tecnica_usada
}
# Prueba:
preguntas = [
"¿Cuánto es el 23% de 1,540?",
"¿Cuáles son las tendencias en renewable energy en 2026?",
"Si todos los A son B, y algunos B son C, ¿necesariamente algunos A son C?"
]
for p in preguntas:
r = qa_robusto(p)
print(f"\nPregunta: {p}")
print(f"Técnica: {r['tecnica_usada']}")
print(f"Respuesta: {r['respuesta'][:100]}...")
Troubleshooting
Problema 1: La combinación es más lenta sin mejora perceptible
Síntoma: Combinar CoT + Self-Consistency para tareas simples no mejora nada y multiplica el costo 5x.
Diagnóstico y solución:
def combinar_solo_si_vale(problema: str) -> dict:
"""Primero intenta CoT simple; solo usa SC si la confianza es baja."""
# Intento 1: CoT simple
resp_inicial = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso. Respuesta: [valor]"}],
temperature=0, max_tokens=400
).choices[0].message.content
# Evaluar confianza del modelo (a través de la longitud del razonamiento)
# Respuestas muy cortas o con "no sé" indican baja confianza
confianza_baja = (
len(resp_inicial.split()) < 30 or
any(p in resp_inicial.lower() for p in ["no sé", "not sure", "unclear", "ambiguous"])
)
if not confianza_baja:
return {"respuesta": extraer_respuesta_numerica(resp_inicial), "tecnica": "cot_solo"}
# Si confianza baja, usar Self-Consistency
resultado = cot_self_consistency(problema, n=3)
return {**resultado, "tecnica": "cot_sc"}
Problema 2: ReAct + Self-Consistency es demasiado caro
Síntoma: ReAct con 4 pasos * 5 muestras = 20+ llamadas por pregunta.
Solución: Usar Self-Consistency solo para el resultado final de ReAct:
def react_con_verificacion(problema: str, n_verificaciones: int = 3) -> dict:
"""ReAct una vez, luego verificar el resultado con SC."""
# ReAct una sola vez
resultado_react = run_react(problema, verbose=False)
respuesta_react = resultado_react["respuesta"]
# Verificar la respuesta de ReAct con SC
verificaciones = []
for _ in range(n_verificaciones):
v = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Verifica si esta respuesta es correcta para el problema dado:\nProblema: {problema}\nRespuesta propuesta: {respuesta_react}\n\nResponde SOLO: CORRECTO o INCORRECTO"}],
temperature=0.3
).choices[0].message.content.strip().upper()
verificaciones.append("CORRECTO" in v)
confianza = sum(verificaciones) / len(verificaciones)
return {
"respuesta": respuesta_react,
"verificada": confianza > 0.6,
"confianza_verificacion": confianza,
"n_llamadas_total": resultado_react.get("steps_used", 4) + n_verificaciones
}
Ejercicios
Ejercicio 1: Diseñar la combinación óptima
Para cada uno de estos sistemas, diseña qué combinación de técnicas usarías y justifica tu elección:
a) Sistema de tutoring de matemáticas para estudiantes de secundaria b) Chatbot de soporte técnico que consulta una base de conocimientos c) Generador de código que debe producir código sin bugs d) Sistema de análisis de riesgo financiero
Ver solución
a) Tutoring de matemáticas:
- CoT + Self-Consistency (N=3-5) para validar respuestas matemáticas
- Self-Refine para explicaciones pedagógicas
- Justificación: la accuracy es crítica, y las explicaciones deben ser claras
b) Chatbot de soporte técnico:
- ReAct (para consultar base de conocimientos) + Structured Output
- Clasificación previa para rutear preguntas simples vs complejas
- Justificación: necesita datos externos, output debe ser parseable
c) Generador de código:
- Self-Refine con criterios técnicos específicos (correctitud, eficiencia, seguridad)
- Meta-prompting para encontrar el mejor prompt de generación
- Justificación: calidad crítica, múltiples iteraciones mejoran el código
d) Análisis de riesgo financiero:
- ReAct (datos de mercado) + CoT + Self-Consistency
- Structured Output para el reporte final
- Justificación: necesita datos actuales, accuracy crítica, output formal
Ejercicio 2: Benchmark de costos
Implementa un tracker de costos que registre cuántas llamadas API hace cada combinación y calcula el costo estimado por problema.
Ver solución
class CostTracker:
PRECIOS_POR_1K_TOKENS = {
"gpt-4o-mini": {"input": 0.00015, "output": 0.00060},
"gpt-4o": {"input": 0.0025, "output": 0.01}
}
def __init__(self):
self.llamadas = []
def registrar(self, modelo: str, tokens_entrada: int, tokens_salida: int):
precios = self.PRECIOS_POR_1K_TOKENS.get(modelo, self.PRECIOS_POR_1K_TOKENS["gpt-4o-mini"])
costo = (tokens_entrada / 1000 * precios["input"] +
tokens_salida / 1000 * precios["output"])
self.llamadas.append({
"modelo": modelo,
"tokens_entrada": tokens_entrada,
"tokens_salida": tokens_salida,
"costo_usd": costo
})
def costo_total(self) -> float:
return sum(l["costo_usd"] for l in self.llamadas)
def resumen(self) -> dict:
return {
"n_llamadas": len(self.llamadas),
"costo_total_usd": self.costo_total(),
"costo_promedio_usd": self.costo_total() / len(self.llamadas) if self.llamadas else 0
}
tracker = CostTracker()
# Para usar: cada vez que haces una llamada, registrar tokens
# response = client.chat.completions.create(...)
# tracker.registrar("gpt-4o-mini", response.usage.prompt_tokens, response.usage.completion_tokens)
Resumen
- CoT + Self-Consistency: La combinación más útil en producción. Cada muestra de SC usa CoT internamente. Mejora +5-15% con N=5.
- ReAct + Structured Output: Para sistemas de producción. Datos externos + output parseable.
- ToT + Self-Consistency: Para problemas muy difíciles con múltiples estrategias. Muy caro, usar con moderación.
- Meta-prompting + Self-Refine: Para optimización sistemática de prompts.
- La regla de oro: Combinar solo cuando cada técnica resuelve un problema distinto y real. No combinar por combinar.