BerriAI/litellm · error · ValueError

Chat provider: Invalid chunk type {type(parsed_chunk)}

Error message

Chat provider: Invalid chunk type {type(parsed_chunk)}

What it means

The chunk transformer raises when parsed_chunk is not a dict after BaseModel normalization — i.e. not a pydantic model, not a dict (e.g. a str, bytes, or arbitrary object). Events must arrive as deserialized objects; raw text frames or unparsed bytes are rejected.

Source

Thrown at litellm/completion_extras/litellm_responses_transformation/transformation.py:1226

        Raises:
            ValueError: If chunk is invalid or missing required fields
        """
        from litellm.types.llms.openai import ChatCompletionToolCallFunctionChunk
        from litellm.types.utils import (
            ChatCompletionToolCallChunk,
            Delta,
            ModelResponseStream,
            StreamingChoices,
        )

        if not parsed_chunk:
            raise ValueError("Chat provider: Empty parsed_chunk")

        if isinstance(parsed_chunk, BaseModel):
            parsed_chunk = parsed_chunk.model_dump()
        if not isinstance(parsed_chunk, dict):
            raise ValueError(f"Chat provider: Invalid chunk type {type(parsed_chunk)}")

        # Handle different event types from responses API
        event_type = parsed_chunk.get("type")
        if isinstance(event_type, ResponsesAPIStreamEvents):
            event_type = event_type.value

        if parsed_chunk.get("object") == "chat.completion.chunk" or (
            event_type is None and isinstance(parsed_chunk.get("choices"), list) and parsed_chunk.get("choices")
        ):
            return ModelResponseStream(**parsed_chunk)

        verbose_logger.debug("Chat provider: Processing event type: %s", event_type)

        if event_type == "response.created":
            # Initial response creation event
            verbose_logger.debug("Chat provider: response.created -> %s", parsed_chunk)
            return ModelResponseStream(
                choices=[

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. json.loads each SSE data payload before handing it to the transformer
  2. Skip non-data frames (event:, id:, comments) in your SSE reader
  3. Let litellm's built-in streaming handle deserialization when possible

Example fix

# before
async for line in response.aiter_lines():
    if line.startswith("data: "):
        out = transformer.chunk_parser(line[6:])  # raw str

# after
import json
async for line in response.aiter_lines():
    if line.startswith("data: "):
        payload = line[6:]
        if payload == "[DONE]":
            break
        out = transformer.chunk_parser(json.loads(payload))
Defensive patterns

Strategy: type-guard

Validate before calling

import json

def is_parsed_event(chunk) -> bool:
    if isinstance(chunk, str):
        try:
            json.loads(chunk)
            return False  # still a string: parse it first
        except json.JSONDecodeError:
            return False
    return isinstance(chunk, dict)

Type guard

def is_event_dict(v) -> bool:
    return isinstance(v, dict)

Try / catch

for raw in sse_lines:
    payload = json.loads(raw[6:])
    if not isinstance(payload, dict):
        continue
    out = transformer.chunk_parser(payload, ...)

Prevention

When it happens

Trigger: Passing raw SSE lines (strings) or bytes from httpx directly into chunk_parser; a custom deserializer returning json.dumps(...) output instead of json.loads(...) result.

Common situations: Hand-rolled SSE clients that forget json.loads on the data: payload; mixing sync/async iterators and accidentally yielding the iterator object; double-encoded JSON strings.

Related errors


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