Módulo 7: Observability Latency And Evals In Production

8. Proyecto: el panel de observabilidad de la carga de IA de Andes Cargo

Descripción

Este proyecto final del Módulo 7 convierte las siete lecciones anteriores en un solo documento: AI-OBSERVABILITY.md, en la raíz de andes-cargo-infra/, junto a SLO.md que sre-and-incident-response-guide ya dejó ahí — complemento, nunca reemplazo, la misma relación exacta que GENAI-COST-PROFILE.md (Módulo 2, lección 8) ya estableció con COST-PROFILE.md. Reúne los tres SLI definidos en la lección 2, el número real de tasa de escalamiento de la lección 4, el número real de tasa de bloqueo del guardrail sobre el arnés de la lección 6, y el límite exacto de latencia y calidad que la lección 7 documentó — con el mismo ledger de honestidad, sección por sección, que cada proyecto de módulo de esta guía ya modeló.

Conexión con el módulo

Este documento no introduce ningún dato nuevo — cada número que contiene ya corrió, de verdad, en una lección anterior de este módulo. Su trabajo es exclusivamente de integración y reconciliación: conectar el 12,0% de la lección 4 con el 10% que GENAI-COST-PROFILE.md ya declaró como hipótesis, y dejar registrado, en un solo lugar, exactamente qué de esta capa de observabilidad es literal y qué es representativo, para que el Módulo 8 —el capstone de esta guía— pueda citarlo sin tener que releer las siete lecciones anteriores.


Paso 1 — Por qué este documento reconcilia, no reemplaza, a GENAI-COST-PROFILE.md

Antes de escribir el documento, vale la pena resolver la misma tensión, con la misma disciplina, que el Módulo 2, lección 8 ya resolvió para dos números de volumen distintos. GENAI-COST-PROFILE.md, sección 4, declaró un supuesto de 10% de tasa de escalamiento (40 de ~400 manifiestos/mes), explícitamente etiquetado como una hipótesis pendiente de revisión "la primera vez que existe un número real de tasa de escalamiento" — y esa misma sección nombró, por adelantado, a este módulo, lección 4 como la fuente de ese número. La lección 4 ya produjo ese número: 12,0%, sobre un lote de prueba de 50 eventos fijos.

   LA RECONCILIACION QUE ESTE DOCUMENTO REGISTRA

   GENAI-COST-PROFILE.md, seccion 4          AI-OBSERVABILITY.md, seccion 3
   "10% -- hipotesis, pendiente de           "12.0% -- medido, sobre un lote
    revision" (Modulo 2, leccion 8)           de prueba fijo" (Modulo 7,
                                               leccion 4)

   Los dos numeros son del MISMO ORDEN DE MAGNITUD -- ninguno invalida al
   otro. GENAI-COST-PROFILE.md, seccion 7, ya declaro la regla: esto
   dispara una REVISION, no una reescritura desde cero. Esa revision queda
   PENDIENTE -- fuera del alcance de este documento, que solo registra el
   dato de entrada para cuando ocurra.

AI-OBSERVABILITY.md no edita GENAI-COST-PROFILE.md — deja ese documento exactamente como el Módulo 2 lo dejó, y registra el 12,0% como el dato de entrada para la revisión que, algún día, alguien con más datos reales haría.


Paso 2 — Confirmando los dos números, una vez más, antes de escribirlos

python3 observability/escalation_rate.py | grep "Escalation rate"
python3 evals/manifest_extraction_smoke_test.py | tail -1

Qué esperar (literal — ambos scripts ya corrieron en las lecciones 4 y 6 de este módulo; esta lección solo confirma que el resultado sigue siendo idéntico antes de citarlo en un documento nuevo):

Escalation rate               12.0%
Smoke test: 3/5 fixtures passed structural validation

Dos scripts deterministas, corridos una tercera vez cada uno (la primera al escribir la lección original, la segunda al verificarlos aquí), con el mismo resultado exacto — la misma confirmación de determinismo que cada script de esta guía ya demostró en su propia lección.


Paso 3 — El documento completo

En la raíz de andes-cargo-infra/, crea AI-OBSERVABILITY.md:

# AI-OBSERVABILITY.md — Andes Cargo AI Workload Observability Charter

**Status:** Active · **Owner:** Platform/SRE · **Framework:** Google SRE (SLI/SLO), extended to
`extract-shipment-manifest-fields`, the one Bedrock-backed escalation path defined in
`ADR-001-llm-as-escalation-path.md` (Module 1)
**Complements, never replaces:** `SLO.md` (`sre-and-incident-response-guide`, Module 2, lesson 8),
which covers the classic availability SLI of `process-shipment-manifest`. This document adds three
SLI specific to the AI escalation path, none of which existed before this guide.
**Does not cover:** semantic quality evaluation of any extraction (Module 7, lesson 5 -- AI
Engineering's job, not this guide's); a formal SLO target for any of the three SLI below (this
document declares SLI only -- fixing a defensible SLO needs more real data than a $0 lab produces).

## 1. Scope

This document declares the three SLI this guide adds for an AI workload, the real or representative
status of each, and the exact number this guide produced for the two that are calculable without
invoking Bedrock. It is the closing deliverable of Module 7, and the honesty ledger for the entire
observability surface this guide built.

## 2. The three SLI, formally

| SLI | Formula | Source lesson |
|---|---|---|
| Escalation rate | `ManifestParseFailed` events / total manifests processed | Module 7, lesson 2 |
| Guardrail block rate | candidates failing `validate_shipment_fields()` / candidates evaluated | Module 7, lesson 2 |
| Inference latency | invocations under a declared threshold / total invocations | Module 7, lesson 2 |

Escalation rate does not fit the classic "good events / valid events" mold (Module 7, lesson 2) --
it measures traffic composition, not success or failure. Guardrail block rate and inference latency
both fit the classic Google SRE formula for an SLI.

## 3. Escalation rate — measured, literal

`observability/escalation_rate.py` (Module 7, lesson 4), run against a fixed, deterministic batch of
50 test events, reusing `parse_manifest()` (Module 1, lesson 3) and `SHIPMENT_FIELDS_SCHEMA` (Module
4, lesson 6) unmodified:

```
Total test events            50
Escalated (ManifestParseFailed) 6
Escalation rate               12.0%
```

**This is the first real data point against `GENAI-COST-PROFILE.md` (Module 2, lesson 8, section 4)'s
10% assumption (40 of the ~400/month baseline `COST-PROFILE.md` declares).** Per that document's own
section 7 ("Document maintenance"), this number triggers a review, not a rewrite, of that assumption
-- both numbers are close in order of magnitude, and 50 fixed test events are explicitly not a month
of production traffic (the same caveat `sre-and-incident-response-guide`, Module 3, lesson 3 already
applied to its own 20-manifest verification batch). `GENAI-COST-PROFILE.md` is not edited by this
document -- it stays exactly as Module 2, lesson 8 left it, with this number recorded here as the
input for that future review.

## 4. Guardrail block rate — measured, on the smoke test harness

`evals/manifest_extraction_smoke_test.py` (Module 7, lesson 6), run against the five fixtures of
`evals/fixtures/sample_manifests.json`, reusing `validate_shipment_fields()` (Module 4, lesson 6)
unmodified:

```
[4471] PASS
[4472] PASS
[4473] FAIL - missing required field(s): weightKg
[4475] PASS
[4476] FAIL - unexpected field(s) not in ShipmentFields: confidenceScore

Smoke test: 3/5 fixtures passed structural validation
Guardrail block rate on this batch: 2/5 = 40%
```

**This number is real only on this specific five-fixture batch**, deliberately built to include two
distinct failure shapes (a missing field, an invented field) -- it is not a measurement of how often
a real Bedrock invocation would produce an incomplete response. What it does confirm, with executed
evidence: `validate_shipment_fields()` distinguishes complete from incomplete candidates with 100%
accuracy on any candidate it is given, real or representative -- the guarantee this SLI would need
the day real traffic exists to measure.

## 5. Inference latency — representative, no number

`InvocationLatency` and `TimeToFirstToken` (`AWS/Bedrock` namespace, milliseconds) are the real,
documented CloudWatch metrics that would populate this SLI -- confirmed against
`docs.aws.amazon.com/bedrock/latest/userguide/monitoring-runtime-metrics.html` (Module 7, lesson 7).
Neither has a value in this guide: no invocation of Nova Lite (the model `GENAI-COST-PROFILE.md`
chose) ever occurs. Module 7, lesson 7 confirmed, by direct search against official AWS sources, that
no published millisecond figure exists for Nova Lite specifically -- not even AWS's own
latency-optimized inference initiative covers this model. This SLI stays undeclared as a number,
by design, until a real invocation exists to measure it.

## 6. The eval boundary, restated

Module 7, lesson 5 drew the line this guide holds without exception: this guide builds the harness
that would run a production eval (Module 7, lesson 6); it never builds the semantic quality metric
that harness would need to actually judge correctness. `evals/fixtures/sample_manifests.json`
carries an `expectedFields` column specifically so a human reader can see what a correct extraction
would look like -- the harness itself never reads that column, precisely so it never crosses into
semantic evaluation. That boundary is AI Engineering's, not this guide's (Module 1, lesson 4).

## 7. Honesty ledger

| Component | Status | Evidence |
|---|---|---|
| `observability/structured_log.py`, the four-event vocabulary | Literal | Module 7, lesson 3 -- five log lines, run for real |
| `awslocal logs`/`awslocal cloudwatch` (metric filters, custom metrics) | Representative | No `LOCALSTACK_AUTH_TOKEN` in this sandbox (Module 7, lesson 3) |
| Escalation rate: 12.0% (6/50) | Literal | Module 7, lesson 4 -- deterministic, reproducible on any machine |
| Guardrail block rate: 40% (2/5) | Literal, on this specific batch | Module 7, lesson 6 -- never a production measurement |
| Inference latency | Representative, no number | Module 7, lesson 7 -- metric schema real, no invocation ever occurred |
| Semantic extraction quality | Out of scope by design | Module 7, lesson 5 -- AI Engineering's boundary |

## 8. Next steps

The moment a real AWS account with Bedrock access runs this guide's code end to end: (a) the
escalation rate script needs no changes -- point it at real `ManifestParseFailed` event history
instead of the fixed 50-event batch; (b) the smoke test harness needs no changes to its comparison
logic -- only `representativeModelResponse` in each fixture would be replaced with a real Bedrock
response, and the `note` field's `"REPRESENTATIVE"` label would need to come off, case by case, only
once that specific fixture's response is real; (c) `InvocationLatency`/`TimeToFirstToken` would begin
populating in CloudWatch automatically, with zero code changes, because Bedrock publishes both
natively for every real invocation. No part of this guide's code is throwaway lab code -- all of it
is the real instrumentation this workload would use in production, run here against fixed and
representative data because that is the exact, named limit of what this $0 lab can do.

Paso 4 — Verificando el documento

wc -l AI-OBSERVABILITY.md
grep -c '^## ' AI-OBSERVABILITY.md

Qué esperar (literal — el contenido lo escribiste tú, así que su forma es determinista):

119 AI-OBSERVABILITY.md
8

Ciento diecinueve líneas, ocho secciones — alcance, los tres SLI, escalamiento, tasa de bloqueo, latencia, la frontera del eval, el ledger de honestidad, próximos pasos. Si tu conteo no da 8, revisa que no hayas fusionado ni omitido ninguna de las ocho ## del Paso 3.


Por qué el "Paso 8" del documento importa tanto como el resto

Vale la pena detenerse en la última sección del documento, "Next steps" — es fácil, al escribir un documento de honestidad, dejar la sensación de que todo lo representativo es una limitación permanente, sin salida. Esa sección existe, específicamente, para contradecir esa lectura: cada pieza representativa de este módulo es código de producción real, esperando datos reales, no código de laboratorio descartable. El arnés de la lección 6 no necesita reescribirse el día que exista una cuenta real — solo necesita que sus dict representativos se reemplacen por respuestas reales, campo por campo, note por note. Esta distinción —"representativo" no significa "de juguete"— es la misma que cada módulo anterior de esta guía ya sostuvo para su propio dominio: el HCL del Módulo 3 no cambiaría si Bedrock estuviera disponible en Hobby mañana; el guardrail del Módulo 4 tampoco. Este módulo cierra esa misma promesa para observabilidad.


Errores comunes

Editar GENAI-COST-PROFILE.md directamente desde esta lección, en vez de solo registrar el 12,0% en AI-OBSERVABILITY.md (de adelantarse a una revisión que esta lección no autoriza). Qué pasa: alguien, con el número nuevo en la mano, va directamente a GENAI-COST-PROFILE.md y cambia el "10%" de su sección 4 por "12%". Cómo detectarlo: si tu diff de este proyecto incluye cambios a un archivo de un módulo anterior. Cómo corregirlo: el Paso 1 de esta lección es explícito — este documento registra el dato de entrada para una revisión futura, no la ejecuta. GENAI-COST-PROFILE.md, sección 7, describe qué cambiaría en una revisión real (el escenario "Realista" pasaría de 40 a un nuevo total, y habría que correr bedrock_cost_estimate.py de nuevo) — un trabajo real, con sus propias implicaciones de costo, que merece su propia decisión consciente, no un cambio de una palabra hecho de paso en otro módulo.

Presentar el 40% de tasa de bloqueo de la sección 4 como si fuera comparable, en el mismo sentido, al 12,0% de tasa de escalamiento de la sección 3 (de tratar dos "números reales sobre un lote fijo" como si tuvieran el mismo peso de evidencia). Qué pasa: alguien lee ambas secciones y las trata como igualmente representativas de la realidad de Andes Cargo. Cómo detectarlo: si tu resumen de este documento dice "la tasa de escalamiento es 12% y la tasa de bloqueo es 40%, ambas confirmadas". Cómo corregirlo: los 50 eventos de la sección 3 se diseñaron para simular, con variedad, el tipo de tráfico real que process-shipment-manifest procesaría —una muestra amplia, aunque pequeña—; los 5 candidatos de la sección 4 se diseñaron deliberadamente para incluir dos tipos específicos de fallo, con el propósito exclusivo de probar que el validador los detecta —no para simular la proporción real de respuestas incompletas que Bedrock produciría—. El documento ya distingue esto con la frase "measured, on the smoke test harness" frente a "measured, literal" — la distinción existe en el texto, y vale la pena mantenerla al citar cualquiera de los dos números fuera de este documento.

Omitir la sección 8 ("Next steps") al resumir este documento para otra persona, dejando la impresión de que este módulo terminó en un callejón sin salida (de subestimar el valor de lo ya construido). Qué pasa: alguien describe este proyecto como "documentamos todo lo que no pudimos medir". Cómo detectarlo: si tu resumen de este documento no menciona ninguna de las tres piezas de código real que este módulo deja lista para producción. Cómo corregirlo: la sección "Por qué el 'Paso 8' del documento importa tanto como el resto" de esta misma lección lo dice con precisión — el trabajo de este módulo no es un ejercicio de documentación de límites, es infraestructura real de observabilidad, con tres piezas de código ejecutable (structured_log.py, escalation_rate.py, manifest_extraction_smoke_test.py) que funcionarían sin cambios el día que exista tráfico real.


Ejercicios

Ejercicio 1 — Sin mirar el documento del Paso 3, enumera las tres piezas de código de este módulo que la sección "Next steps" declara como "código de producción real, no de laboratorio". ¿Qué cambiaría, específicamente, en cada una el día que exista una cuenta real de Bedrock?

Ver solución

observability/structured_log.py (Módulo 7, lección 3) — nada cambiaría; el logger ya está listo para instrumentar invocaciones reales exactamente como instrumenta las representativas de esta guía. observability/escalation_rate.py (Módulo 7, lección 4) — cambiaría solo la fuente de datos, de un lote fijo de 50 eventos a un historial real de eventos ManifestParseFailed, sin tocar la lógica de would_escalate(). evals/manifest_extraction_smoke_test.py (Módulo 7, lección 6) — cambiaría el contenido de representativeModelResponse en cada fixture, reemplazándolo por una respuesta real de Bedrock, sin tocar la lógica de run_smoke_test() ni de validate_shipment_fields(). Las tres comparten el mismo patrón: la lógica de comparación/cálculo es código terminado; solo los datos que consume cambiarían.

Ejercicio 2 — Explica por qué la sección 5 del documento ("Inference latency") no incluye ninguna tabla ni ningún número, a diferencia de las secciones 3 y 4, que sí muestran salida literal de un script. ¿Por qué la ausencia de una tabla es, en sí misma, una decisión de honestidad, no un vacío accidental?

Ver solución

Incluir una tabla con una columna vacía, o con la palabra "N/A" repetida, habría sugerido que existe un dato pendiente de completar — como si solo faltara correr un comando para llenarla. La ausencia total de tabla en la sección 5 comunica algo más preciso: no hay ningún comando que este laboratorio pueda correr, ni ahora ni con más tiempo, para producir ese número — la limitación es estructural (ninguna invocación real ocurre en esta guía, según la decisión del Módulo 1), no una tarea pendiente. Es la misma disciplina que el Módulo 7, lección 7 ya aplicó al escribir "Average": "VARIABLE" en vez de dejar el campo vacío o inventar un placeholder — nombrar la ausencia con precisión, en vez de dejarla ambigua.

Ejercicio 3 — Un auditor externo revisa AI-OBSERVABILITY.md y pregunta: "¿por qué confían en el 12,0% de la sección 3 si es 'solo' un lote de prueba de 50 eventos, no tráfico real?". Redacta la respuesta que el documento, con su propia lógica, ya sostiene.

Ver solución

Una respuesta fiel al documento: "No confiamos en el 12,0% como una medición del volumen real de escalamiento de Andes Cargo — el documento lo dice explícitamente en la sección 3. Confiamos en él como la primera evidencia real, no hipotética, de que el criterio de escalamiento (would_escalate(), reusando parse_manifest() y SHIPMENT_FIELDS_SCHEMA sin modificar) produce un número del mismo orden de magnitud que la hipótesis de 10% que GENAI-COST-PROFILE.md ya declaró meses antes de que este dato existiera. Eso no prueba que Andes Cargo escale exactamente 12% de sus manifiestos reales — prueba que el mecanismo de cálculo funciona como se diseñó, y da una señal razonable de que la hipótesis original no estaba fuera de rango. La medición de tráfico real, si algún día ocurre, reemplazaría este número — pero no invalidaría el trabajo de haberlo calculado con evidencia, en vez de con una suposición sin ningún dato detrás."


Resumen y siguiente paso

Este proyecto final del Módulo 7 escribió AI-OBSERVABILITY.md: ocho secciones que declaran los tres SLI de una carga de IA (escalamiento, tasa de bloqueo del guardrail, latencia), con los dos números reales que este módulo produjo —12,0% de escalamiento sobre un lote fijo de 50 eventos, 40% de tasa de bloqueo sobre el arnés de cinco fixtures— y el límite exacto de latencia y calidad semántica, sin ningún número inventado. Reconciliaste el 12,0% contra la hipótesis de 10% que GENAI-COST-PROFILE.md ya dejó declarada, sin editar ese documento, exactamente como su propia sección de mantenimiento describió. Verificaste el documento con wc y un conteo de secciones, y confirmaste, con la sección "Next steps", que ninguna pieza representativa de este módulo es código descartable.

Antes de avanzar deberías poder: explicar la función de cada una de las ocho secciones del documento; recitar los dos números reales de este módulo sin mirarlo; y explicar por qué el "Next steps" del documento es tan importante como su ledger de honestidad.

Con AI-OBSERVABILITY.md cerrado, el Módulo 7 completo —ocho lecciones, desde el mismo vocabulario SLI/SLO de sre-and-incident-response-guide hasta este panel— queda atrás. El Módulo 8, el capstone de esta guía, recorre el sistema completo de punta a punta, incluida la tasa de escalamiento de este módulo como la prueba viva de la tesis del Módulo 1: un manifiesto bien formado nunca toca Bedrock.

Recursos

  1. sre-and-incident-response-guide, Módulo 2, lección 8 (08-project-andes-cargos-slo-md.md) — la fuente de SLO.md, el documento hermano que este proyecto complementa, nunca reemplaza.
  2. Este mismo curso, Módulo 2, lección 8 — GENAI-COST-PROFILE.md, sección 7, la fuente exacta de la promesa que la sección 3 de este documento cumple.
  3. Este módulo, lecciones 2, 4, 6 y 7 — la fuente completa de cada número y cada límite de este documento.
  4. FinOps Foundation — FinOps Framework, citado por GENAI-COST-PROFILE.md — el mismo espíritu de "declarar hipótesis explícitamente, revisar con datos reales cuando existan" que este documento aplica a observabilidad.