<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AWS Español</title>
    <description>The latest articles on DEV Community by AWS Español (@aws-espanol).</description>
    <link>https://dev.to/aws-espanol</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7402%2F04f86f58-db61-410f-8eda-06b0c052f17f.jpeg</url>
      <title>DEV Community: AWS Español</title>
      <link>https://dev.to/aws-espanol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws-espanol"/>
    <language>en</language>
    <item>
      <title>IAM Principal Cost Allocation para Amazon Bedrock (Novedad)</title>
      <dc:creator>Hector Fernandez CloudparaTodo</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:30:00 +0000</pubDate>
      <link>https://dev.to/aws-espanol/iam-principal-cost-allocation-para-amazon-bedrock-novedad-26cm</link>
      <guid>https://dev.to/aws-espanol/iam-principal-cost-allocation-para-amazon-bedrock-novedad-26cm</guid>
      <description>&lt;p&gt;Hace unos meses inicie una serie de posts sobre como gobernar el uso de IA en AWS. En la primera entrega de esta serie hablamos de cómo dar acceso gobernado a LLMs en AWS desde el día 0: IAM Policies, Guardrails, Inference Profiles y un mecanismo de corte de presupuesto por equipo. Si no la leíste, te recomiendo empezar por ahí.&lt;br&gt;
&lt;a href="https://podcast.hectorfernandez.dev/p/episodio-9-necesito-una-api-key-para" rel="noopener noreferrer"&gt;Podcast: UNA API KEY para LLMs&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Pero había un punto que me quedaba pendiente: &lt;strong&gt;no podíamos saber quién dentro de un equipo estaba generando el consumo&lt;/strong&gt;. Cortábamos al equipo entero, y después había que armar una solución custom con Model Invocation Logging para identificar al responsable.&lt;/p&gt;

&lt;p&gt;AWS acaba de resolver la parte de &lt;strong&gt;visibilidad&lt;/strong&gt; de forma nativa. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Bloquear todo el acceso?&lt;/strong&gt; Eso sigue siendo otro tema, y creo que de forma nativa sería complejo de implementar un proxy (por ahora).&lt;/p&gt;
&lt;h2&gt;
  
  
  ¿Qué anunció AWS?
&lt;/h2&gt;

&lt;p&gt;El 8 de Abril de 2026, Amazon Bedrock lanzó soporte para &lt;strong&gt;asignación de costos por IAM principal&lt;/strong&gt;, hablando en criollo: por usuario o rol de IAM, directamente en Cost Explorer y en CUR 2.0 (Cost and Usage Report).&lt;/p&gt;

&lt;p&gt;¡BIEN! Ahora podemos ver el costo desglosado de Bedrock en Cost Explorer sin muchas vueltas. &lt;br&gt;
&lt;a href="https://aws.amazon.com/es/about-aws/whats-new/2026/04/bedrock-iam-cost-allocation/" rel="noopener noreferrer"&gt;Anuncio oficial&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora, algo que debes tener claro desde ya: esta funcionalidad es de &lt;strong&gt;billing&lt;/strong&gt;, no de enforcement. Los datos llegan a CUR 2.0 y Cost Explorer con &lt;strong&gt;24-48 horas de latencia&lt;/strong&gt;. Eso significa que puedes saber &lt;em&gt;quién&lt;/em&gt; gastó &lt;em&gt;cuánto&lt;/em&gt;, pero &lt;strong&gt;no puedes bloquear el acceso en tiempo real con esta data&lt;/strong&gt;. Para eso, el Budget Cut Lambda de la Parte 1 sigue siendo necesario.&lt;/p&gt;
&lt;h2&gt;
  
  
  ¿Qué cambia respecto a la Parte 1 que habíamos hablado?
&lt;/h2&gt;

&lt;p&gt;En la primera publicación usamos &lt;strong&gt;Inference Profiles con tags&lt;/strong&gt; (&lt;code&gt;CostCenter&lt;/code&gt;, &lt;code&gt;Team&lt;/code&gt;) para atribuir costos por equipo. Eso sigue siendo válido para agrupar por carga de trabajo. Pero ahora hay una capa adicional: la &lt;strong&gt;identidad del que hace la llamada&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mecanismo&lt;/th&gt;
&lt;th&gt;Granularidad&lt;/th&gt;
&lt;th&gt;¿Qué resuelve?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inference Profile + Resource Tags&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Por equipo / carga de trabajo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"¿Cuánto gastó el equipo backend en Haiku?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;IAM Principal Cost Allocation&lt;/strong&gt; (NUEVO)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Por usuario / rol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"¿Cuánto gastó &lt;code&gt;user@empresa.com&lt;/code&gt; en todos los modelos?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Juntando todo esto, tenemos: &lt;strong&gt;quién&lt;/strong&gt; gastó &lt;strong&gt;cuánto&lt;/strong&gt;, en &lt;strong&gt;qué modelo&lt;/strong&gt;, para &lt;strong&gt;qué equipo&lt;/strong&gt;. Pero recuerda: esta foto la ves con &lt;strong&gt;24-48h de retraso&lt;/strong&gt;. Es para análisis y chargeback, no para corte en tiempo real.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pre-requisitos
&lt;/h2&gt;

&lt;p&gt;Todo lo de la Parte 1 más (te invito a leerla)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tags en tus IAM users/roles&lt;/strong&gt; con atributos de negocio (&lt;code&gt;team&lt;/code&gt;, &lt;code&gt;business-unit&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Acceso a la consola de &lt;strong&gt;Billing and Cost Management&lt;/strong&gt; (si es por Organizations, lo haces desde la cuenta management)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CUR 2.0&lt;/strong&gt; habilitado (el CUR legacy no soporta esto)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Paso 1: Taggear los IAM Principals
&lt;/h3&gt;

&lt;p&gt;Si usas SSO, cada Permission Set genera un rol en la cuenta target con formato &lt;code&gt;AWSReservedSSO_{PermissionSetName}_{hash}&lt;/code&gt;. Estos roles se pueden taggear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Taggea los roles SSO con atributos de negocio.
Estos tags son los que aparecerán en Cost Explorer y CUR 2.0.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;iam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iam&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Mapeo: role SSO -&amp;gt; tags de negocio
&lt;/span&gt;&lt;span class="n"&gt;SSO_ROLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWSReservedSSO_BackendDev_a1b2c3d4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;business-unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BU-ENG-001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engineering&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWSReservedSSO_FrontendDev_e5f6g7h8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;business-unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BU-ENG-002&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engineering&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWSReservedSSO_DataTeam_i9j0k1l2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;business-unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BU-DATA-001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data-science&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags_dict&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SSO_ROLES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags_dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

    &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tag_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RoleName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; taggeado con: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tags_dict&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si además tienen &lt;strong&gt;IAM Users&lt;/strong&gt; (para casos legacy o service accounts), se taggean igual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam tag-user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-name&lt;/span&gt; &lt;span class="s2"&gt;"superuser-pipeline-sa"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;team,Value&lt;span class="o"&gt;=&lt;/span&gt;data &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;business-unit,Value&lt;span class="o"&gt;=&lt;/span&gt;BU-DATA-001 &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project,Value&lt;span class="o"&gt;=&lt;/span&gt;recommendation-engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota importante de AWS:&lt;/strong&gt; Los tags solo aparecen para activación en la consola de Billing &lt;strong&gt;después de que el principal haya hecho al menos una llamada a Bedrock&lt;/strong&gt;. Si taggeaste un rol pero nadie lo ha usado aún, no vas a verlo en Cost Allocation Tags.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Paso 2: Activar los tags como Cost Allocation Tags
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6twri5bir2oihv2b3jte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6twri5bir2oihv2b3jte.png" alt="Activar allocation tag" width="800" height="283"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Billing and Cost Management Console
    → Cost Organization 
        → Cost Allocation Tags (Etiquetas de asignación de costos)
            → Filtrar por "IAM principal type"
            → Seleccionar: team, business-unit, department
            → Click "Activate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Después de activarlos, los tags tardan &lt;strong&gt;hasta 24 horas&lt;/strong&gt; en estar disponibles para filtrado en Cost Explorer y CUR.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificar qué tags están activos via CLI&lt;/span&gt;
aws ce list-cost-allocation-tags &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--status&lt;/span&gt; Active &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-keys&lt;/span&gt; &lt;span class="s2"&gt;"team"&lt;/span&gt; &lt;span class="s2"&gt;"business-unit"&lt;/span&gt; &lt;span class="s2"&gt;"department"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; &lt;span class="s2"&gt;"iamPrincipal"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Paso 3: Habilitar IAM Principal en CUR 2.0
&lt;/h3&gt;

&lt;p&gt;Si hay un paso para que todo esto funcione, es este. &lt;/p&gt;

&lt;p&gt;Activa la columna &lt;code&gt;line_item_iam_principal&lt;/code&gt; en los reportes de costos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Billing and Cost Management Console
    → Data Exports
        → Create export → Standard data export (CUR 2.0)
            → Additional export content:
                ✅ Include caller identity (IAM principal) allocation data
            → Destino: S3 bucket + Athena integration
        → Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;¿Qué genera esto?&lt;/strong&gt; Cada línea del CUR 2.0 ahora incluye el ARN exacto del principal que hizo la llamada a Bedrock. Y los tags del principal aparecen con prefijo &lt;code&gt;iamPrincipal/&lt;/code&gt; para no colisionar con resource tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Cómo se ve la data?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  En Cost Explorer: filtrar por equipo/usuario
&lt;/h3&gt;

&lt;p&gt;Una vez activados los tags, Cost Explorer permite agrupar directamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cost Explorer
    → Filtro: Service = "Amazon Bedrock"
    → Group by: Tag → "iamPrincipal/team"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vas a ver algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;iamPrincipal&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;team&lt;/span&gt;    &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Costo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;USD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;---------------------|------------&lt;/span&gt;
&lt;span class="k"&gt;backend&lt;/span&gt;              &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$142.30&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt;                 &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$89.50&lt;/span&gt;
&lt;span class="k"&gt;frontend&lt;/span&gt;             &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$23.10&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sin&lt;/span&gt; &lt;span class="k"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$5.40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Y si quieres ver quién dentro del equipo backend está consumiendo más? Cambias el Group by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cost Explorer
    → Filtro: Service = "Amazon Bedrock"
    → Filtro: Tag "iamPrincipal/team" = "backend"
    → Group by: Tag → "iamPrincipal/business-unit"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;BONUS&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  (Opcional) CUR 2.0 + Athena: análisis avanzado por usuario individual
&lt;/h3&gt;

&lt;p&gt;Si Cost Explorer no te da suficiente granularidad y necesitas ver &lt;strong&gt;por usuario individual&lt;/strong&gt;, puedes consultar CUR 2.0 directamente con Amazon Athena.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sobre costos de Athena:&lt;/strong&gt; Athena cobra &lt;strong&gt;$5 USD por TB escaneado&lt;/strong&gt; (con el modo on-demand). Para CUR de organizaciones pequeñas/medianas esto suele ser centavos por consulta. Si quieres reducir costos, activa la compresión del CUR (formato Parquet) y particiona por mes. También existe el modo Provisioned Capacity para uso intensivo.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ¿Quiénes son los top 10 consumers de Bedrock este mes?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;line_item_iam_principal&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;iam_principal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'iamPrincipal/team'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'iamPrincipal/business-unit'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;business_unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;line_item_product_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_item_unblended_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_item_usage_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_usage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cur_2_0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bedrock_usage&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;line_item_product_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AmazonBedrock'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;billing_period&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;iam&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;principal&lt;/span&gt;                                          &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;team&lt;/span&gt;     &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;business&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;unit&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;total&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;
&lt;span class="err"&gt;-------------------------------------------------------+----------+---------------+-----------&lt;/span&gt;
&lt;span class="nv"&gt;arn:aws:sts:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;123456&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;assumed&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;BackendDev&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;javier&lt;/span&gt;&lt;span class="err"&gt;@...&lt;/span&gt;  &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;backend&lt;/span&gt;  &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;BU&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;ENG&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;001&lt;/span&gt;    &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$67.30&lt;/span&gt;
&lt;span class="nv"&gt;arn:aws:sts:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;123456&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;assumed&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;DataTeam&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;maria&lt;/span&gt;&lt;span class="err"&gt;@...&lt;/span&gt;     &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;     &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;BU&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;DATA&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;001&lt;/span&gt;   &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$52.10&lt;/span&gt;
&lt;span class="nv"&gt;arn:aws:sts:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;123456&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;assumed&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;BackendDev&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;luis&lt;/span&gt;&lt;span class="err"&gt;@...&lt;/span&gt;    &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;backend&lt;/span&gt;  &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;BU&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;ENG&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;001&lt;/span&gt;    &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$41.20&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin proxies, sin Lambdas, sin CloudTrail scraping: &lt;strong&gt;sabes exactamente que Javier del equipo backend gastó $67.30 este mes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Recuerda que cada ejecución tiene un costo mínimo en Athena, pero para un CUR particionado en Parquet suele ser menos de $0.01 por consulta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap ¿Cómo se complementa con la Parte 1?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Parte 1&lt;/th&gt;
&lt;th&gt;Parte 2 (lo nuevo)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;¿Quién puede usar qué modelo?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IAM Policy per-team&lt;/td&gt;
&lt;td&gt;Sin cambios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;¿Se protege PII?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bedrock Guardrails&lt;/td&gt;
&lt;td&gt;Sin cambios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;¿Cuánto gastó cada equipo?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inference Profile tags → Cost Explorer&lt;/td&gt;
&lt;td&gt;IAM Principal tags → Cost Explorer (más granular)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;¿Cuánto gastó cada usuario?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No disponible&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;line_item_iam_principal&lt;/code&gt; en CUR 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;¿Se corta al exceder presupuesto?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Budget Cut Lambda (per-team, ~10 min)&lt;/td&gt;
&lt;td&gt;⚠️ CUR tiene 24h de delay. &lt;strong&gt;NO sirve para bloquear&lt;/strong&gt;. El Budget Cut Lambda sigue siendo el único mecanismo de corte&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Lo que sigue sin resolver nativamente
&lt;/h3&gt;

&lt;p&gt;Seamos honestos: hay cosas que necesitan un proxy real, y para dar gobierno a la IA es lo mejor que puedes pensar. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Corte per-user en tiempo real&lt;/strong&gt;: CUR tiene 24h de delay. Si necesitas cortar a un usuario específico en minutos, necesitas Model Invocation Logging + Lambda + &lt;code&gt;iam:PutRolePolicy&lt;/code&gt; dirigido al session principal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching de respuestas&lt;/strong&gt;: No hay caching nativo de Bedrock. Un proxy como LiteLLM puede cachear respuestas repetitivas y ahorrar costos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-provider routing&lt;/strong&gt;: Si quieres probar OpenAI y Anthropic directamente (no vía Bedrock), necesitas una capa de abstracción&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observabilidad semántica&lt;/strong&gt;: Para ver el árbol de razonamiento de un agente, necesitas OTel + Langfuse. CloudTrail te dice "quién llamó", pero no "por qué razonó así"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La clave es que los tags del &lt;strong&gt;principal&lt;/strong&gt; (quién) y los tags del &lt;strong&gt;recurso&lt;/strong&gt; (qué) se complementan en CUR 2.0. No se pisan porque CUR los distingue con prefijos: &lt;code&gt;iamPrincipal/team&lt;/code&gt; vs &lt;code&gt;resourceTag/Team&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;p&gt;Con este anuncio, el modelo de gobierno nativo que planteamos en la Parte 1 gana una pieza que faltaba: &lt;strong&gt;visibilidad de costos por usuario&lt;/strong&gt;, sin añadir infraestructura, sin scraping de CloudTrail, sin Lambdas custom para atribución. Es una herramienta de análisis y chargeback, no de enforcement. El bloqueo en tiempo real sigue dependiendo del Budget Cut Lambda y CloudWatch.&lt;/p&gt;

&lt;p&gt;Siempre pensemos en etapas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Día 0&lt;/strong&gt;: IAM Policies + Guardrails + Inference Profiles → acceso gobernado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Día 1&lt;/strong&gt;: Budget Cut Lambda, protección contra gastos descontrolados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ahora (Día 2)&lt;/strong&gt;: IAM Principal Cost Allocation, saber exactamente quién gasta qué&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Falta mucho por hacer? Sí. Un proxy real (LiteLLM), observabilidad semántica (Langfuse), y caching siguen siendo evoluciones deseables si la organización escala. Pero la base está puesta, y es 100% nativa.&lt;/p&gt;

&lt;p&gt;Lo bueno de todo esto: &lt;strong&gt;AWS&lt;/strong&gt; sigue mejorando sus productos, pero nosotros como arquitectos debemos de saber identificar el uso para nuestras necesidades.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hoy es cost allocation por principal. Mañana quizás sea throttling per-user nativo o guardrails a nivel de servicio que no requieran &lt;code&gt;guardrailConfig&lt;/code&gt; en el código del dev.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lo que conoces hoy en día en cloud no queda de un lado, todo ese conocimiento es MUY necesario para disponibilizar IA de forma responsable y sobre todo cuantificable. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;¿Te gustaría que estemos en 📩 contacto?&lt;/em&gt;&lt;br&gt;
Te espero en LinkedIn o desde el Podcast: Cloud para Todos&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Héctor Fernández&lt;/strong&gt;&lt;br&gt;
AWS Community Builder&lt;/p&gt;

&lt;p&gt;&lt;a href="https://podcast.hectorfernandez.dev" rel="noopener noreferrer"&gt;https://podcast.hectorfernandez.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bedrock</category>
      <category>aws</category>
      <category>gobierno</category>
    </item>
    <item>
      <title>Guardrails para Agentes de IA que se Autocorrigen en Lugar de Bloquear</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Tue, 07 Apr 2026 20:31:25 +0000</pubDate>
      <link>https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32</link>
      <guid>https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;La mayoría de los guardrails para agentes hacen una sola cosa: bloquear. El agent choca con una regla, el flujo se detiene y el usuario tiene que intervenir. &lt;a href="https://github.com/agentcontrol/agent-control" rel="noopener noreferrer"&gt;Agent Control&lt;/a&gt; añade una segunda opción: &lt;strong&gt;steer&lt;/strong&gt; — el agent recibe instrucciones correctivas, se autocorrige y completa la tarea sin intervención humana.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Los guardrails para agentes hoy son binarios: permitir o denegar. Cuando un agent viola una política, la respuesta típica es bloquear la acción y mostrar un error. Esto funciona para restricciones estrictas (cumplimiento PCI, bloqueos regulatorios), pero genera fricción en reglas donde el agent podría resolver el problema por sí mismo: ajustar un parámetro, redactar datos sensibles o reformatear una salida.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/agentcontrol/agent-control" rel="noopener noreferrer"&gt;Agent Control&lt;/a&gt; es un plano de control en tiempo de ejecución de código abierto que introduce &lt;strong&gt;steer controls&lt;/strong&gt; junto con los bloqueos tradicionales. Los steer controls devuelven instrucciones correctivas mediante &lt;code&gt;Guide()&lt;/code&gt; — el agent reintenta con la corrección aplicada y completa la tarea. Las reglas residen en un servidor, no en el código — se actualizan vía API o dashboard sin necesidad de redesplegar el agent.&lt;/p&gt;

&lt;p&gt;Este artículo muestra cómo funciona Agent Control usando una demo de reservas construida con &lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;. Comparamos dos enfoques sobre el mismo escenario: hooks que bloquean vs Agent Control que corrige. Hooks y Agent Control son complementarios — usa hooks para bloqueos estrictos, steer para correcciones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resumen de la Serie
&lt;/h2&gt;

&lt;p&gt;Este es un artículo adicional en la serie sobre cómo detener las alucinaciones de agentes de IA — añadido tras el lanzamiento de Agent Control:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RAG vs Graph-RAG&lt;/strong&gt; — Los grafos de conocimiento previenen agregaciones alucinadas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Tool Selection&lt;/strong&gt; — El filtrado vectorial reduce las elecciones incorrectas de herramientas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/ai-agent-guardrails-rules-that-llms-cannot-bypass-596d"&gt;AI Agent Guardrails&lt;/a&gt;&lt;/strong&gt; — Reglas simbólicas que el LLM no puede eludir&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus: Agent Control&lt;/strong&gt; (este artículo) — Steer en lugar de block&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  El Problema: Bloquear Detiene el Flujo
&lt;/h2&gt;

&lt;p&gt;Los Strands Hooks aplican reglas a nivel de tool. Cuando el agent llama a &lt;code&gt;book_hotel(guests=15)&lt;/code&gt; y el máximo es 10, el hook establece &lt;code&gt;cancel_tool&lt;/code&gt; y el agent recibe un mensaje de bloqueo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MaxGuestsHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;guests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; guests exceeds maximum of 10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El agent entonces le dice al usuario:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"El Grand Hotel tiene una capacidad máxima de 10 huéspedes. ¿Le gustaría ajustar el número de huéspedes?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El flujo se detiene. El usuario debe responder. Para un asistente de reservas que maneja cientos de solicitudes, cada operación bloqueada es un punto de fricción.&lt;/p&gt;




&lt;h2&gt;
  
  
  La Solución: Steer en Lugar de Block
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/agentcontrol/agent-control" rel="noopener noreferrer"&gt;Agent Control&lt;/a&gt; es un plano de control en tiempo de ejecución de código abierto que evalúa las entradas y salidas del agent contra políticas gestionadas en el servidor. Se integra con Strands como un &lt;code&gt;Plugin&lt;/code&gt; — el mismo punto de extensión que los Hooks, pero con dos diferencias clave:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Las reglas residen en un servidor&lt;/strong&gt; — se modifican vía API o dashboard sin tocar el código del agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los steer controls devuelven &lt;code&gt;Guide()&lt;/code&gt; en lugar de bloquear&lt;/strong&gt; — el agent reintenta con instrucciones correctivas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22ywuawf9j3tgp5u6ak9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22ywuawf9j3tgp5u6ak9.jpg" alt="Hooks (Block) vs Agent Control (Self-Correct) comparison" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo Funciona Steer
&lt;/h3&gt;

&lt;p&gt;Cuando el LLM genera una salida que menciona "15 guests", el &lt;code&gt;AgentControlSteeringHandler&lt;/code&gt; la evalúa contra los controls definidos en el servidor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El LLM genera: &lt;em&gt;"I will book Grand Hotel for 15 guests from May 1 to May 3"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Agent Control evalúa la salida del LLM → el regex coincide con "15 guest"&lt;/li&gt;
&lt;li&gt;El steer control se activa → devuelve &lt;code&gt;Guide("reduce to 10, inform the user")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;El LLM reintenta con la guía → llama a &lt;code&gt;book_hotel(guests=10)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;La reserva se completa → se informa al usuario sobre el ajuste&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxt5id2dr0b4g1cf93g2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxt5id2dr0b4g1cf93g2.jpg" alt="Agent Control steer flow: User Request → LLM → Agent Control server evaluates → Self-Correct → Final Response" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El agent responde:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"La capacidad máxima del Grand Hotel es de 10 huéspedes, así que he ajustado la reserva en consecuencia. ID de reserva: BK002."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sin intervención del usuario. Sin reintentos manuales. El flujo se completó.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementación: Misma Consulta, Dos Enfoques
&lt;/h2&gt;

&lt;p&gt;Las tools son idénticas — funciones de reserva limpias sin lógica de validación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Book a hotel room.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Booking &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; guests, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;check_out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Process payment for a booking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Processed $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Confirm a booking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Confirmed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las tools NO aplican la regla de máximo de huéspedes. Eso es responsabilidad de la capa de guardrails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1 — Hooks (Block)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MaxGuestsHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;book_hotel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;guests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocked&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; guests exceeds maximum of 10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MaxGuestsHook&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book Grand Hotel for 15 guests from 2026-05-01 to 2026-05-03&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hook blocked: 1 call(s)
Agent: "The Grand Hotel has a maximum capacity of 10 guests.
        Would you like to adjust?"
Outcome: BLOCKED — user must intervene
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test 2 — Agent Control (Steer)
&lt;/h3&gt;

&lt;p&gt;Los controls se definen en el servidor de Agent Control — no en el código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# setup_controls.py — run once
&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steer-max-guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step_types&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evaluator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pattern&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(1[1-9]|[2-9]\d)\s*guest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Guest count exceeds maximum of 10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steering_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reduce the guest count to 10, retry the booking, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and inform the user that the maximum capacity is 10.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El agent usa &lt;code&gt;AgentControlPlugin&lt;/code&gt; + &lt;code&gt;AgentControlSteeringHandler&lt;/code&gt; — ambos como Plugins de Strands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_control.integrations.strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentControlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentControlSteeringHandler&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;agent_control&lt;/span&gt;

&lt;span class="n"&gt;agent_control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;booking-guardrails-demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentControlPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;booking-guardrails-demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;event_control_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AfterToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;steering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentControlSteeringHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;booking-guardrails-demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steering&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book Grand Hotel for 15 guests from 2026-05-01 to 2026-05-03&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Steered: 1 time(s)
Agent: "The maximum capacity for the Grand Hotel is 10 guests,
        so I have adjusted the booking accordingly. Booking ID: BK002."
Outcome: SELF-CORRECTED — booking completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resultados
&lt;/h2&gt;

&lt;p&gt;Misma consulta. Mismas tools. Mismo modelo. Solo cambia el guardrail.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Tiempo&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test 1 — Hooks (&lt;code&gt;cancel_tool&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;~4s&lt;/td&gt;
&lt;td&gt;BLOCKED — el agent pide al usuario que ajuste&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test 2 — Agent Control (steer)&lt;/td&gt;
&lt;td&gt;~6s&lt;/td&gt;
&lt;td&gt;Autocorregido — reserva completada con 10 huéspedes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ambos aplican la misma regla. La diferencia es lo que ocurre cuando la regla se viola — no qué enfoque es "mejor". Los hooks son más rápidos y sencillos (Python puro, sin servidor). Agent Control añade latencia (steer → reintento) pero completa el flujo sin intervención del usuario. Elige según la regla, no según la tecnología.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo Usar Cada Enfoque
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Mejor para&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hooks (block)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reglas que DEBEN bloquear estrictamente — sin alternativa posible (p. ej., pago antes de confirmación, cumplimiento PCI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent Control (steer)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reglas donde el agent PUEDE autocorregirse — ajustar parámetros, redactar PII, corregir formato&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent Control (deny)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Igual que hooks pero gestionado en un servidor — cambiar reglas sin redesplegar código&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Son complementarios, no competidores. Los hooks son más sencillos (Python puro, sin servidor, sin sobrecarga de latencia). Agent Control es más flexible (gestionado en servidor, steer + deny, actualizaciones en tiempo de ejecución sin redespliegue). Muchos sistemas en producción usan ambos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; para reglas de cumplimiento que nunca deben eludirse — verificación de pagos, bloqueos regulatorios, PII en parámetros de tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Control (deny)&lt;/strong&gt; para los mismos bloqueos estrictos pero gestionados centralmente en múltiples agents — actualización vía dashboard, sin redespliegue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Control (steer)&lt;/strong&gt; para reglas flexibles donde la autocorrección es preferible — ajustes de capacidad, redacción de PII en salidas, formato de fechas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dos Formas de Definir Controls
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Modo&lt;/th&gt;
&lt;th&gt;Mejor para&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Server&lt;/strong&gt; (esta demo)&lt;/td&gt;
&lt;td&gt;Equipos, producción, gestión vía dashboard — los controls residen en el servidor de Agent Control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local YAML&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prototipado rápido — controls definidos en &lt;code&gt;controls.yaml&lt;/code&gt;, sin necesidad de servidor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Consulta la &lt;a href="https://docs.agentcontrol.dev/" rel="noopener noreferrer"&gt;documentación de Agent Control&lt;/a&gt; para más detalles sobre ambos modos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por Qué Strands lo Hace Sencillo
&lt;/h2&gt;

&lt;p&gt;Tanto Hooks como Agent Control se integran con un solo cambio de línea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Hooks — block violations (existing Strands API):
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MaxGuestsHook&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;

&lt;span class="c1"&gt;# Agent Control — steer violations (plugin API):
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AgentControlPlugin&lt;/span&gt;&lt;span class="p"&gt;(...),&lt;/span&gt; &lt;span class="nc"&gt;AgentControlSteeringHandler&lt;/span&gt;&lt;span class="p"&gt;(...)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin orquestación personalizada. Sin lógica de reintentos. Strands gestiona el ciclo de vida — los hooks interceptan antes de las llamadas a tools, el steering evalúa después de la salida del modelo, y &lt;code&gt;Guide()&lt;/code&gt; activa el reintento automático con instrucciones correctivas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Hooks Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/plugins/steering/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Steering Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://strandsagents.com/docs/community/plugins/agent-control/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Control Plugin for Strands&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Puntos Clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Los hooks bloquean violaciones — efectivo pero detiene el flujo y requiere intervención del usuario&lt;/li&gt;
&lt;li&gt;Agent Control redirige las violaciones — el agent se autocorrige y completa la tarea&lt;/li&gt;
&lt;li&gt;Los steer controls devuelven &lt;code&gt;Guide()&lt;/code&gt; con instrucciones correctivas — el LLM reintenta con la corrección aplicada&lt;/li&gt;
&lt;li&gt;Los controls residen en un servidor — se actualizan las reglas vía API o dashboard sin tocar el código del agent&lt;/li&gt;
&lt;li&gt;Ambos enfoques aplican la misma regla (máximo 10 huéspedes) — la diferencia es lo que ocurre cuando la regla se viola&lt;/li&gt;
&lt;li&gt;Hooks para bloqueos estrictos, Agent Control para autocorrección — usa ambos cuando sea necesario&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Ejecútalo Tú Mismo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/05-agent-control-demo

&lt;span class="c"&gt;# Start Agent Control server (see setup instructions)&lt;/span&gt;
&lt;span class="c"&gt;# https://github.com/agentcontrol/agent-control&lt;/span&gt;

&lt;span class="c"&gt;# Install and run&lt;/span&gt;
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uv run setup_controls.py
uv run test_hooks_vs_control.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Puedes cambiar a cualquier proveedor compatible con Strands — consulta &lt;a href="https://strandsagents.com/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt; para la configuración.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Investigación
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/html/2510.16381v1" rel="noopener noreferrer"&gt;ATA: Autonomous Trustworthy Agents (2024)&lt;/a&gt; — Patrones de fallo en guardrails&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/pdf/2504.07640v1" rel="noopener noreferrer"&gt;Enhancing LLMs through Neuro-Symbolic Integration&lt;/a&gt; — Razonamiento neural + simbólico&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strands Agents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/blog/strands-agents-with-agent-control/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents with Agent Control&lt;/a&gt; — Anuncio en blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/community/plugins/agent-control/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Control Plugin&lt;/a&gt; — Documentación de integración&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Hooks&lt;/a&gt; — &lt;code&gt;BeforeToolCallEvent&lt;/code&gt;, &lt;code&gt;cancel_tool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/plugins/steering/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Steering&lt;/a&gt; — &lt;code&gt;Guide&lt;/code&gt;, &lt;code&gt;Proceed&lt;/code&gt;, &lt;code&gt;SteeringHandler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt; — Cambiar a Amazon Bedrock, Anthropic, Ollama&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agent Control
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/agentcontrol/agent-control" rel="noopener noreferrer"&gt;Agent Control GitHub&lt;/a&gt; — Código abierto, Apache 2.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.agentcontrol.dev/" rel="noopener noreferrer"&gt;Agent Control Docs&lt;/a&gt; — Configuración del servidor y referencia de API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Código
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail" rel="noopener noreferrer"&gt;Code Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Guardrails para Agentes de IA: Reglas Que los LLM No Pueden Evadir</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Tue, 31 Mar 2026 18:49:56 +0000</pubDate>
      <link>https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-reglas-que-los-llm-no-pueden-evadir-5dmn</link>
      <guid>https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-reglas-que-los-llm-no-pueden-evadir-5dmn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Los agentes de IA pueden alucinar el éxito de una operación incluso cuando violan reglas de negocio. Confirman reservas sin verificación de pago, aceptan parámetros inválidos como 15 huéspedes cuando el máximo es 10, o ignoran prerrequisitos obligatorios.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El problema central: los agentes pueden confirmar reservas de hotel a pesar de que el pago nunca fue verificado, violar restricciones de capacidad, o saltarse pasos de validación obligatorios. &lt;strong&gt;El prompt engineering por sí solo no puede prevenir estos errores.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Este post demuestra cómo la &lt;strong&gt;validación neurosimbólica&lt;/strong&gt; — combinando razonamiento del LLM con reglas simbólicas deterministas aplicadas a nivel de framework — bloquea operaciones inválidas antes de que se ejecuten.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta demo utiliza Strands Agents. Patrones similares pueden aplicarse en LangGraph, AutoGen u otros frameworks de agentes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Resumen de la Serie
&lt;/h2&gt;

&lt;p&gt;Esta es la Parte 3 de una serie sobre cómo detener las hallucinations en agentes de IA:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/rag-vs-graphrag-when-agents-hallucinate-answers-2mcb"&gt;RAG vs Graph-RAG&lt;/a&gt;&lt;/strong&gt; — Knowledge graphs para prevenir hallucinations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf"&gt;Semantic Tool Selection&lt;/a&gt;&lt;/strong&gt; — Filtrado de tools basado en vectores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent Guardrails&lt;/strong&gt; (este post) — Reglas simbólicas que los LLM no pueden evadir&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/runtime-guardrails-for-ai-agents-steer-dont-block-278n"&gt;Runtime Guardrails&lt;/a&gt;&lt;/strong&gt; — Redirigir en lugar de bloquear&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/how-to-stop-ai-agents-from-hallucinating-silently-with-multi-agent-validation-3f7e"&gt;Multi-Agent Validation&lt;/a&gt;&lt;/strong&gt; — Detección de hallucinations basada en equipos&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  El Problema: Los Prompts Son Sugerencias, No Restricciones
&lt;/h2&gt;

&lt;p&gt;La investigación de &lt;a href="https://arxiv.org/html/2510.16381v1" rel="noopener noreferrer"&gt;ATA: Autonomous Trustworthy Agents (2024)&lt;/a&gt; identifica tres patrones de hallucination:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Errores de parámetros:&lt;/strong&gt; El agent llama a &lt;code&gt;book_hotel(guests=15)&lt;/code&gt; a pesar de que el docstring indica un máximo de 10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errores de completitud:&lt;/strong&gt; El agent ejecuta reservas sin la verificación de pago requerida&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamiento de evasión de tools:&lt;/strong&gt; El agent confirma éxito sin llamar a los tools de validación obligatorios&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Causa raíz:&lt;/strong&gt; Los prompts son texto que el LLM interpreta. Las reglas de negocio embebidas en docstrings se convierten en sugerencias — el modelo decide si las sigue en cada llamada.&lt;/p&gt;




&lt;h2&gt;
  
  
  La Solución: Validación Neurosimbólica con Strands Hooks
&lt;/h2&gt;

&lt;p&gt;Los hooks a nivel de framework interceptan las llamadas a tools &lt;strong&gt;antes de la ejecución&lt;/strong&gt;. Usando &lt;code&gt;BeforeToolCallEvent&lt;/code&gt; en Strands Agents, puedes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validar reglas simbólicas de forma determinista&lt;/li&gt;
&lt;li&gt;Cancelar la ejecución del tool si alguna regla falla&lt;/li&gt;
&lt;li&gt;Enviar un mensaje de cancelación directamente al LLM (no puede ser anulado)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Diferencia arquitectónica clave:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompts:&lt;/strong&gt; Entrada del LLM (interpretable, anulable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks:&lt;/strong&gt; Interceptores del framework (deterministas, obligatorios)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prerrequisitos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Experiencia con Python y uso de tools en agentes LLM&lt;/li&gt;
&lt;li&gt;Familiaridad con &lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/04-neurosymbolic-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementación: Reglas, Hook y Dos Agentes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Paso 1: Definir Reglas Simbólicas
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;BOOKING_RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maximum 10 guests per booking&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid_dates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check-in must be before check-out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;CONFIRMATION_RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_before_confirm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_verified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Payment must be verified before confirmation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Las reglas son funciones Python simples — deterministas, testeables y auditables de forma independiente a cualquier agent.&lt;/p&gt;


&lt;h3&gt;
  
  
  Paso 2: Crear el Hook de Validación
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NeurosymbolicHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Cuando se establece &lt;code&gt;event.cancel_tool&lt;/code&gt;, Strands reemplaza el resultado del tool con ese mensaje antes de que el LLM lo vea. &lt;strong&gt;El tool nunca se ejecuta.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Paso 3: Definir Tools Limpios
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Book a hotel room.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Booked &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; guests, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;check_out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Process payment for a booking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Processed $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Confirm a booking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Confirmed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Los tools contienen solo lógica de negocio — sin validación mezclada.&lt;/p&gt;


&lt;h3&gt;
  
  
  Paso 4: Crear Ambos Agentes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="c1"&gt;# Using OpenAI-compatible interface via Strands SDK (not direct OpenAI usage)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIModel&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Baseline: no hook, no validation
&lt;/span&gt;&lt;span class="n"&gt;baseline_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Guarded: hook intercepts every tool call
&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NeurosymbolicHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;guarded_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirm_booking&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Mismos tools, mismo modelo, mismos prompts.&lt;/strong&gt; La única diferencia: &lt;code&gt;hooks=[hook]&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Resultados de las Pruebas
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prueba 1: Confirmar Reserva Sin Pago
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Consulta:&lt;/strong&gt; "Confirm booking BK001"&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;❌ Ejecuta — pago nunca verificado, docstring ignorado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarded&lt;/td&gt;
&lt;td&gt;✅ Bloqueado — el hook evalúa &lt;code&gt;CONFIRMATION_RULES&lt;/code&gt;, encuentra &lt;code&gt;payment_verified=False&lt;/code&gt;, cancela&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;El LLM recibe: &lt;code&gt;BLOCKED: Payment must be verified before confirmation&lt;/code&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Prueba 2: Reservar Hotel Excediendo el Límite de Huéspedes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Consulta:&lt;/strong&gt; "Book Grand Hotel for 15 people from 2026-03-20 to 2026-03-25"&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;❌ Ejecuta con 15 huéspedes — máximo en docstring ignorado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarded&lt;/td&gt;
&lt;td&gt;✅ Bloqueado — &lt;code&gt;max_guests_check(15 &amp;lt;= 10)&lt;/code&gt; falla, ejecución cancelada&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La validación ocurre &lt;strong&gt;antes de la ejecución&lt;/strong&gt; — no hay reserva que revertir.&lt;/p&gt;


&lt;h3&gt;
  
  
  Prueba 3: Reserva Válida
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Consulta:&lt;/strong&gt; "Book Grand Hotel for 5 guests from 2026-03-20 to 2026-03-25"&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;✅ Ejecuta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarded&lt;/td&gt;
&lt;td&gt;✅ Ejecuta — todas las reglas pasan, el hook no agrega fricción&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Resumen de Escenarios
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Escenario&lt;/th&gt;
&lt;th&gt;Baseline&lt;/th&gt;
&lt;th&gt;Guarded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Confirmar sin pago&lt;/td&gt;
&lt;td&gt;❌ Ejecuta — hallucination&lt;/td&gt;
&lt;td&gt;✅ Bloqueado antes de la ejecución&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservar 15 huéspedes (máx. 10)&lt;/td&gt;
&lt;td&gt;❌ Ejecuta — regla violada&lt;/td&gt;
&lt;td&gt;✅ Bloqueado antes de la ejecución&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reserva válida (5 huéspedes)&lt;/td&gt;
&lt;td&gt;✅ Ejecuta&lt;/td&gt;
&lt;td&gt;✅ Ejecuta&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Punto Clave: Dónde Se Aplica la Validación
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prompt Engineering (Insuficiente)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
IMPORTANT: Never confirm bookings without payment verification.
CRITICAL: Maximum 10 guests per booking.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="c1"&gt;# The LLM reads this as context. It can hallucinate compliance.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ El LLM decide si sigue estas instrucciones en cada llamada.&lt;/p&gt;
&lt;h3&gt;
  
  
  Aplicación Basada en Hooks (Suficiente)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# Tool never executes. LLM receives the cancellation.
&lt;/span&gt;        &lt;span class="c1"&gt;# There is no path to override this.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✅ El hook se ejecuta fuera del LLM. La decisión no depende del LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diferencia arquitectónica:&lt;/strong&gt; Los prompts son entrada del LLM (interpretable). Los hooks son interceptores del framework (deterministas).&lt;/p&gt;


&lt;h2&gt;
  
  
  Consideraciones para Producción
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Ventajas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restricciones verificables:&lt;/strong&gt; Las reglas son código, no instrucciones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralizadas:&lt;/strong&gt; Un solo hook valida todos los tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testeables de forma independiente:&lt;/strong&gt; Las reglas funcionan fuera de cualquier agent o contexto de LLM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditables:&lt;/strong&gt; Las violaciones de reglas producen eventos explícitos con nombre del tool, parámetros y razón&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Desafíos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Las reglas deben definirse explícitamente para cada operación protegida&lt;/li&gt;
&lt;li&gt;No maneja lógica difusa o probabilística — las reglas son booleanas&lt;/li&gt;
&lt;li&gt;Los casos límite requieren manejo explícito en las condiciones&lt;/li&gt;
&lt;li&gt;Las reglas necesitan mantenimiento a medida que evoluciona la lógica de negocio&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Mejores Prácticas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define hooks para operaciones críticas: reservas, pagos, cancelaciones&lt;/li&gt;
&lt;li&gt;Registra todas las violaciones de reglas con nombre del tool, parámetros y razón&lt;/li&gt;
&lt;li&gt;Testea las reglas de forma independiente y exhaustiva&lt;/li&gt;
&lt;li&gt;Combina con semantic tool selection (Parte 2) y multi-agent validation (Parte 5) para protección en capas&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Siguiente Paso
&lt;/h2&gt;

&lt;p&gt;Los guardrails simbólicos bloquean violaciones de reglas a nivel de tool. Pero bloquear detiene el flujo de trabajo — el usuario debe intervenir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws/runtime-guardrails-for-ai-agents-steer-dont-block-278n"&gt;Parte 4: Runtime Guardrails&lt;/a&gt;&lt;/strong&gt; muestra cómo Agent Control redirige a los agentes para que se autocorrijan en lugar de bloquearlos, completando flujos de trabajo sin intervención humana.&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusiones Clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Los prompts son sugerencias:&lt;/strong&gt; El LLM interpreta docstrings; puede alucinar cumplimiento con cualquier instrucción&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los hooks son aplicación forzada:&lt;/strong&gt; &lt;code&gt;BeforeToolCallEvent&lt;/code&gt; intercepta antes de la ejecución a nivel de framework — el LLM no puede anular un tool cancelado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Todas las operaciones inválidas bloqueadas:&lt;/strong&gt; Cero cambios en tools, cero cambios en prompts, un hook agregado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separación limpia:&lt;/strong&gt; Los tools manejan operaciones; los hooks manejan la aplicación de restricciones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable por diseño:&lt;/strong&gt; Las violaciones de reglas son condiciones Python explícitas — testeables, registrables, trazables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un hook, todos los tools:&lt;/strong&gt; Un solo &lt;code&gt;NeurosymbolicHook&lt;/code&gt; valida cada llamada a tool de forma centralizada&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Ejecútalo Tú Mismo
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/04-neurosymbolic-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uv run test_neurosymbolic_hooks.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Puedes cambiar a cualquier proveedor soportado por Strands — consulta &lt;a href="https://strandsagents.com/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt; para la configuración.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Investigación
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/html/2510.16381v1" rel="noopener noreferrer"&gt;ATA: Autonomous Trustworthy Agents (2024)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2504.07640v1" rel="noopener noreferrer"&gt;Enhancing LLMs through Neuro-Symbolic Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.preprints.org/manuscript/202505.1955" rel="noopener noreferrer"&gt;Mitigating LLM Hallucinations: Meta-Analysis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Strands Agents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Hooks Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Código
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail" rel="noopener noreferrer"&gt;Repositorio de Código&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;br&gt;
&lt;a href="https://linktr.ee/elizabethfuentesleone" rel="noopener noreferrer"&gt;Linktr&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__717518"&gt;
    &lt;a href="/elizabethfuentes12" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F717518%2Fb550b165-b8b9-405d-acfb-e5dc846765b0.png" alt="elizabethfuentes12 image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;Elizabeth Fuentes L&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;I help developers build production-ready AI applications through hands-on tutorials and open-source projects.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Reduce errores y costos de tokens en agentes con seleccion semantica de herramientas</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:25:05 +0000</pubDate>
      <link>https://dev.to/aws-espanol/reduce-errores-y-costos-de-tokens-en-agentes-con-seleccion-semantica-de-herramientas-12o5</link>
      <guid>https://dev.to/aws-espanol/reduce-errores-y-costos-de-tokens-en-agentes-con-seleccion-semantica-de-herramientas-12o5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cuando los agentes de IA tienen muchas herramientas similares, a menudo seleccionan la incorrecta y consumen tokens excesivos al procesar todas las descripciones de herramientas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este articulo demuestra como la seleccion semantica de herramientas filtra tools antes del procesamiento del agente, mejorando la precision y reduciendo costos de tokens. La demostracion utiliza Strands Agents y FAISS para filtrar 29 herramientas y quedarse con las 3 mas relevantes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta demo utiliza Strands Agents. Patrones similares se pueden aplicar en LangGraph, AutoGen u otros frameworks de agentes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Vision general de la serie
&lt;/h2&gt;

&lt;p&gt;Esta es la Parte 2 de una serie sobre como detener las hallucinations en agentes de IA:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws-espanol/rag-vs-graphrag-cuando-los-agentes-alucinan-respuestas-1mlj"&gt;RAG vs Graph-RAG&lt;/a&gt;&lt;/strong&gt; — Knowledge graphs para prevenir hallucinations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seleccion semantica de herramientas&lt;/strong&gt; (este articulo) — Filtrado de tools basado en vectores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/how-to-stop-ai-agents-from-hallucinating-silently-with-multi-agent-validation-3f7e"&gt;Validacion multi-agente&lt;/a&gt;&lt;/strong&gt; — Deteccion de hallucinations basada en equipos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/ai-agent-guardrails-rules-that-llms-cannot-bypass-596d"&gt;AI Agent Guardrails&lt;/a&gt;&lt;/strong&gt; — Aplicacion de razonamiento simbolico&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/runtime-guardrails-for-ai-agents-steer-dont-block-278n"&gt;Runtime Guardrails&lt;/a&gt;&lt;/strong&gt; — Controles autocorrectivos&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configuracion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/02-semantic-tools-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  El problema dual: errores + desperdicio de tokens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Escenario del problema
&lt;/h3&gt;

&lt;p&gt;Un agente de viajes con 29 herramientas similares (&lt;code&gt;search_hotels&lt;/code&gt;, &lt;code&gt;search_flights&lt;/code&gt;, &lt;code&gt;search_hotel_reviews&lt;/code&gt;, etc.) recibe la consulta: "How much does Hotel Marriott cost?"&lt;/p&gt;

&lt;p&gt;El agente puede seleccionar &lt;code&gt;get_hotel_details()&lt;/code&gt; en lugar de &lt;code&gt;get_hotel_pricing()&lt;/code&gt; — una seleccion incorrecta de tool. Este error consume 4,500 tokens procesando las descripciones de las 29 herramientas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Causas raiz
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Nombres de herramientas similares causan confusion&lt;/li&gt;
&lt;li&gt;Las herramientas genericas se usan en exceso&lt;/li&gt;
&lt;li&gt;Mas herramientas aumentan la probabilidad de hallucination&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contexto de investigacion
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Las hallucinations en la seleccion de herramientas aumentan con la cantidad de tools. Sistemas en produccion reportan una reduccion del 89% en tokens con seleccion semantica de herramientas."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  La solucion: seleccion semantica de herramientas con FAISS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Modos de falla de agentes a escala
&lt;/h3&gt;

&lt;p&gt;La investigacion identifica cinco modos criticos de falla:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Errores de seleccion de funcion&lt;/strong&gt; — Llamar a tools inexistentes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errores de parametros&lt;/strong&gt; — Argumentos mal formados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errores de completitud&lt;/strong&gt; — Parametros requeridos faltantes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamiento de bypass de tools&lt;/strong&gt; — Generar respuestas en lugar de llamar herramientas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desbordamiento de contexto&lt;/strong&gt; — Desperdicio de tokens al procesar todas las descripciones&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Analisis costo-beneficio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Cada llamada al LLM envia las descripciones de las 29 herramientas&lt;/li&gt;
&lt;li&gt;En un flujo de 50 pasos: 29 tools x 50 llamadas&lt;/li&gt;
&lt;li&gt;Genera un desperdicio significativo de tokens y retrasos en el procesamiento&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;La investigacion muestra hasta un 86.4% de precision previniendo hallucinations en la seleccion de herramientas en sistemas de produccion.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Demostracion: tres enfoques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prueba 1: Enfoque tradicional (las 29 herramientas)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="c1"&gt;# Using OpenAI-compatible interface via Strands SDK (not direct OpenAI usage)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enhanced_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TESTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_and_capture_with_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: ~1,557 tokens promedio por consulta con precision variable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Prueba 2: Enfoque semantico (top-3 herramientas filtradas)
&lt;/h3&gt;

&lt;p&gt;El agente recibe unicamente las 3 herramientas mas relevantes por consulta.&lt;/p&gt;

&lt;h4&gt;
  
  
  Construccion del indice FAISS
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Build FAISS index from tool docstrings&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexFlatL2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find most relevant tools using FAISS&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;El modelo all-MiniLM-L6-v2 es ligero (22M parametros, 384 dimensiones), optimizado para similitud semantica y funciona de manera eficiente en CPUs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Filtrado en tiempo de ejecucion
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="c1"&gt;# Using OpenAI-compatible interface via Strands SDK (not direct OpenAI usage)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIModel&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TESTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;selected_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_and_capture_with_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: ~275 tokens promedio por consulta — una reduccion desde 1,557 tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dato clave: el agente nunca procesa las otras 26 herramientas. Estas tools permanecen en el sistema pero nunca entran en la ventana de contexto del agente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Prueba 3: Semantico + memoria (agente unico)
&lt;/h3&gt;

&lt;p&gt;Para conversaciones multi-turno en produccion, manteniendo la memoria mientras se intercambian herramientas dinamicamente.&lt;/p&gt;

&lt;h4&gt;
  
  
  Intercambio dinamico de herramientas
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_tools&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Swap tools in a live agent without losing conversation memory.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;reg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_registry&lt;/span&gt;
    &lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamic_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implementacion completa
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="c1"&gt;# Using OpenAI-compatible interface via Strands SDK (not direct OpenAI usage)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enhanced_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;swap_tools&lt;/span&gt;

&lt;span class="n"&gt;initial_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TESTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;initial_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TESTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;swap_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_and_capture_with_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: Mas tokens que el enfoque solo semantico (debido al contexto acumulado), pero significativamente menos comparado con el metodo tradicional, manteniendo el historial completo de la conversacion.&lt;/p&gt;

&lt;h4&gt;
  
  
  Por que funciona
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Strands llama a &lt;code&gt;tool_registry.get_all_tools_config()&lt;/code&gt; en cada ciclo del event loop, detectando automaticamente los cambios en tiempo de ejecucion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cero perdida de conversacion: &lt;code&gt;agent.messages&lt;/code&gt; se preserva entre intercambios de tools&lt;/li&gt;
&lt;li&gt;No requiere recrear el agente&lt;/li&gt;
&lt;li&gt;Flexibilidad en tiempo de ejecucion para necesidades dinamicas de herramientas&lt;/li&gt;
&lt;li&gt;Listo para produccion en conversaciones largas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contexto de investigacion y rendimiento en el mundo real
&lt;/h2&gt;

&lt;p&gt;La demo controlada logro precision perfecta en 13 consultas. Sin embargo, los sistemas de produccion reales con cientos de herramientas y consultas ambiguas muestran resultados diferentes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"La investigacion muestra que en sistemas de produccion con cientos de herramientas, la seleccion semantica de tools alcanza hasta un 86.4% de precision en la deteccion y prevencion de hallucinations en la seleccion de herramientas."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esto reduce significativamente los errores comparado con los enfoques tradicionales (que caen por debajo del 50% de precision con mas de 100 tools), pero sigue siendo un desafio en dominios con semantica de herramientas superpuesta.&lt;/p&gt;




&lt;h2&gt;
  
  
  Siguiente paso
&lt;/h2&gt;

&lt;p&gt;La seleccion semantica de herramientas reduce los errores de seleccion de tools y los costos de tokens. Sin embargo, los agentes aun pueden generar hallucinations sobre el &lt;strong&gt;exito de operaciones&lt;/strong&gt; (por ejemplo, confirmar reservaciones sin procesar pagos o ignorar reglas de negocio).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parte 3: Validacion multi-agente&lt;/strong&gt; demuestra como equipos especializados de agentes (Executor -&amp;gt; Validator -&amp;gt; Critic) detectan hallucinations antes de que lleguen a los usuarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta capacidad esta disponible de forma nativa a traves de &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock Agentcore Gateway&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Problema dual&lt;/strong&gt;: Errores en la seleccion de herramientas Y desperdicio de tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduccion significativa de errores&lt;/strong&gt;: Menos herramientas = menos selecciones incorrectas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;89% de ahorro en tokens&lt;/strong&gt;: Reduciendo de 29 a 3 herramientas por llamada&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementacion sencilla&lt;/strong&gt;: ~20 lineas de codigo con FAISS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integracion con Strands&lt;/strong&gt;: El decorador &lt;code&gt;@tool&lt;/code&gt; y la carga dinamica permiten el filtrado semantico con intercambio de herramientas en tiempo de ejecucion&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Ejecutalo tu mismo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/02-semantic-tools-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uv run test_semantic_tool_selection.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Puedes cambiar a cualquier proveedor soportado por Strands — consulta &lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt; para la configuracion.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Investigacion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2601.05214" rel="noopener noreferrer"&gt;Internal Representations as Indicators of Hallucinations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/html/2511.22729v1" rel="noopener noreferrer"&gt;Solving Context Window Overflow&lt;/a&gt; — Reduccion de tokens 7x&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rconnect.tech/blog/semantic-tool-selection-guide" rel="noopener noreferrer"&gt;Semantic Tool Selection in Practice&lt;/a&gt; — Reduccion del 89%&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strands Agents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/examples/python/meta_tooling/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Meta-Tooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Codigo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail" rel="noopener noreferrer"&gt;Repositorio de codigo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>EKS Auto Mode: Kubernetes sin drama😝</title>
      <dc:creator>Rubén Rodríguez</dc:creator>
      <pubDate>Mon, 23 Mar 2026 08:07:05 +0000</pubDate>
      <link>https://dev.to/aws-espanol/eks-auto-mode-kubernetes-sin-drama-1afk</link>
      <guid>https://dev.to/aws-espanol/eks-auto-mode-kubernetes-sin-drama-1afk</guid>
      <description>&lt;p&gt;Llevábamos tiempo queriendo dedicarle un rato a EKS Auto Mode, pero entre proyectos, eventos y vacaciones no conseguíamos encontrar ese hueco. Hoy lo hemos encontrado. &lt;/p&gt;

&lt;p&gt;Y es que una de las conversaciones recurrentes con compañeros, clientes y jefes es precisamente cómo la tecnología está evolucionando para automatizar capas de infraestructura que antes requerían expertise y horas de configuración.&lt;br&gt;
Nuestra respuesta es siempre la misma: la tecnología no elimina el conocimiento. Donde antes necesitabas configurar Karpenter, gestionar AMIs, parchear nodos y mantener add-ons... ahora puedes focalizarte en arquitectura, seguridad y en construir cosas que aporten valor de otra manera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Qué es EKS Auto Mode?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Podríamos explicarlo de muchas maneras pero ya nos conocéis, nos gusta ser directos:&lt;/p&gt;

&lt;p&gt;AWS gestiona por ti el autoscaling de nodos (con Karpenter), networking de pods, load balancing, DNS, almacenamiento EBS, parches del SO y rotación de nodos. Tú te centras en tus cargas de trabajo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Qué ventajas nos da esto?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS gestiona los add-ons core. No los ves, no los tocas, no los parcheas.&lt;/li&gt;
&lt;li&gt;Karpenter gestionado por AWS, sin instalación ni configuración.&lt;/li&gt;
&lt;li&gt;NodePools y NodeClasses disponibles para tener flexibilidad sobre tus nodos.&lt;/li&gt;
&lt;li&gt;Optimización de costes gracias a la consolidación automática de Karpenter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;¡Al lio!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para el test hemos utilizado el módulo oficial de la community &lt;a href="https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-auto-mode" rel="noopener noreferrer"&gt;eks-auto-mode&lt;/a&gt;, modificando la llamada para adaptarla a este caso. Lo importante del módulo es que con "&lt;em&gt;compute_config.enabled = true&lt;/em&gt;" activamos Auto Mode completo.&lt;/p&gt;

&lt;p&gt;Parece broma pero sí.🥶Es así de fácil.🥶&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Qué crea Auto Mode automáticamente?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Una vez desplegado el cluster, veamos como se gestiona Karpenter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get nodeclass&lt;br&gt;
NAME      ROLE                                                  READY   AGE&lt;br&gt;
default   awtwins-cluster-eks-auto-20260322210213399200000003   True    55m&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get nodepools&lt;br&gt;
NAME              NODECLASS   NODES   READY   AGE&lt;br&gt;
general-purpose   default     0       True    56m&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Auto Mode crea automáticamente el NodeClass y el NodePool correspondiente. &lt;br&gt;
&lt;em&gt;Puedes añadir configuración extra de almacenamiento, restricciones de instancias, límites de capacidad o tags.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El test de autoscaling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para validar que todo funciona usamos el deployment de pause que usa AWS en sus ejemplos oficiales de Karpenter y que ya hemos utilizado anteriormente. No hace nada, solo consume recursos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inflate&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inflate&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inflate&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inflate&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.ecr.aws/eks-distro/kubernetes/pause:3.7&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1Gi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aplicamos el manifest y vemos que tenemos los pods creandose correctamente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bbt36mmg572pljvr35j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bbt36mmg572pljvr35j.png" alt="Pods Creating" width="517" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos a EC2 mediante la consola y podremos ver como Karpenter está creando el nodo necesario para desplegar este tipo de carga.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdw75l3mj0hz00w0ci7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdw75l3mj0hz00w0ci7h.png" alt="EC2 View" width="800" height="33"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para validar que el desescalado también funciona sin problema procedemos a escalar el deployment a 0 replicas:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl scale deployment inflate --replicas=0&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsm906gc5utp9czhg3aiv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsm906gc5utp9czhg3aiv.png" alt="EC2 Deleting" width="800" height="33"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El resultado habla por sí solo: nodos provisionados en segundos, pods corriendo, y consolidación automática al bajar la carga. Sin tocar nada más.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;En este post solo estamos validando el autoscaling, pero el mismo principio aplica para todo lo que mencionamos antes como load balancing, networking, DNS, almacenamiento, parches de nodos... Todo eso se gestiona por AWS sin que tengas que instalar, configurar ni actualizar nada. Como ya sabéis, somos muy fans de Karpenter, pero la gracia de Auto Mode es que Karpenter es solo una pieza de todo lo que te quitas de encima.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Adjuntamos una imagen descriptiva como comparación:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bhtzfjzodhkdl8q8fcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bhtzfjzodhkdl8q8fcw.png" alt=" " width="645" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cuánto cuesta?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El modelo de precios de EKS Auto Mode tiene tres componentes importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precio del cluster igual que en EKS estándar.&lt;/li&gt;
&lt;li&gt;Precio de las EC2 desplegadas.&lt;/li&gt;
&lt;li&gt;Precio de Auto Mode, aprox un 12% extra sobre el coste de cada instancia.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nuestra opinión&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Como hemos hablado de estos temas en diferentes foros con muchos compañeros, llega el momento más esperado...🥁&lt;/p&gt;

&lt;p&gt;EKS Auto Mode no es solo una feature más. Es un cambio en cómo operar Kubernetes en AWS. Pasamos de gestionar un cluster a consumirlo. Y eso, bien entendido, desgasta menos y minimiza problemas.&lt;/p&gt;

&lt;p&gt;Además, como ya vimos en nuestro anterior post de awtwins, si le sumamos features como ArgoCD Capability la vida nos vuelve a sonreír con un cluster magnífico con mucha menos gestión que antes. Los que ya nos conocéis sabéis que para nosotros este tipo de soluciones siempre son un &lt;strong&gt;SÍ&lt;/strong&gt;... si lo puedes pagar, claro💰😄&lt;/p&gt;

&lt;p&gt;Como siempre, esperamos vuestros comentarios y feedback👇&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awtwins</category>
      <category>eks</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>RAG vs GraphRAG: Cuando los Agentes Alucinan Respuestas</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Wed, 18 Mar 2026 01:05:06 +0000</pubDate>
      <link>https://dev.to/aws-espanol/rag-vs-graphrag-cuando-los-agentes-alucinan-respuestas-1mlj</link>
      <guid>https://dev.to/aws-espanol/rag-vs-graphrag-cuando-los-agentes-alucinan-respuestas-1mlj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Un agent que alucina durante la ejecucion es catastrofico: podria fabricar parametros de API, inventar confirmaciones de exito tras fallos, o ejecutar acciones basadas en creencias falsas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este articulo explora como el Retrieval-Augmented Generation (RAG) tradicional provoca que los AI agents alucinen estadisticas y agregaciones, en contraste con GraphRAG usando knowledge graphs de Neo4j. Demostramos las diferencias a traves de un agent de reservas de viajes que compara RAG (FAISS) frente a GraphRAG sobre 300 documentos FAQ de hoteles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta demo utiliza Strands Agents. Patrones similares se pueden aplicar en LangGraph, AutoGen u otros frameworks de agents.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Vision General de la Serie
&lt;/h2&gt;

&lt;p&gt;Esta es la Parte 1 de una serie sobre como detener las hallucinations en AI agents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RAG vs Graph-RAG&lt;/strong&gt; (este articulo) — Los knowledge graphs previenen agregaciones alucinadas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Tool Selection&lt;/strong&gt; — El filtrado vectorial reduce las elecciones incorrectas de tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Agent Validation&lt;/strong&gt; — Los swarms detectan hallucinations antes de que lleguen a los usuarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent Guardrails&lt;/strong&gt; — Reglas simbolicas que el LLM no puede eludir&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Guardrails&lt;/strong&gt; — Dirigir en lugar de bloquear&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  El Problema: Tres Tipos de Hallucinations en RAG
&lt;/h2&gt;

&lt;p&gt;La investigacion (&lt;a href="https://arxiv.org/pdf/2509.09360" rel="noopener noreferrer"&gt;MetaRAG: Metamorphic Testing for Hallucination Detection&lt;/a&gt;) identifica tres tipos de hallucination en sistemas RAG:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Estadisticas fabricadas&lt;/strong&gt; — Los sistemas RAG producen numeros estimados en lugar de valores calculados. El LLM adivina agregaciones a partir de unos pocos documentos recuperados en vez de calcularlas sobre el conjunto completo de datos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recuperacion incompleta&lt;/strong&gt; — La busqueda vectorial recupera los top-k fragmentos mas similares, pero los datos necesarios para una respuesta pueden estar dispersos en muchos documentos. El LLM llena los vacios con fabricaciones que suenan plausibles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fabricacion fuera de dominio&lt;/strong&gt; — Cuando no existen datos relevantes, RAG devuelve los resultados de apariencia mas similar de todos modos. El LLM trata estas coincidencias parciales como contexto valido y fabrica una respuesta con confianza.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  La Solucion: GraphRAG con Knowledge Graphs
&lt;/h2&gt;

&lt;p&gt;Los knowledge graphs resuelven estos problemas a nivel arquitectonico:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Las agregaciones&lt;/strong&gt; son calculadas por el motor de base de datos (AVG, COUNT, SUM) — no adivinadas por el LLM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las relaciones&lt;/strong&gt; son aristas explicitas en el grafo — no inferidas por similitud de texto&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los datos faltantes&lt;/strong&gt; devuelven resultados vacios — el LLM recibe "no se encontraron resultados" en lugar de coincidencias parciales de las cuales alucinar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La diferencia clave: RAG siempre devuelve algo. GraphRAG devuelve resultados vacios cuando los datos no existen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requisitos Previos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.9+&lt;/li&gt;
&lt;li&gt;Neo4j ejecutandose localmente con el plugin APOC&lt;/li&gt;
&lt;li&gt;Clave de API de OpenAI (o cualquier proveedor de modelos compatible con Strands)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/01-faq-graphrag-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementacion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Agent 1: RAG Tradicional (FAISS)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_faqs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search hotel FAQs using vector similarity.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embed_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="n"&gt;rag_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_faqs&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El agent RAG recupera los 3 documentos mas similares y permite que el LLM los resuma. No puede agregar sobre el conjunto completo de datos, recorrer relaciones ni reconocer cuando los datos estan ausentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent 2: GraphRAG (Neo4j + Cypher)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_knowledge_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cypher_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Execute a Cypher query against the hotel knowledge graph.

    Node labels: Hotel, Room, Amenity, Policy, Service
    Relationships: (Hotel)-[:HAS_ROOM]-&amp;gt;(Room), (Hotel)-[:OFFERS_AMENITY]-&amp;gt;(Amenity),
                   (Hotel)-[:HAS_POLICY]-&amp;gt;(Policy), (Hotel)-[:PROVIDES_SERVICE]-&amp;gt;(Service)

    Properties:
    - Hotel: name, location, rating, price_range
    - Room: type, capacity, price_per_night
    - Amenity: name, category
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cypher_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No results found.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; results:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;graph_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_knowledge_graph&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El schema en el docstring habilita Text2Cypher — el LLM genera consultas Cypher precisas en lugar de adivinar a partir de fragmentos de texto.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resultados: 4 Escenarios de Prueba
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prueba 1: Agregacion — "What is the average hotel rating in Paris?"
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;th&gt;Por que&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;4.7 (de solo 2 documentos)&lt;/td&gt;
&lt;td&gt;Recupero 2 documentos similares y promedio esas calificaciones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;4.7 (a nivel de base de datos) ✅&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MATCH (h:Hotel {location:'Paris'}) RETURN avg(h.rating)&lt;/code&gt; — calculado sobre todos los hoteles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;RAG tuvo suerte aqui — mismo numero, pero de 2 documentos en vez del conjunto completo de datos. Con datos diferentes, esto divergiria.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prueba 2: Conteo — "How many hotels have a swimming pool?"
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;th&gt;Por que&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;"No tengo los datos" ❌&lt;/td&gt;
&lt;td&gt;Los 3 documentos principales no mencionaban piscinas; no pudo contar entre 300 documentos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;"133 hoteles" ✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MATCH (h)-[:OFFERS_AMENITY]-&amp;gt;(a {name:'pool'}) RETURN count(h)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Prueba 3: Multi-hop — "What room types does the highest-rated hotel offer?"
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;th&gt;Por que&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;Incompleto ⚠️&lt;/td&gt;
&lt;td&gt;Encontro informacion del hotel pero no pudo recorrer hasta los datos de habitaciones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;Completo con datos de habitaciones ✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MATCH (h:Hotel) WITH h ORDER BY h.rating DESC LIMIT 1 MATCH (h)-[:HAS_ROOM]-&amp;gt;(r) RETURN r&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Prueba 4: Fuera de dominio — "Find hotels in Antarctica"
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Resultado&lt;/th&gt;
&lt;th&gt;Por que&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;Fabrico "Estaciones de Investigacion" como hoteles ❌&lt;/td&gt;
&lt;td&gt;Devolvio documentos de apariencia similar sobre alojamiento en clima frio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;"No se encontraron hoteles" ✅&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MATCH (h:Hotel {location:'Antarctica'}) RETURN h&lt;/code&gt; — resultado vacio&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Cuando Usar Cada Enfoque
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterio&lt;/th&gt;
&lt;th&gt;RAG&lt;/th&gt;
&lt;th&gt;GraphRAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estructura de datos&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Texto no estructurado, documentos&lt;/td&gt;
&lt;td&gt;Estructurado, con relaciones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tipo de consulta&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Similitud semantica, busqueda difusa&lt;/td&gt;
&lt;td&gt;Agregaciones precisas, recorridos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Datos faltantes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Devuelve resultados similares (riesgo de hallucination)&lt;/td&gt;
&lt;td&gt;Devuelve vacio (fallo honesto)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complejidad de configuracion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Menor (embeddings + vector store)&lt;/td&gt;
&lt;td&gt;Mayor (knowledge graph + schema)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ideal para&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Busqueda de contenido, Q&amp;amp;A sobre documentos&lt;/td&gt;
&lt;td&gt;Analitica, razonamiento multi-hop, verificacion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusiones Clave
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;RAG siempre devuelve algo — incluso cuando los datos no existen, creando oportunidades de hallucination&lt;/li&gt;
&lt;li&gt;GraphRAG devuelve resultados vacios cuando los datos estan ausentes — forzando respuestas honestas de "No lo se"&lt;/li&gt;
&lt;li&gt;Las agregaciones deben ser calculadas por la base de datos, no adivinadas por el LLM&lt;/li&gt;
&lt;li&gt;El schema en el docstring del tool habilita Text2Cypher — consultas precisas en lugar de similitud de texto&lt;/li&gt;
&lt;li&gt;Ambos enfoques tienen su lugar — usa RAG para busqueda semantica, GraphRAG para recuperacion precisa de datos&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Siguiente Paso
&lt;/h2&gt;

&lt;p&gt;GraphRAG previene estadisticas y agregaciones alucinadas. Pero los agents aun pueden seleccionar el tool incorrecto o desperdiciar tokens procesando descripciones de tools irrelevantes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf"&gt;Parte 2: Semantic Tool Selection&lt;/a&gt; muestra como el filtrado vectorial reduce los errores de seleccion de tools y los costos de tokens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pruebalo Tu Mismo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations/01-faq-graphrag-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="c"&gt;# Start Neo4j with APOC plugin first&lt;/span&gt;
uv run test_graphrag_hallucinations.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Puedes cambiar a cualquier proveedor compatible con Strands — consulta &lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt; para la configuracion.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Investigacion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2509.09360" rel="noopener noreferrer"&gt;MetaRAG: Metamorphic Testing for Hallucination Detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2503.13514" rel="noopener noreferrer"&gt;RAG-KG-IL: Multi-Agent Hybrid Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strands Agents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/model-providers/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Model Providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Codigo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail" rel="noopener noreferrer"&gt;Repositorio de Codigo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Detén las Hallucinations en Agentes de IA: 4 Técnicas Esenciales</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Wed, 18 Mar 2026 00:13:33 +0000</pubDate>
      <link>https://dev.to/aws-espanol/deten-las-hallucinations-de-ai-agents-4-tecnicas-esenciales-3kcl</link>
      <guid>https://dev.to/aws-espanol/deten-las-hallucinations-de-ai-agents-4-tecnicas-esenciales-3kcl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;4 técnicas para detener las hallucinations en AI agents: Graph-RAG para recuperación precisa de datos, selección semántica de tools para elegir la herramienta correcta, guardrails neurosimbólicos para aplicar reglas de negocio, y validación multi-agent para detección de errores.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Objetivos de Aprendizaje
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cómo Graph-RAG previene hallucinations estadísticas con datos estructurados&lt;/li&gt;
&lt;li&gt;Por qué la selección semántica de tools mejora la precisión y reduce costos de tokens&lt;/li&gt;
&lt;li&gt;Cómo los guardrails neurosimbólicos con Strands Agents bloquean operaciones inválidas&lt;/li&gt;
&lt;li&gt;Cómo la validación multi-agent detecta hallucinations antes de que lleguen a los usuarios&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta demo utiliza Strands Agents. Patrones similares pueden aplicarse en LangGraph, AutoGen u otros frameworks de agents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Repositorio y Prerequisitos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Clonar:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;stop-ai-agent-hallucinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Requisitos:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.9+&lt;/li&gt;
&lt;li&gt;Acceso a un LLM (Amazon Bedrock, OpenAI, Anthropic u Ollama)&lt;/li&gt;
&lt;li&gt;Credenciales de AWS configuradas (si se usa Bedrock)&lt;/li&gt;
&lt;li&gt;Comprensión básica de AI agents y tool calling&lt;/li&gt;
&lt;li&gt;Bibliotecas clave: Strands Agents, Neo4j, FAISS, SentenceTransformers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Técnica 1: Graph-RAG para Recuperación Precisa de Datos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problema:&lt;/strong&gt; El RAG tradicional recupera fragmentos de texto en lugar de ejecutar cálculos, lo que produce tres tipos de hallucination:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Estadísticas fabricadas a partir de agregaciones adivinadas&lt;/li&gt;
&lt;li&gt;Recuperación incompleta cuando los datos están dispersos&lt;/li&gt;
&lt;li&gt;Fabricación fuera de dominio cuando no existen datos relevantes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; Los knowledge graphs proporcionan datos estructurados y verificables, donde las agregaciones son calculadas por la base de datos en lugar de ser adivinadas por los LLMs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Código de Comparación
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="c1"&gt;# RAG Agent — vector similarity search
&lt;/span&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_faqs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search hotel FAQs using vector similarity.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embed_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="c1"&gt;# Graph-RAG Agent — Cypher queries on knowledge graph
&lt;/span&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_knowledge_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cypher_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Execute a Cypher query against the hotel knowledge graph.
    Node labels: Hotel, Room, Amenity, Policy, Service
    Relationships: (Hotel)-[:HAS_ROOM]-&amp;gt;(Room), (Hotel)-[:OFFERS_AMENITY]-&amp;gt;(Amenity)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cypher_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No results found.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; results:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;rag_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_faqs&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_knowledge_graph&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Resultados de la Demo
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tipo de Consulta&lt;/th&gt;
&lt;th&gt;RAG&lt;/th&gt;
&lt;th&gt;Graph-RAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Agregación&lt;/strong&gt;: "¿Calificación promedio en París?"&lt;/td&gt;
&lt;td&gt;⚠️ Calcula solo con 2 documentos&lt;/td&gt;
&lt;td&gt;✅ AVG() nativo sobre todos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Conteo&lt;/strong&gt;: "¿Hoteles con piscina?"&lt;/td&gt;
&lt;td&gt;❌ "No tengo los datos"&lt;/td&gt;
&lt;td&gt;✅ Preciso: 133&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Multi-hop&lt;/strong&gt;: "¿Tipos de habitación del mejor hotel?"&lt;/td&gt;
&lt;td&gt;❌ No puede recorrer relaciones&lt;/td&gt;
&lt;td&gt;✅ Recorrido Hotel → Room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Fuera de dominio&lt;/strong&gt;: "Hoteles en la Antártida"&lt;/td&gt;
&lt;td&gt;❌ Fabrica respuestas&lt;/td&gt;
&lt;td&gt;✅ Honesto: "No hay hoteles"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Hallazgo Clave:&lt;/strong&gt; Graph-RAG reduce las hallucinations porque devuelve resultados vacíos en lugar de respuestas fabricadas cuando los datos no están disponibles.&lt;/p&gt;


&lt;h2&gt;
  
  
  Técnica 2: Selección Semántica de Tools para Elegir la Herramienta Correcta
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problema:&lt;/strong&gt; "Las hallucinations en tool-calling aumentan con la cantidad de tools." Cuando los agents tienen muchas tools similares, exhiben errores de selección de función, errores de idoneidad, errores de parámetros, errores de completitud y comportamiento de evasión de tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problema Dual:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Riesgo de hallucination: Más tools = más selecciones inapropiadas&lt;/li&gt;
&lt;li&gt;❌ Desperdicio de tokens: 31 tools ≈ 4,500 tokens por consulta&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Solución: Filtrado Semántico de Tools
&lt;/h3&gt;

&lt;p&gt;Filtra las tools &lt;em&gt;antes&lt;/em&gt; de que el agent las vea usando similitud vectorial.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;

&lt;span class="c1"&gt;# Build index once
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tool_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexFlatL2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;384&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_embeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Filter per query
&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;relevant_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Intercambio Dinámico de Tools con Strands Agents
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="c1"&gt;# Create agent once
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Swap tools per query without losing conversation history
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;relevant_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;relevant_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# agent.messages preserved
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Resultados en 29 Consultas de Viaje
&lt;/h3&gt;

&lt;p&gt;Las pruebas muestran mejoras significativas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;86.4% de reducción en errores de selección de tools&lt;/li&gt;
&lt;li&gt;89% de reducción en costos de tokens&lt;/li&gt;
&lt;li&gt;Precisión mantenida con menos tools visibles&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Técnica 3: Guardrails Neurosimbólicos para AI Agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problema:&lt;/strong&gt; Investigaciones demuestran que los agents generan hallucinations cuando las reglas de negocio se expresan únicamente en prompts de lenguaje natural. El prompt engineering no puede aplicar restricciones porque los prompts son sugerencias, no reglas ejecutables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; Usar hooks de Strands Agents para interceptar llamadas a tools antes de su ejecución y aplicar reglas simbólicas a nivel del framework.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementación
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;

&lt;span class="c1"&gt;# Define symbolic rules
&lt;/span&gt;&lt;span class="n"&gt;BOOKING_RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maximum 10 guests per booking&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Create validation hook
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NeurosymbolicHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BOOKING_RULES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Clean tool (no validation logic)
&lt;/span&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Book a hotel room.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: Booked &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guests&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Attach hook to agent
&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NeurosymbolicHook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Test
&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book hotel for 15 guests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ Hook blocks before tool executes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Por Qué los Hooks de Strands Destacan
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API sencilla:&lt;/strong&gt; Implementar HookProvider y registrar callbacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación centralizada:&lt;/strong&gt; Un solo hook valida todas las tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools limpias:&lt;/strong&gt; Sin lógica de validación mezclada con lógica de negocio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tipado seguro:&lt;/strong&gt; Objetos de eventos fuertemente tipados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El LLM no puede evadir:&lt;/strong&gt; Las reglas se aplican antes de la ejecución de la tool&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Efectividad
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Escenario&lt;/th&gt;
&lt;th&gt;Prompt Engineering&lt;/th&gt;
&lt;th&gt;Neurosimbólico con Hooks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parámetros Inválidos&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Acepta&lt;/td&gt;
&lt;td&gt;✅ Bloquea&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prerequisitos Faltantes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ A veces detecta&lt;/td&gt;
&lt;td&gt;✅ Siempre bloquea&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evasión de Reglas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Posible&lt;/td&gt;
&lt;td&gt;✅ Imposible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Técnica 4: Validación Multi-Agent para Detección de Errores
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problema:&lt;/strong&gt; Los agents individuales operan en aislamiento. Cuando generan hallucinations, no existe un mecanismo para detectar errores antes de que lleguen a los usuarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; Múltiples agents especializados se validan mutuamente mediante debate estructurado, utilizando Strands Swarm para transferencias autónomas con contexto compartido.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementación
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.multiagent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Swarm&lt;/span&gt;

&lt;span class="c1"&gt;# Define specialized agents
&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;executor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Execute requests, then hand off to validator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check for hallucinations. Say VALID or HALLUCINATION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;critic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;critic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Final review. Say APPROVED or REJECTED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create swarm - agents hand off autonomously
&lt;/span&gt;&lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Swarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;critic&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;entry_point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_handoffs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;swarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book grand_hotel for John&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Comparación de Rendimiento
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Detección de Hallucinations&lt;/th&gt;
&lt;th&gt;Precisión&lt;/th&gt;
&lt;th&gt;Latencia&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent Individual&lt;/td&gt;
&lt;td&gt;❌ Ninguna&lt;/td&gt;
&lt;td&gt;⚠️ Fabrica&lt;/td&gt;
&lt;td&gt;✅ Rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-Agent&lt;/td&gt;
&lt;td&gt;✅ Detecta errores&lt;/td&gt;
&lt;td&gt;✅ Valida&lt;/td&gt;
&lt;td&gt;⚠️ Más lento&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ejemplo:&lt;/strong&gt; Cuando el executor intenta reservar "the_ritz_paris" (no existe), el validator detecta el hotel inválido y el critic devuelve FAILED en lugar de fabricar una alternativa.&lt;/p&gt;


&lt;h2&gt;
  
  
  Combinando Técnicas para Producción
&lt;/h2&gt;

&lt;p&gt;Estas técnicas se complementan entre sí:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Graph-RAG&lt;/strong&gt; asegura la precisión de los datos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selección semántica de tools&lt;/strong&gt; reduce errores de tools y costos de tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reglas neurosimbólicas&lt;/strong&gt; aplican restricciones de negocio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación multi-agent&lt;/strong&gt; detecta las hallucinations restantes&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Por Qué Strands Agents Python SDK
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gestión dinámica de tools sin perder el estado de la conversación&lt;/li&gt;
&lt;li&gt;Multi-agent nativo con Swarm manejando transferencias&lt;/li&gt;
&lt;li&gt;Validación a nivel de tool ejecutándose antes de que el LLM vea los resultados&lt;/li&gt;
&lt;li&gt;Flexibilidad de modelos (Bedrock, OpenAI, Anthropic, Ollama)&lt;/li&gt;
&lt;li&gt;Listo para producción en despliegues AWS&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Conclusiones Clave
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Las hallucinations son inevitables — enfócate en detección y mitigación, no en eliminación&lt;/li&gt;
&lt;li&gt;Graph-RAG para precisión cuando necesitas cálculos exactos o relaciones&lt;/li&gt;
&lt;li&gt;Filtrado semántico esencial con más de 10 tools similares&lt;/li&gt;
&lt;li&gt;Las reglas simbólicas aplican cumplimiento de negocio donde el prompt engineering falla&lt;/li&gt;
&lt;li&gt;La validación multi-agent detecta errores que los agents individuales no captan&lt;/li&gt;
&lt;li&gt;Strands Agents permite despliegues en producción con tools dinámicas y soporte nativo multi-agent&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2509.09360" rel="noopener noreferrer"&gt;MetaRAG: Metamorphic Testing for Hallucination Detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2601.05214" rel="noopener noreferrer"&gt;Internal Representations as Indicators of Hallucinations in Agent Tool Selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2510.19507" rel="noopener noreferrer"&gt;Teaming LLMs to Detect and Mitigate Hallucinations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2503.13514" rel="noopener noreferrer"&gt;RAG-KG-IL: Multi-Agent Hybrid Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;br&gt;
&lt;a href="https://linktr.ee/elizabethfuentesleone" rel="noopener noreferrer"&gt;Linktr&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__717518"&gt;
    &lt;a href="/elizabethfuentes12" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F717518%2Fb550b165-b8b9-405d-acfb-e5dc846765b0.png" alt="elizabethfuentes12 image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;Elizabeth Fuentes L&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;I help developers build production-ready AI applications through hands-on tutorials and open-source projects.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>agents</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🤔AWS me está quitando el trabajo...😵</title>
      <dc:creator>Alex Rodríguez</dc:creator>
      <pubDate>Wed, 04 Mar 2026 12:36:21 +0000</pubDate>
      <link>https://dev.to/aws-espanol/aws-me-esta-quitando-el-trabajo-591f</link>
      <guid>https://dev.to/aws-espanol/aws-me-esta-quitando-el-trabajo-591f</guid>
      <description>&lt;h2&gt;
  
  
  Cómo desplegar ArgoCD en EKS con SSO sin gestionar un solo pod
&lt;/h2&gt;

&lt;p&gt;Después de bastante tiempo entre eventos, organizaciones y mucho trabajo, volvemos por aquí para hablar de uno de los últimos “avances” que AWS ha presentado.&lt;/p&gt;

&lt;p&gt;Con todo el tema de la  IA, parece que solo estemos hablando de modelos, agentes y prompts. Pero nada más lejos de la realidad, seguimos gestionando muchísima infraestructura, y AWS sigue lanzando cosas muy interesantes para simplificar esa parte. (menos mal, algún anuncio no relacionado con IA también nos gusta escuchar).&lt;/p&gt;

&lt;p&gt;Sin intentar que esto tuviese algún sentido, hemos terminado cerrando una rueda interesante alrededor de &lt;strong&gt;EKS, ArgoCD y IAM Identity Center&lt;/strong&gt; en nuestros posts de #awstwins. La verdad es que en este post he tirado un poco de &lt;strong&gt;clickbait&lt;/strong&gt;, a lo influencer, pero en realidad volvemos al barro y con lo de siempre: Kubernetes y automatización.&lt;/p&gt;

&lt;p&gt;En concreto, vamos a hablar de &lt;strong&gt;EKS Capabilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En posts anteriores como el de acontinuación instalábamos y gestionábamos Argo CD nosotros. Hoy toca darle otra vuelta. Que para eso estamos, ¿no?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-espanol/automatiza-el-despliegue-de-eks-y-argocd-con-terraform-3h93"&gt;argocd&amp;amp;terraform&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué es EKS Capabilities?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;EKS Capabilities&lt;/strong&gt; es una nueva feature de AWS que permite utilizar herramientas del ecosistema Kubernetes &lt;strong&gt;gestionadas directamente por AWS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Es decir,no tienes que instalar nada, mantener nada ni preocuparte de por qué algo está caído.&lt;/p&gt;

&lt;p&gt;Básicamente nuestro estilo de vida, no? &lt;/p&gt;

&lt;p&gt;Actualmente hay &lt;strong&gt;3 capabilities disponibles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ArgoCD&lt;/strong&gt;&lt;br&gt;
De este ya hemos hablado mucho en diferentes posts y es el que vamos a usar puesto que cerramos el circulo. Incluiremos la parte del SSO.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;KRO (Kubernetes Resource Orchestrator)&lt;/strong&gt;&lt;br&gt;
Permite crear recursos de forma declarativa agrupando varios recursos en uno solo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ACK (AWS Controllers for Kubernetes)&lt;/strong&gt;&lt;br&gt;
Permite gestionar recursos de AWS directamente desde Kubernetes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿La parte interesante de todo esto?&lt;/p&gt;

&lt;p&gt;Estas capabilities &lt;strong&gt;operan en el control plane de EKS&lt;/strong&gt;, no en tus nodos ni en tu cuenta.&lt;br&gt;
Resultado: &lt;strong&gt;menos componentes que mantener y menos cosas que se rompan.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  ¿Qué hemos desplegado?
&lt;/h2&gt;

&lt;p&gt;Para ir rápido y evitar mantener algo "custom", hemos hecho lo de siempre: usar &lt;strong&gt;módulos de la community&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En este caso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una &lt;strong&gt;VPC&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Un &lt;strong&gt;cluster EKS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Karpenter&lt;/strong&gt; para el autoscaling de nodos (no entraremos en detalle aquí)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si queréis ver más sobre Karpenter, mi hermano ya publicó un post sobre esto, que siempre podéis dejar un Like!💕:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-espanol/optimiza-tu-cluster-eks-con-karpenter-3mk1"&gt;karpenter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Volvamos a "Capabilities" que es lo que nos interesa. Tal y como decíamos, para esta PoC, podéis encontrar el módulo de &lt;strong&gt;EKS Capabilities&lt;/strong&gt; aquí con los ejemplos, ya sabéis "Copy &amp;amp; Paste" o si no, siempre podéis tirar de IA para un código rápido.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-capabilities" rel="noopener noreferrer"&gt;terraform-aws-modules&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  SSO con IAM Identity Center
&lt;/h2&gt;

&lt;p&gt;Una de las partes más interesantes de usar &lt;strong&gt;EKS Capabilities con ArgoCD&lt;/strong&gt; es que &lt;strong&gt;el SSO no requiere ninguna integración adicional&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No hay que tocar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ConfigMap&lt;/code&gt; de ArgoCD&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argocd-cm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;configuración de OIDC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simplemente le dices a AWS &lt;strong&gt;qué grupos de IAM Identity Center corresponden a qué roles dentro de ArgoCD&lt;/strong&gt;, y a funcionar.&lt;/p&gt;

&lt;p&gt;Ejemplo en Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"argocd_eks_capability"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/eks/aws//modules/capability"&lt;/span&gt;

  &lt;span class="nx"&gt;type&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ARGOCD"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;

  &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;argo_cd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;aws_idc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;idc_instance_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt;

      &lt;span class="nx"&gt;rbac_role_mapping&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ADMIN"&lt;/span&gt;

        &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="nx"&gt;id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_identitystore_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_administrator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group_id&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SSO_GROUP"&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En mi caso, para ir rápido con la &lt;strong&gt;PoC&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;He usado mi grupo &lt;strong&gt;Admin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;He desplegado el cluster &lt;strong&gt;en la misma cuenta donde tengo IAM Identity Center&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si queréis hacerlo &lt;strong&gt;cross-account&lt;/strong&gt;, tendrás que crear un &lt;strong&gt;Role&lt;/strong&gt; que permita a Terraform leer los grupos de Identity Center.&lt;/p&gt;

&lt;p&gt;Dicho esto, vamos a la práctica de cómo quedaría esta parte.&lt;/p&gt;

&lt;p&gt;Una vez desplegado nuestro cluster con todos los requisitos, podemos observar que tenemos argocd funcionando en nuestra pestaña de Capabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgke8h4spxi6iu577bw0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgke8h4spxi6iu577bw0t.png" alt="Capabilities" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nos conectamos a nuestro endpoint (Argo API endpoint).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij2f6iecme95kctgsrfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij2f6iecme95kctgsrfr.png" alt="Endpoint" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como hemos comentado, la integración con SSO es super rápida, solo indica qué roles quieres usar y el mapping y listo!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly39x8otgqrsdwitgtqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly39x8otgqrsdwitgtqo.png" alt="SSO-boton" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Introducimos nuestras credenciales de AWS.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c6876lvpoamv4i8oqwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c6876lvpoamv4i8oqwn.png" alt="Credenciales" width="643" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y ya tenemos argo cd funcionando.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hm1u2u4n9w72kn1nb0t.png" alt="Argo-UI" width="800" height="250"&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Lo mejor de todo
&lt;/h2&gt;

&lt;p&gt;Cuando lances el código:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se desplegará &lt;strong&gt;ArgoCD como capability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tendrás &lt;strong&gt;SSO funcionando&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Podrás loguearte directamente&lt;/li&gt;
&lt;li&gt;Y empezar a desplegar tus aplicaciones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y ahora viene lo mejor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ktwweizimcejau6rtih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ktwweizimcejau6rtih.png" alt="kubectl" width="620" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No verás ni un solo pod de ArgoCD.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Porque &lt;strong&gt;ArgoCD está ejecutándose en el control plane de EKS&lt;/strong&gt;, no dentro de tu cluster.&lt;/p&gt;

&lt;p&gt;Llevamos varios posts hablando de ArgoCD, cual es el mejor diseño, como instalarlo, como configurarlo, como gestionarlo y de repente llega un update de AWS y nos dice y si lo hago por ti? Encima con SSO de serie que más podemos pedir? 😁&lt;/p&gt;

&lt;p&gt;Cabe remarcar como siempre que el objetivo es simplemente una PoC, para PROD deberíamos de revisar el tema de dominio privado, clusters adicionales, AppProjects por equipo... pero de eso ya hemos hablado en varias otros posts que podéis recuperar desde #AWTwinS.&lt;/p&gt;

&lt;p&gt;Como siempre, cualquier feedback es bienvenido! o por aquí, por LinkedIn o donde podáis encontrarnos! Nos vemos por los eventos! &lt;/p&gt;

</description>
      <category>aws</category>
      <category>argocd</category>
      <category>awstwins</category>
      <category>eks</category>
    </item>
    <item>
      <title>Crea tu asistente personal en los Meta Glasses.</title>
      <dc:creator>Fernando Silva T</dc:creator>
      <pubDate>Fri, 27 Feb 2026 22:56:03 +0000</pubDate>
      <link>https://dev.to/aws-espanol/crea-tu-asistente-personal-en-los-meta-glasses-4nen</link>
      <guid>https://dev.to/aws-espanol/crea-tu-asistente-personal-en-los-meta-glasses-4nen</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__1774484"&gt;
    &lt;a href="/fernandosilvot" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1774484%2Fc44b2c8a-ceeb-4ac9-8947-bdffe7048292.jpeg" alt="fernandosilvot image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/fernandosilvot"&gt;Fernando Silva T&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/fernandosilvot"&gt;AWS Community Builder | Estudiante de Ingeniería Informática en Duoc UC. Apasionado por AWS, desarrollo Full-Stack y proyectos open source. Siempre buscando aprender y colaborar.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;br&gt;
Implementación de un asistente de IA con &lt;strong&gt;entrada de voz y visión&lt;/strong&gt; para lentes inteligentes usando Android, &lt;strong&gt;&lt;a href="https://aws.amazon.com/es/bedrock/" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;&lt;/strong&gt; y &lt;strong&gt;&lt;a href="https://aws.amazon.com/es/blogs/aws/introducing-claude-sonnet-4-5-in-amazon-bedrock-anthropics-most-intelligent-model-best-for-coding-and-complex-agents/" rel="noopener noreferrer"&gt;Claude Sonnet 4.5&lt;/a&gt;&lt;/strong&gt;, sin dependencia de la IA nativa de Meta ni restricciones geográficas.

&lt;p&gt;Este proyecto aborda cuatro problemas técnicos concretos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wake word detection sin librerías externas
&lt;/li&gt;
&lt;li&gt;Conversión de frames del SDK de Meta a formatos compatibles con Bedrock
&lt;/li&gt;
&lt;li&gt;Uso de la Converse API con input multimodal (imagen + texto)
&lt;/li&gt;
&lt;li&gt;Coordinación de flujos asíncronos en Jetpack Compose
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Kotlin · Jetpack Compose · Meta Wearables DAT SDK · Amazon Bedrock&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Nivel:&lt;/strong&gt; Intermedio&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Repositorio:&lt;/strong&gt; &lt;a href="https://github.com/fernandosilvot/aws-bedrock-meta-glasses" rel="noopener noreferrer"&gt;https://github.com/fernandosilvot/aws-bedrock-meta-glasses&lt;/a&gt;  &lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Todo comenzó con una conversación casual. &lt;a href="https://dev.to/elizabethfuentes12"&gt;Elizabeth Fuentes&lt;/a&gt; me comentó sobre el &lt;strong&gt;&lt;a href="https://wearables.developer.meta.com/docs/reference/android/dat/0.4" rel="noopener noreferrer"&gt;Meta Wearables DAT SDK&lt;/a&gt;&lt;/strong&gt; — una API que Meta había liberado para desarrolladores que permitía integrar funcionalidades personalizadas a los &lt;strong&gt;Meta Smart Glasses&lt;/strong&gt;, más allá de la IA nativa de Meta.&lt;/p&gt;

&lt;p&gt;La idea me fascinó de inmediato: ¿Y si pudiera conectar estos lentes con &lt;strong&gt;Amazon Bedrock&lt;/strong&gt; y usar &lt;strong&gt;Claude Sonnet 4.5&lt;/strong&gt; en lugar de la IA de Meta? ¿Podría crear un asistente que no solo escuchara, sino que también &lt;strong&gt;viera&lt;/strong&gt; lo que yo veo?&lt;/p&gt;

&lt;p&gt;El resultado es &lt;strong&gt;Meta-Rock&lt;/strong&gt;, un asistente activado por voz que captura el frame actual de la cámara, lo envía junto al texto de la consulta y reproduce la respuesta mediante TTS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcmckw6sdd8xexmv7yxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcmckw6sdd8xexmv7yxy.png" alt="Pantalla Principal"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  El problema y la visión
&lt;/h2&gt;

&lt;p&gt;Los &lt;strong&gt;Meta Ray-Ban Smart Glasses&lt;/strong&gt; vienen con IA integrada de Meta, pero tienen una limitación importante: &lt;strong&gt;solo está disponible en ciertos países&lt;/strong&gt;. En Chile, donde vivo, no puedo acceder a esa funcionalidad nativa.&lt;/p&gt;

&lt;p&gt;Además, incluso si estuviera disponible, quería explorar las posibilidades de integrar modelos de IA más avanzados y personalizables. ¿Qué podría lograr si conectara estos lentes directamente con &lt;strong&gt;Amazon Bedrock&lt;/strong&gt; y &lt;strong&gt;Claude Sonnet 4.5&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Objetivo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Construir un asistente hands-free que combine audio y visión, delegando toda la inferencia a Bedrock.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Arquitectura
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Flujo de datos
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfs6k844cu4s2pa3os97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfs6k844cu4s2pa3os97.png" alt="Flujo de datos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pipeline resumido:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wake word → &lt;code&gt;SpeechRecognizer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Captura de frame desde los lentes
&lt;/li&gt;
&lt;li&gt;Conversión I420 → JPEG
&lt;/li&gt;
&lt;li&gt;Request multimodal a Bedrock
&lt;/li&gt;
&lt;li&gt;Respuesta → Text-to-Speech
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Stack tecnológico
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capa&lt;/th&gt;
&lt;th&gt;Tecnología&lt;/th&gt;
&lt;th&gt;Justificación&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Jetpack Compose&lt;/td&gt;
&lt;td&gt;Estado reactivo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voz&lt;/td&gt;
&lt;td&gt;Android SpeechRecognizer&lt;/td&gt;
&lt;td&gt;API nativa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;Meta Wearables DAT SDK&lt;/td&gt;
&lt;td&gt;Acceso directo a cámara&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IA&lt;/td&gt;
&lt;td&gt;Amazon Bedrock&lt;/td&gt;
&lt;td&gt;Inferencia multimodal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modelo&lt;/td&gt;
&lt;td&gt;Claude Sonnet 4.5&lt;/td&gt;
&lt;td&gt;Razonamiento visual sólido&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Demostración en video
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Meta-Rock en ejecución
&lt;/h3&gt;

&lt;p&gt;El video muestra el flujo completo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activación por wake word
&lt;/li&gt;
&lt;li&gt;Captura del frame desde los lentes
&lt;/li&gt;
&lt;li&gt;Envío multimodal a Bedrock
&lt;/li&gt;
&lt;li&gt;Respuesta hablada
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📽️ &lt;strong&gt;Video de demostración:&lt;/strong&gt;  &lt;/p&gt;






&lt;h2&gt;
  
  
  Desafíos técnicos y decisiones de diseño
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Wake word detection sin librerías externas
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Decisión:&lt;/strong&gt; usar &lt;code&gt;SpeechRecognizer&lt;/code&gt; en escucha pasiva.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Motivación:&lt;/strong&gt; evitar dependencias comerciales y mantener el stack 100% nativo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Falsos positivos en ambientes ruidosos
&lt;/li&gt;
&lt;li&gt;Mayor consumo de batería
&lt;/li&gt;
&lt;li&gt;Latencia aproximada de 500 ms
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpeechManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onWakeWord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onTranscript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;speechRecognizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpeechRecognizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSpeechRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;startPassiveListening&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RecognizerIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_RECOGNIZE_SPEECH&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RecognizerIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXTRA_LANGUAGE_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nc"&gt;RecognizerIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LANGUAGE_MODEL_FREE_FORM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RecognizerIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXTRA_PARTIAL_RESULTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;speechRecognizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startListening&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;checkForWakeWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;normalized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lowercase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hey friday"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oye viernes"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onWakeWord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Aceptable para prototipo funcional y pruebas reales.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Envío constante de contexto visual
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Decisión clave:&lt;/strong&gt; enviar siempre el frame de la cámara junto al texto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Motivación técnica:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clasificadores por palabras clave son frágiles
&lt;/li&gt;
&lt;li&gt;Consultas ambiguas requieren visión (“¿y esto?”)
&lt;/li&gt;
&lt;li&gt;El modelo puede ignorar la imagen si no es relevante
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onTranscriptReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Transcript: $transcript"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentFrameProvider&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Siempre captura el frame&lt;/span&gt;

    &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NovaState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROCESSING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;sentWithImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;processingJob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;systemPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bedrockClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;askWithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bedrockClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;askTextOnly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Costo:&lt;/strong&gt; mayor consumo de tokens y ancho de banda&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Beneficio:&lt;/strong&gt; lógica simple y UX consistente&lt;/p&gt;


&lt;h3&gt;
  
  
  3. Conversión de formatos de video
&lt;/h3&gt;

&lt;p&gt;El SDK de Meta entrega frames en formato &lt;strong&gt;I420 (YUV)&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Bedrock requiere imágenes codificadas (JPEG).&lt;/p&gt;

&lt;p&gt;Pipeline implementado:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I420 → NV21 → Bitmap → JPEG (85%)&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;i420ToNV21&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i420&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;frameSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nv21&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frameSize&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Copiar Y&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arraycopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nv21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Intercalar U y V para NV21&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;uvIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;uIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;vIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt; &lt;span class="n"&gt;frameSize&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;nv21&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uvIndex&lt;/span&gt;&lt;span class="p"&gt;++]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i420&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vIndex&lt;/span&gt;&lt;span class="p"&gt;++]&lt;/span&gt;
        &lt;span class="n"&gt;nv21&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uvIndex&lt;/span&gt;&lt;span class="p"&gt;++]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i420&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uIndex&lt;/span&gt;&lt;span class="p"&gt;++]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nv21&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bitmapToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bitmap&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CompressFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JPEG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toByteArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El 85% de compresión mantiene legibilidad de texto con menor latencia.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Integración con Bedrock (Converse API)
&lt;/h3&gt;

&lt;p&gt;Se utiliza una única llamada con input multimodal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Texto del usuario
&lt;/li&gt;
&lt;li&gt;Imagen capturada
&lt;/li&gt;
&lt;li&gt;System prompt dependiente del idioma
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BedrockClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockRuntimeClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us-east-1"&lt;/span&gt;
        &lt;span class="n"&gt;credentialsProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StaticCredentialsProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;secretAccessKey&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;askWithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;imageBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bitmapToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConverseRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;modelId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-5-20250929-v1:0"&lt;/span&gt;
            &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SystemContentBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConversationRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;
                    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="nc"&gt;ImageBlock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImageFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Jpeg&lt;/span&gt;
                                &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImageSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nc"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;inferenceConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;maxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
                &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7F&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asMessage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;asText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La inferencia se realiza directamente desde Android usando el SDK oficial de AWS para Kotlin.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Gestión de estado en la UI
&lt;/h3&gt;

&lt;p&gt;Se definen estados explícitos para coordinar audio, video e inferencia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NovaState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Esperando "Hey Viernes"&lt;/span&gt;
    &lt;span class="nc"&gt;LISTENING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Escuchando pregunta&lt;/span&gt;
    &lt;span class="nc"&gt;PROCESSING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Enviando a Bedrock&lt;/span&gt;
    &lt;span class="nc"&gt;RESPONDING&lt;/span&gt;      &lt;span class="c1"&gt;// Mostrando/leyendo respuesta&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;NovaUiState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NovaState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NovaState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sentWithImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto reduce errores visuales y facilita el debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Casos de uso validados
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Identificación visual (objetos, colores, texto)
&lt;/li&gt;
&lt;li&gt;Lectura de pantallas
&lt;/li&gt;
&lt;li&gt;Consultas generales con o sin contexto visual
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El modelo decide dinámicamente si usar la imagen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resultados
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Latencia end-to-end: 2–3 segundos
&lt;/li&gt;
&lt;li&gt;Precisión wake word: ~95% en ambientes silenciosos
&lt;/li&gt;
&lt;li&gt;Consumo de batería: ~15% por hora
&lt;/li&gt;
&lt;li&gt;Tamaño del APK: 12.4 MB
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lecciones técnicas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Optimizar demasiado temprano complica la arquitectura
&lt;/li&gt;
&lt;li&gt;JPEG al 85% es un buen punto de equilibrio
&lt;/li&gt;
&lt;li&gt;Wake word sin SDK dedicado tiene límites claros
&lt;/li&gt;
&lt;li&gt;Claude maneja bien inputs multimodales ruidosos
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Próximos pasos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clasificador local ligero para decidir envío de imagen
&lt;/li&gt;
&lt;li&gt;Optimización de consumo energético
&lt;/li&gt;
&lt;li&gt;Nuevos flujos de análisis visual continuo
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Construir Meta-Rock fue un desafío técnico fascinante que surgió de una necesidad real: &lt;strong&gt;acceder a funcionalidades de IA en los Meta Ray-Ban que no están disponibles en mi región&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pero más allá de resolver esa limitación, este proyecto me permitió explorar las posibilidades de integrar wearables con modelos de IA de última generación. ¿Qué más se puede lograr cuando tienes acceso directo a la cámara de unos lentes inteligentes y a modelos como Claude Sonnet 4.5?&lt;/p&gt;

&lt;p&gt;Lo más emocionante es que esto es solo el comienzo. Planeo seguir mejorando Meta-Rock, explorando nuevas funcionalidades y documentando cada avance en futuros blogs. Desde análisis de escenas en tiempo real hasta traducción visual instantánea, las posibilidades son infinitas.&lt;/p&gt;

&lt;p&gt;Este proyecto me enseñó que las limitaciones geográficas o de plataforma no tienen por qué detenerte. Con las herramientas adecuadas (AWS, SDKs abiertos, creatividad), puedes construir soluciones que no solo resuelven tus necesidades, sino que abren puertas a experiencias completamente nuevas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio GitHub:&lt;/strong&gt; &lt;a href="https://github.com/fernandosilvot/aws-bedrock-meta-glasses" rel="noopener noreferrer"&gt;aws-bedrock-meta-glasses&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo original de Meta:&lt;/strong&gt; &lt;a href="https://github.com/facebook/meta-wearables-dat-android/tree/main/samples/CameraAccess" rel="noopener noreferrer"&gt;CameraAccess Sample&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta Wearables SDK:&lt;/strong&gt; &lt;a href="https://wearables.developer.meta.com/docs/reference/android/dat/0.4" rel="noopener noreferrer"&gt;Documentación oficial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Bedrock:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html" rel="noopener noreferrer"&gt;Converse API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Sonnet 4.5:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html" rel="noopener noreferrer"&gt;Modelo en Bedrock&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Agradecimientos
&lt;/h2&gt;

&lt;p&gt;Este proyecto no hubiera sido posible sin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/elizabethfuentes12"&gt;Elizabeth Fuentes&lt;/a&gt;&lt;/strong&gt; por compartir el descubrimiento del Meta Wearables DAT SDK y motivarme a explorar esta tecnología&lt;/li&gt;
&lt;li&gt;El equipo de &lt;strong&gt;Meta&lt;/strong&gt; por abrir el SDK de los Ray-Ban a la comunidad de desarrolladores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS&lt;/strong&gt; por hacer Bedrock accesible y proporcionar modelos de IA de clase mundial&lt;/li&gt;
&lt;li&gt;La comunidad de &lt;strong&gt;AWS Community Builders&lt;/strong&gt; por el apoyo constante y el intercambio de conocimientos&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;¿Tienes preguntas o ideas para mejorar Meta-Rock?&lt;/strong&gt; Déjalas en los comentarios o contáctame en &lt;a href="https://fernandosilvot.cl" rel="noopener noreferrer"&gt;fernandosilvot.cl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Made with ❤️ in Chile 🇨🇱&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>android</category>
      <category>meta</category>
    </item>
    <item>
      <title>⚙️ Control PID con AWS Lambda Durable Functions: Simulando un PID y un reactor ⚗️ con estado persistente ☁️</title>
      <dc:creator>olcortesb</dc:creator>
      <pubDate>Tue, 10 Feb 2026 09:04:24 +0000</pubDate>
      <link>https://dev.to/aws-espanol/control-pid-con-aws-lambda-durable-functions-simulando-un-pid-y-un-reactor-con-estado-25op</link>
      <guid>https://dev.to/aws-espanol/control-pid-con-aws-lambda-durable-functions-simulando-un-pid-y-un-reactor-con-estado-25op</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Recordando conceptos básicos de mis años de Ing. Electrónica y cómo ha evolucionado, pensaba en cómo sería la implementación de un controlador PID completamente cloud, al menos a modo práctico, en la operativa posiblemente no sea aplicable a todos los sistemas de control. Los controladores PID (Proporcional-Integral-Derivativo) tradicionalmente requieren ejecución continua, estado persistente y ciclos de control precisos. ¿Cómo implementar esto en un entorno serverless donde las funciones son efímeras por naturaleza?&lt;/p&gt;

&lt;p&gt;En este artículo presento una prueba de concepto que combina &lt;strong&gt;control PID&lt;/strong&gt; con &lt;strong&gt;AWS Lambda Durable Functions&lt;/strong&gt;, demostrando cómo crear sistemas de control de larga duración sin "pagar" por tiempo de espera entre iteraciones. La implementación simula un reactor con control de temperatura, utilizando las nuevas capacidades de Lambda para workflows con estado persistente.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 ¿Qué es un Controlador PID?
&lt;/h2&gt;

&lt;p&gt;Un controlador PID es un mecanismo de control por retroalimentación ampliamente utilizado en sistemas industriales. Calcula una señal de control basándose en tres elementos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proporcional (P)&lt;/strong&gt;: Responde proporcionalmente al error actual entre el setpoint (SP) y el valor del proceso (PV). Cuanto mayor es el error, mayor es la acción correctiva&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integral (I)&lt;/strong&gt;: Corrige el error acumulado en el tiempo, eliminando el error residual en estado estacionario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derivativo (D)&lt;/strong&gt;: Anticipa cambios futuros basándose en la tasa de cambio del error, proporcionando amortiguación al sistema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La fórmula básica del PID es:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CV(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Donde:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CV&lt;/code&gt;: Control Variable o salida del controlador normalmente en porcentaje (0-100%)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e(t)&lt;/code&gt;: Error = SP - PV (Setpoint - Process Variable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SP&lt;/code&gt;: Setpoint o valor deseado&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PV&lt;/code&gt;: Process Variable o valor actual medido&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Kp, Ki, Kd&lt;/code&gt;: Constantes de ajuste o ganancias del controlador&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tipos de respuesta:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La relación entre las constantes y su aplicación sobre distintos sistemas puede producir tres tipos de respuestas conocidas: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subamortiguada (underdamped)&lt;/strong&gt;: Oscila antes de estabilizarse (overshoot + oscilaciones)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Críticamente amortiguada (critically damped)&lt;/strong&gt;: Converge lo más rápido posible sin overshoot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sobreamortiguada (overdamped)&lt;/strong&gt;: Converge lentamente sin overshoot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 ¿Qué son AWS Lambda Durable Functions?
&lt;/h2&gt;

&lt;p&gt;AWS Lambda Durable Functions es una característica nativa que permite crear workflows de larga duración con estado persistente. A diferencia de las funciones Lambda tradicionales, las Durable Functions pueden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suspenderse sin costo&lt;/strong&gt;: &lt;code&gt;context.wait()&lt;/code&gt; pausa la ejecución sin cargos de cómputo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mantener estado&lt;/strong&gt;: Checkpointing automático en cada step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recuperarse de fallos&lt;/strong&gt;: Reinicia desde el último checkpoint exitoso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ejecutar por horas/días&lt;/strong&gt;: Sin mantener la Lambda activa continuamente&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Referencia oficial&lt;/strong&gt;: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started.html" rel="noopener noreferrer"&gt;AWS Lambda Durable Functions&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Primitivas Core del SDK
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_durable_execution_sdk_python&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DurableContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;durable_execution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;durable_step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_durable_execution_sdk_python.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;

&lt;span class="nd"&gt;@durable_step&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Lógica con checkpointing automático
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="nd"&gt;@durable_execution&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DurableContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;my_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# SIN COSTO
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🏗️ Arquitectura de la POC
&lt;/h2&gt;

&lt;p&gt;Bueno, si has llegado leyendo hasta aquí ya es hora de jugarnos, construir e ir a la sustancia. Vamos a implementar una POC que nos permita simular en Lambda Durable Functions tanto un PID como un reactor, persistiendo sus estados internamente y gestionando el flujo con las primitivas del SDK.&lt;/p&gt;

&lt;p&gt;El sistema sería el que se muestra en la imagen a continuación.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30d8dnmijx5h8w41pme4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30d8dnmijx5h8w41pme4.png" alt="Arquitectura con Lambda Durable Functions" width="800" height="741"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bien, ahora, si tuviéramos que simular esto con la versión tradicional de Lambda, necesitaríamos un par de SQS al menos para gestionar el estado y la invocación de las lambdas. La implementación que proponemos sigue este flujo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk18seccchfv99ki06qet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk18seccchfv99ki06qet.png" alt="Arquitectura tradicional con SQS" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Componentes principales:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: Recibe el setpoint deseado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID Controller Lambda&lt;/strong&gt;: Ejecuta el loop de control con estado persistente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactor Simulator Lambda&lt;/strong&gt;: Simula el comportamiento físico del reactor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Metrics&lt;/strong&gt;: Almacena métricas para visualización&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ Implementación
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lambda PID Controller
&lt;/h3&gt;

&lt;p&gt;El controlador implementa el algoritmo PID completo usando Durable Functions. El código completo está disponible en el repositorio:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📁 Código fuente:&lt;/strong&gt; &lt;a href="https://github.com/olcortesb/PID-control-with-lambda-durable/blob/main/terraform/src/pid_controller/app.py" rel="noopener noreferrer"&gt;&lt;code&gt;terraform/src/pid_controller/app.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Componentes principales:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_step calculate_pid()&lt;/code&gt;&lt;/strong&gt;: Implementa el algoritmo PID en forma paralela discreta&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_step invoke_reactor()&lt;/code&gt;&lt;/strong&gt;: Invoca la Lambda del reactor para obtener la nueva temperatura&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_step publish_metrics()&lt;/code&gt;&lt;/strong&gt;: Publica métricas a CloudWatch (SetpointTemperature, ActualTemperature, TemperatureError)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_execution lambda_handler()&lt;/code&gt;&lt;/strong&gt;: Orquesta el loop de control con &lt;code&gt;context.step()&lt;/code&gt; y &lt;code&gt;context.wait()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ecuación PID implementada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;setpoint&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;current_temp&lt;/span&gt;
&lt;span class="n"&gt;integral&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_TIME&lt;/span&gt;
&lt;span class="n"&gt;derivative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_TIME&lt;/span&gt;
&lt;span class="n"&gt;cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;KI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;integral&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;KD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;derivative&lt;/span&gt;
&lt;span class="n"&gt;cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Limitar entre 0-100
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lambda Reactor Simulator
&lt;/h3&gt;

&lt;p&gt;El simulador implementa un modelo físico simplificado del reactor. El código completo está disponible en el repositorio:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📁 Código fuente:&lt;/strong&gt; &lt;a href="https://github.com/olcortesb/PID-control-with-lambda-durable/blob/main/terraform/src/reactor_simulator/app.py" rel="noopener noreferrer"&gt;&lt;code&gt;terraform/src/reactor_simulator/app.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Componentes principales:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_step simulate_reactor_step()&lt;/code&gt;&lt;/strong&gt;: Simula el comportamiento térmico del reactor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@durable_execution lambda_handler()&lt;/code&gt;&lt;/strong&gt;: Recibe el control value y retorna la nueva temperatura&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Física del reactor implementada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Enfriamiento natural (Ley de enfriamiento de Newton)
&lt;/span&gt;&lt;span class="n"&gt;cooling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_temp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;AMBIENT_TEMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;COOLING_RATE&lt;/span&gt;

&lt;span class="c1"&gt;# Calentamiento por control value (0-100)
&lt;/span&gt;&lt;span class="n"&gt;heating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control_value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;HEATING_EFFICIENCY&lt;/span&gt;

&lt;span class="c1"&gt;# Cambio de temperatura con inercia térmica
&lt;/span&gt;&lt;span class="n"&gt;temp_change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heating&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cooling&lt;/span&gt;
&lt;span class="n"&gt;new_temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_temp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;temp_change&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;THERMAL_INERTIA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Despliegue con Terraform
&lt;/h2&gt;

&lt;p&gt;Inicialmente el despliegue lo realizaría con AWS SAM, sin embargo encontré algunos issues con la integración del SDK con AWS SAM. La alternativa era agregarle una lambda layer, pero no quería probar Lambda Durable fuera de la configuración más básica, por lo tanto me mudé a Terraform para el despliegue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuración de Lambda Durable
&lt;/h3&gt;

&lt;p&gt;En Terraform, las funciones durable requieren configuración específica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"pid_controller"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_path&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-pid-controller"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app.lambda_handler"&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.13"&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;
  &lt;span class="nx"&gt;memory_size&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;
  &lt;span class="nx"&gt;publish&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;durable_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;execution_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;  &lt;span class="c1"&gt;# 1 hora&lt;/span&gt;
    &lt;span class="nx"&gt;retention_period&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;     &lt;span class="c1"&gt;# 7 días&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;logging_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;log_format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JSON"&lt;/span&gt;
    &lt;span class="nx"&gt;log_group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_log_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;REACTOR_FUNCTION_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_lambda_function.reactor_simulator.function_name}:prod"&lt;/span&gt;
      &lt;span class="nx"&gt;KP&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.50"&lt;/span&gt;
      &lt;span class="nx"&gt;KI&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0004"&lt;/span&gt;
      &lt;span class="nx"&gt;KD&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.20"&lt;/span&gt;
      &lt;span class="nx"&gt;SAMPLE_TIME&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"60"&lt;/span&gt;
      &lt;span class="nx"&gt;MAX_ITERATIONS&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"40"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IAM Policy para Durable Functions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"pid_controller_durable"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧪 Pruebas y Simulación
&lt;/h2&gt;

&lt;p&gt;Cómo lo probamos: dejamos un valor esperado para que el PID use como referencia realizando un CURL al API Gateway y empezamos a ver cómo se comporta el sistema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iniciar Control PID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://xxxxx.execute-api.us-east-1.amazonaws.com/prod/setpoint &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"setpoint": 75.0}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respuesta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PID control completed - 40 iterations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"final_temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;74.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"setpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;75.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iterations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ¿Cómo se ven los Steps?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzfbaqs8eirsuhsun4c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzfbaqs8eirsuhsun4c8.png" alt="Ejecuciones Lambda Durable" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una nueva pestaña (1) nos mostrará cómo se ven las ejecuciones de toda la Lambda Durable Function, indicando qué versión (2) está corriendo, indicando un ID (3) y el estado de la ejecución (4).&lt;/p&gt;

&lt;h3&gt;
  
  
  La relación de las máquinas de estado de las dos lambdas
&lt;/h3&gt;

&lt;p&gt;Una de las features más interesantes es que podemos aprovechar la persistencia del estado evitando colocar SQS, DynamoDB, S3 dependiendo del caso, y ahora podemos llamar a otra función directamente gracias a la gestión de la máquina de estado. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yooxrxenp1disp4zhbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yooxrxenp1disp4zhbq.png" alt="Interacciones entre steps y waits" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la imagen arriba, 1 y 2 muestran las interacciones entre los steps y los waits, que indicamos antes en el presente artículo. Lo interesante es que se ve claramente el invoke_reactor que para nuestra simulación es invocar a otra Lambda Durable Function que permitirá simular el estado del reactor, guardando la temperatura y la inercia térmica del mismo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqfsuozqplfvxwzdu72lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqfsuozqplfvxwzdu72lm.png" alt="Ejecución del reactor simulator" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ya dentro de la Lambda del reactor podemos ver cómo se ejecutan los pasos que recalculan el comportamiento del reactor respecto a la temperatura, simulando el comportamiento real de un reactor.&lt;/p&gt;

&lt;h3&gt;
  
  
  La respuesta final:
&lt;/h3&gt;

&lt;p&gt;Para visualizar de manera aproximada cómo se estudian las gráficas de los sistemas PID, creé un dashboard que pueden consultar en el código fuente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/olcortesb/PID-control-with-lambda-durable/blob/main/terraform/dashboard.tf" rel="noopener noreferrer"&gt;Código en Terraform del dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vfwf88297ilbz5v8cj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vfwf88297ilbz5v8cj5.png" alt="Dashboard CloudWatch" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los valores configurados son para obtener una respuesta críticamente amortiguada que sería el objetivo a perseguir para un sistema de control tipo PID. Los valores los encontré realizando una simulación en Python que muestro a continuación. &lt;/p&gt;

&lt;h3&gt;
  
  
  Simulación Local
&lt;/h3&gt;

&lt;p&gt;Sí, esto podría ser otro post, pero he disfrutado tanto haciendo este artículo que lo dejo por aquí. Como quería refrescar cómo funcionaba el PID, me creé un proyecto que es básicamente un simulador Python para probar diferentes configuraciones de PID. En base a esta simulación encontré los valores que colocamos al sistema simulado en la POC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;simulation

&lt;span class="c"&gt;# Configurar parámetros en .env&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
KP=0.50
KI=0.0004
KD=0.20
SETPOINT=75.0
SAMPLE_TIME=30
MAX_ITERATIONS=40
THERMAL_INERTIA=0.18
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Ejecutar simulación&lt;/span&gt;
python simulate_pid.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resultados de Simulación
&lt;/h3&gt;

&lt;p&gt;El simulador genera gráficas mostrando el comportamiento del sistema con diferentes configuraciones de PID. Todas las simulaciones utilizan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setpoint&lt;/strong&gt;: 75°C&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperatura inicial&lt;/strong&gt;: 20°C&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample time&lt;/strong&gt;: 30 segundos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iteraciones&lt;/strong&gt;: 40&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inercia térmica&lt;/strong&gt;: 0.18&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Configuración 1: Kp=0.20, Ki=0.0001, Kd=0.30 (Sobreamortiguada)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp0.20_ki0.0001_kd0.30_ti0.18_st30_iter40_sp75.0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp0.20_ki0.0001_kd0.30_ti0.18_st30_iter40_sp75.0.png" alt="Respuesta Sobreamortiguada" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Características observadas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convergencia lenta y suave sin overshoot&lt;/li&gt;
&lt;li&gt;Tiempo de estabilización: ~20 minutos&lt;/li&gt;
&lt;li&gt;Temperatura final: ~74.5°C&lt;/li&gt;
&lt;li&gt;Error final: ~0.5°C&lt;/li&gt;
&lt;li&gt;Control Value máximo: ~15%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamiento&lt;/strong&gt;: Sistema muy conservador, ideal cuando no se permiten sobrepasadas&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Configuración 2: Kp=0.50, Ki=0.0004, Kd=0.20 (Críticamente amortiguada)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp0.50_ki0.0004_kd0.20_ti0.18_st30_iter40_sp75.0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp0.50_ki0.0004_kd0.20_ti0.18_st30_iter40_sp75.0.png" alt="Respuesta Críticamente Amortiguada" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Características observadas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convergencia rápida sin overshoot significativo&lt;/li&gt;
&lt;li&gt;Tiempo de estabilización: ~15 minutos&lt;/li&gt;
&lt;li&gt;Temperatura final: ~74.8°C&lt;/li&gt;
&lt;li&gt;Error final: ~0.2°C&lt;/li&gt;
&lt;li&gt;Control Value máximo: ~30%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamiento&lt;/strong&gt;: Balance óptimo entre velocidad y estabilidad&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Configuración 3: Kp=1.2, Ki=0.002, Kd=0.1 (Subamortiguada)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp1.2_ki0.002_kd0.1_ti0.18_st30_iter40_sp75.0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Folcortesb%2Fpost-article-backup%2Frefs%2Fheads%2Fmain%2Fimages%2Fpid_kp1.2_ki0.002_kd0.1_ti0.18_st30_iter40_sp75.0.png" alt="Respuesta Subamortiguada" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Características observadas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convergencia muy rápida con overshoot&lt;/li&gt;
&lt;li&gt;Overshoot máximo: ~2°C (alcanza ~77°C)&lt;/li&gt;
&lt;li&gt;Oscilaciones visibles antes de estabilizar&lt;/li&gt;
&lt;li&gt;Tiempo de estabilización: ~12 minutos&lt;/li&gt;
&lt;li&gt;Temperatura final: ~75.0°C&lt;/li&gt;
&lt;li&gt;Error final: ~0.05°C&lt;/li&gt;
&lt;li&gt;Control Value máximo: ~70%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamiento&lt;/strong&gt;: Sistema agresivo, respuesta rápida pero con oscilaciones&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📊 Verificación de Resultados
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs
&lt;/h3&gt;

&lt;p&gt;Los logs muestran cada iteración del control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "timestamp": "2026-01-15T10:30:45.123Z",
  "level": "INFO",
  "message": "Iteration 15: Setpoint=75.00, Temp=72.34, CV=45.67"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CloudWatch Metrics
&lt;/h3&gt;

&lt;p&gt;Las métricas publicadas permiten visualizar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SetpointTemperature&lt;/strong&gt;: Temperatura deseada (constante)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ActualTemperature&lt;/strong&gt;: Temperatura real del reactor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TemperatureError&lt;/strong&gt;: Error absoluto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para visualizar en CloudWatch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ir a CloudWatch Console → Metrics&lt;/li&gt;
&lt;li&gt;Buscar namespace "PIDControl-v2"&lt;/li&gt;
&lt;li&gt;Crear dashboard con las tres métricas&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Logs del Reactor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "timestamp": "2026-01-15T10:30:45.456Z",
  "level": "INFO",
  "message": "Reactor: CV=45.67, Temp=72.34, Ambient=20.0"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💡 Ventajas de Lambda Durable Functions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Sin Costo Durante Esperas
&lt;/h3&gt;

&lt;p&gt;El uso de &lt;code&gt;context.wait()&lt;/code&gt; es una de las características más poderosas de Lambda Durable Functions. Según la &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-how-it-works.html" rel="noopener noreferrer"&gt;documentación oficial de AWS&lt;/a&gt;, cuando una función durable está en estado de espera usando &lt;code&gt;context.wait()&lt;/code&gt;, &lt;strong&gt;no se cobra por tiempo de cómputo&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Antes: Lambda ejecutándose = $$$
&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Pagas por 60 segundos de ejecución
&lt;/span&gt;
&lt;span class="c1"&gt;# Ahora: Lambda suspendida = $0
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Sin cargo durante la espera
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ejemplo de ahorro de costos&lt;/strong&gt; para el caso de nuestro control PID con SAMPLE_TIME de 60 segundos y 40 iteraciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sin Durable&lt;/strong&gt;: 40 minutos de ejecución continua = 2,400 segundos facturables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Con Durable&lt;/strong&gt;: ~5 segundos de ejecución real (solo cómputo activo) = 5 segundos facturables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ahorro&lt;/strong&gt;: ~99.8% en costos de cómputo&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Referencia&lt;/strong&gt;: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-how-it-works.html" rel="noopener noreferrer"&gt;How Lambda Durable Functions work - AWS Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Checkpointing Automático
&lt;/h3&gt;

&lt;p&gt;Cada &lt;code&gt;context.step()&lt;/code&gt; guarda el estado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Si falla aquí, reinicia desde el último step exitoso
&lt;/span&gt;&lt;span class="n"&gt;pid_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculate_pid&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;  &lt;span class="c1"&gt;# ✓ Checkpoint
&lt;/span&gt;&lt;span class="n"&gt;reactor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;invoke_reactor&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;  &lt;span class="c1"&gt;# ✓ Checkpoint
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;publish_metrics&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;  &lt;span class="c1"&gt;# ✓ Checkpoint
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sin Infraestructura Extra
&lt;/h3&gt;

&lt;p&gt;Comparación con arquitecturas tradicionales:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sin Durable Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DynamoDB para estado&lt;/li&gt;
&lt;li&gt;SQS para coordinación&lt;/li&gt;
&lt;li&gt;EventBridge para scheduling&lt;/li&gt;
&lt;li&gt;Step Functions para workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Con Durable Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solo Lambda (estado incluido)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Código Más Simple
&lt;/h3&gt;

&lt;p&gt;El código es más legible y mantenible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Loop natural con estado persistente
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pid_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculate_pid&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
    &lt;span class="n"&gt;reactor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;invoke_reactor&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;publish_metrics&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SAMPLE_TIME&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📋 Casos de Uso Prácticos
&lt;/h2&gt;

&lt;p&gt;Según el &lt;a href="https://aws.amazon.com/es/blogs/aws/build-multi-step-applications-and-ai-workflows-with-aws-lambda-durable-functions/" rel="noopener noreferrer"&gt;blog oficial de AWS&lt;/a&gt; y la &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-use-cases.html" rel="noopener noreferrer"&gt;documentación técnica&lt;/a&gt;, Lambda Durable Functions es especialmente útil para escenarios donde se requieren workflows de larga duración con estado persistente. Los casos de uso documentados incluyen: &lt;strong&gt;workflows de aprobación humana&lt;/strong&gt; donde los procesos esperan aprobaciones sin mantener recursos activos; &lt;strong&gt;procesamiento de datos por lotes&lt;/strong&gt; como ETL jobs que esperan entre etapas; &lt;strong&gt;orquestación de microservicios&lt;/strong&gt; para coordinar múltiples servicios con estado; &lt;strong&gt;monitoreo y polling&lt;/strong&gt; para sistemas que verifican condiciones periódicamente; y &lt;strong&gt;workflows de IA y ML&lt;/strong&gt; para pipelines de entrenamiento y procesamiento de modelos. Estos patrones aprovechan la capacidad de &lt;code&gt;context.wait()&lt;/code&gt; para suspender la ejecución sin cargos de cómputo, haciendo viable económicamente mantener workflows activos durante períodos prolongados.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Referencias&lt;/strong&gt;: &lt;a href="https://aws.amazon.com/es/blogs/aws/build-multi-step-applications-and-ai-workflows-with-aws-lambda-durable-functions/" rel="noopener noreferrer"&gt;AWS Blog - Lambda Durable Functions&lt;/a&gt; | &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-use-cases.html" rel="noopener noreferrer"&gt;Use Cases Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ⚠️ Consideraciones Importantes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Límites del Servicio
&lt;/h3&gt;

&lt;p&gt;Según la &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-quotas.html" rel="noopener noreferrer"&gt;documentación oficial de AWS Lambda Durable Functions&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Execution timeout&lt;/strong&gt;: Sin límite de tiempo total de ejecución (la función puede ejecutarse indefinidamente usando &lt;code&gt;context.wait()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Individual function timeout&lt;/strong&gt;: Máximo 15 minutos por invocación individual de función&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention period&lt;/strong&gt;: Máximo 1 año (365 días) para estado persistente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload size&lt;/strong&gt;: Máximo 256 KB por step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum concurrent executions&lt;/strong&gt;: Sujeto a las cuotas de concurrencia de Lambda de la cuenta&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota importante&lt;/strong&gt;: Aunque no hay límite en el tiempo total de ejecución durable, cada invocación individual de la función Lambda está limitada a 15 minutos. El estado se persiste automáticamente entre invocaciones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Mejores Prácticas
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Usar steps granulares&lt;/strong&gt;: Cada step debe ser una unidad lógica de trabajo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limitar payload&lt;/strong&gt;: Pasar solo datos necesarios entre steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementar timeouts&lt;/strong&gt;: Configurar timeouts apropiados para cada función&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitorear métricas&lt;/strong&gt;: Usar CloudWatch para detectar problemas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manejar errores&lt;/strong&gt;: Implementar retry logic en steps críticos&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cuándo NO usar Durable Functions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latencia ultra-baja&lt;/strong&gt;: Si necesitas respuesta en milisegundos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alto throughput&lt;/strong&gt;: Para miles de ejecuciones concurrentes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflows complejos&lt;/strong&gt;: Step Functions puede ser mejor opción&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estado muy grande&lt;/strong&gt;: Si el estado excede 256 KB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📊 Conclusiones (Para nuestro caso de prueba PID)
&lt;/h2&gt;

&lt;p&gt;Esta POC intente validar que es posible implementar un sistema de control PID en arquitecturas serverless usando AWS Lambda Durable Functions. y lo que podemos identificar como concluciones, comparado con el uso de AWS lambda tradicional sin 'Durable Function' seria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Economía&lt;/strong&gt;: &lt;code&gt;context.wait()&lt;/code&gt; elimina costos de tiempo de espera entre iteraciones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicidad&lt;/strong&gt;: Código más limpio sin necesidad de DynamoDB, SQS o Step Functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resiliencia&lt;/strong&gt;: Checkpointing automático permite recuperación ante fallos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibilidad&lt;/strong&gt;: Fácil ajuste de parámetros PID y configuración del reactor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidad&lt;/strong&gt;: Cada control PID es independiente y puede escalar horizontalmente&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitaciones identificadas:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latencia&lt;/strong&gt;: No apto para control en tiempo real (&amp;lt; 1 segundo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt;: Primera invocación puede tardar 2-3 segundos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt;: Más complejo que funciones Lambda tradicionales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finalmente el código completo está disponible en &lt;a href="https://github.com/olcortesb/PID-control-with-lambda-durable" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Referencias y Enlaces Útiles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-quotas.html" rel="noopener noreferrer"&gt;AWS Lambda Durable Functions - Quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/es/blogs/aws/build-multi-step-applications-and-ai-workflows-with-aws-lambda-durable-functions/" rel="noopener noreferrer"&gt;AWS Lambda Durable Functions - Blog Oficial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started.html" rel="noopener noreferrer"&gt;Documentación Oficial - Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/aws-durable-execution-sdk-python/" rel="noopener noreferrer"&gt;SDK Python - PyPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9"&gt;Developing with SAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://es.wikipedia.org/wiki/Controlador_PID" rel="noopener noreferrer"&gt;Control PID - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fujielectric.fr/es/blog/regulacion-pid-para-dummies-todo-lo-que-necesita-saber/" rel="noopener noreferrer"&gt;Regulación PID para Dummies - Fuji Electric&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function" rel="noopener noreferrer"&gt;Terraform AWS Lambda Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Gracias por leer.&lt;/p&gt;

&lt;p&gt;¡Saludos!&lt;/p&gt;

&lt;p&gt;Oscar Cortés&lt;/p&gt;

</description>
      <category>aws</category>
      <category>durablefunction</category>
      <category>pid</category>
    </item>
    <item>
      <title>Agregando reactividad ⚡ a nuestras arquitecturas con AWS CloudWatch Subscription Filter</title>
      <dc:creator>olcortesb</dc:creator>
      <pubDate>Wed, 21 Jan 2026 09:04:40 +0000</pubDate>
      <link>https://dev.to/aws-espanol/agregando-reactividad-a-nuestras-arquitecturas-con-aws-cloudwatch-subscription-filter-27da</link>
      <guid>https://dev.to/aws-espanol/agregando-reactividad-a-nuestras-arquitecturas-con-aws-cloudwatch-subscription-filter-27da</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Uno de los principales desafíos al implementar sistemas basados en eventos es lograr la &lt;strong&gt;reactividad&lt;/strong&gt; necesaria para que respondan automáticamente ante eventos específicos. Cuando integramos sistemas tradicionales de request-response con arquitecturas gestionadas por eventos, siempre surge la necesidad de agregar esta capacidad reactiva.&lt;/p&gt;

&lt;p&gt;En esta búsqueda constante de soluciones robustas para agregar reactividad a los sistemas, hoy presento una prueba de concepto utilizando una de esas &lt;em&gt;features&lt;/em&gt; que están "escondidas" dentro de los servicios de AWS. Exploraremos &lt;strong&gt;AWS CloudWatch Subscription Filter&lt;/strong&gt; como una herramienta poderosa para agregar reactividad automática a nuestros sistemas. &lt;/p&gt;

&lt;p&gt;La intención de esta &lt;strong&gt;POC&lt;/strong&gt; (Proof of Concept) es utilizar &lt;strong&gt;AWS CloudWatch Subscription Filter&lt;/strong&gt; para procesar logs automáticamente y ejecutar acciones basadas en el contenido de los mismos. Crearemos una arquitectura serverless que captura logs específicos y los procesa en tiempo real.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 ¿Qué es AWS CloudWatch Subscription Filter?
&lt;/h2&gt;

&lt;p&gt;AWS CloudWatch Subscription Filter es una &lt;em&gt;feature&lt;/em&gt; que permite &lt;strong&gt;filtrar y procesar logs en "tiempo real"&lt;/strong&gt; desde CloudWatch Logs. Cuando los logs coinciden con un patrón específico, el filtro puede enviar automáticamente esos datos a destinos como Lambda, Kinesis Data Streams, o Amazon Data Firehose. En este &lt;em&gt;post&lt;/em&gt; vamos a explorar la integración con AWS Lambda específicamente.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html" rel="noopener noreferrer"&gt;AWS CloudWatch Logs Subscription Filters&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🏗️ Arquitectura de la POC
&lt;/h2&gt;

&lt;p&gt;Nuestra implementación sigue este flujo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; → Recibe POST requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Controller&lt;/strong&gt; → Procesa el POST y loggea siempre&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Subscription Filter&lt;/strong&gt; → Captura logs con &lt;code&gt;enable=true&lt;/code&gt; automáticamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Processor&lt;/strong&gt; → Procesa logs capturados y guarda en &lt;strong&gt;DynamoDB&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngdm7q5vsh9e5l3znf6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngdm7q5vsh9e5l3znf6q.png" alt="Architecture" width="721" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Implementación
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lambda Controller
&lt;/h3&gt;

&lt;p&gt;La Lambda Controller recibe los POST requests y genera logs específicos que serán capturados por el subscription filter. Aquí es importante anotar que esta lambda podría ser cualquier log de nuestro sistema tradicional o reactivo que tenga la capacidad de escribir logs en CloudWatch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# Log específico para el subscription filter
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;enable&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ ORIGINAL_POST: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST logged - subscription filter will process it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ POST received but enable=false: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST received but enable=false, not processed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;received_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error processing request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚙️ CloudWatch Subscription Filter
&lt;/h3&gt;

&lt;p&gt;El filtro se configura para capturar únicamente logs que contengan el patrón &lt;code&gt;✅ ORIGINAL_POST&lt;/code&gt;. Es importante destacar que este patrón lo agrego en la lambda cuando el post cumple las condiciones de &lt;code&gt;enable:true&lt;/code&gt;. Destaco la manera cómo se configura el filtro; aunque hay límites en el número de filtros que se pueden configurar por log group (&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html" rel="noopener noreferrer"&gt;Ref&lt;/a&gt;), la forma de agregar filtros es muy simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;# Ejemplo en Terraform de cómo configurar un subscription filter
&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s"&gt;"aws_cloudwatch_log_subscription_filter"&lt;/span&gt; &lt;span class="s"&gt;"posts_filter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${var.project_name}-posts-filter"&lt;/span&gt;
  &lt;span class="n"&gt;log_group_name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws_cloudwatch_log_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;controller_lambda_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
  &lt;span class="n"&gt;filter_pattern&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"✅ ORIGINAL_POST"&lt;/span&gt;
  &lt;span class="n"&gt;destination_arn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processor_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lambda Processor
&lt;/h3&gt;

&lt;p&gt;La Lambda Processor recibe los logs filtrados, los descomprime y procesa el contenido. En este caso, a diferencia de la lambda controller, sí necesitamos esta lambda para que procese el log y se active ante el evento del subscription filter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DYNAMODB_TABLE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# Descomprimir datos de CloudWatch Logs
&lt;/span&gt;        &lt;span class="c1"&gt;# Este punto es importante: CloudWatch envía los datos codificados 
&lt;/span&gt;        &lt;span class="c1"&gt;# en base64, hay que decodificarlos primero antes de trabajarlos
&lt;/span&gt;        &lt;span class="n"&gt;compressed_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awslogs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;uncompressed_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncompressed_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Procesar cada evento de log
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;logEvents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;✅ ORIGINAL_POST:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Extraer JSON del POST original
&lt;/span&gt;                &lt;span class="n"&gt;json_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;json_start&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;original_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json_start&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;

                    &lt;span class="c1"&gt;# Guardar en DynamoDB
&lt;/span&gt;                    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;original_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;original_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;processed_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved to DynamoDB: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Successfully processed logs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error processing logs: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Despliegue con Terraform
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Estructura del proyecto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws-cloudwatch-subscription-filter/
├── src/
│   ├── controller_lambda.py
│   └── processor_lambda.py
├──  main.tf
├──  api_gateway.tf
├──  lambda_functions.tf
├──  cloudwatch_subscription_filter.tf
├──  dynamodb_table.tf
├──  iam_roles.tf
├──  build_lambdas.sh
└──  README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comandos de despliegue
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Construir las Lambdas (zip y dependencias)&lt;/span&gt;
./build_lambdas.sh

&lt;span class="c"&gt;# 2. Inicializar Terraform (credenciales previamente configuradas)&lt;/span&gt;
terraform init

&lt;span class="c"&gt;# 3. Planificar el despliegue&lt;/span&gt;
terraform plan

&lt;span class="c"&gt;# 4. Aplicar la infraestructura&lt;/span&gt;
terraform apply

&lt;span class="c"&gt;# 5. Luego de las pruebas&lt;/span&gt;
terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧪 Pruebas y Validación
&lt;/h2&gt;

&lt;h3&gt;
  
  
  POST que NO será procesado
&lt;/h3&gt;

&lt;p&gt;Es importante tomar los outputs del Terraform y reemplazarlos en &lt;code&gt;{your-api-gateway-url}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-api-gateway-url/posts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "enable": false,
    "message": "Este mensaje no será procesado",
    "data": "algunos datos"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  POST que SÍ será procesado
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-api-gateway-url/posts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "enable": true,
    "message": "Este mensaje será procesado y guardado en DynamoDB",
    "data": "algunos datos importantes"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📊 Verificación de Resultados
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. CloudWatch Logs
&lt;/h3&gt;

&lt;p&gt;Revisar los logs de la Lambda Controller para confirmar que se generan correctamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
❌ POST received but enable=false: {"enable": false, "message": "Este mensaje no ser\u00e1 procesado", "data": "algunos datos"}
END RequestId: 
REPORT RequestId: 
Duration: 1.81 ms Billed Duration: 87 ms Memory Size: 128 MB Max Memory Used: 32 MB Init Duration: 84.44 ms

START RequestId:  Version: $LATEST
✅ ORIGINAL_POST: {"enable": true, "message": "Este mensaje ser\u00e1 procesado y guardado en DynamoDB", "data": "algunos datos importantes"}
END RequestId: 
REPORT RequestId:  
Duration: 1.46 ms Billed Duration: 2 ms 
Memory Size: 128 MB Max Memory Used: 32 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. DynamoDB
&lt;/h3&gt;

&lt;p&gt;Verificar que los datos se guardaron en la tabla:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb scan &lt;span class="nt"&gt;--table-name&lt;/span&gt; cloudwatch-subscription-filter-posts &lt;span class="nt"&gt;--limit&lt;/span&gt; 1 &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Response&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"original_post"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"M"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Este mensaje será procesado y guardado en DynamoDB"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"algunos datos importantes"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"enable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"BOOL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"log_timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1768222052525"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51991cf6-23a5-463b-a8d1-dc2f047ab13c"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"processed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-12T12:47:42.465810"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-12T12:47:42.465790"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ScannedCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LastEvaluatedKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51991cf6-23a5-463b-a8d1-dc2f047ab13c"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. CloudWatch Subscription Filter
&lt;/h3&gt;

&lt;p&gt;Confirmar que el filtro se creó y está activo en la consola de AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Características Técnicas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Filtro de Patrones
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Patrón&lt;/strong&gt;: &lt;code&gt;✅ ORIGINAL_POST&lt;/code&gt; (He probado íconos porque me parece un punto crítico)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensible a mayúsculas&lt;/strong&gt;: Sí&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procesamiento&lt;/strong&gt;: máximo 30 segundos hasta que se persiste el registro en DynamoDB. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Compresión de Datos
&lt;/h3&gt;

&lt;p&gt;CloudWatch Logs envía los datos &lt;strong&gt;comprimidos con gzip&lt;/strong&gt; y &lt;strong&gt;codificados en base64&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proceso de descompresión
&lt;/span&gt;&lt;span class="n"&gt;compressed_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awslogs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;uncompressed_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncompressed_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Estructura de Datos
&lt;/h3&gt;

&lt;p&gt;Los logs procesados incluyen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ID único&lt;/strong&gt; generado con UUID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST original&lt;/strong&gt; completo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps&lt;/strong&gt; de creación y procesamiento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadatos&lt;/strong&gt; del log event&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Casos de Uso Prácticos (Siempre como referencia, pero hay muchos más...)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Monitoreo de Errores
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# En la Lambda Controller
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error_occurred&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🚨 ERROR_ALERT: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Auditoría de Transacciones
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Para transacciones críticas
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;transaction_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;💰 PAYMENT_LOG: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Análisis de Comportamiento
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Para eventos de usuario
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;👤 USER_EVENT: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📋 Conclusiones
&lt;/h2&gt;

&lt;p&gt;Aproveché esta POC para validar el funcionamiento de &lt;strong&gt;AWS CloudWatch Subscription Filter&lt;/strong&gt; identificando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatización&lt;/strong&gt; del procesamiento de logs basado en patrones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración&lt;/strong&gt; despierta la lambda processor con una latencia razonable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arquitectura serverless&lt;/strong&gt; escalable y costo-eficiente. Importante anotar que no se cobra por detectar los eventos sino por los datos enviados a la lambda o el Kinesis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtrado inteligente&lt;/strong&gt; que procesa solo logs relevantes y se puede tener un control atómico de los eventos que se necesitan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta implementación sirve como base para casos de uso más complejos como &lt;strong&gt;monitoreo de errores&lt;/strong&gt;, &lt;strong&gt;auditoría de transacciones&lt;/strong&gt;, &lt;strong&gt;análisis de comportamiento de usuarios&lt;/strong&gt;, y &lt;strong&gt;alertas en tiempo real&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;El código completo está disponible en &lt;a href="https://github.com/olcortesb/aws-cloudwatch-subscription-filter" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; y listo para desplegar con Terraform, proporcionando una base sólida para proyectos que requieran procesamiento automático de logs en AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Consideraciones Importantes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Límites del Servicio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Máximo 2 subscription filters&lt;/strong&gt; por log group&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filtros de hasta 1024 caracteres&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; en destinos Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mejores Prácticas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usar patrones específicos&lt;/strong&gt; para evitar ruido&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementar retry logic&lt;/strong&gt; en el processor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitorear métricas&lt;/strong&gt; de CloudWatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurar alertas&lt;/strong&gt; para errores&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Referencias y Enlaces Útiles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html" rel="noopener noreferrer"&gt;AWS CloudWatch Logs Subscription Filters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html" rel="noopener noreferrer"&gt;CloudWatch Logs Filter Pattern Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html" rel="noopener noreferrer"&gt;AWS CloudWatch Limit And Quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchlogs.html" rel="noopener noreferrer"&gt;Lambda Function for CloudWatch Logs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter" rel="noopener noreferrer"&gt;Terraform AWS CloudWatch Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gracias por leer.&lt;/p&gt;

&lt;p&gt;¡Saludos!&lt;/p&gt;

&lt;p&gt;Oscar Cortés&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudwatch</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS DocumentDB Streams: Configuración y caso de uso</title>
      <dc:creator>olcortesb</dc:creator>
      <pubDate>Wed, 07 Jan 2026 15:29:39 +0000</pubDate>
      <link>https://dev.to/aws-espanol/aws-documentdb-streams-configuracion-y-casos-de-uso-574e</link>
      <guid>https://dev.to/aws-espanol/aws-documentdb-streams-configuracion-y-casos-de-uso-574e</guid>
      <description>&lt;p&gt;Entre finales del 2025 y principios del presente año, con el equipo de trabajo de ACKStorm nos enfrentamos a un desafío interesante. Necesitábamos diseñar una arquitectura serverless para una aplicación que necesitaba un manejo de un alto volumen de datos. En el proceso de diseño se identifico que el uso de una DynamoDB no era una buena opción. Con este escenario analizamos el costo de las distintas bases de datos que tenemos en AWS donde DocumentDB se presentó como la mejor opción.&lt;/p&gt;

&lt;p&gt;Una de las mejores características, al menos desde mi parecer, que tiene DynamoDB es el &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html" rel="noopener noreferrer"&gt;DynamoDB Streams&lt;/a&gt;, feature que permite disparar eventos en base a las modificaciones que se realicen sobre la base e integrarlas con el ecosistema de eventos de AWS(lambda, eventbridge, etc). Bien, DocumentDB tiene streams heredados de MongoDB pero no es directamente integrable con el ecosistema. En este post pretendo mostrar cómo configurar DocumentDB streams e integrarlo al flujo de trabajo de AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es AWS DocumentDB Stream?
&lt;/h2&gt;

&lt;p&gt;AWS DocumentDB Streams es una funcionalidad que permite capturar y procesar cambios en los datos de una base de datos DocumentDB en tiempo real. Esta característica es especialmente útil para aplicaciones que requieren sincronización de datos, auditoría o integración con otros servicios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Referencia oficial&lt;/strong&gt;: &lt;a href="https://docs.aws.amazon.com/documentdb/latest/developerguide/change_streams.html" rel="noopener noreferrer"&gt;AWS DocumentDB Change Streams&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Características principales:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Captura de cambios en tiempo real&lt;/strong&gt;: Detecta inserciones, actualizaciones y eliminaciones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibilidad con MongoDB&lt;/strong&gt;: Utiliza la API de Change Streams de MongoDB 3.6+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración nativa&lt;/strong&gt;: Se puede conectar directamente con servicios AWS como Lambda, Kinesis y EventBridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtrado granular&lt;/strong&gt;: Permite filtrar eventos por colección, operación o campos específicos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durabilidad&lt;/strong&gt;: Los eventos se almacenan de forma persistente durante un período configurable&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Documentación técnica&lt;/strong&gt;: &lt;a href="https://docs.aws.amazon.com/documentdb/latest/developerguide/change_streams.html#change_streams-enabling" rel="noopener noreferrer"&gt;Working with Change Streams&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  POC: Arquitectura propuesta
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf02q4d90x4cvhvihx0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf02q4d90x4cvhvihx0r.png" alt="Architecture" width="621" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como muestra la figura, la arquitectura propuesta se basa en una lambda que permite escribir los documentos tomando lo que el API Gateway reciba como un POST desde el exterior, luego eso se envía al DocumentDB y del otro lado del dominio de la aplicación está el Event Bridge que está evaluando cada tiempo (1m para esta POC) si se insertó un nuevo documento.&lt;/p&gt;

&lt;h2&gt;
  
  
  EL codigo:
&lt;/h2&gt;

&lt;p&gt;Todo el codigo utilizada para esta POC esta ene este repositorio &lt;a href="https://github.com/olcortesb/aws-documentdb-streams" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuración de DocumentDB Streams
&lt;/h2&gt;

&lt;p&gt;A diferencia de DynamoDB Streams, en DocumentDB es necesario configurarla. Para configurarla se puede hacer a nivel global de la base de datos o a nivel de colecciones; vamos a configurar a nivel de colecciones. También se puede configurar por la línea de comandos o a través de SDK, este segundo será nuestra opción.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;docdb_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DOCDB_URI&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connecting to DocumentDB...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Conectar a DocumentDB con timeouts agresivos
&lt;/span&gt;        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymongo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;docdb_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tlsCAFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;global-bundle.pem&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;retryWrites&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;connectTimeoutMS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 5 segundos
&lt;/span&gt;            &lt;span class="n"&gt;serverSelectionTimeoutMS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 5 segundos
&lt;/span&gt;            &lt;span class="n"&gt;socketTimeoutMS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 10 segundos
&lt;/span&gt;            &lt;span class="n"&gt;maxPoolSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Conexión única
&lt;/span&gt;            &lt;span class="n"&gt;waitQueueTimeoutMS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;  &lt;span class="c1"&gt;# 2 segundos
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Verificar conexión rápidamente
&lt;/span&gt;        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;demo_db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Habilitar change streams si no están habilitados
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modifyChangeStreams&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo_db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Change streams enabled for collection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Change streams already enabled or error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;....&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prueba de funcionamiento
&lt;/h2&gt;

&lt;p&gt;Una vez configurado el DocumentDB Stream, nos queda realizar pruebas.&lt;/p&gt;

&lt;p&gt;Primero realizamos un POST al API Gateway para agregar un nuevo documento a la DocumentDB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/workspace/aws-documentdb-streams/lambda/writer &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://umncz7md48.execute-api.us-east-1.amazonaws.com/dev/write   &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;   &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "user_id": "user_008",
    "name": "Elena Morales", 
    "email": "elena@example.com"
  }'&lt;/span&gt;
&lt;span class="c"&gt;# {"message": "Document inserted successfully", "document_id": "695e6e66a68ca00398aeb3ca"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verificamos el log del lambda-writer:&lt;/p&gt;

&lt;p&gt;Registro del Log completo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/workspace/aws-documentdb-streams/lambda/writer &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/docdb-streams-demo-writer &lt;span class="nt"&gt;--since&lt;/span&gt; 1m &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] INIT_START Runtime Version: python:3.11.v109      Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:49f733259c7ce7e0deee75ff91c6afe35c7d58b04ed300f32701216263b4590c&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] START RequestId: 717faf29-0efd-470e-925f-12434921dc45 Version: $LATEST&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Connecting to DocumentDB...&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] /var/task/lambda_function.py:14: UserWarning: You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] client = pymongo.MongoClient(&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Connected successfully&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Change streams enabled for collection&lt;/span&gt;
&lt;span class="c"&gt;## 026/01/07/[$LATEST] Inserting document: {'user_id': 'user_008', 'name': 'Elena Morales', 'email': 'elena@example.com', 'timestamp': datetime.datetime(2026, 1, 7, 14, 32, 6, 64071), 'action': 'user_created'}&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Document inserted with ID: 695e6e66a68ca00398aeb3ca&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] END RequestId: 717faf29-0efd-470e-925f-12434921dc45&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] REPORT RequestId: 717faf29-0efd-470e-925f-12434921dc45    Duration: 1026.52 ms    Billed Duration: 1470 msMemory Size: 128 MB     Max Memory Used: 58 MB  Init Duration: 443.42 ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registro específico del momento en que el documento se inserta: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Inserting document: {'user_id': 'user_008', 'name': 'Elena Morales', 'email': '&lt;a href="mailto:elena@example.com"&gt;elena@example.com&lt;/a&gt;', 'timestamp': datetime.datetime(2026, 1, 7, 14, 32, 6, 64071), 'action': 'user_created'}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Luego verificamos el log del lambda-processor:&lt;/p&gt;

&lt;p&gt;Registro del log completo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/workspace/aws-documentdb-streams/lambda/processor &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/docdb-streams-demo-stream-processor &lt;span class="nt"&gt;--since&lt;/span&gt; 1m &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] INIT_START Runtime Version: python:3.11.v109 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:49f733259c7ce7e0deee75ff91c6afe35c7d58b04ed300f32701216263b4590c&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] START RequestId: ba850df0-2e96-4c01-81cd-9e2109db3587 Version: $LATEST&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] /var/task/lambda_function.py:17: UserWarning: You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] client = pymongo.MongoClient(docdb_uri,&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Starting change stream listener...&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Change detected: insert on document 695e6e66a68ca00398aeb3ca&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] New document: {"_id": "695e6e66a68ca00398aeb3ca", "user_id": "user_008", "name": "Elena Morales", "email": "elena@example.com", "timestamp": "2026-01-07 14:32:06.064071", "action": "user_created"}&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] Change detected: insert on document 695e6e66a68ca00398aeb3ca&lt;/span&gt;
&lt;span class="c"&gt;## 2026/01/07/[$LATEST] New document: {"_id": "695e6e66a68ca00398aeb3ca", "user_id": "user_008", "name": "Elena Morales", "email": "elena@example.com", "timestamp": "2026-01-07 14:32:06.064071", "action": "user_created"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registro específico del momento en que el stream detecta el nuevo documento:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Change detected: insert on document 695e6e66a68ca00398aeb3ca&lt;br&gt;
New document: {"_id": "695e6e66a68ca00398aeb3ca", "user_id": "user_008", "name": "Elena Morales", "email": "&lt;a href="mailto:elena@example.com"&gt;elena@example.com&lt;/a&gt;", "timestamp": "2026-01-07 14:32:06.064071", "action": "user_created"}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;p&gt;La implementación de AWS DocumentDB Streams es una solución viable para capturar y procesar cambios en tiempo real en aplicaciones serverless de alto volumen cuando DynamoDB no es una opción. Sin embargo, hay que tener en cuenta:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ventajas identificadas:
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Integración nativa con el ecosistema AWS&lt;/strong&gt;: Aunque requiere configuración adicional comparado con DynamoDB Streams, la integración con Lambda y EventBridge funciona de manera fluida. La desventaja en este punto es que el sistema en general no es reactivo por naturaleza; necesitamos EventBridge para poder detectar el evento. Sin embargo, una vez detectado, lo podemos integrar con el resto del ecosistema. &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Flexibilidad de configuración&lt;/strong&gt;: La posibilidad de habilitar streams a nivel de colección específica permite un control granular sobre qué cambios capturar.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Compatibilidad con MongoDB&lt;/strong&gt;: Al utilizar la API estándar de Change Streams de MongoDB, facilita la migración de aplicaciones existentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consideraciones importantes:
&lt;/h3&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Configuración manual requerida&lt;/strong&gt;: A diferencia de DynamoDB, DocumentDB Streams requiere habilitación explícita por colección.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Gestión de conexiones&lt;/strong&gt;: Es crucial implementar timeouts y pooling de conexiones adecuados para evitar problemas de rendimiento.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Monitoreo&lt;/strong&gt;: Se recomienda implementar alertas para detectar fallos en el procesamiento de streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Casos de uso recomendados (en el papel, pueden ser muchos más):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sincronización de datos&lt;/strong&gt; entre microservicios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditoría en tiempo real&lt;/strong&gt; de cambios en documentos críticos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triggers de notificaciones&lt;/strong&gt; basados en cambios de estado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replicación selectiva&lt;/strong&gt; de datos a sistemas analíticos&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Importante antes de implementar esta solución en un ambiente productivo:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Implementar manejo de errores robusto con DLQ (Dead Letter Queues)&lt;/li&gt;
&lt;li&gt;Configurar métricas personalizadas en CloudWatch&lt;/li&gt;
&lt;li&gt;Evaluar el uso de Kinesis Data Streams para mayor throughput&lt;/li&gt;
&lt;li&gt;Implementar filtros más específicos para optimizar el procesamiento&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En resumen, DocumentDB Streams ofrece una alternativa sólida a DynamoDB Streams cuando se requiere flexibilidad de esquemas, volumen alto de datos y compatibilidad con MongoDB, aunque con un overhead de configuración adicional que debe considerarse en el diseño de la arquitectura.&lt;/p&gt;




&lt;p&gt;Gracias por leer.&lt;/p&gt;

&lt;p&gt;¡Saludos!&lt;/p&gt;

&lt;p&gt;Oscar Cortés&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awddocumentdb</category>
      <category>stream</category>
    </item>
  </channel>
</rss>
