Skip to content

Commit 20a5020

Browse files
authored
fix: Graph tracking refactor — ManagedAgentGraph drives tracking for new runner shape (#154)
1 parent fbb0b4b commit 20a5020

16 files changed

Lines changed: 2109 additions & 258 deletions

packages/sdk/server-ai/poetry.lock

Lines changed: 1178 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ async def create_agent_graph(
799799
if not runner:
800800
return None
801801

802-
return ManagedAgentGraph(runner)
802+
return ManagedAgentGraph(graph, runner)
803803

804804
def agents(
805805
self,

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

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,101 @@
11
"""ManagedAgentGraph — LaunchDarkly managed wrapper for agent graph execution."""
22

3-
from typing import Any
3+
from typing import Any, Dict
44

5-
from ldai.providers import AgentGraphResult, AgentGraphRunner
6-
from ldai.providers.types import GraphMetricSummary, ManagedGraphResult
5+
from ldai.agent_graph import AgentGraphDefinition
6+
from ldai.providers import AgentGraphRunner
7+
from ldai.providers.types import (
8+
AgentGraphRunnerResult,
9+
LDAIMetrics,
10+
ManagedGraphResult,
11+
)
12+
from ldai.tracker import LDAIMetricSummary
713

814

915
class ManagedAgentGraph:
1016
"""
1117
LaunchDarkly managed wrapper for AI agent graph execution.
1218
13-
Holds an AgentGraphRunner. Wraps the runner result in a
14-
:class:`~ldai.providers.types.ManagedGraphResult` and builds a
15-
:class:`~ldai.providers.types.GraphMetricSummary` from the runner's metrics.
19+
Holds an AgentGraphRunner and an AgentGraphDefinition. Delegates execution
20+
to the runner, then drives all graph-level and per-node tracking from the
21+
returned :class:`~ldai.providers.types.AgentGraphRunnerResult`.
1622
1723
Obtain an instance via ``LDAIClient.create_agent_graph()``.
1824
"""
1925

2026
def __init__(
2127
self,
28+
graph: AgentGraphDefinition,
2229
runner: AgentGraphRunner,
2330
):
2431
"""
2532
Initialize ManagedAgentGraph.
2633
34+
:param graph: The AgentGraphDefinition used to drive graph-level and
35+
per-node tracking from the runner result metrics.
2736
:param runner: The AgentGraphRunner to delegate execution to
2837
"""
38+
self._graph = graph
2939
self._runner = runner
3040

3141
async def run(self, input: Any) -> ManagedGraphResult:
3242
"""
3343
Run the agent graph with the given input.
3444
45+
Delegates to the underlying AgentGraphRunner, then drives all
46+
LaunchDarkly tracking from ``result.metrics``.
47+
3548
:param input: The input prompt or structured input for the graph
36-
:return: ManagedGraphResult containing the content, metric summary, raw response,
37-
and an optional evaluations task (currently always ``None`` for graphs —
38-
per-graph evaluations will be added in a future PR).
49+
:return: ManagedGraphResult containing the content, metric summary,
50+
and raw response.
3951
"""
40-
result: AgentGraphResult = await self._runner.run(input)
41-
42-
# Build a GraphMetricSummary from the runner result's LDAIMetrics.
43-
# path and node_metrics will be populated once graph runners are migrated
44-
# to return AgentGraphRunnerResult with GraphMetrics (PR 11).
45-
metrics = result.metrics
46-
summary = GraphMetricSummary(
47-
success=metrics.success,
48-
usage=metrics.usage,
49-
duration_ms=getattr(metrics, 'duration_ms', None),
52+
graph_tracker = self._graph.create_tracker()
53+
result = await graph_tracker.track_graph_metrics_of_async(
54+
lambda r: r.metrics,
55+
lambda: self._runner.run(input),
5056
)
5157

58+
summary = graph_tracker.get_summary()
59+
summary.node_metrics = self._track_node_metrics(result.metrics.node_metrics)
60+
5261
return ManagedGraphResult(
53-
content=result.output,
62+
content=result.content,
5463
metrics=summary,
5564
raw=result.raw,
5665
evaluations=None,
5766
)
5867

68+
def _track_node_metrics(
69+
self, node_metrics: Dict[str, LDAIMetrics]
70+
) -> Dict[str, LDAIMetricSummary]:
71+
"""
72+
Drive per-node LaunchDarkly tracking events and collect node metric summaries.
73+
74+
For each node key present in ``node_metrics``, obtains the node's
75+
config tracker via the graph definition, fires tracking events, and
76+
returns a map of node key to the tracker's metric summary.
77+
"""
78+
node_summaries: Dict[str, LDAIMetricSummary] = {}
79+
for node_key, node_ldai_metrics in node_metrics.items():
80+
node = self._graph.get_node(node_key)
81+
if node is None:
82+
continue
83+
node_tracker = node.get_config().create_tracker()
84+
85+
if node_ldai_metrics.usage is not None:
86+
node_tracker.track_tokens(node_ldai_metrics.usage)
87+
if node_ldai_metrics.duration_ms is not None:
88+
node_tracker.track_duration(node_ldai_metrics.duration_ms)
89+
if node_ldai_metrics.tool_calls:
90+
node_tracker.track_tool_calls(node_ldai_metrics.tool_calls)
91+
if node_ldai_metrics.success:
92+
node_tracker.track_success()
93+
else:
94+
node_tracker.track_error()
95+
96+
node_summaries[node_key] = node_tracker.get_summary()
97+
return node_summaries
98+
5999
def get_agent_graph_runner(self) -> AgentGraphRunner:
60100
"""
61101
Return the underlying AgentGraphRunner for advanced use.

packages/sdk/server-ai/src/ldai/providers/agent_graph_runner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Protocol, runtime_checkable
22

3-
from ldai.providers.types import AgentGraphResult
3+
from ldai.providers.types import AgentGraphRunnerResult
44

55

66
@runtime_checkable
@@ -18,11 +18,11 @@ class AgentGraphRunner(Protocol):
1818
the caller just passes input.
1919
"""
2020

21-
async def run(self, input: Any) -> AgentGraphResult:
21+
async def run(self, input: Any) -> AgentGraphRunnerResult:
2222
"""
2323
Run the agent graph with the given input.
2424
2525
:param input: The input to the agent graph (string prompt or structured input)
26-
:return: AgentGraphResult containing the output, raw response, and metrics
26+
:return: AgentGraphRunnerResult containing the content, raw response, and GraphMetrics
2727
"""
2828
...

packages/sdk/server-ai/src/ldai/providers/types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ class GraphMetrics:
110110
class GraphMetricSummary:
111111
"""Contains a summary of metrics for an agent graph run."""
112112

113-
success: bool
114-
"""Whether the graph run succeeded."""
113+
success: Optional[bool] = None
114+
"""Whether the graph run succeeded. Absent if invocation status has not been tracked."""
115115

116116
path: List[str] = field(default_factory=list)
117117
"""Ordered list of node keys visited during the run."""
@@ -122,8 +122,8 @@ class GraphMetricSummary:
122122
usage: Optional[TokenUsage] = None
123123
"""Optional aggregate token usage information across all nodes in the graph run."""
124124

125-
node_metrics: Dict[str, LDAIMetrics] = field(default_factory=dict)
126-
"""Per-node metrics keyed by node key."""
125+
node_metrics: Dict[str, LDAIMetricSummary] = field(default_factory=dict)
126+
"""Per-node metric summaries keyed by node key."""
127127

128128
resumption_token: Optional[str] = None
129129
"""Optional resumption token from the graph tracker for cross-process resumption."""

0 commit comments

Comments
 (0)