Lab práctico · Semana 5: IA agéntica, herramientas y Amazon Bedrock AgentCore
Desplegar el agente en Amazon Bedrock AgentCore Runtime
Qué vas a construir
Vas a llevar a producción el agente del lab 09: lo empaquetarás con la AgentCore CLI, lo desplegarás en Amazon Bedrock AgentCore Runtime (serverless, una microVM aislada por sesión), lo invocarás desde la CLI y desde Python con InvokeAgentRuntime, comprobarás que cada sesión conserva su contexto y que las sesiones están aisladas, y revisarás logs y trazas. Al final, verás cómo cada despliegue crea una versión nueva del runtime.
flowchart LR
CS["CloudShell: agentcore CLI"] -->|"agentcore deploy (CDK)"| CFN["CloudFormation"]
CFN --> RT["AgentCore Runtime: agente Tienda"]
CFN --> ROL["Rol IAM de ejecución"]
CFN --> S3["Bucket S3 con el zip del código"]
CLI2["agentcore invoke / boto3"] -->|"InvokeAgentRuntime + runtimeSessionId"| RT
RT -->|"Converse"| NOVA["Amazon Nova Lite (perfil eu.)"]
RT -.-> CW["CloudWatch: logs y trazas"]
Antes de empezar
- Cuenta en plan de pago, con una identidad con permisos amplios (administrador de tu cuenta de estudio): el despliegue crea roles IAM, un stack de CloudFormation y hace el bootstrap de AWS CDK.
- Región eu-central-1 (Fráncfort): tiene Runtime, Memory, Gateway, Identity, Observability y el resto de piezas de AgentCore. (En eu-south-2 no hay AgentCore Memory ni Harness.)
- Haber hecho el lab 09 (entenderás el código). No necesitas conservar su carpeta.
- Requisitos de la AgentCore CLI: Node.js 20 o superior, npm y uv (lo usa para empaquetar el código Python). Docker no hace falta porque usarás el tipo de build
CodeZip.
Paso 1: prepara CloudShell
Abre CloudShell en Fráncfort y comprueba versiones:
node --version
npm --version
echo "$AWS_REGION"
aws sts get-caller-identity --query Account --output text
Si node --version es inferior a 20, instala una versión más reciente para esta sesión (Amazon Linux 2023 la ofrece como paquete; se pierde al cerrar la sesión):
sudo dnf install -y nodejs22 && node-22 --version
Instala uv (en tu directorio personal, persiste) y la AgentCore CLI (paquete npm @aws/agentcore):
pip3 install --user --quiet uv
export PATH="$HOME/.local/bin:$PATH"
uv --version
npm install -g @aws/agentcore || sudo npm install -g @aws/agentcore
agentcore --version
Las instalaciones globales de npm quedan fuera de $HOME: si cierras CloudShell, repite el npm install -g.
Paso 2: crea el proyecto
Crea un proyecto con un agente en código (no harness), framework Strands, modelo en Bedrock, sin memoria gestionada y empaquetado como zip:
cd ~
agentcore create \
--project-name Lab10Agente \
--name Tienda \
--language Python \
--framework Strands \
--model-provider Bedrock \
--memory none \
--build CodeZip
cd Lab10Agente
find . -maxdepth 3 -not -path '*/node_modules*' -not -path '*/.venv*' | sort
Lo importante de la estructura:
| Fichero | Para qué |
|---|---|
agentcore/agentcore.json |
Configuración del proyecto: agentes, memorias, gateways… (la gestionan agentcore add y agentcore remove) |
agentcore/aws-targets.json |
Cuenta y región de despliegue (se rellena con tu identidad y AWS_REGION) |
agentcore/cdk/ |
Proyecto de AWS CDK que la CLI usa para aprovisionar |
app/Tienda/main.py |
Punto de entrada del agente |
app/Tienda/pyproject.toml |
Dependencias (incluye strands-agents y bedrock-agentcore) |
cat agentcore/aws-targets.json
grep -A12 'dependencies' app/Tienda/pyproject.toml
Comprueba que la región del target es eu-central-1.
Paso 3: sustituye el agente de ejemplo por el tuyo
La plantilla generada trae un agente genérico. Sustitúyelo por el agente de la tienda, adaptado a un servidor: sin input() (en Runtime no hay consola) y con la cabecera de sesión.
cat > app/Tienda/main.py <<'EOF'
import os
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent, tool
from strands.models import BedrockModel
PEDIDOS = {
"P-1001": {"estado": "enviado", "peso_kg": 2.5, "destino": "Madrid"},
"P-1002": {"estado": "preparando", "peso_kg": 12.0, "destino": "Bilbao"},
}
TARIFAS = {"estandar": {"base": 4.0, "por_kg": 0.6}, "urgente": {"base": 9.0, "por_kg": 1.1}}
@tool
def consultar_pedido(pedido_id: str) -> dict:
"""Devuelve el estado, el peso y el destino de un pedido.
Args:
pedido_id: Identificador del pedido con el formato P-NNNN, por ejemplo P-1001.
"""
pedido = PEDIDOS.get(pedido_id.strip().upper())
if pedido is None:
return {"status": "error", "content": [{"text": f"No existe el pedido {pedido_id}"}]}
return pedido
@tool
def calcular_envio(peso_kg: float, modalidad: str = "estandar") -> dict:
"""Calcula el coste de envío en euros de un paquete.
Args:
peso_kg: Peso del paquete en kilogramos. Debe ser mayor que 0 y como máximo 30.
modalidad: Modalidad de envío: "estandar" o "urgente".
"""
if not 0 < peso_kg <= 30 or modalidad not in TARIFAS:
return {"status": "error", "content": [{"text": "Parámetros fuera de rango"}]}
t = TARIFAS[modalidad]
return {"coste_eur": round(t["base"] + t["por_kg"] * peso_kg, 2), "modalidad": modalidad}
app = BedrockAgentCoreApp()
# Cada sesión de AgentCore Runtime vive en su propia microVM: este objeto
# (y su historial de conversación) pertenece a una única sesión.
agente = Agent(
model=BedrockModel(
model_id=os.environ.get("MODEL_ID", "eu.amazon.nova-lite-v1:0"),
temperature=0.2,
max_tokens=800,
),
system_prompt=(
"Eres el asistente de atención al cliente de una tienda online española. "
"Usa las herramientas para consultar datos; no inventes estados ni precios. "
"Responde en español de España, en dos o tres frases."
),
tools=[consultar_pedido, calcular_envio],
callback_handler=None,
)
@app.entrypoint
def invoke(payload, context):
resultado = agente(payload.get("prompt", "Hola"), limits={"turns": 6})
uso = resultado.metrics.accumulated_usage
return {
"respuesta": str(resultado),
"stop_reason": resultado.stop_reason,
"sesion": context.session_id,
"tokens": {"entrada": uso["inputTokens"], "salida": uso["outputTokens"]},
}
if __name__ == "__main__":
app.run()
EOF
Qué aporta bedrock-agentcore:
BedrockAgentCoreApplevanta el servidor HTTP que exige el contrato de Runtime:POST /invocations(tu función) yGET /ping(salud), en el puerto 8080.@app.entrypointregistra tu función. Si tiene un segundo parámetro llamadocontext, recibe el contexto de la petición, concontext.session_id.- El modelo no lleva
region_name: dentro de Runtime, la región viene de la variableAWS_REGIONdel entorno.
Paso 4 (recomendado): prueba en local
Antes de desplegar, prueba el mismo código en CloudShell. agentcore dev abre un inspector en el navegador, que desde CloudShell no es accesible; en su lugar, ejecuta el servidor en segundo plano y llámalo con curl:
cd ~/Lab10Agente
uv venv --python 3.12 /tmp/lab10-venv
source /tmp/lab10-venv/bin/activate
uv pip install "strands-agents==1.57.1" bedrock-agentcore
AWS_REGION=eu-central-1 python app/Tienda/main.py > /tmp/agente.log 2>&1 &
sleep 5
curl -s http://localhost:8080/ping; echo
curl -s -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "¿Cuánto cuesta enviar por urgente el pedido P-1001?"}'; echo
kill %1
deactivate
Deberías ver {"status":"Healthy",...} y una respuesta JSON con el coste (11,75 €), el stop_reason y los tokens. El entorno está en /tmp, así que no ocupa tu almacenamiento persistente.
Paso 5: despliega en AgentCore Runtime
cd ~/Lab10Agente
agentcore validate
agentcore deploy --dry-run
agentcore deploy -y
Qué ocurre:
- La CLI empaqueta
app/Tiendaen un zip (conuvdescarga las dependencias para la arquitectura de Runtime) y lo sube a S3. - Comprueba el bootstrap de AWS CDK en tu cuenta y región; la primera vez lo crea (stack
CDKToolkit). Con-yaceptas sin pregunta. - Sintetiza y despliega con CloudFormation: rol IAM de ejecución (con permiso para invocar modelos y perfiles de inferencia de Bedrock), el AgentCore Runtime y su endpoint
DEFAULT, y la configuración de logs y observabilidad.
El primer despliegue tarda unos minutos. Después:
agentcore status
Anota el ARN del runtime (arn:aws:bedrock-agentcore:eu-central-1:...:runtime/...). También lo verás en la consola: Amazon Bedrock AgentCore → Runtime.
Paso 6: invoca el agente y comprueba las sesiones
Primera invocación (la CLI crea una sesión nueva y te dice cómo reanudarla):
agentcore invoke --prompt "¿Dónde está el pedido P-1001?"
Al final de la salida verás algo como To resume: agentcore invoke --session-id .... Úsalo para continuar en la misma sesión:
SESION="pega-aqui-el-session-id"
agentcore invoke --session-id "$SESION" --prompt "¿Y cuánto costaría enviarlo por urgente?"
El agente sabe de qué pedido hablas: el historial vive en la microVM de esa sesión. Ahora prueba con una sesión nueva:
agentcore invoke --prompt "¿Y cuánto costaría enviarlo por urgente?"
El agente no sabe a qué pedido te refieres: cada sesión tiene su microVM, su memoria y su sistema de ficheros. Eso es el aislamiento por sesión. Recuerda también que una sesión termina tras 15 minutos de inactividad (o 8 horas en total) y su estado se pierde: para memoria duradera se usa AgentCore Memory.
Invocar desde código con boto3
Así lo haría tu backend (API, Lambda, contenedor):
cat > ~/invocar.py <<'EOF'
import json
import sys
import uuid
import boto3
arn = sys.argv[1]
cliente = boto3.client("bedrock-agentcore", region_name="eu-central-1")
sesion = str(uuid.uuid4()) # un id por conversación de usuario
for pregunta in ["¿Dónde está el pedido P-1002?", "¿Cuánto costaría enviarlo por estándar?"]:
r = cliente.invoke_agent_runtime(
agentRuntimeArn=arn,
runtimeSessionId=sesion,
payload=json.dumps({"prompt": pregunta}).encode(),
qualifier="DEFAULT",
)
cuerpo = b"".join(r["response"]).decode("utf-8")
print(json.dumps(json.loads(cuerpo), ensure_ascii=False, indent=2))
EOF
python3 ~/invocar.py "arn:aws:bedrock-agentcore:eu-central-1:CUENTA:runtime/ID"
Sustituye el ARN por el tuyo. Fíjate en tres cosas: el mismo runtimeSessionId en las dos llamadas (continuidad), el qualifier="DEFAULT" (el endpoint que apunta a la última versión) y que tu identidad necesita el permiso bedrock-agentcore:InvokeAgentRuntime. En producción, un rol IAM con ese permiso limitado al ARN del runtime, o autenticación OAuth de entrada con AgentCore Identity para usuarios finales.
Paso 7: logs, trazas y versiones
cd ~/Lab10Agente
agentcore logs --since 30m
agentcore traces list
En la consola de CloudWatch, busca la sección de observabilidad de IA generativa (GenAI Observability) y la vista de agentes de AgentCore: verás las sesiones, cada llamada al modelo y cada llamada a herramienta como spans. Si no aparecen trazas, revisa la guía de AgentCore Observability: las trazas se apoyan en la búsqueda de transacciones (Transaction Search) de CloudWatch.
Ahora cambia algo y vuelve a desplegar para ver el versionado:
sed -i 's/en dos o tres frases/en una sola frase/' app/Tienda/main.py
agentcore deploy -y
agentcore invoke --prompt "¿Dónde está el pedido P-1002?"
ID=$(aws bedrock-agentcore-control list-agent-runtimes --region eu-central-1 \
--query "agentRuntimes[0].agentRuntimeId" --output text)
aws bedrock-agentcore-control list-agent-runtime-versions --region eu-central-1 \
--agent-runtime-id "$ID" --output table
Verás al menos dos versiones: cada actualización crea una versión inmutable y el endpoint DEFAULT pasa a la nueva. En un entorno real crearías endpoints propios (por ejemplo prod) apuntando a una versión concreta para promocionar o hacer rollback sin tocar el código.
Comprueba que funciona
-
agentcore statusmuestra el runtime desplegado en eu-central-1. - Dentro de la misma sesión, el agente recuerda el pedido; en una sesión nueva, no.
-
invocar.pydevuelve dos respuestas JSON constop_reason,sesionytokens. - Ves logs (y, si está activa la búsqueda de transacciones, trazas) del agente.
- Hay al menos dos versiones del runtime.
Limpieza
Hazla en este orden. Primero, elimina los recursos del proyecto: remove all vacía la configuración y el deploy siguiente destruye lo desplegado.
cd ~/Lab10Agente
agentcore remove all -y
agentcore deploy -y
aws bedrock-agentcore-control list-agent-runtimes --region eu-central-1 --output table
La última orden no debe listar el runtime Tienda. Después, borra los grupos de logs que queden:
for g in $(aws logs describe-log-groups --region eu-central-1 \
--log-group-name-prefix /aws/bedrock-agentcore \
--query 'logGroups[].logGroupName' --output text); do
echo "Borrando $g"; aws logs delete-log-group --region eu-central-1 --log-group-name "$g"
done
Por último, los ficheros locales:
rm -rf ~/Lab10Agente ~/invocar.py /tmp/lab10-venv
Preguntas para pensar como arquitecto
1. ¿Por qué AgentCore Runtime y no una Lambda para este agente?
Una Lambda serviría para un agente corto y sin estado, pero Runtime aporta lo que un agente necesita en producción: aislamiento por sesión en microVM (sin fugas entre usuarios), sesiones de hasta 8 horas frente a los 15 minutos máximos de Lambda, streaming y WebSocket, protocolos MCP y A2A, versiones y endpoints para despliegues controlados, autenticación de entrada IAM u OAuth e integración directa con Memory, Gateway, Identity y Observability. Si el agente fuese una función de un solo paso, Lambda seguiría siendo válida y más simple.
2. El negocio pide que el agente recuerde lo que el cliente dijo ayer. ¿Qué cambiarías?
El estado de una sesión de Runtime es efímero (se pierde a los 15 minutos de inactividad o a las 8 horas). Añadiría AgentCore Memory (agentcore add memory, con memoria a corto y largo plazo) y usaría un actorId por cliente para aislar sus recuerdos. Con una estrategia de resumen o semántica se recuperan los datos relevantes en sesiones posteriores sin reenviar historiales enteros. En eu-south-2 no podría: Memory no está disponible allí.
3. Las herramientas reales son una API REST interna y una Lambda existentes. ¿Cómo las conectarías sin reescribirlas dentro del agente?
Con AgentCore Gateway: un destino OpenAPI para la API REST y un destino Lambda para la función. El gateway las expone como herramientas MCP en un único endpoint, gestiona la autenticación de salida hacia cada una y permite añadir AgentCore Policy para limitar qué puede hacer el agente (por ejemplo, importes máximos). El agente solo necesita un cliente MCP apuntando al gateway.
4. ¿Cómo publicarías una versión nueva del agente sin arriesgar a todos los usuarios a la vez?
Cada despliegue crea una versión inmutable. Mantendría un endpoint prod apuntando a la versión estable y otro canary a la nueva; el backend enviaría un porcentaje pequeño de sesiones al canario (o se evaluaría con AgentCore Evaluations y su A/B testing). Si las métricas y las evaluaciones son buenas, se actualiza prod a la nueva versión; si no, se deja como estaba: rollback sin redesplegar.
5. Un cliente de la empresa migra un agente de Bedrock Agents Classic. ¿Qué camino le recomendarías?
Para un agente sencillo (modelo, action groups y Knowledge Base), AgentCore Harness: es la experiencia declarativa más parecida; los action groups pasan a herramientas en Gateway y la KB se conecta por Gateway o como herramienta de recuperación. La AgentCore CLI puede importar la configuración del agente Classic. Si usa orquestación personalizada o colaboración multiagente completa, agente en código en Runtime (como este lab) con Strands u otro framework. No hay fecha límite para migrar, pero Agents Classic no recibirá funciones ni modelos nuevos.