BerriAI/litellm · error · ValueError

prompt_characters must be provided for tts calls. prompt_cha

Error message

prompt_characters must be provided for tts calls. prompt_characters={prompt_characters}, model={model}, custom_llm_provider={custom_llm_provider}, call_type={call_type}

What it means

For text-to-speech calls priced per character (e.g. OpenAI TTS models in LiteLLM's cost map), the calculator needs the input length. If call_type is 'speech'/'aspeech', the model's cost metric resolves to 'cost_per_character', and prompt_characters is None, it raises ValueError instead of guessing a cost.

Source

Thrown at litellm/cost_calculator.py:495

    """
    if model_with_provider in model_cost_ref:  # Option 2. use model with provider, model = "openai/gpt-4"
        model = model_with_provider
    elif model in model_cost_ref:  # Option 1. use model passed, model="gpt-4"
        model = model
    elif (
        model_without_prefix in model_cost_ref
    ):  # Option 3. if user passed model="bedrock/anthropic.claude-3", use model="anthropic.claude-3"
        model = model_without_prefix

    # see this https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models
    if call_type == "speech" or call_type == "aspeech":
        speech_model_info = litellm.get_model_info(model=model_without_prefix, custom_llm_provider=custom_llm_provider)
        cost_metric: Final = select_cost_metric_for_model(speech_model_info)
        prompt_cost: float = 0.0
        completion_cost: float = 0.0
        if cost_metric == "cost_per_character":
            if prompt_characters is None:
                raise ValueError(
                    f"prompt_characters must be provided for tts calls. prompt_characters={prompt_characters}, model={model}, custom_llm_provider={custom_llm_provider}, call_type={call_type}"
                )
            _prompt_cost, _completion_cost = _generic_cost_per_character(
                model=model_without_prefix,
                custom_llm_provider=custom_llm_provider,
                prompt_characters=prompt_characters,
                completion_characters=0,
                custom_prompt_cost=None,
                custom_completion_cost=0,
            )
            if _prompt_cost is None or _completion_cost is None:
                raise ValueError(
                    f"cost for tts call is None. prompt_cost={_prompt_cost}, completion_cost={_completion_cost}, model={model_without_prefix}, custom_llm_provider={custom_llm_provider}, prompt_characters={prompt_characters}, completion_characters={completion_characters}"
                )
            prompt_cost = _prompt_cost
            completion_cost = _completion_cost
        elif cost_metric == "cost_per_token":
            prompt_cost, completion_cost = generic_cost_per_token(

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass prompt_characters=len(input_text) to completion_cost for speech calls.
  2. Let LiteLLM's own logging path compute it — call the speech API through the standard litellm.speech(...) wrapper rather than computing cost manually.
  3. Update LiteLLM if an older version failed to thread prompt_characters through its logging object.
  4. For per-token-priced TTS models no characters are needed; confirm which metric applies via litellm.get_model_info(model)['cost_metric'] or the model's pricing keys.

Example fix

# before
cost = litellm.completion_cost(
    model="tts-1", call_type="speech", prompt="hello world",
)

# after
cost = litellm.completion_cost(
    model="tts-1", call_type="speech", prompt="hello world",
    prompt_characters=len("hello world"),
)
Defensive patterns

Strategy: validation

Validate before calling

if call_type in ("speech", "aspeech"):
    info = litellm.get_model_info(model=model_without_prefix, custom_llm_provider=custom_llm_provider)
    if "input_cost_per_character" in (info or {}):
        assert prompt_characters is not None, "pass prompt_characters=len(text) for per-character TTS pricing"
        prompt_characters = prompt_characters if prompt_characters is not None else len(input_text)

Type guard

def tts_cost_ready(model: str, text: str, prompt_characters: int | None) -> bool:
    if prompt_characters is not None:
        return True
    try:
        info = litellm.get_model_info(model=model) or {}
        return "input_cost_per_character" not in info
    except Exception:
        return False

Try / catch

try:
    cost = litellm.completion_cost(model=model, call_type="speech", prompt=text, prompt_characters=len(text))
except ValueError as e:
    if "prompt_characters must be provided" in str(e):
        cost = litellm.completion_cost(model=model, call_type="speech", prompt=text, prompt_characters=len(text))
    else:
        raise

Prevention

When it happens

Trigger: Invoking litellm.speech()/aspeech and then completion_cost (usually via logging) without prompt_characters; calling completion_cost(call_type='speech') on a model whose model_info uses cost_per_character while omitting the character count.

Common situations: Custom wrappers that compute TTS costs from a response object only; models newly switched to per-character pricing in a LiteLLM upgrade; forgetting that TTS responses carry audio, not token usage.

Related errors


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