From 855f0bb7651abb5d01bddb6aba6708c9148d5117 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:40:21 -0700 Subject: [PATCH] fix(decisions): return guardrail_information on OpenAI-format decisions when requested --- .../proxy/decisions_endpoints/endpoints.py | 11 ++++++-- litellm/types/decisions.py | 2 +- .../decisions_endpoints/test_endpoints.py | 27 ++++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/decisions_endpoints/endpoints.py b/litellm/proxy/decisions_endpoints/endpoints.py index 6947f109648..2ae33e4c3cc 100644 --- a/litellm/proxy/decisions_endpoints/endpoints.py +++ b/litellm/proxy/decisions_endpoints/endpoints.py @@ -8,7 +8,11 @@ from pydantic import TypeAdapter, ValidationError from litellm.decisions.openai_transformation import to_openai_response, to_systemone_request from litellm.exceptions import BadRequestError from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth -from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing +from litellm.proxy.common_request_processing import ( + ProxyBaseLLMRequestProcessing, + attach_guardrail_information, + include_guardrail_response_requested, +) from litellm.types.decisions import DecisionsRequestBody, DecisionsResponse, OpenAIDecisionRequestBody router: Final = APIRouter() @@ -104,11 +108,14 @@ async def _process_systemone( ) if openai_body is None or isinstance(result, Response): return result - return to_openai_response( + openai_response: Final = to_openai_response( _DECISIONS_RESPONSE_ADAPTER.validate_python(result), openai_body.questions, str(data.get("model", "")), ) + if include_guardrail_response_requested(processor.data): + return attach_guardrail_information(response=openai_response, request_data=processor.data) + return openai_response except ValidationError as error: raise await _invalid_request(raw_data=data, error=error, user_api_key_dict=user_api_key_dict) except Exception as error: diff --git a/litellm/types/decisions.py b/litellm/types/decisions.py index c61593915d1..b543b3b32df 100644 --- a/litellm/types/decisions.py +++ b/litellm/types/decisions.py @@ -294,4 +294,4 @@ class OpenAIDecisionResponse(LiteLLMPydanticObjectBase): answers: tuple[OpenAIDecisionAnswer, ...] usage: OpenAIDecisionUsage - model_config = ConfigDict(frozen=True) + model_config = ConfigDict(extra="allow", frozen=True) diff --git a/tests/unit/proxy/decisions_endpoints/test_endpoints.py b/tests/unit/proxy/decisions_endpoints/test_endpoints.py index f61507e1d1d..b00f2be092c 100644 --- a/tests/unit/proxy/decisions_endpoints/test_endpoints.py +++ b/tests/unit/proxy/decisions_endpoints/test_endpoints.py @@ -385,6 +385,29 @@ def test_openai_format_decisions_translate_through_systemone( assert float(response.headers["x-litellm-response-cost"]) > 0 +@pytest.mark.parametrize( + ("endpoint", "request_body", "upstream_response"), + ( + ("/v1/systemone", _REQUEST, _RESPONSE), + ("/v1/decisions", _OPENAI_FORMAT_REQUEST, _SYSTEMONE_ANSWERS_FOR_OPENAI_REQUEST), + ), + ids=("systemone", "openai_format"), +) +def test_decisions_return_guardrail_information_when_requested( + client: TestClient, + respx_mock: respx.MockRouter, + endpoint: str, + request_body: Mapping[str, object], + upstream_response: Mapping[str, object], +) -> None: + respx_mock.post("https://api.perplexity.ai/v1/decisions").respond(json=upstream_response) + + response: Final = client.post(endpoint, json={**request_body, "include_guardrail_response": True}) + + assert response.status_code == 200, response.text + assert response.json()["guardrail_information"] == [] + + @pytest.mark.parametrize( "request_body", ( @@ -402,7 +425,9 @@ def test_openai_format_decisions_translate_through_systemone( }, { "model": "decider", - "input": [{"role": "user", "content": [{"type": "input_image", "image_url": "data:image/png;base64,AA=="}]}], + "input": [ + {"role": "user", "content": [{"type": "input_image", "image_url": "data:image/png;base64,AA=="}]} + ], "questions": [{"type": "predicate", "instructions": "Is this a defect?"}], }, ),