BerriAI/litellm · error · ValueError
otel.attributes: is a structural token-usage discriminator…
Error message
otel.attributes: {TOKEN_TYPE_ATTRIBUTE} is a structural token-usage discriminator and cannot be filtered What it means
The otel.attributes filter may not include or exclude the attribute gen_ai.token.type (TOKEN_TYPE_ATTRIBUTE). That attribute is the structural discriminator that splits token usage metrics into input/output/reasoning/cache series; filtering it out would collapse distinct series onto the same labels and corrupt the counts. _resolve_metric_attribute_filter raises ValueError whenever the requested name appears in the active include or exclude list.
Solutions
- Remove gen_ai.token.type from whichever list it appears in — there is no override; it is structural by design
- Restrict cardinality via other valid names: gen_ai.operation.name, gen_ai.provider.name, gen_ai.system, gen_ai.request.model, gen_ai.framework, hidden_params, or metadata.<key> entries
- If token-type series themselves are too high-cardinality for your backend, the filter is the wrong tool — reduce at the metrics backend (recording rules/aggregation) instead
Example fix
# before attributes: exclude_list: [gen_ai.token.type, metadata.requester_metadata] # after attributes: exclude_list: [metadata.requester_metadata]
Defensive patterns
Strategy: validation
Validate before calling
TOKEN_TYPE = 'gen_ai.token.type'
attrs = config['otel'].get('attributes') or {}
for mode in ('include_list', 'exclude_list'):
names = attrs.get(mode) or []
if TOKEN_TYPE in names:
raise SystemExit(f'{TOKEN_TYPE} is structural and cannot appear in otel.attributes.{mode}') Type guard
def filters_token_type(attrs: dict) -> bool:
"""True when the config attempts to filter gen_ai.token.type."""
return 'gen_ai.token.type' in ((attrs.get('include_list') or []) + (attrs.get('exclude_list') or [])) Try / catch
try:
otel_cb = OpenTelemetry(attributes=attrs)
except ValueError as e:
if 'cannot be filtered' in str(e):
attrs['include_list'] = [n for n in attrs.get('include_list', []) if n != 'gen_ai.token.type']
attrs['exclude_list'] = [n for n in attrs.get('exclude_list', []) if n != 'gen_ai.token.type']
otel_cb = OpenTelemetry(attributes=attrs)
else:
raise Prevention
- Never generate filter lists by copying the full attribute set from a metrics explorer
- Keep gen_ai.token.type in a documented do-not-filter list next to your OTel config
- When templating configs, exclude structural attributes programmatically rather than by review
- If token series cardinality is the concern, aggregate at the metrics backend instead of the exporter
When it happens
Trigger: Listing gen_ai.token.type in include_list or exclude_list of otel.attributes, e.g. {include_list: [gen_ai.token.type, gen_ai.system]}. Validation fires eagerly for explicit constructor config or on first metric record for proxy-populated callback_settings.otel.attributes.
Common situations: Operators attempting aggressive cardinality reduction who sweep 'everything gen_ai.*' into exclude_list via copy-paste of the full attribute list from docs/metrics explorer; treating gen_ai.token.type as a normal label like provider or model; wildcard-style configs expanded by a templating system (Helm/Ansible) that includes all known attribute names.
Related errors
- otel.attributes: include_list and exclude_list are mutually…
- otel.attributes: unknown attribute name(s)
- otel.attributes must be a mapping with optional…
- unknown mapper name ; known
- OpenTelemetry OTLP gRPC metric exporter is not available…
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/1f10363ca0dbc15f.
Report an issue: GitHub.
Appendix: source
Thrown at litellm/integrations/opentelemetry.py:182
)
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")
requested: Final = include or exclude or []
if TOKEN_TYPE_ATTRIBUTE in requested:
raise ValueError(
f"otel.attributes: {TOKEN_TYPE_ATTRIBUTE} is a structural token-usage discriminator and cannot be filtered"
)
unknown: Final = sorted(name for name in requested if name not in VALID_METRIC_ATTRIBUTE_NAMES)
if unknown:
raise ValueError(
f"otel.attributes: unknown attribute name(s) {unknown}. Valid names: {sorted(VALID_METRIC_ATTRIBUTE_NAMES)}"
)
return (
frozenset(include) if include else None,
frozenset(exclude) if exclude else None,
)
def _normalize_team_metadata_keys(value: Any) -> list[str]:
"""Coerce a team-metadata allowlist from a list or comma-separated string.
config.yaml passes a YAML list; an env var passes a comma-separated string.
Both collapse to a list of stripped, non-empty keys.View on GitHub (pinned to 77b7c6c40c)