BerriAI/litellm · error · ValueError

Chunk is not a string: {chunk}

Error message

Chunk is not a string: {chunk}

What it means

OCIGenericStreamingChunkHandler.chunk_creator validates each SSE event yielded by the stream wrapper; it raises ValueError when a chunk is not a Python str. The handler only accepts raw 'data:'-prefixed SSE lines, so any other object type means the upstream contract (the _iter_sse_events/_aiter_sse_events pipeline) was violated or the wrapper was fed manually with bytes/dicts.

Source

Thrown at litellm/llms/oci/chat/transformation.py:748

            custom_llm_provider=custom_llm_provider,
            stream_options=stream_options,
            make_call=make_call,
            _response_headers=_response_headers,
        )
        # Tracks whether any prior Cohere chunk in this stream has emitted
        # tool calls. The Cohere handler uses this to decide whether the
        # terminal consolidation chunk's tool calls are duplicates (suppress)
        # or the only copy of the tool calls (pass through).
        self._cohere_tool_calls_emitted = False
        # Analogous flag for text content. Lets the Cohere handler distinguish
        # the common case (prior deltas already streamed the text, so the
        # terminal chunk's text is a duplicate to suppress) from the degenerate
        # single-event case (terminal chunk carries the only copy of the text).
        self._cohere_text_emitted = False

    def chunk_creator(self, chunk: Any) -> ModelResponseStream:
        if not isinstance(chunk, str):
            raise ValueError(f"Chunk is not a string: {chunk}")
        if not chunk.startswith("data:"):
            raise ValueError(f"Chunk does not start with 'data:': {chunk}")
        try:
            dict_chunk: Final = json.loads(chunk[5:])
        except json.JSONDecodeError as e:
            raise OCIError(
                status_code=500,
                message=f"Chunk cannot be parsed as JSON: {e}",
            )

        if dict_chunk.get("apiFormat") == "COHERE":
            result: Final = handle_cohere_stream_chunk(
                dict_chunk,
                prior_tool_calls_emitted=self._cohere_tool_calls_emitted,
                prior_text_emitted=self._cohere_text_emitted,
            )
            if not self._cohere_tool_calls_emitted:
                for choice in result.choices:

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. If you consume OCIStreamWrapper directly, iterate it rather than calling chunk_creator yourself with parsed objects.
  2. In tests, feed 'data:{...}' string lines, not dicts (use the model's transform_response instead for parsed chunks).
  3. If a proxy sits between you and OCI, bypass it to confirm the SSE framing is intact.
  4. Upgrade litellm — changes to the SSE iteration contract are fixed in newer patches.

Example fix

# before (test feeding dicts)
handler.chunk_creator({"apiFormat": "COHERE", ...})  # ValueError

# after (feed raw SSE strings)
handler.chunk_creator('data:{"apiFormat": "COHERE", ...}')
Defensive patterns

Strategy: type-guard

Validate before calling

def is_sse_data_line(chunk) -> bool:
    return isinstance(chunk, str) and chunk.startswith("data:")

Type guard

from typing import Any

def is_sse_chunk(chunk: Any) -> bool:  # type guard for manual iteration
    return isinstance(chunk, str)

Try / catch

try:
    parsed = handler.chunk_creator(chunk)
except ValueError:
    logger.error("unexpected stream chunk %r", chunk)
    raise

Prevention

When it happens

Trigger: OCIStreamWrapper is iterated manually (custom embedding of the handler), or an API/proxy in front of OCI re-chunks the SSE body so the text iterator yields non-string objects; also triggered by passing a mock/fake stream of dicts in tests instead of 'data:{json}' strings.

Common situations: Almost always a programming error on the caller's side: unit tests stubbing the stream with parsed dict chunks, wrapping the OCIStreamWrapper in another generator that yields JSON objects, or a transport-layer change (httpx version behavior with iter_text) producing bytes. Rare in normal litellm.completion usage because the pipeline is internal.

Related errors


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