Módulo 1: Qué cambia cuando un componente es no determinista

El núcleo probabilístico y la cáscara determinista

Descripción

Hasta aquí el módulo te dio piezas sueltas: un contrato que verifica propiedades (lección 2), una frontera que valida propuestas (lección 3), cinco propiedades que entran de golpe (lección 4). Esta lección las une en una sola idea —la que da nombre a la guía entera y ordena todo lo que sigue—: un sistema AI-native es un núcleo probabilístico (el LLM, incierto, variable, falible) que vive dentro de una cáscara determinista (código normal, certero, testeable) que lo contiene. El núcleo aporta la inteligencia que ninguna función determinista podría dar; la cáscara aporta la garantía de que esa inteligencia nunca toque el dinero, el estado o la confianza del usuario sin pasar por reglas duras. La consigna de diseño que se desprende es tan simple como poderosa: mantén el núcleo pequeño y contenido, y carga en la cáscara todo lo que da garantías.

Vas a ver esta idea medida. Un núcleo que a veces devuelve basura (una categoría inventada, un string vacío) corre dos veces: una vez desnudo, sin cáscara, y la basura llega al usuario; otra vez envuelto en una cáscara determinista que la contiene, y ni una sola salida insegura escapa. La diferencia entre 5 salidas basura de 12 y 0 de 12 no es un truco: es la forma completa de un sistema AI-native en un ejemplo mínimo.

Conexión con el módulo. Esta es la lección-metáfora, el centro conceptual del módulo. La 2, la 3 y la 4 fueron mostrando pedazos de la cáscara sin nombrarla; aquí le damos el nombre y la forma, y de aquí en adelante todo se ordena bajo ella. La 6 mide cuánta cáscara necesita cada feature (grosor); la 7 lista los campos de la cáscara (la hoja de propiedades); y la 8 la aplica a una feature real. La frontera con el resto de la guía es precisa: la cáscara determinista a fondo es el módulo 6 —qué patrones la componen, cómo mantener el núcleo mínimo, cómo componer validaciones—. Aquí instalamos la metáfora y la medimos en su forma más simple; no agotamos la cáscara. Y la frontera con AI Engineering se mantiene firme: no hablamos de cómo hacer el núcleo más listo (mejor prompt, mejor modelo, RAG) —eso es AI Eng—; hablamos de cómo contener el núcleo que tengas, sea cual sea su calidad.

Una analogía: el reactor nuclear y su edificio de contención

Un reactor nuclear produce una cantidad de energía que ninguna otra fuente compacta puede igualar. También es intrínsecamente peligroso: reacciones que, sin control, se desbocan. Nadie construye un reactor "desnudo", a la intemperie, confiando en que la reacción se porte bien. Se construye un edificio de contención a su alrededor: paredes de concreto y acero de varios metros, sistemas de enfriamiento redundantes, barreras que aseguran que —pase lo que pase dentro— nada peligroso escape al exterior. La ingeniería nuclear no consiste en hacer que la reacción sea "segura por sí misma" (no lo es); consiste en contenerla para que su enorme energía sea aprovechable sin poner en riesgo lo de afuera.

Fíjate en el reparto de responsabilidades. El reactor (el núcleo) hace una cosa: producir energía. No decide a dónde va esa energía, no gestiona la seguridad, no habla con la red eléctrica. La contención y los sistemas alrededor (la cáscara) hacen todo lo demás: contener, enfriar, monitorear, convertir, distribuir, cortar si algo se sale de rango. Y una regla de oro: el núcleo se mantiene lo más pequeño y acotado posible. Cuanto más grande y desparramado el núcleo reactivo, más difícil de contener. La seguridad vive en mantener el núcleo compacto y la contención robusta.

Aquí está el punto: un LLM es el reactor; la cáscara determinista es el edificio de contención. El LLM produce algo enormemente valioso —comprensión de lenguaje, razonamiento, generación— que ninguna función determinista puede dar. Y es intrínsecamente incierto: variable, falible, a veces "desbocado" (una alucinación, una respuesta a una inyección). El diseño AI-native no intenta hacer el LLM "seguro por sí mismo" —imposible, igual que un reactor—; lo contiene. Mantiene el núcleo probabilístico pequeño (que solo proponga, una responsabilidad) y pone alrededor una cáscara determinista robusta que valida, acota, degrada y garantiza que nada peligroso escape al sistema. En Mercado, el clasificador de categorías es el reactor; la cáscara que solo deja pasar categorías válidas es la contención; y ninguna categoría inventada llega jamás al catálogo, por más que el reactor la produzca.

Ejemplo trabajado: la cáscara contiene, medido

Vamos a medir la contención. El núcleo es un probabilistic_core: un stub que simula un LLM sugiriendo la categoría de un producto. A propósito, a veces sugiere una categoría válida y a veces devuelve basura —una categoría mal escrita, una inventada, un string vacío—, porque el punto es ver qué hace la cáscara con la basura. Corremos el mismo núcleo dos veces: desnudo (sin cáscara) y contenido (con cáscara), con la misma semilla, y contamos cuántas salidas inseguras escapan en cada caso.

# Lección 5: el núcleo probabilístico dentro de la cáscara determinista.
# Idea central de la guía: mantén el núcleo (el LLM) PEQUEÑO y CONTENIDO;
# la cáscara determinista valida, acota y da una salida segura pase lo que pase.
import random

_RNG = random.Random(3)

# --- Núcleo probabilístico: SIMULA un LLM que sugiere un tag de categoría. ---
# A veces sugiere una categoría válida; a veces devuelve basura (alucinación).
VALID_CATEGORIES = {"audio", "computo", "hogar", "moda", "libros"}


def probabilistic_core(product_title):
    # El LLM simulado: una sola responsabilidad, y salida NO confiable.
    candidates = ["audio", "computo", "hogar", "moda", "libros",
                  "electrncs", "", "categoria-inventada-42"]
    return _RNG.choice(candidates)


# --- Cáscara determinista: contiene la no-determinación del núcleo. ---
def deterministic_shell(product_title):
    suggestion = probabilistic_core(product_title)   # el núcleo PROPONE
    if suggestion in VALID_CATEGORIES:               # la cáscara VALIDA
        return suggestion
    return "sin_clasificar"                           # fallback determinista


N = 12
print("=== Sin cascara: la salida cruda del nucleo llega al usuario ===")
_RNG.seed(3)
leaked = 0
for i in range(N):
    raw = probabilistic_core("Auriculares BT")
    is_valid = raw in VALID_CATEGORIES
    if not is_valid:
        leaked += 1
    print(f"  req {i:>2}: {raw!r:<24} {'ok' if is_valid else 'BASURA AL USUARIO'}")
print(f"Salidas invalidas que escaparon: {leaked}/{N}")

print()
print("=== Con cascara: el nucleo se contiene, la salida SIEMPRE es valida ===")
_RNG.seed(3)
unsafe = 0
for i in range(N):
    result = deterministic_shell("Auriculares BT")
    safe = result in VALID_CATEGORIES or result == "sin_clasificar"
    if not safe:
        unsafe += 1
    print(f"  req {i:>2}: {result!r:<16} {'seguro' if safe else 'INSEGURO'}")
print(f"Salidas inseguras que escaparon: {unsafe}/{N}")

print()
print("=== Superficie: responsabilidades del nucleo vs de la cascara ===")
print("  nucleo   (probabilistico): 1  -> proponer un tag")
print("  cascara  (determinista):   3  -> validar, dar fallback, garantizar salida")
print("Regla: nucleo pequeno y contenido; la cascara carga el resto.")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

=== Sin cascara: la salida cruda del nucleo llega al usuario ===
  req  0: 'moda'                   ok
  req  1: 'hogar'                  ok
  req  2: 'electrncs'              BASURA AL USUARIO
  req  3: 'categoria-inventada-42' BASURA AL USUARIO
  req  4: 'computo'                ok
  req  5: 'audio'                  ok
  req  6: 'categoria-inventada-42' BASURA AL USUARIO
  req  7: 'libros'                 ok
  req  8: 'moda'                   ok
  req  9: 'moda'                   ok
  req 10: 'categoria-inventada-42' BASURA AL USUARIO
  req 11: 'categoria-inventada-42' BASURA AL USUARIO
Salidas invalidas que escaparon: 5/12

=== Con cascara: el nucleo se contiene, la salida SIEMPRE es valida ===
  req  0: 'moda'           seguro
  req  1: 'hogar'          seguro
  req  2: 'sin_clasificar' seguro
  req  3: 'sin_clasificar' seguro
  req  4: 'computo'        seguro
  req  5: 'audio'          seguro
  req  6: 'sin_clasificar' seguro
  req  7: 'libros'         seguro
  req  8: 'moda'           seguro
  req  9: 'moda'           seguro
  req 10: 'sin_clasificar' seguro
  req 11: 'sin_clasificar' seguro
Salidas inseguras que escaparon: 0/12

y termina con:

=== Superficie: responsabilidades del nucleo vs de la cascara ===
  nucleo   (probabilistico): 1  -> proponer un tag
  cascara  (determinista):   3  -> validar, dar fallback, garantizar salida
Regla: nucleo pequeno y contenido; la cascara carga el resto.

Compara las dos corridas, porque la diferencia entre ellas es la lección.

Sin cáscara, el núcleo desnudo produce lo que produce, y lo que produce llega tal cual al usuario. En 12 requests, 5 salidas basura escaparon: dos veces la categoría mal escrita 'electrncs', y cuatro veces la inventada 'categoria-inventada-42' (una de ellas habría sido un string vacío en otra semilla). Cada una de esas cinco es un producto mal clasificado en el catálogo de Mercado, visible para el cliente, roto. El núcleo hizo lo que un modelo probabilístico hace; la ausencia de contención convirtió su falibilidad en errores expuestos.

Con cáscara, el núcleo es idéntico —misma semilla, mismas sugerencias basura por dentro—, pero ahora cada sugerencia pasa por deterministic_shell antes de salir. Las categorías válidas pasan; las basura se convierten en 'sin_clasificar', un fallback determinista, honesto y seguro. En 12 requests, 0 salidas inseguras escaparon. El núcleo siguió fallando exactamente igual de seguido; la diferencia es que su falla ya no llega al usuario. Eso es la contención: no eliminaste la falibilidad del núcleo (no puedes), la contuviste para que no escape.

Y el cierre mide la consigna de diseño: el núcleo tiene 1 responsabilidad (proponer un tag), la cáscara tiene 3 (validar, dar fallback, garantizar una salida siempre válida). Esa asimetría es deliberada y es la regla de oro del reactor: el núcleo pequeño y acotado, la contención cargando todo el peso de las garantías. Un sistema AI-native bien diseñado se ve así —una gota de incertidumbre rodeada de un océano de código determinista, certero y testeable—.

Profundización: la forma de un sistema AI-native

Vale la pena dibujar la forma completa, porque es el molde de todo lo que viene.

        ┌─────────────────────────────────────────────┐
        │        CÁSCARA DETERMINISTA (certera)        │
        │                                              │
        │   valida entrada · valida salida · fallback  │
        │   presupuesto · eval gate · guardrail        │
        │                                              │
        │        ┌───────────────────────────┐         │
        │        │   NÚCLEO PROBABILÍSTICO    │         │
        │        │   (el LLM: incierto)       │         │
        │        │   una responsabilidad:    │         │
        │        │        PROPONER            │         │
        │        └───────────────────────────┘         │
        │                                              │
        └─────────────────────────────────────────────┘
                         │
                         ▼
              estado / dinero / usuario
           (solo lo que la cáscara aprobó)

El núcleo es pequeño a propósito. La tentación es meterle más al núcleo —que el LLM no solo proponga la categoría sino que también decida si publicar, calcule el precio, envíe el correo—. Cada responsabilidad extra que le das al núcleo es un pedazo más de sistema que hereda la incertidumbre: más superficie que contener, más formas de fallar sin garantía. La disciplina AI-native es la contraria: saca del núcleo todo lo que pueda ser determinista. El LLM propone una categoría; la cáscara decide qué hacer con ella. El LLM propone un reembolso; la cáscara valida la política. Cuanto más chico el núcleo, más del sistema es certero y testeable, y más fácil de contener es lo poco que queda incierto.

La cáscara es donde vive la guía entera. Mira las etiquetas dentro de la cáscara en el diagrama: validar entrada y salida (guardrails, módulo 4), fallback (módulo 5), presupuesto de latencia/costo (módulo 2), eval gate (módulo 3). No es casualidad: cada módulo de esta guía es una parte de la cáscara. Aprender a construir sistemas AI-native es, casi literalmente, aprender a construir la cáscara —porque el núcleo lo construye AI Engineering, y aquí lo tratamos como una caja que propone—. La lección 7 hace esto explícito con la hoja de propiedades, donde cada campo es una porción de la cáscara y apunta a su módulo.

La cáscara garantiza; el núcleo no puede. Esta es la razón profunda por la que el diseño funciona. No puedes garantizar nada sobre la salida del núcleo —es probabilístico, puede alucinar—. Pero puedes garantizar cosas sobre la cáscara, porque es determinista: "ninguna categoría inválida llega al catálogo" es una garantía que la cáscara cumple siempre, sin importar qué haga el núcleo, porque if suggestion in VALID_CATEGORIES es una condición dura. El sistema entero obtiene garantías fuertes a pesar de tener un componente sin garantías, y el truco es que las garantías las da la contención, no el núcleo. Por eso la cáscara es determinista y no otro LLM: solo el código determinista puede prometer algo con certeza.

Contener no es desperdiciar el núcleo. Podría parecer que si la cáscara "arregla" tanto, el núcleo importa poco. Al revés: la cáscara existe para poder usar un núcleo tan potente. Sin contención, no te atreverías a poner un LLM cerca del dinero o del catálogo —sería demasiado riesgoso—. La cáscara es lo que hace seguro aprovechar la inteligencia del núcleo, igual que la contención es lo que hace seguro aprovechar la energía del reactor. No compiten: la cáscara es la condición que habilita al núcleo.

Errores comunes

Meterle demasiadas responsabilidades al núcleo. Qué pasa: el equipo, entusiasmado con lo capaz que es el modelo, le encarga toda una cadena —"que clasifique el producto, decida si es apto para publicar, sugiera el precio y redacte la descripción"—, todo dentro de una sola llamada al LLM. Cada eslabón de esa cadena hereda la incertidumbre del modelo. Por qué pasa: es más cómodo pedirle todo al núcleo que separar lo determinista. Cómo detectarlo: tu núcleo produce decisiones que tienen efectos (publicar, fijar precio), no solo propuestas que la cáscara valida. Cómo corregirlo: saca del núcleo todo lo que pueda ser determinista y déjale una sola responsabilidad de propuesta. La clasificación puede ser del LLM; "es apto para publicar" es una regla determinista sobre esa clasificación; "el precio" no debería ser del LLM en absoluto. Núcleo pequeño, cáscara robusta.

Construir la cáscara con otro LLM. Qué pasa: para "contener" al modelo, alguien pone un segundo modelo a revisar las salidas del primero, creyendo que así lo valida. Por qué pasa: revisar suena a "entender", y entender es lo que un LLM hace bien. Cómo detectarlo: tu capa de contención es en sí misma no determinista; no puedes garantizar nada sobre ella. Cómo corregirlo: la cáscara tiene que ser determinista, porque solo lo determinista da garantías. Un segundo LLM no contiene al primero; solo agrega otro núcleo que a su vez habría que contener —una torre infinita de modelos revisándose sin que nadie prometa nada—. La garantía tiene que venir, en algún punto, de una condición dura de código. (Un segundo modelo puede ayudar a filtrar, pero no es la contención; la contención es la regla determinista final.)

Creer que un núcleo mejor elimina la necesidad de cáscara. Qué pasa: "cuando usemos el modelo grande y afinemos el prompt, va a alucinar tan poco que no hará falta validar la salida". Por qué pasa: se confunde "menos falible" con "infalible", y se apuesta la seguridad del sistema a la calidad del núcleo. Cómo detectarlo: tu justificación para no tener cáscara es la calidad del modelo. Cómo corregirlo: recuerda el reactor —no se contiene menos un reactor porque el combustible sea de mejor calidad—. Un núcleo mejor alucina menos seguido, pero sigue alucinando, y a escala de Mercado "menos seguido" son miles de errores. La cáscara no es proporcional a la falibilidad del núcleo; es la garantía que hace el error imposible de escapar, y esa garantía la quieres tengas el modelo que tengas. Mejorar el núcleo (AI Eng) y contenerlo (esta guía) son dos trabajos distintos, y el segundo no desaparece con el primero.

Ejercicios

Ejercicio 1 — Núcleo o cáscara. Para el generador "describe tu producto" de Mercado, clasifica cada responsabilidad como parte del núcleo probabilístico (solo el LLM puede hacerlo, es propuesta incierta) o de la cáscara determinista (debe ser código certero con garantías): (a) redactar el texto de la descripción a partir de los atributos; (b) verificar que la descripción no exceda 200 caracteres; (c) rechazar la descripción si contiene un claim prohibido; (d) decidir el precio de venta del producto.

Ver solución
  • (a) Redactar la descripción → núcleo. Es exactamente lo que un LLM hace y una función determinista no podría: generar lenguaje natural a partir de atributos. Es una propuesta incierta (variará entre llamadas). Es la única responsabilidad que le corresponde al núcleo.
  • (b) Verificar ≤ 200 caracteres → cáscara. Es una condición dura y determinista (len(text) <= 200), con una garantía clara. Nada probabilístico. Pertenece a la contención.
  • (c) Rechazar claims prohibidos → cáscara. También determinista: una lista de términos prohibidos y una comprobación de pertenencia. Da una garantía ("ninguna descripción con un claim prohibido se publica") que solo el código certero puede prometer.
  • (d) Decidir el precio → ni núcleo ni cáscara: probablemente el LLM no debería estar involucrado. El precio es una decisión de negocio con reglas propias; no es una tarea de generación de lenguaje. Meterla al núcleo sería el error de "demasiadas responsabilidades". Si acaso el LLM sugiere un rango, la decisión final es determinista (o humana) en la cáscara; pero lo más sano aquí es que el precio simplemente no pase por el modelo.

La forma sana: un núcleo con una sola responsabilidad (a), rodeado de una cáscara con varias garantías (b, c), y responsabilidades de negocio (d) que ni entran al núcleo.

Ejercicio 2 — Mide la contención. En el ejemplo, sin cáscara escaparon 5 de 12 salidas basura y con cáscara 0 de 12. Si el núcleo mejorara (menos alucinaciones) y su tasa de basura bajara de ~40% a ~10%, ¿cuántas salidas inseguras escaparían con cáscara? ¿Y sin cáscara, a escala de un millón de clasificaciones al mes? Usa los números para argumentar por qué la cáscara sigue siendo necesaria aunque el núcleo mejore.

Ver solución

Con cáscara, escaparían 0, igual que antes. La cáscara garantiza que ninguna salida inválida pasa, y esa garantía no depende de la tasa de basura del núcleo: if suggestion in VALID_CATEGORIES bloquea el 40% o el 10% con la misma certeza. Mejore el núcleo lo que mejore, la contención sigue en 0 escapes.

Sin cáscara, a una tasa del 10% sobre un millón de clasificaciones al mes, escaparían del orden de 100,000 salidas basura al mes —cien mil productos mal clasificados, visibles para clientes—. Que el núcleo haya mejorado de 40% a 10% redujo el desastre (de ~400,000 a ~100,000), pero cien mil errores mensuales expuestos siguen siendo inaceptables.

El argumento: mejorar el núcleo reduce la cantidad de basura que produce, pero no la lleva a cero (un LLM siempre tiene una tasa de error > 0), y a escala cualquier tasa positiva son muchos errores absolutos. La cáscara, en cambio, lleva los escapes a cero independientemente de la tasa del núcleo. Por eso las dos cosas son complementarias y no sustitutas: AI Engineering baja la tasa de basura del núcleo; la cáscara de esta guía garantiza que la basura que quede no escape. Apostar la seguridad solo a mejorar el núcleo es aceptar "100,000 errores al mes" como el piso; agregar la cáscara lo lleva a cero.

Ejercicio 3 — El fallback honesto. En el ejemplo, cuando el núcleo sugiere basura, la cáscara devuelve 'sin_clasificar' en vez de inventar una categoría o de dejar pasar la basura. Explica por qué 'sin_clasificar' es un mejor fallback que (a) elegir una categoría al azar, o (b) usar la última categoría válida que el núcleo sugirió, y qué principio de diseño ilustra.

Ver solución

'sin_clasificar' es mejor que las dos alternativas porque es honesto sobre la incertidumbre en lugar de disfrazarla:

  • (a) Elegir una categoría al azar sería inventar información. El producto quedaría, digamos, en "libros" sin ninguna base —tan malo como la basura del núcleo, pero ahora disfrazado de dato válido, imposible de distinguir de una clasificación real—. Convierte un error visible ("no sé") en un error invisible ("dato falso que parece verdadero"), que es peor.
  • (b) Reusar la última categoría válida arrastra un dato de otro producto a este. Un teclado podría heredar "moda" porque el producto anterior era una camiseta. Otra vez: información inventada que parece legítima.

'sin_clasificar' dice la verdad: "el núcleo no dio una respuesta usable, y el sistema lo reconoce". Eso permite un manejo correcto río abajo —mostrar el producto en una sección sin categoría, encolarlo para clasificación manual, o pedirle al vendedor que elija—. El principio que ilustra: un fallback debe degradar de forma segura y honesta, no fabricar certeza que no existe. Es mejor un "no sé" explícito y contenido que un "sí" inventado que se filtra como verdad. El módulo 5 desarrolla los patrones de fallback; aquí el principio es que el fallback preserva la honestidad del sistema sobre lo que sabe y lo que no.

Resumen y siguiente paso

En esta lección uniste las piezas sueltas del módulo en la idea central de toda la guía: un sistema AI-native es un núcleo probabilístico dentro de una cáscara determinista. El núcleo (el LLM) aporta la inteligencia y es incierto; la cáscara (código normal) aporta las garantías y es certera. Lo mediste con el reactor y su contención: el mismo núcleo falible dejó escapar 5 de 12 salidas basura sin cáscara y 0 de 12 con cáscara, sin que el núcleo cambiara —la contención, no un núcleo perfecto, es lo que da la garantía—. Y viste la consigna de diseño: mantén el núcleo pequeño (una responsabilidad: proponer), carga en la cáscara todo lo que garantiza, hazla determinista porque solo lo determinista promete con certeza, y usa fallbacks honestos que no fabriquen certeza inexistente.

Antes de avanzar deberías poder: dibujar la forma núcleo/cáscara de un sistema AI-native; explicar por qué el núcleo se mantiene pequeño a propósito; argumentar por qué la cáscara debe ser determinista y no otro LLM; y por qué un núcleo mejor no elimina la necesidad de cáscara.

Ya tienes la forma. La lección 6 hace la pregunta natural que sigue: si todo sistema AI-native es núcleo más cáscara, ¿todas las features necesitan la misma cáscara? No. Vas a ver, ejecutado, que la tolerancia a la no-determinación de cada feature de Mercado —lo que mediste a vuelo de pájaro en la lección 1— determina qué tan gruesa debe ser su cáscara: la búsqueda semántica pide una delgada, el agente de reembolsos una que lo abarca todo. De la tolerancia se derivan, calculados, los mecanismos de contención obligatorios de cada feature.

Recursos

  • Anthropic, "Building Effective Agents" (2024) — anthropic.com/engineering/building-effective-agents. La insistencia en mantener el componente de IA simple y acotado, y en poner el trabajo determinista alrededor, es exactamente la consigna "núcleo pequeño, cáscara robusta" de esta lección. En inglés.
  • Martin Fowler y Bharani Subramaniam, "Emerging Patterns in Building GenAI Apps" — martinfowler.com/articles/gen-ai-patterns. Los patrones de guardrail y de verificación de salida son las paredes de contención de esta lección, descritas como patrones reutilizables. En inglés.
  • Chip Huyen, AI Engineering (O'Reilly, 2024). Su tratamiento de la arquitectura de aplicaciones —el modelo como un componente rodeado de lógica de aplicación— es la versión extendida de la metáfora núcleo/cáscara. Nosotros nos quedamos con la contención; construir el núcleo es la frontera con AI Eng. En inglés.
  • Michael Nygard, Release It! (2ª ed., Pragmatic Bookshelf, 2018). Aunque es sobre resiliencia en sistemas distribuidos (no IA), los patrones de contención —bulkhead, circuit breaker— son la misma idea: contener un componente riesgoso para que su falla no se propague. La cáscara AI-native es un pariente directo. Se aplica en el módulo 5. En inglés.