Documentation
¶
Overview ¶
Package usage owns the canonical per-request usage record: what one inference request consumed, where it ran, and what it cost. Records are written best-effort after request completion and feed the activity API, the console usage page, and budget enforcement.
Index ¶
Constants ¶
const ( // StatusOK reports a completed request. StatusOK = "ok" // StatusError reports a failed request. StatusError = "error" // StatusCancelled reports a client-cancelled request. StatusCancelled = "cancelled" )
Statuses classify how a request finished.
const ( // OperationChat reports a chat completion request. OperationChat = "chat" // OperationEmbeddings reports an embeddings request. OperationEmbeddings = "embeddings" // OperationRerank reports a rerank request. It is separate from embeddings // because the two are metered differently: an embedding is billed by token // and a rerank is billed by token at one provider and by search unit at // another. OperationRerank = "rerank" // OperationImages reports an image generation or image edit request. // One name covers both, because the two are metered the same way and a // spend report reads the meter rather than the path. OperationImages = "images" // OperationSpeech reports a text-to-speech request. OperationSpeech = "speech" // OperationTranscription reports a speech-to-text request, in the spoken // language or translated. OperationTranscription = "transcription" // OperationVideos reports a video generation. It is the one operation whose // record is written after the request that started it returned, because the // work outlives that request and only its end states what it cost. OperationVideos = "videos" )
Operations name the inference surface a record measures.
const ( // CostReasonNoPricing means the catalog offering had no pricing data. CostReasonNoPricing = "no_pricing" // CostReasonNoRoute means the request failed before a route was chosen. CostReasonNoRoute = "no_route" // CostReasonNoUsage means the provider returned no token counts. CostReasonNoUsage = "no_usage" // CostReasonMediaUnpriced means the turn carried a media unit the // offering does not price. The token half of such a turn does have a // price, so a cost is computable; it would omit the media half, which is // the expensive one. A silent understatement is worse than a named gap, // so the whole cost drops and this reason says why. CostReasonMediaUnpriced = "media_unpriced" // CostReasonRerankUnpriced means the turn billed a search unit the // offering publishes no price for. The catalog projection drops such an // offering, so this reason names a snapshot that reached accounting // without that guard rather than a gap an operator has to accept. CostReasonRerankUnpriced = "rerank_unpriced" )
Cost unavailability reasons. A record without a cost carries one so the gap is loud, never a silent zero.
const ( // StorageSchemaVersion identifies the only usage record schema. StorageSchemaVersion = 1 // StoragePrefix is the usage v1 namespace. StoragePrefix = "usage:v1:" // DefaultRetention bounds how long records and counters survive. DefaultRetention = 30 * 24 * time.Hour // DefaultListLimit applies when a query gives no limit. DefaultListLimit = 50 // MaxListLimit bounds one page. MaxListLimit = 1000 )
const ( IntervalDay = "day" IntervalWeek = "week" IntervalMonth = "month" )
Intervals for aggregate windows. Windows are fixed and UTC-aligned.
Variables ¶
var ( // ErrRepositoryRequired reports an absent usage storage adapter. ErrRepositoryRequired = errors.New("usage storage is required") // ErrInvalidQuery reports an unusable list query. ErrInvalidQuery = errors.New("invalid usage query") // ErrInvalidInterval reports an unknown aggregate interval. ErrInvalidInterval = errors.New("invalid usage interval") // ErrInvalidScope reports an aggregate scope that names no subject. ErrInvalidScope = errors.New("invalid usage scope") // ErrCorruptRecord reports invalid durable usage data. ErrCorruptRecord = errors.New("usage record is invalid") )
var ErrInvalidRecord = errors.New("invalid usage record")
ErrInvalidRecord reports a record that cannot be persisted.
Functions ¶
Types ¶
type Media ¶
type Media struct {
GeneratedImages int64 `json:"generated_images,omitempty"`
// GeneratedVideos counts finished videos. A provider prices a video per
// video, not per second and not per token, so this is the whole meter for
// the operation rather than a share of another one.
GeneratedVideos int64 `json:"generated_videos,omitempty"`
}
Media counts the non-token units one request produced. A generated image carries no token count at all, so a token total cannot describe it, and a spend budget that reads tokens alone would meter such a turn as free.
type Options ¶
type Options struct {
// Retention bounds record and counter lifetime; DefaultRetention
// when zero.
Retention time.Duration
}
Options configure a repository.
type Page ¶
type Page struct {
Records []Record
// NextCursor continues the listing; empty when the page is the last.
NextCursor string
}
Page is one newest-first result page.
type Query ¶
type Query struct {
KeyID string
// AccountID selects one account's records across every key it holds.
// Records are keyed by gateway API key, so a query that names only an
// account scans the record namespace and filters. That cost belongs to
// reporting: budget enforcement reads the aggregate counters instead.
AccountID string
Model string
Provider string
Status string
Since time.Time
Until time.Time
Limit int
// Cursor continues a previous page: the opaque NextCursor value.
Cursor string
}
Query selects records. An empty KeyID selects every key and an empty AccountID every account (admin scope); callers own that authorization decision.
type Record ¶
type Record struct {
RequestID string `json:"request_id"`
KeyID string `json:"key_id"`
// AccountID is the account the key belongs to. It is what an account-wide
// spend cap counts, and a key ID cannot stand in for it because an account
// holds many keys. It is optional so that a record written before account
// attribution stays readable; such a record counts toward no account.
AccountID string `json:"account_id,omitempty"`
Timestamp time.Time `json:"timestamp"`
Protocol string `json:"protocol,omitempty"`
Operation string `json:"operation"`
ModelRequested string `json:"model_requested,omitempty"`
ModelUsed string `json:"model_used,omitempty"`
Provider string `json:"provider,omitempty"`
// CredentialSource names which credential plane paid for the request:
// `environment`, `shared`, `byok`, or `anonymous` for a provider that
// accepted the call without one. It is what lets an operator see an
// account drawing on the deployment's credential rather than its own. It
// is empty on a record written before a route was chosen, and on one
// written before the gateway recorded the plane.
CredentialSource string `json:"credential_source,omitempty"`
Streaming bool `json:"streaming,omitempty"`
Status string `json:"status"`
StatusCode int `json:"status_code,omitempty"`
ErrorClass string `json:"error_class,omitempty"`
Tokens Tokens `json:"tokens"`
// Media is nil on a text turn, which is what every record written before
// media accounting existed reads back as.
Media *Media `json:"media,omitempty"`
// SearchUnits counts what a rerank provider that bills by search unit
// billed. It sits beside Media rather than inside it because reranking
// produces no media, and it sits outside Tokens because no token total
// converts into it: a Cohere turn reports a search unit and no tokens at
// all, and a record that read tokens alone would meter it as free.
SearchUnits int64 `json:"search_units,omitempty"`
// TokensEstimated marks counts the gateway synthesized with a
// tokenizer because the provider reported none.
TokensEstimated bool `json:"tokens_estimated,omitempty"`
LatencyMS int64 `json:"latency_ms"`
RoutingMS int64 `json:"routing_ms,omitempty"`
// OverheadMS is the gateway-added latency: total handling time
// minus upstream provider waits.
OverheadMS int64 `json:"overhead_ms,omitempty"`
// TTFTMS is the time from request start to the first stream event.
// Only streamed requests carry it.
TTFTMS int64 `json:"ttft_ms,omitempty"`
Attempts int `json:"attempts,omitempty"`
CacheStatus string `json:"cache_status,omitempty"`
// ParserEngine names which engine read the documents this turn attached:
// `native` for the in-process reader, `recognition` for a catalogued model.
// It is empty on a turn that attached none.
ParserEngine string `json:"parser_engine,omitempty"`
// DocumentPages is how many pages those attachments held, whether this turn
// read them or the cache answered for them.
DocumentPages int64 `json:"document_pages,omitempty"`
// RecognizedPages is how many of those pages this turn sent to a
// recognition model. It is what ExtractionCost is charged for, and it is
// zero on a cached read: the pages were recognized once, on an earlier turn
// that paid for them.
RecognizedPages int64 `json:"recognized_pages,omitempty"`
// NativePages is how many pages this turn read in process. They cost
// nothing: no provider saw them.
NativePages int64 `json:"native_pages,omitempty"`
// ExtractionCached reports that every attachment came back from the
// extraction cache. A cached read and a native read both record no cost,
// and only this field separates a page an earlier turn already paid for
// from a page no provider ever charged for.
ExtractionCached bool `json:"extraction_cached,omitempty"`
// ExtractionMillis is how long the document reads took. A recognition read
// is a provider call inside a provider call, so it is latency an operator
// cannot find anywhere else in this record.
ExtractionMillis int64 `json:"extraction_millis,omitempty"`
// ExtractionCost is the recognized share of Cost below, reported on its own
// so an operator can see what reading a document cost apart from what
// answering about it cost. It is nil when the turn recognized nothing.
ExtractionCost *Cost `json:"extraction_cost,omitempty"`
// Cost is nil when no cost could be computed; CostUnavailableReason
// then names why. It covers the whole request, the document reads
// included.
Cost *Cost `json:"cost,omitempty"`
}
Record is one completed inference request.
type Repository ¶
type Repository interface {
// Put persists one record and advances the aggregate counters.
Put(context.Context, Record) error
// List returns records newest-first.
List(context.Context, Query) (Page, error)
// Totals reads the aggregate counters for one scope in the window
// containing at.
Totals(ctx context.Context, scope Scope, interval string, at time.Time) (Totals, error)
}
Repository is the durable usage contract.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope selects which counter set an aggregate query reads. The gateway keeps one set per key, one per account, and one for the whole deployment, because an account cap and a key cap meter different populations: the account total is the sum over every key it holds, so neither can be derived from the other.
It is comparable, so a caller may use it as a map key.
func AccountScope ¶
AccountScope addresses the counters for one account: every key it holds.
func GatewayScope ¶
func GatewayScope() Scope
GatewayScope addresses the counters for the whole deployment.
type Tokens ¶
type Tokens struct {
Input int64 `json:"input"`
Output int64 `json:"output"`
Total int64 `json:"total"`
Reasoning int64 `json:"reasoning,omitempty"`
CacheRead int64 `json:"cache_read,omitempty"`
CacheWrite int64 `json:"cache_write,omitempty"`
// AudioInput and AudioOutput are the audio shares of Input and Output,
// not additions to them. A provider meters audio at its own rate, so a
// cost reclassifies these out of the plain rates rather than adding them.
AudioInput int64 `json:"audio_input,omitempty"`
AudioOutput int64 `json:"audio_output,omitempty"`
}
Tokens holds provider-reported token counts for one request.