forked from UiPath/uipath-dev-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraph.py
More file actions
130 lines (102 loc) · 4 KB
/
Copy pathgraph.py
File metadata and controls
130 lines (102 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Graph visualization endpoint (React Flow format)."""
from __future__ import annotations
import logging
from typing import Any
from fastapi import APIRouter, Request
router = APIRouter(tags=["graph"])
logger = logging.getLogger(__name__)
# Map runtime node types to React Flow custom node types
_NODE_TYPE_MAP = {
"__start__": "startNode",
"__end__": "endNode",
"model": "modelNode",
"tool": "toolNode",
}
def _process_graph(graph: Any) -> dict[str, Any]:
"""Recursively convert a runtime graph to React Flow format."""
nodes: list[dict[str, Any]] = []
edges: list[dict[str, Any]] = []
graph_nodes = getattr(graph, "nodes", [])
graph_edges = getattr(graph, "edges", [])
for gn in graph_nodes:
node_id = getattr(gn, "id", None) or getattr(gn, "name", str(gn))
node_name = getattr(gn, "name", None) or node_id
raw_type = getattr(gn, "type", None) or ""
# Resolve node type: check type field, then ID, then metadata
node_type = _NODE_TYPE_MAP.get(str(raw_type)) or _NODE_TYPE_MAP.get(
str(node_id), "defaultNode"
)
metadata = getattr(gn, "metadata", {}) or {}
if "node_type" in metadata:
node_type = _NODE_TYPE_MAP.get(metadata["node_type"], metadata["node_type"])
node_data: dict[str, Any] = {"label": node_name}
# Pass through relevant metadata for frontend rendering
if metadata:
for key in ("tool_names", "tool_count", "model_name"):
if key in metadata:
node_data[key] = metadata[key]
# Recursively process subgraph if present
subgraph = getattr(gn, "subgraph", None)
if subgraph is not None:
node_data["subgraph"] = _process_graph(subgraph)
nodes.append(
{
"id": node_id,
"type": node_type,
"data": node_data,
"position": {"x": 0, "y": 0},
}
)
for ge in graph_edges:
source = getattr(ge, "source", None) or (
ge[0] if isinstance(ge, (list, tuple)) else str(ge)
)
target = getattr(ge, "target", None) or (
ge[1] if isinstance(ge, (list, tuple)) else str(ge)
)
label = getattr(ge, "label", None) or getattr(ge, "data", None)
edge_data: dict[str, Any] = {
"id": f"{source}-{target}",
"source": source,
"target": target,
}
if label:
edge_data["label"] = str(label)
edge_data["conditional"] = True
edges.append(edge_data)
return {"nodes": nodes, "edges": edges}
async def snapshot_graph(factory: Any, entrypoint: str) -> dict[str, Any]:
"""Fetch the graph for an entrypoint and return serialised React Flow data."""
runtime = None
try:
runtime = await factory.new_runtime(
entrypoint=entrypoint,
runtime_id="graph-preview",
)
graph = None
if hasattr(runtime, "get_schema"):
try:
schema = await runtime.get_schema()
graph = getattr(schema, "graph", None)
except Exception:
pass
if graph is None and hasattr(runtime, "get_graph"):
graph = runtime.get_graph()
elif graph is None and hasattr(runtime, "graph"):
graph = runtime.graph
if graph is not None:
return _process_graph(graph)
except Exception:
logger.exception("Failed to get graph for %s", entrypoint)
finally:
if runtime is not None:
try:
await runtime.dispose()
except Exception:
pass
return {"nodes": [], "edges": []}
@router.get("/entrypoints/{entrypoint:path}/graph")
async def get_graph(request: Request, entrypoint: str) -> dict[str, Any]:
"""Get the execution graph for an entrypoint in React Flow format."""
server = request.app.state.server
return await snapshot_graph(server.runtime_factory, entrypoint)