From 7ef132947a8689000394103c47490161aa9d8f1b Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:20:31 -0700 Subject: [PATCH] fix(decisions): cap OpenAI-format questions at the System One limit /v1/decisions accepted up to 200 questions, but the System One request it translates to takes at most 128, so 129 to 200 questions failed with an internal validation error. Both now share MAX_DECISION_QUESTIONS. --- litellm/types/decisions.py | 5 +-- .../decisions/test_openai_transformation.py | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/unit/decisions/test_openai_transformation.py 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))