BerriAI/litellm · error · TypeError
{service_name} returned non-dict JSON ({type(result).__name_
Error message
{service_name} returned non-dict JSON ({type(result).__name__}); expected OpenAI chat completion shape or empty object. What it means
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.
Source
Thrown at litellm/integrations/rubrik.py:1032
# -- Webhook services ------------------------------------------------------
async def _post_json(self, endpoint: str, payload: Mapping[str, object], service_name: str) -> Mapping[str, Any]:
"""POST ``payload`` to a Rubrik webhook and return its dict response.
Raises:
Exception: If the service is unavailable or returns an error.
TypeError: If the response JSON is not a dict.
"""
verbose_logger.debug("Sending request to %s: %s", service_name, endpoint)
http_response: Final = await self.moderation_client.post(
endpoint,
json=payload,
headers=self._headers,
)
http_response.raise_for_status()
result: Final[object] = http_response.json()
if not isinstance(result, dict):
raise TypeError(
f"{service_name} returned non-dict JSON "
f"({type(result).__name__}); expected OpenAI chat completion "
"shape or empty object."
)
return result
async def _post_to_response_moderation_endpoint(
self,
response_data: Mapping[str, object],
request_data: Mapping[str, object],
) -> Mapping[str, Any]:
"""Post the ``{request, response}`` envelope to the after_completion
webhook and return its (possibly rewritten) response.
Args:
response_data: The OpenAI-formatted response payload to send.
request_data: Original LLM request data to include alongside
the response for additional context. Empty dict if unavailable.View on GitHub (pinned to 6c2dcb801b)
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
Example fix
# mock server returning a list — triggers TypeError
# before
return Response(json.dumps([{"decision": "allow"}])) # -> TypeError: non-dict JSON (list)
# after
return Response(json.dumps({"choices": [{"message": {"content": "allow"}}]})) Defensive patterns
Strategy: type-guard
Validate before calling
result = http_response.json()
if not isinstance(result, dict):
raise TypeError(f"unexpected moderation payload: {type(result).__name__}") Type guard
from typing import Any, TypeGuard
def is_moderation_dict(v: Any) -> TypeGuard[dict]:
return isinstance(v, dict) or v == {} Try / catch
try:
result = await rubrik._post_to_moderation_endpoint(endpoint, payload)
except TypeError as e:
if "non-dict JSON" in str(e):
logger.error("moderation endpoint returned unexpected JSON shape; failing open")
result = {}
else:
raise Prevention
- 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
When it happens
Trigger: 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"'.
Common situations: 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.
Related errors
- Mavvrik FOCUS destination: response missing 'url' field: {re
- Failed to parse prompt response for '{prompt_id}': {e}
- Rubrik webhook URL not configured. Set RUBRIK_WEBHOOK_URL or
- Cannot normalize tool_call of type {type(tc).__name__}: {tc!
- ContentPolicyViolationError: {exception_provider} - {message
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/8cb9c8ddd713d685.
Report an issue: GitHub.