Skip to content

Commit fc814c6

Browse files
jsonbaileyclaude
andcommitted
feat!: Replace AgentGraphDefinition.get_tracker() with create_tracker factory
Per AIGRAPH spec 1.4.3, AgentGraphDefinition now has a create_tracker callable that returns a new AIGraphTracker per invocation instead of storing a pre-created instance. Removes get_tracker() method entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ae5d752 commit fc814c6

9 files changed

Lines changed: 46 additions & 51 deletions

File tree

packages/ai-providers/server-ai-langchain/src/ldai_langchain/langgraph_agent_graph_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def route(state: WorkflowState) -> str:
260260

261261
self._graph.traverse(fn=handle_traversal)
262262

263-
tracker = self._graph.get_tracker()
263+
tracker = self._graph.create_tracker()
264264
graph_key_str = tracker.graph_key if tracker else 'unknown'
265265
log.debug(
266266
f"LangGraphAgentGraphRunner: graph='{graph_key_str}', root='{root_key}', "
@@ -281,7 +281,7 @@ async def run(self, input: Any) -> AgentGraphResult:
281281
:param input: The string prompt to send to the agent graph
282282
:return: AgentGraphResult with the final output and metrics
283283
"""
284-
tracker = self._graph.get_tracker()
284+
tracker = self._graph.create_tracker()
285285
start_ns = time.perf_counter_ns()
286286

287287
try:

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212

1313
def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
14+
graph_tracker = MagicMock()
1415
root_config = AIAgentConfig(
1516
key='root-agent',
1617
enabled=enabled,
@@ -30,7 +31,7 @@ def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
3031
nodes=nodes,
3132
context=MagicMock(),
3233
enabled=enabled,
33-
tracker=MagicMock(),
34+
create_tracker=lambda: graph_tracker,
3435
)
3536

3637

@@ -77,7 +78,7 @@ async def test_langgraph_runner_run_raises_when_langgraph_not_installed():
7778
@pytest.mark.asyncio
7879
async def test_langgraph_runner_run_tracks_failure_on_exception():
7980
graph = _make_graph()
80-
tracker = graph.get_tracker()
81+
tracker = graph.create_tracker()
8182
runner = LangGraphAgentGraphRunner(graph, {})
8283

8384
with patch.dict('sys.modules', {'langgraph': None, 'langgraph.graph': None}):
@@ -91,7 +92,7 @@ async def test_langgraph_runner_run_tracks_failure_on_exception():
9192
@pytest.mark.asyncio
9293
async def test_langgraph_runner_run_success():
9394
graph = _make_graph()
94-
tracker = graph.get_tracker()
95+
tracker = graph.create_tracker()
9596

9697
mock_message = MagicMock()
9798
mock_message.content = "langgraph answer"

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _make_graph(mock_ld_client: MagicMock, node_key: str = 'root-agent', graph_k
6565
nodes=nodes,
6666
context=context,
6767
enabled=True,
68-
tracker=graph_tracker,
68+
create_tracker=lambda: graph_tracker,
6969
)
7070

7171

@@ -321,7 +321,7 @@ def test_flush_emits_token_events_to_ld_tracker():
321321
"""flush() calls track_tokens on the node's config tracker."""
322322
mock_ld_client = MagicMock()
323323
graph = _make_graph(mock_ld_client, node_key='root-agent', graph_key='g1')
324-
tracker = graph.get_tracker()
324+
tracker = graph.create_tracker()
325325

326326
handler = LDMetricsCallbackHandler({'root-agent'}, {})
327327
node_run_id = uuid4()
@@ -340,7 +340,7 @@ def test_flush_emits_duration():
340340
"""flush() calls track_duration when duration was recorded."""
341341
mock_ld_client = MagicMock()
342342
graph = _make_graph(mock_ld_client)
343-
tracker = graph.get_tracker()
343+
tracker = graph.create_tracker()
344344

345345
handler = LDMetricsCallbackHandler({'root-agent'}, {})
346346
run_id = uuid4()
@@ -356,7 +356,7 @@ def test_flush_emits_tool_calls():
356356
"""flush() calls track_tool_call for each recorded tool invocation."""
357357
mock_ld_client = MagicMock()
358358
graph = _make_graph(mock_ld_client)
359-
tracker = graph.get_tracker()
359+
tracker = graph.create_tracker()
360360

361361
handler = LDMetricsCallbackHandler({'root-agent'}, {'fn_search': 'search'})
362362
# The agent node must be started first so it appears in the path for flush()
@@ -378,7 +378,7 @@ def test_flush_includes_graph_key_in_node_events():
378378
"""flush() passes graph_key to the node tracker so graphKey appears in events."""
379379
mock_ld_client = MagicMock()
380380
graph = _make_graph(mock_ld_client, graph_key='my-graph')
381-
tracker = graph.get_tracker()
381+
tracker = graph.create_tracker()
382382

383383
handler = LDMetricsCallbackHandler({'root-agent'}, {})
384384
node_run_id = uuid4()
@@ -425,7 +425,7 @@ def test_flush_with_no_graph_key_on_node_tracker():
425425
nodes=nodes,
426426
context=context,
427427
enabled=True,
428-
tracker=None,
428+
create_tracker=lambda: None,
429429
)
430430

431431
handler = LDMetricsCallbackHandler({'root-agent'}, {})
@@ -443,7 +443,7 @@ def test_flush_skips_nodes_not_in_path():
443443
"""flush() only emits events for nodes that were actually executed."""
444444
mock_ld_client = MagicMock()
445445
graph = _make_graph(mock_ld_client)
446-
tracker = graph.get_tracker()
446+
tracker = graph.create_tracker()
447447

448448
# Handler with 'root-agent' in node_keys but never started
449449
handler = LDMetricsCallbackHandler({'root-agent'}, {})
@@ -475,7 +475,7 @@ def test_flush_skips_node_without_tracker():
475475
nodes=nodes,
476476
context=context,
477477
enabled=True,
478-
tracker=None,
478+
create_tracker=lambda: None,
479479
)
480480

481481
handler = LDMetricsCallbackHandler({'no-track'}, {})

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _make_graph(
8585
nodes=nodes,
8686
context=context,
8787
enabled=True,
88-
tracker=graph_tracker,
88+
create_tracker=lambda: graph_tracker,
8989
)
9090

9191

@@ -199,7 +199,7 @@ def _make_two_node_graph(mock_ld_client: MagicMock) -> 'AgentGraphDefinition':
199199
nodes=nodes,
200200
context=context,
201201
enabled=True,
202-
tracker=graph_tracker,
202+
create_tracker=lambda: graph_tracker,
203203
)
204204

205205

@@ -231,7 +231,7 @@ async def test_tracks_node_and_graph_tokens_on_success():
231231
# (mock models don't fire LangChain callbacks, so we test flush directly)
232232
mock_ld_client2 = MagicMock()
233233
graph2 = _make_graph(mock_ld_client2)
234-
tracker2 = graph2.get_tracker()
234+
tracker2 = graph2.create_tracker()
235235

236236
handler = LDMetricsCallbackHandler({'root-agent'}, {})
237237
node_run_id = uuid4()
@@ -311,7 +311,7 @@ def get_weather(location: str = 'NYC') -> str:
311311
# Simulate tool call tracking via the callback handler directly
312312
mock_ld_client2 = MagicMock()
313313
graph2 = _make_graph(mock_ld_client2, tool_names=['get_weather'])
314-
tracker2 = graph2.get_tracker()
314+
tracker2 = graph2.create_tracker()
315315

316316
handler = LDMetricsCallbackHandler({'root-agent'}, {'get_weather': 'get_weather'})
317317
# Agent node must appear in path for flush() to emit its events
@@ -363,7 +363,7 @@ def summarize(text: str = '') -> str:
363363
# Simulate multiple tool calls via the callback handler directly
364364
mock_ld_client2 = MagicMock()
365365
graph2 = _make_graph(mock_ld_client2, tool_names=['search', 'summarize'])
366-
tracker2 = graph2.get_tracker()
366+
tracker2 = graph2.create_tracker()
367367

368368
fn_map = {'search': 'search', 'summarize': 'summarize'}
369369
handler = LDMetricsCallbackHandler({'root-agent'}, fn_map)
@@ -391,7 +391,7 @@ async def test_tracks_graph_key_on_node_events():
391391

392392
mock_ld_client = MagicMock()
393393
graph = _make_graph(mock_ld_client, graph_key='my-graph')
394-
tracker = graph.get_tracker()
394+
tracker = graph.create_tracker()
395395

396396
handler = LDMetricsCallbackHandler({'root-agent'}, {})
397397
node_run_id = uuid4()
@@ -464,7 +464,7 @@ def model_factory(node_config, **kwargs):
464464
# Simulate per-node token events via callback handler (mock models don't fire callbacks)
465465
mock_ld_client2 = MagicMock()
466466
graph2 = _make_two_node_graph(mock_ld_client2)
467-
tracker2 = graph2.get_tracker()
467+
tracker2 = graph2.create_tracker()
468468

469469
handler = LDMetricsCallbackHandler({'root-agent', 'child-agent'}, {})
470470

@@ -578,7 +578,7 @@ def _node_tracker(key: str) -> LDAIConfigTracker:
578578
nodes=nodes,
579579
context=context,
580580
enabled=True,
581-
tracker=graph_tracker,
581+
create_tracker=lambda: graph_tracker,
582582
)
583583

584584

@@ -691,7 +691,7 @@ def _node_tracker(key: str) -> LDAIConfigTracker:
691691
nodes=nodes,
692692
context=context,
693693
enabled=True,
694-
tracker=graph_tracker,
694+
create_tracker=lambda: graph_tracker,
695695
)
696696

697697

packages/ai-providers/server-ai-openai/src/ldai_openai/openai_agent_graph_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def run(self, input: Any) -> AgentGraphResult:
6969
:param input: The string prompt to send to the agent graph
7070
:return: AgentGraphResult with the final output and metrics
7171
"""
72-
tracker = self._graph.get_tracker()
72+
tracker = self._graph.create_tracker()
7373
path: List[str] = []
7474
root_node = self._graph.root()
7575
root_key = root_node.get_key() if root_node else ''
@@ -142,7 +142,7 @@ def _build_agents(self, path: List[str], state: _RunState) -> Any:
142142
"Install it with: pip install openai-agents"
143143
) from exc
144144

145-
tracker = self._graph.get_tracker()
145+
tracker = self._graph.create_tracker()
146146
name_map: Dict[str, str] = {}
147147
tool_name_map: Dict[str, str] = {}
148148

packages/ai-providers/server-ai-openai/tests/test_openai_agent_graph_runner.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
1414
"""Build a minimal single-node AgentGraphDefinition for testing."""
1515
node_tracker = MagicMock()
16+
graph_tracker = MagicMock()
1617
root_config = AIAgentConfig(
1718
key='root-agent',
1819
enabled=enabled,
@@ -33,7 +34,7 @@ def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
3334
nodes=nodes,
3435
context=MagicMock(),
3536
enabled=enabled,
36-
tracker=MagicMock(),
37+
create_tracker=lambda: graph_tracker,
3738
)
3839

3940

@@ -82,7 +83,7 @@ async def test_openai_agent_graph_runner_run_raises_when_agents_not_installed():
8283
@pytest.mark.asyncio
8384
async def test_openai_agent_graph_runner_run_tracks_invocation_failure_on_exception():
8485
graph = _make_graph()
85-
tracker = graph.get_tracker()
86+
tracker = graph.create_tracker()
8687
runner = OpenAIAgentGraphRunner(graph, {})
8788

8889
with patch.dict('sys.modules', {'agents': None}):
@@ -96,7 +97,7 @@ async def test_openai_agent_graph_runner_run_tracks_invocation_failure_on_except
9697
@pytest.mark.asyncio
9798
async def test_openai_agent_graph_runner_run_success():
9899
graph = _make_graph()
99-
tracker = graph.get_tracker()
100+
tracker = graph.create_tracker()
100101

101102
mock_result = MagicMock()
102103
mock_result.final_output = "agent answer"

packages/ai-providers/server-ai-openai/tests/test_tracking_openai_agents.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _make_graph(
8080
nodes=nodes,
8181
context=context,
8282
enabled=True,
83-
tracker=graph_tracker,
83+
create_tracker=lambda: graph_tracker,
8484
)
8585

8686

@@ -236,7 +236,7 @@ def _make_two_node_graph(mock_ld_client: MagicMock) -> AgentGraphDefinition:
236236
nodes=nodes,
237237
context=context,
238238
enabled=True,
239-
tracker=graph_tracker,
239+
create_tracker=lambda: graph_tracker,
240240
)
241241

242242

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,13 @@ def __init__(
5454
nodes: Dict[str, AgentGraphNode],
5555
context: Context,
5656
enabled: bool,
57-
tracker: Optional[AIGraphTracker] = None,
57+
create_tracker: Callable[[], AIGraphTracker] = lambda: None,
5858
):
5959
self._agent_graph = agent_graph
6060
self._context = context
6161
self._nodes = nodes
6262
self.enabled = enabled
63-
self._tracker = tracker
64-
65-
def get_tracker(self) -> Optional[AIGraphTracker]:
66-
"""
67-
Get the graph tracker for this graph definition.
68-
69-
:return: The AIGraphTracker instance, or None if not available.
70-
"""
71-
return self._tracker
63+
self.create_tracker = create_tracker
7264

7365
def is_enabled(self) -> bool:
7466
"""Check if the graph is enabled."""

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -596,14 +596,15 @@ def agent_graph(
596596
variation_key = variation.get("_ldMeta", {}).get("variationKey", "")
597597
version = int(variation.get("_ldMeta", {}).get("version", 1))
598598

599-
# Create graph tracker
600-
tracker = AIGraphTracker(
601-
self._client,
602-
variation_key,
603-
key,
604-
version,
605-
context,
606-
)
599+
# Create graph tracker factory
600+
def graph_tracker_factory() -> AIGraphTracker:
601+
return AIGraphTracker(
602+
self._client,
603+
variation_key,
604+
key,
605+
version,
606+
context,
607+
)
607608

608609
if not variation.get("root"):
609610
log.debug(f"Agent graph {key} is disabled, no root config key found")
@@ -617,7 +618,7 @@ def agent_graph(
617618
nodes={},
618619
context=context,
619620
enabled=False,
620-
tracker=tracker,
621+
create_tracker=graph_tracker_factory,
621622
)
622623

623624
edge_keys = list[str](variation.get("edges", {}).keys())
@@ -648,7 +649,7 @@ def agent_graph(
648649
nodes={},
649650
context=context,
650651
enabled=False,
651-
tracker=tracker,
652+
create_tracker=graph_tracker_factory,
652653
)
653654

654655
try:
@@ -679,7 +680,7 @@ def agent_graph(
679680
nodes={},
680681
context=context,
681682
enabled=False,
682-
tracker=tracker,
683+
create_tracker=graph_tracker_factory,
683684
)
684685

685686
nodes = AgentGraphDefinition.build_nodes(
@@ -692,7 +693,7 @@ def agent_graph(
692693
nodes=nodes,
693694
context=context,
694695
enabled=agent_graph_config.enabled,
695-
tracker=tracker,
696+
create_tracker=graph_tracker_factory,
696697
)
697698

698699
async def create_agent_graph(

0 commit comments

Comments
 (0)