Blog de Amazon Web Services (AWS)

Construye un sistema multi-agente de salud financiera con Amazon Bedrock AgentCore

Por Michael Garcia Casas y David Ugarte, Senior Solutions Architects en Amazon Web Services (AWS).

Aprende a construir un sistema multi-agente con Amazon Bedrock AgentCore y Strands Agents SDK que optimiza deudas de tarjetas de crédito, préstamos y líneas de crédito. El code sample incluye código descargable y usa las capacidades de visión de Claude para extracción de documentos, orquestación con Agents as Tools, filtros de seguridad con Amazon Bedrock Guardrails y genera planes de pago personalizados con visualizaciones comparativas.

El desafío de la salud financiera

En América Latina, las tarjetas de crédito retail pueden tener tasas de costo efectivo anual superiores al 90%, mientras que las bancarias oscilan entre 40% y 65%. La mayoría de tarjetahabientes paga montos mínimos en todas sus tarjetas sin saber que redistribuir el mismo presupuesto puede reducir significativamente el costo total de intereses y el tiempo hasta quedar libre de deuda. Los gastos recurrentes —delivery, suscripciones, cafés— pasan desapercibidos y se acumulan, acrecentando la deuda. El usuario no tiene visibilidad de su situación global porque la información reside en múltiples entidades financieras. Esto hace aún más complejo convertir la información dispersa en un plan de acción concreto.

Para resolver estos problemas construimos Zafa, un sistema multi-agente donde cada agente se especializa en una tarea. Un solo agente con un prompt extenso que contempla extracción, cálculos y categorización produce resultados inconsistentes, es más costoso y tiene mayor latencia. Con agentes especializados, cada uno usa el modelo óptimo y se ejecutan en paralelo cuando es posible. En las siguientes secciones explicamos las decisiones de diseño, cómo Amazon Bedrock AgentCore Runtime y Amazon Bedrock AgentCore Memory hacen posible la solución, y algunas técnicas para resolver problemas comunes al usar agentes de IA.

El ciclo virtuoso de la salud financiera con Zafa

Antes de profundizar en la arquitectura técnica, veamos el ciclo que Zafa crea para el usuario.

El usuario sube sus estados de cuenta, recibe un diagnóstico con una estrategia de pago optimizada, simula escenarios y aplica cambios. Cada mes regresa con nuevos estados de cuenta, y la memoria de Zafa —respaldada por Amazon Bedrock AgentCore Memory— produce recomendaciones cada vez más personalizadas.

Figura 1. Ciclo virtuoso de Zafa: bienestar financiero impulsado por datos

Arquitectura multi-agente con Agents as Tools

El sistema implementa el patrón Agents as Tools de Strands Agents SDK, donde cada agente especializado se expone como una herramienta invocable por el agente orquestador, que decide dinámicamente qué agente llamar según la intención del usuario. Esto permite agregar o reemplazar agentes sin modificar la lógica del orquestador, ya que cada agente se registra de forma declarativa con su nombre, descripción y esquema de entrada.

Figura 2. Arquitectura de Zafa: sistema multi-agente con Amazon Bedrock AgentCore

El sistema se compone de cinco agentes, cada uno con el modelo adecuado para su tarea:

Agente Modelo Función
Orquestador Claude Sonnet 4.5 Interpreta la intención del usuario, coordina agentes, consolida resultados con visualizaciones comparativas y gestiona la memoria entre sesiones
Extractor Claude Sonnet 4.5 Extrae datos estructurados de PDFs de cualquier entidad financiera (banco, saldo, TCEA, pago mínimo, movimientos) y procesa múltiples documentos en paralelo
Analista Claude Haiku 4 Diagnostica salud financiera, genera planes de pago (avalancha/bola de nieve), evalúa consolidación de deuda
Detective Claude Haiku 4 Identifica gastos recurrentes por categoría, cuantifica impacto mensual/anual, conecta ahorro con reducción de plazo
Simulador Claude Haiku 4 Evalúa escenarios hipotéticos (gratificaciones, recortes, compras nuevas) y compara contra el plan actual

El Orquestador y el Extractor requieren modelos de alta capacidad: el primero por la complejidad de coordinar y sintetizar; el segundo por sus capacidades multimodales (imágenes). Los tres agentes especializados (Analista, Detective, Simulador) usan Haiku 4 porque las herramientas Python realizan los cálculos; el modelo solo interpreta y formatea los resultados.

Esta selección reduce el costo por sesión a ~$0.10 USD. Haiku 4 cuesta aproximadamente una décima parte de Sonnet 4.5 por token, y los agentes especializados representan la menor fracción del costo total.

Flujo de orquestación

El agente orquestador completa el flujo en dos invocaciones de herramientas (tool calls):

Figura 3. Flujo de orquestación: el usuario sube PDFs y recibe un diagnóstico completo en dos pasos

La herramienta analisis_y_detective_paralelo encapsula al Analista y al Detective en un único tool call por dos razones: (1) ejecutar ambos agentes en paralelo con ThreadPoolExecutor, reduciendo la latencia a la del agente más lento, y (2) inyectar los datos reales desde DynamoDB directamente en el prompt de cada agente especializado para evitar que el modelo genere datos ficticios.

Strands Agents SDK implementa esta orquestación mediante el decorador @tool. Cada agente especializado se define como una función Python que el orquestador invoca según la intención del usuario:

from strands import Agent, tool

@tool
def agente_extractor(consulta: str) -> str:
    """Extrae datos de estados de cuenta PDF subidos a S3."""
    agent = Agent(
        model=extractor_model,  # Claude Sonnet 4.5 Vision
        tools=[extraer_estado_cuenta, extraer_multiples_pdfs, guardar_estado_cuenta],
    )
    return str(agent(consulta))

# El orquestador se construye por invocación: integra AgentCore Memory
# de forma condicional cuando hay memory_id, session_id y actor_id.
def crear_orquestador(session_id, actor_id, country="PE"):
    session_manager = _crear_session_manager(session_id, actor_id)  # AgentCore Memory
    return Agent(
        model=orchestrator_model,  # Claude Sonnet 4.5
        tools=[
            agente_extractor,
            analisis_y_detective_paralelo,  # Analista + Detective en paralelo
            agente_analista,                # consultas individuales de análisis
            agente_detective_gastos,        # consultas individuales de gastos
            agente_simulador,               # escenarios bajo demanda
        ],
        session_manager=session_manager,
    )

Del prototipo a producción: Runtime, memoria y protección

Desplegar cinco agentes coordinados en producción normalmente requiere construir un servidor (FastAPI/Flask), manejar concurrencia y escalamiento, configurar WebSockets y gestionar el ciclo de vida de las sesiones. Amazon Bedrock AgentCore elimina esa complejidad con tres capacidades complementarias: Runtime proporciona hosting serverless con aislamiento por sesión, Memory mantiene el contexto financiero entre visitas mensuales y Guardrails protege los datos sensibles sin bloquear conversaciones legítimas.

AgentCore Runtime

AgentCore Runtime aísla cada invocación en una microVM independiente, expone streaming bidireccional y genera un endpoint HTTPS que el frontend consume a través de una capa de API Gateway + Lambda. El runtime se define como un recurso AWS::BedrockAgentCore::Runtime, especificando el entrypoint, el runtime de Python, la plataforma de ejecución y el modo de red. Las dependencias se compilan para linux/arm64 y se empaquetan como artefacto de código:

# Configuración del AgentCore Runtime (AWS::BedrockAgentCore::Runtime)
agent_runtime_name: zafa_orquestador
agent_runtime_artifact:
  code_configuration:
    entry_point: agentcore_entrypoint.py
    runtime: PYTHON_3_13          # ejecución en Python 3.13
    code:
      s3:                          # artefacto de código empaquetado en S3
        bucket: <asset-bucket>
        prefix: <asset-key>
network_configuration:
  network_mode: PUBLIC             # endpoint HTTPS gestionado
role_arn: arn:aws:iam::ACCOUNT_ID:role/ZafaAgentCoreRole
environment_variables:
  AWS_REGION: us-east-2
# Las dependencias se compilan para la plataforma del runtime (linux/arm64)
pip install -r requirements.txt -t ./build \
  --platform manylinux2014_aarch64 --implementation cp \
  --python-version 3.13 --only-binary=:all:

# Desplegar el stack completo (Runtime + Memory + Guardrail + infra) con AWS CDK
export AWS_REGION=us-east-2
./deploy-v3.sh

El SDK de AgentCore expone el decorador @app.entrypoint (clase BedrockAgentCoreApp), que registra una función Python como punto de entrada del runtime. Al recibir una solicitud, AgentCore Runtime ejecuta esta función y le pasa el payload junto con un contexto que incluye el session_id:

from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

@app.entrypoint
def invoke(payload, context):
    orquestador = crear_orquestador(
        session_id=context.session_id,
        actor_id=payload.get("usuario_id"),
        country=payload.get("country", "PE"),
    )
    resultado = orquestador(payload["prompt"])
    return {"result": str(resultado)}

AgentCore Memory

AgentCore Memory persiste información de corta y larga duración para que un agente la pueda usar como contexto durante su ejecución. Esta información incluye preferencias, hechos y resúmenes de sesión, cada uno con un umbral de relevancia configurable. En Zafa definimos tres estrategias:

  • Preferencias (relevance ≥ 0.7): Decisiones del usuario como «No quiere préstamos nuevos» o «Puede destinar S/ 1,200/mes a deuda». Alta relevancia porque son restricciones que no cambian entre sesiones.
  • Hechos financieros (relevance ≥ 0.3): Saldos, tasas, ingreso mensual del último análisis. Umbral bajo porque queremos recuperar todos los datos financieros disponibles para comparación.
  • Resúmenes de sesión (relevance ≥ 0.5): Consolidados automáticos de cada conversación previa. Permiten al agente comparar la evolución financiera entre visitas sin recargar el historial completo.

AgentCore Memory analiza la conversación al final de cada sesión y extrae automáticamente preferencias, hechos y resúmenes sin código adicional. AgentCoreMemorySessionManager del Strands SDK conecta al agente con el servicio de memoria y configura qué información recuperar en cada sesión:

session_manager = AgentCoreMemorySessionManager(
    agentcore_memory_config=AgentCoreMemoryConfig(
        memory_id=memory_id,
        session_id=session_id,
        actor_id=usuario_id,
        # Namespaces de memoria: cada path define qué tipo de información recuperar
        retrieval_config={
            "/preferences/{actorId}": RetrievalConfig(top_k=5, relevance_score=0.7),
            "/facts/{actorId}": RetrievalConfig(top_k=10, relevance_score=0.3),
            "/summaries/{actorId}/{sessionId}": RetrievalConfig(top_k=3, relevance_score=0.5),
        },
    ),
    region_name="us-east-2",
)

orquestador = Agent(
    model=orchestrator_model,          # Claude Sonnet 4.5
    tools=[
        agente_extractor,
        analisis_y_detective_paralelo,
        agente_analista,
        agente_detective_gastos,
        agente_simulador,
    ],
    session_manager=session_manager,
)

En la siguiente visita, el agente orquestador recupera este contexto y genera una comparación: «Tu deuda bajó de S/ 18,000 a S/ 15,200. Vas por buen camino.» Sin Memory, cada sesión comenzaría sin contexto previo y no sería posible mostrar la evolución entre visitas.

Bedrock Guardrails

Amazon Bedrock Guardrails aplica filtros de contenido, detección de prompt injection y anonimización de PII al agente orquestador, que es el único punto de entrada y el que procesa datos financieros sensibles. La configuración incluye tres capas:

  • Filtros de contenido: Bloqueo de violencia, contenido sexual, odio, insultos y conducta inapropiada (nivel HIGH en input y output).
  • Protección contra prompt injection: Detección de intentos de jailbreak en el input (nivel HIGH), sin filtrar el output para no bloquear respuestas financieras legítimas.
  • Anonimización de PII: Números de tarjeta de crédito, emails, teléfonos y direcciones se anonimizan automáticamente antes de llegar al modelo.

Aplicamos el guardrail exclusivamente al agente orquestador. Los agentes especializados procesan datos ya validados y no requieren filtrado adicional.

Los prompts legítimos a veces activan falsos positivos en los filtros de contenido. Por ejemplo, «me despiden» en un contexto financiero legítimo (el usuario simula pérdida de empleo) puede activar el bloqueo. Cuando el guardrail bloquea un prompt legítimo, una capa de sanitización basada en expresiones regulares reformula estos términos y reintenta una sola vez, sin penalizar la latencia del caso normal:

import re

# Se aplica solo cuando el guardrail bloquea un prompt financiero legítimo,
# antes de reintentar la invocación una vez.
def sanitizar_prompt_financiero(prompt: str) -> str:
    sanitizations = [
        (r'\b(me despid[aeno]+|me bot[aeno]+|me ech[aeno]+)\b', 'pierdo mi empleo'),
        (r'\b(mendigando|pidiendo limosna|en la calle)\b', 'con ingresos reducidos'),
        (r'\b(me amenazan|extorsion\w*)\b', 'me presionan por cobros'),
    ]
    for pattern, replacement in sanitizations:
        prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
    return prompt

Ejecución paralela y grounding determinístico

En un flujo convencional, el agente orquestador crearía los agentes especializados y les pasaría una consulta en lenguaje natural; cada agente especializado tendría que decidir por sí mismo invocar la herramienta que carga las tarjetas desde Amazon DynamoDB con el usuario_id correcto. El problema es que esa decisión depende del LLM, y los modelos de lenguaje son no determinísticos: cuando no tienen los datos en el contexto, tienden a generar información plausible en lugar de invocar la herramienta de persistencia.

La solución es una precarga determinística: antes de crear los agentes especializados, la aplicación invoca _cargar_tarjetas_para_prompt(), que lee las tarjetas reales desde DynamoDB y las formatea como JSON. Ese bloque se concatena directamente al prompt de cada agente especializado, y el system_prompt refuerza la regla: «Usa EXCLUSIVAMENTE esas tarjetas. NO inventes otras.» De esta forma: (1) la carga de datos ocurre en código, no en una decisión del LLM; (2) ambos agentes se ejecutan en paralelo con ThreadPoolExecutor sobre exactamente los mismos datos; y (3) el modelo recibe el contexto factual en lugar de tener que recuperarlo por sí mismo.

from concurrent.futures import ThreadPoolExecutor

tarjetas_json = _cargar_tarjetas_para_prompt(usuario_id)

bloque_datos = (
    "===DATOS REALES DEL USUARIO (de DynamoDB)===\n"
    f"usuario_id: {usuario_id}\n"
    f"tarjetas:\n{tarjetas_json}\n"
    "===FIN DATOS REALES===\n"
    "USA ESTOS DATOS y NO inventes otros."
)

with ThreadPoolExecutor(max_workers=2) as executor:
    # Los sub-agentes acceden a bloque_datos por closure
    future_analista = executor.submit(_run_analista_directo)
    future_detective = executor.submit(_run_detective_directo)

Este patrón de grounding determinístico —inyectar datos reales en el prompt en lugar de depender de que el modelo los recupere— es aplicable a cualquier agente que opere sobre datos factuales. En el contexto financiero, elimina el riesgo de que el modelo genere cifras inventadas en diagnósticos y planes de pago.

Capacidades de visión de Claude para la extracción de documentos financieros

Cada entidad financiera emite estados de cuenta con formatos distintos. Construir analizadores específicos por formato es frágil y costoso de mantener. En su lugar, el agente extractor envía cada PDF a Claude Sonnet 4.5 a través de Amazon Bedrock, que usa sus capacidades de visión para extraer los campos relevantes en formato JSON:

PROMPT_EXTRACCION = """Extrae los siguientes datos del estado de cuenta en formato JSON:
{
  "banco": str,
  "tipo": "bancaria o retail",
  "titular": str,
  "ultimos_4_digitos": str,
  "saldo": float,
  "tcea": float,
  "pago_minimo": float,
  "fecha_corte": "DD/MM/YYYY",
  "movimientos": [{"fecha": str, "comercio": str, "monto": float}]
}
Extrae TODOS los movimientos. Solo JSON, sin texto adicional."""

response = bedrock_runtime.invoke_model(
    modelId='us.anthropic.claude-sonnet-4-5-20250929-v1:0',
    body=json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 2000,
        "messages": [{
            "role": "user",
            "content": [
                {"type": "document", "source": {
                    "type": "base64",
                    "media_type": "application/pdf",
                    "data": pdf_b64
                }},
                {"type": "text", "text": PROMPT_EXTRACCION}
            ]
        }]
    })
)

El sistema procesa múltiples PDFs en paralelo y muestra los datos extraídos para validación antes de generar cualquier plan, lo que mitiga errores de extracción y genera confianza en el usuario. Para evitar duplicados cuando se suben estados de cuenta de varios meses de la misma tarjeta, el sistema compara fechas de corte, usa el saldo más reciente para calcular la deuda actual y conserva los meses anteriores para mostrar la evolución temporal.

Gráficos como parte de la conversación

Un diagnóstico financiero con solo texto no genera el impacto necesario para cambiar comportamientos. El agente orquestador embebe bloques de gráfico directamente en su respuesta usando un protocolo :::chart que el frontend React parsea y renderiza con Recharts. El resultado: cada respuesta combina explicación textual con visualización interactiva sin requerir un paso adicional del usuario.

El sistema genera distintos tipos de gráficos, cada uno diseñado para un aspecto específico del diagnóstico:

  • Composición de deuda (debtComposition): Cada tarjeta con su saldo, TCEA (Tasa de Costo Efectivo Anual) y pago mínimo.
  • Evolución temporal (debtEvolution): Comparación mes a mes de cómo baja la deuda pagando mínimos frente al plan optimizado.
  • Intereses acumulados (interestArea): Cuánto dinero se destina a pagar intereses en cada escenario.
  • Gastos hormiga (spendingPie): Distribución de gastos recurrentes por categoría (delivery, suscripciones, transporte por app).
  • Simulación de escenarios (simulation): Impacto de eventos como bonos, gratificaciones o recortes de gastos sobre el plan.

A continuación, un ejemplo de cómo el agente combina texto y visualización en una sola respuesta:

Tu deuda total es S/ 18,000. Pagando mínimos tardas más de 4 años.

:::chart {"type": "debtComposition", "data": [
  {"tarjeta": "Tarjeta A", "saldo": 4500, "tcea": 98},
  {"tarjeta": "Tarjeta B", "saldo": 5500, "tcea": 95},
  {"tarjeta": "Tarjeta C", "saldo": 8000, "tcea": 48}
], "title": "Composición de deuda"} :::

Tu tarjeta más costosa tiene TCEA de 98%.

Los datos de los gráficos provienen directamente de las herramientas de cálculo —el agente orquestador tiene instrucciones explícitas en su system_prompt de no recalcular ni inventar números—. Este principio refuerza el patrón de grounding determinístico descrito anteriormente: el modelo presenta datos, no los genera.

Lecciones de implementación de agentes

El desarrollo de Zafa reveló patrones aplicables a cualquier sistema multi-agente que opere sobre datos factuales:

  • Inyectar datos en el prompt, no depender de directivas. Las instrucciones «no inventes datos» en el system prompt no garantizan precisión. Inyectar los datos reales con delimitadores claros directamente en el prompt de cada agente especializado produce resultados consistentemente confiables.
  • Calibrar guardrails por dominio. Un guardrail genérico bloquea conversaciones financieras legítimas. Complementar Amazon Bedrock Guardrails con una capa de sanitización por regex que reformule expresiones problemáticas antes del envío al modelo elimina la mayoría de falsos positivos sin sacrificar protección.
  • Visualizar para motivar la acción. El gráfico de intereses acumulados comunica en segundos por qué pagar mínimos es costoso. Ver la diferencia entre pagar mínimos y seguir un plan optimizado en una línea de tiempo entrega una motivación concreta al usuario.
  • Persistir contexto entre sesiones. La memoria persistente —habilitada por Amazon Bedrock AgentCore Memory— produce una comunicación más personalizada, mejora la experiencia y fomenta el uso recurrente del sistema.

Próximos pasos

Con Amazon Bedrock AgentCore y Strands Agents SDK puedes construir un sistema multi-agente que transforma estados de cuenta PDF en planes de pago personalizados. AgentCore Runtime se encarga del hosting serverless y el aislamiento por invocación, AgentCore Memory persiste el contexto financiero entre visitas mensuales, y Bedrock Guardrails protege datos sensibles sin bloquear conversaciones legítimas.

Los patrones de esta implementación —grounding determinístico, ejecución paralela con ThreadPoolExecutor y selección de modelo por complejidad de tarea— aplican a cualquier sistema multi-agente que procese datos reales donde la precisión no es negociable.

Descarga el código fuente del repositorio en GitHub, despliega tu propia instancia con AWS CDK (deploy.sh) y adapta los agentes a tu caso de uso. La configuración por país —moneda, terminología de tasas y formato de presentación— permite operar en múltiples mercados de América Latina sin modificar la lógica de los agentes.

Para profundizar en los servicios utilizados, consulta la documentación de Amazon Bedrock AgentCore, Amazon Bedrock Guardrails y Strands Agents SDK.

Acerca de los autores

mgcasas Michael Garcia Casas es Senior Solutions Architect en Amazon Web Services (AWS), donde ayuda a clientes enterprise del sector financiero en América Latina a acelerar su adopción de la nube y modernización de aplicaciones. Michael se especializa en arquitecturas event-driven, serverless, streaming de datos con Amazon MSK, y soluciones de IA agéntica con Amazon Bedrock. Cuenta con 10 certificaciones de AWS, incluyendo nivel profesional y especialidades.
David Ugarte es Senior Solutions Architect en Amazon Web Services (AWS), donde ayuda a clientes enterprise en América Latina a diseñar e implementar soluciones en la nube que abordan sus desafíos de negocio más críticos. Está en AWS desde 2020 y disfruta convertir problemas técnicos complejos en arquitecturas simples y escalables. Entusiasta usuario de Kiro, David aprovecha el desarrollo asistido por IA para acelerar la construcción de demos, prototipos y herramientas para sus clientes.