LLM cost audit of roomGPT
- Call sites
- 1
- Cost findings
- 3
- Warnings
- 2
- Info
- 0
Estimated exposure
No call site names a model literal, so no repo-wide figure is given. Per-finding estimates below state the reference model they assume.
Findings ranked by severity
- Cost
A billable async job is polled to completion with no deadline and no cancel path — if the poller dies the job keeps billing with nobody reading the result.
app/generate/route.ts:69ruleorphaned-async-jobconfidence mediumEstimate full job cost per abandoned run, ×2 when the user retries — every timeout is a double charge
Generation jobs are billed by the provider from the moment they are submitted, for the whole run. If this poll loop is killed (serverless timeout, client disconnect, restart) the job is never cancelled: you pay in full for a result nobody receives, and the user retries and pays again.
Fix: Bound the wait with a deadline, and on timeout or client abort call the provider's cancel endpoint before returning. Better: use the provider's wait/webhook mode so there is no poll loop to abandon.
Backed by Replicate / OpenAI / Azure async job APIs (primary provider docs) (replicate.com): Async generation jobs bill for the full run once submitted, independent of whether the result is ever fetched; providers expose an explicit cancel endpoint precisely so an abandoned job can be stopped.
- Cost
No dollar/token budget guard or circuit breaker found anywhere; LLM spend is unbounded.
app/generate/route.ts:40ruleno-budget-guardconfidence highEstimate unbounded — no cap on the default path
A runaway loop, retry storm, or traffic spike keeps calling the API with nothing to halt spend. 1 LLM call site(s) are exposed.
Fix: Add a per-run token/dollar budget that halts LLM calls when exceeded.
Backed by When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents (arXiv:2607.01641): Cost exhaustion is a core agentic-loop harm; a spend guard is the backstop.
- Cost
The spend/abuse guard is constructed only when its env vars are set, and the request path skips it when they are not — the default deployment is unguarded.
app/generate/route.ts:8ruleguard-conditional-on-envconfidence mediumEstimate unbounded whenever the guard's env vars are unset — the default path has no ceiling
Whether anything limits spend depends on deployment configuration, not on this code. If the env vars are missing (a fresh deploy, a preview environment, a fork), every request reaches the paid call with no quota, which is a Denial-of-Wallet opening on a public endpoint.
Fix: Fail closed: in production, refuse to serve (503) when the limiter cannot be constructed, rather than silently skipping it. Add the guard's env vars to the documented deploy configuration so the default deployment is protected.
Backed by OWASP LLM10:2025 Unbounded Consumption / Denial of Wallet (genai.owasp.org): A per-user quota that is absent in the default configuration provides no protection: unbounded consumption is governed by the deployed configuration, not by the presence of guard code.
- Warning
Request-body fields reach a paid generation call with no schema or allowlist validation in the handler.
app/generate/route.ts:40ruleunvalidated-input-to-paid-callconfidence mediumEstimate one full billed generation per abusive request — volume bounded only by the attacker
Without a check on what the body may contain, the endpoint serves any workload a caller submits, not just your product's. Every abusive request is a full billed generation, and the volume is set by the attacker rather than by your users.
Fix: Validate the body before the paid call: check each field against its allowed set (or parse with a schema), and reject early — ideally before consuming any rate-limit quota.
Backed by OWASP LLM10:2025 Unbounded Consumption / Denial of Wallet (genai.owasp.org): Unrestricted user input to a model endpoint enables resource-consumption abuse: attackers drive paid inference with inputs the application never intended to serve.
- Warning
A deterministic image call is served from a request handler with no result cache — identical inputs are recomputed and re-billed.
app/generate/route.ts:40ruleresult-cache-candidateconfidence mediumEstimate the full price of the call is paid again for every duplicate request
The same input returns the same output, so every repeat (a retry, a refresh, two users asking for the same thing) is a second full charge for a result you already paid for.
Fix: Key a cache on a hash of the inputs that determine the output and read it before calling the provider; store the result with a TTL. A cache store already present in the repo is usually enough.
Backed by GPTCache: An Open-Source Semantic Cache for LLM Applications (Bang, 2023) (aclanthology.org): Caching model responses keyed on input similarity removes redundant paid calls; repeated or near-identical requests are a large fraction of production traffic.
Spend map where the calls live, ranked by estimated cost per 1k calls
| File | Sites | Models | Runs in | Findings | Est. per 1k calls |
|---|---|---|---|---|---|
| app/generate/route.ts | 1 | unresolved | request handler | 5 | not priced |
Per-1k figures use the same token assumption as the exposure above; a file whose model is not named in code is not priced.
Models usage and right-sizing candidates
| Model | Sites | Est. per call | Est. per 1k calls, all sites |
|---|---|---|---|
| (not statically resolvable) | 1 | not priced | not priced |
No model was flagged as larger than its task needs.
Research behind this report
- Replicate / OpenAI / Azure async job APIs (primary provider docs) (replicate.com): Async generation jobs bill for the full run once submitted, independent of whether the result is ever fetched; providers expose an explicit cancel endpoint precisely so an abandoned job can be stopped.Rules:
orphaned-async-job - When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents (arXiv:2607.01641): Cost exhaustion is a core agentic-loop harm; a spend guard is the backstop.Rules:
no-budget-guard - OWASP LLM10:2025 Unbounded Consumption / Denial of Wallet (genai.owasp.org): A per-user quota that is absent in the default configuration provides no protection: unbounded consumption is governed by the deployed configuration, not by the presence of guard code.Rules:
guard-conditional-on-env,unvalidated-input-to-paid-call - GPTCache: An Open-Source Semantic Cache for LLM Applications (Bang, 2023) (aclanthology.org): Caching model responses keyed on input similarity removes redundant paid calls; repeated or near-identical requests are a large fraction of production traffic.Rules:
result-cache-candidate