Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions internal/message/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

type anthropicEstimator interface {
EstimateTotalCost(model string, promptTks, completionTks int) (float64, error)
EstimateCompletionCost(model string, tks int) (float64, error)
EstimateCompletionCost(model string, promptTks, completionTks int) (float64, error)
EstimatePromptCost(model string, tks int) (float64, error)
Count(input string) int
}
Expand All @@ -33,9 +33,8 @@ type estimator interface {
EstimateCompletionsRequestCostWithTokenCounts(model string, content any) (int, float64, error)
EstimateCompletionsStreamCostWithTokenCounts(model string, content string) (int, float64, error)
EstimateSpeechCost(input string, model string) (float64, error)
EstimateChatCompletionPromptCostWithTokenCounts(r *goopenai.ChatCompletionRequest) (int, float64, error)
EstimateEmbeddingsCost(r *goopenai.EmbeddingRequest) (float64, error)
EstimateChatCompletionStreamCostWithTokenCounts(model, content string) (int, float64, error)
EstimateChatCompletionStreamCostWithTokenCounts(model string, promptTks int, content string) (int, float64, float64, error)
EstimateCompletionCost(model string, tks int) (float64, error)
EstimateTotalCost(model string, promptTks, completionTks int) (float64, error)
EstimateEmbeddingsInputCost(model string, tks int) (float64, error)
Expand Down Expand Up @@ -523,7 +522,7 @@ func (h *Handler) decorateEvent(m Message) error {
completiontks := h.ae.Count(e.Content)
completiontks += anthropicCompletionMagicNum

completionCost, err := h.ae.EstimateCompletionCost(model, completiontks)
completionCost, err := h.ae.EstimateCompletionCost(model, tks, completiontks)
if err != nil {
telemetry.Incr("bricksllm.message.handler.decorate_event.estimate_completion_cost_error", nil, 1)
return err
Expand Down Expand Up @@ -562,7 +561,7 @@ func (h *Handler) decorateEvent(m Message) error {
completiontks := h.ae.Count(e.Content)
completiontks += anthropicCompletionMagicNum

completionCost, err := h.ae.EstimateCompletionCost(translatedModel, completiontks)
completionCost, err := h.ae.EstimateCompletionCost(translatedModel, tks, completiontks)
if err != nil {
telemetry.Incr("bricksllm.message.handler.decorate_event.estimate_completion_cost_error", nil, 1)
return err
Expand Down Expand Up @@ -684,13 +683,13 @@ func (h *Handler) decorateEvent(m Message) error {
}

if ccr.Stream {
tks, cost, err := h.e.EstimateChatCompletionPromptCostWithTokenCounts(ccr)
tks, err := h.e.EstimateChatCompletionPromptTokenCounts(ccr.Model, ccr)
if err != nil {
telemetry.Incr("bricksllm.message.handler.decorate_event.estimate_chat_completion_prompt_cost_with_token_counts", nil, 1)
telemetry.Incr("bricksllm.message.handler.decorate_event.estimate_chat_completion_prompt_token_counts", nil, 1)
return err
}

completiontks, completionCost, err := h.e.EstimateChatCompletionStreamCostWithTokenCounts(e.Event.Model, e.Content)
completiontks, cost, completionCost, err := h.e.EstimateChatCompletionStreamCostWithTokenCounts(e.Event.Model, tks, e.Content)
if err != nil {
telemetry.Incr("bricksllm.message.handler.decorate_event.estimate_chat_completion_stream_cost_with_token_counts", nil, 1)
return err
Expand All @@ -703,13 +702,15 @@ func (h *Handler) decorateEvent(m Message) error {
e.Event.CostInUsd = cost + completionCost

if e.CostMap != nil {
model := openai.ModelWithContextLength(e.Event.Model, int64(tks+completiontks))
model := provider.ModelWithContextLength(e.Event.Model, int64(tks+completiontks))
newCost, err := provider.EstimateTotalCostWithCostMaps(model, tks, completiontks, 1000, e.CostMap.PromptCostPerModel, e.CostMap.CompletionCostPerModel)
if err != nil {
newCost, err = provider.EstimateTotalCostWithCostMaps(e.Event.Model, tks, completiontks, 1000, e.CostMap.PromptCostPerModel, e.CostMap.CompletionCostPerModel)
}
if err != nil {
h.log.Debug("error when estimating total cost with cost maps", zap.Error(err))
telemetry.Incr("bricksllm.proxy.decorate_event.estimate_total_cost_with_cost_maps_error", nil, 1)
}

}
if newCost != 0 {
e.Event.CostInUsd = newCost
}
Expand Down
119 changes: 83 additions & 36 deletions internal/provider/anthropic/cost.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
package anthropic

import (
"errors"
"fmt"
"strings"

"github.com/bricks-cloud/bricksllm/internal/provider"
)

var AnthropicPerMillionTokenCost = map[string]map[string]float64{
"prompt": {
"claude-mythos-5-1": 10.0,
"claude-mythos-5": 10.0,

"claude-fable-5-1": 10.0,
"claude-fable-5": 10.0,

"claude-opus-5-5": 4.0,
"claude-opus-5": 5.0,
"claude-opus-4-8": 5.0,
"claude-opus-4-7": 5.0,
"claude-opus-4-6": 5.0,
"claude-opus-4-5": 5.0,
"claude-opus-4-1": 15.0,
"claude-opus-4": 15.0,

"claude-sonnet-5-5": 2.0,
"claude-sonnet-5": 2.0,
"claude-sonnet-4-6": 3.0,
"claude-sonnet-4-5": 3.0,
"claude-sonnet-4": 3.0,

"claude-haiku-4-5": 1.0,
"claude-haiku-5-5": 0.5,
"claude-haiku-5-5~long": 0.5,
"claude-haiku-5-5~short": 0.1,
"claude-haiku-4-5": 1.0,

// old models
"claude-3-7-sonnet": 3.0,
Expand All @@ -29,18 +43,31 @@ var AnthropicPerMillionTokenCost = map[string]map[string]float64{
"claude-3-opus": 15.0,
},
"completion": {
"claude-mythos-5-1": 50.0,
"claude-mythos-5": 50.0,

"claude-fable-5-1": 50.0,
"claude-fable-5": 50.0,

"claude-opus-5-5": 20.0,
"claude-opus-5": 25.0,
"claude-opus-4-8": 25.0,
"claude-opus-4-7": 25.0,
"claude-opus-4-6": 25.0,
"claude-opus-4-5": 25.0,
"claude-opus-4-1": 75.0,
"claude-opus-4": 75.0,

"claude-sonnet-5-5": 10.0,
"claude-sonnet-5": 10.0,
"claude-sonnet-4-6": 15.0,
"claude-sonnet-4-5": 15.0,
"claude-sonnet-4": 15.0,

"claude-haiku-4-5": 5.0,
"claude-haiku-5-5": 2.5,
"claude-haiku-5-5~long": 2.5,
"claude-haiku-5-5~short": 0.5,
"claude-haiku-4-5": 5.0,

// old models
"claude-3-7-sonnet": 15.0,
Expand Down Expand Up @@ -68,34 +95,39 @@ func NewCostEstimator(tc tokenCounter) *CostEstimator {
}

func (ce *CostEstimator) EstimateTotalCost(model string, promptTks, completionTks int) (float64, error) {
promptCost, err := ce.EstimatePromptCost(model, promptTks)
normalized := normalizeAnthropicModel(model)
modelWithCtx := provider.ModelWithContextLength(normalized, int64(promptTks))

promptCost, err := ce.estimateCost("prompt", modelWithCtx, promptTks)
Comment thread
sergei-bronnikov marked this conversation as resolved.
if err != nil {
return 0, err
}

completionCost, err := ce.EstimateCompletionCost(model, completionTks)
completionCost, err := ce.estimateCost("completion", modelWithCtx, completionTks)
if err != nil {
return 0, err
}

return promptCost + completionCost, nil
}

func (ce *CostEstimator) EstimatePromptCost(model string, tks int) (float64, error) {
costMap, ok := ce.tokenCostMap["prompt"]
if !ok {
return 0, errors.New("prompt token cost is not provided")

// normalizeAnthropicModel strips version/date suffixes (and Bedrock's
// cross-region "us.anthropic...." prefix) down to the base model family name
// used as a key in AnthropicPerMillionTokenCost.
func normalizeAnthropicModel(model string) string {
if strings.HasPrefix(model, "us") {
return convertAmazonModelToAnthropicModel(model)
}
return SelectModel(model)
}

selected := ""
if strings.HasPrefix(model, "us") {
selected = convertAmazonModelToAnthropicModel(model)
} else {
selected = SelectModel(model)
func (ce *CostEstimator) estimateCost(costMapKey, model string, tks int) (float64, error) {
costMap, ok := ce.tokenCostMap[costMapKey]
if !ok {
return 0, fmt.Errorf("%s token cost is not provided", costMapKey)
}

cost, ok := costMap[selected]
cost, ok := costMap[model]
if !ok {
return 0, fmt.Errorf("%s is not present in the cost map provided", model)
}
Expand All @@ -104,7 +136,30 @@ func (ce *CostEstimator) EstimatePromptCost(model string, tks int) (float64, err
return tksInFloat / 1000000 * cost, nil
}

func (ce *CostEstimator) EstimatePromptCost(model string, tks int) (float64, error) {
modelWithCtx := provider.ModelWithContextLength(normalizeAnthropicModel(model), int64(tks))
return ce.estimateCost("prompt", modelWithCtx, tks)
}

func SelectModel(model string) string {
if strings.HasPrefix(model, "claude-mythos-5-1") {
return "claude-mythos-5-1"
}
if strings.HasPrefix(model, "claude-mythos-5") {
return "claude-mythos-5"
}
if strings.HasPrefix(model, "claude-fable-5-1") {
return "claude-fable-5-1"
}
if strings.HasPrefix(model, "claude-fable-5") {
return "claude-fable-5"
}
if strings.HasPrefix(model, "claude-opus-5-5") {
return "claude-opus-5-5"
}
if strings.HasPrefix(model, "claude-opus-5") {
return "claude-opus-5"
}
if strings.HasPrefix(model, "claude-opus-4-8") {
return "claude-opus-4-8"
}
Expand All @@ -123,6 +178,12 @@ func SelectModel(model string) string {
if strings.HasPrefix(model, "claude-opus-4") {
return "claude-opus-4"
}
if strings.HasPrefix(model, "claude-sonnet-5-5") {
return "claude-sonnet-5-5"
}
if strings.HasPrefix(model, "claude-sonnet-5") {
return "claude-sonnet-5"
}
if strings.HasPrefix(model, "claude-sonnet-4-6") {
return "claude-sonnet-4-6"
}
Expand All @@ -132,6 +193,9 @@ func SelectModel(model string) string {
if strings.HasPrefix(model, "claude-sonnet-4") {
return "claude-sonnet-4"
}
if strings.HasPrefix(model, "claude-haiku-5-5") {
return "claude-haiku-5-5"
}
if strings.HasPrefix(model, "claude-haiku-4-5") {
return "claude-haiku-4-5"
}
Expand Down Expand Up @@ -164,26 +228,9 @@ func convertAmazonModelToAnthropicModel(model string) string {
return SelectModel(parts[2])
}

func (ce *CostEstimator) EstimateCompletionCost(model string, tks int) (float64, error) {
costMap, ok := ce.tokenCostMap["completion"]
if !ok {
return 0, errors.New("prompt token cost is not provided")
}

selected := ""
if strings.HasPrefix(model, "us") {
selected = convertAmazonModelToAnthropicModel(model)
} else {
selected = SelectModel(model)
}

cost, ok := costMap[selected]
if !ok {
return 0, errors.New("model is not present in the cost map provided")
}

tksInFloat := float64(tks)
return tksInFloat / 1000000 * cost, nil
func (ce *CostEstimator) EstimateCompletionCost(model string, promptTks, completionTks int) (float64, error) {
modelWithCtx := provider.ModelWithContextLength(normalizeAnthropicModel(model), int64(promptTks))
return ce.estimateCost("completion", modelWithCtx, completionTks)
}

func (ce *CostEstimator) Count(input string) int {
Expand Down
Loading
Loading