Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6b11767
feat(eval): add evaluator type schemas for classification evaluators
ajay-kesavan May 20, 2026
037b60c
test(eval): add e2e tests + sample projects for classification evalua…
ajay-kesavan May 20, 2026
5e574f1
feat(eval): add dataset-level evaluator framework with precision/reca…
ajay-kesavan May 20, 2026
d6b7ab5
docs(eval): add runnable dataset evaluator demo + bump uv.lock for 2.…
ajay-kesavan May 20, 2026
e9ba8aa
Merge remote-tracking branch 'origin/main' into feat/eval-dataset-eva…
ajay-kesavan Jun 19, 2026
46c24e1
Merge remote-tracking branch 'origin/main' into feat/classification-e…
ajay-kesavan Jun 19, 2026
fb091e4
refactor(eval): embed aggregator specs in per-datapoint evaluator con…
ajay-kesavan Jun 19, 2026
d4e06b1
Merge branch 'feat/eval-dataset-evaluators' into feat/classification-…
ajay-kesavan Jun 19, 2026
77fcc10
feat(eval): wire sample classification evaluators to embedded aggrega…
ajay-kesavan Jun 19, 2026
c0436a3
refactor(eval): apply ponytail-review cleanup
ajay-kesavan Jun 19, 2026
05f6697
Merge branch 'feat/eval-dataset-evaluators' into feat/classification-…
ajay-kesavan Jun 19, 2026
50c64f4
refactor(eval): apply ponytail-review cleanup (justification + demo)
ajay-kesavan Jun 19, 2026
ad32c22
fix(eval): address adversarial-review feedback on dataset evaluators
ajay-kesavan Jun 19, 2026
cbbaf5f
Merge branch 'feat/eval-dataset-evaluators' into feat/classification-…
ajay-kesavan Jun 19, 2026
027901c
fix(eval): address adversarial-review feedback on classification samples
ajay-kesavan Jun 19, 2026
4d6afcc
fix(eval): address codex P1 + lint failures on dataset evaluators
ajay-kesavan Jun 19, 2026
c347fc7
Merge branch 'feat/eval-dataset-evaluators' into feat/classification-…
ajay-kesavan Jun 19, 2026
5d78205
test(eval): drop fscore-duplicate test that conflicts with #1663 H2 v…
ajay-kesavan Jun 19, 2026
363855d
fix(eval): publish aggregators in classification evaluator type schemas
ajay-kesavan Jun 19, 2026
2be26c9
Merge remote-tracking branch 'origin/main' into feat/classification-e…
ajay-kesavan Jun 24, 2026
b872c8f
Merge remote-tracking branch 'origin/main' into feat/classification-e…
ajay-kesavan Jul 7, 2026
dd910e4
refactor(eval): move aggregators to ExactMatch, revert on Binary/Mult…
ajay-kesavan Jul 8, 2026
383cbb7
refactor(eval)!: trim PR to the ExactMatch aggregator contract only
ajay-kesavan Jul 8, 2026
2885c26
feat(eval): restore local dataset aggregation for `uipath eval`
ajay-kesavan Jul 8, 2026
1ce1209
feat(eval)!: single aggregation implementation — SDK owns the math
ajay-kesavan Jul 9, 2026
ea3672e
fix(eval): review-round hardening for dataset aggregators
ajay-kesavan Jul 9, 2026
fa0d86c
fix(eval): round-2 review — validator gaps + test leanness
ajay-kesavan Jul 9, 2026
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.6"
version = "2.13.7"
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
62 changes: 62 additions & 0 deletions packages/uipath/src/uipath/eval/evaluators/_aggregator_specs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Aggregator specs embedded in per-datapoint classification evaluator configs.

Each aggregator is a run-level metric (precision / recall / f-score) attached
to a classification evaluator. Classes are declared once on the parent evaluator
config — every aggregator on the same evaluator operates on the same class
vocabulary, so the field is not repeated per spec. Only the metric-shape fields
(``averaging`` and, for fscore, ``f_value``) live on the spec itself.
"""

from __future__ import annotations

from typing import Annotated, Literal, Union

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel


class _AggregatorSpecBase(BaseModel):
"""Shared pydantic config for every aggregator variant."""

model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)


class PrecisionAggregatorSpec(_AggregatorSpecBase):
"""Run-level precision aggregator (multiclass, micro or macro averaged)."""

type: Literal["precision"] = "precision"
averaging: Literal["macro", "micro"]


class RecallAggregatorSpec(_AggregatorSpecBase):
"""Run-level recall aggregator (multiclass, micro or macro averaged)."""

type: Literal["recall"] = "recall"
averaging: Literal["macro", "micro"]


class FScoreAggregatorSpec(_AggregatorSpecBase):
"""Run-level F-beta aggregator (multiclass, micro or macro averaged)."""

type: Literal["fscore"] = "fscore"
averaging: Literal["macro", "micro"]
# Upper bound keeps beta² finite — a huge beta overflows to inf and the
# F-score becomes NaN, which is not representable in JSON.
f_value: float = Field(default=1.0, gt=0, le=1000)


class ConfusionMatrixAggregatorSpec(_AggregatorSpecBase):
"""Run-level raw k×k confusion matrix — no scalar headline, no averaging."""

type: Literal["confusion_matrix"] = "confusion_matrix"


AggregatorSpec = Annotated[
Union[
PrecisionAggregatorSpec,
RecallAggregatorSpec,
FScoreAggregatorSpec,
ConfusionMatrixAggregatorSpec,
],
Field(discriminator="type"),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Base abstractions for dataset-level evaluators.

A dataset-level evaluator runs once per evaluation set, after all per-datapoint
evaluators have produced their results. It consumes the per-datapoint
EvaluationResultDto values from one named source evaluator and emits a single
EvaluationResult that summarizes the dataset.

Concretely distinct from GenericBaseEvaluator: different evaluate() signature,
different lifecycle. Kept as a parallel hierarchy rather than a subclass so the
runtime cannot accidentally dispatch a dataset evaluator through the
per-datapoint loop.
"""

from __future__ import annotations

from abc import ABC, abstractmethod

from ..models.models import EvaluationResult, EvaluationResultDto
from ._aggregator_specs import AggregatorSpec


class BaseDatasetEvaluator(ABC):
"""Abstract base for dataset-level evaluators.

Constructed from an :class:`AggregatorSpec`, the source evaluator's name,
and the class vocabulary of the parent per-datapoint evaluator. Classes
live on the evaluator config (not the spec) — every aggregator on the same
evaluator operates on the same vocabulary.
"""

spec: AggregatorSpec
source_evaluator: str
classes: list[str]

def __init__(
self, spec: AggregatorSpec, source_evaluator: str, classes: list[str]
) -> None:
"""Store the aggregator spec, source evaluator name, and shared classes."""
self.spec = spec
self.source_evaluator = source_evaluator
self.classes = classes

@abstractmethod
def evaluate(self, results: list[EvaluationResultDto]) -> EvaluationResult:
"""Reduce per-datapoint results into a single run-level EvaluationResult."""
16 changes: 16 additions & 0 deletions packages/uipath/src/uipath/eval/evaluators/base_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ class BaseEvaluatorJustification(BaseModel):
expected: str
actual: str

@classmethod
def try_from(cls, details: object) -> "BaseEvaluatorJustification | None":
"""Coerce a free-form details payload into a justification, or return None.

Accepts either an existing instance or a dict that ``model_validate`` can
parse. Anything else (str, None, malformed dict) yields ``None``.
"""
if isinstance(details, cls):
return details
if isinstance(details, dict):
try:
return cls.model_validate(details)
except Exception:
return None
return None


# Additional type variables for Config and Justification
# Note: C must be BaseEvaluatorConfig[T] to ensure type consistency
Expand Down
Loading