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
- Read the embedded traceback: the last frame names the exact field access that failed - fix the payload at that key
- Diff the received_args dump against the expected schema (choices[].message with tool_calls entries carrying id/type/function)
- If the input is a streaming chunk (delta-shaped), use the streaming converters instead
- 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
- The embedded traceback's last frame names the failing key - always read it before guessing
- Never log the full received_args payload uncut: it contains the whole response (prompts included)
- Pin litellm versions and test provider upgrades against a response-fixture corpus
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
- Missing required fields in the result=
- No results found in the response=
- 400
- 404
- Access denied to MCP server
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)