feat(terraform): add cache-read pricing

This commit is contained in:
Erik Bogado 2026-09-01 18:58:47 -03:00
parent 09e9fd5f60
commit c2f88a7246
6 changed files with 70 additions and 14 deletions

View file

@ -16,6 +16,7 @@ longer signal it.
### Added
- **model**: New `cache_read_input_cost_per_million_tokens` argument for custom cached-input pricing
- **jwt_key_mapping**: New `litellm_jwt_key_mapping` resource for the proxy's JWT to virtual key mappings, so JWT clients identified by a claim (`client_id`, `azp`, `sub`) map to virtual keys and inherit their models, budgets and rate limits. Supports `description` and `is_active`, rotating the mapped key in place, and forces replacement when the claim name or value changes
- **team**: `soft_budget`, `tags`, and `soft_budget_alerting_emails` attributes on `litellm_team`, matching what `/team/new` and `/team/update` already accept; `soft_budget_alerting_emails` is sent under `metadata`, where the proxy reads it
- **user**: New `litellm_user` resource and `litellm_user` / `litellm_users` data sources for managing internal users

View file

@ -15,8 +15,9 @@ resource "litellm_model" "gpt4" {
tier = "paid"
mode = "chat"
input_cost_per_million_tokens = 30.0
output_cost_per_million_tokens = 60.0
input_cost_per_million_tokens = 30.0
cache_read_input_cost_per_million_tokens = 3.0
output_cost_per_million_tokens = 60.0
}
```
@ -153,6 +154,8 @@ The following arguments are supported:
* `input_cost_per_million_tokens` - (Optional) float. Cost per million input tokens. The provider converts this to a per-token cost sent to the API.
* `cache_read_input_cost_per_million_tokens` - (Optional) float. Cost per million cached input tokens. The provider converts this to `model_info.cache_read_input_token_cost`.
* `output_cost_per_million_tokens` - (Optional) float. Cost per million output tokens. The provider converts this to a per-token cost sent to the API.
* `input_cost_per_pixel` - (Optional) float. Cost applied per input pixel for models that charge by image size.

View file

@ -111,6 +111,10 @@ func resourceLiteLLMModel() *schema.Resource {
Type: schema.TypeFloat,
Optional: true,
},
"cache_read_input_cost_per_million_tokens": {
Type: schema.TypeFloat,
Optional: true,
},
"output_cost_per_million_tokens": {
Type: schema.TypeFloat,
Optional: true,

View file

@ -0,0 +1,45 @@
package litellm
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func TestResourceLiteLLMModelCreateSendsCacheReadPricing(t *testing.T) {
var payload map[string]interface{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.Method == http.MethodPost && r.URL.Path == endpointModelNew:
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("decode request: %v", err)
}
json.NewEncoder(w).Encode(payload)
case r.Method == http.MethodGet && r.URL.Path == endpointModelInfo:
json.NewEncoder(w).Encode(payload)
default:
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
d := schema.TestResourceDataRaw(t, resourceLiteLLMModel().Schema, map[string]interface{}{
"model_name": "cached-model",
"custom_llm_provider": "openai",
"base_model": "cached-model",
"cache_read_input_cost_per_million_tokens": 0.6,
})
if err := resourceLiteLLMModelCreate(d, NewClient(srv.URL, "test-key", true)); err != nil {
t.Fatalf("create model: %v", err)
}
modelInfo := payload["model_info"].(map[string]interface{})
if got := modelInfo["cache_read_input_token_cost"]; got != 0.0000006 {
t.Fatalf("cache read cost: want 0.0000006, got %v", got)
}
}

View file

@ -246,12 +246,13 @@ func createOrUpdateModel(d *schema.ResourceData, m interface{}, isUpdate bool) e
ModelName: d.Get("model_name").(string),
LiteLLMParams: litellmParams,
ModelInfo: ModelInfo{
ID: modelID,
DBModel: true,
BaseModel: pricingBaseModel,
Tier: d.Get("tier").(string),
Mode: d.Get("mode").(string),
TeamID: d.Get("team_id").(string),
ID: modelID,
DBModel: true,
BaseModel: pricingBaseModel,
Tier: d.Get("tier").(string),
Mode: d.Get("mode").(string),
TeamID: d.Get("team_id").(string),
CacheReadInputTokenCost: d.Get("cache_read_input_cost_per_million_tokens").(float64) / 1000000.0,
},
Additional: make(map[string]interface{}),
}
@ -341,6 +342,7 @@ func resourceLiteLLMModelRead(d *schema.ResourceData, m interface{}) error {
// Store cost information
d.Set("input_cost_per_million_tokens", d.Get("input_cost_per_million_tokens"))
d.Set("cache_read_input_cost_per_million_tokens", d.Get("cache_read_input_cost_per_million_tokens"))
d.Set("output_cost_per_million_tokens", d.Get("output_cost_per_million_tokens"))
// Handle thinking configuration

View file

@ -108,12 +108,13 @@ type LiteLLMParams struct {
// ModelInfo represents information about a model.
type ModelInfo struct {
ID string `json:"id"`
DBModel bool `json:"db_model"`
BaseModel string `json:"base_model"`
Tier string `json:"tier"`
Mode string `json:"mode"`
TeamID string `json:"team_id,omitempty"`
ID string `json:"id"`
DBModel bool `json:"db_model"`
BaseModel string `json:"base_model"`
Tier string `json:"tier"`
Mode string `json:"mode"`
TeamID string `json:"team_id,omitempty"`
CacheReadInputTokenCost float64 `json:"cache_read_input_token_cost,omitempty"`
}
// Key represents a LiteLLM API key.