How to Run an LLM Cost Audit: Cut Your AI API Bill Without Hurting Quality

    AI spend usually grows from a few habits: one default model for every task, the same long prompt sent thousands of times, and nobody knowing which feature costs what. Here's the five-step audit I run on a codebase, with TypeScript examples for model routing, response caching and per-feature metering.

    Published
    Reading time
    8 min read
    On this page

    TL;DR: Most LLM bills are inflated by the same five things: calls nobody inventoried, one model used for every task, repeated context that isn't cached, wasted tokens, and no per-feature metering. Fix them in that order. Teams commonly cut a large share of their AI spend this way without users noticing a change in quality, as long as every change is checked against an eval set first.

    This is the audit I run when AI spend starts growing faster than usage. I've also packaged it as a Claude Code agent skill you can run on your own repo: LLM Cost Audit skill.

    Step 1: Inventory Every Call Site

    You can't cut what you haven't found. Search the codebase for every SDK client, raw HTTP call to an inference endpoint and internal wrapper. For each call site, write down:

    • Model and max_tokens
    • Where the prompt comes from (static, templated, user input, retrieved documents)
    • How often it runs: per request, per item in a loop, on a cron, on every keystroke
    • Who sees the output: a user, or another step in a pipeline

    Flag these right away, because they are where bills explode:

    • LLM calls inside loops over collections
    • Calls on every page load or keystroke
    • Retries without backoff or without a cap

    Step 2: Fix Model-Tier Mismatches

    The biggest single saving is usually using the smallest model that passes your evals for each task. One DEFAULT_MODEL constant used everywhere is a red flag.

    TaskTypical tier
    Classification, routing, yes/no checks, extraction, titlesSmallest / fastest tier
    Summaries, structured rewriting, most tool callsMid tier
    Multi-step reasoning, hard code generation, long user-facing writingLargest tier, after you've tested the mid tier

    Make the choice explicit per task so it can be changed and tested one task at a time:

    typescript
    // One place that decides which model each task uses.
    type Task = "classify" | "extract" | "summarize" | "plan" | "write";
    
    const MODEL_FOR: Record<Task, string> = {
      classify: "small-fast-model",
      extract: "small-fast-model",
      summarize: "mid-model",
      plan: "large-model",
      write: "mid-model", // downgraded from large after it passed the eval set
    };
    
    export function modelFor(task: Task) {
      return MODEL_FOR[task];
    }

    Never downgrade blind. Run the cheaper model on your eval set, compare, and ship only if quality holds. (If you don't have an eval set yet, that's the first thing to build. See What Is an AI Product Engineer? for why.)

    Step 3: Cache What Repeats

    There are three kinds of caching, and most codebases use none of them.

    Prompt caching. Long system prompts, few-shot examples and shared documents that open many requests can use your provider's prompt cache, which bills repeated prefix tokens at a discount. Put the stable parts first and the changing parts last so the prefix actually matches.

    Response caching. Deterministic tasks (extraction, classification, anything at temperature 0 on the same input) can be cached in your own store, keyed on a hash of the normalized input:

    typescript
    import { createHash } from "node:crypto";
    
    export async function cachedCall<T>(
      task: string,
      input: string,
      run: () => Promise<T>,
      ttlSeconds = 60 * 60 * 24,
    ): Promise<T> {
      const key = "llm:" + task + ":" + createHash("sha256").update(input.trim().toLowerCase()).digest("hex");
      const hit = await redis.get(key);
      if (hit) return JSON.parse(hit) as T;
    
      const result = await run();
      await redis.set(key, JSON.stringify(result), "EX", ttlSeconds);
      return result;
    }

    Negative caching. If an input reliably fails or gets refused, cache the failure for a short time so you don't pay for the same failure repeatedly.

    Step 4: Remove Token Waste

    • Send the section, not the document. Retrieve or slice the part the task needs.
    • Bound conversation history. Multi-turn flows that resend everything grow without limit. Window or summarize older turns.
    • Tighten output formats. Ask for compact JSON with short keys, no prose wrapper, and no reasoning text that nobody reads.
    • Set realistic max_tokens and stop sequences so runaway outputs are cut off.
    • Repair locally before retrying. If a JSON parse fails on a trailing comma, fix it in code instead of resending the whole context.

    Step 5: Meter Spend Per Feature

    You can't optimize what you can't attribute. Tag every call with a feature name and record tokens in and out:

    typescript
    export async function meteredCall(feature: string, task: Task, params: CallParams) {
      const started = Date.now();
      const res = await llm.call({ ...params, model: modelFor(task) });
    
      await metrics.record({
        feature,
        task,
        model: res.model,
        inputTokens: res.usage.inputTokens,
        outputTokens: res.usage.outputTokens,
        cachedTokens: res.usage.cachedTokens ?? 0,
        latencyMs: Date.now() - started,
      });
      return res;
    }

    With that in place, add:

    • Spend alerts per feature and a global daily budget alarm
    • Per-user or per-tenant rate limits on anything user-triggered
    • A shadow or A/B path to test model downgrades on live traffic before committing

    What the Audit Should Produce

    The output isn't a rewrite. It's a ranked list:

    1. 01Top savings, each with the call site, current pattern, proposed change, estimated share of that call's cost saved, risk, and how quality will be checked
    2. 02Quick wins that are safe to ship this week (usually caching and loop fixes)
    3. 03Instrumentation gaps to close before the next round

    Rough estimates are fine as long as you state the assumptions (volume, average tokens). A ranked list that's roughly right beats a precise spreadsheet nobody acts on.


    Harsh Rastogi is an AI Product Engineer and Business AI Head at Modelia, building enterprise-grade AI products and agents. He writes about shipping AI to production at harshrastogi.tech.

    Frequently asked questions

    How do I reduce my LLM API costs?

    Inventory every call site, use the smallest model that passes your evals for each task, cache repeated prompt prefixes and deterministic responses, remove token waste such as unbounded history and verbose outputs, and meter spend per feature so you know where the money goes.

    What is the biggest saving in an LLM cost audit?

    Usually model-tier mismatches: one large default model used for every task, including classification and extraction that a small model handles just as well. Per-task model selection, validated against an eval set, is often the single largest lever.

    Is it safe to switch to a cheaper model?

    Only after testing. Run the cheaper model on an eval set of real examples, compare quality, and roll it out behind a flag or A/B test with a way to switch back.

    • LLM
    • Cost Optimization
    • AI Engineering
    • Caching
    • TypeScript

    Written by Harsh Rastogi, AI Product Engineer and Business AI Head at Modelia. More on AI products, agents and production engineering on LinkedIn.

    Share

    LinkedInX
    Portrait of Harsh Rastogi, AI Product Engineer

    Harsh Rastogi

    AI Product Engineer and Business AI Head at Modelia

    I build AI products and agents from the first sketch to production at Modelia, and I'm a Founding Engineer at SelfAgentic. Before that I built platforms at Asynq and Bharat Electronics Limited. I publish the agent skills I use every day, and you can see what I've shipped in my work.

    • How to Ship an AI Feature Safely: Flags, Guardrails, Staged Rollouts and a Kill Switch

      AI features fail in ways CI never catches: a quality drop on real inputs, a cost spike from long prompts, one bad output that ends up in a screenshot. Here's the rollout playbook I use instead of a big-bang launch: blast radius, flags and a kill switch, guardrails, a staged exposure table and the metrics that decide each step.

      • AI Engineering
      • Feature Flags
      • LLM
      • Production
      AI & Machine Learning8 min read
    • Building Agentic AI Systems: Real-World Patterns from Production

      How we built autonomous AI agents at Asynq.ai and Modelia.ai that reason, plan, and execute multi-step tasks. Practical patterns for tool use, memory, orchestration, and guardrails in production agentic systems.

      • Agentic AI
      • AI Systems
      • TypeScript
      • Node.js
      AI & Machine Learning14 min read
    • Building Agentic Workflows with Anthropic's Claude: A Production Guide

      How we use Anthropic's Claude API to power agentic workflows at Modelia.ai — from tool use and extended thinking to structured outputs and multi-turn agents. Practical patterns for building reliable AI agents with Claude.

      • Agentic AI
      • Anthropic
      • Claude
      • AI Systems
      AI & Machine Learning16 min read

    Get the next article by email.

    New articles and AI Pulse editions. Nothing else.