DEV Community

Cover image for How much does it cost to run a text game with LLMs

How much does it cost to run a text game with LLMs

Aliaksei Zelianouski on November 08, 2026

AI costs are confusing. Providers give you a lot of numbers and nothing to actually know how much you are going to pay in the long run. There is no...
Collapse
 
pengeszikra profile image
Peter Vivo • • Edited

Hi Aliaksei! Thank you to share your detailed AI cost and experience those information is very valuable for me. My game building idea is bit different. My game just use a partial AI instead of core mechanic. I don't register player, just let them play it. Maybe take a look:

Collapse
 
hiper2d profile image
Aliaksei Zelianouski • • Edited

Nice work. However, our games are quite different. Your's doesn't seem to have any AI making decisions or actually playing. Which is probably a smarter use of AI. When I started mine, I haven't though about budgets and tokens - I just wanted to build it for myself. I only realized how expensive it is to run after the shipment.

I have to register users to track and limit the spendings. The fact than I only take their emails creates a lot of possibilities for the players to trick me. People create multiple accounts, use VPN + incognito mode to bypass my checks. It would be easier if I asked more info from the players, but try to stay aways from this as much as I can.

Collapse
 
tokenlat profile image
TokenLat •

The 1.54x gap is the most useful number in the post, and not because of the pricing bug. Token counts matched, the bill didn't, and the error lived in the code that interpreted the usage fields. That's a different failure class from "the model cost more than expected" — the measurement layer was the thing that drifted.

It connects to "collecting cost metadata from responses is the only way". Those fields give you a quantity, not a location: cache-read tokens tell you how much was billed at the cached rate, never which prefix or which step failed to hit. A provider caching more aggressively on its side and a client re-sending a more stable prefix produce the same number from your side, and need different fixes. So the usage record is necessary for the bill and insufficient for attribution — the same split that made the 1-hour cache result legible only after the writes were priced correctly.

What I'd add to the monthly reconciliation is a per-run split of cache writes, cache reads and uncached input, recorded where the prompt is assembled. Then a wrong rate is a visible line rather than a surprise at the end of the month, and "did the cache help" becomes a question about a specific prefix instead of an average over many calls.

After you fixed the write price, did you start recording writes and reads as separate lines, or is it still one input total with a cached fraction?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Yes, responses usually tell you how my cached tokens you hit. I write this down. It doesn't tell the cost of those tokens - I have to map the quantity on the price rates from my config. And hope they are up to date and the code has no bugs.

Collapse
 
tokenlat profile image
TokenLat •

That gap is worth separating, because the two errors sitting on top of each other behave differently. A stale rate table or a bug in the pricing code is a valuation error: it moves every token in the same direction, and one reconciliation pass can correct it in bulk. The usage field reporting a quantity without a location is an attribution error: no rate table, however current, tells you which step consumed the cached prefix or which inputs belonged to it. When both are present, a reconciliation gap gets read as a pricing bug and quietly hides the attribution hole.

A cheap way to tell them apart is to write rate_version and a cache key or prefix hash, plus the step identifier, into the same usage event as the quantities. Then replay yesterday's ledger against a newer rate version: if the delta is a stable ratio across runs, you are looking at valuation; if it drifts by run or by prefix, the missing thing is attribution. This is the same discipline as the call ledger you already keep — it just makes the config a versioned input to the replay instead of a file you hope is current.

It also reframes what cache_read is missing. The problem is not that the response omits a currency amount; converting a token count with a price list is mechanical. The problem is that the denominator for "how much of this run reused that prefix" was never recorded, so the count cannot be tied back to a node, and a clean cache-hit number and a growing bill stay perfectly compatible.

This does not make provider-side caching strategies any more transparent, and it does not lower the bill. It only turns a single unexplained variance into two items that can be closed separately. If you replayed yesterday's calls with the corrected rate table today, would the difference come out as a constant ratio, or would it move from run to run — and can the current log answer that without a new capture?

Collapse
 
sreno77 profile image
Scott Reno •

This is very interesting. Are you trending toward having enough paid users to cover your free users?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

I am not, I have zero paying users. Someone tried recently, but Stripe rejected a credit card from Russia. I added crypto payments the same day, but the person never returned.

My plan was to get users, create a community, and only then start thinking of the monetization. I can always shrink the free tier features and limits but I haven't yet reached the level os spendings where I really have to do that.

Collapse
 
onelifevibe profile image
OneLifeVibe •

The cache result that surprised me most was the 1-hour cache costing more — 3.13¢ a turn versus 2.94¢ with no cache at all — because the write premium only pays off if the cached tokens actually get read back. Pairing that with the structured-output finding, that votes and night actions barely hit cache because the JSON schema invalidates it for OpenAI, Google, Mistral and Meta, makes this feel like a field guide rather than a price list. The $6.36-a-day headline for six daily users is remarkably concrete too — did the Sonnet-versus-Haiku gap, 17x the cost per turn at the same speed, change which models you let into free games?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Yeah, it did. In the very beginning, I just select models randomly for each game. A player can always change that if they like. Now, the random selection allocate more sits to cheaper models and a player is limited in selecting expensive model combinations. Sonnet is too expensive for a free tier - even one model a game pushed the price up by a lot.

Collapse
 
aiapikey profile image
于侃 •

That schema result has a mechanical cause, and it is fixable. Schemas usually land after the variable part - tool definitions appended last, or the schema string pinned to the tail - so the shared prefix ends before them and every request forks at the same point.

Put the schema and the static instructions at the very front, keep per-request state and turn counters strictly at the tail: on the structured-output loads I have logged, prefix hit rate went up 40-70 points.

The write premium is the same effect read off the invoice - you paid to write a prefix that was never read twice.

Collapse
 
itsvicmoreno profile image
Vic Moreno •

The $5 daily cap per free user is the line I'd look at first: on paper 651 users can burn about $3,255 a day, so the real bill is entirely the gap between the cap and actual usage. Since every call already writes cost to the stats table, the share of users who ever hit the cap would show whether $5 is a guard or a target. What does that share look like after 53 days?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

I checked. 247 free users spent anything in those 53 days, and 6 of them reached $5 on some day. That's 8 user-days out of 335. The median user's worst day is $0.28.

So it's a guard. Most people play one game and leave, and the $3,255 only exists on paper.

Collapse
 
itsvicmoreno profile image
Vic Moreno •

8 user-days out of 335 settles it: the cap is insurance, not a cost line. The case I'd still watch is one person scripting fresh free accounts, and a limit on new accounts per device or IP covers that without touching your 6 heavy players. Did any of those 6 end up paying?

Collapse
 
richard_verifies profile image
Richard •

Your $6-7/day answer and the $20 abuse day point the same way: spend tracks accounts, not retained users. So $5/day is a ceiling per account, and there is no ceiling on how many accounts show up. What stays fixed while that grows is a global daily budget, with the bands you already have doing the degrading once it is crossed (free games down to the <=0.3c band), instead of a per-user cap that only binds for one honest player. Is there a global ceiling anywhere in the setup, or is $5/user the only one?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

There is a global one, $40 a day across all free users. I added it in September after one person ran four accounts and moved to the next one nine minutes after the cap stopped the first. There is also a $5 cap per browser, shared by every account signed in from it. Incognito beats that one, so the global cap is the only one that really holds.

It has never fired. My worst day so far was about $16. When it does, free games just stop until the next day. Dropping everyone to the cheapest band sounds better than that, I might steal it.

Collapse
 
richard_verifies profile image
Richard •

A global cap that never fired is still doing work: it is the only thing making the abuse path expensive to repeat. But it pays out after the day's spend is gone, so it can end a wave, not stop one.

Your four-account story is the hint. That signature is a rate, not an amount: the next account nine minutes after the cap hit. One counter split out of your stats table, spend by accounts created in the last 24h, makes the fuse tighten before it trips instead of after.

If you do steal the band-down, trigger it on a share of the $40, not on $40. 8 user-days over $5 out of 335 says an honest player's worst day is nowhere near the ceiling, so half of it never touches a real player.

If the cache-hit side is ever worth an outside pair of eyes: that is the work I do. Your logged rows and one bill in, a ranked list of which counters are lying. $60, one week of data.

Collapse
 
maxbuildsdev profile image
Max Kaya •

The 1-hour cache section hurts because it's so easy to walk into. With a chatty session-style loop the median gap between calls decides everything, and the 5-minute default was already borderline before the write premium got involved.

One question on the "every switch to a JSON turn cleared the cache" bit — is that because the serialized turn contains volatile fields (ids, timestamps) so the prefix diverges? If so, moving all the volatile fields to the end of the serialized block and freezing everything before it keeps the cached prefix stable even when the turn changes; that helped in a similar setup we ran.

Also curious: was the stats-pricing bug (cache writes priced at the normal rate) in your own metering layer, or were you reading cost from the response usage field? That distinction seems to decide whether you catch it in your own dashboard or only on the invoice.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

I really don't see the reason to clear the cache when switching to the JSON mode mid-session. Chinese models don't do that. My case is probably rare, so nobody cares. It's just something to know about. I used to use JSON mode for everything but then I noticed that it takes longer - somehow the same input with the plain text response type can be easier for morels.

No provider hands us a dollar amount. I read the token breakdown from the response and then apply the costs from the config. So yeah, I had lots of bugs in this logic. Prices can be very tricky to follow. The cache storage price is a black box, responses don't reveal it.

Collapse
 
paullovelace profile image
Paul Lovelace •

The 1-hour cache costing more than no cache is the line that stuck with me — 3.13¢ vs 2.94¢ a turn, and the write premium only pays off if the prefix actually gets read back. I keep coming back to the free-tier math though: $5 a day per user, 651 free users, zero paying. How close have you actually come to the 'oh no' number, or does the alert just never fire because retention is that low?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

The math is simpler if to look at the active or daily users only. It's $6-7 a day on average. The worst days I had were not because of sudden spikes but because of abusers - some players created multi-accounts and burnt me $20/day. But it's rare.

Collapse
 
devenquan profile image
Zhang Enquan •

The 51,000-call ledger is the right way to do this, and the Claude 1.54x find is the most useful paragraph here — most people never reconcile against the billing dashboard at all.

One push on the diagnosis. DeepSeek at 9% under and Gemini at 16–32% under are the same shape, but only Gemini gets the "maybe other projects on the same key" explanation. If a shared key really explains that drift, it should show up as extra calls too, not just extra tokens — did the call counts reconcile? A cheaper way to separate the causes: take one fixed payload, fire it N times inside a short window, sum every usage field, and compare against the billing delta for that window only. That removes the monthly-aggregate noise and tells you whether the gap is a pricing tier (like your 1-hour cache write bug) or the provider's own usage reporting.

On the opening claim that you can't know how many tokens you'll spend: true for output and reasoning tokens, much less true for input. Your role cards and game state are structured payloads you control. We measured ceil(chars/4) against real cl100k_base counts on 10,000 prompts — batch error only +5.08%, but 40.5% of individual rows off by more than 20%, and structured content under-estimated 21.1% of the time, which is the direction that makes the bill higher than the estimate. The input side is predictable; it's the heuristic that fails.

From your own numbers: summaries are 11% of turns but 21% of spend, with a 7% cache hit. Did you try one shared day summary that all bots read, instead of each bot rewriting its own notes? That looks like the biggest lever left.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

It cannot be one shared summary because bots have their personal reasoning to summarize. Inputs are still unpredictable because you never know how much cache you are going to hit. You don't know how much tokens each model produce from the same text. Tests on fixed payloads are good and I have them, but they don't provide the information at scale.

Collapse
 
ascendra_ventures_92c21f2 profile image
Ascendra Ventures •

The 1-hour cache costing 3.13 cents a turn against 2.94 with no cache at all is the kind of result nobody finds from a pricing page. The write premium only pays back if the prefix gets read again, and a chatty game with gaps between turns is exactly where it does not.

Logging tokens, cache hits, cost, and time for every call is the real lesson here. Cost per turn, and ideally cost per finished game as someone suggested, is a number you can make decisions on. Per million tokens is not, once reasoning tokens and cache behavior differ by model.

At Ascendra Ventures we ask for that kind of per-call log before agreeing a fixed price on any AI feature, because the running cost often decides the design more than the build does. Your Sonnet versus Haiku gap, 17 times the cost for 0.6 seconds of latency, is a good example.

Has the data changed which models you offer on the free tier by default, or only the weighting of the random pick?

Collapse
 
aifrontierpost profile image
AI Frontier Post •

Sonnet 5.5 costs 17 times Haiku per turn, 3.10 cents versus 0.18, for a median reply only 0.6 seconds faster. At that gap, the expensive model only makes sense if the extra quality shows up in the game, not the latency.

Collapse
 
gautamtalksdev profile image
Gautam K •

Collecting cost metadata from the responses is the only honest way to compare, since reasoning tokens make the price per token almost meaningless on its own. 51,000 calls from a real game is a far better sample than a benchmark. Did periodic compaction change the cost per game much, or does caching do most of the work there?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

The compaction is needed to keep the context size under control. It's an expensive but necessary step. Models reason a lot during it - this is why it's expensive. The caching does some work, but it's very tricky and it gets cleared too often without my intent. Different providers have different caching strategies and efficiency.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

@hiper2d Agreed for a welcome message. The cost is in long sessions, where the whole context goes out again on every turn: Tokenade cut session cost 39% on average in the THOL benchmark (2026-09-14, raw data public).

Collapse
 
asiaostrich profile image
AsiaOstrich • • Edited

We have a multi-turn interview agent. It sends the last 10 messages verbatim, sends older ones in a shortened form, and packs the entire history into a single message each turn.

Over a 32-turn interview, 535K tokens were written to the cache but only 334K read from it. That's the same direction as your Sonnet numbers: more written than read.

Our current suspicion: the boundary between the verbatim and shortened parts moves forward every turn, and since the history is sent as a single message, that whole message after the system prompt is different each time. The cache still hits the system prompt itself (which is why reads aren't zero), but once the prefix match breaks at that message, everything after it has to be written again. On top of that, we swap the system prompt when the interview moves to a new topic, which causes another cache reset. This is still our guess, though; we haven't fully isolated it yet.

One question for you: after you compact the daily summaries, does the start of the next day's first discussion prompt change completely as well? What's the cache hit rate on that turn?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

For sure, the sliding window approach nukes the cache. It only works while the messages append to the existing context.

I structure bots prompts this way: static system prompt (games rules, a bot's personal info), summaries, day discussion. When a bot summarizes, it replaces the day discussion with a new summary entry which is then gets appended to the older summaries. So, at least a part of the context survives and should be read from the cache. In practice, it's really hard to check.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

Reasoning tokens bill as output, so in a long game that hidden line can outweigh the visible text.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Yes, outweighs by a lot. By default, many models tend to reason to the roof. 1000-4000 tokens on a simple question easily. I had to tune this down by lowering the reasoning effort and output budget in pretty much every Chinese model. It's nice when models support adaptive reasoning - this usually keep the output tokens tight on simple text questions.

Collapse
 
aiapikey profile image
于侃 •

Worth measuring the ratio rather than the absolute: reasoning tokens as a share of billed output. In multi-turn game loops I have seen 40-60% once the model starts planning moves, and the share grows with turn count because the visible reply stays short while the deliberation does not. Two cheap checks: log reasoning_tokens separately from output_tokens, and cap it per turn - an uncapped reasoner in a 20-turn session can spend more thinking about a move than the move itself costs.****

Collapse
 
lin7c profile image
xcl •

The cache result that runs counter to intuition is the 1-hour cache costing 3.13 cents per turn against 2.94 cents with no cache at all. The explanation that the longer cache only pays off when most of what is written gets read back, combined with the write premium being priced at the normal rate in the tracking code, makes the inversion understandable, but it raises a follow-up: after fixing the write pricing, did the 1-hour cache become cheaper than no cache, or did the low read-back rate still leave it ahead on cost? A related detail is the end-of-day summary: 11% of turns with only a 7% cache hit, where injected per-day data breaks the prefix. Would moving that injected data to the end of the prompt, or splitting the summary into a stable instruction plus a short variable block, recover much of that hit rate without changing the summary quality?

Collapse
 
kartik-nvjk profile image
Kartik N V J K •

I clearly need to start reconciling logged cost against the actual provider balance. If my code had billed cache writes at the normal rate like yours did, nothing downstream would have flagged the 1.5x gap. The 1-hour cache costing more than no cache at all, 3.13 versus 2.94 cents, with 48% of the input written at double price and only 42% ever read back, is the table I'm saving.

Collapse
 
mudassirworks profile image
Mudassir Khan •

51k calls out of your own wallet is the right dataset, provider price pages tell you nothing about reasoning tokens. the number i would want next is cost per finished game, split by whether the model burned hidden reasoning tokens on the periodic compactions. in agent stuff i have shipped the compaction step quietly ended up the most expensive call because it defaulted to the biggest model, and moving it to a cheaper one cut the bill more than any prompt trimming did. did you test a cheap model just for compactions, or does quality fall off there?

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

I haven't tried. Each bot writes its own summary with its own model, because these are the bot's private notes: who it suspects, what it plans for tomorrow. If a cheaper model writes them, the bot plays the next day from someone else's reading of the game.

The summary is expensive because it's a big input with almost no cache hit and a lot of reasoning on top. The cache part is my bug, so I'll fix that first and see what is left.

Collapse
 
micah_spiegel profile image
Micah Spiegel •

Hey, this might be a dumb question, but is there a way you could use local LLMs or use Claude Pro so you don't need to call an API?

Collapse
 
dlife4uuuu profile image
Life • • Edited

it's not dumb question... as long as it is web based but there are still limitations for now to what api can help you achieve

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

No, local models won't work here. It's a web-site hosted on a remove server, it has no access to any local models.

If you only want to play it yourself and run the game locally, then it can reach a local model on your PC. But each bot needs a separate LLM, so it will be challenging. Claude subscription could work though. But I won't be able to serve it to the users this way.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

@hiper2d Same experience: effort had to be tuned by hand, model by model. THOL, 2026-09-14: 39% lower session cost on average on long Claude Code runs, raw data public.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Higher reasoning in coding usually makes sense. Although, yeah - it depends. In my case, it didn't make sense to reason on high for a simple welcome message to the chat.

Collapse
 
kling4ai-production profile image
Kling4 AI •

Great writeup and practical takeaways. Really appreciate how you broke down the actual LLM game costs and latency trade-offs here—super helpful context for handling this in production.

Collapse
 
spritefuse profile image
Chaowen Tan •

images being 28% of the bill surprised me. great breakdown

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Yeah. I didn't have any images in the early version - I though it's expensive. And I was right.

Collapse
 
aiapikey profile image
于侃 •

The number to pin down is cost per playable turn, not cost per 1M tokens. A text game is mostly fixed system prompt plus world state, so a 4k-token prompt at $0.10/M costs $0.0004 per turn before the model writes a word. At 60 turns an hour that is $0.024/hour of pure prompt repetition - which dwarfs the output cost and is where prefix caching actually pays. Two things I would measure separately: cost of a turn the player accepts versus cost including retries, and what fraction of the prompt is a cacheable prefix. In my experience rejections eat 20-40% of the budget while looking like model cost.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

I don't have any rejections. A user doesn't have an option not accept. Some request might fail with a timeout, there are some rare failures on the provider's end, certain images instructions can be locked by Google, but overall, rejected calls are not that significant cost-wise

Collapse
 
aiapikey profile image
于侃 •

Fair point - if there is no rejection path, my 20-40% figure does not apply here. The residual line I would still watch is requests that are billed but produce nothing usable: a timeout on the provider side or a policy block that fires after the prompt was already tokenized can still appear on the invoice. Called-out as rare, but on a long session it is a few percent, and without a billed-but-failed counter it hides inside the per-turn average and reads as model price. That counter is cheap: one integer per turn, next to the token count you already log.

Collapse
 
alexshev profile image
Alex Shev •

The per-turn bands are much more actionable than provider rate cards because they absorb context growth, caching, and reasoning. I would also watch the p95 cost per completed game: a rare runaway summary or retry loop can be invisible in an average while it decides whether a free tier is sustainable.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski •

Yeah, makes sense. Although, a completed game might be too abstract. Games have different duration, different amount of players, there are some replay/retry options in them. But I do track this. Or I can calculate this on the fly from my stats if I need.

Free tier is not sustainable - if I get higher number of active players, I'll have to shrink it. But right now, I have some room in it.

Collapse
 
devkursat profile image
Kürşat Yılmaz •

The obsession with wrapping remote APIs into multi-layered agent abstractions is creating massive hidden latency and failure cascades in production.

If you profile real-world workloads, bare-metal local inference (via direct C++ / llama.cpp or vLLM bindings) consistently beats fragile cloud wrappers on both predictability and cost. Tooling should eliminate abstraction penalties, not compound them.

Collapse
 
aiapikey profile image
于侃 •

Agreed on the abstraction penalty, but it is worth quantifying because the number changes the decision: a well-run proxy hop adds 5-15 ms and near-zero failure rate, so the wrapper itself is rarely the problem - unretried flapping and cross-region egress are. Local clearly wins when the model fits your GPU and you own the queue. A wrapper wins when the model you need is behind an endpoint you cannot host - which is still true for most Chinese frontier models - and then the honest comparison is wrapper latency versus not having the capability at all.

Collapse
 
hiper2d profile image
Aliaksei Zelianouski • • Edited

Bare-metal inference on what hardware? I don't have a personal datacenter with a bunch of B300. And having only open-weight models will kill all the fun.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
aiapikey profile image
于侃 •

That inversion is the tell. Cache writes are usually charged at a premium and reads at a discount, so break-even is per prefix lifetime, not per turn - you need roughly two reads of the same prefix before caching beats not caching at all. Losing 6% (3.13 vs 2.94 cents) usually means either the prefixes are too short-lived to be reused, or the hit rate sits under ~60%. Logging prefix id plus hit/miss per turn is what turns that from a bet on reuse into a number you can act on.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

@aiapikey Agreed on the ratio. The absolute number hides the trend: the share climbs with turn count because the visible reply stays short while the deliberation does not.

Collapse
 
aibigbro profile image
Luokaiyuan •

Looking for long-term stable upstream sources (Azure GPT East US2 T5, Claude Opus, Gemini full series). Prefer first-hand high-volume. DM if you have.