Skip to content

Commit 1a42462

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

File tree

8 files changed

+9532
-12
lines changed

8 files changed

+9532
-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: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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
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+
if self._schema_data:
66+
# Parse from entry-points.json format
67+
entry_points = self._schema_data.get("entryPoints", [])
68+
if entry_points:
69+
ep = entry_points[0]
70+
71+
# Build graph if present
72+
graph = None
73+
if "graph" in ep and ep["graph"]:
74+
graph_data = ep["graph"]
75+
nodes = [
76+
UiPathRuntimeNode(**node)
77+
for node in graph_data.get("nodes", [])
78+
]
79+
edges = [
80+
UiPathRuntimeEdge(**edge)
81+
for edge in graph_data.get("edges", [])
82+
]
83+
graph = UiPathRuntimeGraph(nodes=nodes, edges=edges)
84+
85+
return UiPathRuntimeSchema(
86+
filePath=ep["filePath"],
87+
uniqueId=ep["uniqueId"],
88+
type=ep["type"],
89+
input=ep["input"],
90+
output=ep["output"],
91+
graph=graph,
92+
)
93+
94+
# Fallback: return empty schema
95+
return UiPathRuntimeSchema()
96+
97+
async def execute(
98+
self,
99+
input: dict[str, Any] | None = None,
100+
options: UiPathExecuteOptions | None = None,
101+
) -> UiPathRuntimeResult:
102+
"""Execute by replaying all events and returning final result."""
103+
logger.info("MockTemplateRuntime: starting execution")
104+
105+
# Stream all events and capture the final result
106+
final_result = None
107+
async for event in self.stream(input=input, options=options):
108+
if isinstance(event, UiPathRuntimeResult):
109+
final_result = event
110+
111+
return final_result or UiPathRuntimeResult(
112+
output={},
113+
status=UiPathRuntimeStatus.SUCCESSFUL,
114+
)
115+
116+
async def stream(
117+
self,
118+
input: dict[str, Any] | None = None,
119+
options: UiPathStreamOptions | None = None,
120+
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
121+
"""Stream events from the JSON file."""
122+
logger.info(f"MockTemplateRuntime: streaming {len(self._events)} events")
123+
124+
with self.tracer.start_as_current_span(
125+
"template.stream",
126+
attributes={
127+
"uipath.runtime.name": "MockTemplateRuntime",
128+
"uipath.event.count": len(self._events),
129+
},
130+
):
131+
for i, event_data in enumerate(self._events):
132+
event_type = event_data.get("event_type")
133+
134+
# Add small delay between events for realistic streaming
135+
if i > 0:
136+
await asyncio.sleep(0.01)
137+
138+
try:
139+
if event_type == "runtime_message":
140+
payload_data = event_data.get("payload", {})
141+
142+
conversation_event = (
143+
UiPathConversationMessageEvent.model_validate(payload_data)
144+
)
145+
146+
yield UiPathRuntimeMessageEvent(
147+
payload=conversation_event,
148+
execution_id=event_data.get("execution_id"),
149+
metadata=event_data.get("metadata"),
150+
)
151+
152+
elif event_type == "runtime_state":
153+
yield UiPathRuntimeStateEvent(
154+
payload=event_data.get("payload", {}),
155+
node_name=event_data.get("node_name"),
156+
execution_id=event_data.get("execution_id"),
157+
metadata=event_data.get("metadata"),
158+
)
159+
160+
elif event_type == "runtime_result":
161+
yield UiPathRuntimeResult(
162+
output=event_data.get("output"),
163+
status=UiPathRuntimeStatus(
164+
event_data.get("status", "successful")
165+
),
166+
execution_id=event_data.get("execution_id"),
167+
metadata=event_data.get("metadata"),
168+
)
169+
170+
else:
171+
logger.warning(f"Unknown event type: {event_type}")
172+
173+
except Exception as e:
174+
logger.error(f"Error processing event {i}: {e}", exc_info=True)
175+
continue
176+
177+
logger.info("MockReplayRuntime: streaming completed")
178+
179+
async def dispose(self) -> None:
180+
"""Cleanup resources."""
181+
logger.info("MockReplayRuntime: dispose() invoked")
182+
183+
184+
def create_template_runtime(
185+
events_json: str | Path,
186+
schema_json: str | Path | None = None,
187+
) -> MockTemplateRuntime:
188+
"""Create a template runtime from JSON files.
189+
190+
Args:
191+
events_json: Path to JSON file containing events array
192+
schema_json: Optional path to entry-points.json schema file
193+
194+
Returns:
195+
MockTemplateRuntime instance
196+
"""
197+
return MockTemplateRuntime(
198+
events_file=events_json,
199+
schema_file=schema_json,
200+
)

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)