From c2f88a7246f85fd4cb7bbe4d6d9a65de0633cc26 Mon Sep 17 00:00:00 2001 From: Erik Bogado Date: Tue, 1 Sep 2026 18:58:47 -0300 Subject: [PATCH] feat(terraform): add cache-read pricing --- terraform/provider/CHANGELOG.md | 1 + terraform/provider/docs/resources/model.md | 7 ++- terraform/provider/litellm/resource_model.go | 4 ++ .../resource_model_cache_pricing_test.go | 45 +++++++++++++++++++ .../provider/litellm/resource_model_crud.go | 14 +++--- terraform/provider/litellm/types.go | 13 +++--- 6 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 terraform/provider/litellm/resource_model_cache_pricing_test.go diff --git a/terraform/provider/CHANGELOG.md b/terraform/provider/CHANGELOG.md index 842bfb4bdb1..e6328468fd5 100644 --- a/terraform/provider/CHANGELOG.md +++ b/terraform/provider/CHANGELOG.md @@ -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 diff --git a/terraform/provider/docs/resources/model.md b/terraform/provider/docs/resources/model.md index 0409b48b391..470ea176ecb 100644 --- a/terraform/provider/docs/resources/model.md +++ b/terraform/provider/docs/resources/model.md @@ -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. diff --git a/terraform/provider/litellm/resource_model.go b/terraform/provider/litellm/resource_model.go index b0a7304718b..9c364f2fa0f 100644 --- a/terraform/provider/litellm/resource_model.go +++ b/terraform/provider/litellm/resource_model.go @@ -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, diff --git a/terraform/provider/litellm/resource_model_cache_pricing_test.go b/terraform/provider/litellm/resource_model_cache_pricing_test.go new file mode 100644 index 00000000000..ef33146707e --- /dev/null +++ b/terraform/provider/litellm/resource_model_cache_pricing_test.go @@ -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) + } +} diff --git a/terraform/provider/litellm/resource_model_crud.go b/terraform/provider/litellm/resource_model_crud.go index fc5d5b09dd5..ee8f5a62cba 100644 --- a/terraform/provider/litellm/resource_model_crud.go +++ b/terraform/provider/litellm/resource_model_crud.go @@ -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 diff --git a/terraform/provider/litellm/types.go b/terraform/provider/litellm/types.go index 7bef44409fd..92302efdcdd 100644 --- a/terraform/provider/litellm/types.go +++ b/terraform/provider/litellm/types.go @@ -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.