Llevando la gestión de Shopify al siguiente nivel con GenAI
Tags: genai, bedrock, shopify
Published: 2024-03-11 10:53:15

Tu empresa de venta online recibe muchos pedidos de atención al cliente (Customer Care), y generalmente las preguntas son las mismas siempre, ¿Dónde está mi orden?, ¿Cómo hago una devolución?, Quise aplicar un descuento a mi orden y no funcionó, entre otras, en períodos de muchas ventas y promociones pueden provocar que el equipo de atención al cliente se sature respondiendo emails, contestando llamados telefónicos para justamente responder a estas preguntas para abordar estos temas recurrentes.

Hoy día existen integraciones telefónicas donde un cliente al llamar tendrá que pasar al menos 3 o 4 minutos presionando los botones de su teléfono para llegar a dar con el estado de su orden o hablar con un represente, o tal vez intentando que un robot entienda lo que quiere decir “en pocas palabras” para terminar derivando con un agente para responder siempre las mismas preguntas.

Para solucionar esto, construí una solución que utiliza Generative AI  para entender lo que el cliente quiere y armar un agente capaz de responder a las preguntas del cliente utilizando llamadas a una API de GenAI - Amazon Bedrock.

Si estás dando tus primeros pasos y quieres descubrir cómo aplicar inteligencia artificial a casos de uso de la vida real, si tienes una tienda online y quieres llevar la experiencia de atención al cliente al siguiente nivel, en esta serie de episodios te voy a mostrar cómo utilizando Amazon Bedrock agents y AWS Lambda construí un agente que se encargará de mantener informados a los clientes sobre el estado de sus pedidos realizados en una tienda en Shopify.

¿Qué vas a aprender?

- Crear un Agente de Bedrock utilizando uno de los modelos fundacionales disponibles: Claude Instant V1
- Crear una especificación openAPI 3.0 que funcione con un Agente de Bedrock
- Crear una función lambda que cumpla con los requisitos para un agente de Bedrock

##¿Cómo funciona la aplicación?

![Arquitectura de la aplicación](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/reqjtp315lg9oc11c10f.png)

La aplicación comprende el backend para que luego la puedas integrar a cualquier cliente de Chat a través de API Gateway o bien mediante llamados a una API.

Esta aplicación consultará a la [API de Bedrock Agents](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html) e interpretará lo que el cliente está solicitando y buscará recolectar toda la información necesaria para poder consultar la información de la orden y entregarla a nuestro cliente.

Para esto, el agente al tener la información necesaria, invocará una Función Lambda que será la encargada de interactuar con la API de Shopify y devolverle al agente la información que necesita para poder responder la pregunta que el cliente necesita.

## El  producto final

{% embed https://youtube.com/shorts/9pXsVuMylgQ %}

## 🤓¿Qué son los Amazon Bedrock Agents?

Los [Agentes de Amazon Bedrock](https://aws.amazon.com/es/bedrock/agents/) son herramientas de IA que automatizan tareas empresariales, como la gestión de pedidos, mediante instrucciones en lenguaje natural y AWS Lambda, mejorando la eficiencia y reduciendo el trabajo manual.

## ☑️ Armar un Agente de Bedrock en 4 pasos

Para que el agente funcione vamos a necesitar

1. Generar credenciales de Shopify: Dado que vamos a utilizar Shopify para este tutorial, es necesario un access Token.
2. Escribir una especificación OpenAPI 3.0: Aquí le indicaremos al agente cómo funciona la función, que parámetros recibe y qué respuestas envía y cómo llamarla.
3. Creación de una función Lambda que se encargue de obtener el estado de la orden de Shopify.
4. Creación y configuración del agente.

## 🔑 Paso 1: Generar las credenciales en Shopify

[Shopify](https://www.shopify.com) es una plataforma líder de ecommerce que fue creada por desarrolladores y permite realizar la gestión de una tienda online de manera muy sencilla, ha sido pensada y creada por programadores por lo que para cada funcionalidad tiene una API.

La Función Lambda hace un llamado a una de las API de Shopify, y va a necesitar unas credenciales y permisos para consultar información de los pedidos de la tienda.

a) Obtener las credenciales, (si ya sabes cómo obtener estas credenciales puedes saltarte esta paso).
b) Crear una aplicación personalizada (Custom App) en Shopify:
Tutorial paso a paso: [https://help.shopify.com/es/manual/apps/app-types/custom-apps#create-and-install-a-custom-app](https://help.shopify.com/es/manual/apps/app-types/custom-apps#create-and-install-a-custom-app)
c) Definir los permisos que se necesitan: 
En este caso el unico necesario es: permiso para leer las ordenes (read_orders) [https://help.shopify.com/es/manual/apps/app-types/custom-apps#get-the-api-credentials-for-a-custom-app ](https://help.shopify.com/es/manual/apps/app-types/custom-apps#get-the-api-credentials-for-a-custom-app )
d) Instalar la app y obtener las credenciales: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2co199xr1glyoan7dsw.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5zqg0iwu64ynp5xiefr9.png)

## 📃 Paso 2: Crear la especificación [OpenAPI 3.0](https://www.openapis.org/what-is-openapi)

Este es el punto clave y el secreto de la magia, cuanto mejor este especificada y documentada la API, mejor va a funcionar el Agente de Bedrock.

Una especificación openAPI 3.0 es un archivo de texto json donde con un formato específico se indica:
- Los endpoints de la API
- Los métodos HTTP que acepta
- Los parámetros que requiere y sus formatos
- La respuesta que va a devolver y sus formatos.

Para cada uno de estos existe un campo description donde se debe indicar en lenguaje natural que es lo que hace, esto es lo que ayudará al Agente de Bedrock a tomar una decisión

Cabecera del documento:

```
{
    "openapi": "3.0.0", 
    "info": {
        "title": "Order Info API", 
        "version": "1.0.0",
        "description": "API that provides and identifies order details"
            },
    "paths":{}
}
```

Path:
/orders/{orderId}: Recibirá requests GET y va a devolver la información de la orden, para esto indicar que el orderId es un parámetro requerido y se recibe por URL.

```
"paths": {
        "/orders/{orderNumber}": {
            "get": {
                "summary": "Gets information about an order",
                "description": "Gets the entire information about an order based on its number",
                "operationId": "getOrderInfo",
                "parameters": [
                    {
                        "name": "orderNumber",
                        "in": "path",
                        "description": "ID of the order that needs to be fetched",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "format": "int64"
                        }
                    }
                ],
                "responses": {}
            }

```
Respuesta:

Armar una respuesta formateada de acuerdo a los requerimientos de un agente de Bedrock:

- Status Code: 200
- Un objeto con una propiedad content, application/json
- Una enumeración de las propiedades de dicho objeto.

Crear un objeto con las siguientes propiedades:
- Id: Numero de la orden
- Status: El estado de la orden
- MoreInfo: Si la orden esta en estado Fulfilled aqui enviaremos la URL para que el cliente pueda hacer el tracking del envío.

Aquí una representación del objeto final:

```
{
 'application/json': {
            'body': JSON.stringify({
                "id": orderNumber,
                "status": orderStatus,
                "moreInfo": moreInfo
            })
        }
}
```
La especificación openAPI 3.0 de la respuesta:

```
 "responses": {
                    "200": {
                        "description": "Object with the order information",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "integer",
                                            "format": "int64"
                                        },
                                        "status": {
                                            "type": "string",
                                            "description": "Status of the order"
                                        },
                                        "moreInfo": {
                                            "type" :"string",
                                            "description": "More information about the order usually the tracking URL"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
```

Especificación openAPI 3.0 completa:

```
{
    "openapi": "3.0.0", 
    "info": {
        "title": "Order Info API", 
        "version": "1.0.0",
        "description": "API that provides and identifies order details"
            },
    "paths": {
        "/orders/{orderNumber}": {
            "get": {
                "summary": "Gets information about an order",
                "description": "Gets the entire information about an order based on its number",
                "operationId": "getOrderInfo",
                "parameters": [
                    {
                        "name": "orderNumber",
                        "in": "path",
                        "description": "ID of the order that needs to be fetched",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "format": "int64"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Object with the order information",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "integer",
                                            "format": "int64"
                                        },
                                        "status": {
                                            "type": "string",
                                            "description": "Status of the order"
                                        },
                                        "moreInfo": {
                                            "type" :"string",
                                            "description": "More information about the order usually the tracking URL"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}


```
El archivo final debe ser cargado a un Bucket de S3, será necesario al momento de la creación del Agente.

##🧑‍💻 Paso 3: Creación de la función Lambda

Crear una  función Lambda que será la encargada de consultar la información de la orden en Shopify, para esto crea una función Lambda: 

1. Ingresar a la consola de AWS Lambda [https://console.aws.amazon.com/lambda/home#/functions](https://console.aws.amazon.com/lambda/home#/functions)
2. Presionar Create Function
3. Author from Scratch (Crear desde 0)
4. Asignar un nombre
5. Elegir el runtime, en este caso el ejemplo lo haré con NodeJS pero puedes hacerlo con el lenguaje de programación que gustes.
6. Arquitectura: x86_64 

Para hacer consultas a una API Externa por HTTPs se utilizará la librería Axios de NodeJS y en el cuerpo de la función lambda se arma el objeto.

Si necesitas ayuda sobre cómo utilizar librerías de terceros en NodeJS y Lambda dejame un comentario y escribo un artículo sobre este tema.

El objeto de Axios necesita 2 propiedades:

1. La URL donde vamos a hacer el request
2. Los Headers (Cabeceras del request):
3. Shopify requiere un Header especial llamado X-Shopify-Access-Token, el mismo sirve para autenticar el request
4. El content type del request.

```
const axios = require('axios');

/** 
Let's create the axios request to Shopify
In order to get the Shopify-Access Token we'll need to create a custom app in Shopify to use the token 
*/
const shopifyRequest = axios.create({
        baseURL: `https://your-shopify-store.myshopify.com/admin/api/2024-01/graphql.json`,
        headers: {
            'X-Shopify-Access-Token': '[AQUI VA EL TOKEN GENERADO]',
            'Content-Type': 'application/json'
        }
    });

exports.handler = async (event) => {
// Lambda Function Code GOES here
   
}   
```

La API de Shopify es GraphQL el siguiente request le indica que recibirá el orderNumber como parámetro, y que queremos traer el fulfillment status de la orden y en caso que haya fulfillments, la información del dicho Fulfillment. (Fulfillment es un objeto que trae información sobre el paquete del cliente cuando el mismo ha sido preparado y enviado)

A fines didácticos dado que ya se que el orderNumber va a venir en la posición 0 de los parámetros especificamos, esto se puede mejorar buscando la propiedad con el nombre orderNumber dentro del array parameters del objeto event de Lambda.

```
const orderNumber = event.parameters[0].value;
    const query = `{
        orders(first:1,query: "name:${orderNumber}") {
            edges {
                node {
                    name
                    displayFulfillmentStatus
                    fulfillments {
                        trackingInfo {
                            number
                            url
                        }
                    }
                }
            }
        }
    }`;
    
    // Let's get the response from Shopify

    const shopifyResponse = await shopifyRequest.post('', { query: query });
```

En el objeto shopifyResponse se guardará la información buscada y responseBody es la respuesta formateada tal como la necesita el Agente de Bedrock

```
// Let's get the order fulfillment status from the response

    const orderStatus = shopifyResponse.data.data.orders?.edges[0]?.node.displayFulfillmentStatus;
    const moreInfo = shopifyResponse.data.data.orders?.edges[0]?.node.fulfillments[0]?.trackingInfo[0]?.url;
    // Let's return the response

    let responseBody = {
        'application/json': {
            'body': JSON.stringify({
                "id": orderNumber,
                "status": orderStatus,
                "moreInfo": moreInfo
            })
        }
    }
```

El Agente de Bedrock necesita recibir en la respuesta algunos atributos para poder continuar con el flujo:

Como se verá  mås adelante las respuestas de la API van a pertenecer a un ActionGroup del agente, entonces se debe devolver una respuesta para ese ActionGroup específico, el objeto debe tener las siguientes propiedades:
- El ActionGroup
- El Path
- El Método HTTP que invocó al evento
- El Status Code
- El Body de la respuesta (responseBody)

```
let actionResponse = {
        actionGroup: event.actionGroup,
        apiPath: event.apiPath,
        httpMethod: event.httpMethod,
        httpStatusCode: 200,
        responseBody: responseBody,

    }
```

Luego se debe devolver esta actionResponse como respuesta al request:

```
 let response = {
        messageVersion: '1.0',
        statusCode: 200,
        response: actionResponse,
    };
    return response;
```

Repositorio con la función y la especificación OpenAPI: [https://github.com/ricardoceci/aws-bedrock-agents-demo/](https://github.com/ricardoceci/aws-bedrock-agents-demo/ )

## 👔 Paso 4: Creación de un agente de Bedrock

Lo primero es ir al servicio “Amazon Bedrock”: [https://console.aws.amazon.com/bedrock/](https://console.aws.amazon.com/bedrock/) y allí seleccionar Agents y luego Create Agent


![Creación de un Agente de Bedrock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grzn8ue6djs7j16238g4.png)


En el siguiente paso:
- Asignar un nombre al Agente, por ejemplo: OrderInfoAgent
- Indicar si el agente debe solicitar más información en caso que le falte: Si
- Indicar si deseas utilizar un rol ya creado o crear uno nuevo, las opciones de encriptación y el timeout (en cuanto tiempo el agente considera que la sesión terminó) y los tags

A continuación:

- Seleccionar el FM (Foundational Model) que va a darle vida al agente:
Aquí una guía sobre cómo elegir un modelo fundacional: [https://dev.to/aws-espanol/como-elegir-un-llm-272o](https://dev.to/aws-espanol/como-elegir-un-llm-272o)
Si no te aparece ninguno es porque debes primero habilitarlos (https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).
- Escribir las instrucciones para el agente, aquí es donde es muy importante el prompt que escribas, si bien el modelo puede interpretar múltiples idiomas, escribirlo en inglés hará las cosas más divertidas, por ejemplo: _You're a kind customer care agent that is here to provide order status information to our customers, on each response you'll provide as much information as you can grab about the order and you'll always thank our customer for being a loyal customer, if you don't know the answer tell the customer to contact customercare@mycompany.com_

![Seleccion de Modelo Fundacional](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okxfh4rt5paigywsc26n.png)

Configurar los action groups, un agente puede tener múltiples grupos de acción, que son los que ejecutará en base a lo que el cliente escriba en el chat, en este caso el agente tendrá un solo actiongroup

- Name: GetOrderInformation
- Elegir la lambda function recién creada
- Y la ubicación de la especificación OpenAPI en un bucket S3 que fue creada al principio.

![Configuración de ActionGroup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7ycogw9pgxh3sh9oogv.png)

Omitir la creación de una base de conocimientos,  en la pantalla de revisión revisar que todo esté correcto y presionar “Create Agent” 

**Voilá**, en unos minutos el agente estará listo para probar:
**Alerta de Spoiler**: Copiar el ARN que lo será necesario en breve:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uiwyt4pjly8ue4oi5knv.png)

## 💣 Probar el agente:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rodydo72evngjhnfve6w.png)

Explotó!. 🤯

¿Qué pasó?
Apreció el siguiente error: Access denied while invoking Lambda function `arn:aws:lambda:us-east-1:XXXXXXX:function:XXXXXXX. Check the permissions on Lambda function and retry the request`.

Si presionamos Show trace, no dice nada que ayude, el problema radica en que en ningún momento se le dió permiso al agente para Invocar la Función Lambda.

## 😱 Mi agente de Bedrock no funciona: Permisos necesarios de la Función Lambda

Para que el agente de Bedrock pueda invocar la Función Lambda tiene que tener permisos, para esto hay que agregar una Resouce Based Policy a la Función.

Desde la consola hacerlo de la siguiente manera:

1) Dentro de la función lambda, ir a Configurations -> Permissions

![Lambda Permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00nsn8vasgz10e1651p0.png)

2) En la sección resource based policy statements, presionar “Add Permissions”

![Agregar permisos a una función lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lncwgb0qewfzqdope92g.png)

3) Seleccionamos AWS Service, y desplegar la lista de servicios, desafortunadamente, Bedrock no aparece como lista de servicios, así que seleccionar: Other (Otro)


![Agregar permisos para Bedrock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4uxv6dtmxmahvd5j045.png)

4) Completar la información del Agente:

- StatementId: Un identificador para la policy
- Principal: bedrock.amazonaws.com
- Source ARN: El ARN del agente de Bedrock
- Action: lambda:invokeFunction

![Información de los permisos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pekupd254md0ton2z17l.png)

##🎉 Todo funciona 🙂

Ahora al probar el agente y preguntarle sobre el estado de la orden, será capaz de responder con información y no con un error de permisos, el agente está listo.

El agente además es capaz de hablar en múltiples idiomas dado que el modelo fundacional Claude v1 tiene la capacidad de hacerlo, no todos los modelos tienen esta opción.

En español:

![Bedrock Agent en Español](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49bnaofa3b37nza3tlz3.png)

En inglés:

![Bedrock Agent en Inglés](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uzd6dvrgjyz8zuw9le35.png)

## 🎯 Conclusión:

En este episodio has aprendido a crear un Agente de Bedrock en pocos pasos y con muy poco código relacionado a la inteligencia en si del Agente dado que el mismo es capaz de interpretar las necesidades del cliente porque está construido con Generative AI.

Con este agente se busca reducir la cantidad de llamadas telefónicas o correos electrónicos con preguntas que ya se sabe donde ir a buscar la respuesta o a que endpoints de una API llamar.

El foco principal de los Agentes de Bedrock es que el desarrollador se ocupe de armar el código que le entregue  la información al Agente y que el Agente sea quien se encargue de:
Entender qué quiere el cliente
Buscar la información, Llamar a una API para realizar una acción.
Entregar la respuesta al cliente.

También has aprendido a sortear el error sobre el permiso de invocación de la Función Lambda desde un Agente de Bedrock 🙂.

##⏭️ Siguientes pasos (Próximo episodio):

El Agente de Bedrock está funcionando, los siguientes pasos son modificar el prompt, mejorar la Función Lambda para que devuelva más información, y  disponibilizar para que sea invocado desde cualquier sitio web, esto lo veremos en el próximo episodio de esta serie 🙂