diff --git a/.ai/spec/what/sandbox-execution.md b/.ai/spec/what/sandbox-execution.md index 7ce586b5..89e83948 100644 --- a/.ai/spec/what/sandbox-execution.md +++ b/.ai/spec/what/sandbox-execution.md @@ -56,5 +56,5 @@ Behavioral specification for how workflow steps run inside ephemeral **sandboxes - [PLANNED: OLS-2957] **Sandbox template management** UX and CRD ergonomics (base/derived lifecycle, versioning) may change operator/template coupling described in rules 2–4. - [PLANNED: OLS-3038] **TLS verification and network policy** for agent traffic may replace permissive internal TLS client behavior. -- [PLANNED: OLS-3044] **Provider parity**: environment variable contract for non-Claude providers in templates MUST track sandbox image capabilities. +- **Sandbox provider selection**: Derived templates MUST set `LIGHTSPEED_AGENT_PROVIDER` from `LLMProvider.spec.type` (`Anthropic`/`AWSBedrock` → `claude`; `OpenAI`/`AzureOpenAI` → `openai`; `GoogleCloudVertex` → routed by `googleCloudVertex.modelProvider`: `Anthropic` → `claude`, `Google` → `gemini`, `OpenAI` → `openai`). Model env var follows the provider: `ANTHROPIC_MODEL`, `GEMINI_MODEL`, or `OPENAI_MODEL`. - [PLANNED: OLS-2894] Support **multiple concurrent skills images** in template derivation beyond the first `skills` entry if product requires composite skill bundles. diff --git a/api/v1alpha1/llmprovider_types.go b/api/v1alpha1/llmprovider_types.go index e95ba0cf..0e129781 100644 --- a/api/v1alpha1/llmprovider_types.go +++ b/api/v1alpha1/llmprovider_types.go @@ -72,6 +72,20 @@ type AnthropicConfig struct { URL string `json:"url,omitempty"` } +// GoogleCloudVertexModelProvider selects which model provider stack talks to Vertex AI. +// +// +kubebuilder:validation:Enum=Anthropic;Google;OpenAI +type GoogleCloudVertexModelProvider string + +const ( + // GoogleCloudVertexModelProviderAnthropic uses Anthropic models on Vertex (Claude SDK). + GoogleCloudVertexModelProviderAnthropic GoogleCloudVertexModelProvider = "Anthropic" + // GoogleCloudVertexModelProviderGoogle uses Google models on Vertex (GenAI SDK). + GoogleCloudVertexModelProviderGoogle GoogleCloudVertexModelProvider = "Google" + // GoogleCloudVertexModelProviderOpenAI uses OpenAI-compatible models on Vertex (OpenAI SDK). + GoogleCloudVertexModelProviderOpenAI GoogleCloudVertexModelProvider = "OpenAI" +) + // GoogleCloudVertexConfig contains configuration for the Google Cloud Vertex AI provider. type GoogleCloudVertexConfig struct { // credentialsSecret references a Secret in the operator namespace @@ -100,6 +114,13 @@ type GoogleCloudVertexConfig struct { // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z][a-z0-9-]*[a-z0-9]$')",message="region must contain only lowercase letters, digits, and hyphens, start with a letter, and not end with a hyphen" Region string `json:"region,omitempty"` + // modelProvider selects which model provider stack talks to Vertex AI (required). + // "Anthropic" uses Anthropic models on Vertex; "Google" uses Google models on Vertex; + // "OpenAI" uses OpenAI-compatible models on Vertex. + // Enum is defined on GoogleCloudVertexModelProvider. + // +required + ModelProvider GoogleCloudVertexModelProvider `json:"modelProvider"` + // url is an optional override for the Vertex AI API endpoint. // Only needed for custom deployments or API proxies. // Must be a valid HTTP or HTTPS URL with a hostname. Paths and query @@ -312,6 +333,7 @@ type LLMProviderSpec struct { // name: llm-credentials // projectID: my-gcp-project // region: us-central1 +// modelProvider: Anthropic type LLMProvider struct { metav1.TypeMeta `json:",inline"` diff --git a/config/crd/bases/agentic.openshift.io_llmproviders.yaml b/config/crd/bases/agentic.openshift.io_llmproviders.yaml index 18509a78..d9167138 100644 --- a/config/crd/bases/agentic.openshift.io_llmproviders.yaml +++ b/config/crd/bases/agentic.openshift.io_llmproviders.yaml @@ -36,7 +36,8 @@ spec: AI provider (model specified on Agent, not here):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: LLMProvider\n\tmetadata:\n\t name: vertex-ai\n\tspec:\n\t type: GoogleCloudVertex\n\t \ googleCloudVertex:\n\t credentialsSecret:\n\t name: llm-credentials\n\t - \ projectID: my-gcp-project\n\t region: us-central1" + \ projectID: my-gcp-project\n\t region: us-central1\n\t modelProvider: + Anthropic" properties: apiVersion: description: |- @@ -268,6 +269,17 @@ spec: required: - name type: object + modelProvider: + description: |- + modelProvider selects which model provider stack talks to Vertex AI (required). + "Anthropic" uses Anthropic models on Vertex; "Google" uses Google models on Vertex; + "OpenAI" uses OpenAI-compatible models on Vertex. + Enum is defined on GoogleCloudVertexModelProvider. + enum: + - Anthropic + - Google + - OpenAI + type: string projectID: description: |- projectID is the Google Cloud Project ID where Vertex AI is enabled. @@ -316,6 +328,7 @@ spec: rule: '!self.contains(''#'')' required: - credentialsSecret + - modelProvider - projectID - region type: object diff --git a/controller/proposal/reconciler_test.go b/controller/proposal/reconciler_test.go index 21fd84b6..dcedece2 100644 --- a/controller/proposal/reconciler_test.go +++ b/controller/proposal/reconciler_test.go @@ -106,6 +106,7 @@ func testLLM(name string) *agenticv1alpha1.LLMProvider { CredentialsSecret: agenticv1alpha1.SecretReference{Name: "llm-secret"}, ProjectID: "test-project", Region: "us-central1", + ModelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic, }, }, } diff --git a/controller/proposal/sandbox_agent.go b/controller/proposal/sandbox_agent.go index d12edd46..7eacc815 100644 --- a/controller/proposal/sandbox_agent.go +++ b/controller/proposal/sandbox_agent.go @@ -14,7 +14,7 @@ import ( ) const ( - defaultSandboxTimeout = 5 * time.Minute + defaultSandboxTimeout = 5 * time.Minute defaultBaseTemplateName = "lightspeed-agent" ) diff --git a/controller/proposal/sandbox_agent_test.go b/controller/proposal/sandbox_agent_test.go index 2bbc4977..b28a18b4 100644 --- a/controller/proposal/sandbox_agent_test.go +++ b/controller/proposal/sandbox_agent_test.go @@ -58,11 +58,11 @@ func newTestSandboxAgentCaller(sandbox *mockSandboxProvider, httpClient *mockHTT fc := fake.NewClientBuilder().WithScheme(testScheme()).Build() _ = fc.Create(context.Background(), fakeBaseTemplate()) return &SandboxAgentCaller{ - Sandbox: sandbox, - K8sClient: fc, - ClientFactory: func(_ string) AgentHTTPClientInterface { return httpClient }, - Namespace: "test-ns", - Timeout: 5 * time.Minute, + Sandbox: sandbox, + K8sClient: fc, + ClientFactory: func(_ string) AgentHTTPClientInterface { return httpClient }, + Namespace: "test-ns", + Timeout: 5 * time.Minute, } } @@ -73,11 +73,11 @@ func newTestSandboxAgentCallerWithProposal(sandbox *mockSandboxProvider, httpCli Build() _ = fc.Create(context.Background(), fakeBaseTemplate()) return &SandboxAgentCaller{ - Sandbox: sandbox, - K8sClient: fc, - ClientFactory: func(_ string) AgentHTTPClientInterface { return httpClient }, - Namespace: "test-ns", - Timeout: 5 * time.Minute, + Sandbox: sandbox, + K8sClient: fc, + ClientFactory: func(_ string) AgentHTTPClientInterface { return httpClient }, + Namespace: "test-ns", + Timeout: 5 * time.Minute, } } diff --git a/controller/proposal/sandbox_templates.go b/controller/proposal/sandbox_templates.go index d7c01d90..c5b764ea 100644 --- a/controller/proposal/sandbox_templates.go +++ b/controller/proposal/sandbox_templates.go @@ -26,7 +26,8 @@ var sandboxTemplateGVK = schema.GroupVersionKind{ } const ( - agentModeEnvVar = "LIGHTSPEED_MODE" + agentModeEnvVar = "LIGHTSPEED_MODE" + sandboxAgentProviderEnvVar = "LIGHTSPEED_AGENT_PROVIDER" vertexCredsMountPath = "/var/secrets/google" vertexCredsFileName = "credentials.json" @@ -245,14 +246,74 @@ func providerURL(llm *agenticv1alpha1.LLMProvider) string { } } +func vertexSandboxAgentProvider(mp agenticv1alpha1.GoogleCloudVertexModelProvider) (string, error) { + switch mp { + case agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic: + return "claude", nil + case agenticv1alpha1.GoogleCloudVertexModelProviderGoogle: + return "gemini", nil + case agenticv1alpha1.GoogleCloudVertexModelProviderOpenAI: + return "openai", nil + default: + return "", fmt.Errorf("unsupported googleCloudVertex.modelProvider %q", mp) + } +} + +func sandboxAgentProviderValue(llm *agenticv1alpha1.LLMProvider) (string, error) { + switch llm.Spec.Type { + case agenticv1alpha1.LLMProviderAnthropic, agenticv1alpha1.LLMProviderAWSBedrock: + return "claude", nil + case agenticv1alpha1.LLMProviderGoogleCloudVertex: + return vertexSandboxAgentProvider(llm.Spec.GoogleCloudVertex.ModelProvider) + case agenticv1alpha1.LLMProviderOpenAI, agenticv1alpha1.LLMProviderAzureOpenAI: + return "openai", nil + default: + return "", fmt.Errorf("unsupported LLM provider type %q", llm.Spec.Type) + } +} + +func vertexModelEnvVar(mp agenticv1alpha1.GoogleCloudVertexModelProvider) string { + switch mp { + case agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic: + return "ANTHROPIC_MODEL" + case agenticv1alpha1.GoogleCloudVertexModelProviderGoogle: + return "GEMINI_MODEL" + case agenticv1alpha1.GoogleCloudVertexModelProviderOpenAI: + return "OPENAI_MODEL" + default: + return "" + } +} + +func modelEnvVarName(llm *agenticv1alpha1.LLMProvider) string { + switch llm.Spec.Type { + case agenticv1alpha1.LLMProviderOpenAI, agenticv1alpha1.LLMProviderAzureOpenAI: + return "OPENAI_MODEL" + case agenticv1alpha1.LLMProviderGoogleCloudVertex: + return vertexModelEnvVar(llm.Spec.GoogleCloudVertex.ModelProvider) + default: + return "ANTHROPIC_MODEL" + } +} + func patchLLMCredentials(tmpl *unstructured.Unstructured, llm *agenticv1alpha1.LLMProvider, model string) error { secretName := credentialsSecretName(llm) if err := addEnvFromSecret(tmpl, secretName); err != nil { return fmt.Errorf("add credentials envFrom: %w", err) } - if err := setEnvVar(tmpl, "ANTHROPIC_MODEL", model); err != nil { - return fmt.Errorf("set ANTHROPIC_MODEL: %w", err) + + providerValue, err := sandboxAgentProviderValue(llm) + if err != nil { + return err + } + if err := setEnvVar(tmpl, sandboxAgentProviderEnvVar, providerValue); err != nil { + return fmt.Errorf("set %s: %w", sandboxAgentProviderEnvVar, err) + } + + modelEnv := modelEnvVarName(llm) + if err := setEnvVar(tmpl, modelEnv, model); err != nil { + return fmt.Errorf("set %s: %w", modelEnv, err) } if u := providerURL(llm); u != "" { @@ -264,8 +325,10 @@ func patchLLMCredentials(tmpl *unstructured.Unstructured, llm *agenticv1alpha1.L switch llm.Spec.Type { case agenticv1alpha1.LLMProviderGoogleCloudVertex: cfg := llm.Spec.GoogleCloudVertex - if err := setEnvVar(tmpl, "CLAUDE_CODE_USE_VERTEX", "1"); err != nil { - return fmt.Errorf("set CLAUDE_CODE_USE_VERTEX: %w", err) + if cfg.ModelProvider == agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic { + if err := setEnvVar(tmpl, "CLAUDE_CODE_USE_VERTEX", "1"); err != nil { + return fmt.Errorf("set CLAUDE_CODE_USE_VERTEX: %w", err) + } } if err := setEnvVar(tmpl, "GCP_PROJECT", cfg.ProjectID); err != nil { return fmt.Errorf("set GCP_PROJECT: %w", err) diff --git a/controller/proposal/sandbox_templates_test.go b/controller/proposal/sandbox_templates_test.go index 71968e4a..f6ac9a83 100644 --- a/controller/proposal/sandbox_templates_test.go +++ b/controller/proposal/sandbox_templates_test.go @@ -15,7 +15,7 @@ func testLLMProvider(providerType agenticv1alpha1.LLMProviderType) *agenticv1alp case agenticv1alpha1.LLMProviderAnthropic: spec.Anthropic = agenticv1alpha1.AnthropicConfig{CredentialsSecret: creds} case agenticv1alpha1.LLMProviderGoogleCloudVertex: - spec.GoogleCloudVertex = agenticv1alpha1.GoogleCloudVertexConfig{CredentialsSecret: creds, ProjectID: "test-project", Region: "us-central1"} + spec.GoogleCloudVertex = agenticv1alpha1.GoogleCloudVertexConfig{CredentialsSecret: creds, ProjectID: "test-project", Region: "us-central1", ModelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic} case agenticv1alpha1.LLMProviderOpenAI: spec.OpenAI = agenticv1alpha1.OpenAIConfig{CredentialsSecret: creds} case agenticv1alpha1.LLMProviderAzureOpenAI: @@ -43,6 +43,21 @@ func testLLMProviderWithURL(providerType agenticv1alpha1.LLMProviderType, u stri return p } +func testVertexLLMProvider(mp agenticv1alpha1.GoogleCloudVertexModelProvider) *agenticv1alpha1.LLMProvider { + creds := agenticv1alpha1.SecretReference{Name: "my-llm-secret"} + return &agenticv1alpha1.LLMProvider{ + Spec: agenticv1alpha1.LLMProviderSpec{ + Type: agenticv1alpha1.LLMProviderGoogleCloudVertex, + GoogleCloudVertex: agenticv1alpha1.GoogleCloudVertexConfig{ + CredentialsSecret: creds, + ProjectID: "test-project", + Region: "us-central1", + ModelProvider: mp, + }, + }, + } +} + func emptyTemplate() *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]any{ @@ -246,6 +261,12 @@ func TestPatchLLMCredentials_Anthropic(t *testing.T) { } envs := getEnvVars(tmpl) + if e, ok := findEnv(envs, "LIGHTSPEED_AGENT_PROVIDER"); !ok { + t.Error("missing LIGHTSPEED_AGENT_PROVIDER") + } else if e["value"] != "claude" { + t.Errorf("LIGHTSPEED_AGENT_PROVIDER = %q, want claude", e["value"]) + } + if e, ok := findEnv(envs, "ANTHROPIC_MODEL"); !ok { t.Error("missing ANTHROPIC_MODEL") } else if e["value"] != "claude-opus-4-6" { @@ -259,56 +280,195 @@ func TestPatchLLMCredentials_Anthropic(t *testing.T) { } } +// Vertex env contract per modelProvider: +// +// modelProvider LIGHTSPEED_AGENT_PROVIDER Model env var CLAUDE_CODE_USE_VERTEX +// ───────────── ───────────────────────── ──────────────── ────────────────────── +// Anthropic claude ANTHROPIC_MODEL "1" +// Google gemini GEMINI_MODEL (absent) +// OpenAI openai OPENAI_MODEL (absent) +// +// All three always set: GCP_PROJECT, GCP_REGION, GOOGLE_APPLICATION_CREDENTIALS, +// credentials volume mount at /etc/llm-credentials. func TestPatchLLMCredentials_Vertex(t *testing.T) { + tests := []struct { + name string + modelProvider agenticv1alpha1.GoogleCloudVertexModelProvider + model string + wantProvider string + wantModelEnv string + wantVertex bool // CLAUDE_CODE_USE_VERTEX="1" + }{ + { + name: "Anthropic", + modelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic, + model: "claude-opus-4-6", + wantProvider: "claude", + wantModelEnv: "ANTHROPIC_MODEL", + wantVertex: true, + }, + { + name: "Google", + modelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderGoogle, + model: "gemini-2.5-flash", + wantProvider: "gemini", + wantModelEnv: "GEMINI_MODEL", + wantVertex: false, + }, + { + name: "OpenAI", + modelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderOpenAI, + model: "gpt-4.1", + wantProvider: "openai", + wantModelEnv: "OPENAI_MODEL", + wantVertex: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tmpl := emptyTemplate() + llm := testVertexLLMProvider(tc.modelProvider) + + if err := patchLLMCredentials(tmpl, llm, tc.model); err != nil { + t.Fatalf("patchLLMCredentials: %v", err) + } + + if !hasSecretEnvFrom(tmpl, "my-llm-secret") { + t.Error("missing envFrom secretRef for my-llm-secret") + } + + envs := getEnvVars(tmpl) + + if e, ok := findEnv(envs, "LIGHTSPEED_AGENT_PROVIDER"); !ok { + t.Error("missing LIGHTSPEED_AGENT_PROVIDER") + } else if e["value"] != tc.wantProvider { + t.Errorf("LIGHTSPEED_AGENT_PROVIDER = %q, want %q", e["value"], tc.wantProvider) + } + + if e, ok := findEnv(envs, tc.wantModelEnv); !ok { + t.Errorf("missing %s", tc.wantModelEnv) + } else if e["value"] != tc.model { + t.Errorf("%s = %q, want %q", tc.wantModelEnv, e["value"], tc.model) + } + + if _, ok := findEnv(envs, "CLAUDE_CODE_USE_VERTEX"); ok != tc.wantVertex { + if tc.wantVertex { + t.Error("missing CLAUDE_CODE_USE_VERTEX") + } else { + t.Error("CLAUDE_CODE_USE_VERTEX should not be set") + } + } + + if e, ok := findEnv(envs, "GCP_PROJECT"); !ok { + t.Error("missing GCP_PROJECT") + } else if e["value"] != "test-project" { + t.Errorf("GCP_PROJECT = %q", e["value"]) + } + + if e, ok := findEnv(envs, "GCP_REGION"); !ok { + t.Error("missing GCP_REGION") + } else if e["value"] != "us-central1" { + t.Errorf("GCP_REGION = %q", e["value"]) + } + + if e, ok := findEnv(envs, "GOOGLE_APPLICATION_CREDENTIALS"); !ok { + t.Error("missing GOOGLE_APPLICATION_CREDENTIALS") + } else if e["value"] != vertexCredsMountPath+"/"+vertexCredsFileName { + t.Errorf("GOOGLE_APPLICATION_CREDENTIALS = %q", e["value"]) + } + + containers, _, _ := unstructured.NestedSlice(tmpl.Object, "spec", "podTemplate", "spec", "containers") + container := containers[0].(map[string]any) + mounts, _, _ := unstructured.NestedSlice(container, "volumeMounts") + if len(mounts) != 1 { + t.Fatalf("expected 1 volume mount, got %d", len(mounts)) + } + mount := mounts[0].(map[string]any) + if mount["name"] != llmCredsVolumeName || mount["mountPath"] != vertexCredsMountPath { + t.Errorf("mount = %v", mount) + } + }) + } +} + +func TestPatchLLMCredentials_Bedrock(t *testing.T) { tmpl := emptyTemplate() - llm := testLLMProvider(agenticv1alpha1.LLMProviderGoogleCloudVertex) + llm := testLLMProvider(agenticv1alpha1.LLMProviderAWSBedrock) if err := patchLLMCredentials(tmpl, llm, "claude-opus-4-6"); err != nil { t.Fatalf("patchLLMCredentials: %v", err) } - if !hasSecretEnvFrom(tmpl, "my-llm-secret") { - t.Error("missing envFrom secretRef for my-llm-secret") + envs := getEnvVars(tmpl) + if e, ok := findEnv(envs, "LIGHTSPEED_AGENT_PROVIDER"); !ok { + t.Error("missing LIGHTSPEED_AGENT_PROVIDER") + } else if e["value"] != "claude" { + t.Errorf("LIGHTSPEED_AGENT_PROVIDER = %q, want claude", e["value"]) } - envs := getEnvVars(tmpl) - if e, ok := findEnv(envs, "CLAUDE_CODE_USE_VERTEX"); !ok { - t.Error("missing CLAUDE_CODE_USE_VERTEX") + if e, ok := findEnv(envs, "CLAUDE_CODE_USE_BEDROCK"); !ok { + t.Error("missing CLAUDE_CODE_USE_BEDROCK") } else if e["value"] != "1" { - t.Errorf("CLAUDE_CODE_USE_VERTEX = %q", e["value"]) + t.Errorf("CLAUDE_CODE_USE_BEDROCK = %q", e["value"]) } +} - if e, ok := findEnv(envs, "GOOGLE_APPLICATION_CREDENTIALS"); !ok { - t.Error("missing GOOGLE_APPLICATION_CREDENTIALS") - } else if e["value"] != vertexCredsMountPath+"/"+vertexCredsFileName { - t.Errorf("GOOGLE_APPLICATION_CREDENTIALS = %q", e["value"]) +func TestPatchLLMCredentials_OpenAI(t *testing.T) { + tmpl := emptyTemplate() + llm := testLLMProviderWithURL(agenticv1alpha1.LLMProviderOpenAI, "https://api.example.com/v1") + + if err := patchLLMCredentials(tmpl, llm, "gpt-4.1"); err != nil { + t.Fatalf("patchLLMCredentials: %v", err) } - containers, _, _ := unstructured.NestedSlice(tmpl.Object, "spec", "podTemplate", "spec", "containers") - container := containers[0].(map[string]any) - mounts, _, _ := unstructured.NestedSlice(container, "volumeMounts") - if len(mounts) != 1 { - t.Fatalf("expected 1 volume mount, got %d", len(mounts)) + envs := getEnvVars(tmpl) + if e, ok := findEnv(envs, "LIGHTSPEED_AGENT_PROVIDER"); !ok { + t.Error("missing LIGHTSPEED_AGENT_PROVIDER") + } else if e["value"] != "openai" { + t.Errorf("LIGHTSPEED_AGENT_PROVIDER = %q, want openai", e["value"]) } - mount := mounts[0].(map[string]any) - if mount["name"] != llmCredsVolumeName || mount["mountPath"] != vertexCredsMountPath { - t.Errorf("mount = %v", mount) + + if e, ok := findEnv(envs, "OPENAI_MODEL"); !ok { + t.Error("missing OPENAI_MODEL") + } else if e["value"] != "gpt-4.1" { + t.Errorf("OPENAI_MODEL = %q", e["value"]) + } + + if _, ok := findEnv(envs, "ANTHROPIC_MODEL"); ok { + t.Error("ANTHROPIC_MODEL should not be set for OpenAI provider") + } + + if e, ok := findEnv(envs, "OPENAI_BASE_URL"); !ok { + t.Error("missing OPENAI_BASE_URL") + } else if e["value"] != "https://api.example.com/v1" { + t.Errorf("OPENAI_BASE_URL = %q", e["value"]) } } -func TestPatchLLMCredentials_Bedrock(t *testing.T) { +func TestPatchLLMCredentials_AzureOpenAI(t *testing.T) { tmpl := emptyTemplate() - llm := testLLMProvider(agenticv1alpha1.LLMProviderAWSBedrock) + llm := testLLMProvider(agenticv1alpha1.LLMProviderAzureOpenAI) - if err := patchLLMCredentials(tmpl, llm, "claude-opus-4-6"); err != nil { + if err := patchLLMCredentials(tmpl, llm, "gpt-4.1"); err != nil { t.Fatalf("patchLLMCredentials: %v", err) } envs := getEnvVars(tmpl) - if e, ok := findEnv(envs, "CLAUDE_CODE_USE_BEDROCK"); !ok { - t.Error("missing CLAUDE_CODE_USE_BEDROCK") - } else if e["value"] != "1" { - t.Errorf("CLAUDE_CODE_USE_BEDROCK = %q", e["value"]) + if e, ok := findEnv(envs, "LIGHTSPEED_AGENT_PROVIDER"); !ok { + t.Error("missing LIGHTSPEED_AGENT_PROVIDER") + } else if e["value"] != "openai" { + t.Errorf("LIGHTSPEED_AGENT_PROVIDER = %q, want openai", e["value"]) + } + + if e, ok := findEnv(envs, "OPENAI_MODEL"); !ok { + t.Error("missing OPENAI_MODEL") + } else if e["value"] != "gpt-4.1" { + t.Errorf("OPENAI_MODEL = %q", e["value"]) + } + + if _, ok := findEnv(envs, "ANTHROPIC_MODEL"); ok { + t.Error("ANTHROPIC_MODEL should not be set for AzureOpenAI provider") } } diff --git a/examples/setup/00-llm-providers.yaml b/examples/setup/00-llm-providers.yaml index 237804fa..5366ef46 100644 --- a/examples/setup/00-llm-providers.yaml +++ b/examples/setup/00-llm-providers.yaml @@ -20,3 +20,4 @@ spec: name: llm-credentials projectID: my-gcp-project region: us-central1 + modelProvider: Anthropic diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go index 59a5e2a4..7bbbad05 100644 --- a/test/e2e/helpers_test.go +++ b/test/e2e/helpers_test.go @@ -134,6 +134,7 @@ func createFixtures(t *testing.T, c client.Client) *e2eFixtures { CredentialsSecret: agenticv1alpha1.SecretReference{Name: "e2e-llm-secret"}, ProjectID: "e2e-project", Region: "us-central1", + ModelProvider: agenticv1alpha1.GoogleCloudVertexModelProviderAnthropic, }, }, },