@lalalic/markcut 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/skills/markcut/SKILL.md +14 -17
- package/skills/markcut/docs/markdown-descriptive.md +1 -1
- package/skills/markcut/review.md +480 -0
- package/tests/evals/README.md +41 -0
- package/tests/evals/dataset.json +170 -0
- package/tests/evals/gen_dataset.py +63 -0
- package/tests/evals/metrics.py +70 -0
- package/tests/evals/openrouter_model.py +143 -0
- package/tests/evals/storyboard_app.py +55 -0
- package/tests/evals/test_storyboard.py +32 -0
- package/.vscode/settings.json +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lalalic/markcut",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"markcut": "bin/markcut"
|
|
@@ -65,5 +65,9 @@
|
|
|
65
65
|
"@types/react-dom": "^19.0.0",
|
|
66
66
|
"typescript": "^5.6.0",
|
|
67
67
|
"vitest": "^2.1.0"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/lalalic/markcut.git"
|
|
68
72
|
}
|
|
69
73
|
}
|
package/skills/markcut/SKILL.md
CHANGED
|
@@ -18,6 +18,13 @@ Everything video is a **stream tree** described with markdown. see [docs/markdow
|
|
|
18
18
|
|
|
19
19
|
- Use `scene` nodes to organize your video. Scenes can nest inside other scenes.
|
|
20
20
|
- Use `description`, `scene.instruction`, `script`, `image|video.prompt` to structure your video content.
|
|
21
|
+
- **If a scene has a `script` (or `audio`) plus one primary visual, the visual MUST be `isBackground:true`.** Without it the visual plays only its own duration (3s default for images) while narration continues — the rest of the scene is a black screen. Scene duration follows the audio, not the image.
|
|
22
|
+
|
|
23
|
+
```md
|
|
24
|
+
## scene-1
|
|
25
|
+
- image prompt:"..." isBackground:true # fills the whole scene, narration decides duration
|
|
26
|
+
- script "..."
|
|
27
|
+
```
|
|
21
28
|
|
|
22
29
|
see [docs/markdown-descriptive.md](docs/markdown-descriptive.md) for full details.
|
|
23
30
|
|
|
@@ -71,15 +78,12 @@ npx @lalalic/markcut spots --waypoints "lat,lng;lat,lng" # discover POIs along a
|
|
|
71
78
|
|
|
72
79
|
---
|
|
73
80
|
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- screenshot some key frames, and understand image to verify intent
|
|
81
|
-
- stt the final video audio, and verify if vtt result is correct
|
|
82
|
-
|
|
81
|
+
## review
|
|
82
|
+
use the review contract defined in [./review.md](./review.md) to guide the review process.
|
|
83
|
+
review as early as possible in the video production process to catch issues before they propagate.
|
|
84
|
+
* review md file
|
|
85
|
+
* review compiled.json
|
|
86
|
+
* review the rendered video
|
|
83
87
|
|
|
84
88
|
## Reference
|
|
85
89
|
|
|
@@ -89,8 +93,7 @@ some common issues (photo or video can't be displayed, audio missing), take belo
|
|
|
89
93
|
| Route / vlog map clips (effects, spots, examples) | [docs/map-dynamic-camera.md](docs/map-dynamic-camera.md) |
|
|
90
94
|
| Built-in components & common npm packages | [docs/components.md](docs/components.md) |
|
|
91
95
|
| Sound effects | [docs/sound-effects.md](docs/sound-effects.md) |
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
| Review contract | [./review.md](./review.md) |
|
|
94
97
|
|
|
95
98
|
## Built-in Components
|
|
96
99
|
|
|
@@ -98,12 +101,6 @@ Built-in components available via `@lalalic/markcut/components`. See [docs/compo
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
## Golden rule
|
|
101
|
-
- always check stream start and duration to avoid
|
|
102
|
-
- audio cut off
|
|
103
|
-
- video cut off
|
|
104
|
-
- subtitle mismatch
|
|
105
|
-
- sync issues between audio, video, and subtitles
|
|
106
|
-
according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
|
|
107
104
|
- put all manual assets in `assets` folder, such as bgm, logo, watermark, etc. don't put them in `.markcut` folder, which is auto generated and will be deleted when `markcut clean` command is run.
|
|
108
105
|
|
|
109
106
|
### Don'ts
|
|
@@ -705,7 +705,7 @@ tween(#000, #FFF) — color interpolation
|
|
|
705
705
|
# Best practices
|
|
706
706
|
- Markcut engine will automatically determine the duration of background streams, video, script audio. Don't set duration for them if no special requirements (e.g., speed up/down on purpose).
|
|
707
707
|
- global streams (BGM, Logo, Lip-sync video,...) should be set on root level, and set `isBackground:true` to let it loop to fill the whole video duration.
|
|
708
|
-
- Set `isBackground:true` for static vision content
|
|
708
|
+
- **Set `isBackground:true` for static vision content (image) whenever the scene has audio/script.** Scene duration is driven by the audio (which can be much longer than the image's default 3s); without `isBackground` the image ends early and the scene tail plays over a black screen while narration continues.
|
|
709
709
|
- adjust audio start time with `start` property to avoid audio cut when transition effect is applied on vision scene
|
|
710
710
|
|
|
711
711
|
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# Markcut Skill — Agent Video Review Prompt
|
|
2
|
+
|
|
3
|
+
You are an expert reviewer evaluating a video generated by an LLM using the Markcut skill.
|
|
4
|
+
|
|
5
|
+
Your goal is to determine whether the **skill enabled the LLM to make good video-authoring decisions** and whether the resulting video satisfies the user's intent.
|
|
6
|
+
|
|
7
|
+
You will receive:
|
|
8
|
+
|
|
9
|
+
- **USER_REQUEST** — the original user's request
|
|
10
|
+
- **VIDEO_MD** — the Markdown generated by the agent
|
|
11
|
+
- **VIDEO** — the rendered video
|
|
12
|
+
|
|
13
|
+
Review the rendered video as the primary artifact. Use the request to understand intent and the Markdown to understand authoring decisions.
|
|
14
|
+
|
|
15
|
+
## Review workflow
|
|
16
|
+
|
|
17
|
+
1. Read the user's request and identify the purpose, audience, platform, duration, style, and required information.
|
|
18
|
+
2. Read the generated Markdown and understand its scenes, media, narration, timing, layouts, subtitles, and effects.
|
|
19
|
+
3. Watch the **entire rendered video** or compiled.json before making a final judgment if available.
|
|
20
|
+
4. Compare the video with the user's intent.
|
|
21
|
+
5. Identify only meaningful, observable problems.
|
|
22
|
+
6. For each problem, determine whether it is caused by AUTHORING, CONTENT, DESIGN, ENGINE, ENVIRONMENT, or is UNKNOWN.
|
|
23
|
+
7. For AUTHORING problems, provide a concrete rule that could improve the Markcut skill.
|
|
24
|
+
|
|
25
|
+
Do not judge the Markdown alone. A technically valid Markdown file can still produce a poor video.
|
|
26
|
+
|
|
27
|
+
Do not invent problems. Do not report personal stylistic preferences. Prefer a small number of high-confidence, actionable findings over a long list of subjective observations.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# Critical Markcut Checks
|
|
32
|
+
|
|
33
|
+
The following checks are especially important because they represent common failures observed when agents generate Markcut video Markdown.
|
|
34
|
+
|
|
35
|
+
## 1. No dark scene tail
|
|
36
|
+
|
|
37
|
+
**Problem:** A scene becomes black near the end while narration continues.
|
|
38
|
+
|
|
39
|
+
Typical symptom:
|
|
40
|
+
|
|
41
|
+
- the scene image/video ends before the scene narration;
|
|
42
|
+
- the final frames of the scene are black or empty;
|
|
43
|
+
- narration continues over the black tail.
|
|
44
|
+
|
|
45
|
+
This is a high-priority timing/authoring problem because the viewer sees an unintended blank screen while still hearing narration.
|
|
46
|
+
|
|
47
|
+
**Preferred authoring pattern:** When a scene has one primary visual that should remain visible for the whole scene, make the scene image a background using `isBackground` so it plays for the entire scene.
|
|
48
|
+
|
|
49
|
+
**Review question:**
|
|
50
|
+
|
|
51
|
+
> Does the visual remain intentionally present for the entire narration, especially at the end of the scene?
|
|
52
|
+
|
|
53
|
+
If the narration continues after the visible content has ended, report the issue.
|
|
54
|
+
|
|
55
|
+
Typical classification:
|
|
56
|
+
|
|
57
|
+
- `TIMING-001` or `TIMING-003`
|
|
58
|
+
- source: `AUTHORING`
|
|
59
|
+
|
|
60
|
+
Do not report this if the black/empty tail is clearly intentional and appropriate.
|
|
61
|
+
|
|
62
|
+
## 2. Every scene should have an intentional visual
|
|
63
|
+
|
|
64
|
+
**Problem:** A whole scene is black because the scene contains narration but no image/video/component that produces visible content.
|
|
65
|
+
|
|
66
|
+
Typical symptom:
|
|
67
|
+
|
|
68
|
+
```md
|
|
69
|
+
## Scene
|
|
70
|
+
|
|
71
|
+
- script: ...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
with no visual content.
|
|
75
|
+
|
|
76
|
+
For normal video scenes, the default expectation is that narration should have a meaningful visual supporting it.
|
|
77
|
+
|
|
78
|
+
**Preferred authoring pattern:** Add an image prompt when appropriate, for example:
|
|
79
|
+
|
|
80
|
+
```md
|
|
81
|
+
- image prompt: "..."
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The prompt should describe a visual that supports the narration, not merely repeat the narration.
|
|
85
|
+
|
|
86
|
+
**Review question:**
|
|
87
|
+
|
|
88
|
+
> Is any scene unintentionally blank because the agent forgot to provide visual content?
|
|
89
|
+
|
|
90
|
+
Typical classification:
|
|
91
|
+
|
|
92
|
+
- `VISUAL-009` or `STRUCTURE-001`
|
|
93
|
+
- source: `AUTHORING`
|
|
94
|
+
|
|
95
|
+
Do not flag intentionally audio-only content when the user's request or video style clearly calls for it.
|
|
96
|
+
|
|
97
|
+
## 3. Visuals actually display
|
|
98
|
+
|
|
99
|
+
**Problem:** The Markdown contains a visual reference, but the rendered video shows white, empty, missing, or broken content.
|
|
100
|
+
|
|
101
|
+
Common causes:
|
|
102
|
+
|
|
103
|
+
- 404 media source;
|
|
104
|
+
- invalid URL;
|
|
105
|
+
- absolute local path that is not portable;
|
|
106
|
+
- media path resolved relative to the wrong directory;
|
|
107
|
+
- generated asset does not exist;
|
|
108
|
+
- stale/corrupt cached asset.
|
|
109
|
+
|
|
110
|
+
**Preferred authoring pattern:** Media paths should normally be portable and relative to the Markdown file's folder, for example:
|
|
111
|
+
|
|
112
|
+
```md
|
|
113
|
+
- image src: assets/example.jpg
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
rather than relying on an environment-specific absolute path.
|
|
117
|
+
|
|
118
|
+
**Review question:**
|
|
119
|
+
|
|
120
|
+
> Does every referenced visual actually appear correctly in the rendered video?
|
|
121
|
+
|
|
122
|
+
If the Markdown requests a visual and the rendered result is blank/white/404, report it.
|
|
123
|
+
|
|
124
|
+
Typical classification:
|
|
125
|
+
|
|
126
|
+
- `VISUAL-002` or `VISUAL-008`
|
|
127
|
+
- source: `ENGINE` if the path is reasonable but Markcut fails to render it;
|
|
128
|
+
- source: `AUTHORING` if the agent generated an invalid/non-portable path;
|
|
129
|
+
- source: `ENVIRONMENT` if the resource genuinely cannot be accessed in the evaluation environment.
|
|
130
|
+
|
|
131
|
+
## 4. Audio must not be cut off
|
|
132
|
+
|
|
133
|
+
**Problem:** The final part of the narration is missing or truncated.
|
|
134
|
+
|
|
135
|
+
Typical symptom:
|
|
136
|
+
|
|
137
|
+
- the script contains more text than the generated speech;
|
|
138
|
+
- STT/transcription ends before the script ends;
|
|
139
|
+
- the last sentence or words are missing from the audio;
|
|
140
|
+
- the scene appears visually complete but the spoken narration is cut off.
|
|
141
|
+
|
|
142
|
+
**Preferred authoring principle:** Apply the same timing principle as the scene-tail check: visual and scene duration must accommodate the complete narration.
|
|
143
|
+
|
|
144
|
+
**Review question:**
|
|
145
|
+
|
|
146
|
+
> Does the generated audio contain the complete intended narration, including the final words of the script?
|
|
147
|
+
|
|
148
|
+
If the rendered audio cuts off the script tail, report it.
|
|
149
|
+
|
|
150
|
+
Typical classification:
|
|
151
|
+
|
|
152
|
+
- `AUDIO-006` or `TIMING-001`
|
|
153
|
+
- source: `AUTHORING` when scene timing/authoring causes the truncation;
|
|
154
|
+
- source: `ENGINE` when reasonable Markdown is rendered incorrectly.
|
|
155
|
+
|
|
156
|
+
## 5. Subtitles must match narration
|
|
157
|
+
|
|
158
|
+
**Problem:** Subtitles are missing, stale, or no longer correspond to the actual narration.
|
|
159
|
+
|
|
160
|
+
Check for:
|
|
161
|
+
|
|
162
|
+
- no subtitles when subtitles are expected;
|
|
163
|
+
- subtitle cues missing from the narration;
|
|
164
|
+
- stale VTT generated from an earlier script;
|
|
165
|
+
- subtitle text different from the current script/audio;
|
|
166
|
+
- subtitle timing that clearly does not match speech.
|
|
167
|
+
|
|
168
|
+
**Preferred resolution:** Regenerate stale VTT/subtitle data whenever the narration changes. Do not preserve cached subtitle data that corresponds to an older script.
|
|
169
|
+
|
|
170
|
+
**Review question:**
|
|
171
|
+
|
|
172
|
+
> Do the subtitles represent the narration that the viewer actually hears?
|
|
173
|
+
|
|
174
|
+
Typical classification:
|
|
175
|
+
|
|
176
|
+
- `SUBTITLE-003` or `SUBTITLE-004`
|
|
177
|
+
- source: `AUTHORING` when stale/generated artifacts are knowingly reused;
|
|
178
|
+
- source: `ENGINE` when Markcut fails to regenerate or synchronize subtitles correctly.
|
|
179
|
+
|
|
180
|
+
## 6. Content must match user intent
|
|
181
|
+
|
|
182
|
+
**Problem:** The video technically works, but the imagery or content does not communicate what the user asked for.
|
|
183
|
+
|
|
184
|
+
Examples:
|
|
185
|
+
|
|
186
|
+
- narration says "a child learning to ride a bicycle" but the image is unrelated;
|
|
187
|
+
- a financial explanation uses generic unrelated office imagery;
|
|
188
|
+
- the visual suggests a different meaning from the narration;
|
|
189
|
+
- an important concept is never visually represented;
|
|
190
|
+
- the generated story drifts away from the requested topic.
|
|
191
|
+
|
|
192
|
+
This is generally **advisory** rather than a rendering failure.
|
|
193
|
+
|
|
194
|
+
**Preferred resolution:** Improve the image/media prompt, scene decomposition, or narration. If the incorrect media is caused by a stale generated asset, invalidate the cache and regenerate it.
|
|
195
|
+
|
|
196
|
+
**Review question:**
|
|
197
|
+
|
|
198
|
+
> If a viewer watches the visuals while listening to the narration, do the two communicate the same intended idea?
|
|
199
|
+
|
|
200
|
+
Typical classification:
|
|
201
|
+
|
|
202
|
+
- `VISUAL-001`, `VISUAL-008`, or `VISUAL-009`
|
|
203
|
+
- source: `AUTHORING` or `CONTENT`
|
|
204
|
+
|
|
205
|
+
Do not fail a case merely because the image is metaphorical rather than literal, provided it supports the intended message.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
# General Review Dimensions
|
|
210
|
+
|
|
211
|
+
Score each applicable dimension from 1 to 5.
|
|
212
|
+
|
|
213
|
+
## Storytelling / Communication
|
|
214
|
+
|
|
215
|
+
Check:
|
|
216
|
+
|
|
217
|
+
- Is there a clear purpose?
|
|
218
|
+
- Is the opening effective?
|
|
219
|
+
- Does each scene move the message forward?
|
|
220
|
+
- Is the information ordered logically?
|
|
221
|
+
- Is the main message understandable?
|
|
222
|
+
- Is there unnecessary repetition?
|
|
223
|
+
- Does the ending provide an appropriate conclusion or payoff?
|
|
224
|
+
|
|
225
|
+
## Narration / Visual Alignment
|
|
226
|
+
|
|
227
|
+
Check whether visuals support the narration.
|
|
228
|
+
|
|
229
|
+
A visual does not need to literally illustrate every word. Conceptual visuals, examples, diagrams, and metaphors are acceptable when they improve understanding.
|
|
230
|
+
|
|
231
|
+
## Visual Quality
|
|
232
|
+
|
|
233
|
+
Check:
|
|
234
|
+
|
|
235
|
+
- readability;
|
|
236
|
+
- cropping;
|
|
237
|
+
- overlap;
|
|
238
|
+
- empty space;
|
|
239
|
+
- visual clutter;
|
|
240
|
+
- media relevance;
|
|
241
|
+
- repetition;
|
|
242
|
+
- excessive effects;
|
|
243
|
+
- overall coherence.
|
|
244
|
+
|
|
245
|
+
## Timing / Pacing
|
|
246
|
+
|
|
247
|
+
Check:
|
|
248
|
+
|
|
249
|
+
- narration vs visual duration;
|
|
250
|
+
- static visuals that last too long;
|
|
251
|
+
- visuals that disappear too early;
|
|
252
|
+
- important content shown too briefly;
|
|
253
|
+
- awkward cuts;
|
|
254
|
+
- excessive transitions;
|
|
255
|
+
- unnatural pacing.
|
|
256
|
+
|
|
257
|
+
## Audio
|
|
258
|
+
|
|
259
|
+
Check:
|
|
260
|
+
|
|
261
|
+
- narration speed;
|
|
262
|
+
- pronunciation;
|
|
263
|
+
- unnatural pauses;
|
|
264
|
+
- missing/truncated narration;
|
|
265
|
+
- volume consistency;
|
|
266
|
+
- background music competing with speech.
|
|
267
|
+
|
|
268
|
+
## Subtitles
|
|
269
|
+
|
|
270
|
+
Check:
|
|
271
|
+
|
|
272
|
+
- presence when expected;
|
|
273
|
+
- synchronization;
|
|
274
|
+
- stale content;
|
|
275
|
+
- readability;
|
|
276
|
+
- line breaks;
|
|
277
|
+
- density;
|
|
278
|
+
- visual overlap.
|
|
279
|
+
|
|
280
|
+
## Platform Suitability
|
|
281
|
+
|
|
282
|
+
If the user specifies a platform, judge against it.
|
|
283
|
+
|
|
284
|
+
For short-form video, pay particular attention to the hook, pacing, mobile readability, and visual changes.
|
|
285
|
+
|
|
286
|
+
For tutorials, pay particular attention to step ordering, demonstrations, and sufficient time to understand each step.
|
|
287
|
+
|
|
288
|
+
Do not impose platform-specific requirements when the user did not specify a platform.
|
|
289
|
+
|
|
290
|
+
## Markcut Authoring Quality
|
|
291
|
+
|
|
292
|
+
Evaluate whether the agent uses Markcut appropriately.
|
|
293
|
+
|
|
294
|
+
Look for:
|
|
295
|
+
|
|
296
|
+
- poor scene decomposition;
|
|
297
|
+
- poor duration choices;
|
|
298
|
+
- unnecessary complexity;
|
|
299
|
+
- unnecessary components/effects;
|
|
300
|
+
- poor media instructions;
|
|
301
|
+
- narration packed into one visual beat;
|
|
302
|
+
- missing visuals;
|
|
303
|
+
- non-portable media references.
|
|
304
|
+
|
|
305
|
+
Prefer the simplest Markcut structure that produces a strong result.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
# Issue Taxonomy
|
|
310
|
+
|
|
311
|
+
Use these IDs when applicable.
|
|
312
|
+
|
|
313
|
+
### STORY
|
|
314
|
+
|
|
315
|
+
- `STORY-001` — weak-hook
|
|
316
|
+
- `STORY-002` — no-progression
|
|
317
|
+
- `STORY-003` — repetitive-content
|
|
318
|
+
- `STORY-004` — unclear-message
|
|
319
|
+
- `STORY-005` — weak-ending
|
|
320
|
+
- `STORY-006` — unnecessary-introduction
|
|
321
|
+
- `STORY-007` — poor-information-order
|
|
322
|
+
|
|
323
|
+
### VISUAL
|
|
324
|
+
|
|
325
|
+
- `VISUAL-001` — narration-visual-mismatch
|
|
326
|
+
- `VISUAL-002` — missing-or-broken-visual
|
|
327
|
+
- `VISUAL-003` — repetitive-visual
|
|
328
|
+
- `VISUAL-004` — text-too-small
|
|
329
|
+
- `VISUAL-005` — text-overflow
|
|
330
|
+
- `VISUAL-006` — excessive-empty-space
|
|
331
|
+
- `VISUAL-007` — visual-clutter
|
|
332
|
+
- `VISUAL-008` — poor-media-selection
|
|
333
|
+
- `VISUAL-009` — insufficient-visual-explanation
|
|
334
|
+
|
|
335
|
+
### TIMING
|
|
336
|
+
|
|
337
|
+
- `TIMING-001` — narration-too-long-for-scene
|
|
338
|
+
- `TIMING-002` — narration-too-short-for-scene
|
|
339
|
+
- `TIMING-003` — static-visual-too-long
|
|
340
|
+
- `TIMING-004` — visual-too-short
|
|
341
|
+
- `TIMING-005` — awkward-cut
|
|
342
|
+
- `TIMING-006` — transition-too-long
|
|
343
|
+
- `TIMING-007` — excessive-transitions
|
|
344
|
+
- `TIMING-008` — poor-pacing
|
|
345
|
+
|
|
346
|
+
### AUDIO
|
|
347
|
+
|
|
348
|
+
- `AUDIO-001` — narration-too-fast
|
|
349
|
+
- `AUDIO-002` — narration-too-slow
|
|
350
|
+
- `AUDIO-003` — unnatural-narration
|
|
351
|
+
- `AUDIO-004` — awkward-pause
|
|
352
|
+
- `AUDIO-005` — music-too-loud
|
|
353
|
+
- `AUDIO-006` — narration-cut-off
|
|
354
|
+
|
|
355
|
+
### SUBTITLE
|
|
356
|
+
|
|
357
|
+
- `SUBTITLE-001` — too-dense
|
|
358
|
+
- `SUBTITLE-002` — bad-line-break
|
|
359
|
+
- `SUBTITLE-003` — timing-mismatch
|
|
360
|
+
- `SUBTITLE-004` — missing-or-stale-subtitles
|
|
361
|
+
- `SUBTITLE-005` — visual-overlap
|
|
362
|
+
|
|
363
|
+
### STRUCTURE
|
|
364
|
+
|
|
365
|
+
- `STRUCTURE-001` — missing-content
|
|
366
|
+
- `STRUCTURE-002` — unnecessary-scene
|
|
367
|
+
- `STRUCTURE-003` — wrong-order
|
|
368
|
+
- `STRUCTURE-004` — inappropriate-layout
|
|
369
|
+
- `STRUCTURE-005` — poor-scene-decomposition
|
|
370
|
+
|
|
371
|
+
### AUTHORING
|
|
372
|
+
|
|
373
|
+
- `AUTHORING-001` — unnecessary-complexity
|
|
374
|
+
- `AUTHORING-002` — poor-duration-selection
|
|
375
|
+
- `AUTHORING-003` — poor-media-instruction
|
|
376
|
+
- `AUTHORING-004` — unnecessary-component
|
|
377
|
+
- `AUTHORING-005` — unnecessary-effect
|
|
378
|
+
- `AUTHORING-006` — non-portable-media-path
|
|
379
|
+
- `AUTHORING-007` — stale-generated-artifact
|
|
380
|
+
|
|
381
|
+
### ENGINE
|
|
382
|
+
|
|
383
|
+
Use only when the Markdown contains a reasonable instruction and Markcut appears not to implement it correctly.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
# Severity
|
|
388
|
+
|
|
389
|
+
- `critical` — unusable or fails the core purpose
|
|
390
|
+
- `high` — substantially damages viewer experience
|
|
391
|
+
- `medium` — noticeable and worth fixing
|
|
392
|
+
- `low` — minor improvement or polish
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
# Evidence Rules
|
|
397
|
+
|
|
398
|
+
Every issue must contain concrete evidence.
|
|
399
|
+
|
|
400
|
+
Reference, when possible:
|
|
401
|
+
|
|
402
|
+
- scene name;
|
|
403
|
+
- approximate timestamp;
|
|
404
|
+
- narration;
|
|
405
|
+
- visible content;
|
|
406
|
+
- duration;
|
|
407
|
+
- subtitle behavior;
|
|
408
|
+
- Markdown instruction.
|
|
409
|
+
|
|
410
|
+
Do not invent exact timestamps. Use approximate timestamps when necessary.
|
|
411
|
+
|
|
412
|
+
Do not report the same underlying problem multiple times. Report the root problem and mention related symptoms in the evidence.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
# Output Format
|
|
417
|
+
|
|
418
|
+
Return the following structure.
|
|
419
|
+
|
|
420
|
+
## Overall
|
|
421
|
+
|
|
422
|
+
```yaml
|
|
423
|
+
score: <1-5>
|
|
424
|
+
summary: <short assessment>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Scores
|
|
428
|
+
|
|
429
|
+
```yaml
|
|
430
|
+
storytelling: <1-5>
|
|
431
|
+
visual_quality: <1-5>
|
|
432
|
+
narration_visual_alignment: <1-5>
|
|
433
|
+
timing_pacing: <1-5>
|
|
434
|
+
audio: <1-5>
|
|
435
|
+
subtitles: <1-5>
|
|
436
|
+
platform_suitability: <1-5>
|
|
437
|
+
markcut_authoring: <1-5>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Use the average of applicable dimensions for the overall score.
|
|
441
|
+
|
|
442
|
+
## Issues
|
|
443
|
+
|
|
444
|
+
For each meaningful issue:
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
- id: VISUAL-001
|
|
448
|
+
severity: medium
|
|
449
|
+
source: AUTHORING
|
|
450
|
+
scene: "Solution"
|
|
451
|
+
timestamp: "approximately 00:18-00:25"
|
|
452
|
+
evidence: "..."
|
|
453
|
+
impact: "..."
|
|
454
|
+
recommendation: "..."
|
|
455
|
+
confidence: high
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
If there are no meaningful issues:
|
|
459
|
+
|
|
460
|
+
```yaml
|
|
461
|
+
issues: []
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Strengths
|
|
465
|
+
|
|
466
|
+
List the most important things the video does well.
|
|
467
|
+
|
|
468
|
+
## Priority Improvements
|
|
469
|
+
|
|
470
|
+
List the three most valuable improvements, in priority order.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
# Final Principle
|
|
475
|
+
|
|
476
|
+
Answer this question above all others:
|
|
477
|
+
|
|
478
|
+
> **If a user installed the Markcut skill and asked an LLM to create this video, did the skill enable the LLM to make good decisions?**
|
|
479
|
+
|
|
480
|
+
Evaluate the actual viewer experience, use the Markdown to understand authoring intent, distinguish authoring mistakes from engine failures, and provide actionable evidence-based feedback.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# markcut storyboard evals
|
|
2
|
+
|
|
3
|
+
DeepEval suite that scores an agent authoring markcut storyboards from a
|
|
4
|
+
video brief, using the markcut skill.
|
|
5
|
+
|
|
6
|
+
## Components
|
|
7
|
+
|
|
8
|
+
| File | Purpose |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `openrouter_model.py` | Token-capped OpenRouter LLM wrapper (free-tier friendly, with model fallback rotation) |
|
|
11
|
+
| `gen_dataset.py` | Generates goldens from scratch via the Synthesizer |
|
|
12
|
+
| `dataset.json` | The golden dataset (committed, editable) |
|
|
13
|
+
| `storyboard_app.py` | Traced app: runs `pi` CLI with the markcut skill to author a storyboard |
|
|
14
|
+
| `metrics.py` | Judge metrics: TaskCompletion, Storyboard Format GEval, Story Narrative GEval |
|
|
15
|
+
| `test_storyboard.py` | pytest traced single-turn evals |
|
|
16
|
+
|
|
17
|
+
## Regenerate the dataset
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
.venv-evals/bin/python tests/evals/gen_dataset.py
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Run the evals
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd tests/evals && ../../.venv-evals/bin/deepeval test run test_storyboard.py \
|
|
27
|
+
--identifier "iterating-on-storyboard-authoring-round-1"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Each test runs the pi agent on the golden's video brief and asserts
|
|
31
|
+
trace-level metrics. Failures typically indicate the agent violated a
|
|
32
|
+
skill rule (e.g. `duration:` on scripted scenes, missing
|
|
33
|
+
`isBackground:true`) or produced weak narrative structure.
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- Evaluation model runs on OpenRouter free models; the wrapper caps
|
|
38
|
+
max_tokens at 16000 to fit the workspace key's daily budget and
|
|
39
|
+
rotates through fallback models on rate limits.
|
|
40
|
+
- Traces are local (Confident AI not enabled); view latest results via
|
|
41
|
+
`deepeval view --latest` offline report in `.deepeval/`.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"input": "30s cinematic coastal drive vlog w/ voiceover detailing scenic landmarks & geography",
|
|
4
|
+
"actual_output": null,
|
|
5
|
+
"expected_output": null,
|
|
6
|
+
"context": null,
|
|
7
|
+
"source_file": null
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"input": "Detail how teaser reveals specs, competitive edge, and launch strategy.",
|
|
11
|
+
"actual_output": null,
|
|
12
|
+
"expected_output": null,
|
|
13
|
+
"context": null,
|
|
14
|
+
"source_file": null
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"input": "Draft a 2‑speaker dialogue explaining quantum computing, with visual analogy and real‑world app.",
|
|
18
|
+
"actual_output": null,
|
|
19
|
+
"expected_output": null,
|
|
20
|
+
"context": null,
|
|
21
|
+
"source_file": null
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"input": "Develop a 5‑min pasta recipe paired with lo‑fi background music for enhanced cooking experience",
|
|
25
|
+
"actual_output": null,
|
|
26
|
+
"expected_output": null,
|
|
27
|
+
"context": null,
|
|
28
|
+
"source_file": null
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"input": "How did 1960s Ford integrate sustainability into its corporate brand story?",
|
|
32
|
+
"actual_output": null,
|
|
33
|
+
"expected_output": null,
|
|
34
|
+
"context": null,
|
|
35
|
+
"source_file": null
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"input": "Create a cinematic sci-fi thriller trailer, <2 min, featuring dystopian cityscape and AI antagonist",
|
|
39
|
+
"actual_output": null,
|
|
40
|
+
"expected_output": null,
|
|
41
|
+
"context": null,
|
|
42
|
+
"source_file": null
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"input": "Compare walkthrough of modern luxury villa vs historic estate: design, amenities, layout.",
|
|
46
|
+
"actual_output": null,
|
|
47
|
+
"expected_output": null,
|
|
48
|
+
"context": null,
|
|
49
|
+
"source_file": null
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"input": "Compare high-energy cuts vs. slow cinematic pacing for fitness motivation shorts.",
|
|
53
|
+
"actual_output": null,
|
|
54
|
+
"expected_output": null,
|
|
55
|
+
"context": null,
|
|
56
|
+
"source_file": null
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"input": "Produce a short historical documentary snippet on the Indus. Rev., focusing on inventions",
|
|
60
|
+
"actual_output": null,
|
|
61
|
+
"expected_output": null,
|
|
62
|
+
"context": null,
|
|
63
|
+
"source_file": null
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"input": "Compare dashboard walkthroughs in SW A and B tutorials, noting UI similarities and differences.",
|
|
67
|
+
"actual_output": null,
|
|
68
|
+
"expected_output": null,
|
|
69
|
+
"context": null,
|
|
70
|
+
"source_file": null
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"input": "Compare a 15‑s skincare ad with strong CTA vs a 30‑s ad: effectiveness, engagement, conversion.",
|
|
74
|
+
"actual_output": null,
|
|
75
|
+
"expected_output": null,
|
|
76
|
+
"context": null,
|
|
77
|
+
"source_file": null
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"input": "Describe how pressure adaptations let deep-sea creatures survive extreme conditions in the clip.",
|
|
81
|
+
"actual_output": null,
|
|
82
|
+
"expected_output": null,
|
|
83
|
+
"context": null,
|
|
84
|
+
"source_file": null
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"input": "30s cinematic coastal drive vlog w/ voiceover detailing scenic landmarks & geography",
|
|
88
|
+
"actual_output": null,
|
|
89
|
+
"expected_output": null,
|
|
90
|
+
"context": null,
|
|
91
|
+
"source_file": null
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"input": "Detail how teaser reveals specs, competitive edge, and launch strategy.",
|
|
95
|
+
"actual_output": null,
|
|
96
|
+
"expected_output": null,
|
|
97
|
+
"context": null,
|
|
98
|
+
"source_file": null
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"input": "Draft a 2‑speaker dialogue explaining quantum computing, with visual analogy and real‑world app.",
|
|
102
|
+
"actual_output": null,
|
|
103
|
+
"expected_output": null,
|
|
104
|
+
"context": null,
|
|
105
|
+
"source_file": null
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"input": "Develop a 5‑min pasta recipe paired with lo‑fi background music for enhanced cooking experience",
|
|
109
|
+
"actual_output": null,
|
|
110
|
+
"expected_output": null,
|
|
111
|
+
"context": null,
|
|
112
|
+
"source_file": null
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"input": "How did 1960s Ford integrate sustainability into its corporate brand story?",
|
|
116
|
+
"actual_output": null,
|
|
117
|
+
"expected_output": null,
|
|
118
|
+
"context": null,
|
|
119
|
+
"source_file": null
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"input": "Create a cinematic sci-fi thriller trailer, <2 min, featuring dystopian cityscape and AI antagonist",
|
|
123
|
+
"actual_output": null,
|
|
124
|
+
"expected_output": null,
|
|
125
|
+
"context": null,
|
|
126
|
+
"source_file": null
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"input": "Compare walkthrough of modern luxury villa vs historic estate: design, amenities, layout.",
|
|
130
|
+
"actual_output": null,
|
|
131
|
+
"expected_output": null,
|
|
132
|
+
"context": null,
|
|
133
|
+
"source_file": null
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"input": "Compare high-energy cuts vs. slow cinematic pacing for fitness motivation shorts.",
|
|
137
|
+
"actual_output": null,
|
|
138
|
+
"expected_output": null,
|
|
139
|
+
"context": null,
|
|
140
|
+
"source_file": null
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"input": "Produce a short historical documentary snippet on the Indus. Rev., focusing on inventions",
|
|
144
|
+
"actual_output": null,
|
|
145
|
+
"expected_output": null,
|
|
146
|
+
"context": null,
|
|
147
|
+
"source_file": null
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"input": "Compare dashboard walkthroughs in SW A and B tutorials, noting UI similarities and differences.",
|
|
151
|
+
"actual_output": null,
|
|
152
|
+
"expected_output": null,
|
|
153
|
+
"context": null,
|
|
154
|
+
"source_file": null
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"input": "Compare a 15‑s skincare ad with strong CTA vs a 30‑s ad: effectiveness, engagement, conversion.",
|
|
158
|
+
"actual_output": null,
|
|
159
|
+
"expected_output": null,
|
|
160
|
+
"context": null,
|
|
161
|
+
"source_file": null
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"input": "Describe how pressure adaptations let deep-sea creatures survive extreme conditions in the clip.",
|
|
165
|
+
"actual_output": null,
|
|
166
|
+
"expected_output": null,
|
|
167
|
+
"context": null,
|
|
168
|
+
"source_file": null
|
|
169
|
+
}
|
|
170
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Generate the markcut storyboard goldens with a token-capped OpenRouter model.
|
|
2
|
+
|
|
3
|
+
Run:
|
|
4
|
+
OPENROUTER_API_KEY=... .venv-evals/bin/python tests/evals/gen_dataset.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from deepeval.synthesizer import Synthesizer
|
|
10
|
+
from deepeval.synthesizer.config import StylingConfig
|
|
11
|
+
|
|
12
|
+
from openrouter_model import OpenRouterLLM
|
|
13
|
+
|
|
14
|
+
SCENARIO = (
|
|
15
|
+
"Video creators and AI agents using the markcut markdown-to-video skill "
|
|
16
|
+
"to author video storyboards from a brief. markcut uses a markdown "
|
|
17
|
+
"descriptive format: '## scene' headings, '- image prompt:...' bullets, "
|
|
18
|
+
"script \"...\" narration, isBackground:true for visuals under scripted "
|
|
19
|
+
"scenes, map stream type with view/tween camera moves for route vlogs, "
|
|
20
|
+
"built-in components, TTS/TTI/STT media pipeline, and golden rules such "
|
|
21
|
+
"as never set duration on scripted scenes, keep manual assets in the "
|
|
22
|
+
"assets/ folder, and review md then compiled json then rendered video."
|
|
23
|
+
)
|
|
24
|
+
TASK = (
|
|
25
|
+
"Act as a storyboard author: given a video brief, produce a markcut "
|
|
26
|
+
"storyboard in markdown descriptive format that follows the skill's "
|
|
27
|
+
"scene/isBackground/duration rules and narrative structure (hook, "
|
|
28
|
+
"conflict, resolution, emotion, call to action)."
|
|
29
|
+
)
|
|
30
|
+
INPUT_FORMAT = (
|
|
31
|
+
"A short video brief, e.g. 'a 30s travel vlog of a coastal route with "
|
|
32
|
+
"narration', 'a product launch teaser with stats', 'a two-speaker "
|
|
33
|
+
"dialogue explainer', 'a recipe short with background music'."
|
|
34
|
+
)
|
|
35
|
+
EXPECTED_OUTPUT_FORMAT = (
|
|
36
|
+
"A complete markcut storyboard markdown using ## scene headings, "
|
|
37
|
+
"- image/script bullets, correct isBackground usage, and no manual "
|
|
38
|
+
"durations on scripted scenes."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
model = OpenRouterLLM()
|
|
44
|
+
synthesizer = Synthesizer(
|
|
45
|
+
model=model,
|
|
46
|
+
styling_config=StylingConfig(
|
|
47
|
+
scenario=SCENARIO,
|
|
48
|
+
task=TASK,
|
|
49
|
+
input_format=INPUT_FORMAT,
|
|
50
|
+
expected_output_format=EXPECTED_OUTPUT_FORMAT,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
goldens = synthesizer.generate_goldens_from_scratch(num_goldens=12)
|
|
54
|
+
synthesizer.save_as(
|
|
55
|
+
file_type="json",
|
|
56
|
+
directory="tests/evals",
|
|
57
|
+
file_name="dataset",
|
|
58
|
+
)
|
|
59
|
+
print(f"Saved {len(goldens)} goldens to tests/evals/dataset.json")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""DeepEval metrics for the markcut storyboard eval suite.
|
|
2
|
+
|
|
3
|
+
Judge model is the token-capped OpenRouter wrapper so the whole suite
|
|
4
|
+
runs on the workspace's free OpenRouter key.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
11
|
+
|
|
12
|
+
from deepeval.metrics import GEval, TaskCompletionMetric
|
|
13
|
+
from deepeval.test_case import LLMTestCaseParams
|
|
14
|
+
|
|
15
|
+
from openrouter_model import OpenRouterLLM
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _judge():
|
|
19
|
+
return OpenRouterLLM(temperature=0.0)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Trace-level: did the agent finish the storyboard task at all?
|
|
23
|
+
TASK_COMPLETION_METRICS = [
|
|
24
|
+
TaskCompletionMetric(model=_judge(), threshold=0.7),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Is the storyboard valid markcut markdown following the skill's rules?
|
|
28
|
+
STORYBOARD_FORMAT_METRIC = GEval(
|
|
29
|
+
name="Storyboard Format",
|
|
30
|
+
criteria=(
|
|
31
|
+
"Evaluate whether the actual output is a valid markcut storyboard "
|
|
32
|
+
"in markdown descriptive format. It must: use '## scene' headings "
|
|
33
|
+
"for scenes; use '- image prompt:\"...\"' or similar bullet syntax "
|
|
34
|
+
"for visuals; include narration via script \"...\"; set "
|
|
35
|
+
"isBackground:true on the primary visual of any scene that has a "
|
|
36
|
+
"script or audio (otherwise the scene plays black); and NOT set "
|
|
37
|
+
"manual durations on scripted scenes since the resolver derives "
|
|
38
|
+
"duration from the audio script."
|
|
39
|
+
),
|
|
40
|
+
evaluation_params=[
|
|
41
|
+
LLMTestCaseParams.INPUT,
|
|
42
|
+
LLMTestCaseParams.ACTUAL_OUTPUT,
|
|
43
|
+
],
|
|
44
|
+
model=_judge(),
|
|
45
|
+
threshold=0.7,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Does the storyboard tell a compelling viral story?
|
|
49
|
+
STORY_NARRATIVE_METRIC = GEval(
|
|
50
|
+
name="Story Narrative",
|
|
51
|
+
criteria=(
|
|
52
|
+
"Evaluate whether the storyboard tells a compelling short video "
|
|
53
|
+
"story. It should contain a hook (why watch), conflict or "
|
|
54
|
+
"challenge, resolution, emotional beats, a call to action, and "
|
|
55
|
+
"ideally an open ending where appropriate. Score low if it is a "
|
|
56
|
+
"flat list of scenes with no narrative structure."
|
|
57
|
+
),
|
|
58
|
+
evaluation_params=[
|
|
59
|
+
LLMTestCaseParams.INPUT,
|
|
60
|
+
LLMTestCaseParams.ACTUAL_OUTPUT,
|
|
61
|
+
],
|
|
62
|
+
model=_judge(),
|
|
63
|
+
threshold=0.6,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
SINGLE_TURN_TRACE_METRICS = [
|
|
67
|
+
TASK_COMPLETION_METRICS[0],
|
|
68
|
+
STORYBOARD_FORMAT_METRIC,
|
|
69
|
+
STORY_NARRATIVE_METRIC,
|
|
70
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""OpenRouter-backed DeepEval LLM with a capped max_tokens budget.
|
|
2
|
+
|
|
3
|
+
The workspace OPENROUTER_API_KEY has a daily credit cap that rejects
|
|
4
|
+
requests asking for 65536 max_tokens, so we wrap the OpenAI-compatible
|
|
5
|
+
client ourselves and ask for far fewer output tokens.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
|
|
11
|
+
from deepeval.models import DeepEvalBaseLLM
|
|
12
|
+
from deepeval.synthesizer.schema import (
|
|
13
|
+
Response as _ResponseSchema,
|
|
14
|
+
SyntheticData,
|
|
15
|
+
SyntheticDataList,
|
|
16
|
+
)
|
|
17
|
+
from deepeval.metrics.utils import trimAndLoadJson
|
|
18
|
+
|
|
19
|
+
MAX_OUTPUT_TOKENS = 16000
|
|
20
|
+
|
|
21
|
+
# Free-tier models are flaky; rotate through candidates on rate limits.
|
|
22
|
+
FALLBACK_MODELS = [
|
|
23
|
+
"google/gemma-4-31b-it:free",
|
|
24
|
+
"nvidia/nemotron-3-super-120b-a12b:free",
|
|
25
|
+
"inclusionai/ling-3.0-flash-sante:free",
|
|
26
|
+
"minimax/minimax-m2.7:free",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OpenRouterLLM(DeepEvalBaseLLM):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
model: str = "google/gemma-4-31b-it:free",
|
|
34
|
+
temperature: float = 0.0,
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
37
|
+
self.model_id = model
|
|
38
|
+
self.temperature = temperature
|
|
39
|
+
super().__init__(model, **kwargs)
|
|
40
|
+
|
|
41
|
+
def load_model(self):
|
|
42
|
+
from openai import OpenAI
|
|
43
|
+
|
|
44
|
+
return OpenAI(
|
|
45
|
+
api_key=os.environ["OPENROUTER_API_KEY"],
|
|
46
|
+
base_url="https://openrouter.ai/api/v1",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def generate(
|
|
50
|
+
self,
|
|
51
|
+
prompt: str,
|
|
52
|
+
schema=None,
|
|
53
|
+
max_output_tokens: int = MAX_OUTPUT_TOKENS,
|
|
54
|
+
) -> str:
|
|
55
|
+
client = self.load_model()
|
|
56
|
+
if schema is not None:
|
|
57
|
+
# Sending the raw JSON schema makes weak models echo it back;
|
|
58
|
+
# describe the shape in prose instead.
|
|
59
|
+
if schema is SyntheticDataList:
|
|
60
|
+
shape_desc = (
|
|
61
|
+
'a JSON object {"data": [{"input": "<brief text>"}]} '
|
|
62
|
+
"with as many items as requested"
|
|
63
|
+
)
|
|
64
|
+
elif schema is _ResponseSchema:
|
|
65
|
+
shape_desc = 'a JSON object {"response": "<your answer>"}'
|
|
66
|
+
else:
|
|
67
|
+
shape_desc = (
|
|
68
|
+
"a single JSON object with exactly these keys: "
|
|
69
|
+
f"{list(schema.model_fields.keys())}"
|
|
70
|
+
)
|
|
71
|
+
prompt = (
|
|
72
|
+
"You are a JSON generator. Respond with ONLY one valid JSON "
|
|
73
|
+
"value - no prose, no markdown fences, no commentary, and "
|
|
74
|
+
"never repeat the schema itself. The JSON must be exactly "
|
|
75
|
+
f"{shape_desc}.\n\nTask:\n{prompt}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
import time
|
|
79
|
+
|
|
80
|
+
text = None
|
|
81
|
+
last_err = None
|
|
82
|
+
for model_id in [self.model_id] + [
|
|
83
|
+
m for m in FALLBACK_MODELS if m != self.model_id
|
|
84
|
+
]:
|
|
85
|
+
for attempt in range(3):
|
|
86
|
+
try:
|
|
87
|
+
response = client.chat.completions.create(
|
|
88
|
+
model=model_id,
|
|
89
|
+
messages=[{"role": "user", "content": prompt}],
|
|
90
|
+
temperature=self.temperature,
|
|
91
|
+
max_tokens=min(max_output_tokens, MAX_OUTPUT_TOKENS),
|
|
92
|
+
)
|
|
93
|
+
text = response.choices[0].message.content or ""
|
|
94
|
+
break
|
|
95
|
+
except Exception as err: # rate limits, upstream 429s
|
|
96
|
+
last_err = err
|
|
97
|
+
time.sleep(5 * (attempt + 1))
|
|
98
|
+
if text:
|
|
99
|
+
break
|
|
100
|
+
if not text:
|
|
101
|
+
raise last_err
|
|
102
|
+
if schema is None:
|
|
103
|
+
return text
|
|
104
|
+
# Parse the JSON the prompt asked for into the requested schema
|
|
105
|
+
try:
|
|
106
|
+
data = trimAndLoadJson(text, self)
|
|
107
|
+
except ValueError:
|
|
108
|
+
with open("/tmp/deepeval_last_output.txt", "w") as f:
|
|
109
|
+
f.write(text)
|
|
110
|
+
raise
|
|
111
|
+
if schema is SyntheticDataList:
|
|
112
|
+
return SyntheticDataList(
|
|
113
|
+
data=[SyntheticData(**item) for item in data["data"]]
|
|
114
|
+
)
|
|
115
|
+
if schema is _ResponseSchema and isinstance(data, str):
|
|
116
|
+
return _ResponseSchema(response=data)
|
|
117
|
+
return schema(**data)
|
|
118
|
+
|
|
119
|
+
async def a_generate(
|
|
120
|
+
self,
|
|
121
|
+
prompt: str,
|
|
122
|
+
schema=None,
|
|
123
|
+
max_output_tokens: int = MAX_OUTPUT_TOKENS,
|
|
124
|
+
) -> str:
|
|
125
|
+
return self.generate(prompt, schema, max_output_tokens)
|
|
126
|
+
|
|
127
|
+
def get_model_name(self) -> str:
|
|
128
|
+
return self.model_id
|
|
129
|
+
|
|
130
|
+
def supports_json_mode(self) -> bool:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def supports_structured_outputs(self) -> bool:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def supports_log_probs(self) -> Optional[bool]:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def supports_multimodal(self) -> bool:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def batch_generate(self, prompts: List[str]) -> List[str]:
|
|
143
|
+
return [self.generate(p) for p in prompts]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Traced markcut storyboard authoring app.
|
|
2
|
+
|
|
3
|
+
Runs the pi agent CLI with the markcut skill against a video brief and
|
|
4
|
+
returns the produced storyboard markdown. The @observe decorator makes
|
|
5
|
+
the run a DeepEval agent trace so traced single-turn evals can score it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
|
|
12
|
+
from deepeval.tracing import observe
|
|
13
|
+
|
|
14
|
+
MARKCUT_SKILL = os.path.abspath(
|
|
15
|
+
os.path.join(
|
|
16
|
+
os.path.dirname(__file__), "..", "..", "skills", "markcut", "SKILL.md"
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _read_skill() -> str:
|
|
22
|
+
with open(MARKCUT_SKILL) as f:
|
|
23
|
+
return f.read()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@observe(type="agent", name="markcut-storyboard-author")
|
|
27
|
+
def run_traced_storyboard(brief: str) -> str:
|
|
28
|
+
"""Author a markcut storyboard markdown for the given brief."""
|
|
29
|
+
with tempfile.TemporaryDirectory() as workdir:
|
|
30
|
+
prompt = (
|
|
31
|
+
"You are a video storyboard author using the markcut skill.\n"
|
|
32
|
+
f"Video brief: {brief}\n\n"
|
|
33
|
+
"Write the storyboard as a markcut markdown file named "
|
|
34
|
+
"storyboard.md in the current directory. Follow the skill's "
|
|
35
|
+
"rules strictly. When done, print ONLY the final storyboard "
|
|
36
|
+
"markdown to stdout with no extra commentary."
|
|
37
|
+
)
|
|
38
|
+
result = subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
"pi",
|
|
41
|
+
"-p",
|
|
42
|
+
"--no-session",
|
|
43
|
+
"--skill", MARKCUT_SKILL,
|
|
44
|
+
prompt,
|
|
45
|
+
],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
timeout=600,
|
|
49
|
+
cwd=workdir,
|
|
50
|
+
)
|
|
51
|
+
storyboard_path = os.path.join(workdir, "storyboard.md")
|
|
52
|
+
if os.path.exists(storyboard_path):
|
|
53
|
+
with open(storyboard_path) as f:
|
|
54
|
+
return f.read()
|
|
55
|
+
return result.stdout
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Traced single-turn evals for the markcut storyboard authoring agent.
|
|
2
|
+
|
|
3
|
+
Each golden's input is a video brief; the app runs the pi agent CLI with
|
|
4
|
+
the markcut skill to author a storyboard markdown, traced with @observe.
|
|
5
|
+
|
|
6
|
+
Run:
|
|
7
|
+
cd tests/evals && ../../.venv-evals/bin/deepeval test run test_storyboard.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from deepeval import assert_test
|
|
17
|
+
from deepeval.dataset import EvaluationDataset, Golden
|
|
18
|
+
|
|
19
|
+
from metrics import SINGLE_TURN_TRACE_METRICS
|
|
20
|
+
from storyboard_app import run_traced_storyboard
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
dataset = EvaluationDataset()
|
|
24
|
+
dataset.add_goldens_from_json_file(
|
|
25
|
+
file_path=os.path.join(os.path.dirname(__file__), "dataset.json")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("golden", dataset.goldens)
|
|
30
|
+
def test_storyboard_authoring(golden: Golden):
|
|
31
|
+
run_traced_storyboard(golden.input)
|
|
32
|
+
assert_test(golden=golden, metrics=SINGLE_TURN_TRACE_METRICS)
|
package/.vscode/settings.json
DELETED