Home / AI & GenAI

AI & GenAIHow-toBeginner

Your first Amazon Bedrock call: the Converse API with Python and boto3

Call a Bedrock model from Python with no API key in your code. Find a model ID, grant the right IAM permission, use the Converse API, add a system prompt and streaming, read token usage and fix the common errors.

Deepak Bhardwaj5 min read

Applies toAmazon Bedrock Runtime, boto3, AWS CLI v2

The goal

You want to call a model through Amazon Bedrock from a Python script. You'll use your usual AWS credentials, and the Converse API, which works the same way across many models. That means that you can change models later with little code change.

Step 1: check the basics

You need AWS CLI access and a recent boto3:

aws sts get-caller-identity
aws configure get region

pip install --upgrade boto3
python -c "import boto3; print(boto3.__version__)"

Bedrock isn't in every region, and each region offers different models. Check that yours has what you need.

Step 2: find a model

aws bedrock list-foundation-models \
  --by-output-modality TEXT \
  --query 'modelSummaries[].{id:modelId, provider:providerName, inference:inferenceTypesSupported, status:modelLifecycle.status}' \
  --output table

Copy a model ID from the list. Look at the inference column:

Value Meaning
ON_DEMAND You can call this model ID directly
INFERENCE_PROFILE You must call it through an inference profile
PROVISIONED Needs provisioned capacity

For an INFERENCE_PROFILE model, list the profile IDs. They often have a geographic prefix:

aws bedrock list-inference-profiles \
  --query 'inferenceProfileSummaries[].{id:inferenceProfileId, name:inferenceProfileName, status:status}' \
  --output table

If you use the wrong type, you'll get a ValidationException. See on-demand throughput not supported.

Some models also need access enabled, or terms accepted, for your account. How that works has changed over time, so check the Bedrock console's model access page if you're denied.

Step 3: grant permission

Your identity needs permission to invoke the model. Limit it to the models you use:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "InvokeOneModel",
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": [
        "arn:aws:bedrock:ap-south-1::foundation-model/<model-id>"
      ]
    }
  ]
}

The Converse APIs use these same InvokeModel permissions. If you're using an inference profile, you also need to allow the profile's ARN and the underlying foundation models in each of its regions.

Step 4: make the call

Save this as first_call.py:

import os
import boto3

MODEL_ID = os.environ["BEDROCK_MODEL_ID"]

client = boto3.client("bedrock-runtime")

response = client.converse(
    modelId=MODEL_ID,
    system=[{"text": "You are a concise assistant for IT operations engineers."}],
    messages=[
        {
            "role": "user",
            "content": [{"text": "In two sentences, what does an HTTP 429 error mean?"}],
        }
    ],
    inferenceConfig={"maxTokens": 300, "temperature": 0.2},
)

text = response["output"]["message"]["content"][0]["text"]
print(text)
print("---")
print("stop reason:", response["stopReason"])
print("usage:", response["usage"])
print("latency ms:", response["metrics"]["latencyMs"])

Run it:

export BEDROCK_MODEL_ID="<model or inference profile id>"
python first_call.py

Notice what isn't there: no API key. boto3 uses your normal AWS credentials, including an instance or container role when it's running on AWS.

What's in the response:

Field Meaning
output.message.content A list of content blocks. Some models return more than one kind
stopReason Why it stopped: end_turn, max_tokens, tool_use or guardrail_intervened
usage inputTokens, outputTokens and totalTokens
metrics.latencyMs Time taken

If stopReason is max_tokens, the answer was cut off. See output truncated.

Some models return a reasoning block before the text, so content[0] might not be text. Use this safer way to read it:

blocks = response["output"]["message"]["content"]
text = "".join(b["text"] for b in blocks if "text" in b)

Step 5: have a conversation

The API doesn't remember earlier calls, so your code has to send the history. See chat history basics.

messages = [{"role": "user", "content": [{"text": "My server is named web01."}]}]

r1 = client.converse(modelId=MODEL_ID, messages=messages)
messages.append(r1["output"]["message"])

messages.append({"role": "user", "content": [{"text": "What is my server called?"}]})
r2 = client.converse(modelId=MODEL_ID, messages=messages)

print(r2["output"]["message"]["content"][-1]["text"])

Step 6: stream the reply

For long answers, show text as it arrives:

response = client.converse_stream(
    modelId=MODEL_ID,
    messages=[{"role": "user", "content": [{"text": "List five ways to reduce EC2 costs."}]}],
    inferenceConfig={"maxTokens": 500},
)

for event in response["stream"]:
    if "contentBlockDelta" in event:
        delta = event["contentBlockDelta"]["delta"]
        if "text" in delta:
            print(delta["text"], end="", flush=True)
    elif "messageStop" in event:
        print("\nstop:", event["messageStop"]["stopReason"])
    elif "metadata" in event:
        print("usage:", event["metadata"]["usage"])

Step 7: handle errors

Add retries for throttling, and handle errors explicitly:

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

client = boto3.client(
    "bedrock-runtime",
    config=Config(
        retries={"mode": "adaptive", "max_attempts": 5},
        read_timeout=120,
    ),
)

try:
    response = client.converse(
        modelId=MODEL_ID,
        messages=[{"role": "user", "content": [{"text": "Hello"}]}],
    )
except ClientError as e:
    code = e.response["Error"]["Code"]
    message = e.response["Error"]["Message"]
    print(code, message)
    raise
Error Likely cause Fix
AccessDeniedException IAM, or model access not enabled See access denied
ValidationException Wrong model ID, or inference profile needed Check Step 2
ThrottlingException Quota reached See throttling
ResourceNotFoundException Model isn't in this region, or it's retired Check region, and the lifecycle status
ModelTimeoutException, read timeout Slow or very long response Use streaming, and raise read_timeout
ServiceUnavailableException Temporary service problem Retry with backoff
NoRegionError No region is set Set AWS_REGION, or pass region_name

For a broader list, use the LLM API errors cheat sheet.

Warning

Each call costs money, based on tokens. A loop with a bug can run up a large bill in minutes. Set maxTokens, and add a budget alert. Before you roll out, estimate the cost for your traffic.

Don't send sensitive data without checking

Check your organization's rules before sending customer or confidential data, and read AWS's data handling terms for Bedrock. Turn on invocation logging if you need an audit trail, but remember that prompts in logs need protection too. For input and output checks, see guardrails.

Prevent it next time