{"record":{"id":"574869ff91ec8e6a","repo":"BerriAI/litellm","slug":"chunk-is-not-a-string-chunk-574869","errorCode":null,"errorMessage":"Chunk is not a string: {chunk}","messagePattern":"Chunk is not a string: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/oci/chat/transformation.py","lineNumber":748,"sourceCode":"            custom_llm_provider=custom_llm_provider,\n            stream_options=stream_options,\n            make_call=make_call,\n            _response_headers=_response_headers,\n        )\n        # Tracks whether any prior Cohere chunk in this stream has emitted\n        # tool calls. The Cohere handler uses this to decide whether the\n        # terminal consolidation chunk's tool calls are duplicates (suppress)\n        # or the only copy of the tool calls (pass through).\n        self._cohere_tool_calls_emitted = False\n        # Analogous flag for text content. Lets the Cohere handler distinguish\n        # the common case (prior deltas already streamed the text, so the\n        # terminal chunk's text is a duplicate to suppress) from the degenerate\n        # single-event case (terminal chunk carries the only copy of the text).\n        self._cohere_text_emitted = False\n\n    def chunk_creator(self, chunk: Any) -> ModelResponseStream:\n        if not isinstance(chunk, str):\n            raise ValueError(f\"Chunk is not a string: {chunk}\")\n        if not chunk.startswith(\"data:\"):\n            raise ValueError(f\"Chunk does not start with 'data:': {chunk}\")\n        try:\n            dict_chunk: Final = json.loads(chunk[5:])\n        except json.JSONDecodeError as e:\n            raise OCIError(\n                status_code=500,\n                message=f\"Chunk cannot be parsed as JSON: {e}\",\n            )\n\n        if dict_chunk.get(\"apiFormat\") == \"COHERE\":\n            result: Final = handle_cohere_stream_chunk(\n                dict_chunk,\n                prior_tool_calls_emitted=self._cohere_tool_calls_emitted,\n                prior_text_emitted=self._cohere_text_emitted,\n            )\n            if not self._cohere_tool_calls_emitted:\n                for choice in result.choices:","sourceCodeStart":730,"sourceCodeEnd":766,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/oci/chat/transformation.py#L730-L766","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If you consume OCIStreamWrapper directly, iterate it rather than calling chunk_creator yourself with parsed objects.","In tests, feed 'data:{...}' string lines, not dicts (use the model's transform_response instead for parsed chunks).","If a proxy sits between you and OCI, bypass it to confirm the SSE framing is intact.","Upgrade litellm — changes to the SSE iteration contract are fixed in newer patches."],"exampleFix":"# before (test feeding dicts)\nhandler.chunk_creator({\"apiFormat\": \"COHERE\", ...})  # ValueError\n\n# after (feed raw SSE strings)\nhandler.chunk_creator('data:{\"apiFormat\": \"COHERE\", ...}')","handlingStrategy":"type-guard","validationCode":"def is_sse_data_line(chunk) -> bool:\n    return isinstance(chunk, str) and chunk.startswith(\"data:\")","typeGuard":"from typing import Any\n\ndef is_sse_chunk(chunk: Any) -> bool:  # type guard for manual iteration\n    return isinstance(chunk, str)","tryCatchPattern":"try:\n    parsed = handler.chunk_creator(chunk)\nexcept ValueError:\n    logger.error(\"unexpected stream chunk %r\", chunk)\n    raise","preventionTips":["Never call chunk_creator directly; consume the litellm stream iterator.","In tests, always feed 'data:'-prefixed string lines.","Keep intermediaries from re-chunking the SSE body."],"tags":["oci","streaming","sse","type-validation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}