BerriAI/litellm · error · ValueError

`prompt` must be a non-empty string or a non-empty list of s

Error message

`prompt` must be a non-empty string or a non-empty list of strings. Got: {prompt_type_name}.

What it means

This ValueError comes from LiteLLM's chat-endpoint input normalization: when you pass a bare `prompt` (instead of `messages`) it must be a non-empty string or a non-empty list of non-empty strings. Pre-tokenized prompts (list[int]/list[list[int]]) are deliberately rejected because only OpenAI-family text endpoints accept them. Anything else (None, empty string, empty list, mixed types, ints) fails fast here.

Source

Thrown at litellm/litellm_core_utils/prompt_templates/common_utils.py:1878

def text_completion_prompt_to_messages(prompt: object) -> tuple[AllMessageValues, ...]:
    """
    Wrap an OpenAI ``/v1/completions`` ``prompt`` into Chat Completion messages.

    Mirrors what ``litellm.text_completion`` does on the real-time path: a
    string becomes a single user message, and a list of strings becomes one
    user message per element. Pre-tokenized prompts (``list[int]`` /
    ``list[list[int]]``) are only meaningful for the OpenAI-family text
    endpoints, so they are rejected here rather than silently forwarded, as is
    an empty prompt, which every chat-shaped provider rejects downstream.
    """
    prompt_type_name: Final = type(prompt).__name__
    if isinstance(prompt, str) and prompt:
        return (ChatCompletionUserMessage(role="user", content=prompt),)
    entries: Final = cast("Sequence[object]", prompt) if isinstance(prompt, Sequence) else ()
    string_entries: Final = tuple(entry for entry in entries if isinstance(entry, str) and entry)
    if string_entries and len(string_entries) == len(entries):
        return tuple(ChatCompletionUserMessage(role="user", content=entry) for entry in string_entries)
    raise ValueError(f"`prompt` must be a non-empty string or a non-empty list of strings. Got: {prompt_type_name}.")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass a non-empty prompt: validate/strip the string and supply a default (e.g. '.') when it is empty
  2. Use the messages=[{"role": "user", "content": ...}] API instead of prompt= for chat models
  3. For token-ID prompts, use an OpenAI-family text completion endpoint, not the chat path

Example fix

# before
resp = litellm.completion(model="gpt-4o-mini", prompt=user_text)  # user_text may be ""

# after
user_text = user_text.strip() or "."
resp = litellm.completion(model="gpt-4o-mini", messages=[{"role": "user", "content": user_text}])
Defensive patterns

Strategy: validation

Validate before calling

def normalize_prompt(prompt):
    if isinstance(prompt, str) and prompt.strip():
        return [{"role": "user", "content": prompt}]
    if isinstance(prompt, list) and prompt and all(isinstance(p, str) and p.strip() for p in prompt):
        return [{"role": "user", "content": p} for p in prompt]
    raise ValueError("prompt must be a non-empty string or non-empty list of non-empty strings")

messages = normalize_prompt(user_input)

Type guard

from typing import Any

def is_valid_prompt(p: Any) -> bool:
    if isinstance(p, str):
        return bool(p.strip())
    if isinstance(p, list):
        return bool(p) and all(isinstance(x, str) and x.strip() for x in p)
    return False

Prevention

When it happens

Trigger: Calling a chat-shaped completion (e.g. litellm.completion or a provider chat handler) with prompt="", prompt=[], prompt=["hi", ""], prompt=None, or prompt=[1,2,3] instead of using the messages= parameter with proper role objects.

Common situations: Porting code from OpenAI's legacy completions API (prompt=...) to a chat model; dynamic prompts that are empty after template substitution or stripping; accidentally passing token IDs from a tokenizer to a chat endpoint.

Related errors


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