Módulo 3: Pipelines secuenciales

Mini-proyecto: el pipeline de Reservo

Descripción

Siete lecciones te dejaron con un pipeline completo: la anatomía de una etapa (02), el mecanismo de encadenar el dato (03), las tres etapas corriendo de punta a punta (04), su costo de coordinación medido contra un supervisor (05), la guarda que lo detiene a tiempo (06), y el criterio completo para elegir pipeline sobre supervisor —con el hallazgo de que dos de sus tres etapas ni siquiera dependían entre sí (07). Este mini-proyecto no agrega ningún concepto nuevo — te da tres escenarios de Reservo que nunca viste y te pide correr el pipeline completo, con su guarda, de punta a punta sobre cada uno.

El entregable de esta lección es run_pipeline_guarded corriendo contra los tres escenarios del encargo: dos que completan las tres etapas —cotizar, validar, confirmar— y uno que activa la guarda de la lección 06 antes de llegar a confirmar nada.

Conexión con el módulo

Este mini-proyecto es la síntesis de las siete lecciones anteriores, no una lección nueva. De la 02 usas PipelineStage y PIPELINE_STAGES. De la 03 y la 04, build_stage_task, extract_payload y el mecanismo completo de tres etapas. De la 05, el vocabulario de costo de coordinación. De la 06, stage_ok, PipelineHalted y run_pipeline_guarded — el runner que de verdad vas a ejecutar acá. De la 07, el criterio para reconocer, sin construirlo todavía, qué etapas de este mismo encargo podrían en el futuro correr en paralelo. Cuando termines, el Módulo 4 toma exactamente esa pregunta pendiente y construye el fan-out real.


El encargo

Reservo te pasa tres escenarios que llegaron la misma semana:

Escenario 1: "Cotiza y reserva Studio pro 4h para Lucía, validando antes la política de cancelación."
Escenario 2: "Cotiza y reserva Boardroom basic 3h para Diego, validando antes la política de cancelación."
Escenario 3: "NorthCorp Ops necesita Studio pro 10h -- es una cuenta corporativa, así que antes de
              confirmar hay que validar la política aplicable a reservas corporativas grandes."

Tu encargo tiene dos entregables:

a) Para cada escenario, corre run_pipeline_guarded con PIPELINE_STAGES completo —cotizar, validar política, confirmar— y el guion (concepto) correspondiente a cada etapa.

b) Para el escenario que active la guarda de la lección 06, confirma que el pipeline se detiene ANTES de la etapa confirm — y que reservo_tools.BOOKINGS nunca recibe una reserva de esa cuenta corporativa sin política validada.


La solución completa (el entregable)

Ver la solución completa
import ast
import concurrent.futures
from dataclasses import dataclass
import reservo_tools as rt


def dispatch_parallel(tool_use_blocks, tools):
    with concurrent.futures.ThreadPoolExecutor(max_workers=len(tool_use_blocks)) as pool:
        futures = [pool.submit(tools[b["name"]], **b["input"]) for b in tool_use_blocks]
        results = [f.result() for f in futures]
    return [
        {"type": "tool_result", "tool_use_id": b["id"], "content": str(r)}
        for b, r in zip(tool_use_blocks, results)
    ]


def run_agent_parallel(question, model_script, tools, max_iterations=10):
    messages = [{"role": "user", "content": question}]
    for step in range(max_iterations):
        turn = model_script[step]
        messages.append({"role": "assistant", "content": turn["content"]})
        if turn["stop_reason"] != "tool_use":
            return turn, messages
        tool_result_blocks = dispatch_parallel(turn["content"], tools)
        messages.append({"role": "user", "content": tool_result_blocks})
    raise RuntimeError(f"max_iterations alcanzado ({max_iterations})")


POLICY_DOCS = {
    "no-show-policy": (
        "Si un miembro no se presenta a una reserva confirmada y no cancela "
        "con al menos 2 horas de anticipación, Reservo cobra el 50% del "
        "precio cotizado como cargo por no-presentación."
    ),
    "cancellation-policy": (
        "Las reservas se pueden cancelar sin cargo hasta 2 horas antes del "
        "horario reservado. Cancelaciones dentro de esas 2 horas aplican "
        "el cargo de no-presentación."
    ),
}


def search_docs(query):
    q = query.lower()
    if "cancela" in q:
        return f"[cancellation-policy] {POLICY_DOCS['cancellation-policy']}"
    if "no" in q and ("present" in q or "show" in q):
        return f"[no-show-policy] {POLICY_DOCS['no-show-policy']}"
    return "No se encontró una política relevante para esa pregunta."


SPECIALISTS = {
    "booking_agent": {
        "tools": {
            "list_rooms": rt.list_rooms, "get_quote": rt.get_quote,
            "book_room": rt.book_room, "cancel_booking": rt.cancel_booking,
        },
        "expertise": "cotizar, reservar y cancelar salas",
    },
    "policy_agent": {
        "tools": {"search_docs": search_docs},
        "expertise": "responder preguntas de política (cancelación, no-presentación)",
    },
}


def run_specialist(name, task, model_script):
    tools = SPECIALISTS[name]["tools"]
    return run_agent_parallel(task, model_script, tools)


@dataclass
class PipelineStage:
    kind: str
    name: str
    label: str


def last_tool_result(history):
    for m in reversed(history):
        if isinstance(m["content"], list):
            for b in m["content"]:
                if b["type"] == "tool_result":
                    try:
                        return ast.literal_eval(b["content"])
                    except (ValueError, SyntaxError):
                        return b["content"]
    return None


def build_stage_task(stage, payload):
    if stage.kind == "quote":
        return f"Cotiza {payload['room']} {payload['tier']} {payload['hours']}h."
    if stage.kind == "validate_policy":
        if payload.get("client_type") == "corporate":
            return (f"¿Qué política aplica para una reserva corporativa grande de "
                     f"{payload['room']}, antes de confirmarla para {payload['member']}?")
        return (f"¿Cuál es la política de cancelación para una reserva de "
                 f"{payload['room']} de {payload['hours']}h, antes de confirmarla?")
    if stage.kind == "confirm":
        if payload.get("cleared_to_book"):
            return (f"Reserva {payload['room']} {payload['tier']} {payload['hours']}h "
                     f"para {payload['member']} -- la política de cancelación ya se validó.")
        return (f"Reserva {payload['room']} {payload['tier']} {payload['hours']}h "
                 f"para {payload['member']}.")
    raise ValueError(f"no sé armar la tarea de la etapa {stage.kind!r}")


def extract_payload(stage, history, payload):
    new_payload = dict(payload)
    result = last_tool_result(history)
    if stage.kind == "quote":
        new_payload["price_cents"] = result["price_cents"]
    elif stage.kind == "validate_policy":
        new_payload["policy_result"] = result
        new_payload["cleared_to_book"] = isinstance(result, str) and result.startswith("[")
    elif stage.kind == "confirm":
        new_payload["booking_id"] = result["booking_id"]
        new_payload["confirmed"] = result["confirmed"]
    return new_payload


class PipelineHalted(Exception):
    def __init__(self, reason, trace):
        super().__init__(reason)
        self.trace = trace


def stage_ok(stage, payload):
    if stage.kind == "validate_policy" and not payload.get("cleared_to_book"):
        return False, "no se encontró una política relevante -- no hay base para confirmar"
    return True, None


def run_pipeline_guarded(stages, model_scripts, initial_payload):
    payload = dict(initial_payload)
    trace = []
    for i, stage in enumerate(stages):
        task = build_stage_task(stage, payload)
        final, history = run_specialist(stage.name, task, model_scripts[i])
        payload = extract_payload(stage, history, payload)
        trace.append({
            "stage": i + 1, "kind": stage.kind, "agent": stage.name,
            "label": stage.label, "task": task,
            "output": final["content"][0]["text"],
        })
        ok, reason = stage_ok(stage, payload)
        if not ok:
            raise PipelineHalted(
                f"pipeline detenido después de la etapa {i + 1} ({stage.label}): {reason}",
                trace,
            )
    return payload, trace


PIPELINE_STAGES = [
    PipelineStage(kind="quote", name="booking_agent", label="cotizar"),
    PipelineStage(kind="validate_policy", name="policy_agent",
                  label="validar la política de cancelación"),
    PipelineStage(kind="confirm", name="booking_agent", label="confirmar la reserva"),
]

# === Escenario 1: Studio pro 4h para Lucía -- pipeline completo ===
payload_1 = {"room": "Studio", "tier": "pro", "hours": 4, "member": "Lucía"}
scripts_1 = [
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "get_quote",
             "input": {"room": "Studio", "tier": "pro", "hours": 4}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Studio pro 4h cuesta 12800 centavos."}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "search_docs",
             "input": {"query": "política de cancelación"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": (
                "Puedes cancelar sin cargo hasta 2 horas antes del horario "
                "reservado. No hay ningún impedimento para confirmar."
            )}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "book_room",
             "input": {"room": "Studio", "tier": "pro", "hours": 4, "member": "Lucía"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Reservé Studio pro 4h para Lucía (confirmación #1)."}]},
    ],
]

# === Escenario 2: Boardroom basic 3h para Diego -- pipeline completo ===
payload_2 = {"room": "Boardroom", "tier": "basic", "hours": 3, "member": "Diego"}
scripts_2 = [
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "get_quote",
             "input": {"room": "Boardroom", "tier": "basic", "hours": 3}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Boardroom basic 3h cuesta 24000 centavos."}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "search_docs",
             "input": {"query": "política de cancelación"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": (
                "Puedes cancelar sin cargo hasta 2 horas antes del horario "
                "reservado. No hay ningún impedimento para confirmar."
            )}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "book_room",
             "input": {"room": "Boardroom", "tier": "basic", "hours": 3, "member": "Diego"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Reservé Boardroom basic 3h para Diego (confirmación #2)."}]},
    ],
]

# === Escenario 3: Studio pro 10h para NorthCorp Ops -- corporativo, activa la guarda ===
payload_3 = {
    "room": "Studio", "tier": "pro", "hours": 10, "member": "NorthCorp Ops",
    "client_type": "corporate",
}
scripts_3 = [
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "get_quote",
             "input": {"room": "Studio", "tier": "pro", "hours": 10}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Studio pro 10h cuesta 32000 centavos."}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "search_docs",
             "input": {"query": "política para reservas corporativas grandes"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": (
                "No encontré una política específica para reservas corporativas "
                "grandes en nuestra base -- te recomiendo escribir al equipo de "
                "cuentas corporativas antes de confirmar."
            )}]},
    ],
]

for label, payload, scripts in [("1", payload_1, scripts_1), ("2", payload_2, scripts_2), ("3", payload_3, scripts_3)]:
    print(f"=== Escenario {label}: {payload['room']} {payload['tier']} {payload['hours']}h para {payload['member']} ===")
    try:
        final_payload, trace = run_pipeline_guarded(PIPELINE_STAGES, scripts, payload)
        for step in trace:
            print(f"  etapa {step['stage']} ({step['label']}) -> {step['agent']}: {step['output']!r}")
        print(f"  RESULTADO: pipeline completo. booking_id={final_payload['booking_id']}, price_cents={final_payload['price_cents']}")
    except PipelineHalted as e:
        for step in e.trace:
            print(f"  etapa {step['stage']} ({step['label']}) -> {step['agent']}: {step['output']!r}")
        print(f"  RESULTADO: {e}")
    print()

print("BOOKINGS final:", rt.BOOKINGS)

Qué esperar:

=== Escenario 1: Studio pro 4h para Lucía ===
  etapa 1 (cotizar) -> booking_agent: 'Studio pro 4h cuesta 12800 centavos.'
  etapa 2 (validar la política de cancelación) -> policy_agent: 'Puedes cancelar sin cargo hasta 2 horas antes del horario reservado. No hay ningún impedimento para confirmar.'
  etapa 3 (confirmar la reserva) -> booking_agent: 'Reservé Studio pro 4h para Lucía (confirmación #1).'
  RESULTADO: pipeline completo. booking_id=1, price_cents=12800

=== Escenario 2: Boardroom basic 3h para Diego ===
  etapa 1 (cotizar) -> booking_agent: 'Boardroom basic 3h cuesta 24000 centavos.'
  etapa 2 (validar la política de cancelación) -> policy_agent: 'Puedes cancelar sin cargo hasta 2 horas antes del horario reservado. No hay ningún impedimento para confirmar.'
  etapa 3 (confirmar la reserva) -> booking_agent: 'Reservé Boardroom basic 3h para Diego (confirmación #2).'
  RESULTADO: pipeline completo. booking_id=2, price_cents=24000

=== Escenario 3: Studio pro 10h para NorthCorp Ops ===
  etapa 1 (cotizar) -> booking_agent: 'Studio pro 10h cuesta 32000 centavos.'
  etapa 2 (validar la política de cancelación) -> policy_agent: 'No encontré una política específica para reservas corporativas grandes en nuestra base -- te recomiendo escribir al equipo de cuentas corporativas antes de confirmar.'
  RESULTADO: pipeline detenido después de la etapa 2 (validar la política de cancelación): no se encontró una política relevante -- no hay base para confirmar

BOOKINGS final: {1: {'booking_id': 1, 'room': 'Studio', 'tier': 'pro', 'hours': 4, 'member': 'Lucía', 'price_cents': 12800}, 2: {'booking_id': 2, 'room': 'Boardroom', 'tier': 'basic', 'hours': 3, 'member': 'Diego', 'price_cents': 24000}}

El razonamiento por escenario:

Escenario 1 — Studio pro 4h, pipeline completo. Las tres etapas corren en orden, cleared_to_book queda True después de la etapa 2, y confirm reserva sin problema. 12800 = 4000 * 4 * 80 // 100 —Studio a tarifa pro, con el 20% de descuento—. booking_id: 1, el primero de este proceso.

Escenario 2 — Boardroom basic 3h, pipeline completo. Mismo camino que el Escenario 1, con 24000 = 8000 * 3basic no aplica ningún descuento, así que el precio es la tarifa base sin modificar. booking_id: 2, porque este proceso ya había confirmado una reserva (la del Escenario 1) antes de llegar acá.

Escenario 3 — Studio pro 10h, corporativo, la guarda se activa. La etapa 1 (cotizar) corre sin problema —32000 = 4000 * 10 * 80 // 100—, y ese precio queda guardado en el payload. Pero build_stage_task, al ver client_type == "corporate", arma una pregunta distinta para la etapa 2: no pregunta por la política de cancelación, pregunta por la política de reservas corporativas grandes — algo que el stub de search_docs, con solo dos entradas, no cubre. El resultado real es "No se encontró", cleared_to_book queda False, y stage_ok detiene el pipeline ahí mismo. La etapa 3 nunca corre: no hay ninguna tarea armada, ningún guion consultado, ningún book_room llamado. BOOKINGS al final confirma esto directamente — solo tiene las dos reservas de los Escenarios 1 y 2, nunca una tercera para NorthCorp Ops.


Errores comunes

  1. Forzar el Escenario 3 hacia una confirmación en vez de dejar que la guarda actúe. El punto de este escenario es justamente que la guarda haga su trabajo — escribir un guion para la etapa 3 de todos modos, o modificar stage_ok para que "encuentre" una excusa y confirme igual, es exactamente el tipo de parche que rompe la garantía central del módulo: nunca confirmar sobre una política sin validar.

  2. Pensar que el Escenario 3 "falló" porque el pipeline se detuvo. No falló — stage_ok funcionó exactamente como se diseñó en la lección 06: detectar, a tiempo, que la etapa 2 no dejó lo suficiente para seguir, y evitar una reserva corporativa grande sin ninguna política verificada detrás. Confundir una detención correcta con un error del sistema es el mismo error de lectura que ya viste con cancelled: False en el mini-proyecto del Módulo 2.

  3. Ejecutar los tres escenarios en procesos separados y esperar booking_id: 1 en los tres. Este mini-proyecto corre los tres escenarios en el MISMO proceso, uno después del otro — por eso el Escenario 2 confirma con booking_id: 2, no 1. Si corres cada escenario por separado, en su propio intérprete, cada uno vería BOOKINGS vacío y confirmaría con booking_id: 1.

  4. Olvidar que la etiqueta de la etapa 2 sigue diciendo "validar la política de cancelación" en el trace del Escenario 3. Es correcto — PipelineStage.label describe el ROL de la etapa en el pipeline (lección 02), no la pregunta exacta que build_stage_task arma para un cliente corporativo. El task real del trace sí refleja la pregunta distinta; el label no cambia.

  5. Calcular el precio del Escenario 3 a mano y esperar que aparezca en el payload final. No aparece — PipelineHalted interrumpe la ejecución antes de que run_pipeline_guarded llegue a su return. El precio de 32000 sí quedó en el trace parcial (la salida de la etapa 1), pero nunca en un payload completo, porque ese payload nunca se terminó de construir.


Ejercicios

Ejercicio 1: Confirma tu propia ejecución (Fácil)

Ejecuta los tres escenarios del encargo tú mismo y confirma, línea por línea, que tu salida coincide con la solución completa. Presta especial atención a BOOKINGS al final: debería tener exactamente dos reservas, nunca tres.

Ver solución

No hay una única "solución de código" para este ejercicio — es una verificación: si BOOKINGS al final de tu ejecución tiene exactamente dos entradas (booking_id: 1 de Lucía, booking_id: 2 de Diego), confirmaste que tu Reservo desechable arrancó limpio y que el Escenario 3 nunca llegó a book_room.

Ejercicio 2: Agrega un cuarto escenario (Medio)

Diseña un cuarto escenario, "Boardroom pro 1h para Carla", sin ningún client_type especial —un camino feliz más—, y córrelo con el mismo run_pipeline_guarded, en el mismo proceso donde ya corriste los tres escenarios del encargo.

Ver solución
payload_4 = {"room": "Boardroom", "tier": "pro", "hours": 1, "member": "Carla"}
scripts_4 = [
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "get_quote",
             "input": {"room": "Boardroom", "tier": "pro", "hours": 1}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Boardroom pro 1h cuesta 6400 centavos."}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "search_docs",
             "input": {"query": "política de cancelación"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": (
                "Puedes cancelar sin cargo hasta 2 horas antes del horario "
                "reservado. No hay ningún impedimento para confirmar."
            )}]},
    ],
    [
        {"stop_reason": "tool_use", "content": [
            {"type": "tool_use", "id": "toolu_01", "name": "book_room",
             "input": {"room": "Boardroom", "tier": "pro", "hours": 1, "member": "Carla"}}]},
        {"stop_reason": "end_turn", "content": [
            {"type": "text", "text": "Reservé Boardroom pro 1h para Carla (confirmación #1)."}]},
    ],
]
final_payload_4, trace_4 = run_pipeline_guarded(PIPELINE_STAGES, scripts_4, payload_4)
for step in trace_4:
    print(f"  etapa {step['stage']} ({step['label']}) -> {step['agent']}: {step['output']!r}")
print("payload final:", final_payload_4)

Salida esperada (si corre justo después de los tres escenarios del encargo, en el mismo proceso):

  etapa 1 (cotizar) -> booking_agent: 'Boardroom pro 1h cuesta 6400 centavos.'
  etapa 2 (validar la política de cancelación) -> policy_agent: 'Puedes cancelar sin cargo hasta 2 horas antes del horario reservado. No hay ningún impedimento para confirmar.'
  etapa 3 (confirmar la reserva) -> booking_agent: 'Reservé Boardroom pro 1h para Carla (confirmación #1).'
payload final: {'room': 'Boardroom', 'tier': 'pro', 'hours': 1, 'member': 'Carla', 'price_cents': 6400, 'policy_result': '[cancellation-policy] Las reservas se pueden cancelar sin cargo hasta 2 horas antes del horario reservado. Cancelaciones dentro de esas 2 horas aplican el cargo de no-presentación.', 'cleared_to_book': True, 'booking_id': 3, 'confirmed': True}

Explicación: 6400 = 8000 * 1 * 80 // 100 — Boardroom a tarifa pro, una hora, con el 20% de descuento. El booking_id sale 3 (no 1), porque este proceso ya había confirmado dos reservas —Lucía y Diego— antes de este cuarto escenario. Si corres este bloque en un intérprete nuevo, sin los tres escenarios del encargo antes, el booking_id sale 1.

Ejercicio 3: El costo de detenerse a tiempo (Difícil)

Usando el vocabulario de costo de la lección 05 (2 llamadas al modelo por etapa, más 1 de síntesis final si el pipeline se completa), calcula cuántas llamadas al modelo gastó cada uno de los tres escenarios del encargo. Después, calcula cuántas habría gastado el Escenario 3 si, en cambio de detenerse, hubiera corrido las tres etapas igual — y argumenta, en una frase, por qué ese número no es el único ahorro real de la guarda.

Ver solución
CALLS_PER_STAGE = 2  # de la lección 05: un tool_use + un texto final, por etapa
COMPOSE_CALLS = 1

def pipeline_cost(n_stages_run, completed):
    calls = n_stages_run * CALLS_PER_STAGE
    if completed:
        calls += COMPOSE_CALLS
    return calls

cost_1 = pipeline_cost(3, True)
cost_2 = pipeline_cost(3, True)
cost_3 = pipeline_cost(2, False)
print(f"Escenario 1 (completo, 3 etapas):        {cost_1} llamadas")
print(f"Escenario 2 (completo, 3 etapas):        {cost_2} llamadas")
print(f"Escenario 3 (detenido en la etapa 2):    {cost_3} llamadas")
print(f"si el Escenario 3 hubiera corrido las 3 etapas igual: {pipeline_cost(3, True)} llamadas")
print(f"ahorro de la guarda en el Escenario 3: {pipeline_cost(3, True) - cost_3} llamadas -- además de evitar una reserva sin política validada")

Salida esperada:

Escenario 1 (completo, 3 etapas):        7 llamadas
Escenario 2 (completo, 3 etapas):        7 llamadas
Escenario 3 (detenido en la etapa 2):    4 llamadas
si el Escenario 3 hubiera corrido las 3 etapas igual: 7 llamadas
ahorro de la guarda en el Escenario 3: 3 llamadas -- además de evitar una reserva sin política validada

Explicación: la guarda de la lección 06 ahorra 3 llamadas al modelo en el Escenario 3 —nunca llega a construir ni correr la etapa confirm, ni a sintetizar una respuesta final—. Pero ese número no es el ahorro más importante: aunque la guarda costara lo mismo o más, seguiría siendo la decisión correcta, porque el ahorro real que más importa no es de llamadas — es evitar que Reservo confirme una reserva corporativa grande sin ninguna política verificada detrás. El costo de coordinación (lección 05) y la corrección del resultado (lección 06) son dos razones distintas para la misma guarda, y esta lección confirma que, en este caso, apuntan en la misma dirección.


Resumen y siguiente paso

  • El mini-proyecto no agregó ningún concepto nuevo: aplicó las siete lecciones anteriores —anatomía de una etapa, encadenado del dato, pipeline completo, costo medido, guarda, criterio de elección— sobre tres escenarios de Reservo nuevos.
  • Los Escenarios 1 y 2 completaron las tres etapas de punta a punta, con booking_id: 1 y booking_id: 2 respectivamente — el mismo mecanismo de las lecciones 03/04, sin ningún cambio.
  • El Escenario 3 confirmó, sobre un caso nunca visto, que la guarda de la lección 06 funciona exactamente como se diseñó: detuvo el pipeline antes de confirm, sin que BOOKINGS recibiera una tercera reserva sin política validada.
  • El criterio completo de este módulo —anatomía, encadenado, costo medido, guarda, y el hallazgo de que dos de las tres etapas no dependían entre sí (lección 07)— es la base que el Módulo 4 va a retomar y extender: correr etapas genuinamente independientes al mismo tiempo, agregando sus resultados.

Con esto termina el Módulo 3. Construiste el patrón pipeline secuencial completo: la anatomía de una etapa, el mecanismo de encadenar el dato entre ellas, el pipeline de tres etapas de Reservo corriendo de punta a punta, su costo de coordinación medido contra un supervisor, la guarda que lo protege cuando una etapa no alcanza, y el criterio —confirmado ejecutando el código— para saber cuándo un orden fijo es real y cuándo es apenas una decisión de diseño. En el Módulo 4 construimos el tercer patrón: el fan-out paralelo — cuando los pasos, como quote y validate_policy en este mismo módulo, no dependen entre sí y pueden despacharse juntos, agregando sus resultados antes de seguir.


Recursos adicionales

  1. Anthropic — Building effective agents — El principio de usar el mecanismo más simple que resuelva la tarea, y de agregar puertas de verificación explícitas entre pasos — el criterio detrás de todo este mini-proyecto.
  2. Anthropic — Multi-agent research system — Un caso real donde detener a tiempo un flujo con datos insuficientes, en vez de forzarlo, evitó un resultado incorrecto más costoso de corregir después.
  3. Anthropic — Agent SDK overview — Cómo se ve, en código real, un pipeline de agentes con puertas de verificación — el destino del Módulo 8 de esta guía.
  4. Python — Excepciones y try/except — El mecanismo detrás de PipelineHalted, usado en los tres escenarios de este mini-proyecto para separar el camino feliz de la detención a tiempo.