44
55from ldai import log
66from ldai .agent_graph import AgentGraphDefinition , AgentGraphNode
7- from ldai .providers import AgentGraphResult , AgentGraphRunner , ToolRegistry
8- from ldai .providers .types import LDAIMetrics
7+ from ldai .providers import AgentGraphRunner , ToolRegistry
8+ from ldai .providers .types import AgentGraphRunnerResult , GraphMetrics , LDAIMetrics
99from ldai .tracker import TokenUsage
1010
1111from ldai_openai .openai_helper import (
@@ -22,6 +22,34 @@ def _sanitize_agent_name(key: str) -> str:
2222 return re .sub (r'[^a-zA-Z0-9_]' , '_' , key )
2323
2424
25+ class _NodeMetricsAccumulator :
26+ """Mutable per-node metrics collected during a run (replaces LDAIConfigTracker)."""
27+
28+ def __init__ (self ) -> None :
29+ self .usage : Optional [TokenUsage ] = None
30+ self .duration_ms : Optional [int ] = None
31+ self .tool_calls : List [str ] = []
32+ self .success : bool = True
33+
34+ def set_usage (self , usage : Optional [TokenUsage ]) -> None :
35+ if usage is not None :
36+ self .usage = usage
37+
38+ def set_duration_ms (self , duration_ms : int ) -> None :
39+ self .duration_ms = duration_ms
40+
41+ def add_tool_call (self , tool_name : str ) -> None :
42+ self .tool_calls .append (tool_name )
43+
44+ def to_ldai_metrics (self ) -> LDAIMetrics :
45+ return LDAIMetrics (
46+ success = self .success ,
47+ usage = self .usage ,
48+ duration_ms = self .duration_ms ,
49+ tool_calls = self .tool_calls if self .tool_calls else None ,
50+ )
51+
52+
2553class _RunState :
2654 """Mutable state shared across handoff and tool callbacks during a single run."""
2755
@@ -39,9 +67,10 @@ class OpenAIAgentGraphRunner(AgentGraphRunner):
3967
4068 AgentGraphRunner implementation for the OpenAI Agents SDK.
4169
42- Runs the agent graph with the OpenAI Agents SDK and automatically records
43- graph- and node-level AI metric data to the LaunchDarkly trackers on the
44- graph definition and each node.
70+ Runs the agent graph with the OpenAI Agents SDK and collects graph- and
71+ node-level metrics. Tracking events are emitted by the managed layer
72+ (:class:`~ldai.ManagedAgentGraph`) from the returned
73+ :class:`~ldai.providers.types.AgentGraphRunnerResult`.
4574
4675 Requires ``openai-agents`` to be installed.
4776 """
@@ -61,20 +90,19 @@ def __init__(
6190 self ._tools = tools
6291 self ._agent_name_map : Dict [str , str ] = {}
6392 self ._tool_name_map : Dict [str , str ] = {}
64- self ._node_trackers : Dict [str , Any ] = {}
93+ self ._node_accumulators : Dict [str , _NodeMetricsAccumulator ] = {}
6594
66- async def run (self , input : Any ) -> AgentGraphResult :
95+ async def run (self , input : Any ) -> AgentGraphRunnerResult :
6796 """
6897 Run the agent graph with the given input.
6998
7099 Builds the agent tree via reverse_traverse, then invokes the root
71- agent with Runner.run(). Tracks path, latency, and invocation
72- success/failure .
100+ agent with Runner.run(). Collects path, latency, and per-node metrics.
101+ Graph-level tracking events are emitted by the managed layer .
73102
74103 :param input: The string prompt to send to the agent graph
75- :return: AgentGraphResult with the final output and metrics
104+ :return: AgentGraphRunnerResult with the final content and GraphMetrics
76105 """
77- tracker = self ._graph .create_tracker ()
78106 path : List [str ] = []
79107 root_node = self ._graph .root ()
80108 root_key = root_node .get_key () if root_node else ''
@@ -86,24 +114,29 @@ async def run(self, input: Any) -> AgentGraphResult:
86114 state = _RunState (last_handoff_ns = start_ns , last_node_key = root_key )
87115 try :
88116 from agents import Runner
89- root_agent = self ._build_agents (path , state , tracker )
117+ root_agent = self ._build_agents (path , state )
90118 result = await Runner .run (root_agent , input_str )
91119 self ._flush_final_segment (state , result )
92- self ._track_tool_calls (result )
120+ self ._collect_tool_calls (result )
93121
94- duration = (time .perf_counter_ns () - start_ns ) // 1_000_000
122+ duration_ms = (time .perf_counter_ns () - start_ns ) // 1_000_000
95123 token_usage = get_ai_usage_from_response (result )
96124
97- tracker .track_path (path )
98- tracker .track_duration (duration )
99- tracker .track_invocation_success ()
100- if token_usage is not None :
101- tracker .track_total_tokens (token_usage )
125+ node_metrics = {
126+ key : acc .to_ldai_metrics ()
127+ for key , acc in self ._node_accumulators .items ()
128+ }
102129
103- return AgentGraphResult (
104- output = str (result .final_output ),
130+ return AgentGraphRunnerResult (
131+ content = str (result .final_output ),
105132 raw = result ,
106- metrics = LDAIMetrics (success = True , usage = token_usage ),
133+ metrics = GraphMetrics (
134+ success = True ,
135+ path = path ,
136+ duration_ms = duration_ms ,
137+ usage = token_usage ,
138+ node_metrics = node_metrics ,
139+ ),
107140 )
108141 except Exception as exc :
109142 if isinstance (exc , ImportError ):
@@ -113,17 +146,19 @@ async def run(self, input: Any) -> AgentGraphResult:
113146 )
114147 else :
115148 log .warning (f'OpenAIAgentGraphRunner run failed: { exc } ' )
116- duration = (time .perf_counter_ns () - start_ns ) // 1_000_000
117- tracker .track_duration (duration )
118- tracker .track_invocation_failure ()
119- return AgentGraphResult (
120- output = '' ,
149+ duration_ms = (time .perf_counter_ns () - start_ns ) // 1_000_000
150+ return AgentGraphRunnerResult (
151+ content = '' ,
121152 raw = None ,
122- metrics = LDAIMetrics (success = False ),
153+ metrics = GraphMetrics (
154+ success = False ,
155+ path = path ,
156+ duration_ms = duration_ms ,
157+ ),
123158 )
124159
125160 def _build_agents (
126- self , path : List [str ], state : _RunState , tracker : Any
161+ self , path : List [str ], state : _RunState
127162 ) -> Any :
128163 """
129164 Build the agent tree from the graph definition via reverse_traverse.
@@ -133,7 +168,6 @@ def _build_agents(
133168
134169 :param path: Mutable list to accumulate the execution path
135170 :param state: Shared run state for tracking handoff timing and last node
136- :param tracker: Graph-level tracker shared across the entire run
137171 :return: The root Agent instance
138172 """
139173 try :
@@ -151,12 +185,12 @@ def _build_agents(
151185
152186 name_map : Dict [str , str ] = {}
153187 tool_name_map : Dict [str , str ] = {}
154- node_trackers : Dict [str , Any ] = {}
188+ node_accumulators : Dict [str , _NodeMetricsAccumulator ] = {}
155189
156190 def build_node (node : AgentGraphNode , ctx : dict ) -> Any :
157191 node_config = node .get_config ()
158- config_tracker = node_config . create_tracker ()
159- node_trackers [node_config .key ] = config_tracker
192+ acc = _NodeMetricsAccumulator ()
193+ node_accumulators [node_config .key ] = acc
160194 model = node_config .model
161195
162196 if not model :
@@ -177,8 +211,7 @@ def build_node(node: AgentGraphNode, ctx: dict) -> Any:
177211 node_config .key ,
178212 target_key ,
179213 path ,
180- tracker ,
181- config_tracker ,
214+ acc ,
182215 state ,
183216 ),
184217 )
@@ -212,20 +245,19 @@ def build_node(node: AgentGraphNode, ctx: dict) -> Any:
212245 root = self ._graph .reverse_traverse (fn = build_node )
213246 self ._agent_name_map = name_map
214247 self ._tool_name_map = tool_name_map
215- self ._node_trackers = node_trackers
248+ self ._node_accumulators = node_accumulators
216249 return root
217250
218251 def _make_on_handoff (
219252 self ,
220253 src : str ,
221254 tgt : str ,
222255 path : List [str ],
223- tracker : Any ,
224- config_tracker : Any ,
256+ acc : _NodeMetricsAccumulator ,
225257 state : _RunState ,
226258 ):
227259 def on_handoff (run_ctx : Any ) -> None :
228- self ._handle_handoff (run_ctx , src , tgt , path , tracker , config_tracker , state )
260+ self ._handle_handoff (run_ctx , src , tgt , path , acc , state )
229261 return on_handoff
230262
231263 def _handle_handoff (
@@ -234,13 +266,11 @@ def _handle_handoff(
234266 src : str ,
235267 tgt : str ,
236268 path : List [str ],
237- tracker : Any ,
238- config_tracker : Any ,
269+ acc : _NodeMetricsAccumulator ,
239270 state : _RunState ,
240271 ) -> None :
241272 path .append (tgt )
242273 state .last_node_key = tgt
243- tracker .track_handoff_success (src , tgt )
244274
245275 now_ns = time .perf_counter_ns ()
246276 duration_ms = (now_ns - state .last_handoff_ns ) // 1_000_000
@@ -254,19 +284,15 @@ def _handle_handoff(
254284 except Exception :
255285 pass
256286
257- if config_tracker is not None :
258- if usage is not None :
259- config_tracker .track_tokens (usage )
260- if duration_ms is not None :
261- config_tracker .track_duration (int (duration_ms ))
262- config_tracker .track_success ()
287+ acc .set_usage (usage )
288+ acc .set_duration_ms (int (duration_ms ))
263289
264290 def _flush_final_segment (self , state : _RunState , result : Any ) -> None :
265291 """Record duration/tokens for the last active agent (no handoff after it)."""
266292 if not state .last_node_key :
267293 return
268- config_tracker = self ._node_trackers .get (state .last_node_key )
269- if config_tracker is None :
294+ acc = self ._node_accumulators .get (state .last_node_key )
295+ if acc is None :
270296 return
271297
272298 now_ns = time .perf_counter_ns ()
@@ -280,18 +306,16 @@ def _flush_final_segment(self, state: _RunState, result: Any) -> None:
280306 except Exception :
281307 pass
282308
283- if usage is not None :
284- config_tracker .track_tokens (usage )
285- config_tracker .track_duration (int (duration_ms ))
286- config_tracker .track_success ()
309+ acc .set_usage (usage )
310+ acc .set_duration_ms (int (duration_ms ))
287311
288- def _track_tool_calls (self , result : Any ) -> None :
289- """Track all tool calls from the run result, attributed to the node that called them."""
312+ def _collect_tool_calls (self , result : Any ) -> None :
313+ """Collect all tool calls from the run result, attributed to the node that called them."""
290314 for agent_name , tool_fn_name in get_tool_calls_from_run_items (result .new_items ):
291315 agent_key = self ._agent_name_map .get (agent_name , agent_name )
292316 tool_name = self ._tool_name_map .get (tool_fn_name )
293317 if tool_name is None :
294318 continue
295- config_tracker = self ._node_trackers .get (agent_key )
296- if config_tracker is not None :
297- config_tracker . track_tool_call (tool_name )
319+ acc = self ._node_accumulators .get (agent_key )
320+ if acc is not None :
321+ acc . add_tool_call (tool_name )
0 commit comments