Gestionar el acceso a AWS bedrock utilizando AWS Lambda y API Gateway
Tags: aws, lambda, bedrock
Published: 2024-02-27 16:02:52

---
title: Gestionar el acceso a AWS bedrock utilizando AWS Lambda y API Gateway
published: true
date: 2024-02-26 12:13:54 UTC
tags: aws,lambda,bedrock 
canonical_url: https://olcortesb.hashnode.dev/gestionar-el-acceso-a-aws-bedrock-utilizando-aws-lambda-y-api-gateway
---

Desde el lanzamiento y disponibilidad de [AWS Bedrock](https://aws.amazon.com/es/generative-ai/) en distintas regiones [[1](https://aws.amazon.com/es/about-aws/whats-new/2023/09/amazon-bedrock-generally-available/)][[2](https://aws.amazon.com/es/about-aws/whats-new/2023/10/amazon-bedrock-europe-frankfurt-aws-region/)], las pruebas de conceptos son necesarias para validar y probar las funcionalidades y caractersticas que ofrece el servicio.

Para agregar una capa de seguridad y lmites a las invocaciones construimos un _proxy_ utilizando _AWS lambda_ y _APIGateway_ en los ambientes de pruebas y testing que realizamos, sobre todo si tenemos que habilitar el servicio para otros sectores dentro de la empresa o a nuestros clientes.

> El API key siempre debera ir acompaado de una cuota(Lmite de invocaciones), y en la mayora de los casos para ambientes efmeros o de pruebas; para ms seguridad siempre deberamos agregar _Autorizers_ a nuestras API, como AWS cognito/JWT Customs , etc.

A continuacin un ejemplo de cmo agregar un _proxy_ a AWS _bedrock_ y exponerlo a travs de una API con cuota.

![Arquitectura](https://cdn.hashnode.com/res/hashnode/image/upload/v1708945517369/c2b8ec71-dc8f-4bec-ba41-a72a357cc645.png)

## Conectarnos a AWS _bedrock_ desde Lambda

Para conectarnos usaremos [Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html), y nos conectaremos a modo de prueba a dos de los LLM disponibles tras BedRock. _Titan(amazon.titan-text-express-v1)_ y _Llama(meta.llama2-70b-chat-v1)_ .

```
# src/client_bedrock.pyimport boto3import botocoreimport jsonbedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')
```

Y luego, un par de mtodos para cada _LLM_ al que queremos conectarnos, para el caso de titan(`amazon.titan-text-express-v1`):

```
# src/client_bedrock.pydef titan_model(prompt): body = json.dumps({ "inputText": prompt, "textGenerationConfig":{ "maxTokenCount":4096, "stopSequences":[], "temperature":0, "topP":0.9 } }) modelId = 'amazon.titan-text-express-v1' # change this to use a different version from the model provider accept = 'application/json' contentType = 'application/json' outputText = "\n" text = bedrock_runtime.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType) response_body = json.loads(text.get('body').read()) outputText = response_body.get('results')[0].get('outputText') response = {"statusCode": 200, "body": json.dumps(outputText)} return response
```

Para _llama(_`meta.llama2-70b-chat-v1`_)_ usamos un mtodo similar:

```
# src/client_bedrock.pydef llama_model(prompt): body = json.dumps({ 'prompt': prompt, 'max_gen_len': 512, 'top_p': 0.9, 'temperature': 0.2 }) modelId = 'meta.llama2-70b-chat-v1' # change this to use a different version from the model provider accept = 'application/json' contentType = 'application/json' outputText = "\n" response = bedrock_runtime.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType) response_body = json.loads(response.get('body').read().decode('utf-8')) outputText = response_body['generation'].strip() response = {"statusCode": 200, "body": json.dumps(outputText)} return response
```

Y luego, un _handler_ para decidir a que _LLM_ queremos llamar:

```
# src/handler.pyimport jsonfrom src.client_bedrock import titan_model, llama_modelfrom enum import Enumclass Model(Enum): TITAN = 'titan' LLAMA = 'llama' NONE = 'none' @classmethod def _missing_(cls, value): return cls.NONEdef hello(event, context): q = json.loads(event['body']) model = Model(q['model']) match model: case Model.TITAN: text = titan_model(json.dumps(q['question'])) case Model.LLAMA: text = llama_model(json.dumps(q['question'])) case Model.NONE: text = "Model does not match" response = {"statusCode": 200, "body": json.dumps(text)} return response
```

## El despliegue y gestin de las cuotas

Para el despliegue utilic [Serverless Framework](https://www.serverless.com/) , de la configuracin del _Serverless Framework_ importante destacar:

- Configuracin del API _key_:

```
  apiGateway: apiKeys: - name: tollmkey description: tollmkey api key # Optional usagePlan: quota: limit: 100 # Limint by moth offset: 2 period: MONTH throttle: burstLimit: 200 rateLimit: 100
```

En el proceso de deploy _Serverless Framework_ nos muestra como salidas el nombre de la API y la _API Key_:

```
Deploying lambda-bedrock-proxy to stage dev (us-east-1) Service deployed to stack lambda-bedrock-proxy-dev (80s)# Value of APIKEYapi keys: tollmkey: YOURAPIKEYHERE - tollmkey api key# Optionalendpoint: GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/bedrockfunctions: hello: lambda-bedrock-proxy-dev-hello (3.1 kB)layers: bedrock: arn:aws:lambda:us-east-1:XXXXXXXXX:layer:bedrock-dependencies:4
```

- Configuracin de la Lambda layer: El [runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) para _Python_ de lambda toma una versin de Boto3(1.26) por default, pero para conectarse con _bedrock_ es necesario una versin especfica o superior (`boto3>=1.28.57`), para esto se configur _Boto3_ dentro de una layer al igual que el resto de dependencias.

```
# layers/tools/aws_requirements.txtboto3>=1.28.57awscli>=1.29.57botocore>=1.31.57langchain>=0.0.350
```

## Invocar la API con el _API key_

Para invocar los modelos el `body` tiene dos campos: `question` y `model`. `question` es la consulta que le pasaremos al modelo y `model` el tipo de modelo, para este ejemplo estn configurados dos modelos _Titan(_`amazon.titan-text-express-v1`_)_ y _Llama(_`meta.llama2-70b-chat-v1`_)_ .

```
curl --location 'https://YOURID.execute-api.us-east-1.amazonaws.com/dev/bedrock' \--header 'Content-Type: application/json' \--header 'x-api-key: YOURAPIKEY' \--data '{ "question": "What is a dystopia", "model": "llama"}'
```

## Probando el lmite de la cuota

Si superamos el lmite de las cuotas que definimos en el apartado _Apigateway/usagePlan_ del `serverless.yml` nos indicar la respuesta de la API con un mensaje de que el lmite se ha superado y un cdigo _HTTP 429_

```
HTTP/1.1 429 Content-Type: application/jsonContent-Length: 28Connection: closex-amzn-RequestId: 893834e6-5f4c-4bb6-95e0-a5b7f255bf51x-amzn-ErrorType: LimitExceededExceptionx-amz-apigw-id: TvNwHEKSIAMEQiQ=X-Cache: Error from cloudfront{ "message": "Limit Exceeded"}
```

## Codigo fuente:

El ejemplo completo sobre el que escrib este resumen est en el siguiente repositorio: [https://github.com/olcortesb/lambda-bedrock-proxy/tree/main](https://github.com/olcortesb/lambda-bedrock-proxy/tree/main)

## Conclusiones:

- Configuramos un solo `endpoint` para llamar a distintos modelos que son soportados por _AWS Bedrock_, se pueden extender agregando la funcin para cada _LLM_ y un campo ms en el [Enum](https://docs.python.org/3/library/enum.html)

- Se configur una cuota para una API _key_ y se pueden configurar distintas cuotas para distintos equipos, una cuota para cada _API key_

- Se configur _Boto3_ para usar con lambda las versiones soportadas dentro de una Lambda Layer

- Para ms seguridad se podra habilitar [Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html) a la lambda con [AWS Cognito](https://aws.amazon.com/es/cognito/) o un `JWT Custom`

## Referencias

[1]. [https://aws.amazon.com/es/about-aws/whats-new/2023/09/amazon-bedrock-generally-available/](https://aws.amazon.com/es/about-aws/whats-new/2023/09/amazon-bedrock-generally-available/)

[2]. [https://aws.amazon.com/es/about-aws/whats-new/2023/10/amazon-bedrock-europe-frankfurt-aws-region/](https://aws.amazon.com/es/about-aws/whats-new/2023/10/amazon-bedrock-europe-frankfurt-aws-region/)