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
- bedrock-api-and-converse —
promptVariablesfield on Converse for using a Prompt Management ARN - bedrock-agents — agents are one of the node types in a Flow
- bedrock-knowledge-bases-rag — knowledge bases are another node type in a Flow
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/assistantcontent 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:13. 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, oradditionalModelRequestFields— 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
| Node | Purpose |
|---|---|
| Input | Entry point. Defines the flow’s parameters. |
| Output | Exit point. The flow’s return value. |
| Prompt | Inline prompt OR reference to a Prompt Management ARN |
| Agent | Invoke a Bedrock Agent alias |
| Knowledge Base | Query a KB (returns retrieved chunks or RetrieveAndGenerate result) |
| Lambda function | Invoke a Lambda |
| AWS Lambda | Same as above (different node UI) |
| Condition | Branch on JMESPath expressions over node outputs |
| Iterator | Loop over an array, invoking downstream nodes per item |
| Collector | Collect iterator outputs back into an array |
| Storage | Read/write S3 |
| Retrieval | Retrieve 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
| Component | Cost |
|---|---|
| Prompt Management storage | Free |
| Prompt Management invocations | Underlying model cost only |
| Flow storage | Free |
| Flow invocations | Sum of costs of each invoked node (model calls, Lambda, KB queries) |
| Asynchronous flow execution | Same; 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:
- KB retrieve → Condition (any results?) → Prompt (synthesize) | Prompt (apologize)
Multi-step extraction:
- Input (PDF) → Prompt (extract entities as JSON) → Inline Code (validate JSON) → Lambda (persist to DB)
Tool-augmented agent:
- Input (query) → Agent (with action groups + KB) → Output
Translate-then-summarize:
- Input (text) → Prompt (translate to English) → Prompt (summarize) → Output (summary)
Branching by classification:
- Input → Prompt (classify as billing/support/sales) → Condition (route to one of three specialist agents) → Output
11. Limits
| Limit | Value |
|---|---|
| Prompts per account per region | 500 |
| Versions per prompt | 20 |
| Variants per prompt | 10 |
| Variables per prompt | 20 |
| Flows per account per region | 50 |
| Nodes per flow | 40 |
| Flow connections per flow | 100 |
| Synchronous flow invocation timeout | 30 minutes |
| Async flow execution timeout | 24 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.