mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
feat(terraform): add tags to litellm_model
Add a tags list attribute to litellm_model, matching what /model/new and /model/update already accept under litellm_params.tags for tag-based routing (https://docs.litellm.ai/docs/proxy/tag_routing). Follows the same GetOk-or-HasChange pattern used for litellm_team's tags attribute (see resource_team.go) so an explicitly cleared list is still sent to the API.
This commit is contained in:
parent
071cb49d32
commit
74fcd72001
6 changed files with 170 additions and 0 deletions
|
|
@ -16,6 +16,7 @@ longer signal it.
|
|||
|
||||
### Added
|
||||
|
||||
- **model**: `tags` attribute on `litellm_model`, matching what `/model/new` and `/model/update` already accept under `litellm_params.tags`, for [tag-based routing](https://docs.litellm.ai/docs/proxy/tag_routing)
|
||||
- **team_member_add**: `tpm_limit`, `rpm_limit`, `budget_duration`, and `allowed_models` attributes on `litellm_team_member_add`, applied to every member of the resource; `budget_duration` and `allowed_models` ride on `/team/member_add`, while the limits are sent through `/team/member_update`, which is where the proxy accepts them
|
||||
- **team**: Optional `team_id` argument on `litellm_team`, so teams can be created with a stable, human-readable ID instead of a provider-generated UUID; changing it forces replacement
|
||||
- `litellm_jwt_key_mapping` accepts `token_id` as an alternative to `key`, so a
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ resource "litellm_model" "advanced_gpt4" {
|
|||
tier = "paid"
|
||||
team_id = "team-123"
|
||||
mode = "chat"
|
||||
tags = ["team:platform"]
|
||||
reasoning_effort = "medium"
|
||||
thinking_enabled = true
|
||||
thinking_budget_tokens = 1024
|
||||
|
|
@ -122,6 +123,8 @@ The following arguments are supported:
|
|||
|
||||
* `litellm_credential_name` - (Optional) string. Name of a LiteLLM credential to use for this model.
|
||||
|
||||
* `tags` - (Optional) list(string). Tags applied to this model deployment, used for [tag-based routing](https://docs.litellm.ai/docs/proxy/tag_routing) so requests carrying a matching tag are routed to it.
|
||||
|
||||
* `tier` - (Optional) string. The usage tier for this model. Valid values are `"free"` or `"paid"`. Default: `"free"`.
|
||||
|
||||
* `team_id` - (Optional) string. Associate the model with a specific team.
|
||||
|
|
|
|||
|
|
@ -175,6 +175,12 @@ func resourceLiteLLMModel() *schema.Resource {
|
|||
Optional: true,
|
||||
Description: "Name of the LiteLLM credential to use",
|
||||
},
|
||||
"tags": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Description: "Tags for tag-based routing (see https://docs.litellm.ai/docs/proxy/tag_routing)",
|
||||
},
|
||||
"additional_litellm_params": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
|
|
|
|||
|
|
@ -163,6 +163,9 @@ func createOrUpdateModel(d *schema.ResourceData, m interface{}, isUpdate bool) e
|
|||
if thinking != nil {
|
||||
litellmParams["thinking"] = thinking
|
||||
}
|
||||
if v, ok := d.GetOk("tags"); ok || d.HasChange("tags") {
|
||||
litellmParams["tags"] = v
|
||||
}
|
||||
|
||||
// Add additional parameters if provided
|
||||
if additionalParams, ok := d.GetOk("additional_litellm_params"); ok {
|
||||
|
|
@ -327,6 +330,11 @@ func resourceLiteLLMModelRead(d *schema.ResourceData, m interface{}) error {
|
|||
d.Set("tier", GetStringValue(modelResp.ModelInfo.Tier, d.Get("tier").(string)))
|
||||
d.Set("mode", GetStringValue(modelResp.ModelInfo.Mode, d.Get("mode").(string)))
|
||||
d.Set("team_id", GetStringValue(modelResp.ModelInfo.TeamID, d.Get("team_id").(string)))
|
||||
if modelResp.LiteLLMParams.Tags != nil {
|
||||
d.Set("tags", modelResp.LiteLLMParams.Tags)
|
||||
} else {
|
||||
d.Set("tags", d.Get("tags"))
|
||||
}
|
||||
|
||||
// Preserve credential name from state since it might not be returned by API
|
||||
d.Set("litellm_credential_name", d.Get("litellm_credential_name").(string))
|
||||
|
|
|
|||
151
terraform/provider/litellm/resource_model_tags_test.go
Normal file
151
terraform/provider/litellm/resource_model_tags_test.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
package litellm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
func newModelTestServer(t *testing.T, captured *map[string]interface{}, infoBody string) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.URL.Path {
|
||||
case endpointModelNew, endpointModelUpdate:
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
json.Unmarshal(body, captured)
|
||||
w.Write([]byte(infoBody))
|
||||
case endpointModelInfo:
|
||||
w.Write([]byte(infoBody))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func newModelResourceData(t *testing.T, raw map[string]interface{}) *schema.ResourceData {
|
||||
t.Helper()
|
||||
return schema.TestResourceDataRaw(t, resourceLiteLLMModel().Schema, raw)
|
||||
}
|
||||
|
||||
func TestModelCreateSendsTags(t *testing.T) {
|
||||
var captured map[string]interface{}
|
||||
infoBody := `{
|
||||
"model_name": "gpt-4.1",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-4.1",
|
||||
"custom_llm_provider": "azure",
|
||||
"tags": ["advisor"]
|
||||
},
|
||||
"model_info": {
|
||||
"id": "model-1",
|
||||
"db_model": true,
|
||||
"base_model": "gpt-4.1",
|
||||
"tier": "free",
|
||||
"mode": "chat"
|
||||
}
|
||||
}`
|
||||
srv := newModelTestServer(t, &captured, infoBody)
|
||||
defer srv.Close()
|
||||
|
||||
d := newModelResourceData(t, map[string]interface{}{
|
||||
"model_name": "gpt-4.1",
|
||||
"custom_llm_provider": "azure",
|
||||
"base_model": "gpt-4.1",
|
||||
"tags": []interface{}{"advisor"},
|
||||
})
|
||||
|
||||
if err := resourceLiteLLMModelCreate(d, NewClient(srv.URL, "test-key", true)); err != nil {
|
||||
t.Fatalf("create failed: %v", err)
|
||||
}
|
||||
|
||||
litellmParams, ok := captured["litellm_params"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("payload litellm_params = %v, want a map", captured["litellm_params"])
|
||||
}
|
||||
wantTags := []interface{}{"advisor"}
|
||||
if got := litellmParams["tags"]; !reflect.DeepEqual(got, wantTags) {
|
||||
t.Fatalf("payload litellm_params.tags = %v, want %v", got, wantTags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelReadPopulatesTagsFromResponse(t *testing.T) {
|
||||
var captured map[string]interface{}
|
||||
infoBody := `{
|
||||
"model_name": "gpt-4.1",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-4.1",
|
||||
"custom_llm_provider": "azure",
|
||||
"tags": ["advisor", "callbot"]
|
||||
},
|
||||
"model_info": {
|
||||
"id": "model-1",
|
||||
"db_model": true,
|
||||
"base_model": "gpt-4.1",
|
||||
"tier": "free",
|
||||
"mode": "chat"
|
||||
}
|
||||
}`
|
||||
srv := newModelTestServer(t, &captured, infoBody)
|
||||
defer srv.Close()
|
||||
|
||||
d := newModelResourceData(t, map[string]interface{}{
|
||||
"model_name": "gpt-4.1",
|
||||
"custom_llm_provider": "azure",
|
||||
"base_model": "gpt-4.1",
|
||||
})
|
||||
d.SetId("model-1")
|
||||
|
||||
if err := resourceLiteLLMModelRead(d, NewClient(srv.URL, "test-key", true)); err != nil {
|
||||
t.Fatalf("read failed: %v", err)
|
||||
}
|
||||
|
||||
wantTags := []interface{}{"advisor", "callbot"}
|
||||
if got := d.Get("tags"); !reflect.DeepEqual(got, wantTags) {
|
||||
t.Fatalf("state tags = %v, want %v", got, wantTags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelUpdateOmitsTagsWhenUnset(t *testing.T) {
|
||||
var captured map[string]interface{}
|
||||
infoBody := `{
|
||||
"model_name": "gpt-4.1",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-4.1",
|
||||
"custom_llm_provider": "azure"
|
||||
},
|
||||
"model_info": {
|
||||
"id": "model-1",
|
||||
"db_model": true,
|
||||
"base_model": "gpt-4.1",
|
||||
"tier": "free",
|
||||
"mode": "chat"
|
||||
}
|
||||
}`
|
||||
srv := newModelTestServer(t, &captured, infoBody)
|
||||
defer srv.Close()
|
||||
|
||||
d := newModelResourceData(t, map[string]interface{}{
|
||||
"model_name": "gpt-4.1",
|
||||
"custom_llm_provider": "azure",
|
||||
"base_model": "gpt-4.1",
|
||||
})
|
||||
d.SetId("model-1")
|
||||
|
||||
if err := resourceLiteLLMModelUpdate(d, NewClient(srv.URL, "test-key", true)); err != nil {
|
||||
t.Fatalf("update failed: %v", err)
|
||||
}
|
||||
|
||||
litellmParams, ok := captured["litellm_params"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("payload litellm_params = %v, want a map", captured["litellm_params"])
|
||||
}
|
||||
if _, ok := litellmParams["tags"]; ok {
|
||||
t.Fatalf("payload litellm_params.tags = %v, want omitted when unset", litellmParams["tags"])
|
||||
}
|
||||
}
|
||||
|
|
@ -104,6 +104,7 @@ type LiteLLMParams struct {
|
|||
VertexProject string `json:"vertex_project,omitempty"`
|
||||
VertexLocation string `json:"vertex_location,omitempty"`
|
||||
VertexCredentials string `json:"vertex_credentials,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// ModelInfo represents information about a model.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue