|
1 | 1 | """ManagedAgentGraph — LaunchDarkly managed wrapper for agent graph execution.""" |
2 | 2 |
|
3 | | -from typing import Any |
| 3 | +from typing import Any, Optional |
4 | 4 |
|
5 | 5 | from ldai.providers import AgentGraphResult, AgentGraphRunner |
6 | | -from ldai.providers.types import GraphMetricSummary, ManagedGraphResult |
| 6 | +from ldai.providers.types import ( |
| 7 | + AgentGraphRunnerResult, |
| 8 | + GraphMetricSummary, |
| 9 | + LDAIMetrics, |
| 10 | + ManagedGraphResult, |
| 11 | +) |
7 | 12 |
|
8 | 13 |
|
9 | 14 | class ManagedAgentGraph: |
10 | 15 | """ |
11 | 16 | LaunchDarkly managed wrapper for AI agent graph execution. |
12 | 17 |
|
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. |
| 18 | + Holds an AgentGraphRunner and an optional AgentGraphDefinition. Wraps the |
| 19 | + runner result in a :class:`~ldai.providers.types.ManagedGraphResult` and |
| 20 | + builds a :class:`~ldai.providers.types.GraphMetricSummary` from the runner's |
| 21 | + metrics. |
| 22 | +
|
| 23 | + When the runner returns an :class:`~ldai.providers.types.AgentGraphRunnerResult` |
| 24 | + (new shape), the managed layer drives all graph-level tracking from |
| 25 | + ``result.metrics``. When the runner returns the legacy |
| 26 | + :class:`~ldai.providers.AgentGraphResult`, tracking has already been performed |
| 27 | + inside the runner; the managed layer simply wraps the result. This detection |
| 28 | + branch exists as a deliberate bridge: once PR 11-openai and PR 11-langchain |
| 29 | + migrate both runners to return ``AgentGraphRunnerResult``, the legacy branch |
| 30 | + becomes dead code and will be removed in PR 11-langchain's final cleanup commit. |
16 | 31 |
|
17 | 32 | Obtain an instance via ``LDAIClient.create_agent_graph()``. |
18 | 33 | """ |
19 | 34 |
|
20 | 35 | def __init__( |
21 | 36 | self, |
22 | 37 | runner: AgentGraphRunner, |
| 38 | + graph: Optional[Any] = None, |
23 | 39 | ): |
24 | 40 | """ |
25 | 41 | Initialize ManagedAgentGraph. |
26 | 42 |
|
27 | 43 | :param runner: The AgentGraphRunner to delegate execution to |
| 44 | + :param graph: Optional AgentGraphDefinition used to create the |
| 45 | + graph-level tracker when the runner returns an |
| 46 | + :class:`AgentGraphRunnerResult` (new shape). Not needed for |
| 47 | + legacy runners that still return :class:`AgentGraphResult`. |
28 | 48 | """ |
29 | 49 | self._runner = runner |
| 50 | + self._graph = graph |
30 | 51 |
|
31 | 52 | async def run(self, input: Any) -> ManagedGraphResult: |
32 | 53 | """ |
33 | 54 | Run the agent graph with the given input. |
34 | 55 |
|
| 56 | + Delegates to the underlying AgentGraphRunner. The returned type |
| 57 | + determines which tracking path is taken: |
| 58 | +
|
| 59 | + - :class:`AgentGraphRunnerResult` (new shape): the managed layer drives |
| 60 | + graph-level tracking from ``result.metrics`` via the graph tracker. |
| 61 | + Per-node tracking from ``result.metrics.node_metrics`` will be wired |
| 62 | + in a follow-up commit once the runners populate ``node_metrics``. |
| 63 | + - :class:`AgentGraphResult` (legacy shape): tracking already occurred |
| 64 | + inside the runner; the managed layer wraps the result without |
| 65 | + additional tracking. |
| 66 | +
|
35 | 67 | :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). |
| 68 | + :return: ManagedGraphResult containing the content, metric summary, |
| 69 | + raw response, and an optional evaluations task (always ``None`` |
| 70 | + for now — per-graph evaluations will be added in a future PR). |
39 | 71 | """ |
40 | | - result: AgentGraphResult = await self._runner.run(input) |
| 72 | + raw_result = await self._runner.run(input) |
41 | 73 |
|
| 74 | + if isinstance(raw_result, AgentGraphRunnerResult): |
| 75 | + # New shape: managed layer drives all tracking. |
| 76 | + summary = self._build_summary_from_runner_result(raw_result) |
| 77 | + if self._graph is not None: |
| 78 | + self._flush_graph_tracking(raw_result, self._graph.create_tracker()) |
| 79 | + return ManagedGraphResult( |
| 80 | + content=raw_result.content, |
| 81 | + metrics=summary, |
| 82 | + raw=raw_result.raw, |
| 83 | + evaluations=None, |
| 84 | + ) |
| 85 | + |
| 86 | + # Legacy shape (AgentGraphResult): tracking already happened in the runner. |
42 | 87 | # Build a GraphMetricSummary from the runner result's LDAIMetrics. |
43 | 88 | # path and node_metrics will be populated once graph runners are migrated |
44 | | - # to return AgentGraphRunnerResult with GraphMetrics (PR 11). |
45 | | - metrics = result.metrics |
| 89 | + # to return AgentGraphRunnerResult with GraphMetrics (PR 11-openai/langchain). |
| 90 | + metrics: LDAIMetrics = raw_result.metrics |
46 | 91 | summary = GraphMetricSummary( |
47 | 92 | success=metrics.success, |
48 | 93 | usage=metrics.usage, |
49 | 94 | duration_ms=getattr(metrics, 'duration_ms', None), |
50 | 95 | ) |
51 | | - |
52 | 96 | return ManagedGraphResult( |
53 | | - content=result.output, |
| 97 | + content=raw_result.output, |
54 | 98 | metrics=summary, |
55 | | - raw=result.raw, |
| 99 | + raw=raw_result.raw, |
56 | 100 | evaluations=None, |
57 | 101 | ) |
58 | 102 |
|
| 103 | + def _build_summary_from_runner_result( |
| 104 | + self, |
| 105 | + result: AgentGraphRunnerResult, |
| 106 | + ) -> GraphMetricSummary: |
| 107 | + """Build a GraphMetricSummary from an AgentGraphRunnerResult.""" |
| 108 | + m = result.metrics |
| 109 | + return GraphMetricSummary( |
| 110 | + success=m.success, |
| 111 | + path=list(m.path), |
| 112 | + duration_ms=m.duration_ms, |
| 113 | + usage=m.usage, |
| 114 | + node_metrics=dict(m.node_metrics), |
| 115 | + ) |
| 116 | + |
| 117 | + def _flush_graph_tracking(self, result: AgentGraphRunnerResult, tracker: Any) -> None: |
| 118 | + """ |
| 119 | + Drive graph-level LaunchDarkly tracking events from runner result metrics. |
| 120 | +
|
| 121 | + Called only when the runner returns the new ``AgentGraphRunnerResult`` |
| 122 | + shape. Node-level tracking (from ``result.metrics.node_metrics``) will |
| 123 | + be wired once the runners start populating that field. |
| 124 | + """ |
| 125 | + m = result.metrics |
| 126 | + if m.path: |
| 127 | + tracker.track_path(m.path) |
| 128 | + if m.duration_ms is not None: |
| 129 | + tracker.track_duration(m.duration_ms) |
| 130 | + if m.success: |
| 131 | + tracker.track_invocation_success() |
| 132 | + else: |
| 133 | + tracker.track_invocation_failure() |
| 134 | + if m.usage is not None: |
| 135 | + tracker.track_total_tokens(m.usage) |
| 136 | + |
59 | 137 | def get_agent_graph_runner(self) -> AgentGraphRunner: |
60 | 138 | """ |
61 | 139 | Return the underlying AgentGraphRunner for advanced use. |
|
0 commit comments