Skip to content

Commit 4f4ee1d

Browse files
committed
feat(Mocking): allow functions to be marked as mockable
Initial design wanted to let every tracable elements to also be mockable. However, langgraph and `@traced` annotations uses different tracing mechanisms. Keeping `@mockable` separate from the tracing system will create a cleaner separation of concern. The `mockingStrategy` can be extended to support different types of strategies. Currently, LLM strategy and mockito strategy is supported. The LLM strategy is backwards compatible, however the feature is incomplete: 1. The prompt used to generate LLM responses need to be 1:1 with the c# world. Specially, the current run history may be an important part of the prompt. 2. Dynamic input generation is currently unsupported. 3. Model selection is currently unsupported. 4. Caching is currently unsupported.
1 parent dbffd67 commit 4f4ee1d

28 files changed

+1750
-1012
lines changed

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.1.72"
3+
version = "2.1.73"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"
@@ -19,6 +19,9 @@ dependencies = [
1919
"truststore>=0.10.1",
2020
"textual>=5.3.0",
2121
"pyperclip>=1.9.0",
22+
"mockito>=1.5.4",
23+
"hydra-core>=1.3.2",
24+
"pydantic-function-models>=0.1.10",
2225
]
2326
classifiers = [
2427
"Development Status :: 3 - Alpha",

samples/calculator/evals/eval-sets/default.json

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,47 @@
1010
{
1111
"id": "test",
1212
"name": "Test Addition",
13-
"inputs": {"a": 1, "b": 1, "operator": "+"},
13+
"inputs": {"a": 1, "b": 1, "operator": "random"},
1414
"expectedOutput": {"result": 2},
1515
"simulationInstructions": "",
1616
"expectedAgentBehavior": "",
1717
"simulateInput": false,
1818
"inputGenerationInstructions": "",
1919
"simulateTools": false,
2020
"toolsToSimulate": [],
21+
"mockingStrategy": {
22+
"type": "mockito",
23+
"behaviors": [
24+
{
25+
"function": "get_random_operator",
26+
"arguments": {
27+
"args": [],
28+
"kwargs": {}
29+
},
30+
"then": [
31+
{
32+
"type": "return",
33+
"value": {"result": "+"}
34+
}
35+
]
36+
}
37+
]
38+
},
39+
"evalSetId": "default-eval-set-id",
40+
"createdAt": "2025-09-04T18:54:58.378Z",
41+
"updatedAt": "2025-09-04T18:55:55.416Z"
42+
},
43+
{
44+
"id": "test",
45+
"name": "Test Addition",
46+
"inputs": {"a": 1, "b": 1, "operator": "random"},
47+
"expectedOutput": {"result": 2},
48+
"expectedAgentBehavior": "",
49+
"mockingStrategy": {
50+
"type": "llm",
51+
"prompt": "The random operator is '+'.",
52+
"toolsToSimulate": [{"name": "get_random_operator"}]
53+
},
2154
"evalSetId": "default-eval-set-id",
2255
"createdAt": "2025-09-04T18:54:58.378Z",
2356
"updatedAt": "2025-09-04T18:55:55.416Z"

samples/calculator/main.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
import random
2+
13
from pydantic.dataclasses import dataclass
24
from enum import Enum
35

46
from uipath.tracing import traced
57
import logging
68

9+
from uipath.eval.mocks.mocks import mockable
10+
711
logger = logging.getLogger(__name__)
812

913
class Operator(Enum):
1014
ADD = "+"
1115
SUBTRACT = "-"
1216
MULTIPLY = "*"
1317
DIVIDE = "/"
18+
RANDOM = "random"
1419

1520
@dataclass
1621
class CalculatorInput:
@@ -22,13 +27,27 @@ class CalculatorInput:
2227
class CalculatorOutput:
2328
result: float
2429

25-
# use InputTriggerEventArgs when called by UiPath EventTriggers
30+
@dataclass
31+
class Wrapper:
32+
result: Operator
33+
34+
@traced()
35+
@mockable()
36+
def get_random_operator() -> Wrapper:
37+
"""Get a random operator."""
38+
return Wrapper(result=random.choice([Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE]))
39+
40+
2641
@traced()
27-
def main(input: CalculatorInput) -> CalculatorOutput:
28-
result = 0.0
29-
match input.operator:
42+
async def main(input: CalculatorInput) -> CalculatorOutput:
43+
if input.operator == Operator.RANDOM:
44+
operator = get_random_operator().result
45+
else:
46+
operator = input.operator
47+
match operator:
3048
case Operator.ADD: result = input.a + input.b
3149
case Operator.SUBTRACT: result = input.a - input.b
3250
case Operator.MULTIPLY: result = input.a * input.b
33-
case Operator.DIVIDE: result = input.a / input.b if input.b != 0 else 0
51+
case Operator.DIVIDE: result = input.a / input.b if input.b != 0.0 else 0.0
52+
case _: raise ValueError("Unknown operator")
3453
return CalculatorOutput(result=result)

src/uipath/_cli/_evals/_evaluator_factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
from pydantic import TypeAdapter
44

5-
from uipath._cli._evals._models._evaluator import (
5+
from uipath.eval._models._evaluator import (
66
EqualsEvaluatorParams,
77
Evaluator,
88
JsonSimilarityEvaluatorParams,
99
LLMEvaluatorParams,
1010
TrajectoryEvaluatorParams,
1111
)
12-
from uipath._cli._evals._models._evaluator_base_params import EvaluatorBaseParams
12+
from uipath.eval._models._evaluator_base_params import EvaluatorBaseParams
1313
from uipath.eval.evaluators import (
1414
BaseEvaluator,
1515
ExactMatchEvaluator,

src/uipath/_cli/_evals/_models/_evaluation_set.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

src/uipath/_cli/_evals/_progress_reporter.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@
99
from opentelemetry import trace
1010

1111
from uipath import UiPath
12-
from uipath._cli._evals._models._evaluation_set import EvaluationItem, EvaluationStatus
13-
from uipath._cli._evals._models._sw_reporting import (
14-
StudioWebAgentSnapshot,
15-
StudioWebProgressItem,
16-
)
1712
from uipath._cli._utils._console import ConsoleLogger
1813
from uipath._cli._utils._project_files import ( # type: ignore
1914
get_project_config,
@@ -28,6 +23,11 @@
2823
)
2924
from uipath._utils import Endpoint, RequestSpec
3025
from uipath._utils.constants import ENV_TENANT_ID, HEADER_INTERNAL_TENANT_ID
26+
from uipath.eval._models._evaluation_set import EvaluationItem, EvaluationStatus
27+
from uipath.eval._models._sw_reporting import (
28+
StudioWebAgentSnapshot,
29+
StudioWebProgressItem,
30+
)
3131
from uipath.eval.evaluators import BaseEvaluator
3232
from uipath.eval.models import EvalItemResult, ScoreType
3333
from uipath.tracing import LlmOpsHttpExporter

src/uipath/_cli/_evals/_runtime.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@
1515
EvalSetRunUpdatedEvent,
1616
EvaluationEvents,
1717
)
18+
from ...eval._models._evaluation_set import EvaluationItem, EvaluationSet
19+
from ...eval._models._output import (
20+
EvaluationResultDto,
21+
EvaluationRunResult,
22+
EvaluationRunResultDto,
23+
UiPathEvalOutput,
24+
UiPathEvalRunExecutionOutput,
25+
)
1826
from ...eval.evaluators import BaseEvaluator
27+
from ...eval.mocks.mocks import set_evaluation_item
1928
from ...eval.models import EvaluationResult
2029
from ...eval.models.models import AgentExecution, EvalItemResult
2130
from .._runtime._contracts import (
@@ -27,14 +36,6 @@
2736
)
2837
from .._utils._eval_set import EvalHelpers
2938
from ._evaluator_factory import EvaluatorFactory
30-
from ._models._evaluation_set import EvaluationItem, EvaluationSet
31-
from ._models._output import (
32-
EvaluationResultDto,
33-
EvaluationRunResult,
34-
EvaluationRunResultDto,
35-
UiPathEvalOutput,
36-
UiPathEvalRunExecutionOutput,
37-
)
3839

3940
T = TypeVar("T", bound=UiPathBaseRuntime)
4041
C = TypeVar("C", bound=UiPathRuntimeContext)
@@ -137,6 +138,7 @@ async def execute(self) -> Optional[UiPathRuntimeResult]:
137138
evaluation_set_name=evaluation_set.name, score=0, evaluation_set_results=[]
138139
)
139140
for eval_item in evaluation_set.evaluations:
141+
set_evaluation_item(eval_item)
140142
await event_bus.publish(
141143
EvaluationEvents.CREATE_EVAL_RUN,
142144
EvalRunCreatedEvent(

src/uipath/_cli/_runtime/_logging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def setup(self) -> None:
167167
self.patched_loggers.add(logger_name)
168168

169169
# Set up stdout/stderr redirection
170-
self._redirect_stdout_stderr()
170+
# self._redirect_stdout_stderr()
171171

172172
def _redirect_stdout_stderr(self) -> None:
173173
"""Redirect stdout and stderr to the logging system."""

src/uipath/_cli/_utils/_eval_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import click
66

7-
from uipath._cli._evals._models._evaluation_set import EvaluationSet
87
from uipath._cli._utils._console import ConsoleLogger
8+
from uipath.eval._models._evaluation_set import EvaluationSet
99

1010
console = ConsoleLogger()
1111

src/uipath/_events/_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from opentelemetry.sdk.trace import ReadableSpan
55
from pydantic import BaseModel, ConfigDict
66

7-
from uipath._cli._evals._models._evaluation_set import EvaluationItem
7+
from uipath.eval._models._evaluation_set import EvaluationItem
88
from uipath.eval.models import EvalItemResult
99

1010

0 commit comments

Comments
 (0)