{"record":{"id":"b9d1e9d19c45b444","repo":"sgl-project/sglang","slug":"number-of-labels-must-match-the-number-of-tag-keys","errorCode":null,"errorMessage":"Number of labels must match the number of tag keys. Expected {expected}, got {len(labels)}","messagePattern":"Number of labels must match the number of tag keys\\. Expected (.+?), got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/observability/ray_wrappers.py","lineNumber":108,"sourceCode":"                \"RayPrometheusMetric requires Ray to be installed. \"\n                \"Install with: pip install 'ray[serve]'\"\n            )\n        self.metric: Optional[Metric] = None\n        self._tags: dict = {\"ReplicaId\": _get_replica_id() or \"\"}\n\n    @staticmethod\n    def _get_tag_keys(labelnames: Optional[List[str]]) -> tuple:\n        labels = list(labelnames) if labelnames else []\n        labels.append(\"ReplicaId\")\n        return tuple(labels)\n\n    def _build_tags(self, *labels: str, **labelskwargs: str) -> dict:\n        if labels:\n            # The trailing entry of ``_tag_keys`` is always ``ReplicaId`` which we\n            # populate ourselves; positional args fill the preceding keys only.\n            expected = len(self.metric._tag_keys) - 1\n            if len(labels) != expected:\n                raise ValueError(\n                    \"Number of labels must match the number of tag keys. \"\n                    f\"Expected {expected}, got {len(labels)}\"\n                )\n            labelskwargs.update(zip(self.metric._tag_keys, labels))\n        labelskwargs[\"ReplicaId\"] = _get_replica_id() or \"\"\n        return {k: v if isinstance(v, str) else str(v) for k, v in labelskwargs.items()}\n\n    def labels(self, *labels: str, **labelskwargs: str) -> RayPrometheusMetric:\n        if self._is_labeled:\n            raise ValueError(\"labels() cannot be called on an already-labeled metric.\")\n        clone = copy.copy(self)\n        clone._tags = self._build_tags(*labels, **labelskwargs)\n        clone._is_labeled = True\n        return clone\n\n    @staticmethod\n    def _coerce_positive_boundaries(buckets):\n        # Ray (gRPC OpenCensus / OpenTelemetry export) rejects boundaries","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/observability/ray_wrappers.py#L90-L126","documentation":"When calling .labels(*labels) on a RayPrometheusMetric, positional label values must fill every tag key except the trailing ReplicaId, which the wrapper injects automatically. Supplying a different count of positional args is rejected.","triggerScenarios":"metric.labels('a') on a metric declared with 3 tag keys (expected=2); mixing positional and the wrong number of kwargs.","commonSituations":"Copy-pasting labels() calls between metrics with different tag signatures; adding a tag key to the metric definition without updating call sites.","solutions":["Pass exactly len(tag_keys)-1 positional values (ReplicaId is auto-filled)","Prefer keyword labels matching tag key names to make arity explicit","Update all .labels() call sites whenever tag keys change"],"exampleFix":"# before\nm = metric.labels('model')  # metric has 3 tag keys\n# after\nm = metric.labels('model', 'gpu_id')  # ReplicaId auto-injected","handlingStrategy":"validation","validationCode":"expected = len(metric.metric._tag_keys) - 1\nassert len(label_values) == expected, f'need {expected} positional labels'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use keyword labels matching tag key names","Define tag keys in one constant and generate label call sites from it"],"tags":["observability","ray","metrics","labels"],"backgroundTag":"label-arity-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}