AI-Agents mit Step Functions und Claude ohne Lambda orchestrieren
Step Functions kann Claude direkt ueber die Bedrock-SDK-Integration aufrufen, sodass mehrstufige AI-Workflows als eine visuelle State Machine laufen. Das reduziert Komplexitaet und Latenz gegenueber zwischengeschaltetem Lambda.

Die meisten Entwickler greifen immer dann zu einer Lambda-Funktion, wenn sie eine LLM-Antwort benötigen, was unnötige Komplexität und Latenz mit sich bringt. Step Functions kann Claude über die Bedrock SDK Integration direkt aufrufen und so einen mehrstufigen KI-Workflow in eine einzige, visuelle State Machine verwandeln. In dieser Anleitung sehen Sie, wie Sie diesen Workflow mit Node.js 22 erstellen, ausführen und debuggen.
Wenn Sie ein LLM (Large Language Model) damit beauftragen, etwas zu erledigen, ist die Anfrage nur der erste Schritt. Echte Agenten müssen häufig
Eine naive Lösung wäre, eine Lambda-Funktion zu schreiben, die all diese Arbeit übernimmt. Jeder Lambda-Aufruf verursacht eine Cold-Start-Verzögerung, eine eigene IAM-Rolle und einen weiteren Codeabschnitt, den Sie pflegen müssen.
Step Functions ist ein verwalteter Dienst, der eine State Machine ausführt: ein Diagramm aus Schritten (States), die Daten nacheinander weiterreichen. Der Dienst übernimmt selbst Retry-Mechanismen, Parallelität und visuelles Debugging. Da Step Functions direkt mit anderen AWS-Services kommunizieren kann (sogenannte Service Integration), können Sie Claude, das auf Bedrock gehostete Anthropic-Modell, dazu auffordern, einen Prompt zu beantworten, ohne auch nur eine einzige Lambda-Funktion zu schreiben.
Einfach ausgedrückt: Betrachten Sie Step Functions als Dirigenten, der jedem Musiker (Service) sagt, wann er spielen soll, statt eines Solisten (Lambda), der versucht, das ganze Orchester allein zu spielen.
Nachfolgend sehen Sie die kleinstmögliche State Machine, die einen einzelnen Prompt an Claude sendet und die Antwort zurückgibt. Die Definition ist in Amazon States Language (ASL) geschrieben, einem JSON-Dialekt, der jeden State beschreibt.
{ "Comment": "A one‑step workflow that calls Claude via Bedrock.", "StartAt": "AskClaude", "States": { "AskClaude": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:bedrockruntime:invokeModel", "Parameters": { "ModelId": "anthropic.claude-v2", "ContentType": "application/json", "Accept": "application/json", "Body.$": "$.prompt" }, "End": true } } } Task – ein Schritt, der Arbeit ausführt. Resource – der ARN, der Step Functions anweist, die Operation InvokeModel des Bedrock SDK aufzurufen. $.prompt – ein JSONPath, der das Feld prompt aus dem State-Input zieht. Wenn Sie diese Ausführung mit {"prompt":"{\"prompt\":\"Hello, Claude!\"}"} starten, übergibt die State Machine das JSON an Bedrock, wartet auf die Antwort und endet mit der Ausgabe des Modells.
Ohne jede Lambda-Funktion.
Bevor Sie den Workflow ausführen können, benötigen Sie drei Dinge:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "bedrock-runtime:InvokeModel", "s3:PutObject" ], "Resource": "*" } ] } Hängen Sie diese Policy an eine Rolle namens StepFunctionsClaudeRole an und weisen Sie die Rolle beim Erstellen der State Machine zu.
Die folgende Definition macht drei Dinge:
{ "Comment": "Parallel Claude calls with result stored in S3.", "StartAt": "ProcessPrompts", "TimeoutSeconds": 300, "Roles": "arn:aws:iam::123456789012:role/StepFunctionsClaudeRole", "States": { "ProcessPrompts": { "Type": "Map", "ItemsPath": "$.prompts", "MaxConcurrency": 5, "Iterator": { "StartAt": "AskClaude", "States": { "AskClaude": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:bedrockruntime:invokeModel", "Parameters": { "ModelId": "anthropic.claude-v2", "ContentType": "application/json", "Accept": "application/json", "Body.$": "$" }, "ResultPath": "$.response", "End": true } } }, "ResultPath": "$.responses", "Next": "SummarizeAndStore" }, "SummarizeAndStore": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:putObject", "Parameters": { "Bucket": "my-claude-output", "Key": "summary.json", "Body.$": "States.JsonToString($.responses)" }, "End": true } } } Map – führt denselben Sub-Workflow für jedes Element eines Arrays aus. MaxConcurrency – begrenzt, wie viele untergeordnete Ausführungen gleichzeitig laufen dürfen (das Service-Limit liegt bei 10 000). ResultPath – teilt Step Functions mit, wo die Ausgabe des untergeordneten Schritts gespeichert wird.
Tipp: Die intrinsic function States.JsonToString wandelt ein JSON-Objekt in einen String um, den S3 als Body erwartet.
Wenn Sie das Präfix arn:aws:states:::aws-sdk:… verwenden, wird Step Functions zu einem dünnen Client für das zugrunde liegende SDK. Kein zusätzlicher Code, kein Packaging, nur deklarative Verdrahtung. Das reduziert den Aufwand beim Deployment und hält die Latenz niedrig, weil der Aufruf innerhalb der AWS Control Plane stattfindet.
Stellen Sie sich vor, Sie haben 100 Kundentickets, für die jeweils eine kurze Zusammenfassung von Claude benötigt wird. Sie nacheinander zu verarbeiten würde Minuten dauern. Ein Distributed Map funktioniert wie eine Küchenlinie, auf der mehrere Köche (Tasks) gleichzeitig Gerichte (Antworten) zubereiten.
// Diese Datei dient nur der Illustration; das JSON oben ist die echte Definition. import { SFNClient, CreateStateMachineCommand } from " @aws-sdk/client-sfn "; const client = new SFNClient ({ region: " us-east-1 " }); const definition = { // (JSON aus dem vorherigen Abschnitt kopieren) }; await client. send ( new CreateStateMachineCommand ({ name: " ClaudeParallelDemo ", definition: JSON. stringify ( definition ), roleArn: " arn:aws:iam::123456789012:role/StepFunctionsClaudeRole ", type: " STANDARD " // STANDARD bietet das Limit von 25.000 Events im Verlauf }) ); Der CreateStateMachineCommand registriert den Workflow bei Step Functions. Der Wert type: "STANDARD" weist den Dienst an, einen vollständigen Ausführungsverlauf zu speichern (nützlich für das Debugging, denken Sie aber an die Obergrenze von 25 000 Events). Wenn Sie diese Machine mit folgendem Input starten:
{ "prompts": [ "{ \" prompt \": \" Summarize the first ticket. \" }", "{ \" prompt \": \" Summarize the second ticket. \" }", "{ \" prompt \": \" Summarize the third ticket. \" }" ] } spinnt Step Functions drei parallele AskClaude-Tasks hoch, die jeweils ihr eigenes Prompt-JSON erhalten. Die Ergebnisse werden unter $.responses gesammelt und später in S3 geschrieben.
Einfach ausgedrückt: Ein Map-State ist wie ein Sortierer im Postzimmer, der jeden Umschlag an einen eigenen Angestellten weitergibt; die Angestellten arbeiten gleichzeitig, und der Sortierer sammelt später alle abgestempelten Umschläge wieder ein.
// run-clair-workflow.ts import { SFNClient, StartExecutionCommand, DescribeExecutionCommand, GetExecutionHistoryCommand } from " @aws-sdk/client-sfn "; // 1️⃣ Erstelle einen Client, der in deiner Region mit Step Functions kommuniziert. const sfn = new SFNClient ({ region: " us-east-1 " }); // 2️⃣ Baue das Input-Payload – ein Array von Prompt-Objekten. const executionInput = { prompts: [ JSON. stringify ({ prompt: " Explain the difference between TCP and UDP. " }), JSON. stringify ({ prompt: " Give a short recipe for banana bread. " }), JSON. stringify ({ prompt: " Summarize the plot of *The Little Prince*.
" }) ] }; // 3️⃣ Starte die Ausführung. Der ARN unten ist der ARN der zuvor erstellten State Machine. async function start () { const startCmd = new StartExecutionCommand ({ stateMachineArn: " arn:aws:states:us-east-1:123456789012:stateMachine:ClaudeParallelDemo ", input: JSON. stringify ( executionInput ) // Step Functions erwartet einen JSON-String }); const startResp = await sfn. send ( startCmd ); console. log ( " Execution started: ", startResp. executionArn ); return startResp. executionArn!; } // 4️⃣ Pollen, bis die Ausführung abgeschlossen ist (einfaches exponentielles Back-off). async function waitForCompletion ( executionArn: string ) { const describeCmd = new DescribeExecutionCommand ({ executionArn }); while ( true ) { const { status, output } = await sfn. send ( describeCmd ); if ( status === " SUCCEEDED " ) { console. log ( " ✅ Finished! " ); console. log ( " Final output (S3 key is stored there): ", output ); break; } else if ( status === " FAILED " || status === " TIMED_OUT " || status === " ABORTED " ) { console. error ( `❌ Execution ended with status: ${ status } ` ); break; } // Noch nicht fertig – kurz warten. await new Promise ( r => setTimeout ( r, 2000 )); } } // 5️⃣ Führe die Hilfsfunktionen aus. ( async () => { const arn = await start (); await waitForCompletion ( arn ); })(); Erklärung der einzelnen Zeilen
Kernerkenntnis: Der Node.js-Code kommuniziert nur mit Step Functions; die gesamte eigentliche Arbeit (Prompt zu Claude zu S3) findet innerhalb der State Machine selbst statt.
Wenn Sie an das Limit von 25 000 Events stoßen, können Sie das rohe Event-Log abrufen:
const historyCmd = new GetExecutionHistoryCommand ({ executionArn, maxResults: 1000, // bleibt unter dem Service-Limit pro Aufruf reverseOrder: false }); const { events } = await sfn. send ( historyCmd ); console. log ( " Number of events: ", events?. length ); Der Verlauf ist hilfreich, um herauszufinden, wo ein untergeordneter Task fehlgeschlagen ist, oder um die Latenz pro Schritt zu messen.
Step Functions bietet Ihnen eine visuelle Konsole, doch einige versteckte Grenzen können neue Nutzerninnen und Nutzer zur Verzweiflung bringen.
Eine Standard-State Machine speichert bis zu 25 000 Events pro Ausführung. Jede Map-Iteration, jeder Retry und jeder Service-Integration-Aufruf erzeugt zusätzliche Events. Wenn Sie tausende Prompts in einer einzigen Ausführung verarbeiten, können Sie diese Obergrenze überschreiten, und die Konsole schneidet die Ansicht ab.
Lösung: Teilen Sie einen großen Batch in kleinere Ausführungen auf, oder verwenden Sie einen Express-Workflow, der Events stattdessen zu CloudWatch Logs streamt.
Jeder Status empfängt eine Eingabe und gibt eine Ausgabe aus, die in 256 KB passen muss. Claude kann Antworten erzeugen, die größer sind als dieser Wert, insbesondere wenn man lange Essays anfordert. Übersteigt eine Antwort dieses Limit, schlägt der Task mit einem PayloadTooLarge-Fehler fehl.
Workaround: Füge nach dem AskClaude-Task einen Pass-Status hinzu, der die Roherentwort in einen S3-Bucket kopiert, und ersetze anschließend die Statusausgabe durch eine winzige Referenz (den S3-Key). Beispiel:
"StoreLargeOutput": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:putObject", "Parameters": { "Bucket": "my-claude-output", "Key.$": "States.Format('large/{}.json', $.response.id)", "Body.$": "$.response.body" }, "ResultPath": "$.s3Location", "Next": "TrimOutput" }, "TrimOutput": { "Type": "Pass", "Result": { "s3Key.$": "$.s3Location.Key" }, "End": true } Enter fullscreen mode Exit fullscreen mode Jetzt sehen nachgelagerte Status nur noch den S3-Key (ein paar Bytes) und bleiben damit unter dem Limit.
Wenn du irgendwo einen Lambda-Task in den Workflow einmischt, bedenke, dass der Lambda Cold Start zur gesamten Latenz beiträgt. Indem du alles als Service-Integration-Tasks umsetzt, vermeidest du diese zusätzliche Wartezeit.
Ein einzelnes Map kann bis zu 10.000 untergeordnete Ausführungen gleichzeitig starten. Wenn du MaxConcurrency zu hoch setzt, drosselt dich der Service. Beginne mit einer bescheidenen Zahl (z. B. 5 bis 20) und beobachte die CloudWatch-Metriken ( StepFunctions/MapStateConcurrency ).
Tipp: Wenn du in der Konsole die Meldung „Execution history truncated“ siehst, ist das ein Signal, dass du die Grenze von 25.000 Events überschritten hast und den Job aufteilen solltest.
Mit diesen Bausteinen kannst du AI Agents bauen, die skalieren, beobachtbar bleiben und den Aufwand der Verwaltung von Lambda-Funktionen vermeiden. Viel Erfolg beim Orchestrieren!
Dieser Artikel wurde mit Unterstützung eines KI-Systems verfasst: Groq (GPT OSS 120B).
Veröffentlicht: 2026-10-22 · Primärer Fokus: StepFunctions
Alle Codeblöcke sind als korrekt und lauffähig gedacht, bitte verifiziere sie jedoch vor dem Einsatz in der Produktionsumgebung anhand der offiziellen Dokumentation der genannten Tools.
Fehler gefunden? Hinterlasse einen Kommentar. Korrekturen sind jederzeit willkommen.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Most developers reach for a Lambda function every time they need an LLM response, adding unnecessary complexity and latency. Step Functions can call Claude directly via the Bedrock SDK integration, turning a multi‑step AI workflow into a single, visual state machine. In this guide you’ll see how to build, run, and debug that workflow from Node.js 22.
When you ask an LLM (large language model) to do something, the request is just the first step. Real‑world agents often need to
A naïve solution is to write a Lambda function that does all of this work. Each Lambda invocation adds a cold‑start delay, a separate IAM role, and a piece of code that you have to maintain.
Step Functions is a managed service that runs a state machine: a diagram of steps (states) that pass data from one to the next. The service itself takes care of retries, parallelism, and visual debugging. Because Step Functions can talk directly to other AWS services (called service integration ), you can ask Claude (the Anthropic model hosted on Bedrock ) to answer a prompt without ever writing a Lambda.
In plain English: Think of Step Functions as a conductor that tells each musician (service) when to play, instead of a soloist (Lambda) trying to play the whole orchestra alone.
Below is the tiniest possible state machine that sends a single prompt to Claude and returns the answer. The definition is written in Amazon States Language (ASL), a JSON dialect that describes each state.
{ "Comment": "A one‑step workflow that calls Claude via Bedrock.", "StartAt": "AskClaude", "States": { "AskClaude": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:bedrockruntime:invokeModel", "Parameters": { "ModelId": "anthropic.claude-v2", "ContentType": "application/json", "Accept": "application/json", "Body.$": "$.prompt" }, "End": true } } } Enter fullscreen mode Exit fullscreen mode Task – a step that performs work. Resource – the ARN that tells Step Functions to call the InvokeModel operation of the Bedrock SDK. $.prompt – a JSONPath that pulls the prompt field from the state input. When you start this execution with {"prompt":"{\"prompt\":\"Hello, Claude!\"}"}, the state machine hands the JSON to Bedrock, waits for the response, and finishes with the model’s output.
No Lambda involved.
Before you can run the workflow you need three things:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "bedrock-runtime:InvokeModel", "s3:PutObject" ], "Resource": "*" } ] } Enter fullscreen mode Exit fullscreen mode Attach the policy to a role named StepFunctionsClaudeRole and give the role to the state machine when you create it.
The following definition does three things:
{ "Comment": "Parallel Claude calls with result stored in S3.", "StartAt": "ProcessPrompts", "TimeoutSeconds": 300, "Roles": "arn:aws:iam::123456789012:role/StepFunctionsClaudeRole", "States": { "ProcessPrompts": { "Type": "Map", "ItemsPath": "$.prompts", "MaxConcurrency": 5, "Iterator": { "StartAt": "AskClaude", "States": { "AskClaude": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:bedrockruntime:invokeModel", "Parameters": { "ModelId": "anthropic.claude-v2", "ContentType": "application/json", "Accept": "application/json", "Body.$": "$" }, "ResultPath": "$.response", "End": true } } }, "ResultPath": "$.responses", "Next": "SummarizeAndStore" }, "SummarizeAndStore": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:putObject", "Parameters": { "Bucket": "my-claude-output", "Key": "summary.json", "Body.$": "States.JsonToString($.responses)" }, "End": true } } } Enter fullscreen mode Exit fullscreen mode Map – runs the same sub‑workflow for each element of an array. MaxConcurrency – caps how many child executions can run at once (the service limit is 10 000). ResultPath – tells Step Functions where to store the output of the child.
Tip: The States.JsonToString intrinsic function converts a JSON object to a string that S3 expects as the body.
When you use the arn:aws:states:::aws-sdk:… prefix, Step Functions becomes a thin client for the underlying SDK. No extra code, no packaging, just declarative wiring. This reduces deployment friction and keeps the latency low because the call happens inside the AWS control plane.
Imagine you have 100 customer tickets that each need a short summary from Claude. Processing them one after another would take minutes. A Distributed Map works like a kitchen line where multiple chefs (tasks) prepare dishes (responses) simultaneously.
// This file is only for illustration; the JSON above is the real definition. import { SFNClient, CreateStateMachineCommand } from " @aws-sdk/client-sfn "; const client = new SFNClient ({ region: " us-east-1 " }); const definition = { // (copy the JSON from the previous section) }; await client. send ( new CreateStateMachineCommand ({ name: " ClaudeParallelDemo ", definition: JSON. stringify ( definition ), roleArn: " arn:aws:iam::123456789012:role/StepFunctionsClaudeRole ", type: " STANDARD " // STANDARD gives us the 25,000‑event history limit }) ); Enter fullscreen mode Exit fullscreen mode The CreateStateMachineCommand registers the workflow with Step Functions. The type: "STANDARD" tells the service to keep a full execution history (useful for debugging, but remember the 25 000‑event ceiling). When you start this machine with:
{ "prompts": [ "{ \" prompt \": \" Summarize the first ticket. \" }", "{ \" prompt \": \" Summarize the second ticket. \" }", "{ \" prompt \": \" Summarize the third ticket. \" }" ] } Enter fullscreen mode Exit fullscreen mode Step Functions spins up three parallel AskClaude tasks, each receiving its own prompt JSON. The results are collected under $.responses and later written to S3.
In plain English: A Map state is like a mail‑room sorter that hands each envelope to a separate clerk; the clerks work at the same time, and the sorter later gathers all the stamped envelopes back together.
// run-clair-workflow.ts import { SFNClient, StartExecutionCommand, DescribeExecutionCommand, GetExecutionHistoryCommand } from " @aws-sdk/client-sfn "; // 1️⃣ Create a client that talks to Step Functions in your region. const sfn = new SFNClient ({ region: " us-east-1 " }); // 2️⃣ Build the input payload – an array of prompt objects. const executionInput = { prompts: [ JSON. stringify ({ prompt: " Explain the difference between TCP and UDP. " }), JSON. stringify ({ prompt: " Give a short recipe for banana bread. " }), JSON. stringify ({ prompt: " Summarize the plot of *The Little Prince*.
" }) ] }; // 3️⃣ Start the execution. The ARN below is the ARN of the state machine you created earlier. async function start () { const startCmd = new StartExecutionCommand ({ stateMachineArn: " arn:aws:states:us-east-1:123456789012:stateMachine:ClaudeParallelDemo ", input: JSON. stringify ( executionInput ) // Step Functions expects a JSON string }); const startResp = await sfn. send ( startCmd ); console. log ( " Execution started: ", startResp. executionArn ); return startResp. executionArn!; } // 4️⃣ Poll until the execution finishes (simple exponential back‑off). async function waitForCompletion ( executionArn: string ) { const describeCmd = new DescribeExecutionCommand ({ executionArn }); while ( true ) { const { status, output } = await sfn. send ( describeCmd ); if ( status === " SUCCEEDED " ) { console. log ( " ✅ Finished! " ); console. log ( " Final output (S3 key is stored there): ", output ); break; } else if ( status === " FAILED " || status === " TIMED_OUT " || status === " ABORTED " ) { console. error ( `❌ Execution ended with status: ${ status } ` ); break; } // Not finished yet – wait a bit. await new Promise ( r => setTimeout ( r, 2000 )); } } // 5️⃣ Run the helper functions. ( async () => { const arn = await start (); await waitForCompletion ( arn ); })(); Enter fullscreen mode Exit fullscreen mode Explanation of each line
Key takeaway: The Node.js code only talks to Step Functions; every heavy‑lifting (prompt → Claude → S3) happens inside the state machine itself.
If you hit the 25 000‑event limit, you can fetch the raw event log:
const historyCmd = new GetExecutionHistoryCommand ({ executionArn, maxResults: 1000, // keep under the service limit per call reverseOrder: false }); const { events } = await sfn. send ( historyCmd ); console. log ( " Number of events: ", events?. length ); Enter fullscreen mode Exit fullscreen mode The history is useful for spotting where a child task failed or for measuring latency per step.
Step Functions gives you a visual console, but a few hidden limits can trip new users.
A standard state machine stores up to 25 000 events per execution. Each Map iteration, each retry, and each service‑integration call adds events. If you process thousands of prompts in a single execution, you may exceed the ceiling and the console will truncate the view.
Solution: Break a huge batch into smaller executions, or use an Express workflow (which streams events to CloudWatch Logs instead of storing them).
Every state receives an input and emits an output that must fit into 256 KB. Claude can generate responses larger than this, especially when you ask for long essays. If a response exceeds the limit, the task fails with a PayloadTooLarge error.
Work‑around: After the AskClaude task, add a Pass state that copies the raw response to an S3 bucket, then replace the state output with a tiny reference (the S3 key). Example:
"StoreLargeOutput": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:putObject", "Parameters": { "Bucket": "my-claude-output", "Key.$": "States.Format('large/{}.json', $.response.id)", "Body.$": "$.response.body" }, "ResultPath": "$.s3Location", "Next": "TrimOutput" }, "TrimOutput": { "Type": "Pass", "Result": { "s3Key.$": "$.s3Location.Key" }, "End": true } Enter fullscreen mode Exit fullscreen mode Now downstream states only see the S3 key (a few bytes), staying under the limit.
If you ever mix a Lambda task into the workflow, remember that the Lambda cold start adds to the overall latency. By keeping everything as service integration tasks, you avoid that extra waiting time.
A single Map can launch up to 10 000 child executions concurrently. If you set MaxConcurrency too high, the service throttles you. Start with a modest number (e.g., 5‑20) and monitor CloudWatch metrics ( StepFunctions/MapStateConcurrency ).
Tip: When you see “Execution history truncated” in the console, it’s a signal that you’ve crossed the 25 000‑event line and should split the job.
With these pieces in place, you can build AI agents that scale, stay observable, and avoid the overhead of managing Lambda functions. Happy orchestrating!
This article was written with the help of an AI system — Groq (GPT OSS 120B).
Published: 2026-10-22 · Primary focus: StepFunctions
All code blocks are intended to be correct and runnable, but please verify them against the official docs for the tools mentioned before using in production.
Find an error? Drop a comment — corrections are always welcome.