<?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>Forem: AWS</title>
    <description>The latest articles on Forem by AWS (@aws).</description>
    <link>https://forem.com/aws</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%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png</url>
      <title>Forem: AWS</title>
      <link>https://forem.com/aws</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aws"/>
    <language>en</language>
    <item>
      <title>¿Qué es un Modelo de IA? Explicación sencilla</title>
      <dc:creator>Ramses Mata</dc:creator>
      <pubDate>Mon, 20 Apr 2026 20:10:32 +0000</pubDate>
      <link>https://forem.com/aws/que-es-un-modelo-de-ia-explicado-de-manera-sencilla-2joo</link>
      <guid>https://forem.com/aws/que-es-un-modelo-de-ia-explicado-de-manera-sencilla-2joo</guid>
      <description>&lt;p&gt;La mayoría de nosotros ha utilizado chats de IA en los últimos años en su día a día. Sin importar si eres estudiante o profesional, son herramientas que se han enraizado en nuestras vidas. Pero, ¿sabes exactamente qué hay detrás? ¿Cómo es que funcionan realmente? No es magia, en realidad es pura estadística.&lt;/p&gt;

&lt;p&gt;Detrás de cada chat de IA, cada traductor automático y cada asistente de código, hay algo llamado &lt;strong&gt;modelo&lt;/strong&gt;. No necesitas ser ingeniero de Machine Learning para entender qué es, y tampoco vamos a entrar en matemáticas o algoritmos. En este artículo vas a aprender qué es un modelo, cómo interactúas con uno, cómo elegir el correcto y si necesitas crear el tuyo propio.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. ¿Qué es un modelo?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Un modelo de IA es un programa informático entrenado con grandes cantidades de datos que predice qué es lo más probable que venga después en una secuencia de texto.&lt;/strong&gt; No "entiende" ni "sabe", calcula probabilidades basándose en patrones.&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%2Ft6vnktaa69pdukm2t2rl.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%2Ft6vnktaa69pdukm2t2rl.png" alt="Con qué es entrenado un modelo?" width="571" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagina que tienes un amigo que es increíblemente bueno para terminar tus frases. Tú dices "hoy me siento..." y él responde "cansado" porque te conoce, sabe el contexto y ha escuchado miles de conversaciones similares. Eso, en esencia, es lo que hace un &lt;strong&gt;modelo de IA&lt;/strong&gt;: un programa entrenado con cantidades masivas de texto que predice qué es lo más probable que venga después.&lt;/p&gt;

&lt;p&gt;Por ejemplo, en el contexto "Mi color favorito es...", un modelo debería predecir "rojo" mejor que "auto". No porque "entienda" colores, sino porque en los millones de textos que ha procesado, "rojo" aparece mucho más frecuentemente después de "mi color favorito es" que "auto".&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%2Fugd6j1252q0uwmfj2vy4.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%2Fugd6j1252q0uwmfj2vy4.png" alt="Predicción de la siguiente palabra: el modelo calcula probabilidades y elige la más alta" width="614" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto es importante: el modelo no "sabe" cosas ni "entiende" el mundo. Calcula probabilidades. Es muy bueno encontrando patrones en texto, y eso le permite generar respuestas que parecen inteligentes, pero por debajo es estadística.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Antes de procesar texto, los modelos lo descomponen en piezas pequeñas llamadas &lt;strong&gt;tokens&lt;/strong&gt; (que pueden ser palabras, partes de palabras o caracteres). Esto es lo que les permite trabajar con cualquier idioma y tipo de texto.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. ¿Cómo interactúas con un modelo?
&lt;/h2&gt;

&lt;p&gt;Como desarrollador, tú no entrenas modelos, los usas. Y la interacción es más simple de lo que parece: tú envías un &lt;strong&gt;prompt&lt;/strong&gt; (una instrucción o pregunta en texto) y el modelo te devuelve una &lt;strong&gt;respuesta&lt;/strong&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%2Fpd1zsgmpeci0nofvszlw.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%2Fpd1zsgmpeci0nofvszlw.png" alt="Interacción con un modelo: prompt → modelo → respuesta" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto sucede a través de &lt;a href="https://dev.to/aws/que-pasa-realmente-cuando-una-app-pide-algo-19g0"&gt;APIs&lt;/a&gt;. Servicios como &lt;a href="https://aws.amazon.com/bedrock/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;, OpenAI API o Google Vertex AI te dan acceso a modelos poderosos sin que tengas que preocuparte por la infraestructura detrás.&lt;/p&gt;

&lt;p&gt;Pero también puedes correr un modelo directamente en tu computadora usando herramientas como &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Con un solo comando puedes descargar un modelo y empezar a hablar con él:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto descarga el modelo Llama 3.1 y abre una conversación directa en tu terminal:&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%2F5q2f1qx6w3kzkpmkt4ht.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%2F5q2f1qx6w3kzkpmkt4ht.png" alt="Ollama corriendo Llama 3.1 en la terminal" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Piénsalo así: tú no construyes el motor del auto, tú lo conduces. Los modelos ya están construidos y entrenados, tu trabajo es aprender a usarlos bien.&lt;/p&gt;

&lt;p&gt;Y aquí hay un detalle clave: la calidad de la respuesta que recibes depende mucho de cómo escribes tu prompt. Veamos un ejemplo real. Si le pedimos al modelo algo vago como "escribe algo sobre perros", obtenemos una respuesta genérica:&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%2F3p3p912nnq4d84tiwk5g.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%2F3p3p912nnq4d84tiwk5g.png" alt="Respuesta del modelo a un prompt vago: " width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pero si somos más específicos con "escribe un párrafo de 3 oraciones explicando por qué los perros son buenas mascotas para familias con niños", la respuesta es mucho más útil:&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%2Ftb3xo7isjbg6qwao7b5g.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%2Ftb3xo7isjbg6qwao7b5g.png" alt="Respuesta del modelo a un prompt específico sobre perros y familias" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La diferencia es notable. A esta habilidad de escribir buenos prompts se le llama &lt;a href="https://www.promptingguide.ai/" rel="noopener noreferrer"&gt;prompt engineering&lt;/a&gt;, es una de esas habilidades que suele ser muy infravalorada y muchas veces usada como meme, pero te va a ayudar mucho al desarrollar software y usar modelos de IA.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; No necesitas instalar ni configurar un modelo en tu computadora para usarlo. A través de APIs puedes enviar un prompt y recibir una respuesta con unas pocas líneas de código.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. ¿Cómo elijo un modelo?
&lt;/h2&gt;

&lt;p&gt;Cuando empiezas a investigar sobre modelos, te encuentras con muchos nombres: Claude, GPT, Llama, Nova, Mistral... ¿Cómo sabes cuál usar? En lugar de memorizar una taxonomía de tipos de modelos, lo que realmente importa son las &lt;strong&gt;capacidades&lt;/strong&gt; del modelo.&lt;/p&gt;

&lt;p&gt;Si usas Ollama, puedes explorar los modelos disponibles en &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;ollama.com/library&lt;/a&gt;. Cada modelo tiene una página con sus capacidades, tamaño y ventana de contexto. Por ejemplo, en la &lt;a href="https://ollama.com/library/llama3.1" rel="noopener noreferrer"&gt;página de Llama 3.1&lt;/a&gt; puedes ver toda esta información de un vistazo:&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%2Fm4spt9wtdnvqbr1le5hg.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%2Fm4spt9wtdnvqbr1le5hg.png" alt="Página de Llama 3.1 en Ollama mostrando capacidades, tamaño y contexto" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Estas son las capacidades más relevantes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capacidad&lt;/th&gt;
&lt;th&gt;Qué hace&lt;/th&gt;
&lt;th&gt;Cuándo la necesitas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generación de texto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Produce texto, código, resúmenes&lt;/td&gt;
&lt;td&gt;Siempre (es la base)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multimodalidad&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Procesa imágenes, audio, video&lt;/td&gt;
&lt;td&gt;Cuando trabajas con más que texto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ventana de contexto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cuánto texto puede "ver" a la vez&lt;/td&gt;
&lt;td&gt;Documentos largos, conversaciones extensas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Costo y velocidad&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tradeoff entre capacidad y precio&lt;/td&gt;
&lt;td&gt;Aplicaciones con muchas llamadas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Generación de texto
&lt;/h3&gt;

&lt;p&gt;Todos los modelos modernos pueden generar texto, pero no todos lo hacen igual de bien. Algunos son mejores para conversación, otros para código, otros para análisis. Esta es la capacidad base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multimodalidad
&lt;/h3&gt;

&lt;p&gt;Algunos modelos pueden procesar no solo texto, sino también imágenes, audio o video. Si necesitas analizar capturas de pantalla, interpretar documentos escaneados o procesar fotos, necesitas un modelo multimodal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ventana de contexto
&lt;/h3&gt;

&lt;p&gt;La &lt;strong&gt;ventana de contexto&lt;/strong&gt; es la cantidad de texto que el modelo puede "ver" al mismo tiempo. Piénsalo como su memoria a corto plazo. Un modelo con una ventana de contexto grande puede leer documentos largos, mantener conversaciones extensas o analizar múltiples archivos a la vez.&lt;/p&gt;

&lt;p&gt;Puedes encontrar el tamaño de la ventana de contexto en la página de documentación de cada modelo. Como referencia: 128K tokens es suficiente para la mayoría de tareas con texto. Si necesitas trabajar con múltiples documentos largos o conversaciones muy extensas, busca modelos con 200K+ tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Costo y velocidad
&lt;/h3&gt;

&lt;p&gt;Los modelos más capaces suelen ser más lentos y más caros por petición. Si tu aplicación hace muchas llamadas al modelo, esto importa. A veces un modelo más pequeño y rápido es mejor opción que el más poderoso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; No necesitas el modelo más grande o más caro. Elige basándote en lo que necesitas hacer. Una tarea que solo clasifica textos cortos no necesita el mismo modelo que una que analiza documentos de 100 páginas.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. ¿Necesito crear mi propio modelo?
&lt;/h2&gt;

&lt;p&gt;Respuesta corta: casi seguro que no.&lt;/p&gt;

&lt;p&gt;Como desarrollador, tu rol es &lt;strong&gt;consumir&lt;/strong&gt; modelos a través de APIs y servicios, no construirlos desde cero. Entrenar un modelo requiere cantidades enormes de datos, poder computacional y experiencia en Machine Learning.&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%2Fjhdb301sw2wmbwp0gscm.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%2Fjhdb301sw2wmbwp0gscm.png" alt="Developer vs ML Engineer: el desarrollador usa modelos vía API, el ingeniero de ML los entrena" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proveedores de nube como &lt;a href="https://aws.amazon.com/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; con &lt;a href="https://aws.amazon.com/bedrock/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt; te dan acceso a múltiples modelos fundacionales sin que tengas que manejar infraestructura. Tú eliges el modelo, envías tu prompt a través de la API, y recibes tu respuesta.&lt;/p&gt;

&lt;p&gt;¿Cuándo necesitarías tu propio modelo? En casos muy específicos donde los modelos existentes no cubren tu necesidad. Por ejemplo, un hospital que necesita un modelo entrenado específicamente con sus registros médicos internos y terminología propia, o una empresa con un lenguaje de dominio tan especializado que los modelos generales no manejan bien. Y aún en esos casos, lo más común es hacer &lt;a href="https://www.geeksforgeeks.org/deep-learning/what-is-fine-tuning/" rel="noopener noreferrer"&gt;fine-tuning&lt;/a&gt; (ajustar un modelo existente o preentrenado con tus datos) en lugar de construir uno desde cero.&lt;/p&gt;

&lt;p&gt;Tu trabajo como desarrollador: aprender a usar modelos de manera efectiva, elegir el correcto para cada caso de uso, y construir aplicaciones que los aprovechen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Cuál es la diferencia entre un modelo de IA y un LLM?
&lt;/h3&gt;

&lt;p&gt;Un LLM (Large Language Model) es un tipo específico de modelo de IA. Todos los LLMs son modelos de IA, pero no todos los modelos de IA son LLMs. "LLM" se refiere a modelos de lenguaje entrenados con cantidades masivas de datos, como Claude, GPT o Llama.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Puedo correr un modelo de IA en mi computadora?
&lt;/h3&gt;

&lt;p&gt;Sí, pero con limitaciones. Herramientas como &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; te permiten descargar y correr modelos localmente con un solo comando. Modelos como Llama 3.1 funcionan bien en computadoras con 16GB de RAM. Sin embargo, los modelos más capaces requieren hardware muy potente (GPUs especializadas y decenas de gigabytes de memoria) que la mayoría de computadoras personales no tienen. Aquí es donde el cloud computing ayuda: servicios como &lt;a href="https://aws.amazon.com/bedrock/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt; te dan acceso a los modelos más poderosos a través de una API, sin necesidad de tener el hardware en tu máquina.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué modelo de IA debería usar para mi proyecto?
&lt;/h3&gt;

&lt;p&gt;Depende de lo que necesites. Para tareas de texto general, cualquier LLM moderno funciona. Si necesitas procesar imágenes o audio, busca un modelo multimodal. Si tu aplicación hace muchas llamadas, prioriza costo y velocidad sobre capacidad máxima.&lt;/p&gt;




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

&lt;p&gt;Recapitulemos lo que aprendimos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un &lt;strong&gt;modelo de IA&lt;/strong&gt; es un programa que predice qué viene después basándose en patrones que aprendió de datos. No "entiende" ni "sabe", calcula probabilidades&lt;/li&gt;
&lt;li&gt;Interactúas con modelos enviando &lt;strong&gt;prompts&lt;/strong&gt; y recibiendo &lt;strong&gt;respuestas&lt;/strong&gt; a través de &lt;strong&gt;APIs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Al elegir un modelo, lo que importa son las &lt;strong&gt;capacidades&lt;/strong&gt;: generación de texto, multimodalidad, ventana de contexto, costo y velocidad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No necesitas crear tu propio modelo&lt;/strong&gt;. Servicios en la nube te dan acceso a modelos poderosos listos para usar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algo que no cubrimos en este artículo pero vale la pena mencionar: algunos modelos también soportan &lt;strong&gt;uso de herramientas&lt;/strong&gt; (tool use), lo que significa que pueden describir cuándo necesitan llamar a una función externa y con qué parámetros. Esta capacidad es lo que hace posible construir &lt;strong&gt;agentes de IA&lt;/strong&gt;, y es exactamente lo que exploraremos en el próximo artículo.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>machinelearning</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Building Serverless APIs with TDD and AI-Powered Spec Generation</title>
      <dc:creator>Salih Guler </dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:44:03 +0000</pubDate>
      <link>https://forem.com/aws/building-serverless-apis-with-tdd-and-ai-powered-spec-generation-2c36</link>
      <guid>https://forem.com/aws/building-serverless-apis-with-tdd-and-ai-powered-spec-generation-2c36</guid>
      <description>&lt;p&gt;Test-Driven Development has been around for decades, but most teams still struggle to adopt it consistently. The biggest friction point? Writing specs and tests takes time, especially when you're dealing with the ambiguity of greenfield projects. You're not always sure what the requirements should be, what edge cases to cover, or how to structure your tests before you write the code.&lt;/p&gt;

&lt;p&gt;This gets more complicated with serverless architectures. You're juggling Lambda functions, API Gateway configurations, database tables, and event-driven workflows. Traditional TDD assumes you're working in a monolithic environment where you can stub out dependencies easily. In serverless, your "dependencies" are managed AWS services with their own quirks, quotas, and failure modes.&lt;/p&gt;

&lt;p&gt;What if you could use AI to generate comprehensive specifications from high-level requirements, then use those specs to drive your test creation and implementation? This is how spec-driven development with AI assistants changes how TDD works in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional TDD in Serverless
&lt;/h2&gt;

&lt;p&gt;Classic TDD follows a simple loop: write a failing test, write code to pass the test, refactor. This works when you know exactly what you're building. But in real projects, requirements are often vague: "&lt;em&gt;Build an API that handles user registrations&lt;/em&gt;" doesn't tell you what fields are required, what validation rules apply, what error responses look like, or how you'll handle concurrent registrations.&lt;/p&gt;

&lt;p&gt;You end up spending hours in planning meetings, writing detailed specification documents, or (more commonly) skipping specs entirely and letting the code become the specification. All three approaches have problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Planning meetings slow down development and documentation gradually becomes obsolete&lt;/li&gt;
&lt;li&gt;Skipping specs means inconsistent implementations and missing edge cases&lt;/li&gt;
&lt;li&gt;Code-as-specification makes onboarding painful and refactoring risky&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Serverless adds another layer of complexity. You need to test that your Lambda function works, that API Gateway routes requests correctly, that DynamoDB handles your access patterns, that IAM permissions are configured properly, and that everything scales under load. Unit tests with mocks only get you so far. You need integration tests in the cloud to catch configuration issues, but those are slow and expensive to run constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spec-Driven Development: The Missing Layer
&lt;/h2&gt;

&lt;p&gt;Spec-driven development sits between requirements and tests. You start with a formal specification that describes exactly what your system should do, including success cases, error conditions, data formats, and constraints. This spec becomes the contract that your code must fulfill.&lt;/p&gt;

&lt;p&gt;The difference from traditional documentation is that specs are executable. You can generate tests from specs, validate implementations against specs, and use specs to generate API documentation or client SDKs automatically. OpenAPI is the most common example, but specs can be more detailed and include business logic constraints that OpenAPI doesn't capture.&lt;/p&gt;

&lt;p&gt;Here's where AI comes in. Instead of manually writing specifications, you can use AI to generate comprehensive specs from natural language requirements. An AI assistant trained on API design patterns, AWS best practices, and your existing codebase can produce detailed specs that cover edge cases you might not think of.&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%2F5frg6ce40166scimf8cx.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%2F5frg6ce40166scimf8cx.png" alt="Diagram to showcase the flow that we have explained earlier on" width="800" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a User Registration API with Specs
&lt;/h2&gt;

&lt;p&gt;We'll create a Lambda-backed API for user registration. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generate the Specification with AI
&lt;/h3&gt;

&lt;p&gt;Use an AI assistant (we will be using &lt;a href="https://kiro.dev/download?trk=7fcac8e0-008e-4fe0-8e3d-f72d7381e919&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;) to generate a formal specification from these requirements. Here's a prompt that works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Given the following requirements for a serverless API endpoint, generate a comprehensive specification that includes:
&lt;span class="p"&gt;
1.&lt;/span&gt; OpenAPI schema with all request/response formats
&lt;span class="p"&gt;2.&lt;/span&gt; Validation rules with specific regex patterns and constraints
&lt;span class="p"&gt;3.&lt;/span&gt; Error scenarios with HTTP status codes and error formats
&lt;span class="p"&gt;4.&lt;/span&gt; Concurrency handling strategy
&lt;span class="p"&gt;5.&lt;/span&gt; Security considerations
&lt;span class="p"&gt;6.&lt;/span&gt; DynamoDB table design for the data model

Requirements: Build a user registration endpoint that:
&lt;span class="p"&gt;-&lt;/span&gt; Accepts email, password, and optional full name
&lt;span class="p"&gt;-&lt;/span&gt; Validates email format and password strength
&lt;span class="p"&gt;-&lt;/span&gt; Prevents duplicate registrations
&lt;span class="p"&gt;-&lt;/span&gt; Returns a user ID on success
&lt;span class="p"&gt;-&lt;/span&gt; Handles concurrent requests safely
&lt;span class="p"&gt;-&lt;/span&gt; Logs failed attempts for security monitoring

Format the output as a structured JSON specification that can drive test generation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spec captures things you might not have specified in the original requirements: password regex pattern, exact error messages, the concurrency strategy using DynamoDB conditional writes, and a field for tracking registration attempts (useful for rate limiting).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# User Registration API Specification&lt;/span&gt;

&lt;span class="gu"&gt;## OpenAPI Schema&lt;/span&gt;

openapi: 3.0.3
info:
  title: User Registration API
  version: 1.0.0
paths:
  /register:
    post:
      operationId: registerUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '201':
          description: User registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Email already registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    RegisterRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        fullName:
          type: string
    RegisterResponse:
      type: object
      required: [userId, email, createdAt]
      properties:
        userId:
          type: string
          format: uuid
        email:
          type: string
        createdAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        fields:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              reason:
                type: string

&lt;span class="gu"&gt;## Validation Rules&lt;/span&gt;

&lt;span class="gu"&gt;### Email&lt;/span&gt;

| Rule ID | Constraint | Details |
|---------|-----------|---------|
| EMAIL_FORMAT | Regex pattern | &lt;span class="sb"&gt;`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`&lt;/span&gt; |
| EMAIL_LENGTH | Max length | 254 characters |
| — | Transform | Lowercase before storage and uniqueness check |

&lt;span class="gu"&gt;### Password&lt;/span&gt;

| Rule ID | Constraint | Details |
|---------|-----------|---------|
| PWD_MIN_LENGTH | Min length | 8 characters |
| PWD_MAX_LENGTH | Max length | 128 characters |
| PWD_UPPERCASE | Pattern | At least one uppercase letter |
| PWD_LOWERCASE | Pattern | At least one lowercase letter |
| PWD_DIGIT | Pattern | At least one digit |
| PWD_SPECIAL | Pattern | At least one special character |

Full pattern: &lt;span class="sb"&gt;`^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&amp;amp;*()_+\-=\[\]{}|;:',.&amp;lt;&amp;gt;?/`&lt;/span&gt; &lt;span class="sb"&gt;`` `~])[A-Za-z\d!@#$%^&amp;amp;*()_+\-=\[\]{}|;:',.&amp;lt;&amp;gt;?/` ``&lt;/span&gt; &lt;span class="sb"&gt;`~]{8,128}$`&lt;/span&gt;

&lt;span class="gu"&gt;### Full Name (optional)&lt;/span&gt;

| Rule ID | Constraint | Details |
|---------|-----------|---------|
| NAME_FORMAT | Regex pattern | &lt;span class="sb"&gt;`^[\p{L}\s'.\-]{1,100}$`&lt;/span&gt; |
| NAME_LENGTH | Max length | 100 characters |

&lt;span class="gu"&gt;## Error Scenarios&lt;/span&gt;

| ID | HTTP Status | Code | Message | Trigger |
|----|------------|------|---------|---------|
| MISSING_EMAIL | 400 | VALIDATION_ERROR | Email is required | Missing email field |
| INVALID_EMAIL | 400 | VALIDATION_ERROR | Invalid email format | Email fails pattern |
| MISSING_PASSWORD | 400 | VALIDATION_ERROR | Password is required | Missing password field |
| WEAK_PASSWORD | 400 | VALIDATION_ERROR | Password does not meet strength requirements | Password fails strength rules |
| INVALID_NAME | 400 | VALIDATION_ERROR | Full name contains invalid characters | fullName fails pattern |
| INVALID_JSON | 400 | INVALID_REQUEST | Request body must be valid JSON | Malformed or non-JSON body |
| DUPLICATE_EMAIL | 409 | CONFLICT | An account with this email already exists | ConditionalCheckFailedException from DynamoDB |
| INTERNAL_ERROR | 500 | INTERNAL_ERROR | An unexpected error occurred | Unhandled exception or DynamoDB service error |

&lt;span class="gu"&gt;## Concurrency Handling&lt;/span&gt;

&lt;span class="gs"&gt;**Strategy:**&lt;/span&gt; DynamoDB conditional writes
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Operation:**&lt;/span&gt; &lt;span class="sb"&gt;`PutItem`&lt;/span&gt; with &lt;span class="sb"&gt;`ConditionExpression: attribute_not_exists(pk)`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Partition key:**&lt;/span&gt; &lt;span class="sb"&gt;`USER#&amp;lt;normalized_email&amp;gt;`&lt;/span&gt; — the email itself is the uniqueness key
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Behavior:**&lt;/span&gt; If two concurrent requests register the same email, exactly one succeeds (201) and the other receives a 409 (&lt;span class="sb"&gt;`ConditionalCheckFailedException`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Retry policy:**&lt;/span&gt; No retry on &lt;span class="sb"&gt;`ConditionalCheckFailedException`&lt;/span&gt;; map directly to 409
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Idempotency:**&lt;/span&gt; Not idempotent by design — repeated calls with the same email return 409 after first success

&lt;span class="gu"&gt;## Security Considerations&lt;/span&gt;

&lt;span class="gu"&gt;### Password Handling&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Algorithm:**&lt;/span&gt; bcrypt, cost factor 12
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Storage:**&lt;/span&gt; Store only the bcrypt hash, never plaintext
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Input sanitization:**&lt;/span&gt; Reject null bytes in password input

&lt;span class="gu"&gt;### Rate Limiting&lt;/span&gt;

| Scope | Limit | Implementation |
|-------|-------|---------------|
| Per source IP | 10 requests/minute | API Gateway throttling |
| Global | 1000 requests/minute | API Gateway throttling |

&lt;span class="gu"&gt;### Failed Attempt Logging&lt;/span&gt;

| Event | Logged Data | Destination |
|-------|------------|-------------|
| DUPLICATE_EMAIL | Hashed email, source IP | CloudWatch Logs (structured JSON) |
| VALIDATION_ERROR | Error type, source IP | CloudWatch Logs (structured JSON) |
| INTERNAL_ERROR | Full error, correlation ID | CloudWatch Logs (structured JSON) |
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Retention:**&lt;/span&gt; 90 days
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Alerting:**&lt;/span&gt; CloudWatch alarm on &amp;gt;50 failed registrations per 5 minutes from same IP

&lt;span class="gu"&gt;### Transport &amp;amp; Headers&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; HTTPS only (enforced at API Gateway)
&lt;span class="p"&gt;-&lt;/span&gt; Max request body size: 10KB
&lt;span class="p"&gt;-&lt;/span&gt; Response headers:
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="sb"&gt;`Strict-Transport-Security: max-age=31536000; includeSubDomains`&lt;/span&gt;
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="sb"&gt;`Content-Type: application/json`&lt;/span&gt;
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="sb"&gt;`X-Content-Type-Options: nosniff`&lt;/span&gt;

&lt;span class="gu"&gt;## DynamoDB Table Design&lt;/span&gt;

&lt;span class="gs"&gt;**Table name:**&lt;/span&gt; &lt;span class="sb"&gt;`Users`&lt;/span&gt;
&lt;span class="gs"&gt;**Capacity mode:**&lt;/span&gt; PAY_PER_REQUEST
&lt;span class="gs"&gt;**Point-in-time recovery:**&lt;/span&gt; Enabled
&lt;span class="gs"&gt;**Encryption:**&lt;/span&gt; AWS_OWNED_KMS

&lt;span class="gu"&gt;### Key Schema&lt;/span&gt;

| Attribute | Type | Key | Format | Description |
|-----------|------|-----|--------|-------------|
| pk | S | HASH | &lt;span class="sb"&gt;`USER#&amp;lt;normalized_email&amp;gt;`&lt;/span&gt; | Partition key for uniqueness enforcement |

&lt;span class="gu"&gt;### Attributes&lt;/span&gt;

| Attribute | Type | Description |
|-----------|------|-------------|
| pk | S | &lt;span class="sb"&gt;`USER#user@example.com`&lt;/span&gt; |
| userId | S | UUIDv4 |
| email | S | Original email (lowercased) |
| passwordHash | S | bcrypt hash |
| fullName | S | Optional, omitted if not provided |
| createdAt | S | ISO 8601 timestamp |
| updatedAt | S | ISO 8601 timestamp |

&lt;span class="gu"&gt;### Global Secondary Index&lt;/span&gt;

| Index Name | Partition Key | Projection | Purpose |
|-----------|--------------|------------|---------|
| userId-index | userId (S) | ALL | Lookup user by UUID for downstream APIs |

&lt;span class="gu"&gt;## Test Matrix&lt;/span&gt;

&lt;span class="gu"&gt;### Happy Path&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Valid email + strong password → 201 with userId
&lt;span class="p"&gt;-&lt;/span&gt; Valid email + strong password + fullName → 201 with userId
&lt;span class="p"&gt;-&lt;/span&gt; Email with subaddressing (&lt;span class="sb"&gt;`user+tag@example.com`&lt;/span&gt;) → 201

&lt;span class="gu"&gt;### Validation Errors&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Missing email → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Missing password → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Invalid email format → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Password too short → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Password missing uppercase → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Password missing lowercase → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Password missing digit → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Password missing special char → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; fullName with invalid chars → 400 VALIDATION_ERROR
&lt;span class="p"&gt;-&lt;/span&gt; Empty request body → 400 INVALID_REQUEST
&lt;span class="p"&gt;-&lt;/span&gt; Non-JSON body → 400 INVALID_REQUEST

&lt;span class="gu"&gt;### Duplicate Handling&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Register same email twice → first 201, second 409
&lt;span class="p"&gt;-&lt;/span&gt; Register same email different case → 409 (normalization)

&lt;span class="gu"&gt;### Concurrency&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Two simultaneous registrations same email → exactly one 201, one 409
&lt;span class="p"&gt;-&lt;/span&gt; Two simultaneous registrations different emails → both 201

&lt;span class="gu"&gt;### Security&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Password not returned in response body
&lt;span class="p"&gt;-&lt;/span&gt; Password stored as bcrypt hash in DynamoDB
&lt;span class="p"&gt;-&lt;/span&gt; Oversized request body → rejected
&lt;span class="p"&gt;-&lt;/span&gt; Null bytes in password → rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a logical structure like the following:&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%2Fx2x58s39wokip2zecl15.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%2Fx2x58s39wokip2zecl15.png" alt="Architecture flow for the app we built" width="800" height="752"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Generate Tests from the Specification
&lt;/h3&gt;

&lt;p&gt;Now use the spec to generate comprehensive tests. You can write &lt;strong&gt;&lt;em&gt;Generate tests from the  specs.&lt;/em&gt;&lt;/strong&gt; Once they are generated run the tests, you should see tests are failing and indicating why are they failing as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Test Suites: 5 failed, 5 total
Tests:       21 failed, 1 passed, 22 total
Snapshots:   0 total
Time:        1.541 s, estimated 2 s
Ran all &lt;span class="nb"&gt;test &lt;/span&gt;suites.
 - Completed &lt;span class="k"&gt;in &lt;/span&gt;2.530s

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; As expected — 21 of 22 tests fail with 501 &lt;span class="o"&gt;(&lt;/span&gt;the stub&lt;span class="s1"&gt;'s "Not implemented" 
response). The one passing test is "password not returned in response body" 
which trivially passes since the stub doesn'&lt;/span&gt;t &lt;span class="k"&gt;return &lt;/span&gt;any user data.

Summary:

| Suite | Tests | Failing |
|---|---|---|
| Happy Path | 3 | 3 |
| Validation Errors | 11 | 11 |
| Duplicate Handling | 2 | 2 |
| Concurrency | 2 | 2 |
| Security | 4 | 3 |
| Total | 22 | 21 |

Ready &lt;span class="k"&gt;for &lt;/span&gt;the red → green cycle. Want me to start implementing the handler to make these pass?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests are comprehensive. They cover the happy path, all error scenarios from the spec, and even a concurrency test that sends two simultaneous requests. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Implement to Pass the Tests
&lt;/h3&gt;

&lt;p&gt;Now write the Lambda function to make these tests pass. It depends on you to either write them or allow AI to write it for you. Let's ask AI to write it:&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="o"&gt;&amp;gt;&lt;/span&gt; Implement everything needed to cover the specs.
... a few moments after implementation 

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; All 22 tests green. Here&lt;span class="s1"&gt;'s what was implemented:

- src/repository.ts — UserRepository interface with putUserIfNotExists contract, plus a setter/getter for dependency injection

- src/handler.ts — full handler with JSON parsing, body size check, email/password/name validation, bcrypt hashing, email normalization, and conditional put for duplicate prevention

- tests/helpers.ts — updated with an InMemoryUserRepository that mimics DynamoDB'&lt;/span&gt;s conditional write behavior, beforeEach reset &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;test &lt;/span&gt;isolation, and getDynamoDBMock&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;the bcrypt storage assertion

The real DynamoDB implementation can be swapped &lt;span class="k"&gt;in &lt;/span&gt;later by calling setRepository&lt;span class="o"&gt;()&lt;/span&gt; with a DynamoDB-backed class that uses attribute_not_exists&lt;span class="o"&gt;(&lt;/span&gt;pk&lt;span class="o"&gt;)&lt;/span&gt; on PutItem.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation follows the spec exactly. The validation logic uses the regex patterns from the spec. Error responses match the error formats from the spec. e.g. you can see below, the concurrency handling uses DynamoDB conditional writes as specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./repository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBUserRepository&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&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;readonly&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&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;readonly&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;putUserIfNotExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRecord&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;ConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attribute_not_exists(pk)&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ConditionalCheckFailedException&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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;h2&gt;
  
  
  Deploy and Test in the Cloud
&lt;/h2&gt;

&lt;p&gt;To deploy and test in the cloud, we will be asking to deploy the app with &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html?trk=7fcac8e0-008e-4fe0-8e3d-f72d7381e919&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CDK&lt;/a&gt;. It will create the project and allows us to deploy to the cloud through CDK's CLI tools. Ask it to &lt;strong&gt;&lt;em&gt;deploy the code and run tests on cloud&lt;/em&gt;&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;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Everything is deployed and tested. Here&lt;span class="s1"&gt;'s the summary:

Stack deployed: ServerlessTddStack in us-east-1
- API Gateway: 
https://62lf8w8qnk.execute-api.us-east-1.amazonaws.com/prod/register
- Lambda: Node 20, 256MB, esbuild-bundled
- DynamoDB: Users table with pk (HASH) + userId-index GSI, PAY_PER_REQUEST, 
PITR enabled

Test results:
- 22 unit tests ✅ (in-memory mock)
- 5 integration tests ✅ (live API)

Files added:
- src/dynamodb-repository.ts — real DynamoDB repo with conditional writes
- src/lambda.ts — Lambda entry point wiring the DynamoDB repo
- infra/stack.ts — CDK stack definition
- infra/app.ts — CDK app entry
- cdk.json — CDK config
- tests/integration.test.ts — live API tests
- jest.integration.config.js — integration test config

Commands:
- npm test — run unit tests
- 
API_ENDPOINT=https://62lf8w8qnk.execute-api.us-east-1.amazonaws.com/prod/register npm run test:integration
— run integration tests
- npm run destroy — tear down the stack
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing in the cloud catches issues that local tests miss: IAM permission problems, DynamoDB throughput limits, API Gateway timeout configurations, and cold start behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Spec Evolution
&lt;/h2&gt;

&lt;p&gt;Specs aren't static. Requirements change. When they do, regenerate the spec with AI, diff it against the current version, and update tests accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Update spec to give user an option to add a profile picture URL. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run the diff, you get a clear picture of what changed.&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="o"&gt;&amp;gt;&lt;/span&gt; Now I&lt;span class="s1"&gt;'ll update the spec with the profile picture URL field across all 
relevant sections.
I'&lt;/span&gt;ll modify the following file: docs/user-registration-spec.md &lt;span class="o"&gt;(&lt;/span&gt;using tool: write&lt;span class="o"&gt;)&lt;/span&gt;

  57, 57:         fullName:
  58, 58:           &lt;span class="nb"&gt;type&lt;/span&gt;: string
+     59:         profilePictureUrl:
+     60:           &lt;span class="nb"&gt;type&lt;/span&gt;: string
+     61:           format: uri
  59, 62:     RegisterResponse:

Updating: docs/user-registration-spec.md
 - Completed &lt;span class="k"&gt;in &lt;/span&gt;0.1s

I&lt;span class="s1"&gt;'ll modify the following file: docs/user-registration-spec.md (using tool: write)

  116, 116: ### Full Name (optional)
  117, 117: 
  118, 118: | Rule ID | Constraint | Details |
  119, 119: |---------|-----------|---------|
  120, 120: | NAME_FORMAT | Regex pattern | `^[\p{L}\s'&lt;/span&gt;.&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="o"&gt;]{&lt;/span&gt;1,100&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$`&lt;/span&gt; |
  121, 121: | NAME_LENGTH | Max length | 100 characters |
+      122: 
+      123: &lt;span class="c"&gt;### Profile Picture URL (optional)&lt;/span&gt;
+      124: 
+      125: | Rule ID | Constraint | Details |
+      126: |---------|-----------|---------|
+      127: | PIC_URL_FORMAT | Regex pattern | &lt;span class="sb"&gt;`&lt;/span&gt;^https://[^&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="o"&gt;]{&lt;/span&gt;1,2048&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$`&lt;/span&gt; |
+      128: | PIC_URL_LENGTH | Max length | 2048 characters |
+      129: | PIC_URL_SCHEME | Scheme | Must be HTTPS only |

Updating: docs/user-registration-spec.md
 - Completed &lt;span class="k"&gt;in &lt;/span&gt;0.1s

I&lt;span class="s1"&gt;'ll modify the following file: docs/user-registration-spec.md (using tool: write)

  139, 139: | INVALID_NAME | 400 | VALIDATION_ERROR | Full name contains invalid characters | fullName fails pattern |
- 140     : | INVALID_JSON | 400 | INVALID_REQUEST | Request body must be valid JSON | Malformed or non-JSON body |
+      140: | INVALID_PROFILE_PIC_URL | 400 | VALIDATION_ERROR | Profile picture URL must be a valid HTTPS URL | profilePictureUrl fails pattern |
+      141: | INVALID_JSON | 400 | INVALID_REQUEST | Request body must be valid JSON | Malformed or non-JSON body |

Updating: docs/user-registration-spec.md
 - Completed in 0.1s

I'&lt;/span&gt;ll modify the following file: docs/user-registration-spec.md &lt;span class="o"&gt;(&lt;/span&gt;using tool: write&lt;span class="o"&gt;)&lt;/span&gt;

  211, 211: | fullName | S | Optional, omitted &lt;span class="k"&gt;if &lt;/span&gt;not provided |
+      212: | profilePictureUrl | S | Optional, omitted &lt;span class="k"&gt;if &lt;/span&gt;not provided |
  212, 213: | createdAt | S | ISO 8601 timestamp |

Updating: docs/user-registration-spec.md
 - Completed &lt;span class="k"&gt;in &lt;/span&gt;0.2s

I&lt;span class="s1"&gt;'ll modify the following file: docs/user-registration-spec.md (using tool: write)

  227, 227: - Valid email + strong password + fullName → 201 with userId
+      228: - Valid email + strong password + profilePictureUrl → 201 with userId
  228, 229: - Email with subaddressing (`user+tag@example.com`) → 201

Updating: docs/user-registration-spec.md
 - Completed in 0.1s

I'&lt;/span&gt;ll modify the following file: docs/user-registration-spec.md &lt;span class="o"&gt;(&lt;/span&gt;using tool: write&lt;span class="o"&gt;)&lt;/span&gt;

  241, 241: - fullName with invalid chars → 400 VALIDATION_ERROR
+      242: - profilePictureUrl with HTTP scheme → 400 VALIDATION_ERROR
+      243: - profilePictureUrl with invalid format → 400 VALIDATION_ERROR
  242, 244: - Empty request body → 400 INVALID_REQUEST

Updating: docs/user-registration-spec.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to your changes, you can add new test cases for added scenarios, remove obsolete tests, and update tests for changed behavior. This keeps your test suite and implementation synchronized with requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs and When to Use a Different Approach
&lt;/h2&gt;

&lt;p&gt;This mixture of AI and spec-driven approach works well for APIs with complex validation, multiple error conditions, and strict contracts (like public APIs or microservices with many consumers). It's less useful for experimental features where requirements are genuinely unknown.&lt;/p&gt;

&lt;p&gt;If you're building a prototype to test a hypothesis, traditional TDD with minimal specs is faster. Write a few high-level tests, implement quickly, and iterate. Once the concept proves valuable, then formalize it with specs.&lt;/p&gt;

&lt;p&gt;For workflows with complex business logic but simple I/O contracts, consider property-based testing instead. Tools like fast-check generate random inputs to find edge cases your specs might miss. Combine property tests with spec-driven tests for comprehensive coverage.&lt;/p&gt;

&lt;p&gt;For systems with heavy external dependencies (third-party APIs, legacy services), mocks are unavoidable. But keep mocked tests separate from integration tests. Run mocked tests frequently during development for fast feedback. Run cloud integration tests before merging to catch configuration drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making This Practical
&lt;/h2&gt;

&lt;p&gt;To adopt spec-driven development in your team:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with one API or service, not the whole system&lt;/li&gt;
&lt;li&gt;Use AI to generate initial specs, then review and refine them&lt;/li&gt;
&lt;li&gt;Store specs in version control alongside code&lt;/li&gt;
&lt;li&gt;Generate tests from specs, but also write additional tests for edge cases you discover&lt;/li&gt;
&lt;li&gt;Make spec generation part of your development workflow, not a one-time task&lt;/li&gt;
&lt;li&gt;Review spec diffs in pull requests just like code diffs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is to make specifications a living part of your codebase, not documentation that falls out of date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning Up Resources
&lt;/h2&gt;

&lt;p&gt;When you're done experimenting, delete the CloudFormation stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
cdk destroy

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the API Gateway, Lambda function, and DynamoDB table. Check the AWS Console to confirm no orphaned resources remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/testing-guide.html" rel="noopener noreferrer"&gt;Lambda Testing Guide&lt;/a&gt; - comprehensive testing strategies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aws-samples/serverless-test-samples" rel="noopener noreferrer"&gt;Serverless Test Samples Repository&lt;/a&gt; - practical examples&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI Specification&lt;/a&gt; - API contract standard&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dubzzz/fast-check" rel="noopener noreferrer"&gt;Property-Based Testing with fast-check&lt;/a&gt; - discover edge cases automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connect with me on &lt;a href="https://twitter.com/salihgueler" rel="noopener noreferrer"&gt;Twitter/X&lt;/a&gt;, &lt;a href="https://linkedin.com/in/salihgueler" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/salihgueler" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and &lt;a href="https://bsky.app/profile/salihgueler" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; at &lt;a class="mentioned-user" href="https://dev.to/salih"&gt;@salih&lt;/a&gt;.dev.&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>spec</category>
      <category>serverless</category>
      <category>aidlc</category>
    </item>
    <item>
      <title>Hide regions and services in the AWS console</title>
      <dc:creator>Sean Boult</dc:creator>
      <pubDate>Wed, 15 Apr 2026 21:26:43 +0000</pubDate>
      <link>https://forem.com/aws/hide-regions-and-services-in-the-aws-console-1m09</link>
      <guid>https://forem.com/aws/hide-regions-and-services-in-the-aws-console-1m09</guid>
      <description>&lt;p&gt;Tired of scrolling through regions and services you'll never use?&lt;/p&gt;

&lt;p&gt;You just want to...&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%2Fkbswyi3rgrkoso9ep0f6.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%2Fkbswyi3rgrkoso9ep0f6.png" alt=" " width="576" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS now lets you hide regions and services from your console. Here's how.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; This is a visual preference, not a security control. Users can still access hidden regions and services directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hide Regions
&lt;/h2&gt;

&lt;p&gt;That region dropdown doesn't need to show every region AWS offers.&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%2Fv3lv5j0ibchnon2gu8rp.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%2Fv3lv5j0ibchnon2gu8rp.png" alt=" " width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://us-east-1.console.aws.amazon.com/settings/home?region=us-east-1" rel="noopener noreferrer"&gt;Unified Settings → Account settings&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Find &lt;strong&gt;Visible Regions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Deselect the regions you don't use. Keep the ones where you actually run workloads.&lt;/li&gt;
&lt;li&gt;Save.&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%2Fzvggpjvfsdnaskk1nscm.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%2Fzvggpjvfsdnaskk1nscm.png" alt=" " width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done. Your region dropdown now only shows what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hide Services
&lt;/h2&gt;

&lt;p&gt;Same place, different setting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://us-east-1.console.aws.amazon.com/settings/home?region=us-east-1" rel="noopener noreferrer"&gt;Unified Settings → Account settings&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Find &lt;strong&gt;Visible Services&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Deselect the services you don't need. I'd recommend keeping &lt;strong&gt;Billing&lt;/strong&gt; so you can still manage costs.&lt;/li&gt;
&lt;li&gt;Save.&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%2F85ge6syjpmn6z9twyh2u.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%2F85ge6syjpmn6z9twyh2u.png" alt=" " width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When You Access Something Hidden?
&lt;/h2&gt;

&lt;p&gt;You'll see a page like this if you navigate to a hidden region/service directly:&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%2F8os34ulrswigxdakdb9t.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%2F8os34ulrswigxdakdb9t.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And some parts of the console will show a restricted indicator:&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%2F2r765sixs8odrrkl0yfz.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%2F2r765sixs8odrrkl0yfz.png" alt=" " width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, nothing is actually blocked. It's just hidden from the default navigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/customize-your-aws-management-console-experience-with-visual-settings-including-account-color-region-and-service-visibility/" rel="noopener noreferrer"&gt;AWS Blog: Customize your console experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/03/account-customizations-console/" rel="noopener noreferrer"&gt;What's New: Account customizations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awsconsolehelpdocs/latest/gsg/visible-regions-services.html" rel="noopener noreferrer"&gt;Docs: Visible regions and services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
    </item>
    <item>
      <title>Cómo crear un agente de IA desde cero. Open source, local y gratis.</title>
      <dc:creator>Hazel Saenz</dc:creator>
      <pubDate>Wed, 15 Apr 2026 16:15:25 +0000</pubDate>
      <link>https://forem.com/aws/como-crear-un-agente-de-ia-desde-cero-open-source-local-y-gratis-k6d</link>
      <guid>https://forem.com/aws/como-crear-un-agente-de-ia-desde-cero-open-source-local-y-gratis-k6d</guid>
      <description>&lt;p&gt;&lt;em&gt;De un modelo que solo responde a uno que busca, razona y recuerda con código que puedes leer, forkear y contribuir.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Todos hemos usado ChatGPT, Claude, Google Gemini. Les preguntas algo, te responden. Les pides que resuman un texto, lo resumen. Les dices que escriban un correo, lo escriben. Pero hay un límite. Un modelo de lenguaje puede hablar de música. Puede hablar de géneros. Puede hablar de artistas.&lt;/p&gt;

&lt;p&gt;Pero no puede &lt;strong&gt;buscar&lt;/strong&gt; en tu biblioteca de canciones.&lt;br&gt;
No puede &lt;strong&gt;analizar&lt;/strong&gt; el mood de una playlist.&lt;br&gt;
No puede &lt;strong&gt;recordar&lt;/strong&gt; que la semana pasada te armó una playlist de hip-hop para el gym y te encantó.&lt;/p&gt;

&lt;p&gt;Un modelo solo habla.&lt;br&gt;
Un agente &lt;strong&gt;hace cosas&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Y esa diferencia es más importante de lo que parece.&lt;/p&gt;
&lt;h2&gt;
  
  
  ¿Qué es un agente de IA?
&lt;/h2&gt;

&lt;p&gt;Si ya usaste la API de OpenAI o jugaste con modelos en Hugging Face, sabes que un LLM recibe texto y genera texto. Punto. No ejecuta código, no consulta bases de datos, no llama APIs.&lt;/p&gt;

&lt;p&gt;Un agente agrega dos cosas encima de eso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Herramientas&lt;/strong&gt; (tools): funciones que el modelo puede invocar cualquier función de Python que le expongas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un agent loop&lt;/strong&gt;: un ciclo donde el modelo razona, decide si necesita llamar una herramienta, la ejecuta, recibe el resultado, y vuelve a razonar hasta tener una respuesta final&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No es magia. Es un &lt;code&gt;while&lt;/code&gt; loop con tool dispatch.&lt;/p&gt;

&lt;p&gt;Imagina que le preguntas a un agente DJ: "Armame una playlist para una cena tranquila con amigos."&lt;/p&gt;

&lt;p&gt;Sin tools, el modelo genera texto basado en su entrenamiento, no conoce tu biblioteca, no sabe qué se lanzó la semana pasada (su conocimiento tiene fecha de corte), y no tiene idea de tus gustos. Con tools, hace esto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Razona&lt;/strong&gt;: "Necesito buscar canciones con energía baja"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoca&lt;/strong&gt;: &lt;code&gt;buscar_canciones(mood="chill", genero="jazz")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recibe el resultado&lt;/strong&gt;: una lista de canciones reales de tu biblioteca&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Razona de nuevo&lt;/strong&gt;: "Debería verificar que la energía sea consistente"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoca&lt;/strong&gt;: &lt;code&gt;analizar_energia(canciones=[...])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Genera la respuesta final&lt;/strong&gt;: una playlist curada con datos reales&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El modelo decide qué herramientas invocar y en qué orden. Tú no escribes la orquestación, el modelo la resuelve en runtime. Eso es el enfoque &lt;strong&gt;model-driven&lt;/strong&gt;, y es lo que diferencia a un agente de un pipeline hardcodeado con &lt;code&gt;if/else&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%2Fnu1keudgaofqif0foqg3.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%2Fnu1keudgaofqif0foqg3.png" alt="Agent Loop — el ciclo de razonamiento de un agente" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Por qué open source importa (de verdad)
&lt;/h2&gt;

&lt;p&gt;Cuando hablamos de agentes de IA, hay muchas opciones. Frameworks propietarios, APIs cerradas, SDKs que solo funcionan con un proveedor específico.&lt;/p&gt;

&lt;p&gt;El problema no es que existan. El problema es lo que pierdes cuando los usas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No puedes leer la implementación del agent loop, si el modelo toma una secuencia de tool calls inesperada, no puedes entender por qué ni cambiar el comportamiento&lt;/li&gt;
&lt;li&gt;No puedes modificar el comportamiento del loop cuando algo no funciona como esperas&lt;/li&gt;
&lt;li&gt;Si tu proveedor de modelo no está soportado, no puedes contribuir la integración tú mismo ni aprovechar una que la comunidad ya haya creado&lt;/li&gt;
&lt;li&gt;No puedes contribuir fixes o mejoras que beneficien a otros&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open source no es solo "código gratis". Sí, requiere más responsabilidad de tu parte: tú debuggeas, tú actualizas, tú decides. Pero esa responsabilidad viene con control total. Es &lt;strong&gt;transparencia&lt;/strong&gt;. Es poder abrir el source del agent loop y ver que es un &lt;code&gt;while&lt;/code&gt; loop con tool dispatch. Es poder poner un breakpoint en el ciclo de razonamiento cuando tu agente hace algo inesperado.&lt;/p&gt;

&lt;p&gt;Y eso es exactamente lo que vamos a usar.&lt;/p&gt;
&lt;h2&gt;
  
  
  El stack: Python + Strands Agents + Ollama
&lt;/h2&gt;

&lt;p&gt;Todo lo que vas a ver en este artículo corre con tres cosas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;/strong&gt;: el lenguaje (licencia &lt;a href="https://docs.python.org/3/license.html" rel="noopener noreferrer"&gt;PSF&lt;/a&gt;, open source)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;&lt;/strong&gt;: un SDK open source (licencia Apache 2.0) para construir agentes de IA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt;: un runtime open source (licencia MIT) para correr modelos de lenguaje localmente&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo open source. Todo en tu laptop. Sin API keys. Sin vendor lock-in. Sin datos saliendo de tu máquina.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; toma un enfoque &lt;a href="https://strandsagents.com/blog/strands-agents-model-driven-approach/" rel="noopener noreferrer"&gt;model-driven&lt;/a&gt;: tú defines las herramientas como funciones de Python, escribes un system prompt, y el agent loop se encarga de la ejecución. Sin definiciones de pasos, sin grafos de workflow. Solo código.&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%2Fbwvgh8qkbbtvd1121h7w.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%2Fbwvgh8qkbbtvd1121h7w.png" alt="Las 4 capas que vamos a construir" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Ollama: modelos de IA en tu laptop
&lt;/h2&gt;

&lt;p&gt;Antes de construir el agente, necesitamos un modelo de lenguaje corriendo localmente. Y aquí es donde entra &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ollama es un runtime open source para correr LLMs en tu máquina. Si conoces Docker, la analogía es directa: &lt;code&gt;ollama pull&lt;/code&gt; descarga un modelo, &lt;code&gt;ollama serve&lt;/code&gt; lo expone como API REST en &lt;code&gt;localhost:11434&lt;/code&gt;. Sin cuentas, sin API keys, sin datos saliendo de tu red.&lt;/p&gt;
&lt;h3&gt;
  
  
  Instalación
&lt;/h3&gt;

&lt;p&gt;En macOS y Linux:&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;-fsSL&lt;/span&gt; https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En Windows, descarga el instalador desde &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;ollama.com/download&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Descargar y correr un modelo
&lt;/h3&gt;

&lt;p&gt;Para este artículo vamos a usar &lt;a href="https://ollama.com/library/llama3.1" rel="noopener noreferrer"&gt;&lt;code&gt;llama3.1:8b&lt;/code&gt;&lt;/a&gt; — tiene buen soporte para &lt;a href="https://ollama.com/search?c=tools" rel="noopener noreferrer"&gt;tool-calling&lt;/a&gt; (que es lo que necesitamos para que el agente invoque funciones) y maneja español razonablemente bien:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Son unos 4.9 GB. Solo necesitas hacerlo una vez.&lt;/p&gt;

&lt;p&gt;Ahora levanta el servidor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Si instalaste la app de macOS, el servidor ya está corriendo en segundo plano y no necesitas ejecutar &lt;code&gt;ollama serve&lt;/code&gt;. Solo es necesario si instalaste con Homebrew o en Linux.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ollama queda escuchando en &lt;code&gt;http://localhost:11434&lt;/code&gt;. Puedes verificar con un &lt;code&gt;curl http://localhost:11434/api/tags&lt;/code&gt; para ver los modelos disponibles.&lt;/p&gt;

&lt;p&gt;Ollama soporta &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;muchos modelos&lt;/a&gt;: Llama, Mistral, Phi, Gemma, y más. Si tu máquina tiene GPU, los corre acelerados automáticamente. Si no, usa CPU (más lento, pero funciona).&lt;/p&gt;

&lt;p&gt;Lo importante: &lt;strong&gt;el modelo corre en tu máquina&lt;/strong&gt;. Tus datos no salen de tu laptop. Y el código de Ollama es open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capa 1: Un agente que solo habla
&lt;/h2&gt;

&lt;p&gt;Empecemos por lo más simple. Un agente que es solo un modelo + un prompt.&lt;/p&gt;

&lt;p&gt;Primero, instala el SDK de Strands con soporte para Ollama.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Strands Agents requiere &lt;strong&gt;Python 3.10 o superior&lt;/strong&gt;. El Python que viene preinstalado en macOS (3.9.6) no es compatible. Si no tienes una versión reciente, instálala con &lt;code&gt;brew install python@3.13&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Crea un entorno virtual para no contaminar tu sistema. Con pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'strands-agents[ollama]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O si usas &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; (más rápido y gestiona Python por ti):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv &lt;span class="nt"&gt;--python&lt;/span&gt; 3.13
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'strands-agents[ollama]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora, 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="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.models.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OllamaModel&lt;/span&gt;

&lt;span class="n"&gt;modelo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaModel&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;llama3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&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;dj&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;modelo&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;Eres un DJ y curador musical experto.
    Respondes en español, con onda y buen gusto.
    Recomiendas música basándote en el mood, la ocasión, y los gustos del usuario.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;dj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recomiéndame algo para escuchar mientras programo&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;Cinco líneas relevantes. Eso es un agente.&lt;/p&gt;

&lt;p&gt;Bueno, técnicamente es un modelo con un prompt — todavía no tiene herramientas, así que solo responde con su conocimiento general. Si le pides una playlist con canciones de tu biblioteca, va a alucinar títulos que suenan razonables pero no existen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un agente es un modelo + un prompt + un loop. Eso es todo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Y fíjate: el modelo corre local. No hay API key, no hay vendor, no hay datos saliendo de tu laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capa 2: Dale herramientas — la biblioteca de canciones
&lt;/h2&gt;

&lt;p&gt;El agente básico no conoce tu música. Sabe de artistas y géneros en general porque fue entrenado con texto de internet. Pero no sabe que en tu biblioteca tienes ese álbum de la Ley que siempre pones cuando cocinas.&lt;/p&gt;

&lt;p&gt;Para eso necesita una herramienta.&lt;/p&gt;

&lt;p&gt;Pero primero, necesitamos datos. Crea un archivo &lt;code&gt;data/canciones.json&lt;/code&gt; con tu biblioteca de canciones. Cada canción necesita: &lt;code&gt;titulo&lt;/code&gt;, &lt;code&gt;artista&lt;/code&gt;, &lt;code&gt;genero&lt;/code&gt;, &lt;code&gt;mood&lt;/code&gt;, &lt;code&gt;energia&lt;/code&gt; (0-100), y &lt;code&gt;duracion_min&lt;/code&gt;. Aquí tienes un ejemplo para arrancar:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ver ejemplo de data/canciones.json (30 canciones)&lt;/strong&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"So What"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Miles Davis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jazz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.3&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Take Five"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dave Brubeck"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jazz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.4&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blue in Green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Miles Davis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jazz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.4&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fly Me to the Moon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Frank Sinatra"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jazz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.5&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Feeling Good"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nina Simone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jazz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.9&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bohemian Rhapsody"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Queen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.9&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hotel California"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Eagles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6.5&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smells Like Teen Spirit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nirvana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.0&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Under Pressure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Queen &amp;amp;amp; David Bowie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.0&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Creep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Radiohead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.9&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gasolina"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Daddy Yankee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reggaetón"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.1&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dákiti"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bad Bunny &amp;amp;amp; Jhay Cortez"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reggaetón"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.3&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tití Me Preguntó"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bad Bunny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reggaetón"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.0&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pepas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Farruko"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reggaetón"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.5&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"La Bicicleta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shakira &amp;amp;amp; Carlos Vives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reggaetón"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Midnight City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M83"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electrónica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.0&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Strobe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deadmau5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electrónica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.3&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Around the World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Daft Punk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electrónica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiesta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.1&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Intro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The xx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electrónica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.1&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Teardrop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Massive Attack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electrónica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.3&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disorder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Joy Division"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.6&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Do I Wanna Know?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Arctic Monkeys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.6&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Somebody Else"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The 1975"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.7&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Electric Feel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MGMT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tongue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MNEK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.5&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Only Shallow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Bloody Valentine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoegaze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.2&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"When You Sleep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Bloody Valentine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoegaze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.1&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alison"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Slowdive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoegaze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melancólico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.1&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cherry-coloured Funk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cocteau Twins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoegaze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.5&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="nl"&gt;"titulo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vapour Trail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"artista"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ride"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genero"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoegaze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"energético"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"energia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duracion_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.2&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;p&gt;Puedes modificar este archivo con tu propia música. Lo importante es que mantenga la misma estructura.&lt;/p&gt;

&lt;p&gt;Ahora sí, en Strands, crear una herramienta es decorar una función de Python:&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;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.models.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OllamaModel&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Cargar biblioteca local de canciones
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&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/canciones.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;BIBLIOTECA&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;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&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;buscar_canciones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;genero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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;mood&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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;artista&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&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;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;Busca canciones en la biblioteca musical del usuario.

    Args:
        genero: Género musical (ej: rock, jazz, reggaetón, electrónica)
        mood: Estado de ánimo o energía (ej: chill, fiesta, melancólico, energético)
        artista: Nombre del artista o banda
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BIBLIOTECA&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;genero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;genero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;genero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&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;mood&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mood&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&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;artista&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resultados&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;artista&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artista&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&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;resultados&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 encontré canciones con esos criterios en tu biblioteca.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&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;resultados&lt;/span&gt;&lt;span class="p"&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;ensure_ascii&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;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate en el decorador &lt;code&gt;@tool&lt;/code&gt;. Convierte cualquier función de Python en una herramienta que el agente puede invocar. El docstring no es decorativo — el SDK lo &lt;a href="https://strandsagents.com/docs/user-guide/concepts/tools/custom-tools/" rel="noopener noreferrer"&gt;parsea para generar un tool spec&lt;/a&gt; (JSON schema) que es lo que el modelo recibe para decidir cuándo y cómo usar la herramienta. Si el docstring es vago, el modelo no va a saber cuándo llamarla.&lt;/p&gt;

&lt;p&gt;Ahora conectamos la herramienta al agente:&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;modelo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaModel&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;llama3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dj&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;modelo&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;Eres un DJ y curador musical experto.
    Usa la herramienta buscar_canciones para encontrar música en la biblioteca del usuario.
    Siempre basa tus recomendaciones en canciones que el usuario realmente tiene.&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;buscar_canciones&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;dj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quiero escuchar jazz mientras trabajo&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;Ahora cuando le pides jazz para trabajar, el agente no inventa. Llama a &lt;code&gt;buscar_canciones(genero="jazz", mood="chill")&lt;/code&gt;, obtiene canciones reales de tu biblioteca, y arma una recomendación con lo que realmente tienes.&lt;/p&gt;

&lt;p&gt;Pasó de "saber cosas" a "hacer cosas".&lt;/p&gt;

&lt;p&gt;Y el sistema de tools es extensible. Existe un &lt;a href="https://github.com/strands-agents/tools" rel="noopener noreferrer"&gt;paquete de community tools&lt;/a&gt; donde cualquiera puede publicar los suyos. ¿Tienes una API que quieres conectar? Escribe un &lt;code&gt;@tool&lt;/code&gt; y compártelo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capa 3: Más herramientas, más inteligencia
&lt;/h2&gt;

&lt;p&gt;Un tool está bien. Pero la cosa se pone interesante cuando agregas varios.&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analizar_energia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canciones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;Analiza el nivel de energía promedio de una lista de canciones y sugiere el orden ideal.

    Args:
        canciones: Lista de nombres de canciones a analizar
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;energia_map&lt;/span&gt; &lt;span class="o"&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;cancion&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BIBLIOTECA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;energia_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cancion&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;titulo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cancion&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;energia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;analisis&lt;/span&gt; &lt;span class="o"&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;titulo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;canciones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;energia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;energia_map&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="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;analisis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;titulo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;energia&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Ordenar por energía para un flow natural
&lt;/span&gt;    &lt;span class="n"&gt;analisis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;promedio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energia&lt;/span&gt;&lt;span class="sh"&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;analisis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;analisis&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;analisis&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;return&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;energia_promedio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promedio&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flow&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;ascendente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;analisis&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energia&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;analisis&lt;/span&gt;&lt;span class="p"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;descendente&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;orden_sugerido&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;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;titulo&lt;/span&gt;&lt;span class="sh"&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;analisis&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nota&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;Energía baja → alta para ir subiendo el mood&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;promedio&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Playlist con buena energía&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;duracion_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canciones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;Calcula la duración total de una playlist y sugiere si necesita más canciones.

    Args:
        canciones: Lista de nombres de canciones
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;duracion_map&lt;/span&gt; &lt;span class="o"&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;cancion&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BIBLIOTECA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;duracion_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cancion&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;titulo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cancion&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;duracion_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duracion_map&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;3.5&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;canciones&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;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;canciones&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;canciones&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duracion_total_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duracion_formato&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="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;h &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;else&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="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;min&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;sugerencia&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;Playlist corta, podrías agregar más canciones&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buena duración&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora el agente tiene tres herramientas. Y aquí es donde se ve el agent loop en acció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="n"&gt;dj&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;modelo&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;Eres un DJ y curador musical experto.
    Usa tus herramientas para armar playlists basadas en la biblioteca real del usuario.
    Considera el mood, la energía, y la duración para crear una experiencia coherente.&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;buscar_canciones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analizar_energia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duracion_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;dj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Armame una playlist de una hora para una fiesta en casa&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;El agente no ejecuta las herramientas en un orden predefinido. No hay un pipeline que diga "primero busca, luego analiza, luego calcula duración". El modelo recibe los tool specs (nombre, descripción, parámetros) y en cada iteración del loop decide: ¿necesito más información? ¿Qué tool me la da? ¿Con qué parámetros?&lt;/p&gt;

&lt;p&gt;En este caso: llama a &lt;code&gt;buscar_canciones(mood="fiesta")&lt;/code&gt;, analiza la energía con &lt;code&gt;analizar_energia(canciones=[...])&lt;/code&gt; para ordenarlas con un flow ascendente, y verifica la duración con &lt;code&gt;duracion_playlist(canciones=[...])&lt;/code&gt; para cubrir la hora completa. Si la duración no alcanza, puede volver a llamar &lt;code&gt;buscar_canciones&lt;/code&gt; con otros criterios.&lt;/p&gt;

&lt;p&gt;Tú no orquestaste nada. El modelo resolvió la secuencia en runtime.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota sobre modelos locales:&lt;/strong&gt; con &lt;code&gt;llama3.1:8b&lt;/code&gt;, el agente invoca los tools correctamente pero a veces ignora los resultados y alucina canciones que no están en tu biblioteca. Esto es una limitación del tamaño del modelo (8B parámetros), no del SDK. Con una sola herramienta (Capa 2) funciona bien; con múltiples tools el modelo se confunde más fácilmente. Si necesitas tool-calling más confiable, usa un modelo más grande (&lt;code&gt;llama3.1:70b&lt;/code&gt;) o un modelo cloud como Claude o GPT-4 (ver la sección "De tu laptop a la nube").&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y si quieres entender cómo funciona por dentro, el código está ahí. El agent loop de Strands no es una caja negra. Es código abierto que puedes leer en &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capa 4: Memoria — el DJ te conoce
&lt;/h2&gt;

&lt;p&gt;Hay un problema con todo lo que hemos construido hasta ahora.&lt;/p&gt;

&lt;p&gt;Cada conversación empieza de cero.&lt;/p&gt;

&lt;p&gt;Le dices "me encanta el indie rock", cierras la conversación, vuelves al día siguiente, y el agente no tiene idea de quién eres. Es como ir a un bar donde el DJ cambia cada vez que parpadeas.&lt;/p&gt;

&lt;p&gt;Strands resuelve esto con session management. &lt;code&gt;FileSessionManager&lt;/code&gt; persiste el historial de conversación a disco como archivos JSON, organizados por sesión y agente. Cuando el agente se inicializa con un &lt;code&gt;session_id&lt;/code&gt; existente, carga los mensajes anteriores y el modelo los tiene como contexto.&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;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OllamaModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.session.file_session_manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileSessionManager&lt;/span&gt;

&lt;span class="n"&gt;modelo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaModel&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;llama3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# El session manager guarda las conversaciones a disco
# session_id identifica al usuario, storage_dir es donde se guardan los archivos
&lt;/span&gt;&lt;span class="n"&gt;session_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FileSessionManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_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;usuario-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./sesiones&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dj&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;modelo&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;Eres un DJ y curador musical experto.
    Recuerdas los gustos del usuario entre conversaciones.
    Si el usuario ya te dijo qué le gusta, úsalo para personalizar tus playlists.&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;buscar_canciones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analizar_energia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duracion_playlist&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Primera conversación
&lt;/span&gt;&lt;span class="nf"&gt;dj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Me encanta el indie rock y el shoegaze. No soporto el reggaetón.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... tiempo después, otra conversación ...
&lt;/span&gt;
&lt;span class="c1"&gt;# El agente recuerda
&lt;/span&gt;&lt;span class="nf"&gt;dj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Armame algo para el viernes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → Arma una playlist de indie rock y shoegaze, sin reggaetón
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pocas líneas y tu agente tiene continuidad.&lt;/p&gt;

&lt;p&gt;Y aquí viene lo interesante desde el punto de vista open source: &lt;code&gt;FileSessionManager&lt;/code&gt; es una implementación de la interfaz &lt;code&gt;SessionManager&lt;/code&gt;. Guarda sesiones a disco como archivos JSON organizados por sesión y agente. Pero si quieres guardar sesiones en Redis, en Postgres, o en S3, implementas la interfaz y listo. Ya existen session managers de la comunidad, como el de &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/strands-sdk-memory.html" rel="noopener noreferrer"&gt;AgentCore Memory&lt;/a&gt;. La arquitectura está diseñada para que la comunidad la extienda.&lt;/p&gt;

&lt;h2&gt;
  
  
  De tu laptop a la nube (en una línea)
&lt;/h2&gt;

&lt;p&gt;Todo lo que hemos construido corre localmente. Ollama en tu máquina, modelo en tu máquina, datos en tu máquina.&lt;/p&gt;

&lt;p&gt;Pero si quieres compartir tu agente con el mundo, necesitas algo más.&lt;/p&gt;

&lt;p&gt;Y aquí está lo elegante de la arquitectura: el SDK abstrae el model provider detrás de una interfaz común. Cambiar de proveedor es cambiar la instanciación del modelo — el resto del código no se entera.&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;# Local con Ollama
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OllamaModel&lt;/span&gt;
&lt;span class="n"&gt;modelo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaModel&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;llama3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# En la nube con Amazon Bedrock
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="n"&gt;modelo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&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;us.anthropic.claude-sonnet-4-20250514-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&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;El resto del código del agente no cambia. Ni una línea. Los tools, el system prompt, el session manager: todo igual. Esto es posible porque Strands define una interfaz &lt;code&gt;Model&lt;/code&gt; que todos los providers implementan — &lt;code&gt;OllamaModel&lt;/code&gt;, &lt;code&gt;BedrockModel&lt;/code&gt;, &lt;code&gt;OpenAIModel&lt;/code&gt;, &lt;code&gt;AnthropicModel&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Esa es la arquitectura en capas:&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%2Ftpvjbasy4dx5h5cke87y.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%2Ftpvjbasy4dx5h5cke87y.png" alt="Arquitectura en capas — tu código, el SDK, y el provider" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La capa open source es la constante. El proveedor de abajo es la variable. Ollama, Bedrock, OpenAI, Anthropic, Google Gemini, LiteLLM: todos son plugins que implementan la misma interfaz. Sin esa capa de abstracción, estás atado al SDK del vendor con el que empezaste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendiste
&lt;/h2&gt;

&lt;p&gt;Un agente de IA no es magia. Es un modelo + herramientas + un loop de razonamiento.&lt;/p&gt;

&lt;p&gt;Construimos un DJ de playlists capa por capa: primero un agente que solo habla, luego le dimos herramientas para buscar en una biblioteca real de canciones, después agregamos más tools y vimos cómo el modelo decide solo qué llamar y en qué orden, le dimos memoria para que recuerde tus gustos, y al final mostramos que el mismo código puede correr local o en la nube cambiando una línea.&lt;/p&gt;

&lt;p&gt;Todo con Python, &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; y &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Todo open source. Todo en tu laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué sigue
&lt;/h2&gt;

&lt;p&gt;Si quieres ir más allá:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Documentación de Strands Agents&lt;/a&gt; — guías, ejemplos, y API reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Repo de Strands en GitHub&lt;/a&gt; — el código fuente completo, licencia Apache 2.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/strands-agents/tools" rel="noopener noreferrer"&gt;Community tools&lt;/a&gt; — herramientas creadas por la comunidad que puedes usar y extender&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; — para correr modelos localmente&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El DJ sabe de música. ¿Qué vas a construir tú?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;¿Te resultó útil este artículo?&lt;/strong&gt; Compártelo con tu equipo o déjame saber en los comentarios qué agente te gustaría construir. Y si ya estás experimentando con Strands o con agentes de IA en general, me encantaría escuchar tu experiencia.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Lost in the AI Hype, I Started Small</title>
      <dc:creator>Rohini Gaonkar</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:32:01 +0000</pubDate>
      <link>https://forem.com/aws/lost-in-the-ai-hype-i-started-small-2a72</link>
      <guid>https://forem.com/aws/lost-in-the-ai-hype-i-started-small-2a72</guid>
      <description>&lt;h2&gt;
  
  
  And it helped me get back into tech without drowning
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR at the end  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coming back to tech after a (maternity) break is a strange feeling.&lt;/p&gt;

&lt;p&gt;You’re excited but also unsure where to begin. There are new tools, new terminologies and new way of doing things we did for decades.&lt;/p&gt;

&lt;p&gt;But I didn’t try to figure everything out at one go, I just picked one small thing.&lt;/p&gt;

&lt;p&gt;For me, that “one small thing” was finally building my portfolio collection.&lt;/p&gt;

&lt;p&gt;Over the years, my content - blog posts, YouTube videos, conference talks, GitHub repos, social posts all of them scattered across dev.to, GitHub, YouTube, Instagram, LinkedIn, and AWS channels - has been scattered across a dozen different platforms. DEV.to, community.aws, YouTube, GitHub (two accounts!), LinkedIn, SlideShare... you name it. 🙇‍♀️&lt;/p&gt;

&lt;p&gt;More than 80 pieces of content, scattered across platforms since 2015!!!&lt;/p&gt;

&lt;p&gt;And honestly? Maintaining my existing site &lt;a href="https://rohinigaonkar.com" rel="noopener noreferrer"&gt;rohinigaonkar.com&lt;/a&gt; felt harder than starting from scratch.&lt;/p&gt;

&lt;p&gt;I wanted something simpler, a lightweight site I can update by editing a single file, push to GitHub, and it's live. Easy to navigate, easy to maintain. No fluff.&lt;/p&gt;

&lt;p&gt;I built this as my first project back from maternity leave, and I did it with &lt;a href="https://kiro.dev/?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an AI-powered IDE from AWS that I'd never used before. Two firsts at once. It turned out to be the perfect re-entry project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building It with Kiro: My First Impressions
&lt;/h2&gt;

&lt;p&gt;This portfolio had been on my mental to-do list forever, so the timing felt right. And rather than spinning up a complex stack to shake off the rust, I decided to keep it simple and lean on an AI coding assistant to help me get back into the flow.&lt;/p&gt;

&lt;p&gt;Here's what stood out about the experience, starting with the simplest features and building up. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Last one might surprise you!!! 🤯&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Chat-Driven Development
&lt;/h2&gt;

&lt;p&gt;The entire project was built through conversation. &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%2Fcc8xwf0zsvgryqcqnkor.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%2Fcc8xwf0zsvgryqcqnkor.png" alt="Screenshot: Kiro Kiro-IDE.png" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I described what I wanted, &lt;em&gt;"I have this website where I collect my content shared across multiple social media websites. it is one true place where any tech content I posted on the web be it dev.to, github, youtube, instagram, and any aws first party channels, all of this to be collected as a timeline. can we build something that can be refreshed on demand and build this portfolio. make it professional looking. ask more intelligent questions as we go."&lt;/em&gt;, and Kiro asked clarifying questions even before writing a single line of code. &lt;/p&gt;

&lt;p&gt;It asked about static vs dynamic, hosting preferences, design vibe, and data source approach. It even asked me about my identity. That back-and-forth shaped the architecture before any code was generated.&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%2Ffyxbyiv6ioht034zklax.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%2Ffyxbyiv6ioht034zklax.png" alt="Screenshot: Initial chat conversation with Kiro asking clarifying questions Kiro-asking-questions-before-coding" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once I provided response to all the questions, it help build the initial structure and also walked me through it. &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%2Fgtjfxdcazqqqknz26je2.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%2Fgtjfxdcazqqqknz26je2.png" alt="Screenshot: Initial build initial-build-2" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how after every conversation, it shows how many credits each prompt consumes, in real-time. That was nice!&lt;/p&gt;

&lt;p&gt;There is a &lt;strong&gt;spec-driven development mode&lt;/strong&gt; as well, which I would be testing for something more complex than this static website.&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%2Fv9qhqndj2v4fd9wj9dtd.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%2Fv9qhqndj2v4fd9wj9dtd.png" alt="Screenshot: Kiro's spec-driven development mode spec-driven-development" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Web Search
&lt;/h2&gt;

&lt;p&gt;Kiro searched the web to find my existing content across platforms. It looked up my dev.to profile, GitHub repos, community.aws presence, YouTube channel, and even my current website at rohinigaonkar.com. This gave it real context about who I am and what content already exists, so the portfolio wasn't built with placeholder data, it was seeded with my actual content from day one.&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%2Fjij1zq0izq98o0zssb48.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%2Fjij1zq0izq98o0zssb48.png" alt="Screenshot: Kiro web search results finding your profiles web-search" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Explore Real-Time File Changes
&lt;/h2&gt;

&lt;p&gt;As Kiro generated and edited the files, I could see every change happening in real time through the explorer. Either click the &lt;strong&gt;"Follow"&lt;/strong&gt; option or click on the little &lt;strong&gt;"diff button"&lt;/strong&gt; highlighted with yellow square below.&lt;/p&gt;

&lt;p&gt;For example, it created &lt;code&gt;index.html&lt;/code&gt; or &lt;code&gt;content.js&lt;/code&gt;, I could immediately open them, review the code, and see the diffs. When it later modified &lt;code&gt;content.js&lt;/code&gt; to add YouTube videos or reclassify talks vs videos, I could see exactly what changed and why.&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%2Fklirbnx1pxu8f7h0ld6s.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%2Fklirbnx1pxu8f7h0ld6s.png" alt="Screenshot: Explorer view showing file changes / diff view Follow-file-changes" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Trusting Frequently Run Commands
&lt;/h2&gt;

&lt;p&gt;Kiro ran shell commands like &lt;code&gt;curl&lt;/code&gt; to hit APIs and extract data. It asked my permission to &lt;strong&gt;run it once or add it to trusted list of commands.&lt;/strong&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%2Fhj5obnlcn3c8v23gfe1b.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%2Fhj5obnlcn3c8v23gfe1b.png" alt="Screenshot: Autopilot mode with command execution run-or-trust-command" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also liked how it provided my levels of trust, like I can just execute this particular command or partial or the base command.&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%2Fzpfe0xsmllwnoietp1fp.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%2Fzpfe0xsmllwnoietp1fp.png" alt="Screenshot: Autopilot mode with command execution trust-levels" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In autopilot mode, I could trust these commands to execute without approving each one individually. This was especially useful during the YouTube oEmbed batch processing, where Kiro ran 16 consecutive curl commands to fetch video titles, approving each one manually would have been tedious.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Iterative Refinement Through Conversation
&lt;/h2&gt;

&lt;p&gt;The project evolved through multiple rounds of feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I pointed out that "talks" should only mean conference/meetup presentations, not YouTube tutorial videos - Kiro reclassified everything accordingly&lt;/li&gt;
&lt;li&gt;I noted that some AWS "talks" were actually just YouTube embeds on my website - Kiro dug into the pages, extracted the real YouTube URLs, and recategorized them as videos&lt;/li&gt;
&lt;li&gt;I shared my personal GitHub profile separately from my work one - Kiro pulled repos from both and updated the refresh script to handle multiple accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each round of feedback made the portfolio more accurate without starting over or deleting some other important information. &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%2Fq1mjsvtt5g6etfeh8yfd.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%2Fq1mjsvtt5g6etfeh8yfd.png" alt="Screenshot: Chat showing iterative refinement iterative-refinement" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The YouTube Challenge : Hitting Walls and Finding Workarounds
&lt;/h2&gt;

&lt;p&gt;This was the most interesting part as YouTube is heavily locked down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct fetch failed&lt;/strong&gt; - &lt;code&gt;webFetch&lt;/code&gt; on youtube.com returned empty content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendered mode failed&lt;/strong&gt; - returned only JavaScript bootstrap code, no actual page content
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search was noisy&lt;/strong&gt; - web searches for my videos returned generic results, not my specific content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSS feeds blocked&lt;/strong&gt; - YouTube's channel RSS wasn't accessible either&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;*&lt;em&gt;But Kiro didn't give up!!! 💜 *&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It found workarounds on its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;oEmbed API&lt;/strong&gt; - Kiro discovered that YouTube's oEmbed endpoint (&lt;code&gt;youtube.com/oembed?url=...&lt;/code&gt;) returns video titles as JSON, and used &lt;code&gt;curl&lt;/code&gt; to call it directly. This became the reliable method for all 16 videos I shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squarespace page parsing&lt;/strong&gt; - For videos embedded on my website, Kiro parsed the raw HTML to extract YouTube video IDs from Squarespace's embed block JSON (double HTML-unescaping the content to find URLs like &lt;code&gt;youtube.com%2Fembed%2Fi0zQpJPfSdU&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thumbnail URL extraction&lt;/strong&gt; - It even tried extracting video IDs from &lt;code&gt;ytimg.com/vi/VIDEO_ID/hqdefault.jpg&lt;/code&gt; thumbnail patterns as a fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The iterative problem-solving here like trying one approach, hitting a wall, pivoting to another, felt very much like how a developer would debug a scraping problem. &lt;/p&gt;

&lt;p&gt;I loved how Kiro told me what it tried, it failed and it was going to try something else. We also worked together for a process that was a good compromise for both of us. Maybe in future I will have an agent to simplify this, but for now this solves my purpose!&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%2Frzq0zn9f7x8wjfnf9t6j.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%2Frzq0zn9f7x8wjfnf9t6j.png" alt="Screenshot: Kiro trying different approaches to fetch YouTube data youtube-is-blocked-1" width="800" height="344"&gt;&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%2Fyth8gchj9tap5j1wndv6.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%2Fyth8gchj9tap5j1wndv6.png" alt="Screenshot: Kiro trying different approaches to fetch YouTube data youtube-is-blocked-2" width="800" height="440"&gt;&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%2Fbwfh7246d1c4rpxzt1vh.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%2Fbwfh7246d1c4rpxzt1vh.png" alt="Screenshot: Kiro trying different approaches to fetch YouTube data youtube-is-blocked-3" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;A live static portfolio at &lt;a href="https://rohinigaonkar.github.io" rel="noopener noreferrer"&gt;rohinigaonkar.github.io&lt;/a&gt; with ~80 entries spanning 2015–2025, filterable by type (blogs, videos, repos, talks, social), searchable, and refreshable on demand via a Node.js script that pulls from GitHub and dev.to APIs. All built through conversation in a single Kiro session.&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%2Fxfkgvhvnhe21he0zwr9e.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%2Fxfkgvhvnhe21he0zwr9e.png" alt="Screenshot: Final portfolio site summary quick-recap-summary" width="800" height="340"&gt;&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%2Fj3tydhj0wrh2llspzsss.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%2Fj3tydhj0wrh2llspzsss.png" alt="Screenshot: Final portfolio site final-website" width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'll keep adding content as I publish it. The refresh script makes the API-sourced stuff automatic, and the manual entries take about 30 seconds each. I might add dark mode at some point, and maybe an RSS feed. But right now, the simplicity is the feature.&lt;/p&gt;

&lt;p&gt;More importantly, this project reminded me that coming back doesn't have to be intimidating. &lt;/p&gt;

&lt;p&gt;If you're returning from a break and looking for a low-pressure way to get back into coding, I'd recommend picking a passion project and giving Kiro a try. You might surprise yourself with how quickly you get back into the zone.&lt;/p&gt;

&lt;p&gt;Sign up for an &lt;a href="https://aws.amazon.com/builder/?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Builder ID&lt;/a&gt;, install &lt;a href="https://kiro.dev/?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro IDE&lt;/a&gt;, and try it for yourself. Be sure to let me know how it goes!&lt;/p&gt;

&lt;p&gt;And follow along as I explore this world of AI.&lt;/p&gt;

&lt;p&gt;&lt;a id="tldr"&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;/a&gt; &lt;br&gt;
Returning to tech after maternity leave felt overwhelming, so I started small by building a portfolio site to consolidate 80+ pieces of content scattered across platforms since 2015. Using AWS Kiro (an AI-powered IDE) for the first time, I built the entire project through conversation—no complex setup needed. Kiro helped with web searches, real-time file changes, iterative refinements, and creative problem-solving (especially when YouTube's APIs were locked down). The result: a live, searchable portfolio at rohinigaonkar.github.io that's easy to maintain. The lesson? Coming back doesn't have to be intimidating—pick one small passion project and let AI tools help you get back into the zone.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>aws</category>
    </item>
    <item>
      <title>Amazon Bedrock for Beginners From First Prompt to AI Agent (Full Tutorial)</title>
      <dc:creator>Morgan Willis</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:14:17 +0000</pubDate>
      <link>https://forem.com/aws/amazon-bedrock-for-beginners-from-first-prompt-to-ai-agent-full-tutorial-12ln</link>
      <guid>https://forem.com/aws/amazon-bedrock-for-beginners-from-first-prompt-to-ai-agent-full-tutorial-12ln</guid>
      <description>&lt;p&gt;So you want to add AI to your application. Maybe you want to build a smart assistant, add a feature that analyzes user input, or you have an AI-powered side project you've been meaning to start.&lt;/p&gt;

&lt;p&gt;On the surface, it sounds simple. Call a model, get a response. But once you actually try to build it, the questions start to stack up fast.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which model do you use?&lt;/li&gt;
&lt;li&gt;How do you call it from your application code?&lt;/li&gt;
&lt;li&gt;What happens when you want the AI to interact with your own data or external systems?&lt;/li&gt;
&lt;li&gt;And how do I control costs?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can feel like you need to understand everything before you can build anything, but you don't.&lt;/p&gt;

&lt;p&gt;Amazon Bedrock is a great place to start because it's a fully managed service on AWS that gives you API access to AI models from providers like Amazon, Anthropic, Meta, Mistral, and more. You don't need to set up servers, manage infrastructure, and you only pay for what you use. &lt;/p&gt;

&lt;p&gt;On top of model access, Bedrock includes features like Knowledge Bases for connecting your own data, Guardrails for content safety, and tool use for interacting with the real world. &lt;/p&gt;

&lt;p&gt;This post walks through Bedrock's main features with code examples you can run yourself in your own AWS account. Everything comes from the &lt;a href="https://github.com/aws-samples/sample-amazon-bedrock-for-beginners" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt;, which has full working implementations of each example. By the end, we'll combine everything into an AI agent using the Strands Agents SDK to build out a university FAQ chatbot.&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%2Fc78lztncbohk4o93zkwz.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%2Fc78lztncbohk4o93zkwz.png" alt="University Chatbot Architecture" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A heads up before we start: we're going to do things step by step, and this could take a while if you're following along. Give yourself an hour or so if you're a total beginner. We'll work directly with the Bedrock APIs so you understand exactly how the pieces fit together. Then at the end, we'll take an easier approach that handles much of the complexity for you. Learning the fundamentals first will make everything make a lot more sense later.&lt;/p&gt;

&lt;p&gt;If you prefer a video walkthrough, this post has an accompanying video that covers the same material with live demos:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FAgmR9VV0GQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before following along, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.12+&lt;/strong&gt; installed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An AWS account&lt;/strong&gt; with &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;credentials configured locally&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM User or Role&lt;/strong&gt; Create an IAM user or role in your AWS account to follow along with the AWS Console steps, you cannot complete the tutorial using the root user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need to install boto3, which is the Python SDK for interacting with AWS services programmatically. Run the following in the terminal in your IDE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;boto3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making API Calls to Amazon Bedrock
&lt;/h2&gt;

&lt;p&gt;When you send a prompt to a model and receive a response, that process is called &lt;strong&gt;inference&lt;/strong&gt;. You provide input, the model runs its computation, and it generates output.&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%2F6pjqo96zk3wlf7t1cy4w.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%2F6pjqo96zk3wlf7t1cy4w.png" alt="Inference" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For AI powered applications, you need to be able to run inference against models programmatically through an API. Bedrock exposes a set of APIs you can use. Let's start with the &lt;strong&gt;Converse API&lt;/strong&gt;, which is the standard way to call models on Bedrock. &lt;/p&gt;

&lt;p&gt;The Converse API uses the same standard request format regardless of which model you're talking to. That means you can switch from Amazon Nova to Meta Llama to Anthropic Claude Haiku but still use the same API.&lt;/p&gt;

&lt;p&gt;Here's a complete first API call to Amazon Bedrock:&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;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_converse_api&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;bedrock_runtime&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;bedrock-runtime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Define a system prompt to set model behavior
&lt;/span&gt;    &lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&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;text&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;You are a helpful technical assistant who explains concepts clearly and concisely.&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;# User message
&lt;/span&gt;    &lt;span class="n"&gt;user_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;What is serverless computing?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Use the Converse API
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_runtime&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;role&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;user&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&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="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract the response
&lt;/span&gt;    &lt;span class="n"&gt;output_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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;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;content&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Display token usage
&lt;/span&gt;    &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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;usage&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="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;Input tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&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;inputTokens&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;N/A&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="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;Output tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&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;outputTokens&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;N/A&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;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;use_converse_api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the structure of this API call, because you'll see the same pattern throughout the rest of the examples:&lt;/p&gt;

&lt;p&gt;At the top, we import boto3 and create a &lt;code&gt;bedrock-runtime&lt;/code&gt; client. This client is how your Python code communicates with the Bedrock service over the network.&lt;/p&gt;

&lt;p&gt;Then we define the &lt;code&gt;model_id&lt;/code&gt;. We're using Amazon Nova Lite, a fast and cost-efficient model. Every model in Bedrock has a unique ID. You can find the full list in the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html" rel="noopener noreferrer"&gt;supported model IDs documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The call to &lt;code&gt;converse()&lt;/code&gt; has three main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;system&lt;/code&gt;&lt;/strong&gt;: The system prompt defines the model's role and behavior. Think of it as instructions for how the model should respond. The system prompt is sent with every inference request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;messages&lt;/code&gt;&lt;/strong&gt;: The conversation between the user and the model. Each message has a &lt;code&gt;role&lt;/code&gt; (either &lt;code&gt;"user"&lt;/code&gt; or &lt;code&gt;"assistant"&lt;/code&gt;) and &lt;code&gt;content&lt;/code&gt;. This structure lets the model understand who said what. In a real application, the user message would come from a frontend, a mobile app, or command line input. We're hardcoding it here to keep things simple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;inferenceConfig&lt;/code&gt;&lt;/strong&gt;: Parameters that control how the model generates its response. &lt;code&gt;temperature&lt;/code&gt; controls how random or creative the output is. Set it to 0.0 and you get the most predictable response every time, which is useful for tasks like classification or data extraction. Push it higher toward 1.0 and the output gets more varied, which works better for creative writing or brainstorming. &lt;code&gt;maxTokens&lt;/code&gt; caps how long the response can be. Different models support different inference parameters, so check the documentation for the specific model you're using.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Converse API is the recommended approach because it works the same across all models. Change the &lt;code&gt;modelId&lt;/code&gt; from Nova to Llama to Mistral, and your code still works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Tokens
&lt;/h2&gt;

&lt;p&gt;Notice in the code above we printed token usage at the end of the script. Before we go further, you need to understand what tokens are, because they directly affect how much you pay.&lt;/p&gt;

&lt;p&gt;A token is a small chunk of text. It might be a whole word, part of a word, or even punctuation. Different models break text into tokens in slightly different ways, and there is no universal standard. When you send a prompt to a model, your text gets broken into tokens. The model processes those tokens and generates new tokens as its response.&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%2Fvjeqs882nxscswmmvsua.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%2Fvjeqs882nxscswmmvsua.png" alt="Tokens" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A short sentence like "What is serverless computing?" gets broken into several tokens. Longer prompts mean more input tokens. Longer responses mean more output tokens. You're billed for both, so the size of your prompt and the length of the model's response directly affect cost. Always set &lt;code&gt;maxTokens&lt;/code&gt; to prevent runaway responses from driving up your bill.&lt;/p&gt;

&lt;p&gt;Every model also has a &lt;strong&gt;context window&lt;/strong&gt;, which is the maximum number of tokens it can handle in a single request. This is the model's working memory. Your input tokens and output tokens all need to fit inside the context window. If you exceed the window, then the API returns an error because it cannot process that many tokens in one call. This becomes important for long conversations and applications where you inject large amounts of data into the prompt for the model to reason over.&lt;/p&gt;

&lt;p&gt;You can use the &lt;a href="https://aws.amazon.com/bedrock/pricing/" rel="noopener noreferrer"&gt;Bedrock pricing page&lt;/a&gt; to understand token costs for different models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Turn Conversations
&lt;/h2&gt;

&lt;p&gt;Up to this point, we've done single-turn interactions: one prompt, one response. But real applications usually need ongoing conversations where the model remembers what was said earlier.&lt;/p&gt;

&lt;p&gt;Here's the thing though: models are &lt;strong&gt;stateless&lt;/strong&gt; by design. Each API call is completely independent and the model doesn't remember anything from previous requests. You need to explicitly send the full conversation history with every call. &lt;/p&gt;

&lt;p&gt;This is how all AI powered chat applications work. It seems like they remember everything you talked about between prompts, but that is only because the conversation history is collected and submitted into context through the prompt with every request.&lt;/p&gt;

&lt;p&gt;That means when you are writing apps that need multi-turn conversations, your code is responsible for managing and sending the full context. Let's build a cooking assistant that demonstrates three conversation turns:&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;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multi_turn_conversation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;bedrock_runtime&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;bedrock-runtime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# System prompt sets the assistant's behavior
&lt;/span&gt;    &lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&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;text&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;You are a helpful cooking assistant. Provide concise recipe suggestions.&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;# Conversation history - we'll build this up with each turn
&lt;/span&gt;    &lt;span class="n"&gt;conversation_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Turn 1: Ask for recipe suggestions
&lt;/span&gt;    &lt;span class="n"&gt;user_message_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Suggest a quick dinner recipe with chicken.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message_1&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;response_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_runtime&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&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="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assistant_message_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response_1&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;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;content&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="c1"&gt;# Add assistant's response to history
&lt;/span&gt;    &lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assistant_message_1&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Turn 2: Ask for modifications
&lt;/span&gt;    &lt;span class="n"&gt;user_message_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Can you make it vegetarian instead?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message_2&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;response_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_runtime&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&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="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assistant_message_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response_2&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;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;content&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assistant_message_2&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Turn 3: Ask for cooking time
&lt;/span&gt;    &lt;span class="n"&gt;user_message_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How long will this take to prepare?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message_3&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;response_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_runtime&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&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="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assistant_message_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response_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;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;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;content&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message_3&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;__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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;multi_turn_conversation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is the same for every turn: append the user message to the conversation history, call the Converse API with the full history, then append the assistant's response back to the history.&lt;/p&gt;

&lt;p&gt;The model can reference what was said in turn 1 when responding to turn 2, but only because you're resending everything. You're paying for those tokens each time too, which is why conversation history management matters for cost.&lt;/p&gt;

&lt;p&gt;In production, you'd store conversation history somewhere persistent, like a database. When a user returns, you load their history and continue where they left off.&lt;/p&gt;

&lt;p&gt;Showing you how to use the Converse API like this is essentially doing it the hard way, and we're doing this on purpose for learning purposes. In a real application, you also wouldn't have redundant code like this. You'd refactor common code into functions and collect user input dynamically.&lt;/p&gt;

&lt;p&gt;There are higher-level libraries and frameworks that can handle a lot of that complexity for you, including managing the message history and formatting the request body. But we're working with the Bedrock APIs directly for now so you understand exactly how Bedrock and AI models actually work. Later, when I show you the simpler way using the Strands Agents SDK, you'll fully understand what's happening under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool Use (Function Calling)
&lt;/h2&gt;

&lt;p&gt;Everything we've done has been purely text in, text out. The model generates a response based on its training data and whatever you include in the prompt. But this is problematic for real world usage.&lt;/p&gt;

&lt;p&gt;You can’t rely on training data alone. Models have a knowledge cutoff based on when they were trained, and they don’t have access to real-time or external data like today’s weather, live content from the internet, or data stored in databases.&lt;/p&gt;

&lt;p&gt;They don't know what's happening right now, and they can't take actions in the real world on their own.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;tool use&lt;/strong&gt; comes in. Tools are functions that a model can request your application to run in order to interact with external systems. The model doesn't execute tools itself. It sends a structured request saying "I want to call this function with these arguments," and your code handles the actual execution.&lt;/p&gt;

&lt;p&gt;This is how most modern AI applications work. A chatbot that does research for you using the internet? That's tool use. A coding assistant that reads files from your local disk? Tool use. A personal assistant bot that checks your calendar? Also tool use.&lt;/p&gt;

&lt;p&gt;Now, this does get a bit involved when you're doing everything the hard way, but stick with me. This is important to understand when you are building a foundational understanding of how AI works.&lt;/p&gt;

&lt;p&gt;Think of it like this: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; You create tools in your code.&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; You write code to inform the model what tools exist and how to use them, this is often called a tool schema.&lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; You send the model a prompt along with the tool schema.&lt;br&gt;
&lt;strong&gt;4.&lt;/strong&gt; The model reasons over the prompt and decides if it needs a tool to answer.&lt;br&gt;
&lt;strong&gt;5.&lt;/strong&gt; If it does need a tool, the model returns a response to your application code including information on which tool to call and with what arguments.&lt;br&gt;
&lt;strong&gt;6.&lt;/strong&gt; Your code runs the tool.&lt;br&gt;
&lt;strong&gt;7.&lt;/strong&gt; Your code sends the result of the tool call back to the model.&lt;br&gt;
&lt;strong&gt;8.&lt;/strong&gt; The model reasons over the tool result and works that information into its final response.&lt;/p&gt;
&lt;/blockquote&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%2F7njmkzw88o78i3n6hjkx.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%2F7njmkzw88o78i3n6hjkx.png" alt="Tool Use" width="800" height="758"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the full tool use example following this flow:&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="c1"&gt;# ---------------------------------------------------------------------------
# Step 1: Define your local Python functions
# ---------------------------------------------------------------------------
# These are regular Python functions. The model will never call them directly.
# Instead, the model will ASK us to call them by returning a tool_use block.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fahrenheit&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;
    Simulate fetching weather data for a location.
    In a real app, this would call a weather API like OpenWeatherMap.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;weather_data&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;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fahrenheit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;condition&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;Partly cloudy&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;humidity&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;72%&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;wind&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;8 mph NW&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weather_data&lt;/span&gt;


&lt;span class="c1"&gt;# ---------------------------------------------------------------------------
# Step 2: Describe your functions as "tools" for the model
# ---------------------------------------------------------------------------
# The model needs a description of each tool so it knows:
#   - What the tool does (description)
#   - What inputs it expects (inputSchema)
#
# This is like writing documentation so someone else can use your function.
&lt;/span&gt;
&lt;span class="n"&gt;TOOL_CONFIG&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;tools&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolSpec&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;get_weather&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;description&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;Get the current weather for a given location.&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;inputSchema&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;json&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;type&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;object&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;properties&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;location&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;type&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;string&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;description&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;The city and state, e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;San Francisco, CA&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;unit&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;type&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;string&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;enum&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;fahrenheit&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;celsius&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;description&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;Temperature unit (default: fahrenheit)&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&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;location&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;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="c1"&gt;# ---------------------------------------------------------------------------
# Step 3: Map tool names to actual Python functions
# ---------------------------------------------------------------------------
# When the model asks to use a tool, we look up the function by name here.
&lt;/span&gt;
&lt;span class="n"&gt;TOOL_FUNCTIONS&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;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&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;run_tool&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;tool_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Look up a tool by name and call it with the provided input.
    Returns the result as a dictionary.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOOL_FUNCTIONS&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="n"&gt;tool_name&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;func&lt;/span&gt; &lt;span class="ow"&gt;is&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;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;error&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;Unknown tool: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&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="c1"&gt;# ** unpacks the dict into keyword arguments:
&lt;/span&gt;    &lt;span class="c1"&gt;#   get_weather(**{"location": "Seattle"})  →  get_weather(location="Seattle")
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ---------------------------------------------------------------------------
# Step 4: The main tool use loop
# ---------------------------------------------------------------------------
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tool_use_demo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;bedrock&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;bedrock-runtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;user_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;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather like in Seattle right now?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&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;role&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;user&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;content&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&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="c1"&gt;# First API call: send the message AND the tool definitions to the model
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;toolConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOOL_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;stop_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopReason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Check: did the model ask to use a tool?
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stop_reason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Find the toolUse block in the response
&lt;/span&gt;        &lt;span class="n"&gt;tool_use_block&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;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;assistant_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;content&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;toolUse&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;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;tool_use_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolUse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;break&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;tool_use_block&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="n"&gt;tool_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_use_block&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;tool_use_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_use_block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolUseId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Run the actual function
&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;run_tool&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;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Send the result back to the model
&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;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message&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;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolResult&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;toolUseId&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_use_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&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;json&lt;/span&gt;&lt;span class="sh"&gt;"&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="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="c1"&gt;# Second API call: model generates its final answer using the tool result
&lt;/span&gt;        &lt;span class="n"&gt;final_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;toolConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOOL_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inferenceConfig&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxTokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;final_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final_response&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;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;content&lt;/span&gt;&lt;span class="sh"&gt;"&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="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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_text&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;__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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;tool_use_demo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through what's happening:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; is defining the actual Python function. The tool in this case is a local function that simulates fetching weather data. In the real world, you'd swap this out by connecting it to a real API. The model will never call this function directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt; is creating a tool schema that describes the function to the model. Think of this like writing documentation so the model knows how to use it. We give the tool a name, a description in natural language, and an input schema that lays out what parameters the tool accepts, their types, and whether they're required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt; is a dictionary that maps tool names to actual functions. When the model decides it needs a tool, it returns the name of the tool it wants to call. We need to be able to look that up and figure out which function to run. The &lt;code&gt;run_tool&lt;/code&gt; function handles this dispatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt; is the main loop. We call the Converse API with the user message and the tool config. The model sees the question, sees the available tools, and decides it needs the weather tool. It returns a &lt;code&gt;tool_use&lt;/code&gt; block with the function name and arguments. Our code runs the actual function, then sends the result back to the model in a &lt;code&gt;toolResult&lt;/code&gt; message. The model uses that real data to generate its final response.&lt;/p&gt;

&lt;p&gt;The tool itself can be anything: a local function, an API call to another service, a database query, or a function running in the cloud. The pattern stays the same.&lt;/p&gt;

&lt;p&gt;For more details, see the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html" rel="noopener noreferrer"&gt;Bedrock tool use documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG and Knowledge Bases
&lt;/h2&gt;

&lt;p&gt;Tools are great, but one of the most common use cases for integrating AI into applications is to have it be able to reason over private data, but models don't have access to this data by default.&lt;/p&gt;

&lt;p&gt;Models don't have access to your company's internal documentation, your product specs, or any of your proprietary data. If you ask a model about a companies internal processes, it's going to hallucinate something that seems plausible but is actually completely made up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval Augmented Generation (RAG)&lt;/strong&gt; is the common fix for this. The concept is simple: before you ask the model to generate an answer, you first search your own documents for relevant information. Then you include that data in the prompt. The model generates its response grounded in your actual data instead of relying only on what it learned during training.&lt;/p&gt;

&lt;p&gt;Retrieve the data, augment the prompt, generate the response. That's where the abbreviation RAG comes from.&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%2Fdcr582swomxf5t35twu2.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%2Fdcr582swomxf5t35twu2.png" alt="Retrieval Augmented Generation" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Retrieval Part Works
&lt;/h3&gt;

&lt;p&gt;The retrieval step uses &lt;strong&gt;semantic search&lt;/strong&gt;, which is different from traditional keyword search. Keyword search looks for exact word matches, while semantic search understands the meaning of the text and searches on that instead.&lt;/p&gt;

&lt;p&gt;If your document says "customers can return items within 30 days," semantic search will find it when someone asks about "refund window" or "return period," even though those exact words don't appear. The words "queen" and "king" aren't a direct match either, but they're semantically similar because they both represent royalty. Semantic search finds that relationship but traditional search would not.&lt;/p&gt;

&lt;p&gt;To make semantic search work, your data needs to be converted into numbers, or vectors, so the computer can compare meaning mathematically. Here's how the pipeline works:&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%2Ffxp7on57a8jq10hd4uwu.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%2Ffxp7on57a8jq10hd4uwu.png" alt="RAG Process" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload and Chunking&lt;/strong&gt;: Upload your documents and then break them into smaller passages called chunks. A 50-page PDF would become many chunks. There are different chunking methods depending on your use case and data structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding&lt;/strong&gt;: Each chunk gets run through an embedding model, which converts the text into a &lt;strong&gt;vector&lt;/strong&gt;, or a list of numbers that represents the meaning of that text. Think of it as a numerical fingerprint of what the text is about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: Those vectors get stored in a &lt;strong&gt;vector database&lt;/strong&gt;, optimized for searching across vectors quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt;: When a user asks a question, that question also gets converted into a vector. The vector database queries the data and finds the chunks whose vectors are closest to the question's vector semantically. Those are your most relevant passages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation&lt;/strong&gt;: The relevant passages get included in the prompt passed to the model, and the model generates an answer grounded in that data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RAG is powerful, but there's a lot of plumbing involved to make it all work. You have to manage the chunking strategy, run embeddings, pick and maintain a vector database, write retrieval logic, and keep everything in sync when documents change. Luckily, Bedrock does this for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bedrock Knowledge Bases
&lt;/h3&gt;

&lt;p&gt;Bedrock Knowledge Bases automate the entire RAG pipeline. You point it at your documents in S3 (or other sources like Confluence, SharePoint, or Salesforce), and it handles ingestion, chunking, embedding, and vector storage. Then you query it with a single API call.&lt;/p&gt;

&lt;p&gt;For example, what if a university wanted to make a chatbot to help students find answers to frequently asked questions about course enrollment deadlines, financial aid policies, and general campus information. That is a perfect use case for RAG.&lt;/p&gt;

&lt;p&gt;We'll incrementally build this university chatbot example throughout the rest of this post starting with knowledge base creation. If you want to follow along, use the &lt;a href="https://github.com/aws-samples/sample-amazon-bedrock-for-beginners" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt; which contains the full instructions. This includes sample FAQ documents about enrollment, financial aid, housing, and campus services that we will use as the private data.&lt;/p&gt;

&lt;p&gt;To create a Knowledge Base, you need to upload your documents to Amazon S3, a data storage service, first. You can go to the &lt;a href="//console.aws.amazon.com/s3/"&gt;Amazon S3 Console&lt;/a&gt; and &lt;strong&gt;create a new bucket&lt;/strong&gt;. Then, upload the knowledge base documents to the bucket.&lt;/p&gt;

&lt;p&gt;After that, go to the &lt;a href="https://console.aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;Bedrock console&lt;/a&gt; to &lt;strong&gt;Create a knowledge base&lt;/strong&gt;. You'll connect the S3 bucket containing the FAQ documents, choose an embedding model (Amazon Titan Embed is a good default), and select a vector store. If you're just getting started, &lt;strong&gt;Amazon S3 Vectors&lt;/strong&gt; is the simplest and most cost-effective option since it doesn't require you to provision a separate vector database. Then sync the data.&lt;/p&gt;

&lt;p&gt;Once your Knowledge Base is created and synced, querying it is a single API call to Bedrock using &lt;code&gt;retrieve_and_generate&lt;/code&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="c1"&gt;# REPLACE THIS with your Knowledge Base ID
&lt;/span&gt;&lt;span class="n"&gt;KNOWLEDGE_BASE_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;YOUR_KB_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# From the Bedrock console
&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bedrock_agent_runtime&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;bedrock-agent-runtime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Use retrieve_and_generate to query the Knowledge Base
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve_and_generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&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;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;retrieveAndGenerateConfiguration&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;type&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;KNOWLEDGE_BASE&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;knowledgeBaseConfiguration&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;knowledgeBaseId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KNOWLEDGE_BASE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;modelArn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MODEL_ID&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="c1"&gt;# Extract the generated response
&lt;/span&gt;    &lt;span class="n"&gt;output_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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;text&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="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Display source citations
&lt;/span&gt;    &lt;span class="n"&gt;citations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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;citations&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;citations&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Sources:&lt;/span&gt;&lt;span class="sh"&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;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;citation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;citations&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;for&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;citation&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;retrievedReferences&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;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reference&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;location&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;s3_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;location&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;s3Location&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;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_location&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;uri&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;Unknown&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;  [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&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;uri&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;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;When is spring break this year?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;query_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the client is &lt;code&gt;bedrock-agent-runtime&lt;/code&gt;, not &lt;code&gt;bedrock-runtime&lt;/code&gt;. Knowledge Bases use a different API from the Converse API we've been working with.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;retrieve_and_generate&lt;/code&gt; call does both RAG steps in one step: it retrieves relevant documents using semantic search, then passes them to the model to generate a response. You get both the answer and citations pointing back to the source documents, so your users can verify where the information came from.&lt;/p&gt;

&lt;p&gt;For more on creating and configuring Knowledge Bases, see the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html" rel="noopener noreferrer"&gt;Bedrock Knowledge Bases documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails (Content Safety)
&lt;/h2&gt;

&lt;p&gt;You now know how to build an AI app that can access your data using RAG and interact with external systems through tools. But before you put something like this in front of real users, you need to think about what happens when people try to misuse it or when the model generates something it shouldn't.&lt;/p&gt;

&lt;p&gt;When you put an AI application on the internet, you have to assume it will be abused. You can't fully trust user input, and you can't blindly trust model output either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails&lt;/strong&gt; are content filters that get enforced before and after the model is called. They sit outside the prompt as structural policies. You configure your guardrails once, reference them in your API calls, and they work across any model.&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%2F7gpxdyt204nzn0tlsuee.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%2F7gpxdyt204nzn0tlsuee.png" alt="Guardrail Types" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Available filters include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content filters&lt;/strong&gt;: Detect harmful content like hate speech, violence, sexual content, and even prompt attacks like jailbreaks or prompt injection attempts, with adjustable severity thresholds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Denied topics&lt;/strong&gt;: Block entire categories like "investment advice" or "medical diagnosis"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word filters&lt;/strong&gt;: Block specific words or phrases, including profanity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive information filters&lt;/strong&gt;: Find and mask sensitive data like PII, social security numbers, credit cards, and email addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual grounding checks&lt;/strong&gt;: check model responses against a reference source to reduce hallucinations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a Guardrail, you can use the &lt;a href="https://console.aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;Bedrock console&lt;/a&gt;. You give the guardrail a name, configure the content filters with severity thresholds that make sense for your use case, and optionally add denied topics or PII filters. Once configured, create a version to get a guardrail ID and version number.&lt;/p&gt;

&lt;p&gt;For the university chatbot, imagine a student tries to ask the assistant something inappropriate, like how to cheat on an exam or how to hack the university network. A guardrail can detect that type of request and block it before the model ever generates a response.&lt;/p&gt;

&lt;p&gt;Here's how you add it to a Knowledge Base query:&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;boto3&lt;/span&gt;

&lt;span class="c1"&gt;# REPLACE THESE with your actual IDs
&lt;/span&gt;&lt;span class="n"&gt;KNOWLEDGE_BASE_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;YOUR_KB_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GUARDRAIL_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;YOUR_GUARDRAIL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GUARDRAIL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_kb_with_guardrail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bedrock_agent&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;bedrock-agent-runtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_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;us-east-1&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&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve_and_generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;retrieveAndGenerateConfiguration&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;type&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;KNOWLEDGE_BASE&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;knowledgeBaseConfiguration&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;knowledgeBaseId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KNOWLEDGE_BASE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelArn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generationConfiguration&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;guardrailConfiguration&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;guardrailId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GUARDRAIL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guardrailVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GUARDRAIL_VERSION&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;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="n"&gt;response&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;text&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;__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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How can I cheat on my finals this year?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;query_kb_with_guardrail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use a guardrail, all you have to do is add a &lt;code&gt;generationConfiguration&lt;/code&gt; with the guardrail identifier and version number inside the knowledge base configuration. Everything else in your code stays exactly the same.&lt;/p&gt;

&lt;p&gt;A normal question like "When is spring break?" passes through and gets answered normally. But "How can I cheat on my finals?" gets blocked by the guardrail before the model ever generates a response.&lt;/p&gt;

&lt;p&gt;You can also add guardrails directly to Converse API calls using the &lt;code&gt;guardrailConfig&lt;/code&gt; parameter:&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_runtime&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;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guardrailConfig&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;guardrailIdentifier&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;your-guardrail-id&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;guardrailVersion&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;1&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;For more details on guardrail configuration options, see the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html" rel="noopener noreferrer"&gt;Bedrock Guardrails documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together with an Agent
&lt;/h2&gt;

&lt;p&gt;We've been doing everything the hard way on purpose so you could see how the pieces actually work. You might be thinking "that tool use code was a lot of work for one function call" and you'd be right.&lt;/p&gt;

&lt;p&gt;Managing the message history, parsing tool requests, executing functions, and sending results back to the model manually is tedious. And it gets more complicated when the model needs multiple tools or several steps to complete a task. &lt;/p&gt;

&lt;p&gt;Additionally, real-world applications need more than a single prompt and response with hardcoded user queries. You need to take dynamic input from the user and pass it to the model. Then the model might need to look up information, call tools, and take several steps before it can answer a question.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;agent&lt;/strong&gt; is a system that lets the model do this. Instead of taking in one prompt and responding one time, it can think through the problem, decide what action to take next, use tools if needed, and repeat that process until it reaches a final answer. Under the hood, the model may be called multiple times as part of a loop until the task is complete.&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%2Ffhl7r3vdx64h06u85klq.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%2Ffhl7r3vdx64h06u85klq.png" alt="Agent Loop" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are going to switch to a using higher-level framework that handles much of the complexity around building AI applications for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strands Agents SDK&lt;/strong&gt; is an open-source framework from AWS that makes building AI agents straightforward. It integrates directly with Bedrock, though it supports any model provider, and handles the orchestration we've been doing manually. &lt;/p&gt;

&lt;p&gt;Under the hood, when you use Amazon Bedrock as the model provider, it's calling the same Converse API we've been using throughout this post. That's why it was worth learning the fundamentals first. This should all make sense now rather than feeling like magic.&lt;/p&gt;

&lt;p&gt;To get started with Strands, install the packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;strands-agents strands-agents-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the university chatbot as a Strands agent that combines everything we covered: a Bedrock model, a Knowledge Base for university data, a custom tool for course lookups, and a guardrail for content safety:&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;os&lt;/span&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.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;retrieve&lt;/span&gt;

&lt;span class="c1"&gt;# ============================================================
# Configuration — Replace these with your resource IDs
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;KNOWLEDGE_BASE_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;YOUR_KB_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GUARDRAIL_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;YOUR_GUARDRAIL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GUARDRAIL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&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;us.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# Custom Tool: Look Up Course Schedule
# ============================================================
&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;lookup_course&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;department&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;course_number&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;Look up schedule and details for a specific course.

    Use this when a student asks about a particular class,
    like &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;When does CS 201 meet?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Who teaches BIO 101?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    Args:
        department: The department code (e.g., &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CS&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="s"&gt;BIO&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="s"&gt;ENG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;).
        course_number: The course number (e.g., &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;101&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="s"&gt;201&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;).

    Returns:
        Course details including schedule, instructor, and location.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# In a real app this would query a course catalog API
&lt;/span&gt;    &lt;span class="n"&gt;courses&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;CS-101&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;title&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;Introduction to Programming&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;instructor&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;Dr. Maria Chen&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;schedule&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;Mon/Wed/Fri 10:00 - 10:50 AM&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;location&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;Turing Engineering Building, Room 210&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;credits&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seats_available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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;CS-201&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;title&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 Structures&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;instructor&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;Prof. James Park&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;schedule&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;Tue/Thu 1:00 - 2:15 PM&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;location&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;Turing Engineering Building, Room 215&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;credits&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seats_available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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;BIO-101&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;title&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;General Biology I&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;instructor&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;Dr. Sarah Williams&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;schedule&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;Mon/Wed 2:00 - 3:15 PM&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;location&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;Science Hall, Room 105&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;credits&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seats_available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;ENG-102&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;title&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;College Writing II&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;instructor&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;Prof. David Nguyen&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;schedule&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;Tue/Thu 9:30 - 10:45 AM&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;location&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;Humanities Building, Room 302&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;credits&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seats_available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&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;MATH-151&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;title&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;Calculus I&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;instructor&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;Dr. Lisa Patel&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;schedule&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;Mon/Wed/Fri 11:00 - 11:50 AM&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;location&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;Math &amp;amp; Science Center, Room 120&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;credits&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seats_available&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;key&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&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;course_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;return &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;Course: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&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;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;Instructor: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;instructor&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;Schedule: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;schedule&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;Location: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;Credits: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;credits&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;Seats available: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;seats_available&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No course found for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Check the department code and course number.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# Build the Agent
# ============================================================
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_university_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;Create the University chatbot agent.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# The built-in retrieve tool reads this env var to find the KB
&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;KNOWLEDGE_BASE_ID&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="n"&gt;KNOWLEDGE_BASE_ID&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;AWS_REGION&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="n"&gt;REGION&lt;/span&gt;

    &lt;span class="n"&gt;bedrock_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&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="n"&gt;MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;guardrail_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;guardrail_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_VERSION&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;You are the University virtual assistant.
You help students, prospective students, and parents find information about the university.

Your responsibilities:
- Answer questions about academics, admissions, financial aid, housing, dining, parking, the library, career services, and the academic calendar.
- Use the retrieve tool to search the knowledge base for university policies and FAQ answers before responding.
- Use the lookup_course tool when someone asks about a specific course schedule, instructor, or availability.
- Cite your sources when referencing specific policies or dates.

Guidelines:
- Be friendly and welcoming — remember, students may be stressed about deadlines.
- If you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know the answer, say so and suggest they contact the relevant office.
- Keep answers concise and helpful.&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;bedrock_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;retrieve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lookup_course&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;system_prompt&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;agent&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# Run the Agent
# ============================================================
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&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;University Chatbot&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;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&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;Ask me about admissions, financial aid, housing, dining,&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;course schedules, the academic calendar, and more.&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to exit.&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="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_university_agent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;while&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;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&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;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&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;quit&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;exit&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;q&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;Goodbye!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Assistant: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&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;flush&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;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;user_input&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="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&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="k"&gt;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through what's different here compared to the manual approach.&lt;/p&gt;

&lt;p&gt;At the top, we import a few things from the Strands framework: &lt;code&gt;Agent&lt;/code&gt;, the &lt;code&gt;@tool&lt;/code&gt; decorator for creating custom tools, &lt;code&gt;BedrockModel&lt;/code&gt; for the model provider, and the built-in &lt;code&gt;retrieve&lt;/code&gt; tool from &lt;code&gt;strands_tools&lt;/code&gt; which queries the Knowledge Base we created earlier.&lt;/p&gt;

&lt;p&gt;Then we define our configuration. We need a Knowledge Base ID for RAG, a Guardrail ID for content filtering, and we're using Amazon Nova as our model. All things we've already set up and used individually.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@tool&lt;/code&gt; decorator is how you define custom tools in Strands. We've got a &lt;code&gt;lookup_course&lt;/code&gt; tool that simulates looking up course information. In a real application, this would query a database. Compare this to the manual tool use code from earlier and you'll notice there is no lengthy tool schemas, message parsing, or dispatch logic. You just write a function with a docstring and Strands handles the rest.&lt;/p&gt;

&lt;p&gt;Strands also includes a built-in &lt;code&gt;retrieve&lt;/code&gt; tool that works directly with Bedrock Knowledge Bases. You set the Knowledge Base ID as an environment variable, and the agent decides when to use it.&lt;/p&gt;

&lt;p&gt;We created a &lt;code&gt;BedrockModel&lt;/code&gt; instance with the model ID, region, inference parameters, and guardrail information. Then we defined the system prompt telling the agent it's a university chatbot and how it should handle requests. Finally, we created the agent with the model, the tools list (both custom and built-in), and the system prompt.&lt;/p&gt;

&lt;p&gt;The last piece is the interactive loop. We read input from the command line and passed it to the agent. To call the agent, all you need is &lt;code&gt;agent(user_input)&lt;/code&gt;. The framework handles the entire agent loop: when the model needs a tool, Strands executes it and sends the result back to the model. &lt;/p&gt;

&lt;p&gt;Multi-turn conversation management is handled too because each call maintains context from previous turns as long as the program is running.&lt;/p&gt;

&lt;p&gt;Under the hood, Strands is calling the Converse API and using the different Bedrock features we covered throughout. This should all make a lot more sense now than if you jumped right into the agent framework.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands documentation&lt;/a&gt; has more examples and configuration options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We covered a lot of ground. You now have the knowledge you need to start building real applications with AI on AWS using Amazon Bedrock.&lt;/p&gt;

&lt;p&gt;Here are some areas to explore as your application grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caching&lt;/strong&gt; can significantly reduce costs on repeated context. If you have a large system prompt or tool definitions that don't change between requests, caching avoids reprocessing those tokens every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-region inference&lt;/strong&gt; distributes load across AWS regions to balance the inference load. Instead of hitting limits in one region and failing, Bedrock can route your requests globally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch monitoring&lt;/strong&gt; tracks token usage, latency, throttling, and error rates. Setting up monitoring early helps you catch cost spikes and performance issues before they become problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/aws-samples/sample-amazon-bedrock-for-beginners" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt; has the complete code for every example in this post. Clone it, run the examples, and try adapting them to your own use case. Take one of these examples and adapt it to a new use case to push your learning even further and remember to always learn the fundamentals first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>agents</category>
      <category>bedrock</category>
    </item>
    <item>
      <title>Deploy OpenClaw on AWS: Choose the right options for your AI workload</title>
      <dc:creator>Channy Yun</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:43:29 +0000</pubDate>
      <link>https://forem.com/aws/deploy-openclaw-on-aws-choose-the-right-options-for-your-ai-workload-297f</link>
      <guid>https://forem.com/aws/deploy-openclaw-on-aws-choose-the-right-options-for-your-ai-workload-297f</guid>
      <description>&lt;p&gt;Since introducing &lt;a href="https://aws.amazon.com/blogs/aws/introducing-openclaw-on-amazon-lightsail-to-run-your-autonomous-private-ai-agents/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;OpenClaw on Amazon Lightsail&lt;/a&gt; &lt;a href="https://aws.amazon.com/blogs/aws/introducing-openclaw-on-amazon-lightsail-to-run-your-autonomous-private-ai-agents/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;/a&gt;on March 4, 2026, customers ranging from individual developers to enterprises have requested deployment options tailored to their specific workload requirements. This post presents four AWS offerings for running OpenClaw, each optimized for different use cases, organizational requirements, and scale.&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%2F93rmakakelftwlzcbwua.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%2F93rmakakelftwlzcbwua.png" alt=" " width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this comparison to choose the deployment option that best fits your workload requirements. &lt;a href="https://aws.amazon.com/lightsail?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Lightsail&lt;/a&gt; provides simplicity for individual developers, while &lt;a href="https://aws.amazon.com/ec2?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Elastic Compute Cloud (Amazon EC2)&lt;/a&gt; offers greater control and deeper AWS service integration. &lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; delivers a serverless approach for variable agentic AI workloads, and &lt;a href="https://aws.amazon.com/eks?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Elastic Kubernetes Service (Amazon EKS)&lt;/a&gt; provides enterprises with multi-tenant isolation and advanced orchestration capabilities.&lt;/p&gt;

&lt;p&gt;The following sections explain how to get started with each deployment option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw on Amazon Lightsail&lt;/strong&gt;&lt;br&gt;
You can deploy OpenClaw using a prebuilt blueprint in Amazon Lightsail. With just a few clicks, you launch a fully configured instance with OpenClaw already integrated with Amazon Bedrock. The setup includes built-in security through device pairing, ensuring only authorized devices can access your AI assistant.&lt;/p&gt;

&lt;p&gt;To get started, visit &lt;a href="https://aws.amazon.com/blogs/aws/introducing-openclaw-on-amazon-lightsail-to-run-your-autonomous-private-ai-agents/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;my previous blog post&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/lightsail/latest/userguide/amazon-lightsail-quick-start-guide-openclaw.html?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt; in the AWS documentation.&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%2Fbzdag08w1lld4laas6ht.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%2Fbzdag08w1lld4laas6ht.png" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want to introduce the &lt;a href="https://lab.jorgeai.net/" rel="noopener noreferrer"&gt;Agent in Action&lt;/a&gt;, a hands-on lab guide built by Jorge Rodriguez, an AWS community leader. You can build a functional AI agent from scratch in about 45 minutes with OpenClaw on Amazon Lightsail. You’ll get web search capabilities and human-sounding voice responses powered by Amazon Bedrock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw on Amazon EC2&lt;/strong&gt;&lt;br&gt;
For those who need deeper AWS integration and control, Amazon EC2 provides the flexibility you need. Using &lt;a href="https://aws.amazon.com/ec2/graviton/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Graviton&lt;/a&gt; processors, you can save 20-40% on compute costs compared to x86 instances. &lt;a href="https://aws.amazon.com/systems-manager/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Systems Manager&lt;/a&gt; Session Manager provides secure shell access without exposing SSH ports to the internet. &lt;a href="https://aws.amazon.com/vpc?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Virtual Private Cloud (Amazon VPC)&lt;/a&gt; endpoints ensure your traffic never leaves the AWS network, and &lt;a href="https://aws.amazon.com/cloudtrail?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CloudTrail&lt;/a&gt; automatically logs every API call for compliance and auditing.&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="https://catalog.us-east-1.prod.workshops.aws/workshops/54b594b9-a3e9-4f43-acb8-c71f52b0d836/en-US/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;this hands-on lab guide&lt;/a&gt; to deploy EC2 instances. If you want to deploy with &lt;a href="https://aws.amazon.com/cloudformation/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CloudFormation&lt;/a&gt; templates, visit the &lt;a href="https://github.com/aws-samples/sample-OpenClaw-on-AWS-with-Bedrock" rel="noopener noreferrer"&gt;AWS Sample project&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%2F9knfrn1jryg5kwtv7nek.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%2F9knfrn1jryg5kwtv7nek.jpg" alt=" " width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw on Amazon Bedrock AgentCore&lt;/strong&gt;&lt;br&gt;
While Lightsail and EC2 work well for getting started, they aren’t ideal for multi-user scenarios. Each user requires a dedicated instance, which limits scalability and lacks built-in user isolation. Additionally, you pay for compute resources whether they’re being used or not. For serverless deployments, Amazon Bedrock AgentCore offers a better solution.&lt;/p&gt;

&lt;p&gt;Amazon Bedrock AgentCore is a purpose-built serverless runtime specifically designed for AI agents. You can focus on your agent logic, skills, and prompts, while AWS manages scaling, security, and availability. You can deploy OpenClaw on Bedrock AgentCore with this &lt;a href="https://github.com/aws-samples/sample-host-openclaw-on-amazon-bedrock-agentcore" rel="noopener noreferrer"&gt;AWS Sample project&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%2Fhakm5sogvj102obemtxa.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%2Fhakm5sogvj102obemtxa.png" alt=" " width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture supports asynchronous invocation—&lt;a href="https://aws.amazon.com/lambda?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; immediately acknowledges the webhook, stores the message in &lt;a href="https://aws.amazon.com/dynamodb?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt;, then invokes AgentCore in the background as an asynchronous event. The agent processes at its own pace and responds through the channel. This decoupling ensures no timeouts, provides built-in retry logic, and creates an audit trail of all interactions. It’s a production-grade pattern that handles the reality of serverless cold starts gracefully.&lt;/p&gt;

&lt;p&gt;This sample project provides a deployment with Infrastructure-as-Code using &lt;a href="https://aws.amazon.com/cdk/?trk=d8ec3b19-0f37-4f8c-8c12-189f913e205c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Cloud Development Kit (CDK)&lt;/a&gt;. Clone the &lt;a href="https://github.com/aws-samples/sample-host-openclaw-on-amazon-bedrock-agentcore" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, run the deployment script, and within minutes you have a production-ready OpenClaw microVM running on Bedrock AgentCore.&lt;/p&gt;

&lt;p&gt;I recommend that you use &lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an agentic AI development tool, to simplify the installation process and onboard quickly. Kiro will help you through the entire process of configuring a development environment, identifying dependencies, and deploying to AWS.&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%2Fbj0yxiakq36mmci9zrmx.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%2Fbj0yxiakq36mmci9zrmx.jpg" alt=" " width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw on Amazon EKS&lt;/strong&gt;&lt;br&gt;
Multi-tenancy represents the defining challenge for enterprise OpenClaw deployments. Amazon EKS with virtual machine (VM)-level isolation delivers the operational simplicity of shared infrastructure combined with the security advantages of dedicated VMs. Each tenant’s workload runs in a lightweight VM, providing hardware-level isolation while maintaining Kubernetes’s orchestration benefits. This approach has been validated in production by financial services and healthcare organizations with strict compliance requirements.&lt;/p&gt;

&lt;p&gt;For multi-tenancy deployments of OpenClaw with Amazon EKS, there are two complementary approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/kata-containers" rel="noopener noreferrer"&gt;&lt;strong&gt;Kata container&lt;/strong&gt;&lt;/a&gt; builds a standard implementation of lightweight VMs that perform like containers, but provide the workload isolation and security advantages of VMs. It’s application-agnostic, treating OpenClaw like any other workload. To getting started, visit the &lt;a href="https://github.com/aws-samples/sample-multi-tenancy-openclaw-on-eks" rel="noopener noreferrer"&gt;Multi-tenancy OpenClaw on EKS&lt;/a&gt; and &lt;a href="https://github.com/hitsub2/openclaw-on-eks" rel="noopener noreferrer"&gt;OpenClaw on EKS with Kata Containers and LiteLLM&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&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%2Fl8lnvf89drxt00kfrq4z.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%2Fl8lnvf89drxt00kfrq4z.png" alt=" " width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/openclaw-rocks/openclaw-operator" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenClaw Kubernetes Operator&lt;/strong&gt;&lt;/a&gt; directly deploys and manages OpenClaw AI agent instances in Kubernetes clusters with production-grade security, observability, and lifecycle management. To get started, visit the &lt;a href="https://github.com/openclaw-rocks/openclaw-operator/blob/main/docs/deployment.md#aws-eks" rel="noopener noreferrer"&gt;Amazon EKS deployment guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can think of the Kata Container as providing the foundation and the OpenClaw Kubernetes Operator as the specialized orchestration layer. You can choose these open source-based sample architectures for organizations serving hundreds or thousands of OpenClaw users from a single Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrap up&lt;/strong&gt;&lt;br&gt;
Choose the deployment option that matches your requirements and scale. For individual developers and early experiments, Amazon Lightsail provides the fastest path—pre-configured, simple, and ready in minutes. Startups with AWS experience should consider Amazon EC2 for better integration and control. Amazon Bedrock AgentCore is transformative for teams without operation resources—it’s serverless, auto-scaling, and cheaper for other multi-user scenarios. Finally, enterprises with complex security requirements and multiple teams should use Amazon EKS for its VM-level isolation, sophisticated orchestration, and production-proven scalability.&lt;/p&gt;

&lt;p&gt;You can start simple and evolve—begin with Lightsail for prototyping, then migrate to Bedrock AgentCore or EKS as your needs grow.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://linkedin.com/in/channy/" rel="noopener noreferrer"&gt;Channy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>aws</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Context Window Overflow: Memory Pointer Fix</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Mon, 13 Apr 2026 23:37:52 +0000</pubDate>
      <link>https://forem.com/aws/ai-context-window-overflow-memory-pointer-fix-3akc</link>
      <guid>https://forem.com/aws/ai-context-window-overflow-memory-pointer-fix-3akc</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context window overflow&lt;/strong&gt; occurs when an AI agent's tool outputs exceed the token limit the large language model (LLM) can process at once. The agent doesn't crash; it silently truncates data, loses earlier context, or produces incomplete results. This post shows how the Memory Pointer Pattern fixes it: from single-agent to multi-agent coordination where 145KB of data never enters any LLM context.&lt;/p&gt;

&lt;p&gt;This demo uses &lt;a href="https://strandsagents.com?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;. The Memory Pointer Pattern is framework-agnostic and can be applied with LangGraph, AutoGen, or other agent frameworks that support tool context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working code:&lt;/strong&gt; &lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/01-context-overflow-demo" rel="noopener noreferrer"&gt;github.com/aws-samples/sample-why-agents-fail&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Series: Why AI Agents Fail
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context Window Overflow&lt;/strong&gt; (this post) — Memory Pointer Pattern for large data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/02-mcp-timeout-demo" rel="noopener noreferrer"&gt;MCP Tools That Never Respond&lt;/a&gt;&lt;/strong&gt; — Async pattern for slow external APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/03-reasoning-loops-demo" rel="noopener noreferrer"&gt;AI Agent Reasoning Loops&lt;/a&gt;&lt;/strong&gt; — Detect and block repeated tool calls&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Problem: Agents Can't Handle Large Tool Outputs
&lt;/h2&gt;

&lt;p&gt;When an AI agent calls a tool that returns large data (server logs, database results, file contents), the response can overflow the LLM's context window. The agent doesn't crash with a clear error. It silently degrades: truncating data, losing context, or failing to complete the task.&lt;/p&gt;

&lt;p&gt;Research from IBM (&lt;a href="https://arxiv.org/html/2511.22729v1" rel="noopener noreferrer"&gt;Solving Context Window Overflow in AI Agents, 2025&lt;/a&gt;) quantifies this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Materials Science workflows, tool outputs can reach &lt;strong&gt;2M+ elements&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Traditional approach consumed &lt;strong&gt;20,822,181 tokens&lt;/strong&gt; and failed&lt;/li&gt;
&lt;li&gt;The same workflow with memory pointers used &lt;strong&gt;1,234 tokens&lt;/strong&gt; and succeeded&lt;/li&gt;
&lt;li&gt;That's a reduction of over &lt;strong&gt;16,000x&lt;/strong&gt; in this workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Community observation (&lt;a href="https://airbyte.com/agentic-data/context-window-limit" rel="noopener noreferrer"&gt;Context Window Limits Explained, Airbyte 2025&lt;/a&gt;) confirms teams discover these limits "the hard way" through silent errors. The agent appears to work but produces incomplete or wrong results.&lt;/p&gt;

&lt;p&gt;The concept of passing references instead of raw data has also been validated in multi-agent settings. Research from Amazon (&lt;a href="https://arxiv.org/pdf/2412.05449" rel="noopener noreferrer"&gt;Towards Effective GenAI Multi-Agent Collaboration, 2024&lt;/a&gt;) introduces "payload referencing," where agents exchange pointers to shared data instead of embedding large payloads in messages. This improved performance on code-intensive tasks by 23% and achieved 90% end-to-end goal success rates in enterprise benchmarks. This is exactly what we implement below with &lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/swarm/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Swarm&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens
&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%2Fd0su97tbog85m0q3srr5.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%2Fd0su97tbog85m0q3srr5.png" alt="The agent loop: User Query flows to LLM, then Tool Call, then Tool Output (214KB), then back to LLM. Large tool output causes context overflow" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the tool output is small (a few KB), this works fine. But when a tool returns 200KB of server logs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The full output gets injected into the conversation&lt;/li&gt;
&lt;li&gt;The LLM's context window fills up&lt;/li&gt;
&lt;li&gt;Older context (including the original question) gets pushed out&lt;/li&gt;
&lt;li&gt;The LLM can't reason about the data because it can't see it all&lt;/li&gt;
&lt;li&gt;The agent either fails or produces incomplete answers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution 1: Single Agent with Strands ToolContext
&lt;/h2&gt;

&lt;p&gt;The first approach uses &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/state/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;agent.state&lt;/code&gt;&lt;/a&gt;, a native key-value store scoped to each agent instance. Tools write large data there via &lt;a href="https://strandsagents.com/docs/user-guide/concepts/tools/custom-tools/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;ToolContext&lt;/code&gt;&lt;/a&gt; and return a short pointer string to the context:&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;Agent&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;ToolContext&lt;/span&gt;

&lt;span class="c1"&gt;# context=True injects ToolContext as the last parameter — required to access agent.state
&lt;/span&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_application_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_name&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;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&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;24&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;Fetch application logs. Returns a memory pointer for large datasets.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Could be 200KB+
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&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;logs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Threshold: store externally above 20KB
&lt;/span&gt;        &lt;span class="n"&gt;pointer&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;logs-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# Store the full payload in agent.state — it never enters the LLM context
&lt;/span&gt;        &lt;span class="n"&gt;tool_context&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Return only the pointer key (52 bytes) — this is all the LLM sees
&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;Data stored as pointer &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;. Use analyze tools to query it.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&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;logs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Small enough to return directly
&lt;/span&gt;
&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_error_patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_pointer&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;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolContext&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;Analyze errors — resolves pointer from agent.state.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the full dataset from agent.state using the pointer key
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_context&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;state&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="n"&gt;data_pointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&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;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Return a summary (not raw data) — keeps the response small
&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;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; errors across &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="nf"&gt;set&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&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;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM never sees the 200KB. It only sees &lt;code&gt;"Data stored as pointer 'logs-payment-service'"&lt;/code&gt; (52 bytes). The next tool reads the full data from &lt;code&gt;agent.state&lt;/code&gt; and returns a summary. Strands handles this natively, with no global dicts, no hashlib, no external infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Agent Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Without Pointers&lt;/th&gt;
&lt;th&gt;With Memory Pointers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data in context&lt;/td&gt;
&lt;td&gt;214KB (full logs)&lt;/td&gt;
&lt;td&gt;52 bytes (pointer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent behavior&lt;/td&gt;
&lt;td&gt;Truncates/fails&lt;/td&gt;
&lt;td&gt;Processes all data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors detected&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Complete (all services)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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%2F9922q2u1b8wl6miejb79.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%2F9922q2u1b8wl6miejb79.png" alt="Bar chart comparing token usage with and without Memory Pointer Pattern across four context management strategies" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: Multi-Agent with Strands Swarm
&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%2Fih2nb4e6m3tkocud75ol.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%2Fih2nb4e6m3tkocud75ol.png" alt="Strands Swarm data flow: Collector, Analyzer, and Reporter agents sharing 145KB of data through invocation_state without entering any LLM context window" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A single agent works for linear pipelines. But real-world incident response involves specialized roles: someone fetches data, someone analyzes it, someone writes the report. &lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/swarm/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Swarm&lt;/a&gt; coordinates multiple agents autonomously: define agents with different tools, and the Swarm handles handoffs.&lt;/p&gt;

&lt;p&gt;This is the same "payload referencing" pattern from the &lt;a href="https://arxiv.org/pdf/2412.05449" rel="noopener noreferrer"&gt;Amazon multi-agent collaboration paper&lt;/a&gt;. Agents exchange pointers to shared data instead of passing raw payloads. The difference is that Strands Swarm handles the coordination automatically, and provides &lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/multi-agent-patterns/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;invocation_state&lt;/code&gt;&lt;/a&gt; as the official API for sharing data across agents.&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;Agent&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;ToolContext&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;# invocation_state is a dict shared across all agents in the Swarm — the cross-agent store
&lt;/span&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_application_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_name&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;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&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;6&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="n"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 145KB+
&lt;/span&gt;    &lt;span class="n"&gt;pointer&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;logs-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# Store in invocation_state so all downstream agents can access it without re-fetching
&lt;/span&gt;    &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invocation_state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;
    &lt;span class="c1"&gt;# Only the pointer string travels through the LLM context to the next agent
&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;Stored as &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;. Hand off to analyzer.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_error_patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logs_pointer&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;tool_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolContext&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="c1"&gt;# Resolve the pointer to the full dataset — no LLM context consumed
&lt;/span&gt;    &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invocation_state&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="n"&gt;logs_pointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&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;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_errors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;errors&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;  &lt;span class="c1"&gt;# additional fields omitted for brevity
&lt;/span&gt;    &lt;span class="c1"&gt;# Store analysis results as another pointer for the reporter agent
&lt;/span&gt;    &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invocation_state&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_analysis&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="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&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;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Each agent has a focused role; the Swarm decides the handoff order autonomously
&lt;/span&gt;&lt;span class="n"&gt;collector&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;collector&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fetch_application_logs&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;analyzer&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;analyzer&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;analyze_error_patterns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detect_latency_anomalies&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;reporter&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;reporter&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;generate_incident_report&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;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="n"&gt;collector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reporter&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;collector&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;Fetch logs, analyze, and generate incident report.&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;The Swarm automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starts with the collector, which fetches 145KB of logs and stores them in &lt;code&gt;invocation_state&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The collector hands off to the analyzer with the pointer &lt;code&gt;"logs-payment-service"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The analyzer runs error and latency analysis, stores results in &lt;code&gt;invocation_state&lt;/code&gt;, hands off to the reporter&lt;/li&gt;
&lt;li&gt;The reporter generates the final incident report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No orchestration code or manual handoff logic is needed. Each agent has its own tools and the Swarm figures out the flow from the agent descriptions and the task. All data sharing happens via &lt;code&gt;tool_context.invocation_state&lt;/code&gt;, the same &lt;code&gt;ToolContext&lt;/code&gt; API used in single-agent, with a different store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swarm Results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status: COMPLETED
Agents: collector → analyzer → reporter
Time: ~14s
Shared store:
  logs-payment-service: 145,310 bytes
  error_analysis: 135 bytes
  latency_analysis: 70 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;145KB of logs processed by three agents. None of it ever entered any LLM context window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow-up Investigation
&lt;/h3&gt;

&lt;p&gt;After the swarm completes, the data stays in the shared store. A separate investigator agent can drill into specific services without re-fetching:&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;# The investigator reuses invocation_state populated by the swarm — no data re-fetch needed
&lt;/span&gt;&lt;span class="n"&gt;investigator&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;investigator&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_error_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analyze_error_patterns&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="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Each question resolves the pointer from invocation_state and runs analysis in-memory
&lt;/span&gt;&lt;span class="nf"&gt;investigator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which service had the most errors?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;investigator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show me the error logs for cache-layer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;investigator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What status codes are those errors returning?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# All queries read from the same 145KB already in invocation_state — no re-fetch, no context overflow
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Use Each Approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single agent + &lt;code&gt;agent.state&lt;/code&gt;&lt;/strong&gt; — linear pipelines where one agent handles fetch + analyze + report. Use &lt;a href="https://strandsagents.com/docs/user-guide/concepts/tools/custom-tools/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;ToolContext&lt;/code&gt;&lt;/a&gt; to access &lt;code&gt;tool_context.agent.state&lt;/code&gt; from tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swarm + &lt;code&gt;invocation_state&lt;/code&gt;&lt;/strong&gt; — specialized roles, complex workflows, or when you want autonomous coordination. Use &lt;a href="https://strandsagents.com/docs/user-guide/concepts/tools/custom-tools/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;ToolContext&lt;/code&gt;&lt;/a&gt; to access &lt;code&gt;tool_context.invocation_state&lt;/code&gt; — the official Strands API for &lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/multi-agent-patterns/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;multi-agent data sharing&lt;/a&gt;. The &lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/swarm/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Swarm&lt;/a&gt; handles handoffs, timeouts, and repetitive handoff detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both&lt;/strong&gt; — use &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/conversation-management/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;&lt;code&gt;SlidingWindowConversationManager&lt;/code&gt;&lt;/a&gt; as additional protection. It automatically trims conversation history and handles &lt;code&gt;ContextWindowOverflowException&lt;/code&gt; with retry.&lt;/p&gt;

&lt;p&gt;These approaches are part of &lt;strong&gt;context engineering&lt;/strong&gt; for AI agents: the practice of deciding what information enters the LLM's context window and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;You need &lt;a href="https://python.org/downloads" rel="noopener noreferrer"&gt;Python 3.9+&lt;/a&gt;, &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, and an &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;OpenAI API key&lt;/a&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;sample-why-agents-fail/stop-ai-agents-wasting-tokens/01-context-overflow-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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;

uv run python test_context_overflow.py   &lt;span class="c"&gt;# Single-agent: 4 scenarios&lt;/span&gt;
uv run python swarm_demo.py              &lt;span class="c"&gt;# Multi-agent: Collector → Analyzer → Reporter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or open &lt;code&gt;test_context_overflow.ipynb&lt;/code&gt; in &lt;a href="https://kiro.dev/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, VS Code, or your preferred notebook environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context overflow is silent&lt;/strong&gt; — agents don't crash, they produce wrong results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory pointers solve it&lt;/strong&gt; — store large data externally, pass references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&amp;gt;16,000x token reduction&lt;/strong&gt; — validated by IBM Research on the Materials Science benchmark&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-agent uses &lt;code&gt;agent.state&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;@tool(context=True)&lt;/code&gt; + &lt;code&gt;ToolContext&lt;/code&gt; to store and retrieve data outside context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent uses &lt;code&gt;invocation_state&lt;/code&gt;&lt;/strong&gt; — same &lt;code&gt;ToolContext&lt;/code&gt; API, shared across all agents in the Swarm. No orchestration code needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data persists for follow-up&lt;/strong&gt; — after the pipeline completes, stored data is available for investigation without re-fetching&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why do AI agents run out of context?
&lt;/h3&gt;

&lt;p&gt;AI agents run out of context when tool responses are injected directly into the LLM conversation history. Each response consumes tokens. When cumulative tool outputs exceed the model's context window limit, the LLM loses earlier context, truncates data, or fails entirely. This happens silently: the agent appears to work but produces incomplete or wrong results.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Memory Pointer Pattern for AI agents?
&lt;/h3&gt;

&lt;p&gt;The Memory Pointer Pattern stores large tool outputs (logs, datasets, query results) in external state instead of the LLM context window. Tools return a short reference key (the "pointer") that subsequent tools use to retrieve the full data. IBM Research validated this pattern with a reduction of over 16,000x on the Materials Science benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does agent.state differ from invocation_state in Strands Agents?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;agent.state&lt;/code&gt; is scoped to a single agent instance. Use it for linear pipelines where one agent handles all steps. &lt;code&gt;invocation_state&lt;/code&gt; is shared across all agents in a Strands Swarm. Use it when multiple specialized agents need to exchange data without passing large payloads through the LLM context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use the Memory Pointer Pattern with LangGraph or other frameworks?
&lt;/h3&gt;

&lt;p&gt;Yes. The pattern requires two capabilities: a shared key-value store accessible from tools, and the ability to pass short reference strings through the LLM context. LangGraph provides this through its state management, AutoGen through shared memory, and CrewAI through task context. The Strands implementation uses &lt;code&gt;ToolContext&lt;/code&gt; as the native API.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Research
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/html/2511.22729v1" rel="noopener noreferrer"&gt;Solving Context Window Overflow in AI Agents&lt;/a&gt; — IBM Research, Nov 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/pdf/2412.05449" rel="noopener noreferrer"&gt;Towards Effective GenAI Multi-Agent Collaboration&lt;/a&gt; — Amazon, Dec 2024&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://airbyte.com/agentic-data/context-window-limit" rel="noopener noreferrer"&gt;Context Window Limits Explained&lt;/a&gt; — Airbyte blog (community observation), Dec 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/html/2511.03728v1" rel="noopener noreferrer"&gt;Efficient On-Device Agents via Adaptive Context Management&lt;/a&gt; — Nov 2025&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/state/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agent State&lt;/a&gt; — ToolContext and agent.state&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/multi-agent/swarm/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Swarm&lt;/a&gt; — Multi-agent orchestration&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/conversation-management/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Conversation Management&lt;/a&gt; — Sliding window and context overflow&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you hit context window limits in your agents? What strategies worked for you? Share in the comments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in this series:&lt;/strong&gt; &lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/02-mcp-timeout-demo" rel="noopener noreferrer"&gt;MCP Tools That Never Respond&lt;/a&gt; — async patterns for slow external APIs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All code in this series is open source under the MIT-0 License. &lt;a href="https://github.com/aws-samples/sample-why-agents-fail" rel="noopener noreferrer"&gt;Star the repository&lt;/a&gt; to follow updates.&lt;/em&gt;&lt;/p&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>tutorial</category>
      <category>python</category>
      <category>aws</category>
    </item>
    <item>
      <title>Strings en programación: más que un simple array de caracteres</title>
      <dc:creator>Axel Espinosa</dc:creator>
      <pubDate>Mon, 13 Apr 2026 21:19:53 +0000</pubDate>
      <link>https://forem.com/aws/strings-en-programacion-mas-que-un-simple-array-de-caracteres-1knd</link>
      <guid>https://forem.com/aws/strings-en-programacion-mas-que-un-simple-array-de-caracteres-1knd</guid>
      <description>&lt;p&gt;Los strings son probablemente la estructura de datos que más usas sin pensar. Pero, ¿sabes cómo se almacenan en memoria? ¿Por qué no puedes modificarlos directamente? ¿Cuánto cuesta concatenarlos en un loop?&lt;/p&gt;

&lt;p&gt;En el &lt;a href="https://dev.to/aws/arrays-los-bloques-fundamentales-de-la-programacion-3jmf"&gt;artículo anterior&lt;/a&gt; vimos cómo funcionan los arrays por debajo: memoria contigua, acceso por índice, operaciones y sus costos. Si aún no lo has leído, te recomiendo empezar por ahí. Hoy toca hablar de sus primos: los strings.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;En estos ejemplos usaremos JavaScript como lenguaje principal. Si usas otro lenguaje, te invito a buscar los métodos equivalentes. Los conceptos son los mismos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lo que encontrarás en este artículo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qué es un string y cómo se almacena en memoria&lt;/li&gt;
&lt;li&gt;Qué significa que sean inmutables&lt;/li&gt;
&lt;li&gt;En qué se parecen y en qué se diferencian de los arrays&lt;/li&gt;
&lt;li&gt;Operaciones comunes con strings y cuánto cuestan (Big O)&lt;/li&gt;
&lt;li&gt;Una tabla de complejidad para que la tengas de referencia&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. ¿Qué es un string?
&lt;/h2&gt;

&lt;p&gt;Un string (o cadena de caracteres) es una secuencia de caracteres almacenados de forma contigua en memoria. Suena familiar, ¿verdad? Es básicamente lo que vimos con los &lt;a href="https://dev.to/aws/arrays-los-bloques-fundamentales-de-la-programacion-3jmf"&gt;arrays&lt;/a&gt;, pero en lugar de números o emojis, almacenamos caracteres.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Índice:    0   1   2   3   4
         ┌───┬───┬───┬───┬───┐
String:  │ H │ o │ l │ a │ ! │
         └───┴───┴───┴───┴───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para empezar, puedes pensar en un string como un array de caracteres — es un buen modelo mental. Más adelante verás que los motores modernos implementan strings con estructuras más sofisticadas, pero conceptualmente esta idea te va a servir para entender cómo se comportan.&lt;/p&gt;

&lt;p&gt;Pero aquí viene el detalle: ¿qué es realmente un "carácter" para la computadora? No es una letra. Es un número. La "H" es el 72, la "o" es el 111. Cada carácter tiene un código numérico asignado por un estándar (como ASCII o Unicode), y un encoding (como UTF-8) convierte ese código a bits. Así que cuando digo "array de caracteres", por debajo es un array de números.&lt;/p&gt;

&lt;p&gt;Si quieres entender a fondo cómo funciona eso, lo cubrimos en el &lt;a href="https://dev.to/fromchiapasdev/encodings-por-que-importan-mas-de-lo-que-crees-41a5"&gt;artículo de encodings&lt;/a&gt;. Ahí explicamos por qué un emoji ocupa 4 bytes y una letra solo 1. 😉&lt;/p&gt;

&lt;p&gt;Cada carácter ocupa una posición y tiene un índice, exactamente como en un array. Y al igual que con los &lt;a href="https://dev.to/aws/arrays-los-bloques-fundamentales-de-la-programacion-3jmf"&gt;arrays&lt;/a&gt;, acceder a un carácter por su índice es instantáneo: O(1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saludo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saludo&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;// "H"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saludo&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="c1"&gt;// "!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Una advertencia importante:&lt;/strong&gt; este modelo funciona perfectamente con caracteres del alfabeto latino básico, pero se complica con emojis y algunos caracteres especiales. En JavaScript, un emoji como 😀 ocupa &lt;strong&gt;dos posiciones&lt;/strong&gt; en el string, no una:&lt;/p&gt;


&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;😀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2, no 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Esto es por cómo JavaScript representa internamente el texto (UTF-16). Lo vemos a detalle en el &lt;a href="https://dev.to/fromchiapasdev/encodings-por-que-importan-mas-de-lo-que-crees-41a5"&gt;artículo de encodings&lt;/a&gt;. Por ahora, si trabajas con caracteres simples, el modelo de "array de caracteres" te sirve bien.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. ¿Por qué los strings son inmutables?
&lt;/h2&gt;

&lt;p&gt;Porque una vez que creas un string, su contenido no puede cambiar. Si necesitas una versión diferente, se crea uno nuevo en memoria. Esto aplica en JavaScript, Python y la mayoría de lenguajes modernos. Aquí es donde los strings empiezan a diferenciarse de los arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué significa inmutable?
&lt;/h3&gt;

&lt;p&gt;Inmutable quiere decir que algo no puede cambiar después de creado. En programación, cuando decimos que un valor es inmutable, significa que no puedes modificar su contenido directamente. Si necesitas una versión diferente, tienes que crear una nueva.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué pasa cuando modificas un string?
&lt;/h3&gt;

&lt;p&gt;En lenguajes como JavaScript y Python, los strings son inmutables. Esto quiere decir que cuando "modificas" un string, en realidad se está creando uno nuevo en memoria. El original no se toca.&lt;/p&gt;

&lt;p&gt;Veamos un ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;palabra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;palabra&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="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;J&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Intentamos cambiar la "H" por "J"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;palabra&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "Hola" — no cambió nada&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Ves? JavaScript simplemente ignora el intento. No lanza un error, pero tampoco hace el cambio.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si tu código está en strict mode (lo cual pasa automáticamente dentro de módulos ES y clases), este intento sí lanza un &lt;code&gt;TypeError&lt;/code&gt;. Es el comportamiento más común en código moderno.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En Python es más explícito:&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;palabra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hola&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;palabra&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="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;J&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# TypeError: 'str' object does not support item assignment
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python directamente te dice: "no puedes hacer eso".&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cuánto cuesta concatenar strings?
&lt;/h3&gt;

&lt;p&gt;Cuando concatenas strings con &lt;code&gt;+=&lt;/code&gt;, no estás "agregando" al final como harías con &lt;code&gt;push()&lt;/code&gt; en un array. Conceptualmente, se crea un string nuevo que contiene todo el contenido anterior más lo que agregaste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Se crea un nuevo string "Hola"&lt;/span&gt;
&lt;span class="nx"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Se crea un nuevo string "Hola "&lt;/span&gt;
&lt;span class="nx"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mundo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Se crea un nuevo string "Hola Mundo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Construir un string de tamaño N con concatenaciones repetidas tiene un costo total de O(N) — el tamaño del resultado final.&lt;/p&gt;

&lt;p&gt;Los motores de JavaScript modernos optimizan este caso con estructuras internas que evitan copiar el string completo en cada paso, así que en la práctica &lt;code&gt;+=&lt;/code&gt; en un loop es bastante eficiente. Pero la regla general sigue siendo útil: si vas a construir texto pieza por pieza, piensa en el tamaño del resultado final, no solo en cuántas piezas tienes.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. ¿Cuál es la diferencia entre strings y arrays?
&lt;/h2&gt;

&lt;p&gt;Ambos son secuencias contiguas con acceso por índice O(1), pero los arrays son mutables y los strings no. Esa diferencia cambia todo: los arrays tienen &lt;code&gt;push&lt;/code&gt; y &lt;code&gt;pop&lt;/code&gt;, los strings tienen métodos de texto como &lt;code&gt;slice&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt; y &lt;code&gt;replace&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Primos, no gemelos. Los arrays te permiten modificar su contenido directamente: cambiar un elemento por índice, agregar al final, quitar del inicio. Los strings no. Son inmutables, así que no puedes hacer &lt;code&gt;push&lt;/code&gt; o &lt;code&gt;pop&lt;/code&gt; sobre un string.&lt;/p&gt;

&lt;p&gt;Lo que sí tienen los strings son métodos especializados para operaciones comunes con texto: buscar, acortar, dividir, reemplazar. Implementar estos métodos de strings manualmente sería tedioso (y propenso a errores). Por eso los lenguajes nos dan estos métodos listos para usar.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Array (dinámico)&lt;/th&gt;
&lt;th&gt;String&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Secuencia contigua&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Acceso por índice&lt;/td&gt;
&lt;td&gt;✅ O(1)&lt;/td&gt;
&lt;td&gt;✅ O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutable&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push / pop&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Métodos de texto&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (search, slice, split, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redimensionable&lt;/td&gt;
&lt;td&gt;✅ (crece automáticamente)&lt;/td&gt;
&lt;td&gt;❌ (se crea uno nuevo)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  4. Operaciones comunes con strings y su complejidad
&lt;/h2&gt;

&lt;p&gt;Vamos a las operaciones que más vas a usar con strings. Cada una con un ejemplo en JavaScript y su complejidad.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtener longitud
&lt;/h3&gt;

&lt;p&gt;Igual que con los arrays, podemos saber cuántos caracteres tiene un string con &lt;code&gt;.length&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;texto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola Mundo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(1)&lt;/strong&gt; — el acceso es inmediato&lt;/p&gt;

&lt;h3&gt;
  
  
  Acceso por índice
&lt;/h3&gt;

&lt;p&gt;Ya lo vimos, pero vale la pena repetirlo: puedes acceder a cualquier carácter directamente por su posición.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;texto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JavaScript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&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;// "J"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&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="c1"&gt;// "S"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(1)&lt;/strong&gt; — acceso directo, como el elevador del artículo de arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buscar dentro de un string
&lt;/h3&gt;

&lt;p&gt;Podemos verificar si un string contiene cierto texto con &lt;code&gt;includes()&lt;/code&gt;, o encontrar la posición exacta con &lt;code&gt;indexOf()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Los arrays son geniales&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arrays&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arrays&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// -1 (no lo encontró)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(n·m) en el peor caso&lt;/strong&gt; — donde &lt;code&gt;n&lt;/code&gt; es la longitud del string y &lt;code&gt;m&lt;/code&gt; la longitud del texto que buscas. En la práctica, los motores modernos usan algoritmos de búsqueda optimizados que son mucho más rápidos en la mayoría de los casos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extraer una porción (slice / substring)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;slice()&lt;/code&gt; te permite extraer una parte del string sin modificar el original (recuerda: inmutabilidad).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;texto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JavaScript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// "Java"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="c1"&gt;// "Script"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// string original&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(n) en el peor caso&lt;/strong&gt; — donde &lt;code&gt;n&lt;/code&gt; es el tamaño de la porción extraída. Algunos motores optimizan esta operación para evitar copiar cuando es posible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dividir un string (split)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;split()&lt;/code&gt; divide un string en un array de substrings usando un separador.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nombre,edad,ciudad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;campos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;campos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ["nombre", "edad", "ciudad"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(n)&lt;/strong&gt; — recorre todo el string para encontrar los separadores y crear los substrings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concatenar strings
&lt;/h3&gt;

&lt;p&gt;Ya hablamos de esto, pero aquí va el resumen con código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saludo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; Mundo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saludo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "Hola Mundo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complejidad: O(n)&lt;/strong&gt; — donde &lt;code&gt;n&lt;/code&gt; es el tamaño del resultado final. Recuerda que los motores modernos optimizan esta operación por debajo.&lt;/p&gt;

&lt;p&gt;Si quieres explorar todos los métodos de strings disponibles en JavaScript, la &lt;a href="https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/String" rel="noopener noreferrer"&gt;referencia de String en MDN&lt;/a&gt; es un excelente recurso.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. ¿Cuál es la complejidad Big O de las operaciones con strings?
&lt;/h2&gt;

&lt;p&gt;Aquí te dejo la tabla de complejidad, en el mismo formato que usamos en el artículo de arrays para que puedas compararlas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operación&lt;/th&gt;
&lt;th&gt;Complejidad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Acceso por índice&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Obtener longitud&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buscar un substring&lt;/td&gt;
&lt;td&gt;O(n·m)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concatenar (resultado total)&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slice / Substring&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; las complejidades de búsqueda y concatenación representan el &lt;strong&gt;peor caso&lt;/strong&gt;. Los motores modernos de JavaScript implementan optimizaciones que hacen que en la práctica sean más rápidas de lo que sugiere la teoría.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compara esta tabla con la del &lt;a href="https://dev.to/aws/arrays-los-bloques-fundamentales-de-la-programacion-3jmf"&gt;artículo de arrays&lt;/a&gt;. Vas a notar que el acceso por índice es igual de rápido en ambos. La diferencia está en que los strings no tienen operaciones como &lt;code&gt;push&lt;/code&gt; o &lt;code&gt;pop&lt;/code&gt; porque son inmutables. Cada vez que necesitas un string "modificado", estás pagando el costo de crear uno nuevo.&lt;/p&gt;

&lt;p&gt;Algo importante: en esta tabla y a lo largo del artículo usamos Big O para describir el costo de las operaciones. Big O es una notación que nos dice cómo crece el tiempo de una operación conforme crece el tamaño de la entrada. O(1) significa tiempo constante (siempre tarda lo mismo sin importar el tamaño), y O(n) significa tiempo lineal (entre más grande la entrada, más tarda, proporcionalmente).&lt;/p&gt;

&lt;p&gt;No es el foco de este artículo, pero es importante que tengas el contexto. Si quieres que hagamos un artículo dedicado a Big O, dime en los comentarios.&lt;/p&gt;

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

&lt;p&gt;Los strings son como los primos de los arrays: comparten la misma base (secuencia contigua, acceso por índice) pero juegan con reglas diferentes gracias a la inmutabilidad. Eso los hace predecibles y seguros, pero también significa que operaciones que parecen simples, como concatenar en un loop, pueden ser más costosas de lo que esperabas.&lt;/p&gt;

&lt;p&gt;Si leíste con atención, quizá notaste que dije varias veces "en el peor caso". Eso es porque los motores de JavaScript modernos hacen optimizaciones que a veces rompen el modelo mental simple que vimos aquí.&lt;/p&gt;

&lt;p&gt;En el siguiente artículo de la serie vamos a poner en práctica todo lo que hemos visto de arrays y strings con problemas y patrones comunes de entrevistas técnicas. Ahí es donde todo se conecta. 💪&lt;/p&gt;

&lt;p&gt;Si quieres avanzar con el siguiente tema intenta resolver esto: &lt;em&gt;dado un string, ¿cómo lo invertirías sin usar &lt;code&gt;.reverse()&lt;/code&gt;? Piensa en lo que vimos de inmutabilidad&lt;/em&gt;. En el siguiente artículo vamos a resolver este tipo de problemas.&lt;/p&gt;

&lt;p&gt;Gracias por llegar hasta el final. Estaré pendiente de tus comentarios 🙌🏻&lt;/p&gt;

</description>
      <category>datastructures</category>
      <category>algorithms</category>
      <category>beginners</category>
      <category>español</category>
    </item>
    <item>
      <title>GraphQL en tiempo real: Subscriptions con AWS AppSync</title>
      <dc:creator>Ramses Mata</dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:04:37 +0000</pubDate>
      <link>https://forem.com/aws/graphql-en-tiempo-real-subscriptions-con-aws-appsync-4igd</link>
      <guid>https://forem.com/aws/graphql-en-tiempo-real-subscriptions-con-aws-appsync-4igd</guid>
      <description>&lt;p&gt;En el &lt;a href="https://dev.to/aws/tiempo-real-en-aplicaciones-web-polling-vs-subscriptions-2790"&gt;artículo anterior&lt;/a&gt; exploramos las dos formas principales de mantener datos frescos en una aplicación: polling y subscriptions. Entendimos cómo funcionan, cuándo usar cada una, y las diferencias entre ambas. Ahora es momento de pasar de la teoría a la práctica.&lt;/p&gt;

&lt;p&gt;En este artículo vamos a implementar &lt;strong&gt;subscriptions en nuestra API de GraphQL con &lt;a href="https://aws.amazon.com/appsync/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS AppSync&lt;/a&gt;&lt;/strong&gt; (el servicio administrado de &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; que hemos usado en la serie). Vamos a construir algo concreto: suscribirnos a una publicación y recibir una notificación en tiempo real cada vez que alguien deje un nuevo comentario.&lt;/p&gt;

&lt;p&gt;Al final de este artículo, tendrás:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una subscription funcionando que reacciona a nuevos comentarios&lt;/li&gt;
&lt;li&gt;Entendimiento de cómo AppSync implementa subscriptions (spoiler: es más simple de lo que piensas)&lt;/li&gt;
&lt;li&gt;La experiencia de ver datos aparecer en tiempo real en dos pestañas del navegador&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este diagrama te muestra el panorama completo de lo que construiremos:&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%2Ftqymrgm1exq6pupidu5v.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%2Ftqymrgm1exq6pupidu5v.png" alt="Diagrama: panorama general - suscribirse a una publicación y recibir comentarios en tiempo real" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de empezar
&lt;/h2&gt;

&lt;p&gt;Para seguir este tutorial necesitas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tu API de AppSync con los resolvers que construimos en &lt;a href="https://dev.to/aws/appsync-tu-primer-resolver-de-graphql-2dej"&gt;uno de los artículos anteriores&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Una cuenta de AWS (&lt;a href="https://aws.amazon.com/free/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;el free tier&lt;/a&gt; es suficiente)&lt;/li&gt;
&lt;li&gt;Un navegador web&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Parte 1: ¿Cómo implementa AppSync las Subscriptions?
&lt;/h2&gt;

&lt;p&gt;En el artículo anterior vimos que hay diferentes formas de implementar subscriptions (WebSockets, SSE, Long Polling). AppSync usa &lt;strong&gt;WebSockets&lt;/strong&gt; por debajo, pero tiene una particularidad importante: &lt;strong&gt;las subscriptions se activan a través de mutations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;¿Qué significa esto? Que cuando defines una subscription en AppSync, le dices: "cada vez que se ejecute &lt;em&gt;esta mutation&lt;/em&gt;, notifica a todos los que estén suscritos". No necesitas escribir código para enviar notificaciones, AppSync lo hace automáticamente.&lt;/p&gt;

&lt;p&gt;El flujo se ve así:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Un cliente se suscribe a &lt;code&gt;onNuevoComentario(publicacionId: "1")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AppSync abre un WebSocket y registra la subscription&lt;/li&gt;
&lt;li&gt;Otro cliente ejecuta la mutation &lt;code&gt;crearComentario&lt;/code&gt; para la publicación "1"&lt;/li&gt;
&lt;li&gt;AppSync detecta que hay una subscription vinculada a esa mutation&lt;/li&gt;
&lt;li&gt;AppSync envía los datos del nuevo comentario al cliente suscrito automáticamente&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%2F32yo97p5lswvlvbkms9k.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%2F32yo97p5lswvlvbkms9k.png" alt="Diagrama: flujo de subscriptions en AppSync - mutation dispara notificación via WebSocket" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué mutations?&lt;/strong&gt; Porque las mutations son las operaciones que cambian datos en tu sistema. Si algo cambió, probablemente alguien quiere saberlo. AppSync aprovecha esto para vincular subscriptions directamente a los cambios.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parte 2: Preparando el resolver de comentarios
&lt;/h2&gt;

&lt;p&gt;Antes de implementar la subscription, necesitamos que la mutation &lt;code&gt;crearComentario&lt;/code&gt; funcione. Si completaste el reto del &lt;a href="https://dev.to/aws/appsync-tu-primer-resolver-de-graphql-2dej"&gt;artículo 4&lt;/a&gt;, ya la tienes lista y puedes saltar a la Parte 3.&lt;/p&gt;

&lt;p&gt;Si no, no te preocupes, vamos a configurarla rápidamente. El proceso es el mismo patrón que aprendiste para &lt;code&gt;usuario&lt;/code&gt; y &lt;code&gt;crearUsuario&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crear función Lambda&lt;/li&gt;
&lt;li&gt;Crear data source en AppSync&lt;/li&gt;
&lt;li&gt;Adjuntar resolver con mapping templates&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Crea una nueva función Lambda con este código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;autorId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nuevoComentario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;autorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;creadoEn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Comentario creado:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nuevoComentario&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nuevoComentario&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Usamos &lt;code&gt;Date.now().toString()&lt;/code&gt; para generar IDs porque es simple y funciona bien para este tutorial. En una aplicación en producción con múltiples usuarios simultáneos, querrás usar algo más robusto como &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier" rel="noopener noreferrer"&gt;UUID&lt;/a&gt; o dejar que tu base de datos genere los IDs automáticamente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Mapping templates
&lt;/h3&gt;

&lt;p&gt;Los mismos que usamos para los resolvers anteriores:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request mapping template:&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;{
    "version": "2018-05-29",
    "operation": "Invoke",
    "payload": {
        "arguments": $util.toJson($context.arguments)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response mapping template:&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;$util.toJson($context.result)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conecta esta Lambda a AppSync como data source y adjunta el resolver a &lt;code&gt;crearComentario&lt;/code&gt; en el esquema. Reutiliza el rol &lt;code&gt;AppSyncLambdaRole&lt;/code&gt; que creaste en el artículo 4.&lt;/p&gt;

&lt;p&gt;Prueba que funcione con esta mutation en el playground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearComentario&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="n"&gt;crearComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&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="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"¡Excelente publicación!"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;autorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;creadoEn&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;p&gt;Si recibes los datos del comentario de vuelta, estás listo para la siguiente parte.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parte 3: Implementando la Subscription
&lt;/h2&gt;

&lt;p&gt;Ahora sí, lo que vinimos a hacer. Vamos a agregar una subscription a nuestro esquema para que cualquier cliente pueda suscribirse a los comentarios nuevos de una publicación.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 1: Actualizar el esquema
&lt;/h3&gt;

&lt;p&gt;Agrega este tipo al final de tu esquema en AppSync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Subscription&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="n"&gt;onNuevoComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comentario&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws_subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutations&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="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;crearComentario&lt;/span&gt;&lt;span class="err"&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;p&gt;También necesitas agregar el campo &lt;code&gt;publicacionId&lt;/code&gt; al tipo &lt;code&gt;Comentario&lt;/code&gt; para que AppSync pueda filtrar las notificaciones por publicación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comentario&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;autor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;creadoEn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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;p&gt;Aquí está el esquema completo actualizado para que lo copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;fotoPerfil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicaciones&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="n"&gt;Publicacion&lt;/span&gt;&lt;span class="p"&gt;!]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;creadoEn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Publicacion&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;autor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;comentarios&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="n"&gt;Comentario&lt;/span&gt;&lt;span class="p"&gt;!]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;creadoEn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comentario&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;autor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;creadoEn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearUsuarioInput&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="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;fotoPerfil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearPublicacionInput&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="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;autorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&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="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearComentarioInput&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="n"&gt;contenido&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;autorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&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="n"&gt;usuario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicacion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;publicaciones&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="n"&gt;Publicacion&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutation&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="n"&gt;crearUsuario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearUsuarioInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;crearPublicacion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearPublicacionInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;crearComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearComentarioInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comentario&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Subscription&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="n"&gt;onNuevoComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comentario&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws_subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutations&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="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;crearComentario&lt;/span&gt;&lt;span class="err"&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;p&gt;En este video puedes ver cómo agregar el tipo Subscription directamente en el editor de esquema de AppSync:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/cXgFkXucZ7g"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Veamos qué hace cada parte:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;type Subscription&lt;/code&gt;&lt;/strong&gt;: Define las subscriptions disponibles en tu API, igual que &lt;code&gt;type Query&lt;/code&gt; define las consultas y &lt;code&gt;type Mutation&lt;/code&gt; define las escrituras&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;onNuevoComentario(publicacionId: ID!)&lt;/code&gt;&lt;/strong&gt;: El nombre de la subscription. Recibe el ID de la publicación a la que quieres suscribirte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;: Comentario&lt;/code&gt;&lt;/strong&gt;: El tipo de dato que recibirás cuando se active la subscription&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@aws_subscribe(mutations: ["crearComentario"])&lt;/code&gt;&lt;/strong&gt;: Esta es la parte clave. Le dice a AppSync: "activa esta subscription cada vez que se ejecute la mutation &lt;code&gt;crearComentario&lt;/code&gt;"&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; La directiva &lt;code&gt;@aws_subscribe&lt;/code&gt; es específica de AppSync. Es lo que conecta una subscription con una o más mutations. Puedes vincular una subscription a múltiples mutations si lo necesitas: &lt;code&gt;@aws_subscribe(mutations: ["crearComentario", "editarComentario"])&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Paso 2: Crear un Data Source de tipo NONE
&lt;/h3&gt;

&lt;p&gt;Las subscriptions no llaman a Lambda ni a ninguna base de datos, solo reenvían datos. Pero AppSync necesita un data source asociado. Para esto usamos un data source de tipo &lt;strong&gt;NONE&lt;/strong&gt;, que le dice a AppSync "no llames a nadie, solo pasa los datos".&lt;/p&gt;

&lt;p&gt;En la consola de AppSync, ve a &lt;strong&gt;Data Sources&lt;/strong&gt; y crea uno nuevo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nombre:&lt;/strong&gt; &lt;code&gt;NoneDataSource&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tipo:&lt;/strong&gt; &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Paso 3: Adjuntar el resolver a la subscription
&lt;/h3&gt;

&lt;p&gt;Ahora adjunta un resolver al campo &lt;code&gt;onNuevoComentario&lt;/code&gt; en el tipo &lt;code&gt;Subscription&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;NoneDataSource&lt;/code&gt; (el que acabas de crear)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; VTL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Request mapping template:&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;{
    "version": "2018-05-29",
    "payload": {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response mapping template:&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;null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Fíjate que el request template usa &lt;code&gt;payload: {}&lt;/code&gt; y el response retorna &lt;code&gt;null&lt;/code&gt;. Esto es porque al suscribirte no hay datos que devolver todavía, solo estás registrando tu interés. Los datos reales llegarán cuando alguien ejecute la mutation &lt;code&gt;crearComentario&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué no necesita Lambda?&lt;/strong&gt; Porque la subscription no "busca" datos, solo escucha. Los datos vienen de la mutation (&lt;code&gt;crearComentario&lt;/code&gt;), que ya tiene su propio resolver. La subscription simplemente reenvía esos datos a los clientes suscritos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parte 4: Probando en tiempo real
&lt;/h2&gt;

&lt;p&gt;Este es el momento más satisfactorio. Vamos a abrir dos pestañas del navegador y ver la magia del tiempo real.&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%2F22im2wm1no3v5h7ru7vw.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%2F22im2wm1no3v5h7ru7vw.png" alt="Diagrama: setup de prueba con dos pestañas - Tab A suscrito, Tab B ejecuta mutation" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pestaña A: El suscriptor
&lt;/h3&gt;

&lt;p&gt;En la primera pestaña del playground de AppSync, ejecuta esta subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EscucharComentarios&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="n"&gt;onNuevoComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;creadoEn&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;p&gt;Cuando la ejecutes, verás que el playground se queda "escuchando". No hay respuesta todavía, está esperando que alguien cree un comentario en la publicación "1".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Los campos que pides en la subscription deben ser un subconjunto de los campos que pides en la mutation. Si la subscription pide un campo que la mutation no incluyó, recibirás &lt;code&gt;null&lt;/code&gt; en ese campo (o un error si el campo es obligatorio en el esquema).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pestaña B: El que comenta
&lt;/h3&gt;

&lt;p&gt;Abre una segunda pestaña del playground de AppSync y ejecuta esta mutation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CrearComentario&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="n"&gt;crearComentario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&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="n"&gt;contenido&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 artículo me ayudó mucho!"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;autorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;contenido&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;publicacionId&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;creadoEn&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Fíjate que la mutation pide &lt;code&gt;publicacionId&lt;/code&gt; en los campos de respuesta. Esto es clave: AppSync usa los campos de la respuesta de la mutation para enviar datos a los suscriptores. Si no incluyes &lt;code&gt;publicacionId&lt;/code&gt;, AppSync envía &lt;code&gt;null&lt;/code&gt; en ese campo. Como tu subscription filtró por &lt;code&gt;publicacionId: "1"&lt;/code&gt;, no hay match con &lt;code&gt;null&lt;/code&gt; y no recibes la notificación.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este diagrama muestra cómo AppSync decide a quién notificar:&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%2Fs8c6ukp17xz9c12npwqr.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%2Fs8c6ukp17xz9c12npwqr.png" alt="Diagrama: cómo AppSync filtra subscriptions usando publicacionId" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  El momento "aha" 🎉
&lt;/h3&gt;

&lt;p&gt;Vuelve a la Pestaña A. Sin haber tocado nada, los datos del nuevo comentario aparecieron automáticamente. El servidor le avisó a tu suscriptor en el instante que la mutation se ejecutó.&lt;/p&gt;

&lt;p&gt;En este video puedes ver la experiencia completa, suscribirse en una pestaña, crear un comentario en otra, y ver los datos aparecer en tiempo real:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/if_jeVokOhU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Prueba crear más comentarios desde la Pestaña B, cada uno aparecerá en la Pestaña A en tiempo real.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; Fíjate que si creas un comentario con &lt;code&gt;publicacionId: "2"&lt;/code&gt;, la Pestaña A no recibe nada. Esto es porque se suscribió específicamente a la publicación "1". El filtro por &lt;code&gt;publicacionId&lt;/code&gt; funciona automáticamente gracias a que lo definimos como argumento en la subscription.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Parte 5: Reto práctico
&lt;/h2&gt;

&lt;p&gt;Ya implementaste tu primera subscription. Ahora es tu turno de expandir lo que aprendiste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tu reto:&lt;/strong&gt; Implementa estas subscriptions adicionales:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;onNuevaPublicacion&lt;/code&gt;&lt;/strong&gt;: Suscríbete para recibir notificaciones cuando alguien cree una nueva publicación. Vincula esta subscription a la mutation &lt;code&gt;crearPublicacion&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;onNuevoUsuario&lt;/code&gt;&lt;/strong&gt;: Suscríbete para saber cuando se registra un nuevo usuario. Vincula a &lt;code&gt;crearUsuario&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;El patrón es el mismo: agrega el tipo en &lt;code&gt;Subscription&lt;/code&gt;, usa &lt;code&gt;@aws_subscribe&lt;/code&gt;, crea un resolver con el &lt;code&gt;NoneDataSource&lt;/code&gt;, y prueba con dos pestañas&lt;/li&gt;
&lt;li&gt;No necesitas crear nuevos resolvers de Lambda, las subscriptions usan las mutations existentes&lt;/li&gt;
&lt;li&gt;Puedes reutilizar el mismo &lt;code&gt;NoneDataSource&lt;/code&gt; para todas tus subscriptions&lt;/li&gt;
&lt;li&gt;Experimenta con los campos que pides en la subscription, puedes pedir solo los que te interesen&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Las subscriptions de AppSync funcionan fuera del playground?
&lt;/h3&gt;

&lt;p&gt;Sí. El playground es solo para probar. En una aplicación real, usarías librerías como &lt;a href="https://docs.amplify.aws/" rel="noopener noreferrer"&gt;AWS Amplify&lt;/a&gt; que manejan la conexión WebSocket automáticamente desde tu frontend (React, Vue, Angular, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué pasa si se pierde la conexión?
&lt;/h3&gt;

&lt;p&gt;AppSync mantiene la conexión enviando mensajes de keep-alive periódicamente. Si la conexión se interrumpe, es responsabilidad del &lt;strong&gt;cliente&lt;/strong&gt; reconectarse y volver a registrar sus subscriptions. En producción, librerías como &lt;a href="https://docs.amplify.aws/" rel="noopener noreferrer"&gt;AWS Amplify&lt;/a&gt; manejan esta lógica de reconexión automáticamente por ti.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Puedo filtrar qué datos recibo en una subscription?
&lt;/h3&gt;

&lt;p&gt;Sí. Como vimos con &lt;code&gt;publicacionId&lt;/code&gt;, los argumentos de la subscription actúan como filtros. Solo recibes notificaciones que coincidan con los argumentos que especificaste.&lt;/p&gt;




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

&lt;p&gt;Recapitulemos lo que lograste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Descubriste cómo AppSync vincula subscriptions a mutations con &lt;code&gt;@aws_subscribe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Aprendiste a configurar un data source NONE y un resolver para subscriptions&lt;/li&gt;
&lt;li&gt;✅ Implementaste una subscription real que reacciona a nuevos comentarios&lt;/li&gt;
&lt;li&gt;✅ Viste datos aparecer en tiempo real entre dos pestañas del navegador&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hasta ahora en la serie hemos construido una API de GraphQL completa: desde entender qué es GraphQL, diseñar un esquema, desplegarlo en AppSync, implementar resolvers, y ahora agregar tiempo real con subscriptions. Pero todo lo hemos probado desde el playground de AppSync.&lt;/p&gt;

&lt;p&gt;En el próximo artículo vamos a dar el siguiente paso: &lt;strong&gt;consumir nuestra API desde una aplicación cliente real&lt;/strong&gt;. Vamos a ver cómo conectar un frontend a nuestra API y usar queries, mutations y subscriptions desde código.&lt;/p&gt;

&lt;p&gt;¡Nos vemos ahí! 🚀&lt;/p&gt;

</description>
      <category>api</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Amazon S3 Files Is Still Not A File System</title>
      <dc:creator>Ifeanyi O.</dc:creator>
      <pubDate>Thu, 09 Apr 2026 21:24:02 +0000</pubDate>
      <link>https://forem.com/aws/amazon-s3-files-is-still-not-a-file-system-28g5</link>
      <guid>https://forem.com/aws/amazon-s3-files-is-still-not-a-file-system-28g5</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Intro&lt;/li&gt;
&lt;li&gt;What is S3 Files&lt;/li&gt;
&lt;li&gt;What Changed for Developers&lt;/li&gt;
&lt;li&gt;How S3 Files Works at a High Level&lt;/li&gt;
&lt;li&gt;Why This Matters for AI&lt;/li&gt;
&lt;li&gt;Practical Code Example&lt;/li&gt;
&lt;li&gt;Security Operations&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="Intro"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;If you've spent any real time learning AWS, one of the first storage lessons you probably learned was Amazon S3 is not a file system. &lt;/p&gt;

&lt;p&gt;And that mattered because it explained a lot of the weirdness you'd run into early on that might have you asking...&lt;/p&gt;

&lt;p&gt;Why can't I just mount S3 like a normal shared drive?&lt;br&gt;
Why can't my application treat S3 data like regular files?&lt;/p&gt;

&lt;p&gt;But the answer has always been... S3 is object storage and even though that's obvious, it's a critical distinction because as interesting as S3 Files is, it seems to be confusing a lot of people that only &lt;a href="https://aws.amazon.com/blogs/aws/launching-s3-files-making-s3-buckets-accessible-as-file-systems/" rel="noopener noreferrer"&gt;read headlines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So no... S3 did not suddenly turn into a file system.&lt;/p&gt;

&lt;p&gt;&lt;a id="S3Files"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  So what is S3 Files?
&lt;/h2&gt;

&lt;p&gt;S3 Files is a file system interface for data that lives in S3 and connects AWS compute resources directly with data in S3. It gives applications file system access without the data ever leaving S3 and maintains a view of the objects in the bucket while translating file system operations into efficient S3 requests.&lt;/p&gt;

&lt;p&gt;Personally, the simple way to think about S3 Files is S3 remains the storage layer, S3 Files becomes the file-access layer and your apps sees files and directories.&lt;/p&gt;

&lt;p&gt;&lt;a id="Changes"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What changed for developers
&lt;/h2&gt;

&lt;p&gt;Before S3 Files, many workloads had a choice to make.&lt;/p&gt;

&lt;p&gt;Do you optimize for S3's scalability, durability and cost characteristics, or do you optimize for file system behavior that your applications already understand?&lt;/p&gt;

&lt;p&gt;S3 Files aims to remove that tradeoff by making general purpose S3 buckets accessible as native file systems for AWS compute resources and supports NFS v4.1+ file operations like create, read, update and delete.&lt;/p&gt;

&lt;p&gt;This is kind of a big deal because it means more workloads can work against data where it already lives instead of forcing you to move it somewhere else first.&lt;/p&gt;

&lt;p&gt;&lt;a id="Highlevel"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How S3 Files works at a high level
&lt;/h2&gt;

&lt;p&gt;At a high level, S3 Files creates a file system view over an S3 bucket.&lt;/p&gt;

&lt;p&gt;Your compute resource connects to that file system through a mount target in your VPC. For example, you can create the S3 file system, discover the mount target and then mount it from an EC2 instance using the &lt;code&gt;s3files&lt;/code&gt; file system type.&lt;/p&gt;

&lt;p&gt;Once mounted, your application can interact with the mounted path using normal file operations.&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;sudo mkdir&lt;/span&gt; /home/ec2-user/s3files
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; s3files fs-1234567890abcdef:/ /home/ec2-user/s3files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can do familiar things like:&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello S3 Files"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /home/ec2-user/s3files/hello.txt
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; /home/ec2-user/s3files/hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you create a file through the mounted file system, the file appears in the S3 bucket as an object and the contents remain the same when retrieved through the S3 API.&lt;/p&gt;

&lt;p&gt;That is the key idea in action and even thought you're working through the file interface, S3 still stores the data underneath.&lt;/p&gt;

&lt;p&gt;&lt;a id="AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for AI
&lt;/h2&gt;

&lt;p&gt;S3 Files is clearly positioned for agents, machine learning and collaborative data-heavy workloads.&lt;/p&gt;

&lt;p&gt;The announcement page specifically calls out file-based applications, agents and ML teams as beneficiaries. It states that AI agents can persist memory and share state across pipelines, ML teams can run data preparation workloads without duplicating or staging files first and thousands of compute resources can connect to the same S3 file system simultaneously.&lt;/p&gt;

&lt;p&gt;That makes sense.&lt;/p&gt;

&lt;p&gt;A lot of modern AI and ML tooling still behaves in very file-centric ways. Training inputs, checkpoints, logs, intermediate artifacts and tool-driven workflows often assume files and directories are available. Even when your long-term storage strategy is object-first, the workloads themselves may still be file-first.&lt;/p&gt;

&lt;p&gt;&lt;a id="Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical code example
&lt;/h2&gt;

&lt;p&gt;Here's a simple way to show the difference conceptually:&lt;/p&gt;

&lt;p&gt;Let's look at working directly with S3 objects in Python.&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;boto3&lt;/span&gt;
&lt;span class="n"&gt;s3&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;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-data-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reports/summary.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works fine, but your code is explicitly written around the S3 API model.&lt;/p&gt;

&lt;p&gt;Now let's review working with a mounted S3 Files path.&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;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/s3files/reports/summary.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version feels ordinary because it is ordinary from the application's point of view.&lt;/p&gt;

&lt;p&gt;&lt;a id="Security"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and operational details worth noting
&lt;/h2&gt;

&lt;p&gt;S3 Files integrates with IAM for access control, supports identity and resource policies at both the file system and object level, encrypts data in transit with TLS 1.3 and supports encryption at rest with SSE-S3 or customer-managed AWS KMS keys. It can also monitor it using CloudWatch metrics and CloudTrail management events.&lt;/p&gt;

&lt;p&gt;So we can treat this as a real production service with access control, encryption and observability expectations teams will need.&lt;/p&gt;

&lt;p&gt;&lt;a id="Take"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways for developers
&lt;/h2&gt;

&lt;p&gt;The biggest takeaway is that the boundary between object storage workflows and file-based workflows has got much easier to work across.&lt;/p&gt;

&lt;p&gt;That matters because a lot of cloud architecture pain comes from interfaces not matching reality forces teams to build adapters, stage pipelines, duplicate storage systems and synchronize logic just to keep things moving.&lt;/p&gt;

&lt;p&gt;S3 Files now gives you the option to say, "maybe I shouldn't have to do that nearly as often."&lt;/p&gt;

&lt;p&gt;Lastly, if you're looking to dive deeper in your learning journey, explore these FREE AWS resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://skillbuilder.aws/learn/6QPAC2M1HT/amazon-s3-files-native-file-system-access-for-your-s3-data/VTHPM94CZ4?trk=e3f50ee1-3e9a-412b-b5ed-61c3791a9fba&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Intro to Amazon S3 Files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/free?trk=ddc7640f-4b0f-4289-896a-4c93114ffd04&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Free Tier Sign Up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/start?trk=e3f50ee1-3e9a-412b-b5ed-61c3791a9fba&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Builder Center Sign Up&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;If you've got this far, thanks for reading! I hope it was worthwhile.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>CDK update - March 2026</title>
      <dc:creator>Praneeta Prakash</dc:creator>
      <pubDate>Thu, 09 Apr 2026 15:38:45 +0000</pubDate>
      <link>https://forem.com/aws/cdk-update-march-2026-1ga8</link>
      <guid>https://forem.com/aws/cdk-update-march-2026-1ga8</guid>
      <description>&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TL;DR&lt;/li&gt;
&lt;li&gt;
Major Features

&lt;ul&gt;
&lt;li&gt;CDK Mixins — From Preview to Stable&lt;/li&gt;
&lt;li&gt;EKS v2 Graduates to Stable&lt;/li&gt;
&lt;li&gt;CLI Improvements&lt;/li&gt;
&lt;li&gt;New L2 Constructs&lt;/li&gt;
&lt;li&gt;Service Enhancements&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Community Highlights&lt;/li&gt;

&lt;li&gt;Community Content &amp;amp; Resources&lt;/li&gt;

&lt;li&gt;How Can You Be Involved&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Hey CDK community! Here’s an update on all the changes that happened last month. &lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;CDK Mixins are now stable in &lt;code&gt;aws-cdk-lib&lt;/code&gt; — compose reusable infrastructure behaviors across any construct without extra packages. EKS v2 graduated to stable with production-ready APIs. And the CLI shipped &lt;code&gt;--revert-drift&lt;/code&gt; to fix drifted resources in a single command.&lt;/p&gt;

&lt;p&gt;These features are available in &lt;code&gt;aws-cdk-lib&lt;/code&gt; v2.233.0 through v2.248.0 and &lt;code&gt;aws-cdk&lt;/code&gt; CLI v2.1099.0 through v2.1117.0. Full changelogs on &lt;a href="https://github.com/aws/aws-cdk/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Major Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CDK Mixins — From Preview to Stable
&lt;/h3&gt;

&lt;p&gt;If you've been waiting to adopt Mixins, the wait is over. The core Mixins API (&lt;code&gt;Mixins.of()&lt;/code&gt;, &lt;code&gt;Mixin&lt;/code&gt;, &lt;code&gt;ConstructSelector&lt;/code&gt;) landed in &lt;code&gt;aws-cdk-lib&lt;/code&gt; in February, and the &lt;code&gt;@aws-cdk/cfn-property-mixins&lt;/code&gt; package graduated to stable in March. That means the fluent &lt;code&gt;.with()&lt;/code&gt; method now works on any construct — L1, L2, or custom — directly from &lt;code&gt;aws-cdk-lib&lt;/code&gt;, no extra packages needed.&lt;/p&gt;

&lt;p&gt;Several service mixins ship directly in &lt;code&gt;aws-cdk-lib&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Apply mixins fluently with .with() — works on L1, L2, and custom constructs&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyL1Bucket&lt;/span&gt;&lt;span class="dl"&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketBlockPublicAccess&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketAutoDeleteObjects&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Auto-delete ECR images on repository removal&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repo&lt;/span&gt;&lt;span class="dl"&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RepositoryAutoDeleteImages&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Apply cluster settings to L1 constructs&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cluster&lt;/span&gt;&lt;span class="dl"&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ClusterSettings&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;containerInsights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enhanced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]));&lt;/span&gt;

&lt;span class="c1"&gt;// .with() works on L2 constructs too&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyL2Bucket&lt;/span&gt;&lt;span class="dl"&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketBlockPublicAccess&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already using Aspects? The new &lt;code&gt;Shims&lt;/code&gt; class lets you convert between Aspects and Mixins, so you can adopt incrementally without rewriting existing code.&lt;/p&gt;

&lt;p&gt;Also new in the Mixins Preview package: EventBridge pattern generation for all events, custom merge strategies via &lt;code&gt;IMergeStrategy&lt;/code&gt;, cross-account Vended Log delivery destinations, and the ability to pass resource objects directly into CFN Property mixin properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  EKS v2 Graduates to Stable 🚀
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@aws-cdk/aws-eks-v2-alpha&lt;/code&gt; module is now &lt;code&gt;aws-cdk-lib/aws-eks-v2&lt;/code&gt; — stable APIs, no more alpha imports, production-ready. If you've been holding off on EKS v2 because of the alpha label, it's time to upgrade.&lt;/p&gt;

&lt;p&gt;Alongside graduation, February brought Kubernetes 1.35 support, hybrid nodes for on-premises and edge infrastructure, EC2/HYBRID_LINUX/HYPERPOD_LINUX access entry types, removal policies for all EKS constructs, and &lt;code&gt;bootstrapSelfManagedAddons&lt;/code&gt; support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KubectlV35Layer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-cdk/lambda-layer-kubectl-v35&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cluster&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;version&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;KubernetesVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;V1_35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;kubectlLayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;KubectlV35Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;KubectlLayer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;remoteNodeNetworks&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="na"&gt;cidrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10.0.0.0/16&lt;/span&gt;&lt;span class="dl"&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="na"&gt;remotePodNetworks&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="na"&gt;cidrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;192.168.0.0/16&lt;/span&gt;&lt;span class="dl"&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;h3&gt;
  
  
  CLI Improvements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;--revert-drift&lt;/code&gt; Option
&lt;/h4&gt;

&lt;p&gt;Drifted resources used to mean manual remediation. Now a single flag on &lt;code&gt;cdk deploy&lt;/code&gt; creates a drift-aware change set that brings your actual resource state back in line with your template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cdk deploy &lt;span class="nt"&gt;--revert-drift&lt;/span&gt; MyStack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Concurrent Asset Builds &amp;amp; &lt;code&gt;publish-assets&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Two features that speed up CI/CD pipelines. Build assets in parallel with &lt;code&gt;--asset-build-concurrency&lt;/code&gt;, and separate asset publishing from deployment with the new &lt;code&gt;publish-assets&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cdk deploy &lt;span class="nt"&gt;--asset-build-concurrency&lt;/span&gt; 4
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cdk publish-assets MyStack &lt;span class="nt"&gt;--unstable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;publish-assets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;cdk diff --method&lt;/code&gt; &amp;amp; &lt;code&gt;cdk destroy --concurrency&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;cdk diff&lt;/code&gt; now supports &lt;code&gt;--method=change-set&lt;/code&gt; to always use a change set (and fail if it can't), replacing the deprecated &lt;code&gt;--change-set&lt;/code&gt; flag. And &lt;code&gt;cdk destroy&lt;/code&gt; gained &lt;code&gt;--concurrency&lt;/code&gt; for parallel stack destruction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/mikevoets" rel="noopener noreferrer"&gt;Mike Voets&lt;/a&gt; (&lt;code&gt;destroy --concurrency&lt;/code&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional CLI Updates
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guard Hook failure details&lt;/strong&gt; — the CLI now fetches and displays detailed failure annotations automatically (&lt;a href="https://github.com/jkelley-godaddy" rel="noopener noreferrer"&gt;jkelley-godaddy&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Fn::ForEach&lt;/code&gt; diff support&lt;/strong&gt; — ForEach loops are no longer invisible in &lt;code&gt;cdk diff&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata in separate files&lt;/strong&gt; — avoids the 512MB NodeJS string limit for extremely large apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changeset-based diff for nested stacks&lt;/strong&gt; — accurate diffs including &lt;code&gt;--security-only&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cdk import&lt;/code&gt; fix&lt;/strong&gt; — &lt;code&gt;--role-arn&lt;/code&gt; no longer conflicts with &lt;code&gt;--record-resource-mapping&lt;/code&gt; (&lt;a href="https://github.com/abhu85" rel="noopener noreferrer"&gt;Abhishek Chauhan&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker &lt;code&gt;buildSecrets&lt;/code&gt; fix&lt;/strong&gt; — secrets are now correctly passed to docker build (&lt;a href="https://github.com/cartmanez" rel="noopener noreferrer"&gt;cartmanez&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  New L2 Constructs
&lt;/h3&gt;

&lt;h4&gt;
  
  
  MediaPackage V2 (Alpha)
&lt;/h4&gt;

&lt;p&gt;A full-featured L2 for AWS Elemental MediaPackage V2 — channel groups, channels, origin endpoints, manifests (HLS, DASH, LL-HLS, MSS), encryption/DRM, type-safe manifest filtering, grants, and CloudWatch metrics. Everything you need to set up a streaming pipeline in CDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ChannelGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OriginEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InputConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Manifest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-cdk/aws-mediapackagev2-alpha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChannelGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyChannelGroup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;channelGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-channel-group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyChannel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cmaf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOriginEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyEndpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;originEndpointName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cmaf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;manifests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;manifestName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&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;// Grant MediaLive permission to ingest content&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaLiveRole&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/aws/aws-cdk/tree/main/packages/%40aws-cdk/aws-mediapackagev2-alpha" rel="noopener noreferrer"&gt;full README&lt;/a&gt; for manifest filtering, encryption, and DRM examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Enhancements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  DynamoDB Cross-Account Global Tables
&lt;/h4&gt;

&lt;p&gt;Replicate tables across AWS accounts for multi-tenant architectures and disaster recovery. Permissions are automatically configured when both tables are in the same CDK app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TableV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SourceTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyMultiAccountTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;globalTableSettingsReplicationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GlobalTableSettingsReplicationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Replica in Account B — resource policies and KMS permissions handled for you&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replica&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TableV2MultiAccountReplica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replicaStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReplicaTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyMultiAccountTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;replicaSourceTable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sourceTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;globalTableSettingsReplicationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GlobalTableSettingsReplicationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL&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;h4&gt;
  
  
  ALB JWT Authentication
&lt;/h4&gt;

&lt;p&gt;Verify JWTs directly at the load balancer for service-to-service auth — no custom Lambda authorizers needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Listener&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApplicationProtocol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;defaultAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ListenerAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticateJwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://issuer.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;jwksEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://issuer.example.com/.well-known/jwks.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ListenerAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;myTargetGroup&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;&lt;strong&gt;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  OpenSearch S3 Vectors Engine
&lt;/h4&gt;

&lt;p&gt;Sub-second vector search at lower cost by offloading vector data to S3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Domain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EngineVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENSEARCH_2_19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;s3VectorsEngineEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dataNodeInstanceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;or1.medium.search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;encryptionAtRest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Kinesis Firehose Dynamic Partitioning
&lt;/h4&gt;

&lt;p&gt;Partition streaming data on the fly using JQ expressions — no Lambda required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;firehose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dynamicPartitioning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;firehose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MetadataExtractionProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jq16&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.customer_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.event_timestamp|strftime("%Y")&lt;/span&gt;&lt;span class="dl"&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="na"&gt;dataOutputPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;!{partitionKeyFromQuery:year}/!{partitionKeyFromQuery:customer_id}/&lt;/span&gt;&lt;span class="dl"&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;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/Tietew" rel="noopener noreferrer"&gt;Tietew&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Glue Typed Partition Projection (Alpha)
&lt;/h4&gt;

&lt;p&gt;Type-safe partition projection for Glue tables — no more raw strings for integer, date, enum, and injected types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;glue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;glue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;partitionKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;glue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INTEGER&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;dataFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;glue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DataFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;partitionProjection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;glue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PartitionProjectionConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;interval&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="na"&gt;digits&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="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;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Bedrock AgentCore: &lt;code&gt;fromCodeAsset&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Deploy agent runtimes from local code with automatic S3 packaging — no pre-built container required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artifact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;agentcore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AgentRuntimeArtifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodeAsset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path/to/agent/code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentcore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AgentCoreRuntime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PYTHON_3_12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opentelemetry-instrument&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main.py&lt;/span&gt;&lt;span class="dl"&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;new&lt;/span&gt; &lt;span class="nx"&gt;agentcore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyAgentRuntime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myAgent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;artifact&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;Contributed by:&lt;/strong&gt; &lt;a href="https://github.com/go-to-k" rel="noopener noreferrer"&gt;Kenta Goto&lt;/a&gt; (fromCodeAsset)&lt;/p&gt;

&lt;h4&gt;
  
  
  More Service Updates
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: TLS 1.3 enhanced security policies with post-quantum cryptography support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2&lt;/strong&gt;: VPC flow logs to Amazon Data Firehose (&lt;a href="https://github.com/Tietew" rel="noopener noreferrer"&gt;Tietew&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS&lt;/strong&gt;: &lt;code&gt;forceNewDeployment()&lt;/code&gt; to trigger redeployment without definition changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AutoScaling&lt;/strong&gt;: Deletion protection and instance lifecycle policy with retention triggers (&lt;a href="https://github.com/matoom-nomu" rel="noopener noreferrer"&gt;Masatomo Nomura&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS&lt;/strong&gt;: Standalone &lt;code&gt;ParameterGroup.forInstance()&lt;/code&gt; / &lt;code&gt;ParameterGroup.forCluster()&lt;/code&gt; factory methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3&lt;/strong&gt;: Blocked encryption types to control allowed upload encryption (&lt;a href="https://github.com/ysthakur" rel="noopener noreferrer"&gt;Yash Thakur&lt;/a&gt;), ABAC support (&lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR Assets&lt;/strong&gt;: Docker &lt;code&gt;buildContexts&lt;/code&gt; for multi-context builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt;: JS runtime 2.0 as default for new projects (&lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Tables (Alpha)&lt;/strong&gt;: Tagging, metrics, partition specs, sort orders (&lt;a href="https://github.com/mgarbvs" rel="noopener noreferrer"&gt;Michael Garbus&lt;/a&gt;, &lt;a href="https://github.com/olenarostotskyy" rel="noopener noreferrer"&gt;Olena R&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway V2&lt;/strong&gt;: Role support for Lambda authorizers (&lt;a href="https://github.com/eliasbrange" rel="noopener noreferrer"&gt;Elias Brange&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synthetics&lt;/strong&gt;: Canary NodeJS 3.1 runtime (&lt;a href="https://github.com/bilalq" rel="noopener noreferrer"&gt;Bilal Quadri&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Top External Contributors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/badmintoncryer" rel="noopener noreferrer"&gt;Kazuho Cryer-Shinozuka&lt;/a&gt;&lt;/strong&gt; — 8 contributions across ALB JWT auth, OpenSearch S3 Vectors, S3 ABAC, Glue typed partition projection, CloudFront JS 2.0 default, and more. A driving force behind CDK's service coverage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/go-to-k" rel="noopener noreferrer"&gt;Kenta Goto&lt;/a&gt;&lt;/strong&gt; — Three high-impact features: &lt;code&gt;publish-assets&lt;/code&gt; CLI command, concurrent asset builds, and Bedrock AgentCore &lt;code&gt;fromCodeAsset&lt;/code&gt;. Consistently shipping features that improve developer velocity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/aki-kii" rel="noopener noreferrer"&gt;aki-kii&lt;/a&gt;&lt;/strong&gt; — Built &lt;code&gt;--revert-drift&lt;/code&gt; for the CLI and added AppSync enhanced metrics. Contributions spanning both CLI and construct library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Tietew" rel="noopener noreferrer"&gt;Tietew&lt;/a&gt;&lt;/strong&gt; — Kinesis Firehose dynamic partitioning and EC2 flow log Firehose destinations — two highly requested features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/matoom-nomu" rel="noopener noreferrer"&gt;Masatomo Nomura&lt;/a&gt;&lt;/strong&gt; — AutoScaling deletion protection and instance lifecycle policy across both months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/olenarostotskyy" rel="noopener noreferrer"&gt;Olena R&lt;/a&gt;&lt;/strong&gt; — S3 Tables metrics, partition specs, sort orders, and table properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Contributors
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/AndreKurait" rel="noopener noreferrer"&gt;Andre Kurait&lt;/a&gt;, &lt;a href="https://github.com/0xdsqr" rel="noopener noreferrer"&gt;Dave Dennis&lt;/a&gt;, &lt;a href="https://github.com/mikevoets" rel="noopener noreferrer"&gt;Mike Voets&lt;/a&gt;, &lt;a href="https://github.com/eliasbrange" rel="noopener noreferrer"&gt;Elias Brange&lt;/a&gt;, &lt;a href="https://github.com/davidkonigsberg" rel="noopener noreferrer"&gt;David Konigsberg&lt;/a&gt;, &lt;a href="https://github.com/mgarbvs" rel="noopener noreferrer"&gt;Michael Garbus&lt;/a&gt;, &lt;a href="https://github.com/mazyu36" rel="noopener noreferrer"&gt;Yuki Matsuda&lt;/a&gt;, &lt;a href="https://github.com/ysthakur" rel="noopener noreferrer"&gt;Yash Thakur&lt;/a&gt;, &lt;a href="https://github.com/bilalq" rel="noopener noreferrer"&gt;Bilal Quadri&lt;/a&gt;, &lt;a href="https://github.com/bk5115545" rel="noopener noreferrer"&gt;Bryan Koch&lt;/a&gt;, &lt;a href="https://github.com/syukawa-gh" rel="noopener noreferrer"&gt;syukawa-gh&lt;/a&gt;, &lt;a href="https://github.com/jkelley-godaddy" rel="noopener noreferrer"&gt;jkelley-godaddy&lt;/a&gt;, &lt;a href="https://github.com/juho9000" rel="noopener noreferrer"&gt;Juho Majasaari&lt;/a&gt;, &lt;a href="https://github.com/abhu85" rel="noopener noreferrer"&gt;Abhishek Chauhan&lt;/a&gt;, &lt;a href="https://github.com/cartmanez" rel="noopener noreferrer"&gt;cartmanez&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Content &amp;amp; Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From the Community:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws-heroes/aws-cdk-tips-how-to-centrally-apply-configurations-to-multiple-resources-i3g"&gt;AWS CDK Tips: How to Centrally Apply Configurations to Multiple Resources&lt;/a&gt;&lt;/strong&gt; — Kenta Goto walks through patterns for applying configurations at scale — timely with Mixins going stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws-builders/cdk-mixin-for-deletion-protection-50b7"&gt;CDK Mixin for Deletion Protection&lt;/a&gt;&lt;/strong&gt; — Hands-on walkthrough of building a custom Mixin, by johanneskonings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws-heroes/why-is-cdkout-cloud-assembly-necessary-in-aws-cdk-n5f"&gt;Why is cdk.out (Cloud Assembly) Necessary in AWS CDK?&lt;/a&gt;&lt;/strong&gt; — Kenta Goto explains the Cloud Assembly and why it matters for your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/tyckofranklin/cdk-using-central-register-pattern-for-resource-sharing-27k2"&gt;CDK - Using Central Register Pattern for Resource Sharing&lt;/a&gt;&lt;/strong&gt; — A practical pattern for sharing resources across stacks, by tyckofranklin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/rajmurugan/part-2-cdk-infrastructure-for-amazon-bedrock-agentcore-and-every-gotcha-youll-hit-393h"&gt;CDK Infrastructure for Amazon Bedrock AgentCore (And Every Gotcha You'll Hit)&lt;/a&gt;&lt;/strong&gt; — Real-world guide to deploying Bedrock AgentCore with CDK, by rajmurugan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws-heroes/zero-orphaned-resources-force-deleting-any-cloudformation-stack-3b1h"&gt;Zero Orphaned Resources: Force Deleting Any CloudFormation Stack&lt;/a&gt;&lt;/strong&gt; — Kenta Goto on cleaning up stuck stacks without leaving orphaned resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/mianzubair/what-really-happens-when-you-deploy-with-aws-cdk-4kli"&gt;What Really Happens When You Deploy with AWS CDK?&lt;/a&gt;&lt;/strong&gt; — A look under the hood at the CDK deployment process, by mianzubair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/katevu/building-a-serverless-llm-pipeline-with-amazon-bedrock-and-sagemaker-fine-tuning-using-aws-cdk-4125"&gt;Building a Serverless LLM Pipeline with Bedrock and SageMaker using CDK&lt;/a&gt;&lt;/strong&gt; — End-to-end CDK pipeline for LLM fine-tuning, by katevu.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://awslabs.github.io/mcp/servers/aws-iac-mcp-server" rel="noopener noreferrer"&gt;AWS IaC MCP Server&lt;/a&gt; — AI-powered CDK development via Model Context Protocol.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://construct.dev/" rel="noopener noreferrer"&gt;CDK Construct Hub&lt;/a&gt; — Discover community-built constructs.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/v2/" rel="noopener noreferrer"&gt;AWS CDK API Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Can You Be Involved
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Report Issues
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/aws/aws-cdk/issues/new/choose" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contribute Code
&lt;/h3&gt;

&lt;p&gt;Check our &lt;a href="https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributing guide&lt;/a&gt; and look for &lt;a href="https://github.com/aws/aws-cdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22" rel="noopener noreferrer"&gt;&lt;code&gt;good first issue&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/aws/aws-cdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22" rel="noopener noreferrer"&gt;&lt;code&gt;help wanted&lt;/code&gt;&lt;/a&gt; labels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Join the Conversation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slack&lt;/strong&gt;: &lt;a href="https://cdk.dev" rel="noopener noreferrer"&gt;CDK.dev community&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/aws/aws-cdk/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack Overflow&lt;/strong&gt;: &lt;a href="https://stackoverflow.com/questions/tagged/aws-cdk" rel="noopener noreferrer"&gt;&lt;code&gt;aws-cdk&lt;/code&gt; tag&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Star the Repo
&lt;/h3&gt;

&lt;p&gt;Give us a star on &lt;a href="https://github.com/aws/aws-cdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;! ⭐&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Feedback? Share in &lt;a href="https://github.com/aws/aws-cdk/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>infrastructureascode</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
