BerriAI/litellm · error · ValueError

Invalid JSON message: {message_str}

Error message

Invalid JSON message: {message_str}

What it means

Raised in transform_realtime_input when a client→Gemini message cannot be parsed with json.loads. Before anything is forwarded over the WebSocket, each inbound message must be a JSON object whose 'type' field routes it (session.update, response.create, conversation.item.create, ...). Non-JSON input (plain text, binary audio frames handed to the wrong API) fails fast with the offending string echoed.

Source

Thrown at litellm/llms/gemini/realtime/transformation.py:535

        }

        return [json.dumps(client_content_message)]

    def transform_realtime_request(
        self,
        message: str,
        model: str,
        session_configuration_request: str | None = None,
    ) -> list[str]:
        realtime_input_dict: BidiGenerateContentRealtimeInput = {}
        try:
            json_message: Final = json.loads(message)
        except json.JSONDecodeError:
            if isinstance(message, bytes):
                message_str = message.decode("utf-8", errors="replace")
            else:
                message_str = str(message)
            raise ValueError(f"Invalid JSON message: {message_str}")

        messages: Final[list[str]] = []
        msg_type: Final = json_message.get("type")

        if msg_type == "session.update":
            return self._handle_session_update(json_message, model, session_configuration_request)

        if msg_type == "response.create":
            return []  # Gemini responds automatically; nothing to forward

        if msg_type == "conversation.item.create":
            return self._handle_conversation_item(json_message)

        if msg_type == "input_audio_buffer.append":
            realtime_input_dict["audio"] = HttpxBlobType(
                mimeType=self.get_audio_mime_type(), data=json_message["audio"]
            )

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Ensure every client message is a serialized JSON object with a 'type' field before calling transform_realtime_input.
  2. Send raw audio through the audio-input path (base64 realtimeInput), not the JSON transform path.
  3. Validate inbound frames with json.loads in your client before forwarding.

Example fix

# before
msgs = config.transform_realtime_input(message='hello world', model=model)

# after
import json
frame = json.dumps({'type': 'conversation.item.create', 'item': {'type': 'message', 'role': 'user', 'content': [{'type': 'input_text', 'text': 'hello world'}]}})
msgs = config.transform_realtime_input(message=frame, model=model)
Defensive patterns

Strategy: validation

Validate before calling

import json

def is_valid_client_frame(message) -> bool:
    try:
        parsed = json.loads(message)
    except (json.JSONDecodeError, TypeError, UnicodeDecodeError):
        return False
    return isinstance(parsed, dict) and "type" in parsed

Type guard

import json

def is_json_object_with_type(message: str | bytes) -> bool:
    try:
        parsed = json.loads(message)
    except (json.JSONDecodeError, TypeError):
        return False
    return isinstance(parsed, dict) and isinstance(parsed.get("type"), str)

Try / catch

try:
    msgs = config.transform_realtime_input(message=message, model=model)
except ValueError as e:
    if "Invalid JSON message" in str(e):
        logger.warning("Rejecting non-JSON client frame")
        return []
    raise

Prevention

When it happens

Trigger: Calling the realtime input transform with a plain-text user message, a binary audio buffer that should have gone to the dedicated audio path, or malformed/truncated JSON from a custom client.

Common situations: Porting an OpenAI realtime client that sends audio as base64 strings in the wrong envelope; custom WebSocket frontends emitting debug strings; concatenation bugs producing invalid JSON.

Understand the failure class

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/19f3b870b3e5e07f. Report an issue: GitHub.