AWS Bedrock · Claude · Cost Optimization · Performance · AI Engineering

A Client's Claude API Was Costing $800/Month and Taking 90 Seconds. We Fixed Both.

By Ramesh Nori · April 22, 2026 · 15 min read

A Client's Claude API Was Costing $800/Month and Taking 90 Seconds. We Fixed Both.

A fintech client came to us last year with a problem they couldn't figure out.

They had a Claude-powered financial advisor agent on AWS Bedrock. It answered customer questions about interest rates, compared loan products, and did compound interest math in real time. The AI was accurate. The answers were good. But every response took 90 to 120 seconds.

Their AWS Bedrock bill had climbed past $800 a month. Customer experience scores were dropping because nobody wants to stare at a loading spinner for two minutes while an AI "thinks." Their engineering team had spent weeks trying to speed it up. Different prompts, different temperatures. They even considered switching models entirely.

Their VP of Engineering told us: "We either fix the speed by end of month or we're pulling the plug."

We spent three weeks inside their codebase. What we found wasn't a model problem. It was seven configuration issues hiding in plain sight. After fixing them, the same agent responded in 3 to 10 seconds. The monthly bill dropped under $200. Same accuracy. Same features.

Here's exactly what we changed.

The code examples below are simplified reproductions based on patterns we've seen across multiple client engagements. No client code or proprietary configurations are shared here.

The Setup

The stack was straightforward: Python, LangChain, AWS Bedrock, Claude. The agent had access to a calculator tool, a product database, and a rate API. It handled questions like "What's my monthly payment on a $350,000 mortgage at 6.5% over 30 years?"

The code looked reasonable at first glance. The problems were in the configuration, not the logic.

1. They Were Using the Wrong API Class

This one had cost the team weeks of confusion.

The code used ChatBedrock from LangChain, the legacy wrapper. It kept throwing this error on newer Claude models:

ValidationException: Model is not supported on this API

The fix was one line. Switch to ChatBedrockConverse, which uses the Messages API that current Claude models require:

from langchain_aws import ChatBedrockConverse

llm = ChatBedrockConverse(
    model="us.anthropic.claude-sonnet-4-6-20250514-v1:0",
    region_name="us-east-1"
)

That alone cut response times by 25%. The old wrapper was adding overhead translating between API formats behind the scenes.

If you're still using ChatBedrock, switch today. It's deprecated and slower.

2. The Default AWS Timeout Was Killing Requests

Here's something most teams miss: boto3's default read timeout is 60 seconds. Claude's processing can run much longer for complex queries.

What was happening in this client's system? Long queries would silently fail at the 60-second mark. The retry logic would kick in. The same slow request would run again. And again. Three attempts at 60 seconds each = 180 seconds of wasted compute before the user sees an error.

The fix:

from botocore.config import Config

boto_config = Config(
    read_timeout=300,
    connect_timeout=10,
    retries={'max_attempts': 2}
)

bedrock_client = boto3.client(
    'bedrock-runtime',
    region_name='us-east-1',
    config=boto_config
)

We set the read timeout to 5 minutes (more than enough for any reasonable query) and dropped retries to 2. No more triple-billing on slow requests.

Impact: 20% faster on average. Eliminated the timeout-retry-timeout death spiral entirely.

3. Claude Was Being Paid to Overthink

This was the biggest win.

Claude's extended thinking feature is powerful. It lets the model reason through problems step by step before answering. But the client had no budget on how much thinking it does.

A simple "What's the interest rate on a savings account?" was triggering the same deep reasoning as "Compare the tax implications of a Roth IRA conversion across three income brackets."

We added a thinking budget and matched it to query complexity:

# For simple lookups (fast, minimal thinking)
simple_llm = ChatBedrockConverse(
    model="us.anthropic.claude-sonnet-4-6-20250514-v1:0",
    client=bedrock_client,
    additional_model_request_fields={
        "thinking": {
            "type": "enabled",
            "budget_tokens": 1000
        }
    }
)

# For complex calculations (more room to reason)
complex_llm = ChatBedrockConverse(
    model="us.anthropic.claude-sonnet-4-6-20250514-v1:0",
    client=bedrock_client,
    additional_model_request_fields={
        "thinking": {
            "type": "enabled",
            "budget_tokens": 4000
        }
    }
)

A 1,000-token thinking budget handles 80% of customer queries in 1 to 3 seconds. You only need 4,000 tokens for genuine multi-step calculations.

Impact: 40 to 60% faster. This is also where the biggest cost savings came from. Fewer tokens processed per request means a lower bill.

4. Prompt Caching Changed Everything (With a Catch)

AWS Bedrock supports prompt caching. If you're sending the same system prompt and context with every request, cache it. Subsequent requests skip re-processing that content.

This client's product catalog and rate tables were included in every prompt. That's 3,000+ tokens of context sent and billed on every single call.

With caching:

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": product_catalog_text,
                "cache_control": {"type": "ephemeral"}
            },
            {
                "type": "text",
                "text": user_question
            }
        ]
    }
]

First request: 6 to 8 seconds (builds the cache). Every request after that for the next 5 minutes: under 2 seconds. An 85% reduction in latency.

But here's what caught us off guard. Caching worked perfectly for narrative content like product descriptions, FAQs, and policy documents. It did not work for structured financial data in markdown tables. The cache simply wasn't being used.

We still don't have a definitive answer on why. Our best guess is that Anthropic has restrictions around caching certain content types for compliance reasons. Financial data in particular.

The workaround: We restructured the financial data as narrative paragraphs instead of markdown tables. Cache hit rates went from 0% to 90%.

Impact: 85% latency reduction on cached requests. Roughly 60% reduction in token costs for repeat context.

5. The Agent Had Too Many Instructions

This one is counterintuitive.

The original agent had 250+ lines of system instructions. Detailed examples for every tool. Edge case handling. Formatting rules. Response templates.

The engineering team assumed more guidance would make the agent smarter. It made it slower.

Claude was spending tokens evaluating all those instructions before doing anything useful. Every request started with the model carefully reading a small novel of rules, deciding which ones applied, cross-referencing examples. Then, finally, answering the question.

We cut the system prompt to 3 lines:

agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    max_iterations=3,
    early_stopping_method="generate",
    agent_kwargs={
        "prefix": """You're a financial advisor. Use tools when needed.
        Calculator handles ^ notation for exponentiation.
        Be concise."""
    }
)

Before: 95 seconds average. After: 30 seconds average. A 68% improvement just from deleting instructions.

Claude is already good at reasoning. Get out of its way.

6. The Tools Weren't Handling Input Variants

Small thing, big impact.

The calculator tool expected Python notation (** for exponents). Claude sometimes sent caret notation (^). The tool would fail. Claude would see the error, reformulate, and retry. Each retry cycle burned 5 to 10 seconds and extra tokens.

The fix took 10 minutes:

def calculator(expression):
    cleaned = re.sub(r'(\d+)\s*\^\s*(\d+)', r'\1**\2', expression.strip())
    try:
        result = eval(cleaned)
        return f"{result:.2f}" if isinstance(result, float) else str(result)
    except Exception as e:
        return f"Error: {e}. Try reformatting."

Impact: Eliminated 90% of tool retry cycles. Saved 5 to 10 seconds on math-heavy queries.

7. All of This Compounds

Here's the progression over three weeks:

What we changedResponse timeBill impact
Baseline95-120 sec$800+/mo
Correct API class70-90 sec-15%
Timeout fix55-75 sec-20%
Thinking budget20-30 sec-40%
Prompt caching5-15 sec-60%
Agent simplification8-12 sec-10%
Tool robustness3-10 sec-5%
Final result3-10 secUnder $200/mo

Each fix was small. Together they turned a product that was about to get killed into the fastest feature in the client's app.

What We'd Do Differently on a New Build

If we were building this from scratch today with Claude Sonnet 4.6 (current as of early 2026):

  1. Start with ChatBedrockConverse. The legacy classes shouldn't exist in any new code.
  2. Set a thinking budget from day one. Default to 1,000 tokens and tune up only where needed.
  3. Cache aggressively. Any static context over 1,024 tokens should be cached.
  4. Keep agent instructions under 5 lines. Add more only if the model struggles with a specific task. It usually won't.
  5. Build tools that handle sloppy input. The model will surprise you with how it formats things.

Claude Sonnet 4.6 on Bedrock is noticeably quicker out of the box than 3.7 was. But the same principles apply. Bad configuration will make any model slow.

The Bottom Line

If your Bedrock bill is climbing and your users are waiting, you probably don't need a different model. You need to audit your configuration.

These seven fixes took three weeks to discover and three hours to implement. We originally wrote about this work in May 2025. This is the updated version with current model references and a year's worth of additional client engagements backing up the same findings.

Most of the performance was lost in places nobody thinks to look: SDK timeouts, API wrapper overhead, and an agent that was drowning in its own instructions.


Written by Ramesh Nori. If this was useful or you have feedback, reach me at cloudbuckle@gmail.com.