{"record":{"id":"c75b6e64100bf167","repo":"sgl-project/sglang","slug":"the-number-of-placeholders-does-not-match-the-numb","errorCode":null,"errorMessage":"The number of placeholders does not match the number of replacements.","messagePattern":"The number of placeholders does not match the number of replacements\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/processors/step3_vl.py","lineNumber":433,"sourceCode":"        num_images: int,\n        num_patches: int,\n        patch_new_line_idx: Optional[list[bool]],\n    ) -> tuple[str, list[int]]:\n        if num_patches > 0:\n            patch_repl, patch_repl_ids = self._get_patch_repl(\n                num_patches, patch_new_line_idx\n            )\n        else:\n            patch_repl = \"\"\n            patch_repl_ids = []\n        image_repl, image_repl_ids = self._get_image_repl(num_images)\n        return patch_repl + image_repl, patch_repl_ids + image_repl_ids\n\n    def replace_placeholder(self, text: str, placeholder: str, repls: list[str]) -> str:\n        parts = text.split(placeholder)\n\n        if len(parts) - 1 != len(repls):\n            raise ValueError(\n                \"The number of placeholders does not match the number of replacements.\"  # noqa: E501\n            )\n\n        result = [parts[0]]\n        for i, repl in enumerate(repls):\n            result.append(repl)\n            result.append(parts[i + 1])\n\n        return \"\".join(result)\n\n    def __call__(\n        self,\n        text: Optional[Union[str, list[str]]] = None,\n        images: Optional[Union[Image.Image, list[Image.Image]]] = None,\n        return_tensors: Optional[Union[str, TensorType]] = None,\n        *args,\n        **kwargs,\n    ) -> BatchFeature:","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/processors/step3_vl.py#L415-L451","documentation":"Step3-VL's replace_placeholder splits the prompt text on the image placeholder token and requires the count of placeholder occurrences to exactly equal the number of replacement strings (one per image patch/window). A mismatch means the number of image tags in the text does not match the number of processed image segments produced by the processor.","triggerScenarios":"Prompt contains N image placeholder tags but M != N images were supplied (or vice versa); duplicate placeholder in one tag; placeholder string typo so split counts 0 occurrences while repls is non-empty.","commonSituations":"User chat message embeds two image tags but attaches one image; frontend inserts extra placeholder tokens; multi-image requests where one image failed download and was silently dropped, desynchronizing counts.","solutions":["Make the number of image tags in the text exactly match len(image_inputs)","Check for placeholder typos/duplicates against the processor's expected placeholder constant","Verify all image URLs/base64 payloads actually resolved before the request"],"exampleFix":"# before\ntext = '<img><img>'  # 2 tags\nimages = [img1]      # 1 image\n# after\ntext = '<img>'\nimages = [img1]","handlingStrategy":"validation","validationCode":"n_tags = text.count(placeholder)\nassert n_tags == len(repls), f'{n_tags} tags vs {len(repls)} images'","typeGuard":null,"tryCatchPattern":"try:\n    processor(...)\nexcept ValueError as e:\n    if 'placeholders' in str(e):\n        raise HTTPBadRequest('image tag count must match attached images')","preventionTips":["Generate the prompt tags programmatically from the image list","Validate tag count == image count in the request handler"],"tags":["multimodal","prompt-template","placeholder-mismatch","step3-vl"],"backgroundTag":"placeholder-count-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}