Mit Amazon Bedrock generiertes Bild eines Kubernetes Starter Kits
Ein Large Language Model (LLM) in eine Anwendung einzubetten, kann diese deutlich leistungsfähiger und einfacher in der Nutzung machen. Das so umzusetzen, dass es sich sauber in eine bestehende Codebasis einfügt und dabei skalierbar bleibt, ist allerdings nicht trivial.
Dieser Artikel stellt ein Starter-Projekt vor, mit dem Sie schnell loslegen können.
Warum ein Starter Kit auf Basis von Go?
Die meisten Generative-AI-Beispiele, die einem begegnen, laufen in Python – das ergibt Sinn, denn Python ist die Lingua franca von Machine Learning und Data Science. Was aber, wenn Sie eine bestehende Anwendung lediglich über gängige API-Aufrufe um Machine-Learning-Modelle erweitern möchten und weder eigene Modelle bauen noch andere dedizierte Machine-Learning-Aufgaben umsetzen?
Go eignet sich hervorragend, um Web Services anzusprechen – darunter auch Machine-Learning-APIs wie Amazon Bedrock oder ChatGPT.
Das in diesem Artikel vorgestellte Starter Kit konzentriert sich auf Amazon Bedrock und ist hier zu finden:
https://github.com/p-obrien/bedrock-microservice-starter
Voraussetzung — Amazon-Bedrock-Modelle aktivieren
Um Amazon Bedrock nutzen zu können, müssen Sie die entsprechenden Foundational Models aktivieren. Das ist derzeit (Stand Juli 2024) leider nur über die AWS Console möglich.
Gehen Sie zur Aktivierung wie folgt vor:
1. Melden Sie sich in Ihrer AWS Console an und navigieren Sie zu Amazon Bedrock.
2. Klappen Sie die Navigationsleiste auf der linken Seite auf und scrollen Sie zu "Model access".
3. Suchen Sie im Dialog "Find model" nach dem gewünschten Modell – im Starter-Projekt verwenden wir "Claude 3 Sonnet". Falls es für Ihr Projekt noch nicht aktiviert ist, müssen Sie Zugriff anfordern. Klicken Sie dazu auf "request model access", wählen Sie die zu aktivierenden Modelle aus und folgen Sie den weiteren Schritten.
Nach der Aktivierung sollte es so aussehen:

AWS Bedrock Base Models
Hinweis: Die Modellverfügbarkeit unterscheidet sich je nach Region. Unter Umständen müssen Sie das Modell in einer anderen Region betreiben als Ihre üblichen workloads.
**Überblick über das Starter Kit**
Das Starter Kit ist als Referenz für Ihre eigene Lösung gedacht und übernimmt Folgendes:
- Stellt einen Graviton-basierten EKS-Cluster mit einer Managed Node Group bereit
- Stellt den AWS Load Balancer Controller bereit
- Stellt EKS Pod Identity und Service Account bereit
Der Go-Code stellt einen Microservice bereit, der:
- Aufrufer per API-Key authentifiziert
- EKS Pod Identity zur Kommunikation mit Amazon Bedrock nutzt
- Eine Konversation mit Amazon Bedrock zurückgibt – konkret mit dem Foundational Model Claude Sonnet, was sich aber leicht anpassen lässt
Wenn Sie bereits einen EKS-Cluster betreiben, können Sie den Code dort ausführen. Stellen Sie aber sicher, dass der Pod Identities Agent und der Service Account vorhanden sind.
**Infrastruktur**
Das Starter Kit nutzt Terraform oder OpenTofu, um einen frischen AWS-EKS-Cluster bereitzustellen. Details zur Verwendung finden Sie in der README.md im Infrastructure-Ordner.
Statt Access Keys im Container, Secrets oder anderen Authentifizierungsmethoden setzen wir auf ein AWS-EKS-Feature namens Pod Identities, um Zugriff auf Bedrock zu gewähren.
Bis vor Kurzem kam dafür EKS IRSA (IAM Roles for Service Accounts) zum Einsatz – das war jedoch ziemlich aufwendig einzurichten und brachte einige Einschränkungen mit sich. EKS Pod Identities umgeht diese Einschränkungen und bietet eine deutlich einfachere Lösung. Wer es genauer wissen will, findet die Details HIER.
EKS Pod Identity
Schritt 1 — EKS Pod Identity Add-on bereitstellen

Schritt 2 — Eine Rolle definieren und dem Pod Identity Add-on erlauben, sie zu übernehmen

Schritt 3 — Einen Kubernetes Service Account mit der Rolle verknüpfen

Schritt 4 — Sicherstellen, dass EKS einen passenden Service Account besitzt

Code
Die Kommunikation des Microservice mit Amazon Bedrock läuft über die Amazon Bedrock Go API.
Hinweis: Es sind weitaus komplexere Interaktionen möglich, als in diesem Starter-Projekt gezeigt werden.
Authentifizierung – ganz einfach
Der Vorteil von EKS Pod Identities: Sie sind für Ihre Anwendung weitgehend transparent. Der folgende Code nutzt etwa die Go Bedrock API, um einen Client anzulegen und sich per EKS Pod Identities zu authentifizieren:
func init() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("unable to load SDK config, %v", err)
}
brc = bedrockruntime.NewFromConfig(cfg)
}
Um das Laden eines Secrets müssen Sie sich nicht kümmern: Im Pod sind die Umgebungsvariablen für das AWS-Profil automatisch gesetzt. Sie können den Bedrock-Client also einfach mit den Defaults initialisieren.
REST-Server
Wir verwenden das Echo Framework, um einen authentifizierten REST-Endpunkt für eingehende Requests bereitzustellen. Im Starter-Projekt nutzen wir einen einfachen 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)
}
}
Anschließend nehmen wir den Prompt aus dem Multipart-Form-Input entgegen, reichen ihn an Bedrock weiter und geben die Antwort an den Client zurück.
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)
}
Erweiterungsmöglichkeiten
Folgende Verbesserungen überlassen wir Ihnen als Übung:
- Streaming Responses — Das Echo Framework unterstützt Streaming Responses, und es wäre praktisch, auch von Bedrock eine Streaming-Antwort zu erhalten
- System Prompt — Ein System Prompt, der Amazon Bedrock vor der Verarbeitung der Client-Interaktionen mit einer bestimmten Vorgabe initialisiert
Abschließende Gedanken …
Vielen Dank fürs Lesen. Bei Fragen oder Feedback öffnen Sie gerne ein Issue in meinem Github-Repository.
Falls Sie DoiT International noch nicht kennen, sollten Sie uns unbedingt einen Besuch abstatten. Unser Team möchte mehr über Sie und Ihre Anforderungen im Cloud Engineering erfahren. Wir setzen ausschließlich auf Senior Engineers und sind auf anspruchsvolle Cloud-Beratung, Architekturdesign und Debugging spezialisiert. Kontaktieren Sie uns – sprechen wir miteinander!