BerriAI/litellm · warning · ValueError

Invalid content item type: {content_type}. Expected str or d

Error message

Invalid content item type: {content_type}. Expected str or dict with 'type' field (text, image_url, tool_use, tool_result, thinking, tool_reference).

What it means

A message content list contained an item that is neither a str nor a dict with one of the recognized 'type' discriminators (text, image_url, tool_use, tool_result, thinking, tool_reference). The actual type (or the dict's 'type' value) is reported. When default_token_count is set this is suppressed and the fallback value used instead.

Source

Thrown at litellm/litellm_core_utils/token_counter.py:730

                # Claude extended thinking content block
                # Count the thinking text and skip signature (opaque signature blob)
                thinking_text = str(c.get("thinking", ""))
                if thinking_text:
                    num_tokens += count_function(thinking_text)
            elif c["type"] == "tool_reference":
                # Anthropic tool-search reference block: a lightweight pointer to
                # a deferred tool, e.g. {"type": "tool_reference", "tool_name": ...}.
                # The full tool definition is counted via the `tools` param, so we
                # only count the referenced name here. Without this branch,
                # token_counter raises on tool-search traffic; on the streaming
                # anthropic_messages path that nulls response_cost and causes the
                # proxy to drop the SpendLogs row entirely (silent cost undercount).
                tool_name = str(c.get("tool_name") or "")
                if tool_name:
                    num_tokens += count_function(tool_name)
            else:
                content_type = c.get("type", type(c).__name__) if isinstance(c, dict) else type(c).__name__
                raise ValueError(
                    f"Invalid content item type: {content_type}. "
                    f"Expected str or dict with 'type' field (text, image_url, tool_use, tool_result, thinking, tool_reference)."
                )
        return num_tokens
    except Exception as e:
        if default_token_count is not None:
            return default_token_count
        raise ValueError(
            f"Error getting number of tokens from content list: {e}, default_token_count={default_token_count}"
        )


def _format_function_definitions(tools):
    """Formats tool definitions in the format that OpenAI appears to use.
    Based on https://github.com/forestwanglin/openai-java/blob/main/jtokkit/src/main/java/xyz/felh/openai/jtokkit/utils/TikTokenUtils.java
    """
    lines: Final = []
    lines.append("namespace functions {")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Update litellm to a version that supports the block type (support for new block types lands quickly).
  2. Filter content lists to known types (or stringify unknown blocks) before counting.
  3. Pass default_token_count so unknown blocks degrade to an estimate instead of raising.

Example fix

# before
content = [maybe_block for maybe_block in raw if cond]  # may contain None
n = litellm.token_counter(model=m, messages=[{"content": content}])

# after
KNOWN = ("text", "image_url", "tool_use", "tool_result", "thinking", "tool_reference")
content = [c for c in content if isinstance(c, str) or (isinstance(c, dict) and c.get("type") in KNOWN)]
n = litellm.token_counter(model=m, messages=[{"content": content}], default_token_count=0)
Defensive patterns

Strategy: fallback

Validate before calling

KNOWN = {"text", "image_url", "tool_use", "tool_result", "thinking", "tool_reference"}
content = [c for c in content
           if isinstance(c, str) or (isinstance(c, dict) and c.get("type") in KNOWN)]

Type guard

def is_countable_content_item(item) -> bool:
    if isinstance(item, str):
        return True
    return isinstance(item, dict) and item.get("type") in {
        "text", "image_url", "tool_use", "tool_result", "thinking", "tool_reference"}

Try / catch

try:
    n = litellm.token_counter(model=m, messages=msgs)
except ValueError:
    n = litellm.token_counter(model=m, messages=msgs, default_token_count=0)

Prevention

When it happens

Trigger: Content lists containing None (an optional block that was never built), integers/floats, or dicts with novel 'type' values from newer provider features (e.g. 'document', 'server_tool_use') not yet handled by this litellm version.

Common situations: New Anthropic/OpenAI content block types shipped before litellm support lands; list comprehensions yielding None for skipped items; content assembled from mixed sources.

Related errors


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