{"record":{"id":"8cb9c8ddd713d685","repo":"BerriAI/litellm","slug":"service-name-returned-non-dict-json-type-resul","errorCode":null,"errorMessage":"{service_name} returned non-dict JSON ({type(result).__name__}); expected OpenAI chat completion shape or empty object.","messagePattern":"(.+?) returned non-dict JSON \\((.+?)\\); expected OpenAI chat completion shape or empty object\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/rubrik.py","lineNumber":1032,"sourceCode":"    # -- Webhook services ------------------------------------------------------\n\n    async def _post_json(self, endpoint: str, payload: Mapping[str, object], service_name: str) -> Mapping[str, Any]:\n        \"\"\"POST ``payload`` to a Rubrik webhook and return its dict response.\n\n        Raises:\n            Exception: If the service is unavailable or returns an error.\n            TypeError: If the response JSON is not a dict.\n        \"\"\"\n        verbose_logger.debug(\"Sending request to %s: %s\", service_name, endpoint)\n        http_response: Final = await self.moderation_client.post(\n            endpoint,\n            json=payload,\n            headers=self._headers,\n        )\n        http_response.raise_for_status()\n        result: Final[object] = http_response.json()\n        if not isinstance(result, dict):\n            raise TypeError(\n                f\"{service_name} returned non-dict JSON \"\n                f\"({type(result).__name__}); expected OpenAI chat completion \"\n                \"shape or empty object.\"\n            )\n        return result\n\n    async def _post_to_response_moderation_endpoint(\n        self,\n        response_data: Mapping[str, object],\n        request_data: Mapping[str, object],\n    ) -> Mapping[str, Any]:\n        \"\"\"Post the ``{request, response}`` envelope to the after_completion\n        webhook and return its (possibly rewritten) response.\n\n        Args:\n            response_data: The OpenAI-formatted response payload to send.\n            request_data: Original LLM request data to include alongside\n                the response for additional context. Empty dict if unavailable.","sourceCodeStart":1014,"sourceCodeEnd":1050,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/rubrik.py#L1014-L1050","documentation":"Rubrik's shared _post helper (used by prompt and response moderation) does raise_for_status() then requires http_response.json() to be a dict — it expects an OpenAI-chat-completion-like decision object or {}. If the service returns a JSON list, string, or number, it raises TypeError naming the service and the offending JSON type.","triggerScenarios":"The Rubrik moderation endpoint (or anything fronting it — a gateway, mock, or proxy) returns a JSON array (e.g. a list of findings) instead of an object; a misrouted URL that returns a different JSON API shape; a mock server returning '[]' or '\"allowed\"'.","commonSituations":"Version mismatch between the Rubrik Moderation API and this client; pointing RUBRIK_WEBHOOK_URL at a custom middleware that wraps responses in a list; testing against stub servers that return simplistic JSON.","solutions":["Reproduce the POST (endpoint and payload are logged in the debug line above the raise) and inspect the raw JSON body","If a middleware/gateway transforms responses, fix it to pass through the original object shape","Upgrade litellm for fixes to Rubrik response parsing; if the endpoint legitimately returns non-dict JSON, report it — the client cannot evaluate it"],"exampleFix":"# mock server returning a list — triggers TypeError\n# before\nreturn Response(json.dumps([{\"decision\": \"allow\"}]))  # -> TypeError: non-dict JSON (list)\n\n# after\nreturn Response(json.dumps({\"choices\": [{\"message\": {\"content\": \"allow\"}}]}))","handlingStrategy":"type-guard","validationCode":"result = http_response.json()\nif not isinstance(result, dict):\n    raise TypeError(f\"unexpected moderation payload: {type(result).__name__}\")","typeGuard":"from typing import Any, TypeGuard\n\ndef is_moderation_dict(v: Any) -> TypeGuard[dict]:\n    return isinstance(v, dict) or v == {}","tryCatchPattern":"try:\n    result = await rubrik._post_to_moderation_endpoint(endpoint, payload)\nexcept TypeError as e:\n    if \"non-dict JSON\" in str(e):\n        logger.error(\"moderation endpoint returned unexpected JSON shape; failing open\")\n        result = {}\n    else:\n        raise","preventionTips":["Contract-test any middleware in front of the Rubrik endpoint to assert the response is a JSON object","Monitor responses in staging with a debug tap before enabling fail-closed moderation","Version-pin the Rubrik Moderation API this client is validated against"],"tags":["rubrik","json","response-shape","moderation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}