Acto 0Bedrock

Un modelo no sabe nada

Sabe hablar. Es distinto.

Pregúntale a Claude a qué hora es el keynote de este Summit. Corre de verdad, en Bedrock, ahora mismo.

modelo-solo.mjs Bedrock
// Sin herramientas. Sin memoria. Solo el modelo.
await bedrock.send(new ConverseCommand({
  modelId: "claude-haiku-4-5",
  messages: [{ role: "user", content: [{
    text: "¿A qué hora es el keynote del AWS Summit CDMX?"
  }]}]
}));
¿Por qué así? El modelo no tiene acceso a nada. No sabe qué día es, dónde estás ni qué pasa en este edificio. Todo lo que sigue existe para resolver eso.
Acto 1AWS Lambda

Una computadora que no existe

Hasta que alguien la necesita.

No hay servidor encendido esperándote. Llega tu petición, AWS levanta tu código, responde, y lo apaga. Presiona varias veces y mira los dos últimos números.

index.mjs Lambda
export const handler = async (event) => ({
  mensaje: "Hola desde una Lambda en us-east-1",
  arranqueEnFrio,
  contenedorVivoS: process.uptime(),
  invocacionesDeEsteContenedor
});
¿Por qué así? El código tarda 0 ms. Lo que esperaste fue red y, la primera vez, que AWS levantara el contenedor. Después lo reutiliza: por eso el contador sube y los segundos crecen. Es la misma computadora.
Acto 2BedrockCosto real

Cualquier modelo, mismo código

Cambiar de cerebro es cambiar una variable.

Bedrock te da un solo API para modelos de varios proveedores. Elige uno y vuelve a preguntar: cambia el precio, la latencia y el estilo. El código no cambia.

modelos.mjs Bedrock
new BedrockModel({ modelId: MODELOS[elegido].id })
// ↑ la unica linea que cambia entre un modelo y otro
¿Por qué así? Los precios son reales y por millón de tokens. Nova Micro cuesta 29 veces menos que Haiku — pero encadena herramientas peor. Elegir modelo es elegir dónde pones el riesgo.
Acto 3Herramientas

Darle manos

El modelo pide. Nosotros ejecutamos.

Una herramienta es una función tuya, con nombre y descripción. El modelo no la ejecuta: la pide. Estas leen las 118 sesiones reales de este Summit.

tools/agenda.mjs Código nuestro
tool({
  name: "buscar_sesiones",
  description: "Busca sesiones por tema, nivel o track.",
  inputSchema: z.object({
    tema:  z.string().optional(),
    nivel: z.number().optional()
  }),
  callback: async ({ tema, nivel }) => buscar(tema, nivel)
});
¿Por qué así? La descripción es lo que lee el modelo para decidir si le sirve. Escribirla bien es la mitad del trabajo de un agente: no es documentación, es interfaz.
Acto 4Strands

El bucle

Eso es un agente. Nada más.

Llamas al modelo. Si pide una herramienta, la ejecutas. Le devuelves el resultado. Vuelves a llamarlo. Repites hasta que diga "ya".

agent.mjs Strands
for await (const ev of agent.stream(mensaje)) {
  // Strands corre el bucle completo aqui dentro.
  // 5 llamadas a herramientas caben en este for.
}
¿Por qué así? Strands es una librería open source de AWS, no un servicio: se descarga con npm y cuesta $0. Hay tres sesiones sobre ella hoy — pregúntale al agente del acto 8 cuáles.
Acto 5DynamoDB

Lambda no recuerda

Y eso es una virtud, no un defecto.

Cada mensaje puede caer en una copia distinta de la función. Nada sobrevive dentro. Por eso la conversación vive fuera, en una base de datos, con fecha de caducidad.

agent.mjs DynamoDB
await ddb.send(new PutCommand({
  TableName: "encore-demo-estado",
  Item: {
    id: `sesion#${sessionId}`,
    mensajes: JSON.stringify(historial),
    // DynamoDB lo borra solo. Cero codigo de limpieza.
    expiresAt: ahora + 86400
  }
}));
¿Por qué así? Al ser sin estado, AWS puede levantar diez copias iguales sin coordinarlas. La memoria compartida es lo que lo hace posible — y el TTL evita guardar conversaciones para siempre.
Acto 6La regla de oro

El modelo no decide

Nunca. Y hay que poder demostrarlo.

Nuestro stand tiene lugares limitados. Si hay cupo, se confirma; si no, va a lista de espera. Esa decisión no la toma el modelo: la toma DynamoDB, con una escritura condicional atómica.

tools/stand.mjs Determinista
UpdateExpression:    "ADD ocupados :uno",
ConditionExpression: "ocupados < :cupo"
// O la condicion se cumple y sube, o falla y no sube.
// No hay estado intermedio. No hay sobreventa posible.
¿Por qué así? Un modelo puede alucinar o dejarse convencer. Por eso el estado no es algo que nadie pueda pedir: es el resultado de si la condición se cumplió. Aunque el modelo quisiera regalar un lugar, no tiene por dónde.
Acto 7SAM

Todo esto, en un archivo

Y se reconstruye con un comando.

No creamos nada a mano en la consola. Todo el backend —función, base de datos, permisos, endpoint— vive en 110 líneas de YAML.

template.yaml SAM
AgenteFunction:
  Type: AWS::Serverless::Function
  Properties:
    Runtime: nodejs22.x
    Architectures: [arm64]
    FunctionUrlConfig:
      InvokeMode: RESPONSE_STREAM  ← el chat "escribe"
    Policies:
      - DynamoDBCrudPolicy: { TableName: !Ref EstadoTable }
      - Statement: [{ Action: [bedrock:InvokeModel] }]
          ↑ TODO lo que esta funcion tiene permiso de hacer
¿Por qué así? Si alguien borrara todo por accidente, sam deploy lo reconstruye idéntico. Y los permisos son dos: si el código fallara, el radio de daño está acotado por diseño.
Acto 8Está vivo

Pruébalo tú

Conoce las 118 sesiones de hoy. Pregúntale.

0Invocaciones
0Tokens
$0.0000Costo real
Código QR para abrir esta página en tu teléfono

summit.itsebasvz.net

Todo esto corre en AWS: Lambda, Bedrock, DynamoDB, S3 y CloudFront. Nos hicimos esta misma pregunta en el hackathon del DevDays — y de ahí salió lo que sigue.

EnCore · en línea