İçeriğe geç

Örnek: LLM gateway (SQLite)

examples/llm-gateway: SQLite üzerinde bir LLM gateway. Postpaid; commit bakiyeye bakmaz.

Terminal window
bunx vitest run examples/llm-gateway
examples/llm-gateway/src/gateway.ts
/**
* An LLM gateway on SQLite.
*
* - Tokens are priced per million with `cumulative` rounding, so a month of tiny calls never drifts.
* - Calls are buffered per (account, model) and flushed once a minute as one `observe` (ref type "batch").
* - Output tokens feed a credits pool with a per-model weight; credits are priced by volume with `on_crossing`.
* - Every account gets its first 1M input tokens per model free, for life (usedSoFar over all time).
* - A CRM discount from the context is applied in getRate. Postpaid: commit never checks balances.
*/
examples/llm-gateway/src/gateway.ts
const perMillion = (price: number, id: string): Rate => ({
id,
model: "graduated",
tiers: [
{ from: 0, unitPriceMicroUsd: 0, per: 1_000_000 },
{ from: LIFETIME_FREE_INPUT, unitPriceMicroUsd: price, per: 1_000_000 },
],
policy: { rounding: "cumulative", tierPeriod: "lifetime" },
});
/** Credits: $0.02 per 1k credits, $0.015 once the month passes 10M credits — for the whole month. */
export const CREDIT_RATE: Rate = {
id: "credits-2026",
model: "volume",
tiers: [
{ from: 0, unitPriceMicroUsd: 20_000, per: 1000 },
{ from: 10_000_000, unitPriceMicroUsd: 15_000, per: 1000 },
],
policy: { adjustmentTiming: "on_crossing", rounding: "cumulative", tierPeriod: "month" },
};
  • Girdi token’ı milyon başına fiyatlanır (per: 1_000_000) ve cumulative yuvarlanır: tek tek yukarı yuvarlansa her küçük çağrı en az 1 mikro-USD olurdu; kümülatif yuvarlama ayın toplamını kesin tutar (B2).
  • İlk 1M girdi token’ı model başına ömür boyu ücretsizdir: usedSoFar tüm zamanlar üzerinden sayılır (tierPeriod: "lifetime", B16).
  • Çıktı token’ı yalnızca sayılır (0 fiyat) ve kredi havuzunu model ağırlığıyla besler (opus 5, sonnet 3, haiku 1). Para krediler üzerinden akar.
  • Krediler volume + on_crossing ile fiyatlanır: ay 10M krediyi geçince ayın tamamı ucuz kademeden hesaplanır, önceki kredilerin farkı ayrı bir …:adj kalemidir (B6).
examples/llm-gateway/src/gateway.ts
const metering = buildMetering()
.context(Context)
.refs(["request", "batch"])
.meter("llm/credits")
.meter("llm/input", { model: z.enum(MODELS) })
.meter("llm/output", { model: z.enum(MODELS) }, { feeds: { "llm/credits": (d) => CREDIT_WEIGHT[d.model] } })
.getRate(async (meter, dims, ctx, at) => {
const discount = (r: Rate) => (ctx.discountPct ? applyDiscount({ rate: r, percent: ctx.discountPct }) : r);
if (meter === "llm/input") {
// lifetime quota: the counter spans all time
const usedSoFar = store.usedSoFar({ account: ctx.accountId, meter, dims });
return { rate: discount(perMillion(INPUT_PRICE[dims.model], `input-${dims.model}`)), usedSoFar };
}
if (meter === "llm/output") {
// output tokens only count; money flows through credits
return { rate: { model: "graduated", tiers: [{ from: 0, unitPriceMicroUsd: 0 }] } };
}
const { from, to } = monthWindow({ at });
return { rate: discount(CREDIT_RATE), usedSoFar: store.usedSoFar({ account: ctx.accountId, meter, from, to }) };
})
.commit(store.commit);

CRM indirimi bağlamdan gelir ve getRate içinde applyDiscount ile her tarifeye uygulanır (B19). Aylık kredi sayacı monthWindow({ at }) penceresinden okunur.

examples/llm-gateway/src/gateway.ts
/** Flush every minute that ended before `now`. Safe to call repeatedly: batch refs are idempotent. */
async flush(now: number) {
const results = [];
for (const b of buffer.drain(now)) {
const r = await metering.observe(
[
{ meter: "llm/input", dims: { model: b.model }, quantity: b.inputTokens },
{ meter: "llm/output", dims: { model: b.model }, quantity: b.outputTokens },
],
{ type: "batch", id: `${b.ctx.accountId}:${b.model}:${b.minute}` },
b.ctx,
{ at: b.minute },
);
if (!r.ok) buffer.restore(b); // try again on the next flush
results.push({ batch: b, result: r });
}
return results;
},

Çağrılar (hesap, model, dakika) başına tamponlanır ve dakika bitince tek bir çok satırlı observe olarak yazılır. Ref { type: "batch", id: "{hesap}:{model}:{dakika}" } olduğu için flush tekrar çağrılabilir: aynı parti commit’te no-op’tur. Başarısız parti tampona geri konur. Üretimde tampon bir Durable Object ya da Redis’tir; desen aynıdır. Tampon ve zamanlama kütüphanenin değil uygulamanın işidir.