BerriAI/litellm · error · Exception

Invalid response object

Error message

Invalid response object {traceback.format_exc()}

received_args={received_args}

What it means

convert_to_model_response_object wraps its conversion loop in a broad except: APIErrors are re-raised untouched, but any other exception (KeyError on a missing choice field, TypeError, pydantic ValidationError) is re-raised as Exception('Invalid response object <full traceback> received_args={...}'). The original stack trace is embedded in the message, and received_args dumps every argument including the entire response object - so the real cause is the last exception frame in that embedded traceback, and the message can be enormous (and may contain prompt content).

Solutions

  1. Read the embedded traceback: the last frame names the exact field access that failed - fix the payload at that key
  2. Diff the received_args dump against the expected schema (choices[].message with tool_calls entries carrying id/type/function)
  3. If the input is a streaming chunk (delta-shaped), use the streaming converters instead
  4. If a standard provider response triggers it, capture a sanitized payload and report with litellm/provider versions

Example fix

# before
raw = {"choices": [{"finish_reason": "stop"}]}  # missing 'message'
resp = convert_to_model_response_object(response_object=raw)  # Exception: Invalid response object ...

# after
raw = {"choices": [{"message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}]}
resp = convert_to_model_response_object(response_object=raw)
Defensive patterns

Strategy: try-catch

Validate before calling

required = all(
    isinstance(choice, dict) and isinstance(choice.get("message"), dict)
    for choice in raw.get("choices", [])
)
if not required:
    raise ValueError(f"response missing choices[].message: {list(raw)}")

Type guard

def is_wellformed_chat_dict(v: object) -> bool:
    if not isinstance(v, dict) or not isinstance(v.get("choices"), list):
        return False
    return all(
        isinstance(c, dict) and isinstance(c.get("message"), dict) for c in v["choices"]
    )

Try / catch

from litellm.exceptions import APIError

try:
    resp = convert_to_model_response_object(response_object=raw)
except APIError:
    raise
except Exception as e:
    if str(e).startswith("Invalid response object"):
        # embedded traceback names the failing field; log a truncated, sanitized slice
        log.error("conversion failed: %s", str(e)[:2000])
    raise

Prevention

When it happens

Trigger: Feeding a response dict whose shape drifts from the chat schema - choice['message'] missing, tool_calls entries lacking index/id/function, a delta-shaped (streaming) object where a message-shaped dict is expected - so a field access throws mid-conversion.

Common situations: New/beta provider formats and passthrough endpoints; middleware mutating response dicts; handing ModelResponseStream chunks to the non-streaming converter; mismatched litellm versions between caller and transformer.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/40326f1a06acc12a. Report an issue: GitHub.

Appendix: source

Thrown at litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py:877

                model_response_object.results = response_object["results"]

            return model_response_object
    except Exception as e:
        from litellm.exceptions import APIError

        if isinstance(e, APIError):
            raise

        received_args: Final = dict(
            response_object=response_object,
            model_response_object=model_response_object,
            response_type=response_type,
            stream=stream,
            start_time=start_time,
            end_time=end_time,
            convert_tool_call_to_json_mode=convert_tool_call_to_json_mode,
        )
        raise Exception(f"Invalid response object {traceback.format_exc()}\n\nreceived_args={received_args}")

View on GitHub (pinned to 77b7c6c40c)