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
- Pass only one: embed the raw string as a message ({"role":"user","content":text}) and use messages=, or drop messages= and keep text=.
- 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
- Never forward **kwargs blindly into token_counter from wrapper functions.
- Standardize on messages= as the single counting input across the codebase.
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
- tools or tool_choice cannot be set if using text
- Invalid detail value: {detail}. Expected 'low', 'high', or '
- 'models' param not in kwargs
- start_time_utc and end_time_utc must be provided together
- start_time_utc is required for getting a payload from GCS Bu
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/61bc6eed7c0f3e16.
Report an issue: GitHub.