maveryn commited on
Commit
5baf5ae
verified
1 Parent(s): c03659a

Show answer-only prompt

Browse files
Files changed (3) hide show
  1. README.md +57 -57
  2. app.py +307 -307
  3. trace_demo.py +364 -351
README.md CHANGED
@@ -1,58 +1,58 @@
1
- ---
2
- title: Trace Task Explorer
3
- emoji: 馃攷
4
- colorFrom: blue
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: "3.12"
9
- app_file: app.py
10
- pinned: false
11
- license: apache-2.0
12
- short_description: Generate and inspect 1,000 grounded visual-reasoning tasks.
13
- suggested_hardware: cpu-basic
14
- models:
15
- - maveryn/trace-qwen2.5-vl-3b
16
- - maveryn/trace-qwen2.5-vl-7b
17
- datasets:
18
- - maveryn/trace
19
- tags:
20
- - visual-reasoning
21
- - synthetic-data
22
- - reinforcement-learning
23
- - rlvr
24
- - multimodal
25
- ---
26
-
27
- # Trace Task Explorer
28
-
29
- This Space generates any of Trace's 1,000 deterministic, grounded
30
  visual-reasoning tasks. Select a domain, scene, task, and seed to inspect the
31
- rendered image, prompt, typed supervision, public annotation overlay, exact
32
- reward contract, and sanitized public execution trace.
33
-
34
- The Space is CPU-only and may need a short cold start. It accepts no uploads and
35
- stores no generated examples. The package dependency is pinned to the exact
36
- source revision shown in the **Reproduce** tab.
37
-
38
- - [Source repository](https://github.com/maveryn/trace)
39
- - [Documentation](https://maveryn.github.io/trace/)
40
- - [Paper](https://arxiv.org/abs/2607.19790)
41
- - [Dataset](https://huggingface.co/datasets/maveryn/trace)
42
- - [Hugging Face collection](https://huggingface.co/collections/maveryn/trace-6a604291b4be4ed6399b9f24)
43
- - [3B checkpoint](https://huggingface.co/maveryn/trace-qwen2.5-vl-3b)
44
- - [7B checkpoint](https://huggingface.co/maveryn/trace-qwen2.5-vl-7b)
45
-
46
- ## Citation
47
-
48
- ```bibtex
49
- @misc{alam2026trace,
50
- title = {Trace: A Taxonomy-Guided Environment for Multidomain Visual Reasoning},
51
- author = {Alam, Md Tanvirul},
52
- year = {2026},
53
- eprint = {2607.19790},
54
- archivePrefix = {arXiv},
55
- primaryClass = {cs.CV},
56
- url = {https://arxiv.org/abs/2607.19790}
57
- }
58
- ```
 
1
+ ---
2
+ title: Trace Task Explorer
3
+ emoji: 馃攷
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.20.0
8
+ python_version: "3.12"
9
+ app_file: app.py
10
+ pinned: false
11
+ license: apache-2.0
12
+ short_description: Generate and inspect 1,000 grounded visual-reasoning tasks.
13
+ suggested_hardware: cpu-basic
14
+ models:
15
+ - maveryn/trace-qwen2.5-vl-3b
16
+ - maveryn/trace-qwen2.5-vl-7b
17
+ datasets:
18
+ - maveryn/trace
19
+ tags:
20
+ - visual-reasoning
21
+ - synthetic-data
22
+ - reinforcement-learning
23
+ - rlvr
24
+ - multimodal
25
+ ---
26
+
27
+ # Trace Task Explorer
28
+
29
+ This Space generates any of Trace's 1,000 deterministic, grounded
30
  visual-reasoning tasks. Select a domain, scene, task, and seed to inspect the
31
+ rendered image, answer-mode prompt, typed supervision, public annotation
32
+ overlay, exact reward contract, and sanitized public execution trace.
33
+
34
+ The Space is CPU-only and may need a short cold start. It accepts no uploads and
35
+ stores no generated examples. The package dependency is pinned to the exact
36
+ source revision shown in the **Reproduce** tab.
37
+
38
+ - [Source repository](https://github.com/maveryn/trace)
39
+ - [Documentation](https://maveryn.github.io/trace/)
40
+ - [Paper](https://arxiv.org/abs/2607.19790)
41
+ - [Dataset](https://huggingface.co/datasets/maveryn/trace)
42
+ - [Hugging Face collection](https://huggingface.co/collections/maveryn/trace-6a604291b4be4ed6399b9f24)
43
+ - [3B checkpoint](https://huggingface.co/maveryn/trace-qwen2.5-vl-3b)
44
+ - [7B checkpoint](https://huggingface.co/maveryn/trace-qwen2.5-vl-7b)
45
+
46
+ ## Citation
47
+
48
+ ```bibtex
49
+ @misc{alam2026trace,
50
+ title = {Trace: A Taxonomy-Guided Environment for Multidomain Visual Reasoning},
51
+ author = {Alam, Md Tanvirul},
52
+ year = {2026},
53
+ eprint = {2607.19790},
54
+ archivePrefix = {arXiv},
55
+ primaryClass = {cs.CV},
56
+ url = {https://arxiv.org/abs/2607.19790}
57
+ }
58
+ ```
app.py CHANGED
@@ -1,311 +1,311 @@
1
- """Gradio entry point for the public Trace task explorer."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
-
7
- os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
8
-
9
- import gradio as gr
10
-
11
- from trace_demo import (
12
- DEFAULT_DOMAIN,
13
- DEFAULT_SCENE_ID,
14
- DEFAULT_SEED,
15
- DEFAULT_TASK_ID,
16
- MAX_SEED,
17
- build_catalog,
18
- generate_demo,
19
- load_presets,
20
- sample_random_selection,
21
- )
22
-
23
- CATALOG = build_catalog()
24
- PRESETS = load_presets()
25
-
26
- _CSS = """
27
- .trace-shell {max-width: 1440px; margin: 0 auto;}
28
- .trace-kicker {letter-spacing: .12em; text-transform: uppercase; font-size: .78rem;
29
- color: var(--body-text-color-subdued);}
30
- .trace-title h1 {margin-bottom: .25rem;}
31
- .trace-title p {max-width: 900px; font-size: 1.02rem;}
32
- .trace-stat {border: 1px solid var(--border-color-primary); border-radius: 12px;
33
- padding: .8rem 1rem; background: var(--background-fill-secondary);}
34
- .trace-stat strong {font-size: 1.35rem; display: block;}
35
- .trace-run {min-height: 48px;}
36
- .trace-note {font-size: .9rem; color: var(--body-text-color-subdued);}
37
- """
38
-
39
-
40
- def _scene_update(domain: str):
41
- scenes = CATALOG.scenes(domain)
42
- scene_id = scenes[0]
43
- return (
44
- gr.Dropdown(choices=list(scenes), value=scene_id),
45
- gr.Dropdown(
46
- choices=list(CATALOG.tasks(domain, scene_id)),
47
- value=CATALOG.tasks(domain, scene_id)[0],
48
- ),
49
- )
50
-
51
-
52
- def _task_update(domain: str, scene_id: str):
53
- tasks = CATALOG.tasks(domain, scene_id)
54
- return gr.Dropdown(choices=list(tasks), value=tasks[0])
55
-
56
-
57
- def _preset_update(preset_index: str):
58
- try:
59
- preset = PRESETS[int(preset_index)]
60
- except (IndexError, TypeError, ValueError) as exc:
61
- raise gr.Error("Choose one of the curated Trace presets.") from exc
62
- return (
63
- gr.Dropdown(choices=list(CATALOG.domains), value=preset.domain),
64
- gr.Dropdown(
65
- choices=list(CATALOG.scenes(preset.domain)),
66
- value=preset.scene_id,
67
- ),
68
- gr.Dropdown(
69
- choices=list(CATALOG.tasks(preset.domain, preset.scene_id)),
70
- value=preset.task_id,
71
- ),
72
- preset.seed,
73
- )
74
-
75
-
76
- def _run_generation(task_id: str, seed: int):
77
- try:
78
- result = generate_demo(task_id, seed, catalog=CATALOG)
79
- except (KeyError, TypeError, ValueError, RuntimeError) as exc:
80
- raise gr.Error(f"Trace could not generate that selection: {str(exc)[:300]}") from exc
81
- return (
82
- result.original_image,
83
- result.annotation_overlay,
84
- result.prompt,
85
- result.ground_truth,
86
- result.reward_contract,
87
- result.trace_summary,
88
- result.public_trace,
89
- result.reproduction,
90
- result.links_markdown,
91
- )
92
-
93
-
94
- def _random_question():
95
- selection = sample_random_selection(CATALOG)
96
- return (
97
- gr.Dropdown(
98
- choices=list(CATALOG.domains),
99
- value=selection.domain,
100
- ),
101
- gr.Dropdown(
102
- choices=list(CATALOG.scenes(selection.domain)),
103
- value=selection.scene_id,
104
- ),
105
- gr.Dropdown(
106
- choices=list(CATALOG.tasks(selection.domain, selection.scene_id)),
107
- value=selection.task_id,
108
- ),
109
- selection.seed,
110
- *_run_generation(selection.task_id, selection.seed),
111
- )
112
-
113
-
114
- with gr.Blocks(
115
- title="Trace 路 Grounded visual reasoning",
116
- analytics_enabled=False,
117
- fill_width=True,
118
- ) as demo:
119
- with gr.Column(elem_classes="trace-shell"):
120
- gr.HTML('<div class="trace-kicker">Grounded visual reasoning 路 deterministic by design</div>')
121
- gr.Markdown(
122
- """
123
- # Explore Trace
124
-
125
- Generate any of Trace's **1,000 tasks** across **277 scenes** and **11 visual
126
- domains**. Every image, prompt, typed answer, annotation, reward contract, and
127
- public execution trace comes from the same deterministic state.
128
- """,
129
- elem_classes="trace-title",
130
- )
131
-
132
- with gr.Row(equal_height=True):
133
- gr.HTML("<div class='trace-stat'><strong>1,000</strong>tasks</div>")
134
- gr.HTML("<div class='trace-stat'><strong>277</strong>scenes</div>")
135
- gr.HTML("<div class='trace-stat'><strong>11</strong>domains</div>")
136
-
137
- with gr.Row():
138
- with gr.Column(scale=7):
139
- with gr.Row():
140
- domain = gr.Dropdown(
141
- choices=list(CATALOG.domains),
142
- value=DEFAULT_DOMAIN,
143
- label="1 路 Domain",
144
- interactive=True,
145
- )
146
- scene_id = gr.Dropdown(
147
- choices=list(CATALOG.scenes(DEFAULT_DOMAIN)),
148
- value=DEFAULT_SCENE_ID,
149
- label="2 路 Scene",
150
- interactive=True,
151
- )
152
- task_id = gr.Dropdown(
153
- choices=list(CATALOG.tasks(DEFAULT_DOMAIN, DEFAULT_SCENE_ID)),
154
- value=DEFAULT_TASK_ID,
155
- label="3 路 Task (searchable)",
156
- filterable=True,
157
- interactive=True,
158
- )
159
- with gr.Column(scale=3):
160
- seed = gr.Number(
161
- value=DEFAULT_SEED,
162
- minimum=0,
163
- maximum=MAX_SEED,
164
- precision=0,
165
- label="Seed",
166
- interactive=True,
167
- )
168
- with gr.Row():
169
- randomize = gr.Button("Random question", variant="secondary")
170
- generate = gr.Button(
171
- "Generate task",
172
- variant="primary",
173
- elem_classes="trace-run",
174
- )
175
- gr.Markdown(
176
- "Inputs are limited to a registered task and integer seed. "
177
- "Generation uses `max_attempts=100`.",
178
- elem_classes="trace-note",
179
- )
180
-
181
- preset = gr.Dropdown(
182
- choices=[
183
- (item.label, str(index))
184
- for index, item in enumerate(PRESETS)
185
- ],
186
- value=None,
187
- label="Curated gallery 路 22 deterministic presets, two per domain",
188
- filterable=True,
189
- interactive=True,
190
- )
191
-
192
- with gr.Tabs():
193
- with gr.Tab("Problem"):
194
- with gr.Row():
195
- original_image = gr.Image(
196
- label="Generated image",
197
- type="pil",
198
- format="png",
199
- interactive=False,
200
- )
201
- annotation_overlay = gr.Image(
202
- label="Public annotation overlay",
203
- type="pil",
204
- format="png",
205
- interactive=False,
206
- )
207
  prompt = gr.Textbox(
208
- label="Prompt",
209
  lines=4,
210
  interactive=False,
211
  buttons=["copy"],
212
- )
213
- with gr.Tab("Ground truth"):
214
- with gr.Row():
215
- ground_truth = gr.JSON(label="Typed answer and annotation")
216
- reward_contract = gr.JSON(label="Reward contract")
217
- with gr.Tab("Execution trace"):
218
- with gr.Row():
219
- trace_summary = gr.JSON(label="Trace summary")
220
- public_trace = gr.JSON(
221
- label="Full public trace",
222
- open=False,
223
- )
224
- with gr.Tab("Reproduce"):
225
- reproduction = gr.Code(
226
- label="Exact-revision reproduction",
227
- language="shell",
228
- interactive=False,
229
- lines=13,
230
- )
231
-
232
- links = gr.Markdown(
233
- "Choose a task and seed, then select **Generate task**.",
234
- )
235
- gr.Markdown(
236
- """
237
- Trace uses metadata contracts鈥攏ot pixels鈥攁s verifier ground truth. The overlay
238
- is an inspection aid; the typed payload and reward contract are authoritative.
239
-
240
- [GitHub](https://github.com/maveryn/trace) 路
241
- [Documentation](https://maveryn.github.io/trace/) 路
242
- [Dataset](https://huggingface.co/datasets/maveryn/trace) 路
243
- [Paper](https://arxiv.org/abs/2607.19790)
244
- """,
245
- elem_classes="trace-note",
246
- )
247
-
248
- domain.input(
249
- _scene_update,
250
- inputs=domain,
251
- outputs=[scene_id, task_id],
252
- api_name=False,
253
- concurrency_limit=1,
254
- )
255
- scene_id.input(
256
- _task_update,
257
- inputs=[domain, scene_id],
258
- outputs=task_id,
259
- api_name=False,
260
- concurrency_limit=1,
261
- )
262
- randomize.click(
263
- _random_question,
264
- outputs=[
265
- domain,
266
- scene_id,
267
- task_id,
268
- seed,
269
- original_image,
270
- annotation_overlay,
271
- prompt,
272
- ground_truth,
273
- reward_contract,
274
- trace_summary,
275
- public_trace,
276
- reproduction,
277
- links,
278
- ],
279
- api_name=False,
280
- concurrency_limit=1,
281
- )
282
- preset.change(
283
- _preset_update,
284
- inputs=preset,
285
- outputs=[domain, scene_id, task_id, seed],
286
- api_name=False,
287
- concurrency_limit=1,
288
- )
289
- generate.click(
290
- _run_generation,
291
- inputs=[task_id, seed],
292
- outputs=[
293
- original_image,
294
- annotation_overlay,
295
- prompt,
296
- ground_truth,
297
- reward_contract,
298
- trace_summary,
299
- public_trace,
300
- reproduction,
301
- links,
302
- ],
303
- api_name=False,
304
- concurrency_limit=1,
305
- )
306
-
307
- demo.queue(default_concurrency_limit=1, max_size=32)
308
-
309
-
310
- if __name__ == "__main__":
311
- demo.launch(css=_CSS, footer_links=[])
 
1
+ """Gradio entry point for the public Trace task explorer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
8
+
9
+ import gradio as gr
10
+
11
+ from trace_demo import (
12
+ DEFAULT_DOMAIN,
13
+ DEFAULT_SCENE_ID,
14
+ DEFAULT_SEED,
15
+ DEFAULT_TASK_ID,
16
+ MAX_SEED,
17
+ build_catalog,
18
+ generate_demo,
19
+ load_presets,
20
+ sample_random_selection,
21
+ )
22
+
23
+ CATALOG = build_catalog()
24
+ PRESETS = load_presets()
25
+
26
+ _CSS = """
27
+ .trace-shell {max-width: 1440px; margin: 0 auto;}
28
+ .trace-kicker {letter-spacing: .12em; text-transform: uppercase; font-size: .78rem;
29
+ color: var(--body-text-color-subdued);}
30
+ .trace-title h1 {margin-bottom: .25rem;}
31
+ .trace-title p {max-width: 900px; font-size: 1.02rem;}
32
+ .trace-stat {border: 1px solid var(--border-color-primary); border-radius: 12px;
33
+ padding: .8rem 1rem; background: var(--background-fill-secondary);}
34
+ .trace-stat strong {font-size: 1.35rem; display: block;}
35
+ .trace-run {min-height: 48px;}
36
+ .trace-note {font-size: .9rem; color: var(--body-text-color-subdued);}
37
+ """
38
+
39
+
40
+ def _scene_update(domain: str):
41
+ scenes = CATALOG.scenes(domain)
42
+ scene_id = scenes[0]
43
+ return (
44
+ gr.Dropdown(choices=list(scenes), value=scene_id),
45
+ gr.Dropdown(
46
+ choices=list(CATALOG.tasks(domain, scene_id)),
47
+ value=CATALOG.tasks(domain, scene_id)[0],
48
+ ),
49
+ )
50
+
51
+
52
+ def _task_update(domain: str, scene_id: str):
53
+ tasks = CATALOG.tasks(domain, scene_id)
54
+ return gr.Dropdown(choices=list(tasks), value=tasks[0])
55
+
56
+
57
+ def _preset_update(preset_index: str):
58
+ try:
59
+ preset = PRESETS[int(preset_index)]
60
+ except (IndexError, TypeError, ValueError) as exc:
61
+ raise gr.Error("Choose one of the curated Trace presets.") from exc
62
+ return (
63
+ gr.Dropdown(choices=list(CATALOG.domains), value=preset.domain),
64
+ gr.Dropdown(
65
+ choices=list(CATALOG.scenes(preset.domain)),
66
+ value=preset.scene_id,
67
+ ),
68
+ gr.Dropdown(
69
+ choices=list(CATALOG.tasks(preset.domain, preset.scene_id)),
70
+ value=preset.task_id,
71
+ ),
72
+ preset.seed,
73
+ )
74
+
75
+
76
+ def _run_generation(task_id: str, seed: int):
77
+ try:
78
+ result = generate_demo(task_id, seed, catalog=CATALOG)
79
+ except (KeyError, TypeError, ValueError, RuntimeError) as exc:
80
+ raise gr.Error(f"Trace could not generate that selection: {str(exc)[:300]}") from exc
81
+ return (
82
+ result.original_image,
83
+ result.annotation_overlay,
84
+ result.prompt,
85
+ result.ground_truth,
86
+ result.reward_contract,
87
+ result.trace_summary,
88
+ result.public_trace,
89
+ result.reproduction,
90
+ result.links_markdown,
91
+ )
92
+
93
+
94
+ def _random_question():
95
+ selection = sample_random_selection(CATALOG)
96
+ return (
97
+ gr.Dropdown(
98
+ choices=list(CATALOG.domains),
99
+ value=selection.domain,
100
+ ),
101
+ gr.Dropdown(
102
+ choices=list(CATALOG.scenes(selection.domain)),
103
+ value=selection.scene_id,
104
+ ),
105
+ gr.Dropdown(
106
+ choices=list(CATALOG.tasks(selection.domain, selection.scene_id)),
107
+ value=selection.task_id,
108
+ ),
109
+ selection.seed,
110
+ *_run_generation(selection.task_id, selection.seed),
111
+ )
112
+
113
+
114
+ with gr.Blocks(
115
+ title="Trace 路 Grounded visual reasoning",
116
+ analytics_enabled=False,
117
+ fill_width=True,
118
+ ) as demo:
119
+ with gr.Column(elem_classes="trace-shell"):
120
+ gr.HTML('<div class="trace-kicker">Grounded visual reasoning 路 deterministic by design</div>')
121
+ gr.Markdown(
122
+ """
123
+ # Explore Trace
124
+
125
+ Generate any of Trace's **1,000 tasks** across **277 scenes** and **11 visual
126
+ domains**. Every image, prompt, typed answer, annotation, reward contract, and
127
+ public execution trace comes from the same deterministic state.
128
+ """,
129
+ elem_classes="trace-title",
130
+ )
131
+
132
+ with gr.Row(equal_height=True):
133
+ gr.HTML("<div class='trace-stat'><strong>1,000</strong>tasks</div>")
134
+ gr.HTML("<div class='trace-stat'><strong>277</strong>scenes</div>")
135
+ gr.HTML("<div class='trace-stat'><strong>11</strong>domains</div>")
136
+
137
+ with gr.Row():
138
+ with gr.Column(scale=7):
139
+ with gr.Row():
140
+ domain = gr.Dropdown(
141
+ choices=list(CATALOG.domains),
142
+ value=DEFAULT_DOMAIN,
143
+ label="1 路 Domain",
144
+ interactive=True,
145
+ )
146
+ scene_id = gr.Dropdown(
147
+ choices=list(CATALOG.scenes(DEFAULT_DOMAIN)),
148
+ value=DEFAULT_SCENE_ID,
149
+ label="2 路 Scene",
150
+ interactive=True,
151
+ )
152
+ task_id = gr.Dropdown(
153
+ choices=list(CATALOG.tasks(DEFAULT_DOMAIN, DEFAULT_SCENE_ID)),
154
+ value=DEFAULT_TASK_ID,
155
+ label="3 路 Task (searchable)",
156
+ filterable=True,
157
+ interactive=True,
158
+ )
159
+ with gr.Column(scale=3):
160
+ seed = gr.Number(
161
+ value=DEFAULT_SEED,
162
+ minimum=0,
163
+ maximum=MAX_SEED,
164
+ precision=0,
165
+ label="Seed",
166
+ interactive=True,
167
+ )
168
+ with gr.Row():
169
+ randomize = gr.Button("Random question", variant="secondary")
170
+ generate = gr.Button(
171
+ "Generate task",
172
+ variant="primary",
173
+ elem_classes="trace-run",
174
+ )
175
+ gr.Markdown(
176
+ "Inputs are limited to a registered task and integer seed. "
177
+ "Generation uses `max_attempts=100`.",
178
+ elem_classes="trace-note",
179
+ )
180
+
181
+ preset = gr.Dropdown(
182
+ choices=[
183
+ (item.label, str(index))
184
+ for index, item in enumerate(PRESETS)
185
+ ],
186
+ value=None,
187
+ label="Curated gallery 路 22 deterministic presets, two per domain",
188
+ filterable=True,
189
+ interactive=True,
190
+ )
191
+
192
+ with gr.Tabs():
193
+ with gr.Tab("Problem"):
194
+ with gr.Row():
195
+ original_image = gr.Image(
196
+ label="Generated image",
197
+ type="pil",
198
+ format="png",
199
+ interactive=False,
200
+ )
201
+ annotation_overlay = gr.Image(
202
+ label="Public annotation overlay",
203
+ type="pil",
204
+ format="png",
205
+ interactive=False,
206
+ )
207
  prompt = gr.Textbox(
208
+ label="Answer prompt",
209
  lines=4,
210
  interactive=False,
211
  buttons=["copy"],
212
+ )
213
+ with gr.Tab("Ground truth"):
214
+ with gr.Row():
215
+ ground_truth = gr.JSON(label="Typed answer and annotation")
216
+ reward_contract = gr.JSON(label="Reward contract")
217
+ with gr.Tab("Execution trace"):
218
+ with gr.Row():
219
+ trace_summary = gr.JSON(label="Trace summary")
220
+ public_trace = gr.JSON(
221
+ label="Full public trace",
222
+ open=False,
223
+ )
224
+ with gr.Tab("Reproduce"):
225
+ reproduction = gr.Code(
226
+ label="Exact-revision reproduction",
227
+ language="shell",
228
+ interactive=False,
229
+ lines=13,
230
+ )
231
+
232
+ links = gr.Markdown(
233
+ "Choose a task and seed, then select **Generate task**.",
234
+ )
235
+ gr.Markdown(
236
+ """
237
+ Trace uses metadata contracts鈥攏ot pixels鈥攁s verifier ground truth. The overlay
238
+ is an inspection aid; the typed payload and reward contract are authoritative.
239
+
240
+ [GitHub](https://github.com/maveryn/trace) 路
241
+ [Documentation](https://maveryn.github.io/trace/) 路
242
+ [Dataset](https://huggingface.co/datasets/maveryn/trace) 路
243
+ [Paper](https://arxiv.org/abs/2607.19790)
244
+ """,
245
+ elem_classes="trace-note",
246
+ )
247
+
248
+ domain.input(
249
+ _scene_update,
250
+ inputs=domain,
251
+ outputs=[scene_id, task_id],
252
+ api_name=False,
253
+ concurrency_limit=1,
254
+ )
255
+ scene_id.input(
256
+ _task_update,
257
+ inputs=[domain, scene_id],
258
+ outputs=task_id,
259
+ api_name=False,
260
+ concurrency_limit=1,
261
+ )
262
+ randomize.click(
263
+ _random_question,
264
+ outputs=[
265
+ domain,
266
+ scene_id,
267
+ task_id,
268
+ seed,
269
+ original_image,
270
+ annotation_overlay,
271
+ prompt,
272
+ ground_truth,
273
+ reward_contract,
274
+ trace_summary,
275
+ public_trace,
276
+ reproduction,
277
+ links,
278
+ ],
279
+ api_name=False,
280
+ concurrency_limit=1,
281
+ )
282
+ preset.change(
283
+ _preset_update,
284
+ inputs=preset,
285
+ outputs=[domain, scene_id, task_id, seed],
286
+ api_name=False,
287
+ concurrency_limit=1,
288
+ )
289
+ generate.click(
290
+ _run_generation,
291
+ inputs=[task_id, seed],
292
+ outputs=[
293
+ original_image,
294
+ annotation_overlay,
295
+ prompt,
296
+ ground_truth,
297
+ reward_contract,
298
+ trace_summary,
299
+ public_trace,
300
+ reproduction,
301
+ links,
302
+ ],
303
+ api_name=False,
304
+ concurrency_limit=1,
305
+ )
306
+
307
+ demo.queue(default_concurrency_limit=1, max_size=32)
308
+
309
+
310
+ if __name__ == "__main__":
311
+ demo.launch(css=_CSS, footer_links=[])
trace_demo.py CHANGED
@@ -1,375 +1,388 @@
1
- """Framework-independent logic for the public Trace task explorer."""
2
-
3
- from __future__ import annotations
4
-
5
- from collections.abc import Mapping, Sequence
6
- from dataclasses import dataclass
7
- import json
8
- import math
9
- from numbers import Integral, Real
10
- from pathlib import Path
11
- import secrets
12
- from typing import Any
13
-
14
- from PIL import Image
15
-
16
- from trace_tasks import generate_task, list_task_ids
17
- from trace_tasks.core.annotation_sanitization import (
18
- sanitize_trace_payload_for_public_annotation,
19
- )
20
- from trace_tasks.core.reward_contracts import resolve_reward_contract
21
- from trace_tasks.core.source_layout_policy import parse_public_task_id
22
- from trace_tasks.core.taxonomy import ACTIVE_DOMAINS
23
-
24
- from overlay import render_annotation_overlay
25
-
26
- REPOSITORY_URL = "https://github.com/maveryn/trace"
27
- DOCUMENTATION_URL = "https://maveryn.github.io/trace/"
28
- DATASET_URL = "https://huggingface.co/datasets/maveryn/trace"
29
- SPACE_URL = "https://huggingface.co/spaces/maveryn/trace"
30
- COLAB_URL = (
31
- "https://colab.research.google.com/github/maveryn/trace/blob/main/"
32
- "examples/notebooks/trace_quickstart.ipynb"
33
- )
34
- PINNED_REVISION = "bb7fdd1fc8a0f8a2e3db7efe910a14e81d58feb7"
35
- DEFAULT_TASK_ID = "task_geometry__graph_paper__polygon_area_value"
36
- DEFAULT_DOMAIN = "geometry"
37
- DEFAULT_SCENE_ID = "graph_paper"
38
- DEFAULT_SEED = 42
39
  MAX_SEED = (1 << 53) - 1
40
  MAX_ATTEMPTS = 100
41
-
42
-
43
- @dataclass(frozen=True)
44
- class Preset:
45
- """One deterministic curated example."""
46
-
47
- domain: str
48
- scene_id: str
49
- task_id: str
50
- seed: int
51
-
52
- @property
53
- def label(self) -> str:
54
- objective = parse_public_task_id(self.task_id).objective_contract
55
- return f"{self.domain} 路 {self.scene_id} 路 {objective} seed {self.seed}"
56
-
57
-
58
- @dataclass(frozen=True)
59
- class TaskCatalog:
60
- """Cascading domain, scene, and task choices."""
61
-
62
- task_ids: tuple[str, ...]
63
- domains: tuple[str, ...]
64
- scenes_by_domain: dict[str, tuple[str, ...]]
65
- tasks_by_scene: dict[tuple[str, str], tuple[str, ...]]
66
-
67
- def scenes(self, domain: str) -> tuple[str, ...]:
68
- if domain not in self.scenes_by_domain:
69
- raise ValueError(f"unknown domain: {domain!r}")
70
- return self.scenes_by_domain[domain]
71
-
72
- def tasks(self, domain: str, scene_id: str) -> tuple[str, ...]:
73
- key = (domain, scene_id)
74
- if key not in self.tasks_by_scene:
75
- raise ValueError(f"unknown scene: {domain}/{scene_id}")
76
- return self.tasks_by_scene[key]
77
-
78
-
79
- @dataclass(frozen=True)
80
- class RandomSelection:
81
- """One uniformly sampled registered task and browser-safe seed."""
82
-
83
- domain: str
84
- scene_id: str
85
- task_id: str
86
- seed: int
87
-
88
-
89
- @dataclass(frozen=True)
90
- class DemoResult:
91
- """Serializable outputs shown by the Gradio wrapper."""
92
-
93
- original_image: Image.Image
94
- annotation_overlay: Image.Image
95
- prompt: str
96
- ground_truth: dict[str, Any]
97
- reward_contract: dict[str, Any]
98
- trace_summary: dict[str, Any]
99
- public_trace: dict[str, Any]
100
- reproduction: str
101
- links_markdown: str
102
-
103
-
104
- def build_catalog(task_ids: Sequence[str] | None = None) -> TaskCatalog:
105
- """Build deterministic cascading choices from the installed registry."""
106
-
107
- resolved_task_ids = tuple(task_ids if task_ids is not None else list_task_ids())
108
- if not resolved_task_ids:
109
- raise ValueError("Trace registry is empty")
110
- if len(set(resolved_task_ids)) != len(resolved_task_ids):
111
- raise ValueError("Trace registry contains duplicate task ids")
112
-
113
- mutable_scenes: dict[str, set[str]] = {}
114
- mutable_tasks: dict[tuple[str, str], list[str]] = {}
115
- for task_id in sorted(resolved_task_ids):
116
- parts = parse_public_task_id(task_id)
117
- mutable_scenes.setdefault(parts.domain, set()).add(parts.scene_id)
118
- mutable_tasks.setdefault((parts.domain, parts.scene_id), []).append(task_id)
119
-
120
- active = [domain for domain in ACTIVE_DOMAINS if domain in mutable_scenes]
121
- extras = sorted(set(mutable_scenes).difference(active))
122
- domains = tuple([*active, *extras])
123
- scenes = {
124
- domain: tuple(sorted(mutable_scenes[domain]))
125
- for domain in domains
126
- }
127
- tasks = {
128
- key: tuple(sorted(values))
129
- for key, values in sorted(mutable_tasks.items())
130
- }
131
- return TaskCatalog(
132
- task_ids=tuple(sorted(resolved_task_ids)),
133
- domains=domains,
134
- scenes_by_domain=scenes,
135
- tasks_by_scene=tasks,
136
- )
137
-
138
-
139
- def sample_random_selection(
140
- catalog: TaskCatalog | None = None,
141
- ) -> RandomSelection:
142
- """Sample uniformly from every registered task and choose a fresh seed."""
143
-
144
- resolved_catalog = catalog or build_catalog()
145
- task_id = secrets.choice(resolved_catalog.task_ids)
146
- parts = parse_public_task_id(task_id)
147
- return RandomSelection(
148
- domain=parts.domain,
149
- scene_id=parts.scene_id,
150
- task_id=task_id,
151
- seed=secrets.randbelow(MAX_SEED + 1),
152
- )
153
-
154
-
155
- def load_presets(path: Path | None = None) -> tuple[Preset, ...]:
156
- """Load curated deterministic examples bundled with the Space."""
157
-
158
- preset_path = path or Path(__file__).with_name("presets.json")
159
- payload = json.loads(preset_path.read_text(encoding="utf-8"))
160
- if payload.get("schema_version") != "trace_space_presets_v1":
161
- raise ValueError("unsupported Trace Space preset schema")
162
-
163
- presets: list[Preset] = []
164
- for raw in payload.get("presets", []):
165
- preset = Preset(
166
- domain=str(raw["domain"]),
167
- scene_id=str(raw["scene_id"]),
168
- task_id=str(raw["task_id"]),
169
- seed=validate_seed(raw["seed"]),
170
- )
171
- parts = parse_public_task_id(preset.task_id)
172
- if (parts.domain, parts.scene_id) != (preset.domain, preset.scene_id):
173
- raise ValueError(f"preset taxonomy mismatch: {preset.task_id}")
174
- presets.append(preset)
175
- if len(presets) != 22:
176
- raise ValueError(f"expected 22 curated presets, found {len(presets)}")
177
- return tuple(presets)
178
-
179
-
180
- def validate_seed(value: Any) -> int:
181
- """Return a browser-safe integer seed."""
182
-
183
- if isinstance(value, bool) or value is None:
184
- raise ValueError("seed must be an integer")
185
- if isinstance(value, Integral):
186
- seed = int(value)
187
- elif isinstance(value, Real) and math.isfinite(float(value)):
188
- if not float(value).is_integer():
189
- raise ValueError("seed must be an integer")
190
- seed = int(value)
191
- elif isinstance(value, str):
192
- normalized = value.strip()
193
- if not normalized or not normalized.isdecimal():
194
- raise ValueError("seed must be an integer")
195
- seed = int(normalized)
196
- else:
197
- raise ValueError("seed must be an integer")
198
- if seed < 0 or seed > MAX_SEED:
199
- raise ValueError(f"seed must be between 0 and {MAX_SEED}")
200
- return seed
201
-
202
-
203
- def generate_demo(
204
- task_id: str,
205
- seed: Any,
206
- *,
207
- catalog: TaskCatalog | None = None,
208
- ) -> DemoResult:
209
- """Generate one deterministic task and its public inspection payloads."""
210
-
211
- resolved_catalog = catalog or build_catalog()
212
- normalized_task_id = str(task_id).strip()
213
- if normalized_task_id not in set(resolved_catalog.task_ids):
214
- raise ValueError("choose a registered Trace task")
215
- normalized_seed = validate_seed(seed)
216
-
 
217
  output = generate_task(
218
  normalized_task_id,
219
  seed=normalized_seed,
220
  params={},
221
  max_attempts=MAX_ATTEMPTS,
222
  )
 
 
 
 
 
 
 
 
 
 
223
  answer_gt = json_safe(output.answer_gt.to_dict())
224
  annotation_gt = json_safe(output.annotation_gt.to_dict())
225
- reward_contract = resolve_reward_contract(
226
- answer_type=output.answer_gt.type,
227
- annotation_type=output.annotation_gt.type,
228
- ).to_dict()
229
- public_trace = sanitize_trace_payload_for_public_annotation(
230
- output.trace_payload,
231
- annotation_gt=output.annotation_gt,
232
- )
233
- public_trace = json_safe(public_trace)
234
- overlay = render_annotation_overlay(output.image, annotation_gt)
235
- parts = parse_public_task_id(normalized_task_id)
236
-
237
  query_spec = public_trace.get("query_spec", {})
238
  prompt_trace = query_spec if isinstance(query_spec, Mapping) else {}
 
 
 
 
 
 
 
 
 
 
239
  trace_summary = {
240
  "task_id": normalized_task_id,
241
- "taxonomy": {
242
- "domain": parts.domain,
243
- "scene_id": parts.scene_id,
244
- "objective_contract": parts.objective_contract,
245
- },
246
- "instance_seed": normalized_seed,
247
- "resolved_scene_id": output.scene_id,
248
- "query_id": output.query_id,
249
- "image": {
250
- "image_id": output.image_id,
251
- "width": output.image.width,
252
- "height": output.image.height,
253
  },
254
  "answer_type": output.answer_gt.type,
255
  "annotation_type": output.annotation_gt.type,
256
- "prompt_selection": {
257
- key: json_safe(prompt_trace[key])
258
- for key in (
259
- "template_id",
260
- "prompt_variant",
261
- "prompt_variant_active_key",
262
- )
263
- if key in prompt_trace
264
- },
265
  "task_versions": json_safe(output.task_versions),
266
  "trace_sections": sorted(public_trace),
267
- }
268
-
269
- source_path = (
270
- f"src/trace_tasks/tasks/{parts.domain}/{parts.scene_id}/"
271
- f"{parts.objective_contract}.py"
272
- )
273
- doc_path = f"docs/tasks/{parts.domain}/{parts.scene_id}/{normalized_task_id}.md"
274
- source_url = f"{REPOSITORY_URL}/blob/{PINNED_REVISION}/{source_path}"
275
- task_doc_url = f"{REPOSITORY_URL}/blob/{PINNED_REVISION}/{doc_path}"
276
- answer_preview = json.dumps(
277
- answer_gt["value"],
278
- ensure_ascii=False,
279
- separators=(",", ":"),
280
- sort_keys=True,
281
- ).replace("`", "'")
282
- if len(answer_preview) > 120:
283
- answer_preview = f"{answer_preview[:117]}..."
284
- links = (
285
- f"**Typed result** 路 answer `{answer_gt['type']}` = `{answer_preview}` 路 "
286
- f"annotation `{annotation_gt['type']}`\n\n"
287
- f"**Verifier** 路 `{reward_contract['answer']['id']}` + "
288
- f"`{reward_contract['annotation']['id']}`\n\n"
289
- f"Generated from [`{normalized_task_id}`]({task_doc_url}) at "
290
- f"[revision `{PINNED_REVISION[:7]}`]({source_url}). "
291
- f"[Documentation]({DOCUMENTATION_URL}) 路 "
292
- f"[Dataset]({DATASET_URL}) 路 [Colab]({COLAB_URL})"
293
- )
294
-
295
- reproduction = "\n".join(
296
- [
297
- "python -m pip install \\",
298
- ' "trace-tasks @ git+https://github.com/maveryn/trace.git'
299
- f'@{PINNED_REVISION}"',
300
- "",
301
- "python - <<'PY'",
302
- "from trace_tasks import generate_task",
303
- "",
304
- f'task_id = "{normalized_task_id}"',
305
- f"sample = generate_task(task_id, seed={normalized_seed}, max_attempts=100)",
306
- "sample.image.save('trace-example.png')",
307
- "print(sample.prompt)",
308
- "print(sample.answer_gt.to_dict())",
309
- "print(sample.annotation_gt.to_dict())",
310
- "PY",
311
- ]
312
- )
313
-
314
  return DemoResult(
315
  original_image=output.image.convert("RGB"),
316
  annotation_overlay=overlay,
317
- prompt=output.prompt,
318
  ground_truth={
319
  "answer_gt": answer_gt,
320
- "annotation_gt": annotation_gt,
321
- },
322
- reward_contract=json_safe(reward_contract),
323
- trace_summary=json_safe(trace_summary),
324
- public_trace=public_trace,
325
- reproduction=reproduction,
326
- links_markdown=links,
327
- )
328
-
329
-
330
- def json_safe(value: Any) -> Any:
331
- """Convert Trace payload values to strict JSON-compatible objects."""
332
-
333
- if value is None or isinstance(value, (str, bool)):
334
- return value
335
- if isinstance(value, Integral):
336
- return int(value)
337
- if isinstance(value, Real):
338
- number = float(value)
339
- if math.isfinite(number):
340
- return number
341
- return str(number)
342
- if isinstance(value, Mapping):
343
- return {str(key): json_safe(item) for key, item in value.items()}
344
- if isinstance(value, (list, tuple)):
345
- return [json_safe(item) for item in value]
346
- if isinstance(value, (set, frozenset)):
347
- return [json_safe(item) for item in sorted(value, key=str)]
348
- if hasattr(value, "to_dict"):
349
- return json_safe(value.to_dict())
350
- if hasattr(value, "tolist"):
351
- return json_safe(value.tolist())
352
- if hasattr(value, "item"):
353
- return json_safe(value.item())
354
- return str(value)
355
-
356
-
357
- __all__ = [
358
- "COLAB_URL",
359
- "DEFAULT_DOMAIN",
360
- "DEFAULT_SCENE_ID",
361
- "DEFAULT_SEED",
362
- "DEFAULT_TASK_ID",
363
- "DemoResult",
364
- "MAX_ATTEMPTS",
365
- "MAX_SEED",
366
- "PINNED_REVISION",
367
- "Preset",
368
- "SPACE_URL",
369
- "TaskCatalog",
370
- "build_catalog",
371
- "generate_demo",
372
- "json_safe",
373
- "load_presets",
374
- "validate_seed",
375
- ]
 
1
+ """Framework-independent logic for the public Trace task explorer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from dataclasses import dataclass
7
+ import json
8
+ import math
9
+ from numbers import Integral, Real
10
+ from pathlib import Path
11
+ import secrets
12
+ from typing import Any
13
+
14
+ from PIL import Image
15
+
16
+ from trace_tasks import generate_task, list_task_ids
17
+ from trace_tasks.core.annotation_sanitization import (
18
+ sanitize_trace_payload_for_public_annotation,
19
+ )
20
+ from trace_tasks.core.reward_contracts import resolve_reward_contract
21
+ from trace_tasks.core.source_layout_policy import parse_public_task_id
22
+ from trace_tasks.core.taxonomy import ACTIVE_DOMAINS
23
+
24
+ from overlay import render_annotation_overlay
25
+
26
+ REPOSITORY_URL = "https://github.com/maveryn/trace"
27
+ DOCUMENTATION_URL = "https://maveryn.github.io/trace/"
28
+ DATASET_URL = "https://huggingface.co/datasets/maveryn/trace"
29
+ SPACE_URL = "https://huggingface.co/spaces/maveryn/trace"
30
+ COLAB_URL = (
31
+ "https://colab.research.google.com/github/maveryn/trace/blob/main/"
32
+ "examples/notebooks/trace_quickstart.ipynb"
33
+ )
34
+ PINNED_REVISION = "bb7fdd1fc8a0f8a2e3db7efe910a14e81d58feb7"
35
+ DEFAULT_TASK_ID = "task_geometry__graph_paper__polygon_area_value"
36
+ DEFAULT_DOMAIN = "geometry"
37
+ DEFAULT_SCENE_ID = "graph_paper"
38
+ DEFAULT_SEED = 42
39
  MAX_SEED = (1 << 53) - 1
40
  MAX_ATTEMPTS = 100
41
+ DISPLAY_PROMPT_MODE = "answer_only"
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class Preset:
46
+ """One deterministic curated example."""
47
+
48
+ domain: str
49
+ scene_id: str
50
+ task_id: str
51
+ seed: int
52
+
53
+ @property
54
+ def label(self) -> str:
55
+ objective = parse_public_task_id(self.task_id).objective_contract
56
+ return f"{self.domain} 路 {self.scene_id} 路 {objective} 路 seed {self.seed}"
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class TaskCatalog:
61
+ """Cascading domain, scene, and task choices."""
62
+
63
+ task_ids: tuple[str, ...]
64
+ domains: tuple[str, ...]
65
+ scenes_by_domain: dict[str, tuple[str, ...]]
66
+ tasks_by_scene: dict[tuple[str, str], tuple[str, ...]]
67
+
68
+ def scenes(self, domain: str) -> tuple[str, ...]:
69
+ if domain not in self.scenes_by_domain:
70
+ raise ValueError(f"unknown domain: {domain!r}")
71
+ return self.scenes_by_domain[domain]
72
+
73
+ def tasks(self, domain: str, scene_id: str) -> tuple[str, ...]:
74
+ key = (domain, scene_id)
75
+ if key not in self.tasks_by_scene:
76
+ raise ValueError(f"unknown scene: {domain}/{scene_id}")
77
+ return self.tasks_by_scene[key]
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class RandomSelection:
82
+ """One uniformly sampled registered task and browser-safe seed."""
83
+
84
+ domain: str
85
+ scene_id: str
86
+ task_id: str
87
+ seed: int
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class DemoResult:
92
+ """Serializable outputs shown by the Gradio wrapper."""
93
+
94
+ original_image: Image.Image
95
+ annotation_overlay: Image.Image
96
+ prompt: str
97
+ ground_truth: dict[str, Any]
98
+ reward_contract: dict[str, Any]
99
+ trace_summary: dict[str, Any]
100
+ public_trace: dict[str, Any]
101
+ reproduction: str
102
+ links_markdown: str
103
+
104
+
105
+ def build_catalog(task_ids: Sequence[str] | None = None) -> TaskCatalog:
106
+ """Build deterministic cascading choices from the installed registry."""
107
+
108
+ resolved_task_ids = tuple(task_ids if task_ids is not None else list_task_ids())
109
+ if not resolved_task_ids:
110
+ raise ValueError("Trace registry is empty")
111
+ if len(set(resolved_task_ids)) != len(resolved_task_ids):
112
+ raise ValueError("Trace registry contains duplicate task ids")
113
+
114
+ mutable_scenes: dict[str, set[str]] = {}
115
+ mutable_tasks: dict[tuple[str, str], list[str]] = {}
116
+ for task_id in sorted(resolved_task_ids):
117
+ parts = parse_public_task_id(task_id)
118
+ mutable_scenes.setdefault(parts.domain, set()).add(parts.scene_id)
119
+ mutable_tasks.setdefault((parts.domain, parts.scene_id), []).append(task_id)
120
+
121
+ active = [domain for domain in ACTIVE_DOMAINS if domain in mutable_scenes]
122
+ extras = sorted(set(mutable_scenes).difference(active))
123
+ domains = tuple([*active, *extras])
124
+ scenes = {
125
+ domain: tuple(sorted(mutable_scenes[domain]))
126
+ for domain in domains
127
+ }
128
+ tasks = {
129
+ key: tuple(sorted(values))
130
+ for key, values in sorted(mutable_tasks.items())
131
+ }
132
+ return TaskCatalog(
133
+ task_ids=tuple(sorted(resolved_task_ids)),
134
+ domains=domains,
135
+ scenes_by_domain=scenes,
136
+ tasks_by_scene=tasks,
137
+ )
138
+
139
+
140
+ def sample_random_selection(
141
+ catalog: TaskCatalog | None = None,
142
+ ) -> RandomSelection:
143
+ """Sample uniformly from every registered task and choose a fresh seed."""
144
+
145
+ resolved_catalog = catalog or build_catalog()
146
+ task_id = secrets.choice(resolved_catalog.task_ids)
147
+ parts = parse_public_task_id(task_id)
148
+ return RandomSelection(
149
+ domain=parts.domain,
150
+ scene_id=parts.scene_id,
151
+ task_id=task_id,
152
+ seed=secrets.randbelow(MAX_SEED + 1),
153
+ )
154
+
155
+
156
+ def load_presets(path: Path | None = None) -> tuple[Preset, ...]:
157
+ """Load curated deterministic examples bundled with the Space."""
158
+
159
+ preset_path = path or Path(__file__).with_name("presets.json")
160
+ payload = json.loads(preset_path.read_text(encoding="utf-8"))
161
+ if payload.get("schema_version") != "trace_space_presets_v1":
162
+ raise ValueError("unsupported Trace Space preset schema")
163
+
164
+ presets: list[Preset] = []
165
+ for raw in payload.get("presets", []):
166
+ preset = Preset(
167
+ domain=str(raw["domain"]),
168
+ scene_id=str(raw["scene_id"]),
169
+ task_id=str(raw["task_id"]),
170
+ seed=validate_seed(raw["seed"]),
171
+ )
172
+ parts = parse_public_task_id(preset.task_id)
173
+ if (parts.domain, parts.scene_id) != (preset.domain, preset.scene_id):
174
+ raise ValueError(f"preset taxonomy mismatch: {preset.task_id}")
175
+ presets.append(preset)
176
+ if len(presets) != 22:
177
+ raise ValueError(f"expected 22 curated presets, found {len(presets)}")
178
+ return tuple(presets)
179
+
180
+
181
+ def validate_seed(value: Any) -> int:
182
+ """Return a browser-safe integer seed."""
183
+
184
+ if isinstance(value, bool) or value is None:
185
+ raise ValueError("seed must be an integer")
186
+ if isinstance(value, Integral):
187
+ seed = int(value)
188
+ elif isinstance(value, Real) and math.isfinite(float(value)):
189
+ if not float(value).is_integer():
190
+ raise ValueError("seed must be an integer")
191
+ seed = int(value)
192
+ elif isinstance(value, str):
193
+ normalized = value.strip()
194
+ if not normalized or not normalized.isdecimal():
195
+ raise ValueError("seed must be an integer")
196
+ seed = int(normalized)
197
+ else:
198
+ raise ValueError("seed must be an integer")
199
+ if seed < 0 or seed > MAX_SEED:
200
+ raise ValueError(f"seed must be between 0 and {MAX_SEED}")
201
+ return seed
202
+
203
+
204
+ def generate_demo(
205
+ task_id: str,
206
+ seed: Any,
207
+ *,
208
+ catalog: TaskCatalog | None = None,
209
+ ) -> DemoResult:
210
+ """Generate one deterministic task and its public inspection payloads."""
211
+
212
+ resolved_catalog = catalog or build_catalog()
213
+ normalized_task_id = str(task_id).strip()
214
+ if normalized_task_id not in set(resolved_catalog.task_ids):
215
+ raise ValueError("choose a registered Trace task")
216
+ normalized_seed = validate_seed(seed)
217
+
218
  output = generate_task(
219
  normalized_task_id,
220
  seed=normalized_seed,
221
  params={},
222
  max_attempts=MAX_ATTEMPTS,
223
  )
224
+ try:
225
+ display_prompt = output.prompt_variants[DISPLAY_PROMPT_MODE]
226
+ except KeyError as exc:
227
+ raise RuntimeError(
228
+ f"generated task is missing the {DISPLAY_PROMPT_MODE!r} prompt variant"
229
+ ) from exc
230
+ if not display_prompt.strip():
231
+ raise RuntimeError(
232
+ f"generated task has an empty {DISPLAY_PROMPT_MODE!r} prompt variant"
233
+ )
234
  answer_gt = json_safe(output.answer_gt.to_dict())
235
  annotation_gt = json_safe(output.annotation_gt.to_dict())
236
+ reward_contract = resolve_reward_contract(
237
+ answer_type=output.answer_gt.type,
238
+ annotation_type=output.annotation_gt.type,
239
+ ).to_dict()
240
+ public_trace = sanitize_trace_payload_for_public_annotation(
241
+ output.trace_payload,
242
+ annotation_gt=output.annotation_gt,
243
+ )
244
+ public_trace = json_safe(public_trace)
245
+ overlay = render_annotation_overlay(output.image, annotation_gt)
246
+ parts = parse_public_task_id(normalized_task_id)
247
+
248
  query_spec = public_trace.get("query_spec", {})
249
  prompt_trace = query_spec if isinstance(query_spec, Mapping) else {}
250
+ prompt_selection = {
251
+ key: json_safe(prompt_trace[key])
252
+ for key in (
253
+ "template_id",
254
+ "prompt_variant",
255
+ "prompt_variant_active_key",
256
+ )
257
+ if key in prompt_trace
258
+ }
259
+ prompt_selection["displayed_mode"] = DISPLAY_PROMPT_MODE
260
  trace_summary = {
261
  "task_id": normalized_task_id,
262
+ "taxonomy": {
263
+ "domain": parts.domain,
264
+ "scene_id": parts.scene_id,
265
+ "objective_contract": parts.objective_contract,
266
+ },
267
+ "instance_seed": normalized_seed,
268
+ "resolved_scene_id": output.scene_id,
269
+ "query_id": output.query_id,
270
+ "image": {
271
+ "image_id": output.image_id,
272
+ "width": output.image.width,
273
+ "height": output.image.height,
274
  },
275
  "answer_type": output.answer_gt.type,
276
  "annotation_type": output.annotation_gt.type,
277
+ "prompt_selection": prompt_selection,
 
 
 
 
 
 
 
 
278
  "task_versions": json_safe(output.task_versions),
279
  "trace_sections": sorted(public_trace),
280
+ }
281
+
282
+ source_path = (
283
+ f"src/trace_tasks/tasks/{parts.domain}/{parts.scene_id}/"
284
+ f"{parts.objective_contract}.py"
285
+ )
286
+ doc_path = f"docs/tasks/{parts.domain}/{parts.scene_id}/{normalized_task_id}.md"
287
+ source_url = f"{REPOSITORY_URL}/blob/{PINNED_REVISION}/{source_path}"
288
+ task_doc_url = f"{REPOSITORY_URL}/blob/{PINNED_REVISION}/{doc_path}"
289
+ answer_preview = json.dumps(
290
+ answer_gt["value"],
291
+ ensure_ascii=False,
292
+ separators=(",", ":"),
293
+ sort_keys=True,
294
+ ).replace("`", "'")
295
+ if len(answer_preview) > 120:
296
+ answer_preview = f"{answer_preview[:117]}..."
297
+ links = (
298
+ f"**Typed result** 路 answer `{answer_gt['type']}` = `{answer_preview}` 路 "
299
+ f"annotation `{annotation_gt['type']}`\n\n"
300
+ f"**Verifier** 路 `{reward_contract['answer']['id']}` + "
301
+ f"`{reward_contract['annotation']['id']}`\n\n"
302
+ f"Generated from [`{normalized_task_id}`]({task_doc_url}) at "
303
+ f"[revision `{PINNED_REVISION[:7]}`]({source_url}). "
304
+ f"[Documentation]({DOCUMENTATION_URL}) 路 "
305
+ f"[Dataset]({DATASET_URL}) 路 [Colab]({COLAB_URL})"
306
+ )
307
+
308
+ reproduction = "\n".join(
309
+ [
310
+ "python -m pip install \\",
311
+ ' "trace-tasks @ git+https://github.com/maveryn/trace.git'
312
+ f'@{PINNED_REVISION}"',
313
+ "",
314
+ "python - <<'PY'",
315
+ "from trace_tasks import generate_task",
316
+ "",
317
+ f'task_id = "{normalized_task_id}"',
318
+ f"sample = generate_task(task_id, seed={normalized_seed}, max_attempts=100)",
319
+ "sample.image.save('trace-example.png')",
320
+ "print(sample.prompt)",
321
+ "print(sample.answer_gt.to_dict())",
322
+ "print(sample.annotation_gt.to_dict())",
323
+ "PY",
324
+ ]
325
+ )
326
+
327
  return DemoResult(
328
  original_image=output.image.convert("RGB"),
329
  annotation_overlay=overlay,
330
+ prompt=display_prompt,
331
  ground_truth={
332
  "answer_gt": answer_gt,
333
+ "annotation_gt": annotation_gt,
334
+ },
335
+ reward_contract=json_safe(reward_contract),
336
+ trace_summary=json_safe(trace_summary),
337
+ public_trace=public_trace,
338
+ reproduction=reproduction,
339
+ links_markdown=links,
340
+ )
341
+
342
+
343
+ def json_safe(value: Any) -> Any:
344
+ """Convert Trace payload values to strict JSON-compatible objects."""
345
+
346
+ if value is None or isinstance(value, (str, bool)):
347
+ return value
348
+ if isinstance(value, Integral):
349
+ return int(value)
350
+ if isinstance(value, Real):
351
+ number = float(value)
352
+ if math.isfinite(number):
353
+ return number
354
+ return str(number)
355
+ if isinstance(value, Mapping):
356
+ return {str(key): json_safe(item) for key, item in value.items()}
357
+ if isinstance(value, (list, tuple)):
358
+ return [json_safe(item) for item in value]
359
+ if isinstance(value, (set, frozenset)):
360
+ return [json_safe(item) for item in sorted(value, key=str)]
361
+ if hasattr(value, "to_dict"):
362
+ return json_safe(value.to_dict())
363
+ if hasattr(value, "tolist"):
364
+ return json_safe(value.tolist())
365
+ if hasattr(value, "item"):
366
+ return json_safe(value.item())
367
+ return str(value)
368
+
369
+
370
+ __all__ = [
371
+ "COLAB_URL",
372
+ "DEFAULT_DOMAIN",
373
+ "DEFAULT_SCENE_ID",
374
+ "DEFAULT_SEED",
375
+ "DEFAULT_TASK_ID",
376
+ "DemoResult",
377
+ "MAX_ATTEMPTS",
378
+ "MAX_SEED",
379
+ "PINNED_REVISION",
380
+ "Preset",
381
+ "SPACE_URL",
382
+ "TaskCatalog",
383
+ "build_catalog",
384
+ "generate_demo",
385
+ "json_safe",
386
+ "load_presets",
387
+ "validate_seed",
388
+ ]