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.
// 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?" }]}] }));
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.
export const handler = async (event) => ({ mensaje: "Hola desde una Lambda en us-east-1", arranqueEnFrio, contenedorVivoS: process.uptime(), invocacionesDeEsteContenedor });
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.
new BedrockModel({ modelId: MODELOS[elegido].id }) // ↑ la unica linea que cambia entre un modelo y otro
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.
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)
});
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".
for await (const ev of agent.stream(mensaje)) { // Strands corre el bucle completo aqui dentro. // 5 llamadas a herramientas caben en este for. }
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.
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 } }));
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.
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.
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.
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
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.
Pruébalo tú
Conoce las 118 sesiones de hoy. Pregúntale.
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.