diff --git a/pkgs/sdk/server-ai/src/Config/ConfigFactory.cs b/pkgs/sdk/server-ai/src/Config/ConfigFactory.cs index fcad708f..c4f4ca81 100644 --- a/pkgs/sdk/server-ai/src/Config/ConfigFactory.cs +++ b/pkgs/sdk/server-ai/src/Config/ConfigFactory.cs @@ -48,6 +48,24 @@ public LdAiCompletionConfig BuildCompletionConfig( var (enabled, variationKey, version, mode) = ParseMeta(ldValue); + // A disabled variation is served as {"_ldMeta": {"enabled": false}} with no mode field, + // so check the disabled state before comparing mode. Otherwise the missing mode defaults + // to "completion" and trips the mode-mismatch path for agent and judge callers. + if (!enabled) + { + return new LdAiCompletionConfig( + key, + enabled: false, + variationKey, + version, + messages: new List(), + tools: ImmutableDictionary.Empty, + judgeConfiguration: null, + model: null, + provider: null, + trackerFactory); + } + if (mode != LdAiCompletionConfig.Mode) { _logger.Warn( @@ -117,6 +135,24 @@ public LdAiAgentConfig BuildAgentConfig( var (enabled, variationKey, version, mode) = ParseMeta(ldValue); + // A disabled variation is served as {"_ldMeta": {"enabled": false}} with no mode field, + // so check the disabled state before comparing mode. Otherwise the missing mode defaults + // to "completion" and trips the mode-mismatch path for agent and judge callers. + if (!enabled) + { + return new LdAiAgentConfig( + key, + enabled: false, + variationKey, + version, + instructions: null, + tools: ImmutableDictionary.Empty, + model: null, + provider: null, + judgeConfiguration: null, + trackerFactory); + } + if (mode != LdAiAgentConfig.Mode) { _logger.Warn( @@ -184,6 +220,23 @@ public LdAiJudgeConfig BuildJudgeConfig( var (enabled, variationKey, version, mode) = ParseMeta(ldValue); + // A disabled variation is served as {"_ldMeta": {"enabled": false}} with no mode field, + // so check the disabled state before comparing mode. Otherwise the missing mode defaults + // to "completion" and trips the mode-mismatch path for agent and judge callers. + if (!enabled) + { + return new LdAiJudgeConfig( + key, + enabled: false, + variationKey, + version, + messages: new List(), + evaluationMetricKey: null, + model: null, + provider: null, + trackerFactory); + } + if (mode != LdAiJudgeConfig.Mode) { _logger.Warn( diff --git a/pkgs/sdk/server-ai/test/LdAiClientAgentJudgeTest.cs b/pkgs/sdk/server-ai/test/LdAiClientAgentJudgeTest.cs index d84ceb65..96bc622d 100644 --- a/pkgs/sdk/server-ai/test/LdAiClientAgentJudgeTest.cs +++ b/pkgs/sdk/server-ai/test/LdAiClientAgentJudgeTest.cs @@ -85,6 +85,33 @@ public void AgentConfig_ModeMismatch_ReturnsCallerDefault() ), Times.Once); } + [Fact] + public void AgentConfig_DisabledVariation_ReturnsDisabledAgentWithoutModeMismatchWarning() + { + var (mockClient, mockLogger, client) = MakeClient(); + + // A disabled variation is served with no mode field, so its mode defaults to "completion". + const string json = """ + { + "_ldMeta": {"variationKey": "v1", "enabled": false} + } + """; + + mockClient.Setup(x => x.JsonVariation("agent-key", It.IsAny(), It.IsAny())) + .Returns(LdValue.Parse(json)); + + var defaultConfig = LdAiAgentConfigDefault.New().SetInstructions("fallback").Build(); + var result = client.AgentConfig("agent-key", Context.New("user"), defaultConfig); + + Assert.IsType(result); + Assert.False(result.Enabled); + + mockLogger.Verify(x => x.Warn( + It.Is(s => s.Contains("AI Config mode mismatch")), + It.IsAny() + ), Times.Never); + } + [Fact] public void AgentConfig_InstructionsInterpolated() { @@ -276,6 +303,36 @@ public void JudgeConfig_ModeMismatch_ReturnsCallerDefault() Assert.Equal("$ld:ai:judge:fallback", result.EvaluationMetricKey); } + [Fact] + public void JudgeConfig_DisabledVariation_ReturnsDisabledJudgeWithoutModeMismatchWarning() + { + var (mockClient, mockLogger, client) = MakeClient(); + + // A disabled variation is served with no mode field, so its mode defaults to "completion". + const string json = """ + { + "_ldMeta": {"variationKey": "v1", "enabled": false} + } + """; + + mockClient.Setup(x => x.JsonVariation("judge-key", It.IsAny(), It.IsAny())) + .Returns(LdValue.Parse(json)); + + var defaultConfig = LdAiJudgeConfigDefault.New() + .SetEvaluationMetricKey("$ld:ai:judge:fallback") + .Build(); + + var result = client.JudgeConfig("judge-key", Context.New("user"), defaultConfig); + + Assert.IsType(result); + Assert.False(result.Enabled); + + mockLogger.Verify(x => x.Warn( + It.Is(s => s.Contains("AI Config mode mismatch")), + It.IsAny() + ), Times.Never); + } + [Fact] public void JudgeConfig_FiresUsageEvent() {