Skip to content

Commit a5f560a

Browse files
committed
fix: add mock template runtime with chat events
1 parent 1c34109 commit a5f560a

File tree

8 files changed

+9524
-12
lines changed

8 files changed

+9524
-12
lines changed

src/uipath/dev/_demo/chat_agent/entry-points.json

Lines changed: 2204 additions & 0 deletions
Large diffs are not rendered by default.

src/uipath/dev/_demo/chat_agent/events.json

Lines changed: 7076 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Minimal demo script to run UiPathDevTerminal with mock runtimes."""
22

33
import logging
4+
from pathlib import Path
45

56
from uipath.runtime import (
67
UiPathRuntimeProtocol,
78
)
89

9-
from uipath.dev._demo.mock_context_runtime import ENTRYPOINT_CONTEXT, MockContextRuntime
1010
from uipath.dev._demo.mock_greeting_runtime import (
1111
ENTRYPOINT_GREETING,
1212
MockGreetingRuntime,
@@ -19,13 +19,32 @@
1919
ENTRYPOINT_SUPPORT_CHAT,
2020
MockSupportChatRuntime,
2121
)
22+
from uipath.dev._demo.mock_telemetry_runtime import (
23+
ENTRYPOINT_TELEMETRY,
24+
MockTelemetryRuntime,
25+
)
26+
from uipath.dev._demo.mock_template_runtime import (
27+
create_template_runtime,
28+
)
2229

2330
logger = logging.getLogger(__name__)
2431

32+
# Template mappings: entrypoint -> (events_file, schema_file)
33+
TEMPLATE_RUNTIMES = {
34+
"chat/movies.py:graph": (
35+
"chat_agent/events.json",
36+
"chat_agent/entry-points.json",
37+
),
38+
}
39+
2540

2641
class MockRuntimeFactory:
2742
"""Runtime factory compatible with UiPathRuntimeFactoryProtocol."""
2843

44+
def __init__(self):
45+
# Get the directory where this file is located (src/uipath/dev/_demo/)
46+
self.demo_dir = Path(__file__).parent
47+
2948
async def new_runtime(
3049
self, entrypoint: str, runtime_id: str
3150
) -> UiPathRuntimeProtocol:
@@ -36,8 +55,19 @@ async def new_runtime(
3655
return MockNumberAnalyticsRuntime(entrypoint=entrypoint)
3756
if entrypoint == ENTRYPOINT_SUPPORT_CHAT:
3857
return MockSupportChatRuntime(entrypoint=entrypoint)
39-
if entrypoint == ENTRYPOINT_CONTEXT:
40-
return MockContextRuntime(entrypoint=entrypoint)
58+
if entrypoint == ENTRYPOINT_TELEMETRY:
59+
return MockTelemetryRuntime(entrypoint=entrypoint)
60+
61+
if entrypoint in TEMPLATE_RUNTIMES:
62+
events_file, schema_file = TEMPLATE_RUNTIMES[entrypoint]
63+
64+
events_path = self.demo_dir / events_file
65+
schema_path = self.demo_dir / schema_file
66+
67+
return create_template_runtime(
68+
events_json=events_path,
69+
schema_json=schema_path,
70+
)
4171

4272
# Fallback: still return something so the demo doesn't explode
4373
logger.warning(
@@ -47,20 +77,30 @@ async def new_runtime(
4777

4878
async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
4979
"""Return prototype instances for discovery (not really used by the UI)."""
50-
return [
80+
runtimes = [
5181
MockGreetingRuntime(entrypoint=ENTRYPOINT_GREETING),
5282
MockNumberAnalyticsRuntime(entrypoint=ENTRYPOINT_ANALYZE_NUMBERS),
5383
MockSupportChatRuntime(entrypoint=ENTRYPOINT_SUPPORT_CHAT),
54-
MockContextRuntime(entrypoint=ENTRYPOINT_CONTEXT),
84+
MockTelemetryRuntime(entrypoint=ENTRYPOINT_TELEMETRY),
5585
]
5686

87+
for entrypoint in TEMPLATE_RUNTIMES.keys():
88+
try:
89+
runtime = await self.new_runtime(entrypoint, runtime_id="discovery")
90+
runtimes.append(runtime)
91+
except Exception as e:
92+
logger.error(f"Failed to load template runtime '{entrypoint}': {e}")
93+
94+
return runtimes
95+
5796
def discover_entrypoints(self) -> list[str]:
5897
"""Return all available entrypoints."""
5998
return [
60-
ENTRYPOINT_CONTEXT,
99+
ENTRYPOINT_TELEMETRY,
61100
ENTRYPOINT_GREETING,
62101
ENTRYPOINT_ANALYZE_NUMBERS,
63102
ENTRYPOINT_SUPPORT_CHAT,
103+
*TEMPLATE_RUNTIMES.keys(),
64104
]
65105

66106
async def dispose(self) -> None:

src/uipath/dev/_demo/mock_numbers_runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
)
1313
from uipath.runtime.schema import UiPathRuntimeSchema
1414

15-
ENTRYPOINT_ANALYZE_NUMBERS = "analytics/numbers.py:analyze"
15+
ENTRYPOINT_ANALYZE_NUMBERS = "agent/numbers.py:analyze"
1616

1717
logger = logging.getLogger(__name__)
1818

src/uipath/dev/_demo/mock_support_runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
)
1313
from uipath.runtime.schema import UiPathRuntimeSchema
1414

15-
ENTRYPOINT_SUPPORT_CHAT = "agent/support.py:chat"
15+
ENTRYPOINT_SUPPORT_CHAT = "agent/support.py:main"
1616

1717
logger = logging.getLogger(__name__)
1818

src/uipath/dev/_demo/mock_context_runtime.py renamed to src/uipath/dev/_demo/mock_telemetry_runtime.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
)
1313
from uipath.runtime.schema import UiPathRuntimeSchema
1414

15-
ENTRYPOINT_CONTEXT = "agent/context.py:run"
15+
ENTRYPOINT_TELEMETRY = "agent/telemetry.py:run"
1616

1717
logger = logging.getLogger(__name__)
1818

1919

20-
class MockContextRuntime:
20+
class MockTelemetryRuntime:
2121
"""A mock runtime that simulates a multi-step workflow with rich telemetry."""
2222

23-
def __init__(self, entrypoint: str = ENTRYPOINT_CONTEXT) -> None:
23+
def __init__(self, entrypoint: str = ENTRYPOINT_TELEMETRY) -> None:
2424
self.entrypoint = entrypoint
2525
self.tracer = trace.get_tracer("uipath.dev.mock.context")
2626
# State tracking for breakpoints
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Mock runtime that replays events from a JSON file."""
2+
3+
import asyncio
4+
import json
5+
import logging
6+
from pathlib import Path
7+
from typing import Any, AsyncGenerator, cast
8+
9+
from opentelemetry import trace
10+
from uipath.core.chat import UiPathConversationMessageEvent
11+
from uipath.runtime import (
12+
UiPathExecuteOptions,
13+
UiPathRuntimeEvent,
14+
UiPathRuntimeResult,
15+
UiPathRuntimeStatus,
16+
UiPathStreamOptions,
17+
)
18+
from uipath.runtime.events import (
19+
UiPathRuntimeMessageEvent,
20+
UiPathRuntimeStateEvent,
21+
)
22+
from uipath.runtime.schema import (
23+
UiPathRuntimeEdge,
24+
UiPathRuntimeGraph,
25+
UiPathRuntimeNode,
26+
UiPathRuntimeSchema,
27+
)
28+
29+
logger = logging.getLogger(__name__)
30+
31+
32+
class MockTemplateRuntime:
33+
"""Mock runtime that replays events from a JSON file."""
34+
35+
def __init__(
36+
self,
37+
events_file: str | Path,
38+
schema_file: str | Path,
39+
) -> None:
40+
self.events_file = Path(events_file)
41+
self.schema_file = Path(schema_file)
42+
self.tracer = trace.get_tracer("uipath.dev.mock.template")
43+
self._events: list[dict[str, Any]] = []
44+
self._schema_data: dict[str, Any] | None = None
45+
self._load_events()
46+
self._load_schema()
47+
48+
def _load_events(self) -> None:
49+
"""Load events from JSON file."""
50+
with open(self.events_file, "r", encoding="utf-8") as f:
51+
data = json.load(f)
52+
53+
if isinstance(data, list):
54+
self._events = data
55+
else:
56+
raise ValueError("Expected JSON array of events")
57+
58+
def _load_schema(self) -> None:
59+
"""Load schema from separate JSON file if provided."""
60+
with open(self.schema_file, "r", encoding="utf-8") as f:
61+
self._schema_data = json.load(f)
62+
63+
async def get_schema(self) -> UiPathRuntimeSchema:
64+
"""Get runtime schema from the loaded data or build default."""
65+
assert self._schema_data is not None
66+
entry_points = self._schema_data.get("entryPoints", [])
67+
assert entry_points is not None and len(entry_points) > 0
68+
ep = entry_points[0]
69+
70+
# Build graph if present
71+
graph = None
72+
if "graph" in ep and ep["graph"]:
73+
graph_data = ep["graph"]
74+
nodes = [UiPathRuntimeNode(**node) for node in graph_data.get("nodes", [])]
75+
edges = [UiPathRuntimeEdge(**edge) for edge in graph_data.get("edges", [])]
76+
graph = UiPathRuntimeGraph(nodes=nodes, edges=edges)
77+
78+
return UiPathRuntimeSchema(
79+
filePath=ep["filePath"],
80+
uniqueId=ep["uniqueId"],
81+
type=ep["type"],
82+
input=ep["input"],
83+
output=ep["output"],
84+
graph=graph,
85+
)
86+
87+
async def execute(
88+
self,
89+
input: dict[str, Any] | None = None,
90+
options: UiPathExecuteOptions | None = None,
91+
) -> UiPathRuntimeResult:
92+
"""Execute by replaying all events and returning final result."""
93+
logger.info("MockTemplateRuntime: starting execution")
94+
95+
# Stream all events and capture the final result
96+
final_result = None
97+
async for event in self.stream(
98+
input=input, options=cast(UiPathStreamOptions, options)
99+
):
100+
if isinstance(event, UiPathRuntimeResult):
101+
final_result = event
102+
103+
return final_result or UiPathRuntimeResult(
104+
output={},
105+
status=UiPathRuntimeStatus.SUCCESSFUL,
106+
)
107+
108+
async def stream(
109+
self,
110+
input: dict[str, Any] | None = None,
111+
options: UiPathStreamOptions | None = None,
112+
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
113+
"""Stream events from the JSON file."""
114+
logger.info(f"MockTemplateRuntime: streaming {len(self._events)} events")
115+
116+
with self.tracer.start_as_current_span(
117+
"template.stream",
118+
attributes={
119+
"uipath.runtime.name": "MockTemplateRuntime",
120+
"uipath.event.count": len(self._events),
121+
},
122+
):
123+
for i, event_data in enumerate(self._events):
124+
event_type = event_data.get("event_type")
125+
126+
# Add small delay between events for realistic streaming
127+
if i > 0:
128+
await asyncio.sleep(0.01)
129+
130+
try:
131+
if event_type == "runtime_message":
132+
payload_data = event_data.get("payload", {})
133+
134+
conversation_event = (
135+
UiPathConversationMessageEvent.model_validate(payload_data)
136+
)
137+
138+
yield UiPathRuntimeMessageEvent(
139+
payload=conversation_event,
140+
execution_id=event_data.get("execution_id"),
141+
metadata=event_data.get("metadata"),
142+
)
143+
144+
elif event_type == "runtime_state":
145+
yield UiPathRuntimeStateEvent(
146+
payload=event_data.get("payload", {}),
147+
node_name=event_data.get("node_name"),
148+
execution_id=event_data.get("execution_id"),
149+
metadata=event_data.get("metadata"),
150+
)
151+
152+
elif event_type == "runtime_result":
153+
yield UiPathRuntimeResult(
154+
output=event_data.get("output"),
155+
status=UiPathRuntimeStatus(
156+
event_data.get("status", "successful")
157+
),
158+
execution_id=event_data.get("execution_id"),
159+
metadata=event_data.get("metadata"),
160+
)
161+
162+
else:
163+
logger.warning(f"Unknown event type: {event_type}")
164+
165+
except Exception as e:
166+
logger.error(f"Error processing event {i}: {e}", exc_info=True)
167+
continue
168+
169+
logger.info("MockReplayRuntime: streaming completed")
170+
171+
async def dispose(self) -> None:
172+
"""Cleanup resources."""
173+
logger.info("MockReplayRuntime: dispose() invoked")
174+
175+
176+
def create_template_runtime(
177+
events_json: str | Path,
178+
schema_json: str | Path,
179+
) -> MockTemplateRuntime:
180+
"""Create a template runtime from JSON files.
181+
182+
Args:
183+
events_json: Path to JSON file containing events array
184+
schema_json: Optional path to entry-points.json schema file
185+
186+
Returns:
187+
MockTemplateRuntime instance
188+
"""
189+
return MockTemplateRuntime(
190+
events_file=events_json,
191+
schema_file=schema_json,
192+
)

src/uipath/dev/_demo/run_dev_console.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from uipath.core.tracing import UiPathTraceManager
22

33
from uipath.dev import UiPathDeveloperConsole
4-
from uipath.dev._demo.mock_runtime import MockRuntimeFactory
4+
from uipath.dev._demo.mock_factory import MockRuntimeFactory
55

66

77
def main():

0 commit comments

Comments
 (0)