diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 4699bfeeb..df2ba2732 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.36" +version = "0.2.37" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/common/_endpoints_manager.py b/packages/uipath-platform/src/uipath/platform/common/_endpoints_manager.py index 8e3b436ac..a463e004a 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_endpoints_manager.py +++ b/packages/uipath-platform/src/uipath/platform/common/_endpoints_manager.py @@ -20,6 +20,9 @@ class UiPathEndpoints(Enum): AH_VENDOR_COMPLETION_ENDPOINT = ( "agenthub_/llm/raw/vendor/{vendor}/model/{model}/completions" ) + AH_VENDOR_DECISION_ENDPOINT = ( + "agenthub_/llm/raw/vendor/{vendor}/model/{model}/decisions" + ) AH_CAPABILITIES_ENDPOINT = "agenthub_/llm/api/capabilities" AH_DISCOVERY_ENDPOINT = "agenthub_/llm/api/discovery" @@ -29,6 +32,9 @@ class UiPathEndpoints(Enum): OR_VENDOR_COMPLETION_ENDPOINT = ( "orchestrator_/llm/raw/vendor/{vendor}/model/{model}/completions" ) + OR_VENDOR_DECISION_ENDPOINT = ( + "orchestrator_/llm/raw/vendor/{vendor}/model/{model}/decisions" + ) OR_CAPABILITIES_ENDPOINT = "orchestrator_/llm/api/capabilities" OR_DISCOVERY_ENDPOINT = "orchestrator_/llm/api/discovery" @@ -61,6 +67,7 @@ class EndpointManager: get_normalized_endpoint(): Returns the appropriate normalized completion endpoint. get_embeddings_endpoint(): Returns the appropriate embeddings endpoint. get_vendor_endpoint(): Returns the appropriate vendor completion endpoint. + get_vendor_decisions_endpoint(): Returns the appropriate vendor decisions endpoint. All endpoint methods automatically select the best available endpoint using the fallback order, unless overridden by the UIPATH_LLM_SERVICE environment variable. """ # noqa: D205 @@ -210,3 +217,11 @@ def get_vendor_endpoint(cls) -> str: UiPathEndpoints.AH_VENDOR_COMPLETION_ENDPOINT, UiPathEndpoints.OR_VENDOR_COMPLETION_ENDPOINT, ) + + @classmethod + def get_vendor_decisions_endpoint(cls) -> str: + """Get the vendor decisions endpoint (typed classification decisions).""" + return cls._select_endpoint( + UiPathEndpoints.AH_VENDOR_DECISION_ENDPOINT, + UiPathEndpoints.OR_VENDOR_DECISION_ENDPOINT, + ) diff --git a/packages/uipath-platform/tests/common/test_endpoints_manager.py b/packages/uipath-platform/tests/common/test_endpoints_manager.py new file mode 100644 index 000000000..6c411bb87 --- /dev/null +++ b/packages/uipath-platform/tests/common/test_endpoints_manager.py @@ -0,0 +1,32 @@ +import pytest + +from uipath.platform.common._endpoints_manager import EndpointManager + + +@pytest.mark.parametrize( + ("service", "expected"), + [ + ("agenthub", "agenthub_/llm/raw/vendor/{vendor}/model/{model}/decisions"), + ( + "orchestrator", + "orchestrator_/llm/raw/vendor/{vendor}/model/{model}/decisions", + ), + ], +) +def test_vendor_decisions_endpoint_follows_the_llm_service( + monkeypatch: pytest.MonkeyPatch, service: str, expected: str +) -> None: + monkeypatch.setenv("UIPATH_LLM_SERVICE", service) + + assert EndpointManager.get_vendor_decisions_endpoint() == expected + + +def test_vendor_completion_endpoint_is_unchanged( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("UIPATH_LLM_SERVICE", "agenthub") + + assert ( + EndpointManager.get_vendor_endpoint() + == "agenthub_/llm/raw/vendor/{vendor}/model/{model}/completions" + ) diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 99070bbf4..9ada6c4ac 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.36" +version = "0.2.37" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index c8ae36006..03591f19e 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath" -version = "2.14.37" +version = "2.14.38" 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" dependencies = [ "uipath-core>=0.5.33, <0.6.0", "uipath-runtime>=0.13.5, <0.14.0", - "uipath-platform>=0.2.34, <0.3.0", + "uipath-platform>=0.2.37, <0.3.0", "uipath-ipc>=2.5.1, <2.6.0", "click>=8.3.3, <9.0.0", "httpx>=0.28.1", diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index fab429836..4f0ce3691 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -131,7 +131,7 @@ class AgentInternalToolType(str, CaseInsensitiveEnum): BATCH_TRANSFORM = "batch-transform" HTTP_REQUEST = "http-request" CREATE_FILE = "create-file" - JEV_CLASSIFIER = "jev-classifier" + CLASSIFIER = "classifier" class AgentEscalationRecipientType(str, CaseInsensitiveEnum): @@ -1115,13 +1115,13 @@ class AgentInternalCreateFileToolProperties(BaseResourceProperties): ) -class AgentInternalJevClassifierToolProperties(BaseResourceProperties): - """Agent internal Jev classifier tool properties model.""" +class AgentInternalClassifierToolProperties(BaseResourceProperties): + """Agent internal classifier tool properties model.""" - tool_type: Literal[AgentInternalToolType.JEV_CLASSIFIER] = Field( - alias="toolType", default=AgentInternalToolType.JEV_CLASSIFIER, frozen=True + tool_type: Literal[AgentInternalToolType.CLASSIFIER] = Field( + alias="toolType", default=AgentInternalToolType.CLASSIFIER, frozen=True ) - settings: AgentInternalJevClassifierSettings = Field(..., alias="settings") + settings: AgentInternalClassifierSettings = Field(..., alias="settings") AgentInternalToolProperties = Annotated[ @@ -1131,7 +1131,7 @@ class AgentInternalJevClassifierToolProperties(BaseResourceProperties): AgentInternalBatchTransformToolProperties, AgentInternalHttpRequestToolProperties, AgentInternalCreateFileToolProperties, - AgentInternalJevClassifierToolProperties, + AgentInternalClassifierToolProperties, ], Field(discriminator="tool_type"), _case_insensitive_enum_validator("tool_type", AgentInternalToolType, "toolType"), @@ -1167,38 +1167,31 @@ class AgentInternalBatchTransformSettings(BaseCfg): ) -JEV_NAME_PATTERN = r"^[A-Za-z_][A-Za-z0-9_]{0,63}$" +# Question names are the keys of the answers in the tool output, for every provider. +CLASSIFIER_QUESTION_NAME_PATTERN = r"^[A-Za-z_][A-Za-z0-9_]{0,63}$" -class JevQuestionType(str, CaseInsensitiveEnum): - """Jev question type enumeration (matches TypeSafe's question types).""" +class ClassifierProvider(str, CaseInsensitiveEnum): + """The classification API a classifier tool calls, implied by its model.""" - CHOICE = "choice" - SCORE = "score" - NOUL = "noul" + TYPESAFE = "typesafe" # TypeSafe AI's Jev models + OPENAI = "openai" # OpenAI's Decisions API -class JevChoiceOption(BaseCfg): - """An option of a Jev choice question.""" +class BaseClassifierQuestion(BaseCfg): + """Common fields of a classifier question. - name: str = Field(..., min_length=1) - description: Optional[str] = Field(None) - - -class BaseJevQuestion(BaseCfg): - """Common fields of a Jev question. - - A question carries only the fields of its type: ``options`` belong to choice - questions, ``levels`` to score questions and ``criteria`` to noul questions. + A question carries only the fields of its provider and type: ``foreign_fields`` + lists the fields of the other types (and of the other provider) it rejects. """ foreign_fields: ClassVar[tuple[str, ...]] = () - name: str = Field(..., pattern=JEV_NAME_PATTERN) + name: str = Field(..., pattern=CLASSIFIER_QUESTION_NAME_PATTERN) instructions: str = Field(..., min_length=1) @model_validator(mode="after") - def _only_own_fields(self) -> "BaseJevQuestion": + def _only_own_fields(self) -> "BaseClassifierQuestion": extra = self.model_extra or {} present = [f for f in self.foreign_fields if extra.get(f) is not None] if present: @@ -1208,10 +1201,28 @@ def _only_own_fields(self) -> "BaseJevQuestion": return self -class JevChoiceQuestion(BaseJevQuestion): +# --- Jev (TypeSafe) questions --- + + +class JevQuestionType(str, CaseInsensitiveEnum): + """Jev question type enumeration (matches TypeSafe's question types).""" + + CHOICE = "choice" + SCORE = "score" + NOUL = "noul" + + +class JevChoiceOption(BaseCfg): + """An option of a Jev choice question.""" + + name: str = Field(..., min_length=1) + description: Optional[str] = Field(None) + + +class JevChoiceQuestion(BaseClassifierQuestion): """Jev question selecting one of a set of options.""" - foreign_fields: ClassVar[tuple[str, ...]] = ("levels", "criteria") + foreign_fields: ClassVar[tuple[str, ...]] = ("levels", "criteria", "choices") type: Literal[JevQuestionType.CHOICE] = JevQuestionType.CHOICE options: List[JevChoiceOption] = Field(..., min_length=2, max_length=255) @@ -1224,10 +1235,10 @@ def _unique_option_names(self) -> "JevChoiceQuestion": return self -class JevScoreQuestion(BaseJevQuestion): +class JevScoreQuestion(BaseClassifierQuestion): """Jev question placing the input on an ordered scale of levels.""" - foreign_fields: ClassVar[tuple[str, ...]] = ("options", "criteria") + foreign_fields: ClassVar[tuple[str, ...]] = ("options", "criteria", "choices") type: Literal[JevQuestionType.SCORE] = JevQuestionType.SCORE levels: List[Annotated[str, Field(min_length=1)]] = Field( @@ -1244,10 +1255,10 @@ class JevNoulCriteria(BaseModel): false: Optional[str] = Field(None) -class JevNoulQuestion(BaseJevQuestion): +class JevNoulQuestion(BaseClassifierQuestion): """Jev yes/no question answered with a probability.""" - foreign_fields: ClassVar[tuple[str, ...]] = ("options", "levels") + foreign_fields: ClassVar[tuple[str, ...]] = ("options", "levels", "choices") type: Literal[JevQuestionType.NOUL] = JevQuestionType.NOUL criteria: Optional[JevNoulCriteria] = Field(None) @@ -1260,15 +1271,88 @@ class JevNoulQuestion(BaseJevQuestion): ] -class AgentInternalJevClassifierSettings(BaseCfg): - """Agent internal Jev classifier tool settings model. +# --- OpenAI Decisions questions (matches the Decisions API request) --- + + +class DecisionsQuestionType(str, CaseInsensitiveEnum): + """OpenAI Decisions question type enumeration.""" + + CHOICE = "choice" + SCORE = "score" + PREDICATE = "predicate" + + +class DecisionsChoice(BaseCfg): + """A choice of a Decisions choice question.""" + + value: str = Field(..., min_length=1) + description: Optional[str] = Field(None) + + +class DecisionsScoreLevel(BaseCfg): + """A level of a Decisions score question.""" + + label: str = Field(..., min_length=1) + description: Optional[str] = Field(None) + + +class DecisionsChoiceQuestion(BaseClassifierQuestion): + """Decisions question selecting one of a set of choices.""" + + foreign_fields: ClassVar[tuple[str, ...]] = ("levels", "options", "criteria") + + type: Literal[DecisionsQuestionType.CHOICE] = DecisionsQuestionType.CHOICE + choices: List[DecisionsChoice] = Field(..., min_length=2) + + @model_validator(mode="after") + def _unique_choice_values(self) -> "DecisionsChoiceQuestion": + values = [choice.value for choice in self.choices] + if len(values) != len(set(values)): + raise ValueError(f"Question '{self.name}' has duplicate choice values") + return self + + +class DecisionsScoreQuestion(BaseClassifierQuestion): + """Decisions question placing the input on levels ordered lowest to highest.""" + + foreign_fields: ClassVar[tuple[str, ...]] = ("choices", "options", "criteria") + + type: Literal[DecisionsQuestionType.SCORE] = DecisionsQuestionType.SCORE + levels: List[DecisionsScoreLevel] = Field(..., min_length=2) + + +class DecisionsPredicateQuestion(BaseClassifierQuestion): + """Decisions question estimating the probability that a condition is true.""" + + foreign_fields: ClassVar[tuple[str, ...]] = ( + "choices", + "levels", + "options", + "criteria", + ) + + type: Literal[DecisionsQuestionType.PREDICATE] = DecisionsQuestionType.PREDICATE + + +DecisionsQuestion = Annotated[ + Union[DecisionsChoiceQuestion, DecisionsScoreQuestion, DecisionsPredicateQuestion], + Field(discriminator="type"), + _case_insensitive_enum_validator("type", DecisionsQuestionType), +] + +ClassifierQuestion = Union[JevQuestion, DecisionsQuestion] + + +class AgentInternalClassifierSettings(BaseCfg): + """Agent internal classifier tool settings model. - Only the configuration that is never a call argument: the ``state`` and - ``questions`` the tool sends to Jev are declared in the resource's - ``inputSchema`` and supplied through its ``argumentProperties``. The model - is required: there is no default Jev model. + Only the configuration that is never a call argument: the input and the + ``questions`` the tool sends are declared in the resource's ``inputSchema`` + and supplied through its ``argumentProperties``, in the shapes of the + provider. The provider and the model are required: there is no default. """ + provider: ClassifierProvider = Field(...) model: str = Field(..., min_length=1) diff --git a/packages/uipath/tests/agent/models/test_jev_classifier_tool.py b/packages/uipath/tests/agent/models/test_classifier_tool.py similarity index 56% rename from packages/uipath/tests/agent/models/test_jev_classifier_tool.py rename to packages/uipath/tests/agent/models/test_classifier_tool.py index c5e0f0927..3263a69e2 100644 --- a/packages/uipath/tests/agent/models/test_jev_classifier_tool.py +++ b/packages/uipath/tests/agent/models/test_classifier_tool.py @@ -4,13 +4,18 @@ from pydantic import TypeAdapter, ValidationError from uipath.agent.models.agent import ( - AgentInternalJevClassifierSettings, - AgentInternalJevClassifierToolProperties, + AgentInternalClassifierSettings, + AgentInternalClassifierToolProperties, AgentInternalToolResourceConfig, AgentInternalToolType, AgentToolArgumentArgumentProperties, AgentToolObjectBuilderArgumentProperties, AgentToolStaticArgumentProperties, + ClassifierProvider, + DecisionsChoiceQuestion, + DecisionsPredicateQuestion, + DecisionsQuestion, + DecisionsScoreQuestion, JevChoiceQuestion, JevNoulCriteria, JevNoulQuestion, @@ -23,7 +28,7 @@ def _resource(**settings: Any) -> dict[str, Any]: return { "$resourceType": "tool", "id": "3f1c2a3e-0000-0000-0000-000000000001", - "name": "Jev Classifier", + "name": "Classifier", "description": "Classify support tickets", "type": "Internal", "referenceKey": None, @@ -39,7 +44,7 @@ def _resource(**settings: Any) -> dict[str, Any]: "outputSchema": {"type": "object", "properties": {}}, "settings": {}, "argumentProperties": {}, - "properties": {"toolType": "jev-classifier", "settings": settings}, + "properties": {"toolType": "classifier", "settings": settings}, } @@ -62,6 +67,34 @@ def _resource(**settings: Any) -> dict[str, Any]: _QUESTION: TypeAdapter[JevQuestion] = TypeAdapter(JevQuestion) +DECISIONS_CHOICE = { + "name": "department", + "type": "choice", + "instructions": "Which department should handle this complaint?", + "choices": [ + {"value": "billing", "description": "Payments, invoices, and refunds."}, + {"value": "technical", "description": None}, + ], +} +DECISIONS_SCORE = { + "name": "frustration", + "type": "score", + "instructions": "How frustrated the customer appears", + "levels": [ + {"label": "Calm", "description": "No frustration"}, + {"label": "Very angry"}, + ], +} +DECISIONS_PREDICATE = { + "name": "is_urgent", + "type": "predicate", + "instructions": "The customer needs an answer today.", +} + +_DECISIONS_QUESTION: TypeAdapter[DecisionsQuestion] = TypeAdapter(DecisionsQuestion) + +JEV = {"provider": "typesafe", "model": "jev-1.13.0"} + def _validate(data: dict[str, Any]) -> AgentInternalToolResourceConfig: return AgentInternalToolResourceConfig.model_validate(data) @@ -69,57 +102,64 @@ def _validate(data: dict[str, Any]) -> AgentInternalToolResourceConfig: def _settings( resource: AgentInternalToolResourceConfig, -) -> AgentInternalJevClassifierSettings: - assert isinstance(resource.properties, AgentInternalJevClassifierToolProperties) +) -> AgentInternalClassifierSettings: + assert isinstance(resource.properties, AgentInternalClassifierToolProperties) return resource.properties.settings -def test_settings_hold_only_the_model() -> None: - resource = _validate(_resource(model="jev-1.13.0")) +@pytest.mark.parametrize( + ("settings", "provider"), + [ + pytest.param(JEV, ClassifierProvider.TYPESAFE, id="typesafe"), + pytest.param( + {"provider": "openai", "model": "gpt-6-luna"}, + ClassifierProvider.OPENAI, + id="openai", + ), + ], +) +def test_settings_hold_the_provider_and_the_model( + settings: dict[str, Any], provider: ClassifierProvider +) -> None: + resource = _validate(_resource(**settings)) - assert isinstance(resource.properties, AgentInternalJevClassifierToolProperties) - assert resource.properties.tool_type == AgentInternalToolType.JEV_CLASSIFIER - assert _settings(resource).model == "jev-1.13.0" - assert _settings(resource).model_dump() == {"model": "jev-1.13.0"} + assert isinstance(resource.properties, AgentInternalClassifierToolProperties) + assert resource.properties.tool_type == AgentInternalToolType.CLASSIFIER + assert _settings(resource).provider is provider + assert _settings(resource).model == settings["model"] @pytest.mark.parametrize( "settings", - [pytest.param({}, id="missing"), pytest.param({"model": ""}, id="empty")], + [ + pytest.param({"provider": "typesafe"}, id="missing-model"), + pytest.param({"provider": "typesafe", "model": ""}, id="empty-model"), + pytest.param({"model": "jev-1.13.0"}, id="missing-provider"), + pytest.param({"provider": "anthropic", "model": "x"}, id="unknown-provider"), + ], ) -def test_model_is_required(settings: dict[str, Any]) -> None: - # No default Jev model: a tool without one fails to load. - with pytest.raises(ValidationError, match="model"): +def test_provider_and_model_are_required(settings: dict[str, Any]) -> None: + # No default provider or model: a tool without them fails to load. + with pytest.raises(ValidationError): _validate(_resource(**settings)) -def test_tool_type_is_case_insensitive() -> None: - data = _resource(model="jev-1.13.0") - data["properties"]["toolType"] = "Jev-Classifier" +def test_tool_type_and_provider_are_case_insensitive() -> None: + data = _resource(provider="OpenAI", model="gpt-6-luna") + data["properties"]["toolType"] = "Classifier" - assert isinstance( - _validate(data).properties, AgentInternalJevClassifierToolProperties - ) + resource = _validate(data) + assert isinstance(resource.properties, AgentInternalClassifierToolProperties) + assert _settings(resource).provider is ClassifierProvider.OPENAI -def test_legacy_state_and_questions_settings_are_ignored_extras() -> None: - # BaseCfg allows extra fields: settings written before questions and state - # moved to inputSchema still parse, but nothing reads or validates them. - resource = _validate( - _resource( - model="jev-1.13.0", - state={"type": "number"}, - questions=[{"type": "freeform"}], - ) - ) - settings = _settings(resource) - assert settings.model == "jev-1.13.0" - assert not hasattr(AgentInternalJevClassifierSettings, "questions") - assert settings.model_extra == { - "state": {"type": "number"}, - "questions": [{"type": "freeform"}], - } +def test_the_jev_classifier_tool_type_is_gone() -> None: + data = _resource(**JEV) + data["properties"]["toolType"] = "jev-classifier" + + with pytest.raises(ValidationError): + _validate(data) def test_parses_all_question_types() -> None: @@ -177,6 +217,8 @@ def test_noul_criteria_serialise_with_true_false_keys() -> None: @pytest.mark.parametrize( "question", [ + pytest.param({**CHOICE, "choices": [{"value": "a"}]}, id="choices-on-choice"), + pytest.param({**NOUL, "type": "predicate"}, id="decisions-type"), pytest.param({**NOUL, "name": "has space"}, id="invalid-name"), pytest.param({**NOUL, "name": "1st"}, id="name-starts-with-digit"), pytest.param({**NOUL, "name": "a" * 65}, id="name-too-long"), @@ -217,7 +259,7 @@ def test_rejects_invalid_question(question: dict[str, Any]) -> None: def test_parses_object_builder_argument_properties() -> None: - raw = _resource(model="jev-1.13.0") + raw = _resource(**JEV) raw["argumentProperties"] = { "$['questions']": {"variant": "ObjectBuilder"}, "$['questions']['department']": {"variant": "objectBuilder"}, @@ -248,3 +290,62 @@ def test_parses_object_builder_argument_properties() -> None: props["$['questions']['is_urgent']['instructions']"], AgentToolStaticArgumentProperties, ) + + +def test_parses_all_decisions_question_types() -> None: + choice = _DECISIONS_QUESTION.validate_python(DECISIONS_CHOICE) + score = _DECISIONS_QUESTION.validate_python(DECISIONS_SCORE) + predicate = _DECISIONS_QUESTION.validate_python( + {**DECISIONS_PREDICATE, "type": "PREDICATE"} + ) + + assert isinstance(choice, DecisionsChoiceQuestion) + assert [c.value for c in choice.choices] == ["billing", "technical"] + assert isinstance(score, DecisionsScoreQuestion) + assert [level.label for level in score.levels] == ["Calm", "Very angry"] + assert score.levels[1].description is None + assert isinstance(predicate, DecisionsPredicateQuestion) + + +@pytest.mark.parametrize( + "question", + [ + pytest.param({**DECISIONS_PREDICATE, "name": "has space"}, id="invalid-name"), + pytest.param({**DECISIONS_PREDICATE, "instructions": ""}, id="empty-instr"), + pytest.param( + {**DECISIONS_CHOICE, "choices": DECISIONS_CHOICE["choices"][:1]}, + id="one-choice", + ), + pytest.param( + {**DECISIONS_CHOICE, "choices": [{"value": "a"}, {"value": "a"}]}, + id="duplicate-choices", + ), + pytest.param( + {**DECISIONS_CHOICE, "choices": [{"value": "a"}, {"value": ""}]}, + id="empty-choice", + ), + pytest.param( + {**DECISIONS_SCORE, "levels": [{"label": "only"}]}, id="one-level" + ), + pytest.param({**DECISIONS_SCORE, "levels": ["Low", "High"]}, id="jev-levels"), + pytest.param( + {**DECISIONS_SCORE, "levels": [{"label": "a"}, {}]}, id="no-label" + ), + pytest.param({**CHOICE, "type": "choice"}, id="jev-options"), + pytest.param({**NOUL, "type": "noul"}, id="jev-type"), + pytest.param( + {**DECISIONS_PREDICATE, "criteria": {"true": "Yes"}}, id="jev-criteria" + ), + pytest.param( + {**DECISIONS_PREDICATE, "choices": DECISIONS_CHOICE["choices"]}, + id="choices-on-predicate", + ), + pytest.param( + {**DECISIONS_CHOICE, "levels": DECISIONS_SCORE["levels"]}, + id="levels-on-choice", + ), + ], +) +def test_rejects_invalid_decisions_question(question: dict[str, Any]) -> None: + with pytest.raises(ValidationError): + _DECISIONS_QUESTION.validate_python(question) diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index cede61da3..1a9dc47af 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2599,7 +2599,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.14.37" +version = "2.14.38" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.36" +version = "0.2.37" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" },