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
11 changes: 6 additions & 5 deletions internal/embed/infrastructure/base/templates/llm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ metadata:
data: {}

---
# Secret for LiteLLM master key and cloud provider API keys.
# Master key is unique per cluster ({{CLUSTER_ID}} resolved at init).
# Provider keys start empty; patched via `obol model setup`.
# Secret for LiteLLM master key. Provider API keys (ANTHROPIC_API_KEY,
# OPENAI_API_KEY, ...) are patched in by `obol model setup` and the
# autoConfigureLLM hook on first up. We deliberately do NOT declare empty
# placeholders here — re-applying this manifest on every `obol stack up`
# would overwrite any keys the user has set, leaving `obol model status`
# in the misleading state of `enabled: true, api_key: false`.
apiVersion: v1
kind: Secret
metadata:
Expand All @@ -107,8 +110,6 @@ metadata:
type: Opaque
stringData:
LITELLM_MASTER_KEY: "sk-obol-{{CLUSTER_ID}}"
ANTHROPIC_API_KEY: ""
OPENAI_API_KEY: ""

---
apiVersion: apps/v1
Expand Down
12 changes: 9 additions & 3 deletions internal/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,9 +630,15 @@ func autoDetectCloudProvider(cfg *config.Config, u *ui.UI) string {
return ""
}

// Already configured — skip.
if model.HasProviderConfigured(cfg, provider) {
return ""
// Skip only when both the model entry AND the API key are present.
// A re-up that lost the Secret's API key (or any drift between
// ConfigMap and Secret) heals here by re-patching from the env var,
// instead of leaving `obol model status` reporting
// `enabled: true, api_key: false` as it did before this check.
if statuses, err := model.GetProviderStatus(cfg); err == nil {
if st, ok := statuses[provider]; ok && st.Enabled && st.HasAPIKey {
return ""
}
}

// Resolve API key: try primary + alt env vars, then .env in dev mode.
Expand Down
14 changes: 14 additions & 0 deletions internal/stack/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,20 @@ func TestLLMTemplate_IncludesPaidRouteAndBuyerSidecar(t *testing.T) {
if strings.Contains(out, "custom_provider_map") {
t.Fatalf("llm template should not require a custom provider:\n%s", out)
}

// Regression guard: provider API keys must not be pre-declared as
// empty placeholders in the bootstrap Secret. If they are, every
// `obol stack up` re-applies the manifest and overwrites whatever
// `obol model setup` (or autoConfigureLLM) patched in, leaving the
// user with `obol model status` reporting `enabled: true, api_key: false`.
for _, forbidden := range []string{
`ANTHROPIC_API_KEY: ""`,
`OPENAI_API_KEY: ""`,
} {
if strings.Contains(out, forbidden) {
t.Fatalf("llm template must not pre-declare empty provider API keys (found %q) — these get clobbered on every `obol stack up`", forbidden)
}
}
}

func TestMergeLiteLLMConfigPreservesChartDefaultsAndPreviousModels(t *testing.T) {
Expand Down
Loading