Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# UiPath Developer Console

The **UiPath Developer Console** is an interactive terminal application for building, testing, and debugging UiPath Python runtimes, agents, and automation scripts.
[![PyPI downloads](https://img.shields.io/pypi/dm/uipath-dev.svg)](https://pypi.org/project/uipath-dev/)
[![Python versions](https://img.shields.io/pypi/pyversions/uipath-dev.svg)](https://pypi.org/project/uipath-dev/)


Interactive terminal application for building, testing, and debugging UiPath Python runtimes, agents, and automation scripts.

## Overview

Expand All @@ -12,6 +16,8 @@ This tool is designed for:
- Python engineers testing **standalone automation scripts** before deployment
- Contributors exploring **runtime orchestration** and **execution traces**

![Runtime Trace Demo](https://raw.githubusercontent.com/UiPath/uipath-dev-python/main/docs/demo_traces.svg)

## Features

- Run and inspect Python runtimes interactively
Expand Down
172 changes: 172 additions & 0 deletions docs/demo_traces.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
206 changes: 199 additions & 7 deletions src/uipath/dev/_demo/mock_runtime.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Minimal demo script to run UiPathDevTerminal with mock runtimes."""

import asyncio
import logging
from typing import Any, Optional

from opentelemetry import trace
from uipath.runtime import (
UiPathBaseRuntime,
UiPathExecuteOptions,
Expand All @@ -12,9 +14,11 @@
)
from uipath.runtime.schema import UiPathRuntimeSchema

logger = logging.getLogger(__name__)


class MockRuntime(UiPathBaseRuntime):
"""A simple mock runtime that echoes its input."""
"""A mock runtime that simulates a multi-step workflow with rich telemetry."""

async def get_schema(self) -> UiPathRuntimeSchema:
return UiPathRuntimeSchema(
Expand All @@ -39,19 +43,207 @@ async def execute(
options: Optional[UiPathExecuteOptions] = None,
) -> UiPathRuntimeResult:
payload = input or {}
# Simulate some async work
await asyncio.sleep(0.2)

tracer = trace.get_tracer("uipath.dev.mock-runtime")

execution_id = getattr(self.context, "job_id", None) or "mock-execution"
entrypoint = getattr(self.context, "entrypoint", None) or "mock-entrypoint"
message = str(payload.get("message", ""))
message_length = len(message)

with tracer.start_as_current_span(
"mock-runtime.execute",
attributes={
"uipath.runtime.name": "MockRuntime",
"uipath.runtime.type": "agent",
"uipath.execution.id": execution_id,
"uipath.runtime.entrypoint": entrypoint,
"uipath.input.message.length": message_length,
"uipath.input.has_message": "message" in payload,
},
) as root_span:
logger.info(
"MockRuntime: starting execution",
extra={
"uipath.execution.id": execution_id,
"uipath.runtime.entrypoint": entrypoint,
},
)
print(f"[MockRuntime] Starting execution (execution_id={execution_id})")

# Stage 1: Initialization
with tracer.start_as_current_span(
"initialize.environment",
attributes={
"uipath.step.name": "initialize-environment",
"uipath.step.kind": "init",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: initializing environment")
print("[MockRuntime] Initializing environment...")
await asyncio.sleep(0.5)

# Stage 2: Validation
with tracer.start_as_current_span(
"validate.input",
attributes={
"uipath.step.name": "validate-input",
"uipath.step.kind": "validation",
"uipath.execution.id": execution_id,
"uipath.input.has_message": "message" in payload,
},
) as validate_span:
logger.info("MockRuntime: validating input")
print("[MockRuntime] Validating input...")
await asyncio.sleep(0.5)

if "message" not in payload:
logger.warning("MockRuntime: missing 'message' in payload")
validate_span.set_attribute(
"uipath.validation.missing_field", "message"
)

# Stage 3: Preprocessing
with tracer.start_as_current_span(
"preprocess.data",
attributes={
"uipath.step.name": "preprocess-data",
"uipath.step.kind": "preprocess",
"uipath.execution.id": execution_id,
"uipath.input.size.bytes": len(str(payload).encode("utf-8")),
},
):
logger.info("MockRuntime: preprocessing data")
print("[MockRuntime] Preprocessing data...")
await asyncio.sleep(0.5)

# Stage 4: Compute / reasoning
with tracer.start_as_current_span(
"compute.result",
attributes={
"uipath.step.name": "compute-result",
"uipath.step.kind": "compute",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: compute phase started")
print("[MockRuntime] Compute phase...")

# Subtask: embedding computation
with tracer.start_as_current_span(
"compute.embeddings",
attributes={
"uipath.step.name": "compute-embeddings",
"uipath.step.kind": "compute-subtask",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: computing embeddings")
print("[MockRuntime] Computing embeddings...")
await asyncio.sleep(0.5)

# Subtask: KB query
with tracer.start_as_current_span(
"query.knowledgebase",
attributes={
"uipath.step.name": "query-knowledgebase",
"uipath.step.kind": "io",
"uipath.execution.id": execution_id,
"uipath.kb.query.length": message_length,
},
):
logger.info("MockRuntime: querying knowledge base")
print("[MockRuntime] Querying knowledge base...")
await asyncio.sleep(0.5)

# Stage 5: Post-processing
with tracer.start_as_current_span(
"postprocess.results",
attributes={
"uipath.step.name": "postprocess-results",
"uipath.step.kind": "postprocess",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: post-processing results")
print("[MockRuntime] Post-processing results...")
await asyncio.sleep(0.4)

with tracer.start_as_current_span(
"generate.output",
attributes={
"uipath.step.name": "generate-output",
"uipath.step.kind": "postprocess-subtask",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: generating structured output")
print("[MockRuntime] Generating output...")
await asyncio.sleep(0.4)

# Stage 6: Persistence
with tracer.start_as_current_span(
"persist.artifacts",
attributes={
"uipath.step.name": "persist-artifacts",
"uipath.step.kind": "io",
"uipath.execution.id": execution_id,
"uipath.persistence.enabled": False,
},
):
logger.info("MockRuntime: persisting artifacts (mock)")
print("[MockRuntime] Persisting artifacts (mock)...")
await asyncio.sleep(0.4)

# Stage 7: Cleanup
with tracer.start_as_current_span(
"cleanup.resources",
attributes={
"uipath.step.name": "cleanup-resources",
"uipath.step.kind": "cleanup",
"uipath.execution.id": execution_id,
},
):
logger.info("MockRuntime: cleaning up resources")
print("[MockRuntime] Cleaning up resources...")
await asyncio.sleep(0.3)

result_payload = {
"result": f"Mock runtime processed: {payload.get('message', '<no message>')}",
"metadata": {
"execution_id": execution_id,
"entrypoint": entrypoint,
"message_length": message_length,
},
}

root_span.set_attribute("uipath.runtime.status", "success")
root_span.set_attribute("uipath.runtime.duration.approx", "5s")
root_span.set_attribute("uipath.output.has_error", False)
root_span.set_attribute(
"uipath.output.message_length", len(str(result_payload))
)

logger.info(
"MockRuntime: execution completed successfully",
extra={
"uipath.execution.id": execution_id,
"uipath.runtime.status": "success",
},
)
print(f"[MockRuntime] Finished successfully with result={result_payload!r}")

return UiPathRuntimeResult(
output={"result": f"Mock runtime got: {payload!r}"},
output=result_payload,
status=UiPathRuntimeStatus.SUCCESSFUL,
)

async def cleanup(self) -> None:
# Nothing to clean up in this mock
pass
logger.info("MockRuntime: cleanup() invoked")
print("[MockRuntime] cleanup() invoked")


# 2) Mock runtime factory
class MockRuntimeFactory(UiPathRuntimeFactory[MockRuntime]):
"""Runtime factory compatible with UiPathDevTerminal expectations."""

Expand Down