Bedrock Prompt Management and Flows

Two related-but-separate Bedrock products. Prompt Management is the prompt-as-a-resource layer — versioned prompts with template variables, an ARN you reference from your code, and a console UI for non-engineers to iterate on prompts without redeploying the app. Bedrock Flows is the visual workflow builder — a low-code canvas where you wire together prompt nodes, agent nodes, knowledge-base nodes, condition nodes, and Lambda nodes to build multi-step pipelines, then invoke them via InvokeFlow or InvokeFlowStream. Both ship versions + aliases the same way Agents do.

See also

1. Prompt Management — concepts

Per docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html.

A prompt is a versioned resource containing:

  • Messages — array of user/assistant content blocks, or a single text template
  • System prompt — optional, separate field
  • Variables{{name}} placeholders filled in at invocation time
  • Inference configuration — model ID + inference params (temperature, maxTokens, topP, etc.)
  • Tool configuration — optional toolConfig (same shape as Converse)
  • Variants — alternative configurations for A/B testing (different models, different params, different message templates)

Once published, a prompt version is immutable. Aliases point to versions (same pattern as Lambda or Bedrock Agents).

2. Creating a prompt — Python

import boto3
 
ctl = boto3.client("bedrock-agent", region_name="us-east-1")
 
resp = ctl.create_prompt(
    name="summarize-doc",
    description="Summarize a document at a target length and reading level",
    defaultVariant="default",
    variants=[
        {
            "name": "default",
            "modelId": "anthropic.claude-sonnet-4-6-20260119-v1:0",
            "templateType": "TEXT",
            "templateConfiguration": {
                "text": {
                    "text": (
                        "Summarize the following document in {{length}} sentences "
                        "for a {{audience}} audience:\n\n{{document}}"
                    ),
                    "inputVariables": [
                        {"name": "length"},
                        {"name": "audience"},
                        {"name": "document"},
                    ],
                }
            },
            "inferenceConfiguration": {
                "text": {
                    "temperature": 0.3,
                    "topP": 0.9,
                    "maxTokens": 512,
                }
            },
        },
        # Optional second variant for A/B testing
        {
            "name": "haiku-cheap",
            "modelId": "anthropic.claude-haiku-4-5-20251001-v1:0",
            "templateType": "TEXT",
            "templateConfiguration": {
                "text": {
                    "text": "Summarize in {{length}} sentences for {{audience}}: {{document}}",
                    "inputVariables": [...],
                }
            },
            "inferenceConfiguration": {"text": {"temperature": 0.3, "maxTokens": 512}},
        },
    ],
    tags={"team": "search"},
)
prompt_id = resp["id"]
 
# Publish a version
v = ctl.create_prompt_version(promptIdentifier=prompt_id)
version_arn = v["arn"]   # arn:aws:bedrock:us-east-1:...:prompt/PROMPT12345:1

3. Invoking a stored prompt

The prompt ARN goes in modelId; the variables go in promptVariables:

runtime = boto3.client("bedrock-runtime", region_name="us-east-1")
 
resp = runtime.converse(
    modelId=version_arn,   # ARN of the prompt:version
    promptVariables={
        "length": {"text": "3"},
        "audience": {"text": "executive"},
        "document": {"text": "[long doc text here]"},
    },
)
print(resp["output"]["message"]["content"][0]["text"])

Restrictions when using a prompt-management ARN as modelId (per docs):

  • Cannot include system, inferenceConfig, toolConfig, or additionalModelRequestFields — those are baked into the prompt.
  • Can include messages — they’re appended after the prompt’s messages.
  • Can include guardrailConfig — applies to the entire prompt.

For chat-style prompts (with messages baked in), append the user’s new message via messages:

resp = runtime.converse(
    modelId=version_arn,
    promptVariables={"persona": {"text": "tutor"}},
    messages=[{"role": "user", "content": [{"text": "Explain CAP."}]}],
)

4. Variants and A/B testing

When you publish a prompt with multiple variants, you can invoke a specific variant via promptVariant:

# Invoke a specific variant (e.g. haiku-cheap for cost A/B)
resp = runtime.converse(
    modelId=version_arn,
    promptVariables={...},
    additionalModelRequestFields={"promptVariant": "haiku-cheap"},
)

For systematic comparison, use the Prompt Builder in the console — side-by-side variant testing with the same inputs.

5. Optimization — auto-rewrite prompts

Per re:Invent 2024. The OptimizePrompt API takes a draft prompt and returns an optimized version. Bedrock uses a model to rewrite for clarity, structure, and (where relevant) model-specific best practices (XML tags for Claude, JSON output instructions for Nova).

resp = runtime.optimize_prompt(
    targetModelId="anthropic.claude-sonnet-4-6-20260119-v1:0",
    input={
        "textPrompt": {
            "text": "summarize this article for me"
        }
    },
)
 
for event in resp["optimizedPrompt"]:
    if "optimizedPromptEvent" in event:
        print(event["optimizedPromptEvent"]["optimizedPrompt"])

Useful for non-engineers iterating on prompts. The output isn’t always better — verify with eval before committing.

6. Bedrock Flows — visual workflow builder

Per docs.aws.amazon.com/bedrock/latest/userguide/flows.html. A flow is a graph of nodes connected by edges. Each node has typed inputs and outputs that flow into downstream nodes.

Node types

NodePurpose
InputEntry point. Defines the flow’s parameters.
OutputExit point. The flow’s return value.
PromptInline prompt OR reference to a Prompt Management ARN
AgentInvoke a Bedrock Agent alias
Knowledge BaseQuery a KB (returns retrieved chunks or RetrieveAndGenerate result)
Lambda functionInvoke a Lambda
AWS LambdaSame as above (different node UI)
ConditionBranch on JMESPath expressions over node outputs
IteratorLoop over an array, invoking downstream nodes per item
CollectorCollect iterator outputs back into an array
StorageRead/write S3
RetrievalRetrieve from a KB (chunks only, no generation)
Inline Code (Python)Run Python directly (re:Invent 2024) — useful for small transformations

Example: a multi-step flow

[Input: user query]
       ↓
[Knowledge Base: retrieve top-5 chunks]
       ↓
[Condition: did retrieval return results?]
   ↓ yes              ↓ no
[Prompt: synthesize  [Prompt: politely
 answer with        decline + suggest
 citations]         rephrasing]
   ↓                   ↓
       [Output: answer text]

Creating a flow programmatically

Flows are designed to be built in the console (drag-and-drop). The programmatic API exists but is verbose:

flow = ctl.create_flow(
    name="rag-with-fallback",
    description="RAG with empty-result fallback",
    executionRoleArn="arn:aws:iam::...:role/AmazonBedrockExecutionRoleForFlows",
    definition={
        "nodes": [
            {
                "name": "Input",
                "type": "Input",
                "outputs": [{"name": "document", "type": "String"}],
            },
            {
                "name": "Retrieval",
                "type": "Retrieval",
                "configuration": {
                    "retrieval": {
                        "serviceConfiguration": {
                            "s3": {
                                "bucketName": "my-kb-input"
                            }
                        }
                    }
                },
                "inputs": [{"name": "objectKey", "type": "String", "expression": "$.data"}],
                "outputs": [{"name": "s3Object", "type": "Object"}],
            },
            # ... more nodes
        ],
        "connections": [
            {
                "name": "InputToRetrieval",
                "source": "Input",
                "target": "Retrieval",
                "type": "Data",
                "configuration": {
                    "data": {"sourceOutput": "document", "targetInput": "objectKey"}
                },
            },
        ],
    },
)
flow_id = flow["id"]
 
# Prepare + version + alias (same pattern as agents)
ctl.prepare_flow(flowIdentifier=flow_id)
v = ctl.create_flow_version(flowIdentifier=flow_id)
alias = ctl.create_flow_alias(
    flowIdentifier=flow_id,
    name="prod",
    routingConfiguration=[{"flowVersion": v["version"]}],
)
alias_id = alias["id"]

Invoking a flow

agent_runtime = boto3.client("bedrock-agent-runtime", region_name="us-east-1")
 
resp = agent_runtime.invoke_flow(
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
    inputs=[
        {
            "content": {"document": {"text": "What is our PTO policy?"}},
            "nodeName": "Input",
            "nodeOutputName": "document",
        }
    ],
    enableTrace=True,
)
 
for event in resp["responseStream"]:
    if "flowOutputEvent" in event:
        print(event["flowOutputEvent"]["content"])
    elif "flowTraceEvent" in event:
        trace = event["flowTraceEvent"]["trace"]
        print(f"[TRACE] {trace}")

7. Asynchronous flow executions

Per re:Invent 2024. Long-running flows can run asynchronously via StartFlowExecution — useful when the flow does batch processing, video generation, large RAG ingestion, etc.

resp = agent_runtime.start_flow_execution(
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
    inputs=[...],
)
execution_arn = resp["executionArn"]
 
# Poll
status_resp = agent_runtime.get_flow_execution(
    executionIdentifier=execution_arn,
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
)
print(status_resp["status"])  # Running | Succeeded | Failed | Aborted
 
# List events
events = agent_runtime.list_flow_execution_events(
    executionIdentifier=execution_arn,
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
    eventType="Flow",  # or "Node"
)

Asynchronous executions can run up to 24 hours.

8. Multi-turn flows (Converse-with-Flow)

Per docs.aws.amazon.com/bedrock/latest/userguide/flows-multi-turn-invocation.html. A flow can be marked as multi-turn-capable by including a MultiTurnInputRequest node — the flow pauses, returns a question to the user, and resumes when the user replies.

# First call
resp = agent_runtime.invoke_flow(
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
    inputs=[{"content": {"document": {"text": "Book me a flight"}}, ...}],
)
 
# Suppose the flow asks "What's your destination?"
# resp contains a flowMultiTurnInputRequestEvent with the question
 
# Resume the flow with the user's answer
resp = agent_runtime.invoke_flow(
    flowIdentifier=flow_id,
    flowAliasIdentifier=alias_id,
    executionId=prior_execution_id,   # carries state
    inputs=[{"content": {"document": {"text": "Paris"}}, "nodeName": "Input2", ...}],
)

Useful for slot-filling flows (gathering parameters interactively) without writing the loop yourself.

9. Pricing

ComponentCost
Prompt Management storageFree
Prompt Management invocationsUnderlying model cost only
Flow storageFree
Flow invocationsSum of costs of each invoked node (model calls, Lambda, KB queries)
Asynchronous flow executionSame; plus S3 storage if the flow uses it

There is no per-invocation Flow fee — you pay only for the underlying resources each node consumes.

10. Pattern catalog

Common Flow patterns:

RAG with grounding fallback:

  1. KB retrieve → Condition (any results?) → Prompt (synthesize) | Prompt (apologize)

Multi-step extraction:

  1. Input (PDF) → Prompt (extract entities as JSON) → Inline Code (validate JSON) → Lambda (persist to DB)

Tool-augmented agent:

  1. Input (query) → Agent (with action groups + KB) → Output

Translate-then-summarize:

  1. Input (text) → Prompt (translate to English) → Prompt (summarize) → Output (summary)

Branching by classification:

  1. Input → Prompt (classify as billing/support/sales) → Condition (route to one of three specialist agents) → Output

11. Limits

LimitValue
Prompts per account per region500
Versions per prompt20
Variants per prompt10
Variables per prompt20
Flows per account per region50
Nodes per flow40
Flow connections per flow100
Synchronous flow invocation timeout30 minutes
Async flow execution timeout24 hours

12. Pitfalls

  • Inline prompts in Flow nodes are not the same as Prompt Management prompts — they’re stored inline in the flow definition. Edit means new flow version. For prompts you want to evolve independently, reference a Prompt Management ARN from a Flow Prompt node.
  • Flow editing is brittle — the visual editor saves frequently but mistakes can wedge the flow. Test the DRAFT before publishing a version.
  • Lambda nodes have a 15-minute timeout (inherited from Lambda). For longer tasks, use async flow execution + Lambda → SQS → polling pattern.
  • Iterator nodes serialize — they invoke downstream nodes once per array item. For parallel processing, use a fan-out pattern with Lambda async invocations instead.
  • Inline Code (Python) nodes are subject to ~3-second CPU limits — they’re for transformations, not heavy compute.

Further reading