BerriAI/litellm · error · ValueError

tools or tool_choice cannot be set if using text

Error message

tools or tool_choice cannot be set if using text

What it means

Token counting for tools/tool_choice is only implemented on the messages= path, which mirrors OpenAI's function-definition overhead accounting. When counting a bare text= string there is no place for tool definitions to contribute, so passing tools or tool_choice together with text= is rejected.

Source

Thrown at litellm/litellm_core_utils/token_counter.py:381

    #########################################################
    # 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:
            includes_system_message: Final = any([message.get("role", None) == "system" for message in new_messages])
            num_tokens += _count_extra(params.count_function, tools, tool_choice, includes_system_message)

    else:
        raise ValueError("Either text or messages must be provided")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Wrap the string as messages: token_counter(model=m, messages=[{"role": "user", "content": text}], tools=tools, tool_choice=tc).
  2. If only the raw string count is needed, drop tools/tool_choice from the call.

Example fix

# before
n = litellm.token_counter(model="gpt-4o", text=prompt, tools=my_tools, tool_choice="auto")

# after
n = litellm.token_counter(model="gpt-4o",
    messages=[{"role": "user", "content": prompt}], tools=my_tools, tool_choice="auto")
Defensive patterns

Strategy: validation

Validate before calling

if tools or tool_choice:
    assert messages is not None, "tools counting requires messages=, not text="
    n = litellm.token_counter(model=m, messages=messages, tools=tools, tool_choice=tool_choice)
else:
    n = litellm.token_counter(model=m, text=text, messages=messages)

Prevention

When it happens

Trigger: litellm.token_counter(text="...", tools=[...]) or token_counter(text="...", tool_choice="auto") - the caller wants a prompt-plus-tools estimate but passed the prompt as text= instead of as messages.

Common situations: Cost estimators built around a prompt template string plus a tool list; migrating from counting only the user string to accounting for tool definitions.

Related errors


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