BerriAI/litellm · error · ValueError
chunk is not a string: {chunk}
Error message
chunk is not a string: {chunk} What it means
Codestral text-completion branch (custom_llm_provider 'text-completion-codestral') asserts the chunk is a str before handing it to CodestralTextCompletionConfig._chunk_parser. If the iterator yields bytes or a parsed object, it raises this ValueError.
Source
Thrown at litellm/litellm_core_utils/streaming_handler.py:1371
elif self.custom_llm_provider == "text-completion-openai":
response_obj = self.handle_openai_text_completion_chunk(chunk)
completion_obj["content"] = response_obj["text"]
print_verbose(f"completion obj content: {completion_obj['content']}")
if response_obj["is_finished"]:
self.received_finish_reason = response_obj["finish_reason"]
if response_obj["usage"] is not None:
setattr(
model_response,
"usage",
litellm.Usage(
prompt_tokens=response_obj["usage"].prompt_tokens,
completion_tokens=response_obj["usage"].completion_tokens,
total_tokens=response_obj["usage"].total_tokens,
),
)
elif self.custom_llm_provider == "text-completion-codestral":
if not isinstance(chunk, str):
raise ValueError(f"chunk is not a string: {chunk}")
response_obj = cast(
dict[str, Any],
litellm.CodestralTextCompletionConfig()._chunk_parser(chunk),
)
completion_obj["content"] = response_obj["text"]
print_verbose(f"completion obj content: {completion_obj['content']}")
if response_obj["is_finished"]:
self.received_finish_reason = response_obj["finish_reason"]
if "usage" in response_obj is not None:
_codestral_usage: Final[Usage] = response_obj["usage"]
setattr(
model_response,
"usage",
litellm.Usage(
prompt_tokens=_codestral_usage.prompt_tokens,
completion_tokens=_codestral_usage.completion_tokens,
total_tokens=_codestral_usage.total_tokens,
),View on GitHub (pinned to 6c2dcb801b)
Solutions
- Ensure the streaming iterator passed to litellm decodes bytes to str before yielding.
- Update litellm — codestral handling has been reworked across versions.
- Prefer the standard 'codestral' OpenAI-compatible provider entry point instead of the text-completion variant unless you specifically need raw completion mode.
Example fix
# before (custom generator)
async def gen():
async for b in resp.content.iter_any():
yield b # bytes
# after
async def gen():
async for b in resp.content.iter_any():
yield b.decode("utf-8") # str Defensive patterns
Strategy: type-guard
Validate before calling
def ensure_str_chunks(source):
for c in source:
if isinstance(c, bytes):
c = c.decode("utf-8")
if not isinstance(c, str):
raise TypeError(f"expected str chunk, got {type(c)}")
yield c Type guard
def is_str_chunk(c) -> bool:
return isinstance(c, str) Try / catch
try:
for part in litellm.completion(model="text-completion-codestral/...", stream=True, ...):
...
except ValueError as e:
if "chunk is not a string" in str(e):
stream = (c.decode("utf-8") if isinstance(c, bytes) else str(c) for c in raw_source)
# retry with decoded chunks
raise Prevention
- Decode transport bytes to str before handing iterators to litellm.
- Prefer the OpenAI-compatible codestral provider over the text-completion variant.
When it happens
Trigger: Using model='text-completion-codestral/<model>' with stream=True when the transport yields bytes (not decoded) or a dict, e.g. an aiohttp/aiohttp-sse wrapper passing raw bytes chunks.
Common situations: Custom routers/proxies that forward bytes chunks; version mismatches where an upstream change stopped decoding chunks to str; mocking streams in tests with bytes payloads.
Related errors
- response.text
- Unexpected responses stream payload
- Braintrust API error: {e.response.text}
- Failed to connect to Braintrust API: {str(e)}
- Invalid Authorization header format. Expected: Bearer <token
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/503313840efd43bc.
Report an issue: GitHub.