diff --git a/litellm/types/decisions.py b/litellm/types/decisions.py index 7dacc1b3678..563329bb6e3 100644 --- a/litellm/types/decisions.py +++ b/litellm/types/decisions.py @@ -8,6 +8,7 @@ from litellm.types.llms.base import LiteLLMPydanticObjectBase DecisionsJSON: TypeAlias = str | Mapping[str, object] | Sequence[object] NoulCriteria: TypeAlias = Mapping[Literal["true", "false"], DecisionsJSON | None] +MAX_DECISION_QUESTIONS: Final = 128 class NoulQuestion(LiteLLMPydanticObjectBase): @@ -47,7 +48,7 @@ DecisionQuestion: TypeAlias = Annotated[ DecisionQuestionMap: TypeAlias = Annotated[ Mapping[Annotated[str, Field(min_length=1)], DecisionQuestion], - Field(min_length=1, max_length=128), + Field(min_length=1, max_length=MAX_DECISION_QUESTIONS), ] @@ -203,7 +204,7 @@ OpenAIDecisionQuestion: TypeAlias = Annotated[ class OpenAIDecisionRequestBody(LiteLLMPydanticObjectBase): input: str | Sequence[OpenAIDecisionInputMessage] - questions: Annotated[Sequence[OpenAIDecisionQuestion], Field(min_length=1, max_length=200)] + questions: Annotated[Sequence[OpenAIDecisionQuestion], Field(min_length=1, max_length=MAX_DECISION_QUESTIONS)] safety_identifier: str | None = None model_config = ConfigDict(extra="allow", frozen=True) diff --git a/tests/unit/decisions/test_openai_transformation.py b/tests/unit/decisions/test_openai_transformation.py new file mode 100644 index 00000000000..a3cc1583d40 --- /dev/null +++ b/tests/unit/decisions/test_openai_transformation.py @@ -0,0 +1,32 @@ +from collections.abc import Mapping +from typing import Final + +import pytest +from pydantic import TypeAdapter, ValidationError + +from litellm.decisions.openai_transformation import to_systemone_request +from litellm.types.decisions import MAX_DECISION_QUESTIONS, DecisionsRequestBody, OpenAIDecisionRequestBody + +_OPENAI_BODY: Final[TypeAdapter[OpenAIDecisionRequestBody]] = TypeAdapter(OpenAIDecisionRequestBody) +_SYSTEMONE_BODY: Final[TypeAdapter[DecisionsRequestBody]] = TypeAdapter(DecisionsRequestBody) + + +def _openai_request(question_count: int) -> Mapping[str, object]: + return { + "model": "decider", + "input": "The package arrived with a broken screen.", + "questions": [{"type": "predicate", "instructions": f"Question {index}?"} for index in range(question_count)], + } + + +def test_the_largest_openai_request_accepted_translates_to_a_valid_systemone_request() -> None: + raw: Final = _openai_request(MAX_DECISION_QUESTIONS) + + translated: Final = _SYSTEMONE_BODY.validate_python(to_systemone_request(raw, _OPENAI_BODY.validate_python(raw))) + + assert len(translated.questions) == MAX_DECISION_QUESTIONS + + +def test_an_openai_request_with_more_questions_than_systemone_takes_is_rejected_before_translation() -> None: + with pytest.raises(ValidationError, match="questions"): + _OPENAI_BODY.validate_python(_openai_request(MAX_DECISION_QUESTIONS + 1))