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
2 changes: 1 addition & 1 deletion packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.13.21"
version = "2.13.22"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
3 changes: 3 additions & 0 deletions packages/uipath/samples/simulate-component-agent/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

Run with per-component simulation (routes each tool call to the API):
uipath run main -f input.json --simulation "$(cat simulation.json)"

Debug with per-component simulation:
uipath debug main -f input.json --simulation "$(cat simulation.json)"
"""

from pydantic import BaseModel
Expand Down
49 changes: 39 additions & 10 deletions packages/uipath/src/uipath/_cli/cli_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from typing import Any, cast, get_args

import click
from pydantic import ValidationError

from uipath._cli._chat._bridge import get_chat_bridge
from uipath._cli._debug._bridge import DebugAttachMode, get_debug_bridge
from uipath._cli._utils._debug import setup_debugging
from uipath._cli._utils._studio_project import StudioClient
from uipath._cli._utils._tracing import create_trace_manager
from uipath.eval.mocks import UiPathMockRuntime
from uipath.eval.mocks import SimulationConfig, UiPathMockRuntime, build_mocking_context
from uipath.eval.mocks._mock_runtime import load_simulation_config
from uipath.platform.common import (
ExecutionSourceContext,
Expand Down Expand Up @@ -79,6 +80,12 @@
"'console' for local runs."
),
)
@click.option(
"--simulation",
required=False,
default=None,
help="Simulation config as a JSON object (same schema as simulation.json)",
)
@track_command("debug")
def debug(
entrypoint: str | None,
Expand All @@ -90,13 +97,22 @@ def debug(
debug: bool,
debug_port: int,
attach: str | None,
simulation: str | None,
) -> None:
"""Debug the project."""
input_file = file or input_file
# Setup debugging if requested
if not setup_debugging(debug, debug_port):
console.error(f"Failed to start debug server on port {debug_port}")

simulation_config: SimulationConfig | None = None
if simulation:
try:
simulation_config = SimulationConfig.model_validate_json(simulation)
except (ValidationError, ValueError) as e:
console.error(f"Invalid --simulation config: {e}")
return

attach_mode: DebugAttachMode | None = (
cast(DebugAttachMode, attach.lower()) if attach else None
)
Expand Down Expand Up @@ -223,22 +239,35 @@ async def execute_debug_runtime():
if schema.metadata and "settings" in schema.metadata:
agent_model = schema.metadata["settings"].get("model")

mocking_context = load_simulation_config(
agent_model=agent_model
)

mock_runtime = UiPathMockRuntime(
delegate=debug_runtime,
mocking_context=mocking_context,
delegate_runtime: UiPathDebugRuntime | UiPathMockRuntime = (
debug_runtime
)
if simulation_config:
mocking_context = build_mocking_context(
simulation_config, agent_model
)
if mocking_context:
delegate_runtime = UiPathMockRuntime(
delegate=debug_runtime,
mocking_context=mocking_context,
)
else:
mocking_context = load_simulation_config(
agent_model=agent_model
)
delegate_runtime = UiPathMockRuntime(
delegate=debug_runtime,
mocking_context=mocking_context,
)

try:
ctx.result = await mock_runtime.execute(
ctx.result = await delegate_runtime.execute(
ctx.get_input(),
options=UiPathExecuteOptions(resume=resume),
)
finally:
await mock_runtime.dispose()
if delegate_runtime is not debug_runtime:
await delegate_runtime.dispose()
await debug_runtime.dispose()
if chat_runtime:
await chat_runtime.dispose()
Expand Down
12 changes: 12 additions & 0 deletions packages/uipath/testcases/debug-simulation-testcase/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "debug-simulation-testcase"
version = "0.0.1"
description = "debug-simulation-testcase"
authors = [{ name = "UiPath", email = "python-sdk@uipath.com" }]
dependencies = [
"uipath",
]
requires-python = ">=3.11"

[tool.uv.sources]
uipath = { path = "../../", editable = true }
30 changes: 30 additions & 0 deletions packages/uipath/testcases/debug-simulation-testcase/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail

TESTCASE_DIR="$(cd "$(dirname "$0")" && pwd)"
SAMPLE_DIR="$(cd "$TESTCASE_DIR/../../samples/runtime-simulations-agent" && pwd)"

echo "Syncing testcase dependencies (local editable uipath)..."
uv sync --project "$TESTCASE_DIR"

UIPATH_BIN="$TESTCASE_DIR/.venv/bin/uipath"

# Run auth and agent from the sample dir so credentials are stored and read
# from the same location.
cd "$SAMPLE_DIR"

echo "Authenticating with UiPath..."
"$UIPATH_BIN" auth \
--client-id="$CLIENT_ID" \
--client-secret="$CLIENT_SECRET" \
--base-url="$BASE_URL"

echo "Running agent with debug + simulation..."
"$UIPATH_BIN" debug main \
-f input.json \
--attach none \
--simulation "$(cat simulation.json)" 2>&1 | tee "$TESTCASE_DIR/run.log"

# Copy the runtime output file back to the testcase dir for assert.py
mkdir -p "$TESTCASE_DIR/__uipath"
cp "$SAMPLE_DIR/__uipath/output.json" "$TESTCASE_DIR/__uipath/output.json"
57 changes: 57 additions & 0 deletions packages/uipath/testcases/debug-simulation-testcase/src/assert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json
import os

# ── 1. Verify agent output exists and succeeded ──────────────────────────────
output_file = "__uipath/output.json"
assert os.path.isfile(output_file), "Agent output file not found"

with open(output_file, "r", encoding="utf-8") as f:
output_data = json.load(f)

status = output_data.get("status")
assert status == "successful", f"Agent execution failed with status: {status}"

output = output_data.get("output", {})

assert "syntax" in output, "Missing 'syntax' in output"
assert "style" in output, "Missing 'style' in output"
assert "improvements" in output, "Missing 'improvements' in output"
assert "summary" in output, "Missing 'summary' in output"

assert isinstance(output["syntax"]["valid"], bool), "'syntax.valid' must be a bool"
assert isinstance(output["syntax"]["errors"], list), "'syntax.errors' must be a list"

score = output["style"]["score"]
assert isinstance(score, int), "'style.score' must be an int"
assert 0 <= score <= 100, f"'style.score' out of range: {score}"
assert isinstance(output["style"]["violations"], list), (
"'style.violations' must be a list"
)

assert isinstance(output["improvements"]["suggestions"], list), (
"'improvements.suggestions' must be a list"
)
assert isinstance(output["improvements"]["refactored_snippet"], str), (
"'improvements.refactored_snippet' must be a str"
)

# ── 2. Verify simulation produced non-default values ─────────────────────────
# Real tool impls always return: score=100, violations=[], suggestions=[].
# The LLM simulation should detect issues in the input code and return richer output.
simulated_something = (
score < 100
or len(output["style"]["violations"]) > 0
or len(output["improvements"]["suggestions"]) > 0
)
assert simulated_something, (
"Output matches hardcoded real-tool defaults β€” simulation may not have run. "
f"style.score={score}, violations={output['style']['violations']}, "
f"suggestions={output['improvements']['suggestions']}"
)

print(
f"Simulation confirmed: score={score}, "
f"violations={len(output['style']['violations'])}, "
f"suggestions={len(output['improvements']['suggestions'])}"
)
print("All assertions passed.")
Loading
Loading