Skip to content

Commit 04f14eb

Browse files
jsonbaileyclaude
andcommitted
fix: Create graph tracker once per run, not twice
run() and _build_agents() each called create_tracker() on the graph, producing two tracker instances. Now run() creates the tracker once and passes it to _build_agents() so handoff callbacks and run-level tracking share the same instance. Tests now assert graph.create_tracker is called exactly once per run and node create_tracker is called exactly once per node. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5313ce5 commit 04f14eb

2 files changed

Lines changed: 23 additions & 8 deletions

File tree

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async def run(self, input: Any) -> AgentGraphResult:
8181
state = _RunState(last_handoff_ns=start_ns, last_node_key=root_key)
8282
try:
8383
from agents import Runner
84-
root_agent = self._build_agents(path, state)
84+
root_agent = self._build_agents(path, state, tracker)
8585
result = await Runner.run(root_agent, str(input))
8686
self._flush_final_segment(state, result)
8787
self._track_tool_calls(result)
@@ -119,7 +119,9 @@ async def run(self, input: Any) -> AgentGraphResult:
119119
metrics=LDAIMetrics(success=False),
120120
)
121121

122-
def _build_agents(self, path: List[str], state: _RunState) -> Any:
122+
def _build_agents(
123+
self, path: List[str], state: _RunState, tracker: Any
124+
) -> Any:
123125
"""
124126
Build the agent tree from the graph definition via reverse_traverse.
125127
@@ -128,6 +130,7 @@ def _build_agents(self, path: List[str], state: _RunState) -> Any:
128130
129131
:param path: Mutable list to accumulate the execution path
130132
:param state: Shared run state for tracking handoff timing and last node
133+
:param tracker: Graph-level tracker shared across the entire run
131134
:return: The root Agent instance
132135
"""
133136
try:
@@ -143,7 +146,6 @@ def _build_agents(self, path: List[str], state: _RunState) -> Any:
143146
"Install it with: pip install openai-agents"
144147
) from exc
145148

146-
tracker = self._graph.create_tracker()
147149
name_map: Dict[str, str] = {}
148150
tool_name_map: Dict[str, str] = {}
149151
node_trackers: Dict[str, Any] = {}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
1414
"""Build a minimal single-node AgentGraphDefinition for testing."""
1515
node_tracker = MagicMock()
1616
graph_tracker = MagicMock()
17+
node_factory = MagicMock(return_value=node_tracker)
18+
graph_factory = MagicMock(return_value=graph_tracker)
1719
root_config = AIAgentConfig(
1820
key='root-agent',
1921
enabled=enabled,
2022
model=ModelConfig(name='gpt-4'),
2123
provider=ProviderConfig(name='openai'),
2224
instructions='You are a helpful assistant.',
23-
create_tracker=lambda: node_tracker,
25+
create_tracker=node_factory,
2426
)
2527
graph_config = AIAgentGraphConfig(
2628
key='test-graph',
@@ -34,7 +36,7 @@ def _make_graph(enabled: bool = True) -> AgentGraphDefinition:
3436
nodes=nodes,
3537
context=MagicMock(),
3638
enabled=enabled,
37-
create_tracker=lambda: graph_tracker,
39+
create_tracker=graph_factory,
3840
)
3941

4042

@@ -83,7 +85,7 @@ async def test_openai_agent_graph_runner_run_raises_when_agents_not_installed():
8385
@pytest.mark.asyncio
8486
async def test_openai_agent_graph_runner_run_tracks_invocation_failure_on_exception():
8587
graph = _make_graph()
86-
tracker = graph.create_tracker()
88+
tracker = graph.create_tracker.return_value
8789
runner = OpenAIAgentGraphRunner(graph, {})
8890

8991
with patch.dict('sys.modules', {'agents': None}):
@@ -97,7 +99,7 @@ async def test_openai_agent_graph_runner_run_tracks_invocation_failure_on_except
9799
@pytest.mark.asyncio
98100
async def test_openai_agent_graph_runner_run_success():
99101
graph = _make_graph()
100-
tracker = graph.create_tracker()
102+
tracker = graph.create_tracker.return_value
101103

102104
mock_result = MagicMock()
103105
mock_result.final_output = "agent answer"
@@ -140,8 +142,19 @@ async def test_openai_agent_graph_runner_run_success():
140142

141143
# The runner caches one tracker per node — verify it is the same instance
142144
# returned by create_tracker() and that all tracking calls hit it.
145+
node_factory = graph.get_node('root-agent').get_config().create_tracker
146+
147+
# The runner caches one tracker per node — verify it is the same instance
148+
# returned by create_tracker and that all tracking calls hit it.
143149
cached = runner._node_trackers['root-agent']
144-
assert cached is graph.get_node('root-agent').get_config().create_tracker()
150+
assert cached is node_factory.return_value
145151
cached.track_duration.assert_called_once()
146152
cached.track_tokens.assert_called_once()
147153
cached.track_success.assert_called_once()
154+
155+
# Graph-level create_tracker is called exactly once per run (not twice)
156+
# so that handoff callbacks and run() share the same tracker instance.
157+
graph.create_tracker.assert_called_once()
158+
159+
# Node-level create_tracker is called exactly once per node.
160+
node_factory.assert_called_once()

0 commit comments

Comments
 (0)