Forecasting monthly LLM spend from daily token trends is a survival skill once your app scales past a prototype. You can’t manage what you can’t predict, and finance will chase you down if the bill triples because a new feature quietly doubled your prompt size. This guide walks through a reproducible pipeline to turn raw token logs into a defensible month-end projection.
Step 1: Collect daily token totals per model
Start with the raw material: per-request token counts. Every OpenAI-compatible chat completion response returns a usage object with prompt_tokens, completion_tokens, and total_tokens. If you persist those fields alongside the model name and timestamp, you already have what you need.
import sqlite3
conn = sqlite3.connect("llm_logs.db")
cur = conn.cursor()
cur.execute("""
SELECT model,
date(timestamp) AS day,
SUM(prompt_tokens) AS prompt_tokens,
SUM(completion_tokens) AS completion_tokens
FROM requests
GROUP BY model, day
ORDER BY day
""")
rows = cur.fetchall()
If you route traffic through a gateway that handles failover, you still get the same usage shape on successful responses. n4n.ai exposes per-token usage metering that aggregates these totals for you, so you can skip the log parsing and pull daily sums directly from its API instead of maintaining your own table.
Either way, the output of this step is a list of (model, day, prompt_tokens, completion_tokens) tuples.
Step 2: Map tokens to cost with a price table
Token counts are not dollars. You need a price table keyed by model and token type. Pull list prices from provider docs; they change, so pin a version or store effective dates. Public list prices as of early 2025 include roughly $5 per 1M input tokens and $15 per 1M output tokens for GPT-4o-class models, with variations across vendors.
PRICES = {
"gpt-4o": {"input": 5.0 / 1e6, "output": 15.0 / 1e6},
"claude-3-5-sonnet": {"input": 3.0 / 1e6, "output": 15.0 / 1e6},
"llama-3.1-70b": {"input": 0.5 / 1e6, "output": 0.7 / 1e6},
}
def cost_for(model, prompt_tokens, completion_tokens):
price = PRICES.get(model)
if not price:
raise ValueError(f"No price for {model}")
return price["input"] * prompt_tokens + price["output"] * completion_tokens
Cache hits complicate this. If your provider honors cache-control hints and returns prompt_tokens_details.cached_tokens, price those at the discounted rate (often 50% of input). Extend the price table with a cached key and subtract cached tokens from billed input.
Step 3: Build a clean daily cost series
Collapse the per-model rows into a single daily total cost. Use pandas to handle missing days—a silent outage or zero-traffic Sunday should show as $0, not a gap that breaks your forecaster.
import pandas as pd
records = []
for model, day, pt, ct in rows:
records.append({"model": model, "day": day, "cost": cost_for(model, pt, ct)})
df = pd.DataFrame(records)
daily = df.groupby("day")["cost"].sum()
daily.index = pd.to_datetime(daily.index)
daily = daily.asfreq("D", fill_value=0.0)
You now have a Series indexed by date with a continuous daily spend history. This is the series you will forecast against.
Step 4: Choose a forecasting model
For most teams, forecasting monthly LLM spend doesn’t require a PhD in econometrics. If your product usage grows steadily, ordinary least squares on the day index works:
import numpy as np
x = np.arange(len(daily))
slope, intercept = np.polyfit(x, daily.values, 1)
trend_forecast = intercept + slope * x
If you see weekly seasonality—lower weekends, spikes on Monday—use additive Holt-Winters:
from statsmodels.tsa.holtwinters import ExponentialSmoothing
model = ExponentialSmoothing(
daily, trend="add", seasonal="add", seasonal_periods=7
).fit()
Avoid throwing a transformer at this. You have at most 30 points per month. A simple, interpretable model beats a black box when the CFO asks why the projection moved.
Step 5: Project the remaining days of the month
Forecasting monthly LLM spend means combining actuals already incurred with a projection of the rest of the month. Slice the series up to today, forecast the remaining calendar days, and sum.
from datetime import date
def project_month(daily_series, model, today=None):
today = today or date.today()
days_in_month = 30 # or use calendar.monthrange
mtd_mask = daily_series.index.day <= today.day
month_to_date = daily_series[mtd_mask].sum()
remaining = days_in_month - today.day
if remaining <= 0:
return month_to_date
forecast = model.forecast(remaining)
return month_to_date + forecast.sum()
projected = project_month(daily, model)
Run this daily. The number will converge toward the real bill as the month closes, but early in the month it gives finance a planning figure instead of a guess.
Step 6: Quantify uncertainty and set alerts
A point estimate is not enough. Holt-Winters exposes prediction intervals via model.get_prediction().summary_frame(), or you can bootstrap by resampling residuals.
resid = daily - model.fittedvalues
boot = []
for _ in range(1000):
sample = np.random.choice(resid.dropna(), size=len(daily), replace=True)
boot.append(daily + sample)
Compute the 90th percentile of projected totals and compare against your approved budget. If the upper bound exceeds budget by 20%, fire a alert to Slack. That threshold is opinionated but sane: it catches a real overrun without paging you for normal variance.
Step 7: Automate and backtest
Wrap steps 1–6 in a script and run it on a cron job at 01:00 UTC. Store the daily projection in a small table so you can plot the forecast trajectory over the month.
0 1 * * * /usr/bin/python3 /opt/forecast_llm_spend.py >> /var/log/forecast.log 2>&1
How to verify success
Backtest before you trust it. Take three closed months from your logs. For each day d in those months, pretend it’s the current day and run your pipeline on data up to d. Compare the projected month-end spend against the actual billed amount.
Track mean absolute percentage error (MAPE):
errors = []
for month in closed_months:
actual = month["actual_total"]
for day in range(1, 28):
pred = project_month(month["daily_up_to_day"], model, fake_today=day)
errors.append(abs(pred - actual) / actual)
mape = sum(errors) / len(errors)
If MAPE is under 15% for the last three months, the pipeline is solid. If not, your traffic is too spiky for naive smoothing—move to a model that ingests product signals (active users, feature flags) as exogenous regressors.
Accurate forecasting monthly LLM spend is a feedback loop, not a one-shot script. Revisit the price table when providers send pricing emails, and revisit the model when a new product line changes your seasonality. The engineers who survive LLM cost spikes are the ones who saw them in the projection two weeks earlier.