Lab práctico · Semana 5: IA agéntica, herramientas y Amazon Bedrock AgentCore

Desplegar el agente en Amazon Bedrock AgentCore Runtime

⏱ 75-100 minDificultad: avanzadaTask statements: 2.1

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:

  • BedrockAgentCoreApp levanta el servidor HTTP que exige el contrato de Runtime: POST /invocations (tu función) y GET /ping (salud), en el puerto 8080.
  • @app.entrypoint registra tu función. Si tiene un segundo parámetro llamado context, recibe el contexto de la petición, con context.session_id.
  • El modelo no lleva region_name: dentro de Runtime, la región viene de la variable AWS_REGION del 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:

  1. La CLI empaqueta app/Tienda en un zip (con uv descarga las dependencias para la arquitectura de Runtime) y lo sube a S3.
  2. Comprueba el bootstrap de AWS CDK en tu cuenta y región; la primera vez lo crea (stack CDKToolkit). Con -y aceptas sin pregunta.
  3. 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 status muestra 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.py devuelve dos respuestas JSON con stop_reason, sesion y tokens.
  • 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.


Volver al módulo