Skip to content

Latest commit

 

History

History
82 lines (63 loc) · 3.42 KB

File metadata and controls

82 lines (63 loc) · 3.42 KB

Billing & metering (Step 7.9)

Per-tenant usage metering, the usage-export API, and invoice generation — rag_observability.billing. Mirrors the Step 5.6c cost-anomaly design: plain dataclasses in rag-observability (the gateway wraps them in a Pydantic response), so there's no rag-core type / dist/schemas churn.

Overview

Piece What
UsageMeter Thread-safe per-tenant, per-dimension counters; fed O(1) from the request paths
BillingDimension queries · docs_ingested · storage_gb · reranker_calls · llm_tokens
UsageSnapshot One tenant's accrued usage (dimension → quantity)
PricingModel / load_pricing Tiers + metered rates, loadable from marketplace/pricing.yaml
generate_invoice Pure usage + pricing → Invoice; line-item quantities equal metered overage exactly
BillingProvider Seam for reporting usage to Stripe / a marketplace metering API (degrade-open)

Usage

Metering (gateway)

UsageMeter is observe-only — it never rejects or mutates a request. It's fed from record_request_usage (queries + LLM tokens) and serves the usage API:

curl 'localhost:8000/v1/billing/usage?tenant_id=acme'
# {"tenant_id": "acme", "usage": {"queries": 1234, "llm_tokens": 50000}}

Empty ({}) when cfg.billing.enabled is false (the default). Enable it:

# rag.yaml
billing:
  enabled: true
  default_tier: pro
  max_tenants: 10000

Invoicing (offline)

Invoicing is offline (not a hot-path concern) and priced from config, so a price change is a pricing.yaml edit, not code:

ragctl billing --tenant acme --tier pro
# meters synthetic usage → invoice line items + total + a reconciliation check

generate_invoice(snapshot, pricing, tier_id) is pure: a base-fee line plus one line per dimension whose usage exceeds the tier's inclusion, billed at the metered rate. Because each metered line item's quantity equals the metered overage, the bill reconciles with usage exactly (well within the ±0.5% acceptance bar).

Internals

  • No dist/schemas churn — the gateway's BillingUsageResponse is a gateway-local Pydantic model wrapping the dataclass via dataclasses.asdict (same pattern as CostStatusResponse). Only dist/openapi.* gains the endpoint.
  • No governed SPI call — metering is observe-only (like cost / drift / feedback), so the PolicyEngine coverage linter needs no new entry. Enforcement is the quota enforcer (Step 4.5); billing only meters.
  • Fed independently of quotas — a deployment can meter usage without enabling quota enforcement.

Extension points

  • Report to an external biller. Implement BillingProvider.report_usage (a Stripe usage-records adapter, or the AWS/Azure/GCP marketplace metering APIs) and call it degrade-open from the gateway. The default NoopBillingProvider records nothing (the usage API still serves).
  • More feed points. docs_ingested (ingest path), reranker_calls (rerank stage), and storage_gb (a gauge) are supported by the meter; wire additional observe / set_gauge calls at those sites.

See also