Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Un Starter Kit de Amazon Bedrock basado en Go y EKS

By Paul O'BrienAug 11, 20247 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

Imagen de un Starter Kit de Kubernetes generada con Amazon Bedrock

Integrar un Large Language Model (LLM) en una aplicación puede hacerla más potente y fácil de usar, pero conseguirlo de forma escalable y bien acoplada a una base de código existente puede traer sus complicaciones.

Este artículo presenta un proyecto inicial que te permite arrancar rápido.

¿Por qué armar un starter kit en Go?

La mayoría de los ejemplos de IA generativa que encuentras corren en Python, lo cual tiene sentido porque Python es la lingua franca del Machine Learning y la ciencia de datos. Pero ¿qué pasa si tienes una aplicación existente que solo quieres potenciar con modelos de Machine Learning mediante llamadas comunes a API, sin entrenar tus propios modelos ni hacer otras tareas dedicadas de Machine Learning?

Go es un lenguaje ideal para hacer llamadas a servicios web, incluyendo APIs de Machine Learning como Amazon Bedrock o ChatGPT.

El Starter Kit que se describe en este artículo está enfocado en Amazon Bedrock y lo encuentras aquí:

https://github.com/p-obrien/bedrock-microservice-starter

Requisito previo: habilitar los modelos de Amazon Bedrock

Para usar Amazon Bedrock necesitas habilitar los Foundational Models correspondientes; lamentablemente, a día de hoy (julio de 2024) esto solo se puede hacer desde la consola de AWS.

Para habilitar el Foundational Model sigue estos pasos:

1. Inicia sesión en tu consola de AWS y dirígete a Amazon Bedrock

2. Despliega el panel de navegación de la izquierda y baja hasta "Model access"

3. En el cuadro "Find model" busca el modelo que quieras habilitar; para el proyecto inicial usamos "Claude 3 Sonnet". Si no está habilitado en tu proyecto, tendrás que solicitar acceso. Haz clic en el enlace "request model access", selecciona los modelos que quieras habilitar y avanza por las siguientes pantallas.

Una vez habilitado, debería verse así:

Modelos base de AWS Bedrock

Nota: la disponibilidad del modelo varía según la región; quizás te convenga ejecutarlo en una región distinta a la de tus workloads habituales.

**Visión general del Starter Kit**

El starter kit está pensado como un punto de referencia que puedas reutilizar en tu propia solución. Hace lo siguiente:

  • Despliega un clúster de EKS basado en Graviton con un managed node group
  • Despliega el AWS Load Balancer Controller
  • Despliega EKS Pod Identity y la service account

El código en Go despliega un microservicio que:

  • Requiere una API Key para autenticar a quien hace la llamada
  • Usa EKS Pod Identity para comunicarse con Amazon Bedrock
  • Devuelve una conversación con Amazon Bedrock, en concreto con el Foundational Model Claude Sonnet, aunque puedes personalizarlo fácilmente

Si ya tienes un clúster de EKS, puedes correr el código allí, pero deberás asegurarte de que el Pod Identities Agent y la Service Account estén desplegados.

**Infraestructura**

El starter kit usa Terraform u OpenTofu para desplegar un clúster de AWS EKS desde cero; el README.md de la carpeta Infrastructure tiene los detalles para usarlo.

En lugar de usar Access Keys dentro de un contenedor, secrets u otros métodos de autenticación, usaremos una funcionalidad de AWS EKS llamada Pod Identities para conceder acceso a Bedrock.

Hasta hace poco se usaba EKS IRSA (IAM Roles for Service Accounts) con este propósito, pero era bastante complejo de configurar y tenía limitaciones. EKS Pod Identities resuelve esas limitaciones y aporta una solución sencilla. Si te da curiosidad, los detalles están AQUÍ.

EKS Pod Identity

Paso 1: desplegar el add-on de EKS Pod Identity

Paso 2: definir un rol y permitir que el add-on de Pod Identity lo asuma

Paso 3: asociar una Kubernetes Service Account con el rol

Paso 4: verificar que EKS tenga una service account que coincida

Código

La comunicación con Amazon Bedrock desde el microservicio se gestiona a través de la API de Amazon Bedrock para Go.

Nota: se pueden lograr interacciones mucho más complejas que las que se muestran en este proyecto inicial.

La forma fácil de autenticarte

La ventaja de usar EKS Pod Identities es que resulta prácticamente transparente para tu aplicación. Por ejemplo, el siguiente código usa la API de Bedrock para Go para crear un cliente y autenticarse mediante EKS Pod Identities:

func init() {
 cfg, err := config.LoadDefaultConfig(context.TODO())
 if err != nil {
  log.Fatalf("unable to load SDK config, %v", err)
 }

 brc = bedrockruntime.NewFromConfig(cfg)
}

En vez de tener que ocuparte de cargar un secret, el pod ya tiene configuradas automáticamente las variables de entorno del AWS Profile, así que basta con inicializar el cliente de Bedrock con los valores por defecto.

Servidor REST

Usamos el framework Echo para crear un endpoint REST autenticado que reciba la solicitud de quien llama; en el proyecto inicial usamos una API key sencilla:

// Sample API Key please don't use this in production and consider something more robust
var apiKey string = "test-api-key"
var brc *bedrockruntime.Client

const modelID = "anthropic.claude-3-sonnet-20240229-v1:0"

func main() {

 cfg, err := config.LoadDefaultConfig(context.TODO())
 if err != nil {
  log.Fatal(err)
 }

 brc = bedrockruntime.NewFromConfig(cfg)

 e := echo.New()
 e.Use(middleware.Logger())
 e.Use(middleware.Recover())

 e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
  return key == apiKey, nil
 }))

 e.POST("/converse", converseHandler)

 if err := e.Start(":8080"); err != http.ErrServerClosed {
  log.Fatal(err)
 }
}

A partir de ahí, tomamos el prompt del Multipart Form y se lo pasamos a Bedrock antes de devolverle la respuesta al cliente.

func converseHandler(c echo.Context) error {
 // Initialize the Bedrock ConverseInput with the model ID
 converseInput := &bedrockruntime.ConverseInput{
  ModelId: aws.String(modelID),
 }

 // Get the user input from the form value
 input := c.FormValue("message")

 // Create the user's message
 userMsg := types.Message{
  Role: types.ConversationRoleUser,
  Content: []types.ContentBlock{
   &types.ContentBlockMemberText{
    Value: input,
   },
  },
 }

 // Append the user's message to the conversation input
 converseInput.Messages = append(converseInput.Messages, userMsg)

 // Call the Bedrock Converse API
 output, err := brc.Converse(context.Background(), converseInput)
 if err != nil {
  log.Fatal("Error calling Converse API:", err)
  return c.String(http.StatusInternalServerError, "Internal Server Error")
 }

 // Extract the response from the assistant
 response, ok := output.Output.(*types.ConverseOutputMemberMessage)
 if !ok {
  return c.String(http.StatusInternalServerError, "Failed to parse response")
 }

 responseContentBlock := response.Value.Content[0]
 text, ok := responseContentBlock.(*types.ContentBlockMemberText)
 if !ok {
  return c.String(http.StatusInternalServerError, "Failed to parse response content")
 }

 // Create the assistant's message
 assistantMsg := types.Message{
  Role:    types.ConversationRoleAssistant,
  Content: response.Value.Content,
 }

 // Append the assistant's message to the conversation input
 converseInput.Messages = append(converseInput.Messages, assistantMsg)

 // Return the assistant's response to the client
 return c.JSON(http.StatusOK, text.Value)
}

Mejoras

Algunas mejoras que dejamos como ejercicio para el lector:

  • Respuestas en streaming: el framework Echo admite respuestas en streaming y sería interesante recibir una respuesta en streaming desde Bedrock
  • System Prompt: agregar un system prompt para inicializar Amazon Bedrock con una instrucción específica antes de gestionar las interacciones con el cliente

Reflexiones finales…

Gracias por leer; si tienes alguna pregunta o comentario, no dudes en abrir un issue en mi repositorio de Github.

Si todavía no conoces a DoiT International, te invitamos a echarnos un vistazo. Nuestro equipo está listo para conocerte y entender tus necesidades de cloud engineering. Contamos exclusivamente con talento senior de Engineering y nos especializamos en asesoría avanzada de arquitectura cloud y consultoría de debugging. ¡Contáctanos y conversemos!