Skip to content

Commit 609451e

Browse files
jsonbaileyclaude
andcommitted
refactor!: Make create_tracker required on AIConfig (no default)
AIConfig.create_tracker is now a required field with no default value. The SDK client always injects a real tracker factory, so any direct construction of AIConfig subclasses must now provide one explicitly. This eliminates the entire class of null-safety issues around tracker factories. Reverts the RuntimeError guard in Judge.evaluate() since it is no longer needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2fc7151 commit 609451e

8 files changed

Lines changed: 25 additions & 14 deletions

File tree

packages/ai-providers/server-ai-langchain/tests/test_langchain_provider.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ def sync_tool(x: str = '') -> str:
530530
cfg = AIAgentConfig(
531531
key='n',
532532
enabled=True,
533+
create_tracker=MagicMock(),
533534
model=ModelConfig(
534535
name='gpt-4',
535536
parameters={'tools': [{'name': 'my_tool', 'type': 'function', 'parameters': {}}]},
@@ -552,6 +553,7 @@ async def async_tool(x: str = '') -> str:
552553
cfg = AIAgentConfig(
553554
key='n',
554555
enabled=True,
556+
create_tracker=MagicMock(),
555557
model=ModelConfig(
556558
name='gpt-4',
557559
parameters={'tools': [{'name': 'my_tool', 'type': 'function', 'parameters': {}}]},

packages/ai-providers/server-ai-langchain/tests/test_langgraph_agent_graph_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212

1313
def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
1414
graph_tracker = MagicMock()
15+
node_tracker = MagicMock()
1516
root_config = AIAgentConfig(
1617
key='root-agent',
1718
enabled=enabled,
19+
create_tracker=MagicMock(return_value=node_tracker),
1820
model=ModelConfig(name='gpt-4'),
1921
provider=ProviderConfig(name='openai'),
2022
instructions='You are a helpful assistant.',

packages/ai-providers/server-ai-langchain/tests/test_langgraph_callback_handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ def test_flush_skips_node_without_tracker():
462462
node_config_no_tracker = AIAgentConfig(
463463
key='no-track',
464464
enabled=True,
465+
create_tracker=lambda: None,
465466
model=ModelConfig(name='gpt-4', parameters={}),
466467
provider=ProviderConfig(name='openai'),
467468
instructions='',

packages/sdk/server-ai/src/ldai/agent_graph/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from ldclient import Context
66

7-
from ldai.models import AIAgentConfig, AIAgentGraphConfig, Edge
7+
from ldai.models import AIAgentConfig, AIAgentConfigDefault, AIAgentGraphConfig, Edge
88
from ldai.tracker import AIGraphTracker
99

10-
DEFAULT_FALSE = AIAgentConfig(key="", enabled=False)
10+
DEFAULT_FALSE = AIAgentConfigDefault(enabled=False)
1111

1212

1313
class AgentGraphNode:

packages/sdk/server-ai/src/ldai/judge/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ async def evaluate(
7171
judge_result.sampled = True
7272

7373
tracker = self._ai_config.create_tracker()
74-
if tracker is None:
75-
raise RuntimeError(
76-
"AIConfig.create_tracker returned None. "
77-
"Ensure the config was obtained from the client rather than constructed directly."
78-
)
7974
messages = self._construct_evaluation_messages(input_text, output_text)
8075
assert self._evaluation_response_structure is not None
8176

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,16 @@ def _base_to_dict(self) -> Dict[str, Any]:
167167
class AIConfig:
168168
"""
169169
Base AI Config interface without mode-specific fields.
170+
171+
Instances are always created by the SDK client, which injects a real
172+
``create_tracker`` factory. User code should never need to construct
173+
this directly — use the ``*Default`` variants for default values.
170174
"""
171175
key: str
172176
enabled: bool
177+
create_tracker: Callable[[], Any]
173178
model: Optional[ModelConfig] = None
174179
provider: Optional[ProviderConfig] = None
175-
create_tracker: Callable[[], Any] = lambda: None
176180

177181
def _base_to_dict(self) -> Dict[str, Any]:
178182
"""

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from ldclient import Config, Context, LDClient
33
from ldclient.integrations.test_data import TestData
44

5+
from unittest.mock import MagicMock
6+
57
from ldai import (
68
LDAIClient,
79
AIAgentGraphConfig,
@@ -268,13 +270,17 @@ def test_agent_graph_build_nodes(ldai_client: LDAIClient):
268270
ai_graph_config,
269271
{
270272
"customer-support-agent": AIAgentConfig(
271-
key="customer-support-agent", enabled=True
273+
key="customer-support-agent", enabled=True, create_tracker=MagicMock(),
274+
),
275+
"personalized-agent": AIAgentConfig(
276+
key="personalized-agent", enabled=True, create_tracker=MagicMock(),
272277
),
273-
"personalized-agent": AIAgentConfig(key="personalized-agent", enabled=True),
274278
"multi-context-agent": AIAgentConfig(
275-
key="multi-context-agent", enabled=True
279+
key="multi-context-agent", enabled=True, create_tracker=MagicMock(),
280+
),
281+
"minimal-agent": AIAgentConfig(
282+
key="minimal-agent", enabled=True, create_tracker=MagicMock(),
276283
),
277-
"minimal-agent": AIAgentConfig(key="minimal-agent", enabled=True),
278284
},
279285
)
280286

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ def _make_judge_config(
8383
model=model or ModelConfig('gpt-4'),
8484
provider=provider or ProviderConfig('openai'),
8585
)
86-
if tracker is not None:
87-
kwargs['create_tracker'] = lambda: tracker
86+
kwargs['create_tracker'] = (lambda: tracker) if tracker is not None else MagicMock()
8887
return AIJudgeConfig(**kwargs)
8988

9089

@@ -372,6 +371,7 @@ def test_to_dict_includes_evaluation_metric_key(self):
372371
config = AIJudgeConfig(
373372
key='test-judge',
374373
enabled=True,
374+
create_tracker=MagicMock(),
375375
evaluation_metric_key='$ld:ai:judge:relevance',
376376
messages=[LDMessage(role='system', content='You are a judge.')],
377377
)
@@ -386,6 +386,7 @@ def test_to_dict_handles_none_evaluation_metric_key(self):
386386
config = AIJudgeConfig(
387387
key='test-judge',
388388
enabled=True,
389+
create_tracker=MagicMock(),
389390
evaluation_metric_key=None,
390391
messages=[LDMessage(role='system', content='You are a judge.')],
391392
)

0 commit comments

Comments
 (0)