ai-meter
PG-atomic reserve/reconcile token metering for LLM calls, per-tenant spend caps, a circuit breaker, and a MinHash dedup gate. Integer credits only, no floats.
@caisson-sh/ai-meter is the money path for metered AI inference: estimate a call's cost
before it runs, reserve that amount up front, then true the charge to the provider's
actual usage once the call completes. Built on the integer credit ledger, so a charge is
never a float and never drifts.
What it does
- Estimate → reserve → reconcile.
reserve()prices a call from a versioned, per-provider/modelprice book and debits the wallet before the provider is ever called, a short wallet or an open circuit breaker fails the call with no spend and no provider round-trip.reconcile()trues the reservation to the provider's reported usage: refunds an over-reservation, charges a shortfall, or leaves the ledger untouched when the estimate was exact. - A per-tenant spend window + soft/hard caps. Every reserve bumps an atomic running-spend counter for the tenant's current window (day/month/etc.); crossing a soft cap warns, crossing a hard cap trips a circuit breaker so every subsequent call fails closed until an operator resets it.
- A bundled, overridable price book. Ships default per-million-token rates for common
provider/model pairs; you can override the whole book or the credit denomination.
resolvePriceEntrythrows on an unrecognized provider/model instead of metering at zero. - Idempotent by construction. Both
reserve()andreconcile()key off the caller'scallId: a retried call settles exactly once instead of double-charging. - A pre-call dedup gate.
checkDedupGate()flags a prompt that's near-identical to one already in flight (an agent loop rewording a retry, a user re-asking the same question) before the price book ever prices it. Detection only, it never auto-skips a call or moves a credit itself.
Quickstart
import { withTenant } from "@caisson-sh/tenancy-rls";
import { reserve, reconcile } from "@caisson-sh/ai-meter";
await withTenant(db, accountId, async (tx) => {
const reserved = await reserve(tx, {
accountId,
callId,
provider: "openai",
model: "gpt-4o-mini",
lane: "default",
messages: [{ role: "user", content: "hello" }],
});
// ... call the provider, using reserved.reservedCredits to size the request ...
await reconcile(tx, {
accountId,
callId,
provider: "openai",
model: "gpt-4o-mini",
lane: "default",
reservedCredits: reserved.reservedCredits,
usage: { inputTokens: 12, outputTokens: 40, cachedInputTokens: 0 },
windowKey: reserved.windowKey,
});
});Circuit breaker
assertBreakerClosed runs before every reserve(): an open breaker throws
SpendCapError (402) with no provider call made. A crossed hard cap trips it
(tripBreaker); it stays open until an operator calls resetBreaker, so a runaway loop
can't spend past the cap on the next retry.
import { assertBreakerClosed, resetBreaker } from "@caisson-sh/ai-meter";
await assertBreakerClosed(tx, accountId, "default"); // throws SpendCapError if open
// ... after investigating a tripped breaker ...
await resetBreaker(tx, accountId, "default");Dedup-before-meter gate
checkDedupGate runs a dependency-free MinHash/LSH similarity check against recent calls
in the same account and scope, ahead of the price-book estimate, a Jaccard similarity
above 0.92 returns duplicate-of so the caller can choose to skip or reuse the earlier
result.
Configuration
parsePriceBook and parseCreditConversion validate an operator-supplied price book or
credit denomination before it replaces BUNDLED_PRICE_BOOK / CREDIT_CONVERSION: an
invalid override fails closed rather than metering silently at zero.
Composing with the base
ai-meter is a base primitive: it never depends "up" on a higher-layer module. It runs on the
same Postgres-atomic accounting as @caisson-sh/credits and is scoped per tenant through
@caisson-sh/tenancy-rls's withTenant. The AI Production Kit's inference gateway composes
reserve()/reconcile() around the provider call site; ai-meter itself never talks to
a provider.
Standalone or in AI-Production
ai-meter works standalone on the open base, or as one of the modules composing the
AI-Production family alongside guardrails and the prompt registry.
ai-kit
The AI Production Kit — one metered gateway for language inference, streaming, and embeddings, enforcing reserve-before-provider-call accounting behind Vercel AI SDK v7.
Governed agent loop
Run a bounded tool loop with runToolLoop, where every model and tool step reserves credits before it executes, a gated tool parks the run for an operator decision, and an approval resumes it from durable state.