BerriAI/litellm · error · ValueError

text and messages cannot both be set

Error message

text and messages cannot both be set

What it means

token_counter() refuses ambiguous input: passing both text= and messages= makes the count undefined (count the string or the conversation?), so it raises immediately. The function counts exactly one input kind per call; this is an API-contract error, not a model or tokenizer problem.

Source

Thrown at litellm/litellm_core_utils/token_counter.py:375

    default_token_count (Optional[int]): The default number of tokens to return for a message block, if an error occurs. Default is None.

    Returns:
    int: The number of tokens in the text.
    """
    from litellm.utils import convert_list_message_to_dict

    #########################################################
    # Flag to disable token counter
    # We've gotten reports of this consuming CPU cycles,
    # exposing this flag to allow users to disable
    # it to confirm if this is indeed the issue
    #########################################################
    if litellm.disable_token_counter is True:
        return 0

    verbose_logger.debug("messages in token_counter: %s, text in token_counter: %s", messages, text)
    if text is not None and messages is not None:
        raise ValueError("text and messages cannot both be set")
    if use_default_image_token_count is None:
        use_default_image_token_count = False

    if text is not None:
        if tools or tool_choice:
            raise ValueError("tools or tool_choice cannot be set if using text")
        if isinstance(text, list):
            text_to_count = "".join(t for t in text if isinstance(t, str))
        elif isinstance(text, str):
            text_to_count = text
        count_function: Final = _get_count_function(model, custom_tokenizer)
        num_tokens = count_function(text_to_count)

    elif messages is not None:
        new_messages: Final = cast(list[AllMessageValues], convert_list_message_to_dict(messages))
        params: Final = _MessageCountParams(model, custom_tokenizer)
        num_tokens = _count_messages(params, new_messages, use_default_image_token_count, default_token_count)
        if count_response_tokens is False:

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass only one: embed the raw string as a message ({"role":"user","content":text}) and use messages=, or drop messages= and keep text=.
  2. Audit intermediate wrapper functions that forward both kwargs with **.

Example fix

# before
n = litellm.token_counter(model="gpt-4o", text="summarize this", messages=[{"role": "user", "content": "hi"}])

# after
n = litellm.token_counter(model="gpt-4o", messages=[{"role": "user", "content": "summarize this"}])
Defensive patterns

Strategy: validation

Validate before calling

def safe_token_count(model, text=None, messages=None, **kw):
    if text is not None and messages is not None:
        raise ValueError("pass exactly one of text or messages")
    return litellm.token_counter(model=model, text=text, messages=messages, **kw)

Prevention

When it happens

Trigger: Calling litellm.token_counter(text="hello", messages=[...]) - usually a wrapper forwarding both kwargs, or a call site that accreted parameters during a refactor.

Common situations: Wrappers around token_counter that pass **kwargs through; refactors that add messages= to an existing text= call; example code merged from two snippets.

Related errors


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