BerriAI/litellm · error · ValueError

otel.attributes must be a mapping with optional…

Error message

otel.attributes must be a mapping with optional 'include_list' / 'exclude_list', got {type(value).__name__}

What it means

The OpenTelemetry callback accepts a metric-attribute cardinality filter (otel.attributes / the attributes= kwarg on OpenTelemetry(...)). _build_metric_attribute_filter only accepts a mapping (dict) with optional 'include_list' and 'exclude_list' keys; anything else — a bare list, a string, None-like sentinels that are not the dataclass, numbers — raises ValueError naming the offending type. This is fail-fast type validation of proxy/litellm config before any metric is recorded.

Solutions

  1. Change the value to a mapping: otel.attributes: {include_list: [gen_ai.system, gen_ai.request.model]} (or exclude_list)
  2. If you meant to allowlist team metadata keys instead, use the baggage/team-metadata config which does accept a list or comma-separated string
  3. After fixing, restart and confirm no ValueError at OpenTelemetry(...) construction

Example fix

# before (list -> ValueError: got list)
litellm_settings:
  callbacks: [otel]
callbacks:
  otel:
    attributes: [gen_ai.system]
# after (mapping)
callbacks:
  otel:
    attributes:
      include_list: [gen_ai.system]
Defensive patterns

Strategy: type-guard

Validate before calling

import os

raw = os.getenv('OTEL_ATTRIBUTES')  # if sourced from env, parse first
value = raw if raw is None else __import__('json').loads(raw)
if value is not None and not isinstance(value, dict):
    raise SystemExit(f'otel.attributes must be a mapping, got {type(value).__name__}')

Type guard

def is_valid_otel_attributes(value) -> bool:
    """Narrow otel.attributes config to the accepted mapping shape."""
    return isinstance(value, dict) and set(value) <= {'include_list', 'exclude_list'}

Try / catch

try:
    otel_cb = OpenTelemetry(attributes=config['otel']['attributes'])
except ValueError as e:
    raise ConfigError(f'otel callback rejected config: {e}') from e

Prevention

When it happens

Trigger: Passing OpenTelemetry(attributes=[...]) or configuring otel.attributes in config.yaml/callback_settings as a YAML list (e.g. attributes: [gen_ai.system]) or a comma-separated string instead of a dict like {include_list: [...]}; also programmatic constructions that forward an untyped value. Raised eagerly at logger construction when an explicit config/override is supplied, or when the proxy later populates callback_settings.otel.attributes.

Common situations: Copy-pasting an env-var-style comma-separated list into the YAML key; assuming the filter takes the same list shape as baggage_team_metadata_keys (which does accept a list); schema drift after upgrading litellm when otel.attributes changed from list-style to mapping-style; YAML indentation turning the mapping into a list of strings.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/65d2910fb4703e7e. Report an issue: GitHub.

Appendix: source

Thrown at litellm/integrations/opentelemetry.py:161

        "gen_ai.request.model",
        "gen_ai.framework",
        "hidden_params",
    )
    + tuple(f"metadata.{key}" for key in METRIC_METADATA_KEYS)
)


@dataclass(frozen=True)
class OTELMetricAttributeFilter:
    include_list: list[str] | None = None
    exclude_list: list[str] | None = None


def _build_metric_attribute_filter(value: Any) -> OTELMetricAttributeFilter:
    if isinstance(value, OTELMetricAttributeFilter):
        return value
    if not isinstance(value, dict):
        raise ValueError(
            "otel.attributes must be a mapping with optional 'include_list' / "
            f"'exclude_list', got {type(value).__name__}"
        )
    return OTELMetricAttributeFilter(
        include_list=value.get("include_list"),
        exclude_list=value.get("exclude_list"),
    )


def _resolve_metric_attribute_filter(
    attributes: OTELMetricAttributeFilter | None,
) -> tuple[frozenset[str] | None, frozenset[str] | None]:
    if attributes is None:
        return None, None
    include: Final = attributes.include_list or None
    exclude: Final = attributes.exclude_list or None
    if include and exclude:
        raise ValueError("otel.attributes: include_list and exclude_list are mutually exclusive")

View on GitHub (pinned to 77b7c6c40c)