sgl-project/sglang · error · ValueError

The number of placeholders does not match the number of…

Error message

The number of placeholders does not match the number of replacements.

What it means

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.

Solutions

  1. Make the number of image tags in the text exactly match len(image_inputs)
  2. Check for placeholder typos/duplicates against the processor's expected placeholder constant
  3. Verify all image URLs/base64 payloads actually resolved before the request

Example fix

# before
text = '<img><img>'  # 2 tags
images = [img1]      # 1 image
# after
text = '<img>'
images = [img1]
Defensive patterns

Strategy: validation

Validate before calling

n_tags = text.count(placeholder)
assert n_tags == len(repls), f'{n_tags} tags vs {len(repls)} images'

Try / catch

try:
    processor(...)
except ValueError as e:
    if 'placeholders' in str(e):
        raise HTTPBadRequest('image tag count must match attached images')

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/c75b6e64100bf167. Report an issue: GitHub.

Appendix: source

Thrown at python/sglang/srt/multimodal/processors/step3_vl.py:433

        num_images: int,
        num_patches: int,
        patch_new_line_idx: Optional[list[bool]],
    ) -> tuple[str, list[int]]:
        if num_patches > 0:
            patch_repl, patch_repl_ids = self._get_patch_repl(
                num_patches, patch_new_line_idx
            )
        else:
            patch_repl = ""
            patch_repl_ids = []
        image_repl, image_repl_ids = self._get_image_repl(num_images)
        return patch_repl + image_repl, patch_repl_ids + image_repl_ids

    def replace_placeholder(self, text: str, placeholder: str, repls: list[str]) -> str:
        parts = text.split(placeholder)

        if len(parts) - 1 != len(repls):
            raise ValueError(
                "The number of placeholders does not match the number of replacements."  # noqa: E501
            )

        result = [parts[0]]
        for i, repl in enumerate(repls):
            result.append(repl)
            result.append(parts[i + 1])

        return "".join(result)

    def __call__(
        self,
        text: Optional[Union[str, list[str]]] = None,
        images: Optional[Union[Image.Image, list[Image.Image]]] = None,
        return_tensors: Optional[Union[str, TensorType]] = None,
        *args,
        **kwargs,
    ) -> BatchFeature:

View on GitHub (pinned to 0132848349)