NovuSpark
All articles
AWSOctober 10, 2025 · NovuSpark Team

Getting Started with Amazon Bedrock

This is the first post in our Amazon Bedrock series. Later posts cover Knowledge Bases, Agents, Guardrails, and choosing a foundation model.

A common first assumption about Amazon Bedrock is that it's "AWS's own AI model" — it isn't. Bedrock is a managed gateway to foundation models from multiple providers (Anthropic's Claude, Meta's Llama, Amazon's own Titan and Nova models, and others), accessed through one consistent API, billed through your existing AWS account, and governed by the same IAM permissions model you already use for every other AWS service.

your applicationboto3 + IAMAmazon Bedrockone API, one billAnthropic ClaudeMeta LlamaAmazon Titan / Novano separate account, credential, or billing relationship per provider
Fig. 1 — Bedrock consolidates multiple providers behind one AWS-native API, IAM model, and bill

Why reach for Bedrock instead of calling a provider's API directly

The OpenAI API, covered in an earlier series on this blog, requires its own separate API key, its own separate billing relationship, and its own separate access-control mechanism, entirely outside AWS. For an organization already running infrastructure on AWS, Bedrock's genuine value is consolidation: one IAM policy model, one bill, one set of audit logs (via CloudTrail), across every model provider Bedrock supports — rather than a different credential and a different security review for each model provider a team wants to evaluate.

Making your first call

import boto3
import json
 
client = boto3.client("bedrock-runtime", region_name="eu-west-2")
 
response = client.invoke_model(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    body=json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 300,
        "messages": [
            {"role": "user", "content": "Explain what a load balancer does, in two sentences."}
        ],
    }),
)
 
result = json.loads(response["body"].read())
print(result["content"][0]["text"])

Two details worth noticing immediately: boto3 — the same AWS SDK used for every other AWS service (S3, EC2, Lambda) — is the client here too, meaning a team already comfortable with boto3 for infrastructure work has no new SDK to learn just to call a foundation model. And the request body's exact shape is provider-specific — this example uses Anthropic's Claude message format because modelId specifies a Claude model; a Llama or Titan model expects a differently-shaped body, since Bedrock passes the request through to each provider's own underlying format rather than fully normalizing every provider behind one identical schema.

Model access: an explicit step, not automatic

Unlike calling OpenAI's API directly with an API key, Bedrock requires explicitly requesting access to each foundation model in the AWS Console before your account can invoke it:

AWS Console → Bedrock → Model access → Request model access

This is a deliberate governance checkpoint, not friction for its own sake — an organization can review and approve which specific models are permitted before any application code can actually call them, which matters for compliance-sensitive environments in a way that a per-provider, individually-managed API key doesn't naturally provide on its own.

IAM: the same permission model as everything else in AWS

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:eu-west-2::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"
    }
  ]
}

This is the exact same least-privilege principle applied throughout this blog — the scoped IAM user we built for our own GitHub Actions deploy pipeline, covered in our CI/CD case study — now applied to model access specifically: an application's IAM role can be restricted to invoking only specific, approved models, not a blanket "any Bedrock model" grant. A CI pipeline or Lambda function calling Bedrock inherits your organization's existing IAM controls directly, rather than needing a separate, parallel access-control system just for AI model calls.

Streaming responses

response = client.invoke_model_with_response_stream(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    body=json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 300,
        "messages": [{"role": "user", "content": "Write a short paragraph about Kubernetes."}],
    }),
)
 
for event in response["body"]:
    chunk = json.loads(event["chunk"]["bytes"])
    if chunk.get("type") == "content_block_delta":
        print(chunk["delta"]["text"], end="", flush=True)

The same streaming benefit covered in our OpenAI series — improved perceived latency, response rendered incrementally rather than all at once — applies here through invoke_model_with_response_stream, AWS's equivalent of the stream=True parameter from the OpenAI SDK.

Cross-region inference: routing around capacity constraints

For models in high demand, a single AWS region can occasionally hit throttling under heavy load. Bedrock supports cross-region inference profiles, which route a request across a defined set of regions automatically:

response = client.invoke_model(
    modelId="us.anthropic.claude-3-5-sonnet-20241022-v2:0",  # a cross-region inference profile ID
    body=json.dumps({...}),
)

Using an inference profile ID (prefixed with a geography identifier like us.) rather than a single-region model ID lets Bedrock select whichever region in that set currently has available capacity — genuinely useful for production workloads sensitive to occasional regional throttling, at the cost of losing the guarantee that a request is processed in one specific, always-known region, worth checking against your organization's own data-residency requirements before adopting broadly.

Logging every invocation for audit and cost tracking

# Enable model invocation logging (typically configured once, via the console or IaC)
{
  "loggingConfig": {
    "cloudWatchConfig": {
      "logGroupName": "/aws/bedrock/modelinvocations",
      "roleArn": "arn:aws:iam::008971632408:role/bedrock-logging-role"
    },
    "textDataDeliveryEnabled": true
  }
}

Enabling invocation logging sends every model call's input, output, and metadata to CloudWatch Logs — the same audit-trail instinct as CloudTrail logging for infrastructure changes, applied here to actual model usage. This is genuinely useful both for cost attribution (which application or team is driving usage) and for the kind of after-the-fact debugging covered for LangChain tracing in an earlier series — except here, it's a managed AWS logging pipeline rather than a separate third-party observability tool.

The Converse API: a unified interface across providers

The invoke_model calls shown so far require a provider-specific request body — Claude's message format differs from Llama's or Titan's. Bedrock's Converse API addresses this directly, offering one consistent request and response shape regardless of which underlying model is actually called:

response = client.converse(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    messages=[{"role": "user", "content": [{"text": "Explain what a load balancer does."}]}],
    inferenceConfig={"maxTokens": 300, "temperature": 0.2},
)
 
print(response["output"]["message"]["content"][0]["text"])

Swapping modelId to a Llama or Titan model here requires no change to the request body's structure at all — a genuine convenience once an application needs to compare or switch between providers, the same normalization LangChain's consistent model interface provides across OpenAI and Anthropic, covered in our LangChain series, but here built directly into the AWS SDK itself rather than a third-party library.

Provisioning access to a model via infrastructure as code

Model access, introduced above as a console-driven step, can also be requested and managed through Terraform — worth doing for the same reason any other piece of infrastructure benefits from being defined in code rather than clicked through manually:

resource "aws_bedrock_model_invocation_logging_configuration" "example" {
  logging_config {
    cloudwatch_config {
      log_group_name = "/aws/bedrock/modelinvocations"
      role_arn       = aws_iam_role.bedrock_logging.arn
    }
  }
}

Managing Bedrock's logging configuration and IAM policies as Terraform resources — rather than configured once by hand and never captured anywhere — extends the same "infrastructure lives in version control" principle covered throughout our Terraform series to an AI platform specifically, so a Bedrock setup is reviewable, reproducible, and recoverable the same way any other piece of infrastructure is.

What to actually remember from this post

  • Bedrock is a managed gateway to multiple providers' models, not AWS's own proprietary model — Claude, Llama, and Amazon's own models are all accessed through the same service.
  • The request body format is provider-specific, even though the client and authentication mechanism are unified through boto3 and IAM.
  • Model access must be explicitly requested and approved before an account can invoke a given model — a deliberate governance checkpoint.
  • IAM policies can scope exactly which models a given role can invoke — the same least-privilege principle covered throughout this blog, applied directly to AI model access.
  • Cross-region inference profiles route around regional capacity constraints, at the cost of losing single-region processing guarantees — check this against data-residency requirements first.
  • Invocation logging to CloudWatch gives an audit trail and cost-attribution signal, the same instinct as CloudTrail for infrastructure changes.

Next in the series: Amazon Bedrock Knowledge Bases: Building RAG Without Managing Infrastructure, where Bedrock handles the embedding and vector storage pipeline we built by hand earlier in this blog.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.