Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Um Starter Kit do Amazon Bedrock com Go e EKS

By Paul O'BrienAug 11, 20247 min read

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

Imagem de um Kubernetes Starter Kit gerada pelo Amazon Bedrock

Embutir um Large Language Model (LLM) em uma aplicação pode deixá-la mais poderosa e fácil de usar, mas fazer isso de forma integrada a uma base de código existente e de maneira escalável traz alguns desafios.

Este artigo apresenta um projeto inicial que você pode usar para acelerar esse trabalho.

Por que basear um starter kit em Go?

A maioria dos exemplos de IA Generativa que aparecem por aí roda em Python, o que faz sentido, já que Python é a língua franca do Machine Learning e da Data Science. Mas e se você já tem uma aplicação e só quer turbiná-la com modelos de Machine Learning via chamadas comuns de API, sem criar seus próprios modelos nem se aventurar em outras tarefas dedicadas de Machine Learning?

Go é uma linguagem ideal para fazer chamadas a Web Services, incluindo APIs de Machine Learning como o Amazon Bedrock ou o ChatGPT.

O Starter Kit apresentado neste artigo é focado no Amazon Bedrock e está disponível aqui:

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

Pré-requisito — habilitar os modelos do Amazon Bedrock

Para usar o Amazon Bedrock, é preciso habilitar os Foundational Models que você vai usar. Infelizmente, no momento (julho de 2024) isso só pode ser feito pelo AWS Console.

Para habilitar o foundational model, siga estes passos:

1. Faça login no AWS Console e abra o Amazon Bedrock

2. Expanda o menu de navegação à esquerda e role até "Model access"

3. Na caixa "Find model", procure pelo modelo que você quer habilitar; no projeto inicial, usamos o "Claude 3 Sonnet". Se ele ainda não estiver liberado para o seu projeto, será preciso solicitar acesso. Clique em "request model access", selecione os modelos desejados e siga as próximas telas.

Depois de habilitado, deve ficar assim:

Modelos base do AWS Bedrock

Observação: a disponibilidade dos modelos varia conforme a região; talvez você precise rodar o modelo em uma região diferente da que hospeda seus workloads do dia a dia.

**Visão geral do Starter Kit**

O starter kit foi pensado como um ponto de referência para você adaptar à sua própria solução. Ele faz o seguinte:

  • Provisiona um EKS Cluster baseado em Graviton com um managed node group
  • Implanta o AWS Load Balancer Controller
  • Implanta o EKS Pod Identity e a service account

O código em Go implanta um Microservice que:

  • Exige uma API Key para autenticar quem chama o serviço
  • Usa o EKS Pod Identity para se comunicar com o Amazon Bedrock
  • Retorna uma conversa com o Amazon Bedrock, mais especificamente com o foundational model Claude Sonnet, mas é fácil personalizar isso

Se você já tem um EKS Cluster, dá para rodar o código em um cluster existente, mas será preciso garantir que o Pod Identities Agent e a Service Account estejam implantados.

**Infraestrutura**

O starter kit usa Terraform ou OpenTofu para provisionar um AWS EKS Cluster do zero. O README.md na pasta Infrastructure traz os detalhes de uso.

Em vez de usar Access Keys em um container, secrets ou outros métodos de autenticação, vamos usar um recurso do AWS EKS chamado Pod Identities para conceder acesso ao Bedrock.

Até pouco tempo, o EKS IRSA (IAM Roles for Service Accounts) era usado para isso, mas era bem complexo de configurar e tinha limitações. O EKS Pod Identities resolve essas limitações com uma solução simples de usar. Para quem quiser se aprofundar, os detalhes estão AQUI.

EKS Pod Identity

Passo 1 — Implantar o EKS Pod Identity Add-on

Passo 2 — Definir uma role e permitir que o Pod Identity Add-on a assuma

Passo 3 — Associar uma Service Account do Kubernetes à role

Passo 4 — Garantir que o EKS tenha uma service account correspondente

Código

A comunicação com o Amazon Bedrock dentro do Microservice acontece via API Go do Amazon Bedrock.

Observação: dá para fazer interações bem mais complexas do que as mostradas neste projeto inicial.

Autenticação descomplicada

A vantagem de usar o EKS Pod Identities é que ele é praticamente transparente para a sua aplicação. Por exemplo, o código abaixo usa a API Go do Bedrock para criar um cliente já autenticado via 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)
}

Em vez de se preocupar em carregar um secret, o pod já recebe automaticamente as variáveis de ambiente do AWS Profile, então basta inicializar o cliente Bedrock com os valores padrão.

Servidor Rest

Usamos o Echo Framework para criar um endpoint Rest autenticado que recebe a requisição de quem chama. No projeto inicial, usamos uma API key simples:

// 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 daí, pegamos o prompt do input Multipart Form e o enviamos para o Bedrock antes de devolver a resposta ao 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)
}

Melhorias

Algumas melhorias que ficam como exercício para o leitor:

  • Streaming de respostas — o Echo Framework suporta streaming, e seria legal receber respostas em streaming direto do Bedrock
  • System Prompt — adicionar um system prompt para inicializar o Amazon Bedrock com instruções específicas antes de começar a atender as interações com o cliente

Considerações finais…

Obrigado pela leitura. Se tiver dúvidas ou feedback, fique à vontade para abrir uma issue no meu repositório no Github.

Se você ainda não conhece a DoiT International, vale muito a pena dar uma olhada. Nosso time está pronto para entender você e suas necessidades de engenharia em nuvem. Formados exclusivamente por engenheiros sêniores, somos especialistas em consultoria avançada de nuvem, design arquitetural e suporte em debugging. Entre em contato e vamos conversar!