From 9c81076780dfbbb52db46061d0b3686cc7a1dce4 Mon Sep 17 00:00:00 2001 From: Cristian Pufu Date: Sun, 9 Nov 2025 17:45:04 +0200 Subject: [PATCH] fix: tracing mock data --- README.md | 8 +- docs/demo_traces.svg | 172 ++++++++++++++++++++++ src/uipath/dev/_demo/mock_runtime.py | 206 ++++++++++++++++++++++++++- 3 files changed, 378 insertions(+), 8 deletions(-) create mode 100644 docs/demo_traces.svg diff --git a/README.md b/README.md index 120ddaf..438436e 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/docs/demo_traces.svg b/docs/demo_traces.svg new file mode 100644 index 0000000..b72fda4 --- /dev/null +++ b/docs/demo_traces.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UiPath Debugging Terminal + + + + + + + + + + HistoryDetailsTracesLogs +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✔  default  (17:42:36) [4.1s  ]▼ TraceSpan: validate.input +✔  default  (17:42:17) [4.1s  ]└── ▼ 🟢 mock-runtime.execute (4116.8 +├── ▼ 🟢 initialize.environment (Status: COMPLETED +├── ▶ 🟢 validate.input (515.4ms)Started: 17:42:36.933 +├── ▼ 🟢 preprocess.data (514.4msDuration: 515.37ms +├── ▼ 🟢 compute.result (1029.7ms +│   ├── ▼ 🟢 compute.embeddings (Attributes: +│   └── ▶ 🟢 query.knowledgebase   uipath.step.name: validate-input +├── ▼ 🟢 postprocess.results (824  uipath.step.kind: validation +│   └── ▼ 🟢 generate.output (404  uipath.execution.id: mock-execution +├── ▼ 🟢 persist.artifacts (414.1  uipath.input.has_message: False +└── ▼ 🟢 cleanup.resources (312.2  execution.id: a4890064 +  uipath.validation.missing_field: message + +Trace ID: 9faa29e12348850d90cdcbd19432941f +Span ID: 16bba84d70a086cf +Run ID: a4890064 +Parent Span: f154071c6bb29da8 + + + New  + + + q Quit  n New  r Run  c Copy  h Clear History  esc Cancel ^p palette + + + diff --git a/src/uipath/dev/_demo/mock_runtime.py b/src/uipath/dev/_demo/mock_runtime.py index 47adf2e..922bcc7 100644 --- a/src/uipath/dev/_demo/mock_runtime.py +++ b/src/uipath/dev/_demo/mock_runtime.py @@ -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, @@ -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( @@ -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', '')}", + "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."""