Image d'un Starter Kit Kubernetes générée par Amazon Bedrock
Intégrer un Large Language Model (LLM) à une application la rend plus puissante et plus simple à utiliser. Le faire de façon à s'inscrire dans une base de code existante tout en restant scalable peut toutefois s'avérer délicat.
Cet article présente un projet de démarrage qui vous permettra de vous lancer rapidement.
Pourquoi bâtir un starter kit sur Go ?
La plupart des exemples d'IA générative que l'on rencontre tournent en Python, ce qui est logique puisque Python est la lingua franca du Machine Learning et de la Data Science. Mais que faire lorsque vous disposez d'une application existante que vous souhaitez simplement enrichir avec des modèles de Machine Learning via de simples appels d'API, sans construire vos propres modèles ni mener d'autres tâches dédiées au Machine Learning ?
Go est un langage idéal pour effectuer des appels vers des Web Services, y compris vers des API de Machine Learning telles qu'Amazon Bedrock ou ChatGPT.
Le Starter Kit présenté dans cet article est centré sur Amazon Bedrock et est disponible ici :
https://github.com/p-obrien/bedrock-microservice-starter
Prérequis — activer les modèles Amazon Bedrock
Pour utiliser Amazon Bedrock, vous devez activer les Foundational Models concernés. À ce jour (juillet 2024), cela n'est malheureusement possible que via la console AWS.
Pour activer le foundational model, suivez ces étapes :
1. Connectez-vous à votre console AWS et accédez à Amazon Bedrock.
2. Déployez le panneau de navigation à gauche et faites défiler jusqu'à Model access.
3. Dans la boîte de dialogue Find model, recherchez le modèle à activer ; pour le starter project, nous utilisons Claude 3 Sonnet. S'il n'est pas activé pour votre projet, vous devrez en demander l'accès. Cliquez sur le lien request model access, sélectionnez les modèles à activer et parcourez les écrans suivants.
Une fois activé, l'écran devrait ressembler à ceci :

Modèles de base AWS Bedrock
Remarque : la disponibilité des modèles varie selon les régions ; il peut être nécessaire d'exécuter le modèle dans une région différente de celle de vos workloads habituels.
**Vue d'ensemble du Starter Kit**
Le starter kit a été pensé comme un point de référence à reprendre dans votre propre solution. Il effectue les opérations suivantes :
- Déploie un cluster EKS basé sur Graviton avec un managed node group
- Déploie l'AWS Load Balancer Controller
- Déploie EKS Pod Identity et le service account
Le code Go déploie un microservice qui :
- Exige une API Key pour authentifier les appelants
- S'appuie sur EKS Pod Identity pour communiquer avec Amazon Bedrock
- Renvoie une conversation avec Amazon Bedrock, plus précisément le foundational model Claude Sonnet, que vous pouvez facilement personnaliser
Si vous disposez déjà d'un cluster EKS, vous pouvez exécuter le code dans un cluster existant, à condition que le Pod Identities Agent et le Service Account aient bien été déployés.
**Infrastructure**
Le starter kit s'appuie sur Terraform ou OpenTofu pour déployer un cluster AWS EKS entièrement neuf. Le fichier README.md du dossier Infrastructure détaille la marche à suivre.
Plutôt que d'utiliser des Access Keys dans un conteneur, des secrets ou d'autres méthodes d'authentification, nous nous appuierons sur une fonctionnalité d'AWS EKS appelée Pod Identities pour autoriser l'accès à Bedrock.
Jusqu'à récemment, EKS IRSA (IAM Roles for Service Accounts) remplissait ce rôle, mais sa configuration restait complexe et présentait certaines limites. EKS Pod Identities lève ces limites et propose une solution simple à mettre en œuvre. Pour les curieux, les détails se trouvent ICI.
EKS Pod Identity
Étape 1 — Déployer l'add-on EKS Pod Identity

Étape 2 — Définir un rôle et autoriser l'add-on Pod Identity à l'assumer

Étape 3 — Associer un Service Account Kubernetes au rôle

Étape 4 — Vérifier qu'EKS dispose d'un service account correspondant

Code
La communication avec Amazon Bedrock depuis le microservice passe par l'API Go d'Amazon Bedrock.
Remarque : des interactions bien plus poussées sont possibles que celles présentées dans ce starter project.
L'authentification en toute simplicité
L'avantage d'EKS Pod Identities, c'est que le mécanisme reste largement transparent pour votre application. Le code ci-dessous s'appuie par exemple sur l'API Go Bedrock pour créer un client et tirer parti de l'authentification 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)
}
Plutôt que d'avoir à charger un secret, le pod dispose automatiquement des variables d'environnement définies pour le profil AWS. Il suffit donc d'initialiser le client Bedrock avec les valeurs par défaut.
Serveur REST
Nous utilisons le framework Echo pour créer un endpoint REST authentifié, capable de recevoir les requêtes des appelants. Pour le starter project, nous nous contentons d'une simple API key :
// 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)
}
}
Nous récupérons ensuite le prompt depuis le Multipart Form et le transmettons à Bedrock avant de renvoyer la réponse au client.
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)
}
Améliorations
Quelques pistes laissées en exercice au lecteur :
- Réponses en streaming — le framework Echo prend en charge le streaming, et il serait intéressant d'obtenir une réponse en streaming depuis Bedrock
- System Prompt — ajouter un system prompt pour initialiser Amazon Bedrock avec une consigne précise avant de traiter les interactions avec le client
Pour conclure…
Merci de votre lecture. Pour toute question ou retour, n'hésitez pas à ouvrir une issue sur mon dépôt GitHub.
Si vous ne connaissez pas encore DoiT International, c'est le moment de nous découvrir. Notre équipe est à votre écoute pour comprendre vos besoins en cloud engineering. Composée exclusivement d'ingénieurs seniors, elle est spécialisée dans le conseil cloud avancé, la conception d'architectures et l'aide au débogage. Contactez-nous, et discutons-en !