Skip to content

Commit e15082a

Browse files
committed
fix: Make default optional with a disabled config default
1 parent 3b485fc commit e15082a

5 files changed

Lines changed: 135 additions & 15 deletions

File tree

packages/sdk/server-ai/src/ldai/client.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,27 +72,30 @@ def completion_config(
7272
self,
7373
key: str,
7474
context: Context,
75-
default_value: AICompletionConfigDefault,
75+
default_value: Optional[AICompletionConfigDefault] = None,
7676
variables: Optional[Dict[str, Any]] = None,
7777
) -> AICompletionConfig:
7878
"""
7979
Get the value of a completion configuration.
8080
8181
:param key: The key of the completion configuration.
8282
:param context: The context to evaluate the completion configuration in.
83-
:param default_value: The default value of the completion configuration.
83+
:param default_value: The default value of the completion configuration. When not provided,
84+
a disabled config is used as the fallback.
8485
:param variables: Additional variables for the completion configuration.
8586
:return: The completion configuration with a tracker used for gathering metrics.
8687
"""
8788
self._client.track(_TRACK_USAGE_COMPLETION_CONFIG, context, key, 1)
8889

89-
return self._completion_config(key, context, default_value, variables)
90+
return self._completion_config(
91+
key, context, default_value or AICompletionConfigDefault.disabled(), variables
92+
)
9093

9194
def config(
9295
self,
9396
key: str,
9497
context: Context,
95-
default_value: AICompletionConfigDefault,
98+
default_value: Optional[AICompletionConfigDefault] = None,
9699
variables: Optional[Dict[str, Any]] = None,
97100
) -> AICompletionConfig:
98101
"""
@@ -152,27 +155,30 @@ def judge_config(
152155
self,
153156
key: str,
154157
context: Context,
155-
default_value: AIJudgeConfigDefault,
158+
default_value: Optional[AIJudgeConfigDefault] = None,
156159
variables: Optional[Dict[str, Any]] = None,
157160
) -> AIJudgeConfig:
158161
"""
159162
Get the value of a judge configuration.
160163
161164
:param key: The key of the judge configuration.
162165
:param context: The context to evaluate the judge configuration in.
163-
:param default_value: The default value of the judge configuration.
166+
:param default_value: The default value of the judge configuration. When not provided,
167+
a disabled config is used as the fallback.
164168
:param variables: Additional variables for the judge configuration.
165169
:return: The judge configuration with a tracker used for gathering metrics.
166170
"""
167171
self._client.track(_TRACK_USAGE_JUDGE_CONFIG, context, key, 1)
168172

169-
return self._judge_config(key, context, default_value, variables)
173+
return self._judge_config(
174+
key, context, default_value or AIJudgeConfigDefault.disabled(), variables
175+
)
170176

171177
async def create_judge(
172178
self,
173179
key: str,
174180
context: Context,
175-
default_value: AIJudgeConfigDefault,
181+
default_value: Optional[AIJudgeConfigDefault] = None,
176182
variables: Optional[Dict[str, Any]] = None,
177183
default_ai_provider: Optional[str] = None,
178184
) -> Optional[Judge]:
@@ -222,7 +228,9 @@ async def create_judge(
222228
extended_variables['message_history'] = '{{message_history}}'
223229
extended_variables['response_to_evaluate'] = '{{response_to_evaluate}}'
224230

225-
judge_config = self._judge_config(key, context, default_value, extended_variables)
231+
judge_config = self._judge_config(
232+
key, context, default_value or AIJudgeConfigDefault.disabled(), extended_variables
233+
)
226234

227235
if not judge_config.enabled or not judge_config.tracker:
228236
return None
@@ -346,7 +354,7 @@ def agent_config(
346354
self,
347355
key: str,
348356
context: Context,
349-
default_value: AIAgentConfigDefault,
357+
default_value: Optional[AIAgentConfigDefault] = None,
350358
variables: Optional[Dict[str, Any]] = None,
351359
) -> AIAgentConfig:
352360
"""
@@ -374,7 +382,8 @@ def agent_config(
374382
375383
:param key: The agent configuration key.
376384
:param context: The context to evaluate the agent configuration in.
377-
:param default_value: Default agent values.
385+
:param default_value: Default agent values. When not provided, a disabled config is used
386+
as the fallback.
378387
:param variables: Variables for interpolation.
379388
:return: Configured AIAgentConfig instance.
380389
"""
@@ -385,7 +394,9 @@ def agent_config(
385394
1
386395
)
387396

388-
return self.__evaluate_agent(key, context, default_value, variables)
397+
return self.__evaluate_agent(
398+
key, context, default_value or AIAgentConfigDefault.disabled(), variables
399+
)
389400

390401
def agent(
391402
self,
@@ -457,7 +468,7 @@ def agent_configs(
457468
agent = self.__evaluate_agent(
458469
config.key,
459470
context,
460-
config.default_value,
471+
config.default_value or AIAgentConfigDefault.disabled(),
461472
config.variables
462473
)
463474
result[config.key] = agent

packages/sdk/server-ai/src/ldai/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ class AIConfigDefault:
152152
model: Optional[ModelConfig] = None
153153
provider: Optional[ProviderConfig] = None
154154

155+
@classmethod
156+
def disabled(cls):
157+
"""
158+
Returns a new disabled config default with enabled set to false.
159+
When called on a subclass, returns an instance of that subclass.
160+
"""
161+
return cls(enabled=False)
162+
155163
def _base_to_dict(self) -> Dict[str, Any]:
156164
"""
157165
Render the base config fields as a dictionary object.
@@ -334,7 +342,7 @@ class AIAgentConfigRequest:
334342
Combines agent key with its specific default configuration and variables.
335343
"""
336344
key: str
337-
default_value: AIAgentConfigDefault
345+
default_value: Optional[AIAgentConfigDefault] = None
338346
variables: Optional[Dict[str, Any]] = None
339347

340348

packages/sdk/server-ai/tests/test_agents.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,26 @@ def test_agent_config_dataclass():
340340

341341
assert config_no_vars.key == 'test-agent-2'
342342
assert config_no_vars.variables is None
343+
344+
345+
def test_agent_config_without_default_uses_disabled(ldai_client: LDAIClient):
346+
"""Test that agent_config uses disabled config when no default is provided."""
347+
context = Context.create('user-key')
348+
349+
agent = ldai_client.agent_config('missing-agent', context)
350+
351+
assert agent.enabled is False
352+
353+
354+
def test_agents_request_without_default_uses_disabled(ldai_client: LDAIClient):
355+
"""Test that agent_configs uses disabled config when request has no default_value."""
356+
context = Context.create('user-key')
357+
358+
agent_requests = [
359+
LDAIAgentConfig(key='missing-agent'),
360+
]
361+
362+
agents = ldai_client.agents(agent_requests, context)
363+
364+
assert 'missing-agent' in agents
365+
assert agents['missing-agent'].enabled is False

packages/sdk/server-ai/tests/test_judge.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,23 @@ def test_judge_config_prefers_evaluation_metric_key_over_keys(
574574
assert config is not None
575575
assert config.evaluation_metric_key == '$ld:ai:judge:preferred'
576576

577+
def test_judge_config_without_default_uses_disabled(
578+
self, context: Context
579+
):
580+
"""judge_config should use a disabled config when no default is provided."""
581+
from ldai import LDAIClient
582+
from ldclient import Config, LDClient
583+
from ldclient.integrations.test_data import TestData
584+
585+
td = TestData.data_source()
586+
test_client = LDClient(Config('sdk-key', update_processor_class=td, send_events=False))
587+
ldai_client = LDAIClient(test_client)
588+
589+
config = ldai_client.judge_config('missing-judge', context)
590+
591+
assert config is not None
592+
assert config.enabled is False
593+
577594
def test_judge_config_uses_same_variation_for_consistency(
578595
self, context: Context
579596
):

packages/sdk/server-ai/tests/test_model_config.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from ldclient.integrations.test_data import TestData
44

55
from ldai import LDAIClient, LDMessage, ModelConfig
6-
from ldai.models import AICompletionConfigDefault
6+
from ldai.models import (AIAgentConfigDefault, AICompletionConfigDefault,
7+
AIConfigDefault, AIJudgeConfigDefault)
78

89

910
@pytest.fixture
@@ -351,3 +352,63 @@ def test_sdk_info_tracked_on_init():
351352
},
352353
1,
353354
)
355+
356+
357+
# ============================================================================
358+
# disabled() classmethod tests
359+
# ============================================================================
360+
361+
def test_ai_config_default_disabled_returns_disabled_instance():
362+
result = AIConfigDefault.disabled()
363+
assert isinstance(result, AIConfigDefault)
364+
assert result.enabled is False
365+
366+
367+
def test_completion_config_default_disabled_returns_correct_type():
368+
result = AICompletionConfigDefault.disabled()
369+
assert isinstance(result, AICompletionConfigDefault)
370+
assert result.enabled is False
371+
assert result.messages is None
372+
assert result.model is None
373+
374+
375+
def test_agent_config_default_disabled_returns_correct_type():
376+
result = AIAgentConfigDefault.disabled()
377+
assert isinstance(result, AIAgentConfigDefault)
378+
assert result.enabled is False
379+
assert result.instructions is None
380+
assert result.model is None
381+
382+
383+
def test_judge_config_default_disabled_returns_correct_type():
384+
result = AIJudgeConfigDefault.disabled()
385+
assert isinstance(result, AIJudgeConfigDefault)
386+
assert result.enabled is False
387+
assert result.messages is None
388+
assert result.evaluation_metric_key is None
389+
390+
391+
def test_disabled_returns_new_instance_each_call():
392+
first = AICompletionConfigDefault.disabled()
393+
second = AICompletionConfigDefault.disabled()
394+
assert first is not second
395+
396+
397+
# ============================================================================
398+
# Optional default value tests
399+
# ============================================================================
400+
401+
def test_completion_config_without_default_uses_disabled(ldai_client: LDAIClient):
402+
context = Context.create('user-key')
403+
404+
config = ldai_client.completion_config('missing-flag', context)
405+
406+
assert config.enabled is False
407+
408+
409+
def test_config_method_without_default_uses_disabled(ldai_client: LDAIClient):
410+
context = Context.create('user-key')
411+
412+
config = ldai_client.config('missing-flag', context)
413+
414+
assert config.enabled is False

0 commit comments

Comments
 (0)