Patrón Prompt Chaining
Encadena múltiples llamadas LLM donde la salida de cada paso alimenta la entrada del siguiente. Divide tareas complejas en prompts más pequeños y verificables.
Descripción general
Prompt chaining divide una tarea compleja en una secuencia de llamadas LLM más pequeñas. Cada llamada recibe la salida de la anterior como entrada, más su propio prompt enfocado. En lugar de pedirle a un modelo que “investigue, analice, resuma y formatee un reporte” en un solo prompt gigante, encadenas cuatro llamadas separadas: una para investigar, una para analizar, una para resumir, una para formatear.
Cada paso en la cadena tiene un solo objetivo claro. Esto mejora la calidad del output porque el modelo puede enfocarse en una tarea a la vez. También te permite validar entre pasos — si el paso 2 produce basura, puedes reintentar solo ese paso en lugar de todo el pipeline.
Cuándo usarlo
Usa el patrón Prompt Chaining cuando:
- Un solo prompt produce resultados inconsistentes o de baja calidad para una tarea compleja
- Necesitas validación intermedia o revisión humana entre pasos
- Diferentes pasos se benefician de diferentes modelos o parámetros (temperatura, max tokens)
- La tarea tiene una estructura secuencial natural (investigar luego escribir, extraer luego transformar)
- Ejemplos: generación de reportes, pipelines de revisión de código, extracción y formateo de datos, traducción multi-idioma con revisión
Para enrutar entre diferentes modelos según el tipo de tarea, consulta el Patrón LLM Router. Para selección de herramientas basada en agentes dentro de una cadena, consulta el Patrón Agent Tool Selection.
Solución
Python
from dataclasses import dataclass
from typing import Callable, Dict, List, Optional, Any
@dataclass
class ChainStep:
name: str
prompt_template: str
model: str = "gpt-4o"
temperature: float = 0.7
max_retries: int = 2
validator: Optional[Callable[[str], bool]] = None
@dataclass
class ChainResult:
step_name: str
input: str
output: str
success: bool
retries: int
def mock_llm_call(prompt: str, model: str, temperature: float) -> str:
"""Simula una llamada a la API del LLM."""
return f"[{model}] Processed: {prompt[:60]}..."
class PromptChain:
def __init__(self, steps: List[ChainStep]):
self.steps = steps
def run(self, initial_input: str) -> List[ChainResult]:
results: List[ChainResult] = []
current_input = initial_input
for step in self.steps:
prompt = step.prompt_template.format(input=current_input)
success = False
output = ""
retries = 0
for attempt in range(step.max_retries + 1):
output = mock_llm_call(prompt, step.model, step.temperature)
retries = attempt
if step.validator and not step.validator(output):
print(f" Step '{step.name}' validation failed (attempt {attempt + 1})")
continue
success = True
break
results.append(ChainResult(
step_name=step.name,
input=current_input,
output=output,
success=success,
retries=retries,
))
if not success:
print(f"Chain stopped at step '{step.name}' after {retries + 1} attempts")
break
current_input = output
print(f"Step '{step.name}' completed")
return results
# Uso
def validate_non_empty(text: str) -> bool:
return len(text.strip()) > 10
def validate_has_code(text: str) -> bool:
return "```" in text or "def " in text or "function " in text
chain = PromptChain([
ChainStep(
name="extract_requirements",
prompt_template="Extract key requirements from this text:\n{input}\n\nList each requirement as a bullet point.",
model="gpt-4o",
temperature=0.2,
validator=validate_non_empty,
),
ChainStep(
name="generate_code",
prompt_template="Based on these requirements, generate code:\n{input}\n\nProvide working code with comments.",
model="gpt-4o",
temperature=0.3,
validator=validate_has_code,
),
ChainStep(
name="review_code",
prompt_template="Review this code for bugs and improvements:\n{input}\n\nList issues found.",
model="gpt-4o",
temperature=0.5,
validator=validate_non_empty,
),
ChainStep(
name="format_report",
prompt_template="Create a summary report from this review:\n{input}\n\nFormat as markdown with sections.",
model="gpt-4o-mini",
temperature=0.7,
),
])
results = chain.run("Build a REST API endpoint that accepts JSON, validates input, and returns 201 on success")
print(f"\nChain completed: {len(results)}/{len(chain.steps)} steps")
for r in results:
status = "OK" if r.success else "FAILED"
print(f" {r.step_name}: {status} (retries: {r.retries})")
JavaScript
class ChainStep {
constructor(name, promptTemplate, options = {}) {
this.name = name;
this.promptTemplate = promptTemplate;
this.model = options.model || "gpt-4o";
this.temperature = options.temperature ?? 0.7;
this.maxRetries = options.maxRetries ?? 2;
this.validator = options.validator || null;
}
}
function mockLlmCall(prompt, model, temperature) {
return `[${model}] Processed: ${prompt.slice(0, 60)}...`;
}
class PromptChain {
constructor(steps) {
this.steps = steps;
}
async run(initialInput) {
const results = [];
let currentInput = initialInput;
for (const step of this.steps) {
const prompt = step.promptTemplate.replace("{input}", currentInput);
let success = false;
let output = "";
let retries = 0;
for (let attempt = 0; attempt <= step.maxRetries; attempt++) {
output = mockLlmCall(prompt, step.model, step.temperature);
retries = attempt;
if (step.validator && !step.validator(output)) {
console.log(` Step '${step.name}' validation failed (attempt ${attempt + 1})`);
continue;
}
success = true;
break;
}
results.push({ stepName: step.name, input: currentInput, output, success, retries });
if (!success) {
console.log(`Chain stopped at step '${step.name}' after ${retries + 1} attempts`);
break;
}
currentInput = output;
console.log(`Step '${step.name}' completed`);
}
return results;
}
}
// Uso
function validateNonEmpty(text) {
return text.trim().length > 10;
}
function validateHasCode(text) {
return text.includes("```") || text.includes("function ") || text.includes("const ");
}
const chain = new PromptChain([
new ChainStep("extract_requirements", "Extract key requirements from:\n{input}\n\nList as bullet points.", { temperature: 0.2, validator: validateNonEmpty }),
new ChainStep("generate_code", "Generate code from these requirements:\n{input}\n\nProvide working code.", { temperature: 0.3, validator: validateHasCode }),
new ChainStep("review_code", "Review this code for bugs:\n{input}\n\nList issues found.", { temperature: 0.5, validator: validateNonEmpty }),
new ChainStep("format_report", "Create a summary report:\n{input}\n\nFormat as markdown.", { model: "gpt-4o-mini", temperature: 0.7 }),
]);
chain.run("Build a REST API endpoint that accepts JSON, validates input, and returns 201 on success").then(results => {
console.log(`\nChain completed: ${results.length}/${chain.steps.length} steps`);
results.forEach(r => {
const status = r.success ? "OK" : "FAILED";
console.log(` ${r.stepName}: ${status} (retries: ${r.retries})`);
});
});
Java
import java.util.*;
import java.util.function.Predicate;
public class PromptChaining {
record ChainStep(String name, String promptTemplate, String model,
double temperature, int maxRetries, Predicate<String> validator) {}
record ChainResult(String stepName, String input, String output, boolean success, int retries) {}
static String mockLlmCall(String prompt, String model, double temperature) {
return "[" + model + "] Processed: " + prompt.substring(0, Math.min(60, prompt.length())) + "...";
}
static List<ChainResult> runChain(List<ChainStep> steps, String initialInput) {
List<ChainResult> results = new ArrayList<>();
String currentInput = initialInput;
for (ChainStep step : steps) {
String prompt = step.promptTemplate().replace("{input}", currentInput);
boolean success = false;
String output = "";
int retries = 0;
for (int attempt = 0; attempt <= step.maxRetries(); attempt++) {
output = mockLlmCall(prompt, step.model(), step.temperature());
retries = attempt;
if (step.validator() != null && !step.validator().test(output)) {
System.out.printf(" Step '%s' validation failed (attempt %d)%n", step.name(), attempt + 1);
continue;
}
success = true;
break;
}
results.add(new ChainResult(step.name(), currentInput, output, success, retries));
if (!success) {
System.out.printf("Chain stopped at '%s' after %d attempts%n", step.name(), retries + 1);
break;
}
currentInput = output;
System.out.printf("Step '%s' completed%n", step.name());
}
return results;
}
public static void main(String[] args) {
Predicate<String> nonEmpty = s -> s.trim().length() > 10;
Predicate<String> hasCode = s -> s.contains("```") || s.contains("void ") || s.contains("public ");
var steps = List.of(
new ChainStep("extract_requirements",
"Extract key requirements from:\n{input}\n\nList as bullet points.",
"gpt-4o", 0.2, 2, nonEmpty),
new ChainStep("generate_code",
"Generate code from these requirements:\n{input}\n\nProvide working code.",
"gpt-4o", 0.3, 2, hasCode),
new ChainStep("review_code",
"Review this code for bugs:\n{input}\n\nList issues found.",
"gpt-4o", 0.5, 2, nonEmpty),
new ChainStep("format_report",
"Create a summary report:\n{input}\n\nFormat as markdown.",
"gpt-4o-mini", 0.7, 2, null)
);
var results = runChain(steps,
"Build a REST API endpoint that accepts JSON, validates input, and returns 201 on success");
System.out.printf("%nChain completed: %d/%d steps%n", results.size(), steps.size());
for (ChainResult r : results) {
String status = r.success() ? "OK" : "FAILED";
System.out.printf(" %s: %s (retries: %d)%n", r.stepName(), status, r.retries());
}
}
}
Explicación
La cadena ejecuta pasos secuencialmente:
- Propagación de entrada: La entrada inicial entra al primer paso. Cada paso subsiguiente recibe la salida del paso anterior como su entrada.
- Ejecución del paso: Cada paso formatea su plantilla de prompt con la entrada actual, llama al LLM con parámetros específicos del paso (modelo, temperatura) y produce una salida.
- Validación: Si un paso tiene una función validadora, la salida se verifica. Si la validación falla, el paso reintenta hasta
max_retriesveces. Si todos los reintentos fallan, la cadena se detiene. - Colección de resultados: El resultado de cada paso (entrada, salida, éxito, reintentos) se registra para debugging y auditoría.
El patrón intercambia latencia por calidad. Una cadena de 4 pasos toma 4x la latencia de una sola llamada pero produce output más confiable porque cada paso se enfoca en una tarea y puede validarse independientemente. Este enfoque está documentado en el cookbook de OpenAI sobre prompt chaining y la guía de composición de cadenas de LangChain, que ambos recomiendan dividir tareas complejas en pasos más pequeños y verificables. La guía de prompt engineering de Anthropic también cubre la descomposición en cadena para tareas de contexto largo.
Variantes
| Variante | Descripción | Caso de uso |
|---|---|---|
| Fan-out paralelo | Ejecutar 2-4 pasos en paralelo, luego fusionar | Subtareas independientes (ej. traducir a 3 idiomas) |
| Branching condicional | El siguiente paso depende del output anterior | Flujos dinámicos (ej. si el código tiene bugs, ir al paso de fix) |
| Loop con condición de salida | Repetir un paso hasta que se cumpla una condición | Refinamiento iterativo (ej. mejorar hasta score > umbral) |
| Map-reduce | Mapear sobre items en paralelo, reducir resultados | Procesamiento por lotes (ej. resumir 100 documentos) |
Buenas prácticas
- Mantén los prompts cortos y enfocados: cada paso debe tener un solo objetivo claro
- Usa temperatura baja para extracción: 0.1-0.3 para pasos factuales, más alta para pasos creativos
- Valida entre pasos: detecta output malo temprano antes de que se propague
- Cachea resultados intermedios: si el paso 3 falla, no necesitas re-ejecutar los pasos 1 y 2
- Usa modelos más baratos para pasos simples: formateo y resumen pueden usar modelos pequeños
- Registra cada paso: entradas, salidas, modelo y parámetros para debugging
Errores comunes
- Hacer cadenas demasiado largas (10+ pasos), causando latencia excesiva y acumulación de errores
- No validar entre pasos, dejando que el output malo corrompa los pasos siguientes
- Usar el mismo modelo y temperatura para cada paso sin importar el tipo de tarea
- No manejar fallos parciales: si el paso 3 falla, el usuario no recibe output en lugar de resultados parciales
- Pasar output crudo sin limpiar ni formatear entre pasos
- Hardcodear prompts en lugar de usar plantillas, haciendo las cadenas imposibles de reutilizar
- Ignorar límites de tokens: un paso que recibe 8k tokens de contexto del paso anterior puede exceder la ventana de contexto del modelo
- No configurar timeouts en las llamadas al LLM, así un modelo lento o atascado bloquea toda la cadena indefinidamente
Véase también
- Patrón LLM Router para seleccionar modelos por paso según el tipo de tarea
- Patrón Agent Tool Selection para cadenas aumentadas con herramientas
- Guía de prompt engineering de OpenAI para fundamentos de diseño de prompts
- Documentación de chains de LangChain para composición de cadenas a nivel framework
Preguntas frecuentes
¿Cuántos pasos debe tener una cadena?
3-6 pasos funciona mejor. Más allá de eso, la latencia y la probabilidad de fallo se acumulan. Si necesitas más pasos, considera dividir en sub-cadenas o usar un loop de agente.
¿Debo usar el mismo modelo para todos los pasos?
No. Los pasos de extracción y clasificación se benefician de temperatura baja y modelos enfocados. Pasos creativos como escritura o formateo pueden usar parámetros diferentes. Usa el Patrón LLM Router para seleccionar modelos por paso.
¿Cómo manejo un paso que falla después de todos los reintentos?
Devuelve resultados parciales con un estado claro. Deja que el llamador decida si reintentar el paso fallido, saltarlo, o abortar. Nunca ocultes fallos silenciosamente.
¿Puedo paralelizar pasos en una cadena?
Solo si los pasos son independientes. Si el paso B depende del output del paso A, deben ser secuenciales. Para subtareas independientes, usa la variante fan-out paralelo y fusiona los resultados después.
Recursos Relacionados
Patrón LLM Router
Enruta consultas a diferentes modelos LLM segun complejidad, costo y latencia. Clasifica la entrada antes de despachar al modelo adecuado.
PatternPatrón Agent Tool Selection
Selecciona dinamicamente que herramientas puede usar un agente LLM segun el contexto de la tarea. Reduce tokens y mejora la calidad de decisiones.
RecipeLangChain LCEL: componer cadenas con pipes y fallbacks
Construye pipelines LLM componibles con LangChain Expression Language (LCEL) usando pipes, ejecución paralela y componentes runnable personalizados