Senior AWS Consultant
Was ist ein KI-Agent?
Ein KI-Agent ist ein Programm, bei dem ein LLM (Large Language Model) nicht nur Fragen beantwortet, sondern auch Aktionen ausführt. Statt direkt eine Antwort zu generieren, kann ein Agent beschließen, vorher externe Funktionen aufzurufen – eine Datenbank abzufragen, sich eine Datei zu holen, eine API zu nutzen –, und dann anhand der Ergebnisse seine Antwort formulieren.
Diese Schleife sieht grundsätzlich wie folgt aus:
- Der Benutzer stellt eine Frage.
- Das LLM liest die Frage und die Liste der verfügbaren Tools.
- Das LLM entscheidet: Kann ich direkt antworten oder muss ich erst ein Tool aufrufen?
- Wenn es ein Tool aufruft, erhält es den Output des Tools und „überlegt“ weiter.
- Die Schritte 3 und 4 werden wiederholt, bis das LLM genügend Informationen hat, um antworten zu können.
- Das LLM gibt die endgültige Antwort aus.
Diese Schleife aus Überlegen → Handeln → Lesen → Wiederholen unterscheidet einen Agenten von einem einfachen Chatbot. Das LLM wird zum Entscheider, der selbstständig Toolaufrufe orchestriert.
AgentCore vs. Bedrock Agents: Warum ein neuer Dienst?
AWS legt die Bedrock Agents still. Das war bereits ein Fully-Managed-Agentendienst, in dem man über die Konsole Tools konfiguriert und Aktionsgruppen hinzugefügt und AWS die Orchestrierung übernommen hat. Warum wird jetzt also ein neuer Dienst eingeführt? Bedrock Agents funktioniert für Standardanwendungen gut, aber die Orchestrierungslogik ist eine Blackbox. Man hat keinen Einfluss darauf, wie der Agent seine Schleifen ausführt, welches Framework er verwendet und wie Tools zugewiesen werden.
Bedrock AgentCore ist die Infrastrukturschicht, die unter dem Ganzen liegt. Damit haben Sie:
- Ihren eigenen Container – Sie können jedes beliebige Agenten-Framework nutzen (Strands, LangChain, CrewAI oder eigener Code)
- Volle Kontrolle über die Orchestrierung – individuelle Hooks, Iterationslimits, Short-Circuiting-Logik usw.
- MCP-Protokoll für Tools – das offene Model Context Protocol statt eines proprietären Formats
- Managed Compute – Sie betreiben keine eigenen Server, AWS skaliert die Container für Sie
|
|
Bedrock Agents |
Bedrock AgentCore |
|
Orchestrierung |
von AWS festgelegt |
Ihr Code, beliebiges Framework |
|
Tool-Protokoll |
Aktionsgruppen (OpenAPI) |
MCP (offener Standard) |
|
Bereitstellung |
Nur Konfiguration (kein Container) |
Ihr Docker-Image |
|
Anpassung |
Einstellungen in der Konsole |
Unbegrenzt – Sie kontrollieren den Code |
Die AgentCore-Komponenten
AgentCore wirkt auf den ersten Blick kompliziert, aber um einen Agenten aufzusetzen und zu starten, müssen Sie erst einmal nur die drei Kernkomponenten kennen und verstehen, was sie tun (und was nicht):
- Runtime
Die Runtime ist Ihr Docker-Container, der auf AWS-Rechenressourcen läuft. Hier liegt Ihre Agentenlogik – der Python-Code, die LLM-Aufrufe, die Orchestrierungsschleife.
Sie können sich das als Fargate-Task vorstellen, die AWS für Sie verwaltet: Sie zieht Ihr Image aus der ECR, startet den Container und leitet eingehende Aufrufe dort hin. Sie müssen sich nicht um Instanzen, Skalierung oder Networking zu kümmern (es sei denn, Sie wählen für private Ressourcen den VPC-Modus).
Was die Komponente tut
- Führt Ihren Agentencode aus (alle Sprachen, alle Frameworks)
- Empfängt Aufrufe über die API invoke-agent-runtime (IAM-authentifiziert)
- Hat eine IAM-Rolle, mit dem sie Bedrock (für das LLM) und das Gateway (für die Tools) aufrufen kann
Was die Komponente nicht tut
- Entscheidet nicht, welche Tools aufgerufen werden – das macht das LLM
- Leitet keine Toolaufrufe weiter – das macht das Gateway
- Führt keine Tools aus – das macht das Tool-Backend (Lambda, eine API oder ein anderer Dienst)
Wichtige Konfiguration:
- server_protocol = „HTTP“ – Ihr Container ist ein HTTP-Server, kein MCP-Server
- network_mode = „PUBLIC“ – AWS regelt den ausgehenden Internet-Traffic; „VPC“ nur verwenden, wenn Sie privaten Netzwerkzugriff brauchen
- Container-Architektur muss ARM64 sein
- Gateway
Das Gateway ist ein MCP-Endpunkt (Model Context Protocol), der Toolaufrufe von Ihrer Runtime zu den Backends weiterleitet, die sie ausführen. Das Gateway ist der Router zwischen „das LLM hat entschieden, ein Tool aufzurufen“ und „das Tool wird tatsächlich ausgeführt“.
Wenn der MCP-Client Ihres Agenten einen Toolaufruf sendet, passiert im Gateway Folgendes:
- Es prüft die Anfrage (Authentifizierung mit AWS_IAM SigV4).
- Es sieht nach, welches Ziel (Target) dem Toolnamen entspricht.
- Es ruft das Backend dieses Ziels mit den Parametern des Tools auf.
- Es sendet die Antwort an Ihren Agenten zurück.
Was die Komponente tut
- Agiert als MCP-Endunkt (Tools finden und ausführen)
- Leitet jeden Toolnamen an das richtige Backend weiter (Lambda, externer MCP-Server oder andere AWS-Dienste)
- Authentifiziert jede Anfrage mit IAM
Was die Komponente nicht tut
- Entscheidet nicht, welches Tools aufgerufen wird – das macht das LLM
- Führt keine Tool-Logik aus – das macht das Backend, auf den das Target zeigt
- Kennt die Konversation und den Zustand Ihres Agenten nicht
- Gateway-Targets
Ein Gateway-Target ist die Verknüpfung (Binding) zwischen einem Toolnamen und einem Backend, das dieses Tool ausführt. Für jedes Tool, das Ihr Agent nutzen soll, erstellen Sie ein Target, das besagt: „wenn der MCP-Client das Tool X aufruft, leite ihn an das Backend Y“.
Das Backend muss keine Lambda-Funktion sein, Gateway-Targets unterstützen mehrere Target-Typen. Lambda wird für individuell angepasste Logik am häufigsten verwendet, aber Sie können Targets auch auf andere MCP-kompatible Endpunkte oder AWS-Dienste zeigen lassen. In diesem Tutorial beziehen wir uns auf Lambda, weil das die einfachste Möglichkeit ist, eigenen Code auszuführen, ohne eigene Server zu verwalten. Beachten Sie jedoch, dass das Gateway ein generischer MCP-Router und keine Lambda-Funktion ist.
Jedes Target enthält ein Tool-Schema – mit dem Namen, der Beschreibung und den Eingabeparametern, die dem LLM präsentiert werden. Wenn Ihr Agent list_tools_sync() aufruft, erhält er eine Liste dieser Schemas, und daran erkennt das LLM, welche Tools verfügbar sind und wozu sie dienen.
Prinzip bei Lambda-Targets: ein Lambda, viele Aliase, viele Targets. Sie brauchen nicht für jedes Tool ein separates Lambda, sondern können stattdessen Aliase verwenden (Aliasname = Toolname). Das Dispatching geschieht im Handler.
Anleitung zum Erstellen eines einfachen Agenten
Wir bauen jetzt einen minimalen Agenten:
- Er führt Claude Sonnet in einem Container aus, der von AgentCore verwaltet wird.
- Er hat ein einziges MCP-Tool (GetCakeRecipe), hinter dem eine Lambda-Funktion steht.
- Er wird komplett über terraform apply bereitgestellt.
Wenn Sie den Agenten fragen „Kannst du mir ein Tortenrezept geben?“, ruft er das Tool auf, erhält ein Rezept für Schwarzwälder Kirschtorte und formatiert die Antwort. Wenn Sie irgendetwas anderes fragen, antwortet er auf Basis seines eigenen Wissens, ohne das Tool aufzurufen.
Architektur
invoke-agent-runtime (AWS API, SigV4-signed)
│
▼
AgentCore Runtime (your Docker container)
└─ Strands Agent + Claude Sonnet via Bedrock
│
│ MCP over HTTPS (SigV4-signed automatically)
▼
AgentCore Gateway (AWS_IAM auth, MCP protocol)
│
│ Invokes tool backend
▼
Tool Backend (Lambda function in this example)
└─ Returns static recipe JSON
Der Python-Code im Detail
Tool-Funktion
Das einfachste Tool erhält die Parameter, die das LLM übergibt, und gibt eine JSON-Antwort zurück:
# src/lambda/tool/handler.py
def handler(event, context):
return {
"status": "ok",
"recipe": {
"name": "Oma's Schwarzwälder Kirschtorte",
"servings": 12,
"ingredients": [
"200g dark chocolate", "200g butter", "200g sugar",
"5 eggs", "150g flour", "2 tsp baking powder",
"500ml heavy cream", "3 tbsp kirsch (cherry brandy)",
"1 jar sour cherries (drained)", "chocolate shavings",
],
"steps": [
"Melt chocolate and butter, let cool.",
"Beat eggs and sugar until fluffy, fold in chocolate.",
"Fold in flour + baking powder. Bake 175°C, 25 min.",
"Slice into 3 layers. Whip cream with kirsch.",
"Layer: cake, cream, cherries. Repeat. Decorate.",
],
},
}
Bei einem echten Agenten würde diese Funktion eine Datenbank abfragen, eine API aufrufen oder etwas anderes tun, um die Frage zu beantworten. Wir nutzen hier eine Lambda-Funktion, weil das die einfachste serverlose Option ist, aber Gateway-Targets können auch auf andere Backends zeigen. Das Prinzip bleibt das gleiche: Parameter erhalten, JSON ausgeben.
Agenten-Container (app.py)
Er ist das „Gehirn“. Wir gehen jetzt nacheinander alle Bestandteile durch:
app = BedrockAgentCoreApp()
_agent = None
Die app ist der HTTP-Server, mit dem AgentCore kommuniziert. Der _agent wird global gecacht, damit wir ihn nicht bei jedem Aufruf neu bauen (und nicht jedes Mal die Tool-Liste abfragen) müssen. Die MCP-Verbindung des Agenten bleibt auch zwischen Requests innerhalb des Container-Lebenszyklus bestehen.
def _get_agent():
global _agent
if _agent is not None:
return _agent
mcp_tools = []
if GATEWAY_URL:
mcp = MCPClient(lambda: aws_iam_streamablehttp_client(
endpoint=GATEWAY_URL,
aws_region=AWS_REGION,
aws_service="bedrock-agentcore",
))
mcp.start()
mcp_tools = list(mcp.list_tools_sync())
_agent = Agent(
model=model,
tools=mcp_tools,
system_prompt="You are a helpful assistant. Use the GetCakeRecipe tool when the user asks for a recipe.",
)
return _agent
MCPClient nimmt eine Factory-Funktion (das lambda:), die die Transportverbindung herstellt. Die tatsächliche HTTPS-Verbindung zum Gateway wird allerdings erst hergestellt, wenn mcp.start() aufgerufen wird.
mcp.start() öffnet die Verbindung und erhält sie für die darauffolgenden Aufrufe aufrecht.
mcp.list_tools_sync() fragt das Gateway „Welche Tools hast du?“ und erhält die Tool-Schemas zurück, die Sie in Terraform definiert haben. So erfährt der Agent dynamisch, welche Tools vorhanden sind – wenn Sie in Terraform ein neues Tool hinzufügen, findet der Agent es beim nächsten Start, ohne dass Sie etwas am Code ändern müssen.
Agent(…) verknüpft alles miteinander: das LLM (model), die verfügbaren Aktionen (tools) und die Anweisungen (system_prompt). Der Systemprompt sagt dem LLM, wann es das Tool nutzen soll. Ohne diesen Prompt könnte es Rezeptfragen aus seinen eigenen Trainingsdaten beantworten, statt GetCakeRecipe aufzurufen.
@app.entrypoint
def invoke_agent(payload: dict):
prompt = payload.get("prompt", "")
agent = _get_agent()
result = agent(prompt)
return {"result": str(result)}
@app.entrypoint registriert diese Funktion als Handler für eingehende Aufrufe. Wenn jemand invoke-agent-runtime aufruft, liefert AgentCore hier den Payload.
agent(prompt) – Diese Zeile löst die komplette Agentenschleife aus:
- Der Prompt wird zusammen mit den Tool-Schemas an Claude gesendet.
- Claude entscheidet, ob es GetCakeRecipe aufruft oder direkt antwortet.
- Wenn das Tool aufgerufen wird, sendet der MCP-Client die Anfrage an das Gateway. Das Gateway ruft das Tool-Backend auf, und das Ergebnis kommt zurück.
- Claude sieht das Ergebnis aus dem Tool und formuliert seine Antwort.
- Das result-Objekt enthält den finalen Text.
Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc g++ \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
gcc und g++ werden gebraucht, weil einige Python-Pakete (cryptography, bestimmte boto3-Abhängigkeiten) C-Erweiterungen kompilieren. Das Image muss für ARM64 erstellt sein, AgentCore unterstützt x86 nicht.
Terraform
IAM: drei Rollen, drei Boundaries
# Runtime role — AgentCore runs your container with this identity
resource "aws_iam_role" "runtime" {
assume_role_policy = jsonencode({
Statement = [{ Principal = { Service = "bedrock-agentcore.amazonaws.com" }, ... }]
})
}
# Gateway role — AgentCore uses this to invoke your tool backends
resource "aws_iam_role" "gateway" {
assume_role_policy = jsonencode({
Statement = [{ Principal = { Service = "bedrock-agentcore.amazonaws.com" }, ... }]
})
}
# Lambda role — execution role for our tool function (this example uses Lambda)
resource "aws_iam_role" "lambda" {
assume_role_policy = jsonencode({
Statement = [{ Principal = { Service = "lambda.amazonaws.com" }, ... }]
})
}
Die Runtime-Rolle braucht folgende Berechtigungen:
- bedrock:InvokeModel – Claude aufrufen
- bedrock-agentcore:InvokeGateway – das Gateway aufrufen (die vergessen ALLE)
- ecr:BatchGetImage – Container-Image abrufen
- logs:PutLogEvents – CloudWatch-Logs schreiben
Das braucht die Gateway-Rolle:
- Berechtigungen zum Aufrufen aller Backends, die Ihre Targets nutzen (in diesem Beispiel lambda:InvokeFunction)
Gateway und Tool-Binding
resource "aws_bedrockagentcore_gateway" "main" {
name = "myagent-gateway"
role_arn = aws_iam_role.gateway.arn
authorizer_type = "AWS_IAM"
protocol_type = "MCP"
}
resource "aws_lambda_alias" "get_cake_recipe" {
name = "GetCakeRecipe"
function_name = aws_lambda_function.tool.function_name
function_version = "$LATEST"
}
resource "aws_bedrockagentcore_gateway_target" "get_cake_recipe" {
gateway_identifier = aws_bedrockagentcore_gateway.main.gateway_id
target_configuration {
mcp {
lambda {
lambda_arn = aws_lambda_alias.get_cake_recipe.arn
tool_schema {
inline_payload {
name = "GetCakeRecipe"
description = "Returns a cake recipe. Call this when the user asks about baking."
input_schema { type = "object" }
}
}
}
}
}
}
Die Beschreibung in tool_schema ist das, was das LLM sieht. Sie sollte klar und deutlich sein, denn sie entscheidet darüber, ob Claude Ihr Tool aufruft. Bei einer vagen Beschreibung wird das Tool nie genutzt, bei einer zu weitgefassten wird es auch in unpassenden Fällen aufgerufen.
Runtime
resource "aws_bedrockagentcore_agent_runtime" "main" {
agent_runtime_name = "myagent_runtime"
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.agent.repository_url}:latest"
}
}
role_arn = aws_iam_role.runtime.arn
protocol_configuration { server_protocol = "HTTP" }
network_configuration { network_mode = "PUBLIC" }
environment_variables = {
MODEL_ID = "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
AWS_REGION = "eu-central-1"
GATEWAY_URL = aws_bedrockagentcore_gateway.main.gateway_url
}
}
Die GATEWAY_URL wird aus der Gateway-Ressource berechnet – Terraform verknüpft sie automatisch miteinander.
Bereitstellen und testen
terraform init && terraform apply
Die Terraform enthält eine null_resource, die das ARM64-Docker-Image erstellt und in die ECR pusht, bevor die Runtime erstellt wird. Das ist ein einziger Befehl ohne manuelle Schritte.
Nach apply (Container ist nach 2–3 Minuten gestartet):
aws bedrock-agentcore invoke-agent-runtime \
--agent-runtime-arn "$(terraform output -raw runtime_arn)" \
--payload '{"prompt": "Give me a cake recipe"}' \
--content-type application/json \
/dev/stdout
Fehler, die nicht passieren dürfen
- InvokeGateway-Berechtigung vergessen. Der Agent arbeitet, aber ruft nie Tools auf. Der MCP-Client erhält einen 403-Fehler, der aber in keinen Logs erscheint. Fügen Sie unbedingt bedrock-agentcore:InvokeGateway zur Runtime-Rolle hinzu.
- server_protocol = „MCP“ in der Runtime. Ihr Container spricht HTTP (BedrockAgentCoreApp ist ein HTTP-Server). MCP ist das einzige Protokoll zwischen Runtime und Gateway. Setzen Sie deshalb unbedingt „HTTP“.
- x86-Container-Image. AgentCore läuft nur auf ARM64. Für die Cross-Compilation auf Intel-/AMD-Maschinen brauchen Sie –platform linux/arm64 und QEMU/buildx.
- Image wird nach dem Push nicht aktualisiert. AgentCore cacht das gezogene Image. Ändern Sie eine beliebige Umgebungsvariable, um einen Neustart mit dem aktuellen Image zu erzwingen.
- VPC-Modus ohne ausgehendes Routing. Bei network_mode = „VPC“ müssen die Subnetze Internetzugriff haben (NAT- oder VPC-Endpunkte). Verwenden Sie „PUBLIC“, es sei denn, Sie brauchen tatsächlich privaten Netzwerkzugriff.
Was Sie damit machen können
Das Tortenrezept ist ein Tool, das statische Daten ausgibt. Mit der gleichen Architektur können Sie aber auch Agenten erstellen, die für ihr Reasoning mehrere Tools aufrufen, um komplexe Fragen zu beantworten.
Vorstellbar ist zum Beispiel ein Abrechnungsassistent, den man fragen kann: „Welche meiner Kunden haben unbezahlte Rechnungen aus dem letzten Quartal und wie hoch ist jeweils der offene Gesamtbetrag?“
Das lässt sich nicht mit einer Datenbankabfrage beantworten. Der Agent unterteilt die Aufgabe in mehrere Schritte:
- Zuerst ruft er ein Tool auf, das die offenen Rechnungen aus einem bestimmten Zeitraum auflistet.
- Dann ruft er ein Tool auf, das die offenen Beträge der einzelnen Kunden zusammenrechnet.
- Zum Schluss kombiniert er beide Ergebnisse und gibt eine sortierte Liste mit Gesamtbeträgen aus.
Das LLM beschließt diese Sequenz allein, sie wird nicht von Ihnen hartkodiert. Sie stellen nur die Tools bereit (jeweils eine parametrisierte Abfrage) sowie einen Systemprompt, der beschreibt, wann welches Tool eingesetzt werden soll. Der Agent erkennt, dass zum Beantworten der Benutzerfrage zwei Schritte nötig sind, wählt für jeden Schritt das richtige Tool aus, übergibt die richtigen Parameter (Datumsbereich, Filter für Zahlungsstatus) und fasst die Ergebnisse in einer kohärenten Antwort zusammen.
Wenn Sie das Ganze jetzt auf 10 bis 15 Tools skalieren, die Unterschiedliches aus einer Datenbank abfragen – Kunden, Rechnungen, Zahlungen, Umsatz, Volltextsuche –, kann der Agent fast jede Auswertungsfrage beantworten, die ein Benutzer stellt.
Das ist der Sprung von „ein Tool, eine Antwort“ zu einem echten Produktionsagenten: Das LLM wird zum selbstständigen Abfrageplaner, der mehrere zweckbestimmte, parametrisierte Datenabrufe verkettet und auf diese Weise Fragen beantworten kann, die keine Abfrage allein lösen könnte.