{"record":{"id":"1f10363ca0dbc15f","repo":"BerriAI/litellm","slug":"otel-attributes-token-type-attribute-is-a-struc","errorCode":null,"errorMessage":"otel.attributes: {TOKEN_TYPE_ATTRIBUTE} is a structural token-usage discriminator and cannot be filtered","messagePattern":"otel\\.attributes: (.+?) is a structural token-usage discriminator and cannot be filtered","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/opentelemetry.py","lineNumber":182,"sourceCode":"        )\n    return OTELMetricAttributeFilter(\n        include_list=value.get(\"include_list\"),\n        exclude_list=value.get(\"exclude_list\"),\n    )\n\n\ndef _resolve_metric_attribute_filter(\n    attributes: OTELMetricAttributeFilter | None,\n) -> tuple[frozenset[str] | None, frozenset[str] | None]:\n    if attributes is None:\n        return None, None\n    include: Final = attributes.include_list or None\n    exclude: Final = attributes.exclude_list or None\n    if include and exclude:\n        raise ValueError(\"otel.attributes: include_list and exclude_list are mutually exclusive\")\n    requested: Final = include or exclude or []\n    if TOKEN_TYPE_ATTRIBUTE in requested:\n        raise ValueError(\n            f\"otel.attributes: {TOKEN_TYPE_ATTRIBUTE} is a structural token-usage discriminator and cannot be filtered\"\n        )\n    unknown: Final = sorted(name for name in requested if name not in VALID_METRIC_ATTRIBUTE_NAMES)\n    if unknown:\n        raise ValueError(\n            f\"otel.attributes: unknown attribute name(s) {unknown}. Valid names: {sorted(VALID_METRIC_ATTRIBUTE_NAMES)}\"\n        )\n    return (\n        frozenset(include) if include else None,\n        frozenset(exclude) if exclude else None,\n    )\n\n\ndef _normalize_team_metadata_keys(value: Any) -> list[str]:\n    \"\"\"Coerce a team-metadata allowlist from a list or comma-separated string.\n\n    config.yaml passes a YAML list; an env var passes a comma-separated string.\n    Both collapse to a list of stripped, non-empty keys.","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/integrations/opentelemetry.py#L164-L200","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nattributes:\n  exclude_list: [gen_ai.token.type, metadata.requester_metadata]\n# after\nattributes:\n  exclude_list: [metadata.requester_metadata]","handlingStrategy":"validation","validationCode":"TOKEN_TYPE = 'gen_ai.token.type'\nattrs = config['otel'].get('attributes') or {}\nfor mode in ('include_list', 'exclude_list'):\n    names = attrs.get(mode) or []\n    if TOKEN_TYPE in names:\n        raise SystemExit(f'{TOKEN_TYPE} is structural and cannot appear in otel.attributes.{mode}')","typeGuard":"def filters_token_type(attrs: dict) -> bool:\n    \"\"\"True when the config attempts to filter gen_ai.token.type.\"\"\"\n    return 'gen_ai.token.type' in ((attrs.get('include_list') or []) + (attrs.get('exclude_list') or []))","tryCatchPattern":"try:\n    otel_cb = OpenTelemetry(attributes=attrs)\nexcept ValueError as e:\n    if 'cannot be filtered' in str(e):\n        attrs['include_list'] = [n for n in attrs.get('include_list', []) if n != 'gen_ai.token.type']\n        attrs['exclude_list'] = [n for n in attrs.get('exclude_list', []) if n != 'gen_ai.token.type']\n        otel_cb = OpenTelemetry(attributes=attrs)\n    else:\n        raise","preventionTips":["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"],"tags":["opentelemetry","otel","configuration","metrics","cardinality","validation"],"backgroundTag":"reserved-config-key","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}