Skip to content

Commit 82cb40a

Browse files
jsonbaileyclaude
andcommitted
feat!: Remove config.tracker field — use create_tracker() instead
BREAKING CHANGE: The `tracker` field has been removed from all config dataclasses (AICompletionConfig, AIJudgeConfig, AIAgentConfig). Users must now call `config.create_tracker()` to obtain a tracker instance. ManagedModel and ManagedAgent no longer accept a tracker constructor parameter — they call `create_tracker()` from the config on each invocation. The `__evaluate` return tuple no longer includes a pre-created tracker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4c0451b commit 82cb40a

7 files changed

Lines changed: 29 additions & 52 deletions

File tree

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def _completion_config(
8484
default: AICompletionConfigDefault,
8585
variables: Optional[Dict[str, Any]] = None,
8686
) -> AICompletionConfig:
87-
(model, provider, messages, instructions, tracker,
87+
(model, provider, messages, instructions,
8888
tracker_factory, enabled, judge_configuration, _) = self.__evaluate(
8989
key, context, default.to_dict(), variables
9090
)
@@ -95,7 +95,6 @@ def _completion_config(
9595
model=model,
9696
messages=messages,
9797
provider=provider,
98-
tracker=tracker,
9998
create_tracker=tracker_factory,
10099
judge_configuration=judge_configuration,
101100
)
@@ -152,7 +151,7 @@ def _judge_config(
152151
default: AIJudgeConfigDefault,
153152
variables: Optional[Dict[str, Any]] = None,
154153
) -> AIJudgeConfig:
155-
(model, provider, messages, instructions, tracker,
154+
(model, provider, messages, instructions,
156155
tracker_factory, enabled, judge_configuration, variation) = self.__evaluate(
157156
key, context, default.to_dict(), variables
158157
)
@@ -181,7 +180,6 @@ def _extract_evaluation_metric_key(variation: Dict[str, Any]) -> Optional[str]:
181180
model=model,
182181
messages=messages,
183182
provider=provider,
184-
tracker=tracker,
185183
create_tracker=tracker_factory,
186184
)
187185

@@ -381,7 +379,7 @@ async def create_model(
381379
default_ai_provider,
382380
)
383381

384-
return ManagedModel(config, config.create_tracker(), runner, judges)
382+
return ManagedModel(config, runner, judges)
385383

386384
async def create_chat(
387385
self,
@@ -455,7 +453,7 @@ async def create_agent(
455453
if not runner:
456454
return None
457455

458-
return ManagedAgent(config, config.create_tracker(), runner)
456+
return ManagedAgent(config, runner)
459457

460458
def agent_config(
461459
self,
@@ -485,7 +483,8 @@ def agent_config(
485483
486484
if agent.enabled:
487485
research_result = agent.instructions # Interpolated instructions
488-
agent.tracker.track_success()
486+
tracker = agent.create_tracker()
487+
tracker.track_success()
489488
490489
:param key: The agent configuration key.
491490
:param context: The context to evaluate the agent configuration in.
@@ -555,7 +554,8 @@ def agent_configs(
555554
], context)
556555
557556
research_result = agents["research_agent"].instructions
558-
agents["research_agent"].tracker.track_success()
557+
tracker = agents["research_agent"].create_tracker()
558+
tracker.track_success()
559559
560560
:param agent_configs: List of agent configurations to retrieve.
561561
:param context: The context to evaluate the agent configurations in.
@@ -774,7 +774,7 @@ def __evaluate(
774774
graph_key: Optional[str] = None,
775775
) -> Tuple[
776776
Optional[ModelConfig], Optional[ProviderConfig], Optional[List[LDMessage]],
777-
Optional[str], LDAIConfigTracker, Callable[[], LDAIConfigTracker], bool, Optional[Any], Dict[str, Any]
777+
Optional[str], Callable[[], LDAIConfigTracker], bool, Optional[Any], Dict[str, Any]
778778
]:
779779
"""
780780
Internal method to evaluate a configuration and extract components.
@@ -784,7 +784,8 @@ def __evaluate(
784784
:param default_dict: Default configuration as dictionary.
785785
:param variables: Variables for interpolation.
786786
:param graph_key: When set, passed to the tracker so all events include ``graphKey``.
787-
:return: Tuple of (model, provider, messages, instructions, tracker, enabled, judge_configuration, variation).
787+
:return: Tuple of (model, provider, messages, instructions,
788+
tracker_factory, enabled, judge_configuration, variation).
788789
"""
789790
variation = self._client.variation(key, context, default_dict)
790791

@@ -844,8 +845,6 @@ def tracker_factory() -> LDAIConfigTracker:
844845
graph_key=graph_key,
845846
)
846847

847-
tracker = tracker_factory()
848-
849848
enabled = variation.get('_ldMeta', {}).get('enabled', False)
850849

851850
judge_configuration = None
@@ -864,7 +863,7 @@ def tracker_factory() -> LDAIConfigTracker:
864863
judge_configuration = JudgeConfiguration(judges=judges)
865864

866865
return (
867-
model, provider_config, messages, instructions, tracker,
866+
model, provider_config, messages, instructions,
868867
tracker_factory, enabled, judge_configuration, variation,
869868
)
870869

@@ -886,7 +885,7 @@ def __evaluate_agent(
886885
:param graph_key: When set, passed to the tracker so all events include ``graphKey``.
887886
:return: Configured AIAgentConfig instance.
888887
"""
889-
(model, provider, messages, instructions, tracker,
888+
(model, provider, messages, instructions,
890889
tracker_factory, enabled, judge_configuration, _) = self.__evaluate(
891890
key, context, default.to_dict(), variables, graph_key=graph_key
892891
)
@@ -900,7 +899,6 @@ def __evaluate_agent(
900899
model=model or default.model,
901900
provider=provider or default.provider,
902901
instructions=final_instructions,
903-
tracker=tracker,
904902
create_tracker=tracker_factory,
905903
judge_configuration=judge_configuration or default.judge_configuration,
906904
)

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,22 @@
22

33
from ldai.models import AIAgentConfig
44
from ldai.providers import AgentResult, AgentRunner
5-
from ldai.tracker import LDAIConfigTracker
65

76

87
class ManagedAgent:
98
"""
109
LaunchDarkly managed wrapper for AI agent invocations.
1110
12-
Holds an AgentRunner and an LDAIConfigTracker. Handles tracking automatically.
11+
Holds an AgentRunner. Handles tracking automatically via ``create_tracker()``.
1312
Obtain an instance via ``LDAIClient.create_agent()``.
1413
"""
1514

1615
def __init__(
1716
self,
1817
ai_config: AIAgentConfig,
19-
tracker: LDAIConfigTracker,
2018
agent_runner: AgentRunner,
2119
):
2220
self._ai_config = ai_config
23-
self._tracker = tracker
2421
self._agent_runner = agent_runner
2522

2623
async def run(self, input: str) -> AgentResult:
@@ -47,7 +44,3 @@ def get_agent_runner(self) -> AgentRunner:
4744
def get_config(self) -> AIAgentConfig:
4845
"""Return the AI agent config."""
4946
return self._ai_config
50-
51-
def get_tracker(self) -> LDAIConfigTracker:
52-
"""Return the config tracker."""
53-
return self._tracker

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,18 @@ class ManagedModel:
1313
"""
1414
LaunchDarkly managed wrapper for AI model invocations.
1515
16-
Holds a ModelRunner and an LDAIConfigTracker. Handles conversation
17-
management, judge evaluation dispatch, and tracking automatically.
16+
Holds a ModelRunner. Handles conversation management, judge evaluation
17+
dispatch, and tracking automatically via ``create_tracker()``.
1818
Obtain an instance via ``LDAIClient.create_model()``.
1919
"""
2020

2121
def __init__(
2222
self,
2323
ai_config: AICompletionConfig,
24-
tracker: LDAIConfigTracker,
2524
model_runner: ModelRunner,
2625
judges: Optional[Dict[str, Judge]] = None,
2726
):
2827
self._ai_config = ai_config
29-
self._tracker = tracker
3028
self._model_runner = model_runner
3129
self._judges = judges or {}
3230
self._messages: List[LDMessage] = []
@@ -119,10 +117,6 @@ def get_config(self) -> AICompletionConfig:
119117
"""Return the AI completion config."""
120118
return self._ai_config
121119

122-
def get_tracker(self) -> LDAIConfigTracker:
123-
"""Return the config tracker."""
124-
return self._tracker
125-
126120
def get_judges(self) -> Dict[str, Judge]:
127121
"""Return the judges associated with this model."""
128122
return self._judges

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ class AIConfig:
180180
enabled: bool
181181
model: Optional[ModelConfig] = None
182182
provider: Optional[ProviderConfig] = None
183-
tracker: Optional[Any] = None
184183
create_tracker: Callable[[], Any] = lambda: None
185184

186185
def _base_to_dict(self) -> Dict[str, Any]:

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,9 @@ def test_agent_graph_node_trackers_have_graph_key(ldai_client: LDAIClient):
396396
graph.get_node("multi-context-agent"),
397397
graph.get_node("minimal-agent")]:
398398
config = node.get_config()
399-
assert config.tracker is not None
400-
assert config.tracker._graph_key == "test-agent-graph"
399+
assert callable(config.create_tracker)
400+
tracker = config.create_tracker()
401+
assert tracker._graph_key == "test-agent-graph"
401402

402403

403404
def test_agent_graph_handoff(ldai_client: LDAIClient):

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_single_agent_method(ldai_client: LDAIClient):
140140
assert agent.provider is not None
141141
assert agent.provider.name == 'openai'
142142
assert agent.instructions == 'You are a research assistant specializing in quantum computing. Your expertise level should match advanced.'
143-
assert agent.tracker is not None
143+
assert callable(agent.create_tracker)
144144

145145

146146
def test_single_agent_with_defaults(ldai_client: LDAIClient):
@@ -164,7 +164,7 @@ def test_single_agent_with_defaults(ldai_client: LDAIClient):
164164
assert agent.model is not None and agent.model.get_parameter('temp') == 0.8
165165
assert agent.provider is not None and agent.provider.name == 'default-provider'
166166
assert agent.instructions == "You are a default assistant for general assistance."
167-
assert agent.tracker is not None
167+
assert callable(agent.create_tracker)
168168

169169

170170
def test_agents_method_with_configs(ldai_client: LDAIClient):
@@ -275,7 +275,7 @@ def test_disabled_agent_single_method(ldai_client: LDAIClient):
275275
agent = ldai_client.agent(config, context)
276276

277277
assert agent.enabled is False
278-
assert agent.tracker is not None
278+
assert callable(agent.create_tracker)
279279

280280

281281
def test_disabled_agent_multiple_method(ldai_client: LDAIClient):
@@ -313,7 +313,7 @@ def test_agent_with_missing_metadata(ldai_client: LDAIClient):
313313
assert agent.enabled is True # From flag
314314
assert agent.instructions == 'Minimal agent configuration.'
315315
assert agent.model == config.default.model # Falls back to default
316-
assert agent.tracker is not None
316+
assert callable(agent.create_tracker)
317317

318318

319319
def test_agent_config_dataclass():

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ async def test_run_delegates_to_agent_runner(self):
7373
)
7474
)
7575

76-
agent = ManagedAgent(mock_config, mock_tracker, mock_runner)
76+
agent = ManagedAgent(mock_config, mock_runner)
7777
result = await agent.run("Hello")
7878

7979
assert result.output == "Test response"
8080
assert result.metrics.success is True
81+
mock_config.create_tracker.assert_called_once()
8182
mock_tracker.track_metrics_of_async.assert_called_once()
8283

8384
@pytest.mark.asyncio
84-
async def test_run_uses_create_tracker_when_available(self):
85+
async def test_run_uses_create_tracker_for_fresh_tracker(self):
8586
"""Should use create_tracker() factory for a fresh tracker per invocation."""
8687
mock_config = MagicMock(spec=AIAgentConfig)
8788
fresh_tracker = MagicMock()
@@ -94,38 +95,29 @@ async def test_run_uses_create_tracker_when_available(self):
9495
)
9596
mock_config.create_tracker = MagicMock(return_value=fresh_tracker)
9697

97-
old_tracker = MagicMock()
9898
mock_runner = MagicMock()
9999

100-
agent = ManagedAgent(mock_config, old_tracker, mock_runner)
100+
agent = ManagedAgent(mock_config, mock_runner)
101101
result = await agent.run("Hello")
102102

103103
assert result.output == "Fresh tracker response"
104104
mock_config.create_tracker.assert_called_once()
105105
fresh_tracker.track_metrics_of_async.assert_called_once()
106-
old_tracker.track_metrics_of_async.assert_not_called()
107106

108107
def test_get_agent_runner_returns_runner(self):
109108
"""Should return the underlying AgentRunner."""
110109
mock_runner = MagicMock()
111-
agent = ManagedAgent(MagicMock(), MagicMock(), mock_runner)
110+
agent = ManagedAgent(MagicMock(), mock_runner)
112111

113112
assert agent.get_agent_runner() is mock_runner
114113

115114
def test_get_config_returns_config(self):
116115
"""Should return the AI agent config."""
117116
mock_config = MagicMock()
118-
agent = ManagedAgent(mock_config, MagicMock(), MagicMock())
117+
agent = ManagedAgent(mock_config, MagicMock())
119118

120119
assert agent.get_config() is mock_config
121120

122-
def test_get_tracker_returns_tracker(self):
123-
"""Should return the tracker."""
124-
mock_tracker = MagicMock()
125-
agent = ManagedAgent(MagicMock(), mock_tracker, MagicMock())
126-
127-
assert agent.get_tracker() is mock_tracker
128-
129121

130122
class TestLDAIClientCreateAgent:
131123
"""Tests for LDAIClient.create_agent."""

0 commit comments

Comments
 (0)