Skip to content

Commit a5f4418

Browse files
Chibionosclaude
andcommitted
feat: extract evaluation framework into uipath-eval package
Move src/uipath/eval to a new namespace package distribution (packages/uipath-eval, import path unchanged: uipath.eval) so the evaluation framework — evaluators, mocking, eval runtime — can be consumed standalone, e.g. by the python eval worker in the agents backend, without pulling in the CLI and the rest of the SDK. - uipath-eval 0.1.0: depends only on uipath-core, uipath-platform, uipath-runtime (+ mockito, pydantic-function-models, coverage, which move out of the main package's dependencies) - uipath 2.10.82 depends on uipath-eval>=0.1.0,<0.2.0; editable link via [tool.uv.sources] - pure-eval tests move with the code (731 tests); CLI-coupled eval tests (discovery, telemetry, progress reporter, live tracking) stay in packages/uipath - the three legacy evaluators' relative import of uipath._utils.constants now uses the eval-local constant - CI: detect_changed_packages dependency graph, test/lint jobs, cd.yml publish tier (core -> platform -> eval -> uipath), labeler Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 13bc71d commit a5f4418

130 files changed

Lines changed: 1858 additions & 54 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/labeler.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ test:uipath-langchain:
33
- any-glob-to-any-file: ['packages/uipath/src/**/*.py']
44
- changed-files:
55
- any-glob-to-any-file: ['packages/uipath-platform/src/**/*.py']
6+
- changed-files:
7+
- any-glob-to-any-file: ['packages/uipath-eval/src/**/*.py']
68
- changed-files:
79
- any-glob-to-any-file: ['packages/uipath-core/src/**/*.py']
810

@@ -11,6 +13,8 @@ test:uipath-integrations:
1113
- any-glob-to-any-file: ['packages/uipath/src/**/*.py']
1214
- changed-files:
1315
- any-glob-to-any-file: ['packages/uipath-platform/src/**/*.py']
16+
- changed-files:
17+
- any-glob-to-any-file: ['packages/uipath-eval/src/**/*.py']
1418
- changed-files:
1519
- any-glob-to-any-file: ['packages/uipath-core/src/**/*.py']
1620

.github/scripts/detect_changed_packages.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
# External dependents (uipath-langchain, uipath-runtime, etc.) are
1818
# handled separately via labeler.yml auto-labels.
1919
DEPENDENTS: dict[str, list[str]] = {
20-
"uipath-core": ["uipath-platform", "uipath"],
21-
"uipath-platform": ["uipath"],
20+
"uipath-core": ["uipath-platform", "uipath-eval", "uipath"],
21+
"uipath-platform": ["uipath-eval", "uipath"],
22+
"uipath-eval": ["uipath"],
2223
}
2324

2425

@@ -117,7 +118,9 @@ def main():
117118
if base_sha and head_sha:
118119
packages = get_changed_packages(base_sha, head_sha)
119120
event_type = "pull request" if event_name == "pull_request" else "push"
120-
print(f"{event_type.capitalize()} - detected {len(packages)} directly changed package(s):")
121+
print(
122+
f"{event_type.capitalize()} - detected {len(packages)} directly changed package(s):"
123+
)
121124
for pkg in packages:
122125
print(f" - {pkg}")
123126

.github/workflows/cd.yml

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ jobs:
133133
needs: [detect-publishable-packages, publish-uipath-platform]
134134
if: |
135135
always() &&
136-
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath')
136+
(contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval') ||
137+
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath'))
137138
runs-on: ubuntu-latest
138139
steps:
139140
- name: Checkout
@@ -154,9 +155,70 @@ jobs:
154155
if: "!contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-platform')"
155156
run: echo "uipath-platform not being published — skipping wait"
156157

157-
# --- Tier 2: uipath (depends on core + platform) ---
158-
build-uipath:
158+
# --- Tier 1.5: uipath-eval (depends on core + platform) ---
159+
build-uipath-eval:
159160
needs: [detect-publishable-packages, wait-for-uipath-platform]
161+
if: |
162+
always() &&
163+
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')
164+
uses: ./.github/workflows/build-package.yml
165+
with:
166+
package: uipath-eval
167+
needs-relock: true
168+
169+
publish-uipath-eval:
170+
name: Publish uipath-eval
171+
needs: [detect-publishable-packages, build-uipath-eval]
172+
if: |
173+
always() &&
174+
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')
175+
runs-on: ubuntu-latest
176+
environment: pypi
177+
permissions:
178+
contents: read
179+
id-token: write
180+
steps:
181+
- name: Retrieve release distributions
182+
uses: actions/download-artifact@v4
183+
with:
184+
name: release-dists-uipath-eval
185+
path: dist/
186+
187+
- name: Publish package distributions to PyPI
188+
uses: pypa/gh-action-pypi-publish@release/v1
189+
with:
190+
verbose: true
191+
skip-existing: true
192+
193+
wait-for-uipath-eval:
194+
name: Wait for uipath-eval on PyPI
195+
needs: [detect-publishable-packages, publish-uipath-eval]
196+
if: |
197+
always() &&
198+
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath')
199+
runs-on: ubuntu-latest
200+
steps:
201+
- name: Checkout
202+
if: contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')
203+
uses: actions/checkout@v4
204+
205+
- name: Setup Python
206+
if: contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')
207+
uses: actions/setup-python@v5
208+
with:
209+
python-version: '3.11'
210+
211+
- name: Wait for uipath-eval
212+
if: contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')
213+
run: python .github/scripts/wait_for_pypi.py uipath-eval
214+
215+
- name: Skip
216+
if: "!contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath-eval')"
217+
run: echo "uipath-eval not being published — skipping wait"
218+
219+
# --- Tier 2: uipath (depends on core + platform + eval) ---
220+
build-uipath:
221+
needs: [detect-publishable-packages, wait-for-uipath-eval]
160222
if: |
161223
always() &&
162224
contains(fromJson(needs.detect-publishable-packages.outputs.packages), 'uipath')

.github/workflows/lint-packages.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,60 @@ jobs:
139139
working-directory: packages/uipath-platform
140140
run: uv run ruff format --check .
141141

142+
lint-uipath-eval:
143+
name: Lint uipath-eval
144+
needs: detect-changed-packages
145+
runs-on: ubuntu-latest
146+
steps:
147+
- name: Check if package changed
148+
id: check
149+
run: |
150+
if echo '${{ needs.detect-changed-packages.outputs.packages }}' | jq -e 'index("uipath-eval")' > /dev/null; then
151+
echo "skip=false" >> $GITHUB_OUTPUT
152+
else
153+
echo "skip=true" >> $GITHUB_OUTPUT
154+
fi
155+
156+
- name: Skip
157+
if: steps.check.outputs.skip == 'true'
158+
run: echo "Skipping - no changes to uipath-eval"
159+
160+
- name: Checkout
161+
if: steps.check.outputs.skip != 'true'
162+
uses: actions/checkout@v4
163+
164+
- name: Setup uv
165+
if: steps.check.outputs.skip != 'true'
166+
uses: astral-sh/setup-uv@v5
167+
with:
168+
enable-cache: true
169+
170+
- name: Setup Python
171+
if: steps.check.outputs.skip != 'true'
172+
uses: actions/setup-python@v5
173+
with:
174+
python-version-file: "packages/uipath-eval/.python-version"
175+
176+
- name: Install dependencies
177+
if: steps.check.outputs.skip != 'true'
178+
working-directory: packages/uipath-eval
179+
run: uv sync --locked --all-extras
180+
181+
- name: Check static types
182+
if: steps.check.outputs.skip != 'true'
183+
working-directory: packages/uipath-eval
184+
run: uv run mypy --config-file pyproject.toml .
185+
186+
- name: Check linting
187+
if: steps.check.outputs.skip != 'true'
188+
working-directory: packages/uipath-eval
189+
run: uv run ruff check .
190+
191+
- name: Check formatting
192+
if: steps.check.outputs.skip != 'true'
193+
working-directory: packages/uipath-eval
194+
run: uv run ruff format --check .
195+
142196
lint-uipath:
143197
name: Lint uipath
144198
needs: detect-changed-packages

.github/workflows/test-packages.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,76 @@ jobs:
224224
UIPATH_FOLDER_KEY: ${{ secrets.UIPATH_MEMORY_FOLDER }}
225225
run: uv run pytest tests/services/test_memory_service_e2e.py -m e2e -v --no-cov
226226

227+
test-uipath-eval:
228+
name: Test (uipath-eval, ${{ matrix.python-version }}, ${{ matrix.os }})
229+
needs: detect-changed-packages
230+
runs-on: ${{ matrix.os }}
231+
strategy:
232+
fail-fast: false
233+
matrix:
234+
python-version: ["3.11", "3.12", "3.13"]
235+
os: [ubuntu-latest, windows-latest]
236+
steps:
237+
- name: Check if package changed
238+
id: check
239+
shell: bash
240+
run: |
241+
if echo '${{ needs.detect-changed-packages.outputs.packages }}' | jq -e 'index("uipath-eval")' > /dev/null; then
242+
echo "skip=false" >> $GITHUB_OUTPUT
243+
else
244+
echo "skip=true" >> $GITHUB_OUTPUT
245+
fi
246+
247+
- name: Skip
248+
if: steps.check.outputs.skip == 'true'
249+
shell: bash
250+
run: echo "Skipping - no changes to uipath-eval"
251+
252+
- name: Checkout
253+
if: steps.check.outputs.skip != 'true'
254+
uses: actions/checkout@v4
255+
256+
- name: Setup uv
257+
if: steps.check.outputs.skip != 'true'
258+
uses: astral-sh/setup-uv@v5
259+
260+
- name: Setup Python
261+
if: steps.check.outputs.skip != 'true'
262+
uses: actions/setup-python@v5
263+
with:
264+
python-version: ${{ matrix.python-version }}
265+
266+
- name: Install dependencies
267+
if: steps.check.outputs.skip != 'true'
268+
working-directory: packages/uipath-eval
269+
run: uv sync --all-extras --python ${{ matrix.python-version }}
270+
271+
- name: Run tests
272+
if: steps.check.outputs.skip != 'true' && !(matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13')
273+
working-directory: packages/uipath-eval
274+
run: uv run pytest
275+
276+
- name: Run tests with coverage
277+
if: steps.check.outputs.skip != 'true' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
278+
working-directory: packages/uipath-eval
279+
run: uv run pytest --cov-report=xml --cov-report=html --tb=short
280+
281+
- name: Upload coverage HTML report
282+
if: steps.check.outputs.skip != 'true' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' && always()
283+
uses: actions/upload-artifact@v4
284+
with:
285+
name: coverage-html-uipath-eval
286+
path: packages/uipath-eval/htmlcov/
287+
retention-days: 30
288+
289+
- name: Upload coverage XML report
290+
if: steps.check.outputs.skip != 'true' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' && always()
291+
uses: actions/upload-artifact@v4
292+
with:
293+
name: coverage-xml-uipath-eval
294+
path: packages/uipath-eval/coverage.xml
295+
retention-days: 30
296+
227297
test-uipath:
228298
name: Test (uipath, ${{ matrix.python-version }}, ${{ matrix.os }})
229299
needs: detect-changed-packages

CLAUDE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Repository Overview
66

7-
This is the **UiPath Python SDK** monorepo — a Python SDK and CLI for programmatic interaction with the UiPath Cloud Platform. It publishes three packages to PyPI: `uipath`, `uipath-core`, and `uipath-platform`.
7+
This is the **UiPath Python SDK** monorepo — a Python SDK and CLI for programmatic interaction with the UiPath Cloud Platform. It publishes four packages to PyPI: `uipath`, `uipath-core`, `uipath-platform`, and `uipath-eval`.
88

99
## Monorepo Structure
1010

11-
The repo contains three packages under `packages/`, each with its own `pyproject.toml`, `src/`, and `tests/`:
11+
The repo contains four packages under `packages/`, each with its own `pyproject.toml`, `src/`, and `tests/`:
1212

13-
- **`packages/uipath`** — The main SDK package. Contains the CLI (`uipath` command), agent framework, evaluation framework, tracing/telemetry, and function utilities. This is what users `uv pip install uipath`. Entry point: `src/uipath/_cli:cli`.
13+
- **`packages/uipath`** — The main SDK package. Contains the CLI (`uipath` command), agent framework, tracing/telemetry, and function utilities. This is what users `uv pip install uipath`. Entry point: `src/uipath/_cli:cli`.
1414
- **`packages/uipath-core`** — Core abstractions shared across packages: tracing, serialization, events, feature flags, error types, chat models, guardrails. Depends on OpenTelemetry and Pydantic.
15+
- **`packages/uipath-eval`** — The evaluation framework (`uipath.eval` namespace): evaluators (deterministic + LLM-based), the `@mockable` simulation/mocking system, and the eval runtime. Consumable standalone (e.g. by the python eval worker in the agents backend). Depends on `uipath-core`, `uipath-platform`, and `uipath-runtime`.
1516
- **`packages/uipath-platform`** — HTTP client layer for UiPath Platform APIs. Contains service classes for orchestrator resources (assets, buckets, jobs, processes, queues), action center, context grounding, documents, connections, chat, and guardrails. Depends on `uipath-core`, httpx, and tenacity.
1617

17-
Dependency chain: `uipath``uipath-platform``uipath-core`. Local editable links are configured via `[tool.uv.sources]`.
18+
Dependency chain: `uipath``uipath-eval``uipath-platform``uipath-core`. Local editable links are configured via `[tool.uv.sources]`.
1819

1920
## Build & Development Commands
2021

@@ -96,7 +97,7 @@ The CLI uses **click** and is organized as `cli_<command>.py` files in `src/uipa
9697

9798
### Evaluation Framework
9899

99-
`src/uipath/eval/` provides evaluators (ExactMatch, Contains, JsonSimilarity, LLMJudge, Trajectory, ToolCall, Classification) for testing agent quality.
100+
`packages/uipath-eval/src/uipath/eval/` provides evaluators (ExactMatch, Contains, JsonSimilarity, LLMJudge, Trajectory, ToolCall, Classification) for testing agent quality.
100101

101102
## Code Conventions
102103

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

packages/uipath-eval/CLAUDE.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with the `uipath-eval` package.
4+
5+
## Package Purpose
6+
7+
The evaluation framework for UiPath agents, providing the `uipath.eval` namespace package. Extracted from the main `uipath` SDK so consumers (e.g. the python eval worker in the agents backend) can depend on evaluators without the CLI and the rest of the SDK. Depends on `uipath-core`, `uipath-platform`, and `uipath-runtime`.
8+
9+
## Development Commands
10+
11+
```bash
12+
cd packages/uipath-eval
13+
14+
uv sync --all-extras # Install dependencies
15+
pytest # Run all tests
16+
pytest tests/eval/mocks/ # Run a test subdirectory
17+
ruff check . # Lint
18+
ruff format --check . # Format check
19+
mypy src # Type check
20+
```
21+
22+
No justfile exists for this package — run commands directly.
23+
24+
## Module Layout (`src/uipath/eval/`)
25+
26+
| Module | Purpose |
27+
|--------|---------|
28+
| `evaluators/` | Deterministic evaluators (ExactMatch, Contains, JsonSimilarity, classification, tool-call order/args/count/output), LLM-judge evaluators, legacy evaluators, evaluator factory + registration |
29+
| `models/` | `EvaluationSet`, `EvaluationItem`, `EvaluationResult`, `AgentExecution`, score types |
30+
| `mocks/` | `@mockable` decorator, LLM tool/input mocking, mockito integration, response caching, `UiPathMockRuntime` |
31+
| `runtime/` | `UiPathEvalRuntime`, `UiPathEvalContext`, `evaluate()` entry point, eval events, parallelization, exporters |
32+
| `helpers.py` | `EvalHelpers` (eval set loading/migration), `get_agent_model()` |
33+
34+
## Constraints
35+
36+
- This is a **namespace package**: `src/uipath/` has no `__init__.py`; only `src/uipath/eval/` does. Import paths are unchanged from when the code lived in the main SDK (`from uipath.eval...`).
37+
- Do not import from the main `uipath` package internals (`uipath._cli`, `uipath._utils`, `uipath.agent`, ...) — only `uipath.core`, `uipath.platform`, and `uipath.runtime` are available here. The main `uipath` package depends on this one, not vice versa.
38+
- Structured output across model providers must use function calling, not `response_format` (Claude returns prose, Gemini returns empty content for `response_format` on the normalized gateway — see `mocks/_structured_output.py`).
39+
- The CLI-facing progress reporters (`_progress_reporter.py`, `_console_progress_reporter.py`) intentionally stay in `packages/uipath/src/uipath/_cli/_evals/` — they are CLI infrastructure, not part of this package.

packages/uipath-eval/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# uipath-eval
2+
3+
Evaluation framework for UiPath agents, extracted from the main `uipath` SDK so
4+
it can be consumed standalone (for example by the python eval worker in the
5+
agents backend) without pulling in the CLI and the rest of the SDK.
6+
7+
Provides the `uipath.eval` namespace package:
8+
9+
- **`uipath.eval.evaluators`** — deterministic evaluators (ExactMatch, Contains,
10+
JsonSimilarity, classification, tool-call order/args/count/output) and
11+
LLM-based evaluators (LLM-judge output/trajectory), plus their legacy
12+
counterparts and the evaluator factory/registration system.
13+
- **`uipath.eval.models`** — evaluation sets, evaluation results, score types,
14+
agent execution models.
15+
- **`uipath.eval.mocks`** — the `@mockable` decorator, LLM tool/input mocking,
16+
mockito integration, and response caching used by simulation runs.
17+
- **`uipath.eval.runtime`**`UiPathEvalRuntime`, `UiPathEvalContext`, the
18+
`evaluate()` entry point, eval events, parallelization, and exporters.
19+
20+
## Installation
21+
22+
```bash
23+
uv pip install uipath-eval
24+
```
25+
26+
Import paths are unchanged from when this code lived in the `uipath` package:
27+
28+
```python
29+
from uipath.eval.evaluators import ExactMatchEvaluator
30+
from uipath.eval.models.evaluation_set import EvaluationSet
31+
from uipath.eval.runtime import UiPathEvalContext, evaluate
32+
```
33+
34+
## Development
35+
36+
```bash
37+
cd packages/uipath-eval
38+
39+
uv sync --all-extras # Install dependencies
40+
pytest # Run all tests
41+
ruff check . # Lint
42+
ruff format --check . # Format check
43+
mypy src # Type check
44+
```

0 commit comments

Comments
 (0)