From 627b667688e2c368433ef83066ca54235a201fba Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:05:33 +0000 Subject: [PATCH] fix(decisions): log non-UTF-8 bytes as base64 so the request reaches validation and gets a 400 (#47564) Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Backport of #47564 to stable/1.105.x. Cherry-picked from ce63f48ed2ee3a18b7860b72f292e90ebeb1a986 (main). --- litellm/decisions/call.py | 2 +- tests/unit/decisions/test_call.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/litellm/decisions/call.py b/litellm/decisions/call.py index 1d75d6942f0..4299736bcff 100644 --- a/litellm/decisions/call.py +++ b/litellm/decisions/call.py @@ -160,7 +160,7 @@ def _jsonable_fallback(value: object) -> object: def _logged_request_message(caller_request: object) -> ChatCompletionUserMessage: caller_fields: Final = _LOGGED_FIELDS_ADAPTER.validate_python( - to_jsonable_python(caller_request, fallback=_jsonable_fallback) + to_jsonable_python(caller_request, fallback=_jsonable_fallback, bytes_mode="base64") ) sent_fields: Final = json.dumps( dict((name, value) for name, value in caller_fields.items() if value not in (None, [], {})) diff --git a/tests/unit/decisions/test_call.py b/tests/unit/decisions/test_call.py index 0aec79ceb09..138d9dba906 100644 --- a/tests/unit/decisions/test_call.py +++ b/tests/unit/decisions/test_call.py @@ -1,3 +1,4 @@ +import base64 import json from collections.abc import Mapping from datetime import datetime, timezone @@ -100,3 +101,20 @@ def test_decisions_request_is_logged_even_when_its_questions_fail_validation(): logged: Final = _logged_request(logging_obj) assert logged["questions"][0]["instructions"] == QUESTION_TEXT + + +def test_non_utf8_bytes_in_the_request_are_logged_and_rejected_with_a_400(): + logging_obj: Final = _logging_obj("adecisions") + raw_input: Final = b"\xff\xfe not utf-8" + + with pytest.raises(litellm.BadRequestError): + _prepare_decisions_call( + **CONNECTION, + decision_input=raw_input, + questions=[{"type": "predicate", "instructions": QUESTION_TEXT}], + kwargs={"litellm_logging_obj": logging_obj}, + ) + + logged: Final = _logged_request(logging_obj) + assert base64.urlsafe_b64decode(logged["input"]) == raw_input + assert logged["questions"][0]["instructions"] == QUESTION_TEXT