Módulo 8: Capstone The Andes Cargo Genai Extractor
2. Repaso de arquitectura: el sistema completo, dos caminos
Descripción
Cada uno de los siete módulos anteriores dibujó, en su propia lección de arquitectura, un fragmento del sistema completo: el M1.3 dibujó el "antes/después" del parser; el M4.4 dibujó la brecha exacta entre el guardrail gestionado y los chequeos propios; el M8.1 nombró las siete piezas heredadas sin dibujarlas juntas. Esta lección hace lo que ninguna lección anterior hizo todavía: dibuja el sistema completo, de punta a punta, con las dos rutas —determinista y de escalamiento— superpuestas sobre la infraestructura que las siete guías hermanas de este ecosistema ya dejaron construida. Ningún componente de este diagrama es nuevo; lo nuevo es verlos todos juntos, en un solo lugar, por primera vez.
Conexión con el módulo
Esta lección es el mapa que las lecciones 3 y 4 recorren, cada una siguiendo una de las dos rutas de punta a punta. Sin este mapa, "el camino determinista" y "el camino de escalamiento" serían frases sueltas; con él, son dos trayectorias concretas sobre un diagrama que cualquiera puede señalar con el dedo.
Analogía: el mapa completo del cajero automático, no solo el mostrador
Un banco que quiere explicar cómo funciona su servicio de atención no empieza mostrando el cajero automático aislado — muestra el mapa completo: la sucursal, la fila de cajeros automáticos en la entrada, el mostrador de atención al fondo, y la regla que conecta ambos —el cliente intenta primero el cajero, que resuelve más del 90 % de los trámites solo, y solo si el cajero no puede resolverlo (un caso fuera de su alcance, un problema con la cuenta) la fila avanza hacia un humano en el mostrador, más lento, más caro de operar, pero capaz de manejar lo que el cajero no puede—. Esta lección es exactamente ese mapa completo, aplicado al sistema de Andes Cargo: process-shipment-manifest es el cajero automático, resolviendo la gran mayoría de los manifiestos solo, gratis, en milisegundos; extract-shipment-manifest-fields es el mostrador humano, caro y lento en comparación, que entra en escena únicamente cuando el cajero —el parser determinista— ya lo intentó y no pudo.
Paso 1 — El diagrama ASCII: dos caminos, un solo destino
manifiesto (S3: andes-cargo-shipment-docs)
│
▼
┌────────────────────────────────────┐
│ process-shipment-manifest │
│ (aws-core-services-guide, M6-M7) │
│ parse_manifest() -- clave=valor │
└────────────────────────────────────┘
│
┌─────────────────┴─────────────────┐
│ │
los 5 campos SÍ falta AL MENOS
aparecieron 1 de los 5 campos
│ │
▼ ▼
╔═══════════════════════╗ ╔══════════════════════════╗
║ CAMINO DETERMINISTA ║ ║ CAMINO DE ESCALAMIENTO ║
║ (M8.3 -- EJECUTADO) ║ ║ (M8.4 -- MIXTO) ║
╚═══════════════════════╝ ╚══════════════════════════╝
│ │
│ publica ManifestParseFailed
│ (bus andes-cargo-events)
│ │
│ ▼
│ ┌────────────────────────────────┐
│ │ extract-shipment-manifest- │
│ │ fields (M3, M5) │
│ │ 1. pre_invoke_checks.py (M4) │
│ │ scrub PII -- REAL │
│ │ 2. bedrock:InvokeModel │
│ │ + guardrail (M3, M4) │
│ │ -- REPRESENTATIVO desde aquí │
│ │ 3. post_invoke_checks.py (M4) │
│ │ valida ShipmentFields -- REAL│
│ └────────────────────────────────┘
│ │
│ ┌────────────┴────────────┐
│ esquema OK esquema FAIL
│ │ │
▼ ▼ ▼
write_shipment_record write_shipment_record NO se escribe --
│ │ registro queda
└───────────┬────────────┘ pendiente de
▼ revisión manual
Shipments (DynamoDB)
Fíjate en el punto exacto donde el diagrama se bifurca: no es un paso adicional antes de process-shipment-manifest — es una salida de ese mismo Lambda, la misma decisión que el M1.3 ya trazó en prosa. Ningún manifiesto pasa por extract-shipment-manifest-fields "por si acaso"; solo llega ahí un manifiesto que el camino barato ya intentó y no pudo resolver.
Paso 2 — El diagrama de secuencia completo, en mermaid
El diagrama ASCII de arriba muestra las decisiones; este diagrama de secuencia muestra el orden temporal exacto en que cada componente heredado y cada componente nuevo de esta guía conversan entre sí, incluida la frontera representativa marcada sin ambigüedad:
sequenceDiagram
participant S3 as S3 (andes-cargo-shipment-docs)
participant PSM as process-shipment-manifest
participant EB as EventBridge (andes-cargo-events)
participant ESM as extract-shipment-manifest-fields
participant PRE as pre_invoke_checks.py
participant BR as bedrock-runtime + Guardrail
participant POST as post_invoke_checks.py
participant DDB as Shipments (DynamoDB)
S3->>PSM: manifiesto subido (evento S3)
PSM->>PSM: parse_manifest(text)
alt los 5 campos presentes
PSM->>DDB: write_shipment_record(fields)
Note over PSM,DDB: Camino determinista -- M8.3, EJECUTADO
else falta al menos 1 campo
PSM->>EB: put-events ManifestParseFailed
Note over PSM,EB: Real -- M8.4, EJECUTADO
EB->>ESM: invoca (regla EventBridge, M3)
ESM->>PRE: scrub_pii(rawText)
PRE-->>ESM: redacted_text, found_pii
Note over ESM,PRE: Real -- M4, M8.4
ESM->>BR: InvokeModel + guardrailIdentifier
Note over ESM,BR: REPRESENTATIVO -- nunca ejecutado (M3.6, M4.7)
BR-->>ESM: representativeModelResponse (dict fijo, etiquetado)
ESM->>POST: validate_shipment_fields(candidate)
POST-->>ESM: is_valid, errors
Note over ESM,POST: Real -- M4, M8.4
alt is_valid
ESM->>DDB: write_shipment_record(candidate)
else not is_valid
ESM->>ESM: DO NOT WRITE -- queda para revisión manual
end
end
Dos anotaciones (Note over) marcan, dentro del diagrama mismo, exactamente dónde termina lo real y empieza lo representativo — la misma disciplina de etiquetado que cada lección de esta guía ya aplicó a su propio bloque de código, ahora aplicada a la vista de sistema completo.
Paso 3 — Qué hereda este diagrama, componente por componente
| Componente del diagrama | Guía de origen | Estado en esta guía |
|---|---|---|
S3: andes-cargo-shipment-docs | aws-core-services-guide, Módulo 5 | Heredado, sin ningún cambio |
process-shipment-manifest / parse_manifest() | aws-core-services-guide, Módulo 6-7 | Heredado, sin ningún cambio (M1.3) |
EventBridge / bus andes-cargo-events | aws-serverless-and-containers-guide, Módulo 4 | Heredado; ManifestParseFailed es el evento nuevo que esta guía agrega, con la misma forma que ShipmentProcessed |
extract-shipment-manifest-fields (función) | Nueva de esta guía | Declarada en M3 (bedrock.tf), asegurada en M5, firmada en M5.6 |
pre_invoke_checks.py / post_invoke_checks.py | Nuevos de esta guía | M4, construidos y probados con pytest |
bedrock-runtime + Guardrail | Nuevo de esta guía | Declarado como código (M3-M4), nunca invocado (M1.2, M3.6, M4.7) |
Shipments (DynamoDB) | aws-core-services-guide, Módulo 7 | Heredado, sin ningún cambio en su esquema — ShipmentFields (M4.6) es, exactamente, la forma que ya esperaba |
Ninguna fila de esta tabla introduce infraestructura que esta guía no haya construido ya, en un módulo anterior — el punto exacto de esta lección es mostrar que las siete piezas conversan entre sí, no presentar una octava.
Errores comunes
Dibujar mentalmente al guardrail de Bedrock como un paso separado, antes de bedrock-runtime, en vez de parte de la misma llamada (de fragmentar un mecanismo que la API trata como uno solo). Qué pasa: alguien, viendo "Guardrails" mencionado tantas veces en el M4, imagina un servicio intermedio distinto que la solicitud atraviesa antes de llegar al modelo. Cómo detectarlo: si tu versión del diagrama de esta lección tiene una flecha separada de ESM hacia "Guardrails" y otra, distinta, hacia "bedrock-runtime". Cómo corregirlo: el M4.2 ya lo estableció — un guardrail se referencia con el parámetro guardrailIdentifier dentro de la misma llamada a InvokeModel/Converse, no como una llamada separada. El diagrama de esta lección lo dibuja correctamente como un solo participante (bedrock-runtime + Guardrail), exactamente por esa razón.
Asumir que el camino de escalamiento reemplaza, en algún punto, al camino determinista para el mismo manifiesto (de no entender la bifurcación como definitiva). Qué pasa: alguien interpreta el diagrama como si un manifiesto pudiera "reintentar" primero con parse_manifest(), fallar, y luego el mismo manifiesto volviera a intentar el camino barato después de que el modelo respondiera. Cómo detectarlo: si tu lectura del diagrama incluye una flecha que regresa desde extract-shipment-manifest-fields hacia process-shipment-manifest. Cómo corregirlo: la bifurcación del Paso 1 es definitiva para cada manifiesto específico — una vez que parse_manifest() no produjo los cinco campos, ese manifiesto exacto sigue únicamente el camino de escalamiento; no hay ningún ciclo de vuelta al camino barato para el mismo texto, exactamente como el M1.3 ya lo estableció con el diagrama "HOY / DESPUÉS".
Leer la nota "REPRESENTATIVO" del diagrama de secuencia como si aplicara a todo el bloque alt/else, incluidos pre_invoke_checks/post_invoke_checks (de sobre-generalizar la etiqueta). Qué pasa: alguien, viendo una sola anotación "REPRESENTATIVO" cerca del medio del diagrama, concluye que todo el camino de escalamiento es representativo. Cómo detectarlo: si tu resumen del diagrama de secuencia es "la mitad de abajo es toda representativa". Cómo corregirlo: la etiqueta está puesta, deliberadamente, en la interacción específica ESM->>BR/BR-->>ESM — las interacciones con PRE y POST, antes y después de esa, llevan su propia nota "Real", exactamente porque son ejecutables sin depender de Bedrock. La lección 4 de este módulo hace esta misma distinción con código corrido de verdad, no solo con un diagrama.
Ejercicios
Ejercicio 1 — Sin mirar el diagrama ASCII de esta lección, dibuja tú mismo, en texto, la bifurcación exacta que ocurre dentro de process-shipment-manifest. ¿Qué condición exacta decide entre los dos caminos?
Ver solución
La condición exacta es si parse_manifest(text) produjo los cinco campos de SHIPMENT_FIELDS_SCHEMA (shipmentId, originCountry, destinationCountry, carrier, weightKg) — la misma condición que would_escalate() (M7.4) codifica como una sola línea: bool(set(SHIPMENT_FIELDS_SCHEMA) - set(parsed.keys())). Si esa resta de conjuntos está vacía, el manifiesto sigue el camino determinista; si falta cualquiera de los cinco, publica ManifestParseFailed y sigue el camino de escalamiento. La condición vive enteramente dentro de process-shipment-manifest, nunca en un componente separado que "decida" desde afuera.
Ejercicio 2 — Explica por qué el diagrama de secuencia de esta lección NO incluye ninguna interacción entre Shipments (DynamoDB) y extract-shipment-manifest-fields en el caso not is_valid. ¿Qué le pasa, exactamente, a un manifiesto que llega hasta ahí?
Ver solución
Porque post_invoke_checks.py (M4.6) existe, precisamente, para evitar esa escritura — un candidato que falla la validación de esquema nunca llega a write_shipment_record(), sin excepción, la misma regla que defense_in_depth_flow.py (M4.8) ya demostró con el Escenario B. El manifiesto que llega hasta ahí queda, según la nota del diagrama, "para revisión manual" — no hay ninguna flecha hacia DDB en esa rama porque, con toda intención, no ocurre ninguna escritura. Es la misma regla que el M4.4 ya nombró con su ejemplo de weightKg faltante: una respuesta que pasaría las seis políticas de Bedrock Guardrails sin problema, pero que este chequeo propio, sí, rechaza.
Ejercicio 3 — Predice qué cambiaría en el diagrama de esta lección si Andes Cargo, algún día, agregara un segundo consumidor del evento ManifestParseFailed (por ejemplo, una cola de revisión manual, mencionada como posibilidad en el M1.3, Ejercicio 2). ¿extract-shipment-manifest-fields necesitaría cambiar?
Ver solución
No — ni una sola línea de extract-shipment-manifest-fields cambiaría. El diagrama agregaría, simplemente, una segunda flecha desde EB (EventBridge) hacia el nuevo consumidor, en paralelo a la que ya existe hacia ESM — exactamente la ventaja de diseño que el M1.3, Ejercicio 2 ya explicó en prosa: ManifestParseFailed anuncia un hecho ("el parseo determinista falló"), no una instrucción ("usa IA para resolverlo"), así que cualquier número de consumidores nuevos puede suscribirse al mismo evento sin que el productor (process-shipment-manifest) ni ningún consumidor existente tengan que modificarse. Es el mismo principio event-driven que sostiene todo el bus andes-cargo-events desde aws-serverless-and-containers-guide.
Resumen y siguiente paso
Esta lección dibujó, por primera vez en esta guía, el sistema completo de punta a punta: un diagrama ASCII de las dos rutas y su bifurcación exacta, y un diagrama de secuencia en mermaid con cada componente heredado y cada componente nuevo conversando en el orden temporal real, incluida la frontera representativa marcada sin ambigüedad dentro del diagrama mismo. Confirmaste, con la tabla del Paso 3, que ningún componente de este mapa es nuevo — cada uno tiene su propia lección de origen, en este módulo o en uno anterior.
Antes de avanzar deberías poder: dibujar de memoria la bifurcación exacta dentro de process-shipment-manifest; explicar por qué el guardrail de Bedrock aparece como un solo participante, no dos; y señalar, en el diagrama de secuencia, exactamente dónde empieza y dónde termina la única interacción representativa de todo el flujo.
La lección 3 recorre el camino determinista de este diagrama, de punta a punta, con código ejecutado en su totalidad — la primera de las dos lecciones "manos a la obra" de este capstone.
Recursos
- Este mismo curso, Módulo 1, lección 3 (
03-andes-cargos-ai-workload-when-the-deterministic-parser-is-not-enough.md) — el origen del diagrama "antes/después" que el Paso 1 de esta lección extiende con el resto del sistema. - Este mismo curso, Módulo 4, lección 8 (
08-project-andes-cargos-guardrails-layer.md) —defense_in_depth_flow.py, la fuente exacta del orden de tres pasos que el diagrama de secuencia de esta lección representa. - Mermaid — Sequence diagrams — referencia oficial de la sintaxis usada en el Paso 2 de esta lección.
- AWS Docs — Amazon Bedrock guardrails, using with InvokeModel — confirma que un guardrail se referencia dentro de la misma llamada de inferencia, la base del Error común 1 de esta lección.