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
- Change the value to a mapping: otel.attributes: {include_list: [gen_ai.system, gen_ai.request.model]} (or exclude_list)
- If you meant to allowlist team metadata keys instead, use the baggage/team-metadata config which does accept a list or comma-separated string
- 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
- Schema-validate config.yaml against the expected otel.attributes mapping shape in CI
- Keep the mapping/list distinction in mind: attributes is a dict, baggage_team_metadata_keys accepts a list
- Use YAML anchors from a reviewed example block instead of hand-writing new otel config
- Prefer explicit keys (include_list:/exclude_list:) over bare values so YAML cannot coerce to a list
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
- otel.attributes: include_list and exclude_list are mutually…
- otel.attributes: is a structural token-usage discriminator…
- otel.attributes: unknown attribute name(s)
- OpenTelemetry OTLP gRPC metric exporter is not available…
- OpenTelemetry OTLP gRPC metric exporter is not available…
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)