Lab práctico · Semana 1: Fundamentos de IA generativa y selección de modelos en Amazon Bedrock
Prepara la cuenta y haz tu primera llamada a Amazon Bedrock
Qué vas a construir
Vas a dejar lista la cuenta de AWS para todo el curso y a hablar por primera vez con un modelo fundacional:
- Un presupuesto en AWS Budgets con avisos por correo, antes de gastar el primer céntimo.
- La región de trabajo elegida con criterio (eu-central-1 o eu-west-1).
- Un repaso del catálogo de modelos, sus estados de ciclo de vida y los perfiles de inferencia de la UE.
- Tu primera llamada con la Converse API, desde la AWS CLI y desde Python (
boto3), en modo normal y en streaming (el streaming, solo desde Python: la AWS CLI no ofrececonverse-streamniinvoke-model-with-response-stream). - La comprobación en CloudTrail de que la llamada quedó registrada.
flowchart LR
CS["AWS CloudShell (tu identidad IAM)"] -->|"Converse / ConverseStream"| BR["Amazon Bedrock (eu-central-1)"]
BR -->|"perfil eu."| EU["Regiones de la UE que sirven Nova Micro"]
BR --> CT["AWS CloudTrail: evento Converse"]
BU["AWS Budgets"] -->|"correo al 50 % y al 80 %"| TU["Tú"]
Antes de empezar
- Una cuenta de AWS a la que accedas con un usuario de IAM Identity Center o un rol de IAM, no con el usuario raíz (lo sabes del SAA-C03: raíz con MFA y guardada en un cajón).
- Plan de la cuenta: Amazon Bedrock funciona en el Free plan con créditos, pero algunas ofertas de AWS Marketplace pueden no estar disponibles en ese plan, y Amazon Bedrock AgentCore exige el Paid plan (lo usarás en el módulo 5). Si tu cuenta es nueva y estás en el Free plan, valora pasar al Paid plan antes del módulo 5; los créditos se siguen aplicando.
- Abre AWS CloudShell desde la consola, en la región Europe (Frankfurt) eu-central-1. CloudShell ya trae AWS CLI v2, Python 3 y
boto3, y usa las credenciales temporales de tu sesión: no hay claves de acceso que guardar.
Paso 1: crea un presupuesto con alertas
Hazlo antes que nada. AWS Budgets queda fuera del temario del examen, pero es tu red de seguridad.
export AWS_REGION=eu-central-1
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "Cuenta: $ACCOUNT_ID"
Define un presupuesto mensual de 20 USD (ajústalo a tu caso):
cat > presupuesto.json <<'EOF'
{
"BudgetName": "curso-genai-mensual",
"BudgetLimit": { "Amount": "20", "Unit": "USD" },
"BudgetType": "COST",
"TimeUnit": "MONTHLY"
}
EOF
Y dos avisos por correo: gasto real por encima del 50 % y gasto previsto por encima del 80 % (cambia la dirección):
cat > avisos.json <<'EOF'
[
{
"Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 50, "ThresholdType": "PERCENTAGE" },
"Subscribers": [ { "SubscriptionType": "EMAIL", "Address": "tu-correo@ejemplo.com" } ]
},
{
"Notification": { "NotificationType": "FORECASTED", "ComparisonOperator": "GREATER_THAN", "Threshold": 80, "ThresholdType": "PERCENTAGE" },
"Subscribers": [ { "SubscriptionType": "EMAIL", "Address": "tu-correo@ejemplo.com" } ]
}
]
EOF
aws budgets create-budget --account-id $ACCOUNT_ID \
--budget file://presupuesto.json \
--notifications-with-subscribers file://avisos.json
aws budgets describe-budgets --account-id $ACCOUNT_ID \
--query "Budgets[].{Nombre:BudgetName,Limite:BudgetLimit.Amount}" --output table
Paso 2: explora el catálogo de modelos
Lista los modelos de Amazon disponibles en la región con su estado de ciclo de vida:
aws bedrock list-foundation-models --by-provider amazon \
--query "modelSummaries[].{id:modelId,entrada:join(',',inputModalities),salida:join(',',outputModalities),estado:modelLifecycle.status}" \
--output table
Fíjate en dos cosas:
- Aparecen modelos ACTIVE y LEGACY. Un modelo Legacy tiene fecha de retirada: no lo elijas para algo nuevo.
- Hay modelos de embeddings (salida
EMBEDDING) y de texto (salidaTEXT).
Ahora los perfiles de inferencia de sistema de la UE:
aws bedrock list-inference-profiles --type-equals SYSTEM_DEFINED \
--query "inferenceProfileSummaries[?starts_with(inferenceProfileId, 'eu.')].inferenceProfileId" \
--output text | tr '\t' '\n' | sort | head -40
Busca eu.amazon.nova-micro-v1:0. Mira a qué regiones enruta:
aws bedrock get-inference-profile --inference-profile-identifier eu.amazon.nova-micro-v1:0 \
--query "models[].modelArn" --output text | tr '\t' '\n'
Verás ARN de modelos en varias regiones de la UE: esas son las regiones a las que Bedrock puede enviar tus peticiones. Nunca sale de la UE, pero sí de Fráncfort.
Paso 3: acceso a modelos
- Modelos de Amazon (Nova, Titan): no requieren nada más. Los usarás en casi todos los labs.
- Modelos de terceros (Anthropic, Cohere…): el acceso está habilitado por defecto si tu identidad tiene los permisos de AWS Marketplace
aws-marketplace:Subscribe,aws-marketplace:Unsubscribeyaws-marketplace:ViewSubscriptionsy la cuenta tiene un método de pago válido. La primera invocación lanza la suscripción en segundo plano (hasta 15 minutos). - Anthropic pide además, una sola vez por cuenta, el formulario de caso de uso (First Time Use). Si quieres probar Claude Haiku 4.5 en el paso 6, entra en la consola de Bedrock, abre el playground de chat, elige un modelo de Anthropic y rellena el formulario (vale una descripción personal del uso, por ejemplo «estudio para la certificación AWS»).
Así sería una política de mínimo privilegio para una aplicación que solo debe usar Nova Micro a través del perfil de la UE (no hace falta que la apliques ahora; observa que incluye el ARN del perfil y los del modelo en las regiones de destino):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SoloNovaMicroEU",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:eu-central-1:111122223333:inference-profile/eu.amazon.nova-micro-v1:0",
"arn:aws:bedrock:*::foundation-model/amazon.nova-micro-v1:0"
]
}
]
}
Paso 4: tu primera llamada desde la AWS CLI
aws bedrock-runtime converse \
--model-id eu.amazon.nova-micro-v1:0 \
--system '[{"text": "Eres un profesor que explica conceptos de IA en español, en frases cortas."}]' \
--messages '[{"role": "user", "content": [{"text": "¿Qué es un token en un modelo de lenguaje? Tres frases como máximo."}]}]' \
--inference-config '{"maxTokens": 200, "temperature": 0.3}'
Lee la respuesta con calma:
output.message.content[0].text: el texto generado.stopReason: debería serend_turn.usage:inputTokens,outputTokens,totalTokens. Esto es lo que pagas.metrics.latencyMs: cuánto tardó el modelo.
Paso 5: la misma llamada desde Python
Crea primera.py:
import boto3
cliente = boto3.client("bedrock-runtime", region_name="eu-central-1")
MODELO = "eu.amazon.nova-micro-v1:0"
PRECIO_IN, PRECIO_OUT = 0.046, 0.184 # USD por millón de tokens, eu-central-1, 01/10/2026 (orientativo)
conversacion = [
{"role": "user", "content": [{"text": "Dame una definición de embedding en una frase."}]}
]
r = cliente.converse(
modelId=MODELO,
system=[{"text": "Responde en español de España, de forma precisa y breve."}],
messages=conversacion,
inferenceConfig={"maxTokens": 150, "temperature": 0.2},
)
texto = r["output"]["message"]["content"][0]["text"]
uso = r["usage"]
print(texto)
print(f"stopReason={r['stopReason']} | entrada={uso['inputTokens']} salida={uso['outputTokens']} "
f"| latencia={r['metrics']['latencyMs']} ms")
print(f"Coste estimado: {uso['inputTokens']/1e6*PRECIO_IN + uso['outputTokens']/1e6*PRECIO_OUT:.8f} USD")
# Segundo turno: el modelo NO recuerda nada; le reenvías el historial completo.
conversacion.append(r["output"]["message"])
conversacion.append({"role": "user", "content": [{"text": "Ahora ponme un ejemplo cotidiano."}]})
r2 = cliente.converse(modelId=MODELO, messages=conversacion, inferenceConfig={"maxTokens": 150})
print("\nSegundo turno:", r2["output"]["message"]["content"][0]["text"])
print("Tokens de entrada del segundo turno:", r2["usage"]["inputTokens"])
python3 primera.py
Paso 6: respuesta en streaming
Con ConverseStream recibes el texto a trozos según se genera: el usuario ve la primera palabra mucho antes. Crea stream.py:
import boto3
cliente = boto3.client("bedrock-runtime", region_name="eu-central-1")
r = cliente.converse_stream(
modelId="eu.amazon.nova-micro-v1:0",
messages=[{"role": "user", "content": [{"text": "Explica en un párrafo qué es Amazon Bedrock."}]}],
inferenceConfig={"maxTokens": 300, "temperature": 0.5},
)
for evento in r["stream"]:
if "contentBlockDelta" in evento:
print(evento["contentBlockDelta"]["delta"]["text"], end="", flush=True)
elif "messageStop" in evento:
print(f"\n\nstopReason: {evento['messageStop']['stopReason']}")
elif "metadata" in evento:
print("Uso:", evento["metadata"]["usage"], "| latencia:", evento["metadata"]["metrics"]["latencyMs"], "ms")
python3 stream.py
Opcional (si rellenaste el formulario de Anthropic): cambia el modelo por eu.anthropic.claude-haiku-4-5-20251001-v1:0 y ejecuta de nuevo. Es el mismo código: esa es la gracia de la Converse API. Coste orientativo de la prueba: menos de 0,01 USD.
Paso 7: la llamada, en CloudTrail
Las llamadas Converse y ConverseStream se registran en CloudTrail como eventos de gestión (visibles en Event history sin configurar nada). El contenido de prompts y respuestas no aparece: para eso está el model invocation logging (módulo 8).
aws cloudtrail lookup-events --region eu-central-1 \
--lookup-attributes AttributeKey=EventName,AttributeValue=Converse \
--max-results 3 --query "Events[].{hora:EventTime,usuario:Username}" --output table
Puede tardar unos minutos en aparecer. Si abres un evento completo (--query "Events[0].CloudTrailEvent"), verás el modelId en requestParameters y, en llamadas con perfiles entre regiones, la región que procesó la petición en additionalEventData.inferenceRegion.
Comprueba que funciona
aws budgets describe-budgetsmuestracurso-genai-mensualy has recibido (o recibirás) el correo de confirmación de la suscripción a avisos, si aplica.- La llamada de la CLI devuelve
stopReason: end_turny un bloqueusage. primera.pyimprime la respuesta, el coste estimado (millonésimas de dólar) y un segundo turno con más tokens de entrada.stream.pymuestra el texto apareciendo poco a poco.- CloudTrail muestra al menos un evento
Converse.
Limpieza
Este lab no deja recursos con coste. Mantén el presupuesto durante todo el curso. Borra solo los ficheros:
rm -f presupuesto.json avisos.json primera.py stream.py
Si algún día quieres eliminar el presupuesto:
aws budgets delete-budget --account-id $ACCOUNT_ID --budget-name curso-genai-mensual
Preguntas para pensar como arquitecto
1. Tu empresa exige que ningún dato de clientes se procese fuera de Alemania. ¿Te vale el perfil eu.amazon.nova-micro-v1:0?
No. El perfil geográfico de la UE puede procesar la petición en varias regiones de la UE (Estocolmo, Irlanda, París…), no solo en Fráncfort. Necesitas un modelo con disponibilidad In-Region en eu-central-1 (por ejemplo, alguno de los que la tabla de disponibilidad marca así, como gpt-oss o Qwen3 a fecha de hoy), o bien alojar el modelo tú mismo en esa región (Custom Model Import o SageMaker AI). Revisa siempre la tabla de disponibilidad de la ficha del modelo.
2. ¿Por qué CloudShell no necesita claves de acceso y por qué eso importa en producción?
CloudShell usa las credenciales temporales de tu sesión de consola. En producción el principio es el mismo: Lambda, ECS o EC2 obtienen credenciales temporales de su rol de IAM. Nunca incrustes claves en el código. Las API keys de Bedrock de largo plazo, según AWS, son solo para exploración.
3. En el segundo turno pagaste más tokens de entrada. Propón dos formas de contenerlo en un chat con conversaciones largas.
(1) Mantener solo los últimos N turnos y un resumen de los anteriores (generado por un modelo barato). (2) Prompt caching de la parte estable del prompt (instrucciones de sistema largas, documentos fijos), que abarata y acelera los tokens repetidos en los modelos que lo soportan. Lo verás en el módulo 9.
4. Un compañero propone usar el ID «pelado» anthropic.claude-haiku-4-5… en vez del perfil. ¿Qué pasará?
Según la ficha del modelo, en el endpoint bedrock-runtime Claude Haiku 4.5 exige un perfil de inferencia geográfico o global para on-demand: el ID sin prefijo no está admitido y la llamada fallará con un error de validación. Para inferencia en una sola región, la ficha indica el endpoint bedrock-mantle con el ID sin prefijo, en las regiones donde está disponible.