config.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. Copyright 2026 LocoStack.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package litellm
  14. import (
  15. "fmt"
  16. "strconv"
  17. "strings"
  18. "github.com/LocoStack/loco-operator/api/v1alpha1"
  19. )
  20. type LiteLLMConfig struct {
  21. ModelList []modelEntry `yaml:"model_list"`
  22. MCPServers map[string]mcpServerEntry `yaml:"mcp_servers,omitempty"`
  23. LiteLLMSettings *liteLLMSettings `yaml:"litellm_settings,omitempty"`
  24. GeneralSettings *liteLLMGeneralSettings `yaml:"general_settings,omitempty"`
  25. EnvVars map[string]string `yaml:"environment_variables,omitempty"`
  26. }
  27. type modelEntry struct {
  28. ModelName string `yaml:"model_name"`
  29. LiteLLMParams map[string]any `yaml:"litellm_params"`
  30. }
  31. type mcpServerEntry struct {
  32. // url is the HTTP/SSE endpoint for sse and http transports.
  33. URL string `yaml:"url,omitempty"`
  34. // transport selects the MCP transport: sse (default), http, or stdio.
  35. Transport string `yaml:"transport,omitempty"`
  36. // --- authentication ---
  37. // authType selects the auth method: api_key, bearer_token, basic,
  38. // authorization, oauth2, or aws_sigv4.
  39. AuthType string `yaml:"auth_type,omitempty"`
  40. // authValue is the credential for api_key, bearer_token, basic, and
  41. // authorization auth types. May reference an env var via os.environ/VAR.
  42. AuthValue string `yaml:"auth_value,omitempty"`
  43. // --- headers ---
  44. // staticHeaders are key-value pairs sent with every request to this server.
  45. StaticHeaders map[string]string `yaml:"static_headers,omitempty"`
  46. // extraHeaders lists client request header names that LiteLLM should
  47. // forward to this MCP server.
  48. ExtraHeaders []string `yaml:"extra_headers,omitempty"`
  49. // allowAllKeys grants every LiteLLM API key access to this server when true.
  50. AllowAllKeys bool `yaml:"allow_all_keys,omitempty"`
  51. }
  52. type liteLLMSettings struct {
  53. SuccessCallback []string `yaml:"success_callback,omitempty"`
  54. FailureCallback []string `yaml:"failure_callback,omitempty"`
  55. JSONLogs bool `yaml:"json_logs,omitempty"`
  56. }
  57. type liteLLMGeneralSettings struct {
  58. MasterKey string `yaml:"master_key,omitempty"`
  59. }
  60. type LiteLLMConfigBuilder struct {
  61. MasterKeyEnvName string
  62. Stack *v1alpha1.Stack
  63. ObservabilityComponent *v1alpha1.Component
  64. ExternalModels []*v1alpha1.ExternalModel
  65. ManagedModels []*v1alpha1.ManagedModel
  66. }
  67. func (l *LiteLLMConfigBuilder) BuildLiteLLMConfig() (LiteLLMConfig, error) {
  68. modelList := []modelEntry{}
  69. for _, em := range l.ExternalModels {
  70. entry := buildExternalModelEntry(em)
  71. modelList = append(modelList, entry)
  72. }
  73. for _, mm := range l.ManagedModels {
  74. if mm.Status.Endpoint == "" {
  75. continue
  76. }
  77. entry := buildManagedModelEntry(mm)
  78. modelList = append(modelList, entry)
  79. }
  80. phoenixEnabled := phoenixEnabled(l.Stack)
  81. successCB := []string{}
  82. failureCB := []string{}
  83. if phoenixEnabled {
  84. successCB = append(successCB, "arize_phoenix")
  85. failureCB = append(failureCB, "arize_phoenix")
  86. }
  87. cfg := LiteLLMConfig{
  88. ModelList: modelList,
  89. MCPServers: map[string]mcpServerEntry{},
  90. LiteLLMSettings: &liteLLMSettings{
  91. SuccessCallback: successCB,
  92. FailureCallback: failureCB,
  93. JSONLogs: true,
  94. },
  95. GeneralSettings: &liteLLMGeneralSettings{
  96. MasterKey: "os.environ/" + l.MasterKeyEnvName,
  97. },
  98. }
  99. if phoenixEnabled {
  100. cfg.EnvVars = map[string]string{
  101. "PHOENIX_PROJECT_NAME": "locostack",
  102. "PHOENIX_COLLECTOR_HTTP_ENDPOINT": l.ObservabilityComponent.Status.Endpoint + "/v1/traces",
  103. }
  104. }
  105. return cfg, nil
  106. }
  107. func phoenixEnabled(stack *v1alpha1.Stack) bool {
  108. if stack == nil {
  109. return false
  110. }
  111. if stack.Spec.Observability == nil {
  112. return false
  113. }
  114. if !stack.Spec.Observability.Enabled {
  115. return false
  116. }
  117. if stack.Spec.Observability.Template != nil && stack.Spec.Observability.Template.Name != "phoenix" {
  118. return false
  119. }
  120. return true
  121. }
  122. func AuthEnvVarName(kind string, name string) string {
  123. return fmt.Sprintf("%s_%s_AUTH", strings.ToUpper(kind), strings.ToUpper(strings.ReplaceAll(name, "-", "_")))
  124. }
  125. // buildExternalModelEntry converts an ExternalModel into a LiteLLM model_list entry.
  126. func buildExternalModelEntry(em *v1alpha1.ExternalModel) modelEntry {
  127. params := map[string]any{
  128. "model": fmt.Sprintf("%s/%s", em.Spec.Provider, em.Spec.ProviderModel),
  129. }
  130. if em.Spec.Auth != nil && (em.Spec.Auth.APIKey != nil || em.Spec.Auth.BearerToken != nil) {
  131. params["api_key"] = "os.environ/" + AuthEnvVarName("ExternalModel", em.Name)
  132. }
  133. if em.Spec.APIBase != "" {
  134. params["api_base"] = em.Spec.APIBase
  135. }
  136. if em.Spec.APIVersion != "" {
  137. params["api_version"] = em.Spec.APIVersion
  138. }
  139. switch em.Spec.Category {
  140. case "embedding":
  141. params["mode"] = "embedding"
  142. case "reranker":
  143. params["mode"] = "rerank"
  144. }
  145. for k, v := range em.Spec.ExtraParams {
  146. params[k] = v
  147. }
  148. if em.Spec.DefaultInferenceParams != nil {
  149. applyInferenceParams(params, em.Spec.DefaultInferenceParams)
  150. }
  151. return modelEntry{
  152. ModelName: em.Spec.ModelName,
  153. LiteLLMParams: params,
  154. }
  155. }
  156. // buildManagedModelEntry converts a ManagedModel into a LiteLLM model_list entry.
  157. func buildManagedModelEntry(mm *v1alpha1.ManagedModel) modelEntry {
  158. // litellm's rerank dispatch does not recognize "openai" as a provider;
  159. // "hosted_vllm" is the generic self-hosted provider for the /rerank call type.
  160. provider := "openai"
  161. if mm.Spec.Category == "reranker" {
  162. provider = "hosted_vllm"
  163. }
  164. params := map[string]any{
  165. "model": fmt.Sprintf("%s/%s", provider, mm.Spec.ModelName),
  166. "api_base": mm.Status.Endpoint + "/v1",
  167. "api_key": "not-set",
  168. }
  169. switch mm.Spec.Category {
  170. case "embedding":
  171. params["mode"] = "embedding"
  172. case "reranker":
  173. params["mode"] = "rerank"
  174. }
  175. return modelEntry{
  176. ModelName: mm.Spec.ModelName,
  177. LiteLLMParams: params,
  178. }
  179. }
  180. func applyInferenceParams(params map[string]any, inf *v1alpha1.InferenceParameters) {
  181. if inf.Temperature != "" {
  182. if v, err := strconv.ParseFloat(inf.Temperature, 64); err == nil {
  183. params["temperature"] = v
  184. }
  185. }
  186. if inf.TopP != "" {
  187. if v, err := strconv.ParseFloat(inf.TopP, 64); err == nil {
  188. params["top_p"] = v
  189. }
  190. }
  191. if inf.MaxTokens != nil {
  192. params["max_tokens"] = *inf.MaxTokens
  193. }
  194. for k, v := range inf.Extra {
  195. params[k] = v
  196. }
  197. }