Vectrum
Sign In
58 lines · 2.0 KB · Go
package llm

import (
	"fmt"
	"strconv"
)

// Price is per-million-token list pricing.
type Price struct {
	InputPerMTok  float64
	OutputPerMTok float64
}

// Cache multipliers relative to the input rate.
const (
	cacheReadMultiplier  = 0.1
	cacheWriteMultiplier = 1.25
)

// prices covers the models this harness is likely to be pointed at. A model
// that is not listed reports no cost rather than a wrong one.
var prices = map[string]Price{
	"claude-opus-5":    {InputPerMTok: 5, OutputPerMTok: 25},
	"claude-opus-4-8":  {InputPerMTok: 5, OutputPerMTok: 25},
	"claude-opus-4-7":  {InputPerMTok: 5, OutputPerMTok: 25},
	"claude-sonnet-5":  {InputPerMTok: 2, OutputPerMTok: 10},
	"claude-haiku-4-5": {InputPerMTok: 1, OutputPerMTok: 5},
	"claude-fable-5-1": {InputPerMTok: 10, OutputPerMTok: 50},
	"claude-fable-5":   {InputPerMTok: 10, OutputPerMTok: 50},
}

// Cost prices one usage record. The boolean reports whether the model's
// pricing is known; a made-up number in METRICS.md is worse than a blank.
func (u Usage) Cost(model string) (float64, bool) {
	p, ok := prices[model]
	if !ok {
		return 0, false
	}
	return (float64(u.InputTokens)*p.InputPerMTok +
		float64(u.OutputTokens)*p.OutputPerMTok +
		float64(u.CacheReadInputTokens)*p.InputPerMTok*cacheReadMultiplier +
		float64(u.CacheCreationInputTokens)*p.InputPerMTok*cacheWriteMultiplier) / 1_000_000, true
}

// PriceLabel renders a model's list pricing as the wizard shows it:
// "$5 / $25 per Mtok", input then output. A model with no known pricing gets
// an empty label rather than a zero, because "$0" beside a frontier model is
// a worse answer than saying nothing.
func PriceLabel(model string) string {
	p, ok := prices[model]
	if !ok {
		return ""
	}
	// Shortest representation that round-trips: the whole-dollar rates read
	// as "$5" rather than "$5.00", and a fractional one keeps its decimals.
	num := func(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) }
	return fmt.Sprintf("$%s / $%s per Mtok", num(p.InputPerMTok), num(p.OutputPerMTok))
}