rpx_benchmark.vqa.prompts

Task prompts before each model's official chat/image template is applied.

  1"""Task prompts before each model's official chat/image template is applied."""
  2
  3from __future__ import annotations
  4
  5from dataclasses import dataclass
  6
  7from ..exceptions import ConfigError
  8from .contract import BBOX_TYPES, BINARY_TYPES, VQASample
  9
 10
 11@dataclass(frozen=True)
 12class PromptSpec:
 13    text: str
 14    max_new_tokens: int
 15    output_kind: str
 16
 17
 18def display_question(question: str) -> str:
 19    """Hide catalog encoding without changing the stored ground truth label."""
 20    return " ".join(question.replace("_", " ").split())
 21
 22
 23def native_referring_expression(sample: VQASample) -> str:
 24    """Rewrite an RPX question into a native grounding expression.
 25
 26    Florence/PaliGemma grounding heads are trained on referring expressions,
 27    not interrogative VQA syntax.  This transformation is deterministic and
 28    uses only the public question/type -- never the answer or GT bbox -- so the
 29    scored path remains one model generation.
 30    """
 31    question = display_question(sample.question).strip().rstrip("?")
 32    lower = question.lower()
 33    if sample.question_type == "spatial_lr_extreme":
 34        if "left" in lower:
 35            return "the leftmost object"
 36        if "right" in lower:
 37            return "the rightmost object"
 38    if sample.question_type == "depth_closest" and "closest" in lower and "camera" in lower:
 39        return "the object closest to the camera"
 40    if sample.question_type == "spatial_farthest":
 41        marker = "farthest from the "
 42        if marker in lower:
 43            return "the object farthest from " + question[lower.index(marker) + len(marker) :]
 44    if sample.question_type == "inctx_spatial_farthest":
 45        return "the object in image 2 farthest from the object shown in image 1"
 46
 47    # Normal attribute questions all begin with one of these stable forms.
 48    for prefix in ("which object is ", "what is the object "):
 49        if lower.startswith(prefix):
 50            description = question[len(prefix) :]
 51            if sample.question_type == "attr_single_color":
 52                return "the " + description + " object"
 53            return "the object " + description
 54
 55    # In-context questions already encode the complete reference relation;
 56    # remove only the redundant request for output formatting and turn the
 57    # interrogative into a grammatical relative clause.
 58    suffix = "? What is its bounding box in Image 2"
 59    if question.endswith(suffix):
 60        question = question[: -len(suffix)]
 61    prefix = "Which object in Image 2 "
 62    if question.startswith(prefix):
 63        relation = question[len(prefix) :]
 64        if relation.startswith("has "):
 65            relation = "that " + relation
 66        elif relation.startswith("is "):
 67            relation = "that " + relation
 68        return "the object in image 2 " + relation
 69    if question.lower().startswith("which object "):
 70        return "the object " + question[len("Which object ") :]
 71    return question
 72
 73
 74def _bbox_instruction(question: str) -> str:
 75    """Direct, model-neutral bbox instruction for every JSON-capable VLM.
 76
 77    The scored path is deliberately one model call: the model sees the original
 78    question and every required image while choosing and localizing the answer.
 79    Splitting this into answer-then-ground calls compounds two independent model
 80    errors and no longer measures the dataset's end-to-end task.
 81    """
 82    return (
 83        f"{question}\n"
 84        "Identify and localize the one visible object that answers the question. "
 85        "For a relational question, localize the result object, not the reference "
 86        "object named or shown in the question. The answer object is in the target "
 87        "image (Image 2 for a two-image input; otherwise the only image). Return only "
 88        'JSON: {"label":"object name","bbox":[x_min,y_min,x_max,y_max]}. '
 89        "Normalize every bbox coordinate from 0 to 1000 relative to the target "
 90        "image width and height. Use XYXY corner order and enclose the entire object. "
 91        "No Markdown or explanation."
 92    )
 93
 94
 95def _molmo_bbox_instruction(question: str) -> str:
 96    """Request an entire box on Molmo's native percentage coordinate grid.
 97
 98    Molmo's documented spatial markup uses image-relative percentages.  Asking
 99    these checkpoints for the generic RPX 0--1000 grid made otherwise plausible
100    percentage boxes score as tiny top-left boxes.  Keep the scored task to one
101    model call, but make the model-facing coordinate convention explicit; the
102    output parser performs the deterministic 0--100 to image-pixel conversion.
103    """
104    return (
105        f"{question}\n"
106        "Identify and localize the one visible object that answers the question. "
107        "For a relational question, localize the result object, not the reference "
108        "object named or shown in the question. The answer object is in the target "
109        "image (Image 2 for a two-image input; otherwise the only image). Return only "
110        '{"label":"object name","bbox":[x_min,y_min,x_max,y_max]}. '
111        "Express every bbox coordinate as a percentage from 0 to 100 relative to "
112        "the target image width and height. Use XYXY corner order and enclose the "
113        "entire object. No Markdown or explanation."
114    )
115
116
117def _internvl_bbox_instruction(expression: str) -> str:
118    """Use InternVL 3.5's trained RefCOCO-style native grounding protocol."""
119    # Match OpenGVLab's RefCOCO evaluation prompt exactly.  Output constraints
120    # added after this sentence can knock a native grounding checkpoint out of
121    # its trained response protocol; strictness belongs in the parser instead.
122    return (
123        "Please provide the bounding box coordinate of the region this sentence "
124        f"describes: <ref>{expression}</ref>"
125    )
126
127
128def _cosmos_reason2_bbox_instruction(question: str) -> str:
129    """Use a strict Reason2 2D-grounding JSON and coordinate grid.
130
131    NVIDIA's official Reason2 grounding prompt asks to locate a bounding box
132    and return JSON. RPX makes that underspecified JSON contract explicit using
133    Qwen3-VL's ``bbox_2d`` field and target-relative 0--1000 XYXY coordinates;
134    Reason2 is a Qwen3-VL architecture. RPX also adds the GT-free selection
135    rules required by relational questions. The optional Reason2 chain-of-thought
136    instruction is deliberately omitted: this benchmark scores one direct answer
137    and retains deterministic decoding.
138    """
139    return (
140        f"{question}\n"
141        "Identify the one visible object that answers the question. For a relational "
142        "question, select the result object, not the reference object named or shown "
143        "in the question. The answer object is in the target image (Image 2 for a "
144        "two-image input; otherwise the only image). Locate the accurate bounding box "
145        "of that answer object. Return only a JSON list in this exact form: "
146        '[{"bbox_2d":[x1,y1,x2,y2],"label":"object name"}]. '
147        "Normalize x and y independently from 0 to 1000 relative to the target image. "
148        "Use XYXY corner order and enclose the entire object. No Markdown, reasoning, "
149        "or explanation."
150    )
151
152
153def build_semantic_diagnostic_prompt(sample: VQASample, model_key: str) -> PromptSpec:
154    """Ask for the answer label only, without leaking the ground truth.
155
156    This is deliberately diagnostic rather than a scored benchmark protocol:
157    it separates target selection/reasoning from coordinate prediction.
158    """
159    question = display_question(sample.question)
160    if model_key.startswith("florence2-"):
161        # Florence task tokens are a closed native interface.  Extra formatting
162        # prose is treated as VQA question text and can be echoed by the model.
163        return PromptSpec(question, 32, "diagnostic_semantic_label")
164    if model_key.startswith("paligemma2-"):
165        # Follow the checkpoint's documented `answer {lang} {question}` form
166        # exactly.  Enforce short-answer formatting in the diagnostic parser.
167        return PromptSpec(f"answer en {question}\n", 32, "diagnostic_semantic_label")
168    instruction = (
169        f"{question}\nIdentify the one object that answers the question. "
170        "For a relational question, name the result object, not the reference "
171        "object named or shown in the question. Return only the shortest common "
172        "noun phrase naming that object: 1 to 5 words, with no sentence, article, "
173        "coordinates, Markdown, punctuation, or explanation."
174    )
175    return PromptSpec(instruction, 32, "diagnostic_semantic_label")
176
177
178def build_oracle_localization_prompt(sample: VQASample, model_key: str) -> PromptSpec:
179    """Localize the known GT label in the target image for diagnosis only."""
180    return build_label_localization_prompt(display_question(sample.answer), model_key, oracle=True)
181
182
183def build_label_localization_prompt(
184    label: str, model_key: str, *, oracle: bool = False
185) -> PromptSpec:
186    """Ground a label/referring phrase without canonicalizing its wording.
187
188    The predicted-label diagnostic deliberately passes the model's own answer
189    phrase through unchanged.  Correctness is determined spatially against the
190    target identity, so synonyms and harmless modifiers cannot create a false
191    semantic failure merely because their strings differ from the catalog.
192    """
193    label = display_question(label)
194    output_kind = "diagnostic_oracle_bbox" if oracle else "diagnostic_predicted_label_bbox"
195    if model_key.startswith("florence2-"):
196        # Florence's official phrase-grounding interface is TASK + phrase.
197        # Adding JSON instructions makes every noun in those instructions a
198        # grounding candidate and was the cause of the invalid smoke results.
199        return PromptSpec(label, 128, output_kind)
200    if model_key.startswith("paligemma2-"):
201        return PromptSpec(f"detect {label}\n", 64, output_kind)
202    if model_key.startswith("deepseek-vl2"):
203        return PromptSpec(f"<|ref|>{label}<|/ref|>.", 128, output_kind)
204    if model_key.startswith("cosmos-reason2-"):
205        return PromptSpec(
206            f'Locate the accurate bounding box of "{label}" in the target image. '
207            "Return only a JSON list in this exact form: "
208            '[{"bbox_2d":[x1,y1,x2,y2],"label":"object name"}]. '
209            "Normalize x and y independently from 0 to 1000 relative to the target "
210            "image. Use XYXY corner order. No Markdown, reasoning, or explanation.",
211            96,
212            output_kind,
213        )
214    if model_key in {"molmo-7b-d", "molmoe-1b"}:
215        # Molmo 0924 is natively trained to answer pointing prompts with
216        # <point>/<points> markup on a 0--100 coordinate grid.  This remains
217        # an explicitly unscored localization diagnostic: a point is never
218        # fabricated into a bbox or substituted for the scored RPX result.
219        return PromptSpec(
220            f"Point to the {label} in the image.",
221            64,
222            (
223                "diagnostic_oracle_point"
224                if oracle
225                else "diagnostic_predicted_label_point"
226            ),
227        )
228    return PromptSpec(
229        f'Locate the visible object named "{label}" in the target image. '
230        'Return only JSON: {"label":"object name","bbox":'
231        "[x_min,y_min,x_max,y_max]}. Normalize every bbox coordinate from 0 "
232        "to 1000 relative to the target-image width and height. Use XYXY corner "
233        "order and enclose the entire object. No Markdown or explanation.",
234        96,
235        output_kind,
236    )
237
238
239def build_prompt(sample: VQASample, model_key: str) -> PromptSpec:
240    question = display_question(sample.question)
241    if sample.question_type in BINARY_TYPES:
242        if model_key.startswith("paligemma2-"):
243            return PromptSpec(f"answer en {question}\n", 8, "binary")
244        return PromptSpec(
245            f"{question}\nAnswer using exactly one lowercase word: yes or no.", 4, "binary"
246        )
247    if sample.question_type in BBOX_TYPES:
248        if model_key.startswith("cosmos-reason2-"):
249            return PromptSpec(
250                _cosmos_reason2_bbox_instruction(question),
251                128,
252                "bbox_json_normalized_1000",
253            )
254        if model_key.startswith("deepseek-vl2"):
255            expression = native_referring_expression(sample)
256            if sample.is_in_context:
257                # DeepSeek-VL2 documents <|ref|> for ordinary single-image
258                # referring-expression localization, but its native
259                # multi-image/in-context grounding examples use the
260                # <|grounding|> mode.  Image 1 is already the hash-verified
261                # reference crop; Image 2 is the target scene.
262                return PromptSpec(
263                    "<|grounding|>The first image is a crop showing the reference "
264                    f"object. Locate only {expression} and return its grounding in "
265                    "the second image.",
266                    128,
267                    "bbox_native_question_grounding",
268                )
269            return PromptSpec(
270                f"<|ref|>{expression}<|/ref|>.",
271                128,
272                "bbox_native_question_grounding",
273            )
274        if model_key.startswith("florence2-"):
275            # One scored native call: a deterministic, GT-free rewrite of the
276            # question is supplied to caption-to-phrase grounding. Only the
277            # returned bbox is evaluated; no answer-label call is involved.
278            return PromptSpec(
279                native_referring_expression(sample),
280                128,
281                "bbox_native_question_grounding",
282            )
283        if model_key.startswith("paligemma2-"):
284            # PaliGemma's detector accepts a referring phrase rather than an
285            # interrogative. Rewrite question syntax without using its answer,
286            # then perform selection/localization in one generation.
287            expression = native_referring_expression(sample)
288            return PromptSpec(f"detect {expression}\n", 64, "bbox_native_question_grounding")
289        if model_key in {"molmo-7b-d", "molmoe-1b"}:
290            return PromptSpec(
291                _molmo_bbox_instruction(question),
292                96,
293                "bbox_molmo_percent_100",
294            )
295        if model_key in {"internvl3.5-1b", "internvl3.5-14b"}:
296            return PromptSpec(
297                _internvl_bbox_instruction(native_referring_expression(sample)),
298                96,
299                "bbox_native_internvl_grounding",
300            )
301        return PromptSpec(
302            _bbox_instruction(question),
303            96,
304            "bbox_json_normalized_1000",
305        )
306    raise ConfigError(
307        f"unsupported VQA question type: {sample.question_type}",
308        hint="use a type in the frozen VQA task contract",
309    )
@dataclass(frozen=True)
class PromptSpec:
12@dataclass(frozen=True)
13class PromptSpec:
14    text: str
15    max_new_tokens: int
16    output_kind: str
PromptSpec(text: str, max_new_tokens: int, output_kind: str)
text: str
max_new_tokens: int
output_kind: str
def display_question(question: str) -> str:
19def display_question(question: str) -> str:
20    """Hide catalog encoding without changing the stored ground truth label."""
21    return " ".join(question.replace("_", " ").split())

Hide catalog encoding without changing the stored ground truth label.

def native_referring_expression(sample: rpx_benchmark.vqa.VQASample) -> str:
24def native_referring_expression(sample: VQASample) -> str:
25    """Rewrite an RPX question into a native grounding expression.
26
27    Florence/PaliGemma grounding heads are trained on referring expressions,
28    not interrogative VQA syntax.  This transformation is deterministic and
29    uses only the public question/type -- never the answer or GT bbox -- so the
30    scored path remains one model generation.
31    """
32    question = display_question(sample.question).strip().rstrip("?")
33    lower = question.lower()
34    if sample.question_type == "spatial_lr_extreme":
35        if "left" in lower:
36            return "the leftmost object"
37        if "right" in lower:
38            return "the rightmost object"
39    if sample.question_type == "depth_closest" and "closest" in lower and "camera" in lower:
40        return "the object closest to the camera"
41    if sample.question_type == "spatial_farthest":
42        marker = "farthest from the "
43        if marker in lower:
44            return "the object farthest from " + question[lower.index(marker) + len(marker) :]
45    if sample.question_type == "inctx_spatial_farthest":
46        return "the object in image 2 farthest from the object shown in image 1"
47
48    # Normal attribute questions all begin with one of these stable forms.
49    for prefix in ("which object is ", "what is the object "):
50        if lower.startswith(prefix):
51            description = question[len(prefix) :]
52            if sample.question_type == "attr_single_color":
53                return "the " + description + " object"
54            return "the object " + description
55
56    # In-context questions already encode the complete reference relation;
57    # remove only the redundant request for output formatting and turn the
58    # interrogative into a grammatical relative clause.
59    suffix = "? What is its bounding box in Image 2"
60    if question.endswith(suffix):
61        question = question[: -len(suffix)]
62    prefix = "Which object in Image 2 "
63    if question.startswith(prefix):
64        relation = question[len(prefix) :]
65        if relation.startswith("has "):
66            relation = "that " + relation
67        elif relation.startswith("is "):
68            relation = "that " + relation
69        return "the object in image 2 " + relation
70    if question.lower().startswith("which object "):
71        return "the object " + question[len("Which object ") :]
72    return question

Rewrite an RPX question into a native grounding expression.

Florence/PaliGemma grounding heads are trained on referring expressions, not interrogative VQA syntax. This transformation is deterministic and uses only the public question/type -- never the answer or GT bbox -- so the scored path remains one model generation.

def build_semantic_diagnostic_prompt( sample: rpx_benchmark.vqa.VQASample, model_key: str) -> PromptSpec:
154def build_semantic_diagnostic_prompt(sample: VQASample, model_key: str) -> PromptSpec:
155    """Ask for the answer label only, without leaking the ground truth.
156
157    This is deliberately diagnostic rather than a scored benchmark protocol:
158    it separates target selection/reasoning from coordinate prediction.
159    """
160    question = display_question(sample.question)
161    if model_key.startswith("florence2-"):
162        # Florence task tokens are a closed native interface.  Extra formatting
163        # prose is treated as VQA question text and can be echoed by the model.
164        return PromptSpec(question, 32, "diagnostic_semantic_label")
165    if model_key.startswith("paligemma2-"):
166        # Follow the checkpoint's documented `answer {lang} {question}` form
167        # exactly.  Enforce short-answer formatting in the diagnostic parser.
168        return PromptSpec(f"answer en {question}\n", 32, "diagnostic_semantic_label")
169    instruction = (
170        f"{question}\nIdentify the one object that answers the question. "
171        "For a relational question, name the result object, not the reference "
172        "object named or shown in the question. Return only the shortest common "
173        "noun phrase naming that object: 1 to 5 words, with no sentence, article, "
174        "coordinates, Markdown, punctuation, or explanation."
175    )
176    return PromptSpec(instruction, 32, "diagnostic_semantic_label")

Ask for the answer label only, without leaking the ground truth.

This is deliberately diagnostic rather than a scored benchmark protocol: it separates target selection/reasoning from coordinate prediction.

def build_oracle_localization_prompt( sample: rpx_benchmark.vqa.VQASample, model_key: str) -> PromptSpec:
179def build_oracle_localization_prompt(sample: VQASample, model_key: str) -> PromptSpec:
180    """Localize the known GT label in the target image for diagnosis only."""
181    return build_label_localization_prompt(display_question(sample.answer), model_key, oracle=True)

Localize the known GT label in the target image for diagnosis only.

def build_label_localization_prompt( label: str, model_key: str, *, oracle: bool = False) -> PromptSpec:
184def build_label_localization_prompt(
185    label: str, model_key: str, *, oracle: bool = False
186) -> PromptSpec:
187    """Ground a label/referring phrase without canonicalizing its wording.
188
189    The predicted-label diagnostic deliberately passes the model's own answer
190    phrase through unchanged.  Correctness is determined spatially against the
191    target identity, so synonyms and harmless modifiers cannot create a false
192    semantic failure merely because their strings differ from the catalog.
193    """
194    label = display_question(label)
195    output_kind = "diagnostic_oracle_bbox" if oracle else "diagnostic_predicted_label_bbox"
196    if model_key.startswith("florence2-"):
197        # Florence's official phrase-grounding interface is TASK + phrase.
198        # Adding JSON instructions makes every noun in those instructions a
199        # grounding candidate and was the cause of the invalid smoke results.
200        return PromptSpec(label, 128, output_kind)
201    if model_key.startswith("paligemma2-"):
202        return PromptSpec(f"detect {label}\n", 64, output_kind)
203    if model_key.startswith("deepseek-vl2"):
204        return PromptSpec(f"<|ref|>{label}<|/ref|>.", 128, output_kind)
205    if model_key.startswith("cosmos-reason2-"):
206        return PromptSpec(
207            f'Locate the accurate bounding box of "{label}" in the target image. '
208            "Return only a JSON list in this exact form: "
209            '[{"bbox_2d":[x1,y1,x2,y2],"label":"object name"}]. '
210            "Normalize x and y independently from 0 to 1000 relative to the target "
211            "image. Use XYXY corner order. No Markdown, reasoning, or explanation.",
212            96,
213            output_kind,
214        )
215    if model_key in {"molmo-7b-d", "molmoe-1b"}:
216        # Molmo 0924 is natively trained to answer pointing prompts with
217        # <point>/<points> markup on a 0--100 coordinate grid.  This remains
218        # an explicitly unscored localization diagnostic: a point is never
219        # fabricated into a bbox or substituted for the scored RPX result.
220        return PromptSpec(
221            f"Point to the {label} in the image.",
222            64,
223            (
224                "diagnostic_oracle_point"
225                if oracle
226                else "diagnostic_predicted_label_point"
227            ),
228        )
229    return PromptSpec(
230        f'Locate the visible object named "{label}" in the target image. '
231        'Return only JSON: {"label":"object name","bbox":'
232        "[x_min,y_min,x_max,y_max]}. Normalize every bbox coordinate from 0 "
233        "to 1000 relative to the target-image width and height. Use XYXY corner "
234        "order and enclose the entire object. No Markdown or explanation.",
235        96,
236        output_kind,
237    )

Ground a label/referring phrase without canonicalizing its wording.

The predicted-label diagnostic deliberately passes the model's own answer phrase through unchanged. Correctness is determined spatially against the target identity, so synonyms and harmless modifiers cannot create a false semantic failure merely because their strings differ from the catalog.

def build_prompt( sample: rpx_benchmark.vqa.VQASample, model_key: str) -> PromptSpec:
240def build_prompt(sample: VQASample, model_key: str) -> PromptSpec:
241    question = display_question(sample.question)
242    if sample.question_type in BINARY_TYPES:
243        if model_key.startswith("paligemma2-"):
244            return PromptSpec(f"answer en {question}\n", 8, "binary")
245        return PromptSpec(
246            f"{question}\nAnswer using exactly one lowercase word: yes or no.", 4, "binary"
247        )
248    if sample.question_type in BBOX_TYPES:
249        if model_key.startswith("cosmos-reason2-"):
250            return PromptSpec(
251                _cosmos_reason2_bbox_instruction(question),
252                128,
253                "bbox_json_normalized_1000",
254            )
255        if model_key.startswith("deepseek-vl2"):
256            expression = native_referring_expression(sample)
257            if sample.is_in_context:
258                # DeepSeek-VL2 documents <|ref|> for ordinary single-image
259                # referring-expression localization, but its native
260                # multi-image/in-context grounding examples use the
261                # <|grounding|> mode.  Image 1 is already the hash-verified
262                # reference crop; Image 2 is the target scene.
263                return PromptSpec(
264                    "<|grounding|>The first image is a crop showing the reference "
265                    f"object. Locate only {expression} and return its grounding in "
266                    "the second image.",
267                    128,
268                    "bbox_native_question_grounding",
269                )
270            return PromptSpec(
271                f"<|ref|>{expression}<|/ref|>.",
272                128,
273                "bbox_native_question_grounding",
274            )
275        if model_key.startswith("florence2-"):
276            # One scored native call: a deterministic, GT-free rewrite of the
277            # question is supplied to caption-to-phrase grounding. Only the
278            # returned bbox is evaluated; no answer-label call is involved.
279            return PromptSpec(
280                native_referring_expression(sample),
281                128,
282                "bbox_native_question_grounding",
283            )
284        if model_key.startswith("paligemma2-"):
285            # PaliGemma's detector accepts a referring phrase rather than an
286            # interrogative. Rewrite question syntax without using its answer,
287            # then perform selection/localization in one generation.
288            expression = native_referring_expression(sample)
289            return PromptSpec(f"detect {expression}\n", 64, "bbox_native_question_grounding")
290        if model_key in {"molmo-7b-d", "molmoe-1b"}:
291            return PromptSpec(
292                _molmo_bbox_instruction(question),
293                96,
294                "bbox_molmo_percent_100",
295            )
296        if model_key in {"internvl3.5-1b", "internvl3.5-14b"}:
297            return PromptSpec(
298                _internvl_bbox_instruction(native_referring_expression(sample)),
299                96,
300                "bbox_native_internvl_grounding",
301            )
302        return PromptSpec(
303            _bbox_instruction(question),
304            96,
305            "bbox_json_normalized_1000",
306        )
307    raise ConfigError(
308        f"unsupported VQA question type: {sample.question_type}",
309        hint="use a type in the frozen VQA task contract",
310    )