usage

package
v1.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package usage defines usage tracking types and service.

Index

Constants

View Source
const (
	// MaxMetadataPairs bounds the labels one record, or one filter, carries.
	MaxMetadataPairs = 16
	// MaxMetadataValueBytes bounds one label value.
	MaxMetadataValueBytes = 256
)

Variables

View Source
var ErrInvalidMetadata = errors.New("nexus: invalid usage metadata")

ErrInvalidMetadata reports record metadata or a metadata filter that breaks the limits above or uses a key outside the allowed pattern.

View Source
var ErrInvalidPeriod = errors.New("nexus: period must be day, week or month")

ErrInvalidPeriod reports a period other than day, week or month.

View Source
var ErrInvalidSeries = errors.New("nexus: invalid series")

ErrInvalidSeries reports a range or bucket a series cannot be built for.

Functions

func BucketStart

func BucketStart(t time.Time, b Bucket) time.Time

BucketStart truncates t, in UTC, to the start of its bucket.

func PeriodStart

func PeriodStart(period string, now time.Time) (time.Time, error)

PeriodStart is the UTC instant a period began as of now: midnight UTC for "day", seven days before now for "week", the first of the month at midnight UTC for "month". Every backend uses it, so none computes "now" in its own time zone.

func ValidateMetadata

func ValidateMetadata(m map[string]string) error

ValidateMetadata checks m as record metadata or as a filter: at most MaxMetadataPairs pairs, keys matching ^[A-Za-z0-9_-]{1,64}$ and values of at most MaxMetadataValueBytes bytes. A nil or empty map is valid.

Types

type Bucket

type Bucket string

Bucket is the width of one point in a series.

const (
	BucketHour Bucket = "hour"
	BucketDay  Bucket = "day"
)

type ModelUsage

type ModelUsage struct {
	Requests int       `json:"requests"`
	Tokens   int       `json:"tokens"`
	CostUSD  money.USD `json:"cost_usd"`
	Unpriced int       `json:"unpriced"`
}

ModelUsage is usage aggregated by model.

type Outcome

type Outcome string

Outcome says what happened to a request.

const (
	// OutcomeOK: a provider answered.
	OutcomeOK Outcome = "ok"
	// OutcomeCached: served from the response cache; no provider was called.
	OutcomeCached Outcome = "cached"
	// OutcomeBlocked: a guard refused the request; BlockedBy names the guard.
	OutcomeBlocked Outcome = "blocked"
	// OutcomeRefused: authentication or a quota refused the request;
	// RefusalCode says which.
	OutcomeRefused Outcome = "refused"
	// OutcomeError: the provider call failed.
	OutcomeError Outcome = "error"
)

type PricingStatus

type PricingStatus string

PricingStatus says whether a request's cost is known, and if not, why.

const (
	// PricingPriced: the cost is the model's list price over the tokens the
	// provider reported.
	PricingPriced PricingStatus = "priced"
	// PricingUnpricedModel: the model has no price for the tokens used, so
	// the cost is unknown. It is never reported as $0.
	PricingUnpricedModel PricingStatus = "unpriced_model"
	// PricingCached: served from the response cache, so no provider was
	// called and the cost is exactly $0.
	PricingCached PricingStatus = "cached"
	// PricingNotCharged: no provider was called (the request was refused,
	// blocked before the call, or failed before reaching one), so the cost
	// is exactly $0.
	PricingNotCharged PricingStatus = "not_charged"
	// PricingUnknown: a provider was called but the request failed, so
	// whether and what it charged is unknown. The cost is nil, never $0.
	PricingUnknown PricingStatus = "unknown"
)

func (PricingStatus) CostUnknown

func (s PricingStatus) CostUnknown() bool

CostUnknown reports whether a record with this status has no known cost.

type ProviderUsage

type ProviderUsage struct {
	Requests int       `json:"requests"`
	Tokens   int       `json:"tokens"`
	CostUSD  money.USD `json:"cost_usd"`
	Unpriced int       `json:"unpriced"`
}

ProviderUsage is usage aggregated by provider.

type QueryOptions

type QueryOptions struct {
	TenantID  string    `json:"tenant_id,omitempty"`
	KeyID     string    `json:"key_id,omitempty"`
	Provider  string    `json:"provider,omitempty"`
	Model     string    `json:"model,omitempty"`
	Outcome   Outcome   `json:"outcome,omitempty"`
	StartTime time.Time `json:"start_time,omitzero"`
	EndTime   time.Time `json:"end_time,omitzero"`
	Limit     int       `json:"limit,omitempty"`
	Cursor    string    `json:"cursor,omitempty"`
	// Metadata keeps records whose metadata holds every pair given, with an
	// exactly equal value. An empty map filters nothing.
	Metadata map[string]string `json:"metadata,omitempty"`
}

QueryOptions filters the request log. Every filter is applied by the store, never after reading a window, so an empty page means nothing matched. An empty TenantID means every tenant. EndTime is exclusive.

type QueryResult

type QueryResult struct {
	Items      []*Record `json:"items"`
	NextCursor string    `json:"next_cursor"`
}

QueryResult is one page of records. NextCursor is "" on the last page.

type Record

type Record struct {
	ID id.UsageID `json:"id"`
	// TenantID, KeyID and RequestID are id.Nil when the request was not
	// attributed to them.
	TenantID         id.TenantID  `json:"tenant_id"`
	KeyID            id.KeyID     `json:"key_id"`
	RequestID        id.RequestID `json:"request_id"`
	Provider         string       `json:"provider"`
	Model            string       `json:"model"`
	PromptTokens     int          `json:"prompt_tokens"`
	CompletionTokens int          `json:"completion_tokens"`
	TotalTokens      int          `json:"total_tokens"`
	// CostUSD is nil when the request could not be priced. It is never $0
	// standing in for "unknown"; PricingStatus says why it is missing.
	CostUSD       *money.USD    `json:"cost_usd"`
	PricingStatus PricingStatus `json:"pricing_status"`
	Outcome       Outcome       `json:"outcome"`
	BlockedBy     string        `json:"blocked_by,omitempty"`
	RefusalCode   string        `json:"refusal_code,omitempty"`
	Latency       time.Duration `json:"latency"`
	Cached        bool          `json:"cached"`
	StatusCode    int           `json:"status_code"`
	CreatedAt     time.Time     `json:"created_at"`
	// Metadata is the caller's labels for the request (for example the
	// project and run it belongs to). See ValidateMetadata for the limits.
	Metadata map[string]string `json:"metadata,omitempty"`
}

Record captures a single API call's usage.

type SeriesOptions

type SeriesOptions struct {
	TenantID string    `json:"tenant_id,omitempty"`
	Start    time.Time `json:"start"`
	End      time.Time `json:"end"`
	Bucket   Bucket    `json:"bucket"`
	// Metadata keeps records whose metadata holds every pair given, with an
	// exactly equal value. An empty map filters nothing.
	Metadata map[string]string `json:"metadata,omitempty"`
}

SeriesOptions asks for usage between Start and End (exclusive), one point per bucket. An empty TenantID means every tenant.

Start is rounded down to the start of its bucket, so every point covers its whole bucket and its label is true. End is used as given: when it is not on a bucket boundary the last bucket covers usage up to End only.

type SeriesPoint

type SeriesPoint struct {
	Start    time.Time `json:"start"`
	Requests int       `json:"requests"`
	Tokens   int       `json:"tokens"`
	CostUSD  money.USD `json:"cost_usd"`
	Unpriced int       `json:"unpriced"`
}

SeriesPoint is usage within one bucket. CostUSD sums priced requests; Unpriced counts the requests it leaves out.

func FillSeries

func FillSeries(opts *SeriesOptions, points []SeriesPoint) ([]SeriesPoint, error)

FillSeries merges the points a store found (possibly one per record) into one point per bucket from Start to End, oldest first, with empty buckets present as zeros.

type Service

type Service interface {
	Record(ctx context.Context, rec *Record) error
	MonthlySpend(ctx context.Context, tenantID string) (money.USD, error)
	DailyRequests(ctx context.Context, tenantID string) (int, error)
	Summary(ctx context.Context, tenantID string, period string) (*Summary, error)
	Series(ctx context.Context, opts *SeriesOptions) ([]SeriesPoint, error)
	Query(ctx context.Context, opts *QueryOptions) (*QueryResult, error)
}

Service tracks and queries usage data. Wherever a method takes a tenant id, "" means every tenant, including requests attributed to none. DailyRequests counts every request recorded since midnight UTC except refused ones, so refusals never use up the quota.

func NewService

func NewService(store Store) Service

NewService creates a new usage service.

type Store

type Store interface {
	Insert(ctx context.Context, rec *Record) error
	MonthlySpend(ctx context.Context, tenantID string) (money.USD, error)
	DailyRequests(ctx context.Context, tenantID string) (int, error)
	Summary(ctx context.Context, tenantID string, period string) (*Summary, error)
	Series(ctx context.Context, opts *SeriesOptions) ([]SeriesPoint, error)
	Query(ctx context.Context, opts *QueryOptions) (*QueryResult, error)
}

Store is the persistence interface for usage records. Wherever a method takes a tenant id, "" means every tenant, including requests attributed to none. DailyRequests counts every request recorded since midnight UTC except refused ones, so refusals never use up the quota. Summary returns ErrInvalidPeriod for a period other than day, week or month.

type Summary

type Summary struct {
	// TenantID is "" when the summary covers every tenant, unattributed
	// requests included.
	TenantID      string `json:"tenant_id"`
	Period        string `json:"period"`
	TotalRequests int    `json:"total_requests"`
	TotalTokens   int    `json:"total_tokens"`
	// TotalCostUSD sums priced requests only. UnpricedRequests counts the
	// requests it leaves out because their cost is unknown.
	TotalCostUSD     money.USD                 `json:"total_cost_usd"`
	UnpricedRequests int                       `json:"unpriced_requests"`
	CacheHitRate     float64                   `json:"cache_hit_rate"` // share of requests served from cache, 0 to 1
	AvgLatency       time.Duration             `json:"avg_latency"`
	ByProvider       map[string]*ProviderUsage `json:"by_provider"`
	ByModel          map[string]*ModelUsage    `json:"by_model"`
	ByOutcome        map[Outcome]int           `json:"by_outcome"`
}

Summary aggregates usage over a period.

func BuildSummary

func BuildSummary(tenantID, period string, rows []SummaryRow) *Summary

BuildSummary turns grouped rows into a Summary. Every backend calls it, so the rules for what counts as unpriced, cached or average live in one place.

type SummaryRow

type SummaryRow struct {
	Provider      string
	Model         string
	Outcome       Outcome
	PricingStatus PricingStatus
	Cached        bool
	Requests      int
	Tokens        int
	LatencyNs     int64
	Cost          money.USD
}

SummaryRow is one group of records a backend aggregated: requests sharing provider, model, outcome, pricing status and cache flag. A backend that cannot group (SQLite, memory) passes one row per record with Requests 1. Cost sums priced records only; it is zero for an unpriced group.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL