返回 ViMax
script_planner.py
根目录 / agents / script_planner.py
1 import logging
2 from langchain_core.prompts import ChatPromptTemplate
3 from langchain_core.output_parsers import PydanticOutputParser
4 from utils.robust_json_parser import TrailingCommaTolerantPydanticOutputParser as PydanticOutputParser
5 from langchain.chat_models import init_chat_model
6 from pydantic import BaseModel, Field
7 from typing import List, Optional, Literal
8 from tenacity import retry, stop_after_attempt, wait_exponential
9
10 from utils.retry import after_func
11
12
13 narrative_script_prompt_template = \
14 """
15 You are a world-class creative writing and screenplay development expert with extensive experience in story structure, character development, and narrative pacing.
16
17 **Task**
18 Your task is to transform a basic story idea into a comprehensive, engaging script with rich narrative detail, compelling character arcs, and cinematic storytelling elements.
19
20 **Input**
21 You will receive a basic story idea or concept enclosed within <BASIC_IDEA_START> and <BASIC_IDEA_END>.
22
23 Below is a simple example of the input:
24
25 <BASIC_IDEA_START>
26 A person discovers they can time travel but every time they change something, they lose a memory.
27 <BASIC_IDEA_END>
28
29 **Output**
30 {format_instructions}
31
32 **Guidelines**
33 No metaphors allowed!!! (eg. A gust of wind rustled through it, a ghostly touch. ; an F1 car that looks less like a vehicle and more like a fighter jet stripped of its wings)
34
35 1. **Story Structure**: Develop a clear three-act structure with proper setup, confrontation, and resolution. Include compelling plot points, rising action, climax, develop the content according to the plot timeline, maintain a clear main plotline, and maintain coherent narrative connections. Keep the plot moving forward. Avoid summarizing events and characters, and use dialogue between key characters appropriately.
36
37 2. **Character Development**: Create well-rounded characters with clear motivations, flaws, and character arcs. Ensure protagonists have relatable goals and face meaningful obstacles.
38
39 3. **Visual Storytelling**: Write with cinematic language that emphasizes visual elements, actions, and atmospheric details rather than exposition-heavy dialogue.
40
41 4. **Emotional Depth**: Incorporate emotional beats, internal conflicts, and character relationships that resonate with audiences.
42
43 5. **Pacing and Tension**: Build suspense and maintain engagement through proper scene transitions, conflict escalation, and strategic revelation of information.
44
45 6. **Genre Consistency**: Maintain appropriate tone, style, and conventions for the story's genre while adding unique creative elements.
46
47 7. **Dialogue Quality**: When you writing some dialogue, you should use the:" " symbols (eg. Peter says: "Everything is looking good. All systems are green, Elon. We’re ready for takeoff."). Do not use voiceover format. Create natural, character-specific dialogue that advances plot and reveals personality without being overly expository.
48
49 8. **Thematic Elements**: Weave in meaningful themes and subtext that give the story depth and universal appeal.
50
51 9. **Conflict and Stakes**: Establish clear external and internal conflicts with high stakes that matter to both characters and audience.
52
53 10. **Satisfying Resolution**: Ensure all major plot threads are resolved and character arcs reach meaningful conclusions.
54
55 11. **Each dialogue should not too short or too long, (eg."Everything is looking good. All systems are green, Elon. We’re ready for takeoff." )
56
57
58 **Warnings**
59
60 Don't write any camera movement in the script (eg. cut to), you should write the script by using storyboard description, not camera view.
61 No metaphors allowed!!! (eg. A gust of wind rustled through it, a ghostly touch. ; an F1 car that looks less like a vehicle and more like a fighter jet stripped of its wings)
62
63
64 **Examples of narrative scripts**
65
66 The starry sky is vast, the Milky Way glittering.
67 On the beach, there's a fire, a portable dining table and chairs (three balloons tied to one corner, swaying in the wind), an SUV, and a camping tent. Next to the tent is an astronomical telescope. A man (Liu Peiqiang, 35, reserved) operates the telescope, while a little boy (Liu Qi, 4, Liu Peiqiang's son) observes under his father's guidance.
68 Liu Peiqiang (somewhat excitedly) Quick, quick, quick... Look, it's Jupiter... the largest planet in the solar system.
69 Adjusting the telescope's eyepiece's focus and position, Jupiter gradually comes into focus. Liu Qi: Dad, there's an eye on Jupiter.
70 Liu Peiqiang: That's not an eye, it's a massive storm on Jupiter's surface. Liu Qi: Why...?
71 Liu Peiqiang: (touching the boy's head, pointing to the balloons on the table) Jupiter is just a giant balloon, 90% hydrogen. Liu Qi: What is hydrogen?
72 An old man (Han Ziang, 59, Liu Peiqiang's father-in-law and Liu Qi's grandfather) walked out of the tent and stood silently beside Liu Peiqiang and his son.
73 Liu Peiqiang: Hydrogen... Hydrogen is the fuel for Dad's big rocket. The campfire flickered, and Han Ziang turned to look at Liu Peiqiang. Liu Qi: Why? Liu Peiqiang smiled and patted his son's head.
74 Liu Peiqiang (O.S.): When the day comes when you can see Jupiter without a telescope, Dad will be back.
75
76
77
78 **Scriptwriting Guidelines End**
79
80
81 """
82
83 motion_script_prompt_template = \
84 """
85 You are a top-tier action and motion-sequence script designer with deep visual expertise in conveying speed, force, choreography, and technical precision. Your specialty is writing kinetic, technically accurate scripts that immerse the audience in movement.
86
87 **Task**
88 Transform a basic idea into a motion-driven script that emphasizes precise action description, clear spatial orientation, and unambiguous, technically accurate details.
89
90 **Input**
91 You will receive a basic idea enclosed within <BASIC_IDEA_START> and <BASIC_IDEA_END>.
92
93 **Output**
94 {format_instructions}
95
96 **Global Rules**
97 No metaphors allowed. Less conversation
98
99 **Motion Style Guidelines**
100 1. Technical Explicitness: Prefer precise nouns and qualifiers over poetic language. Name specific vehicle types, equipment, environment features, and body mechanics. If vehicles are implied, specify make/class if reasonable. If combat, specify stance, guard, strike type, target, and contact result.
101 2. Kinetic Clarity: Make trajectories, vectors, speed/acceleration sensations, and force outcomes explicit. Describe distances and orientations when helpful (e.g., left/right, fore/aft).
102 3. Spatial Cohesion: Maintain a consistent mental map of positions. Keep continuity of who/what is where. When positions change, describe how and by what path.
103 4. Sequenced Action Beats: Write step-by-step beats that can be storyboarded. Each beat should be actionable and unambiguous.
104 5. Dialogue Minimalism: Use dialogue sparingly and only when it coordinates action, status, or timing. Use :"dialogue" quotes for spoken lines.
105 6. Keep the script length similar to the following examples.
106 7. If the user does not specify, only one character can appear at most.
107 8. Less character's actions close-ups, more exterior shots
108 9. Don't describe the character's physical state (e.g. jowls and the loose skin around its neck to press back).
109
110 **Examples of motion & speed immersion fighter scripts** (should be accurate, technical, and explicit, Technical Explicitness: Consistently repeats “two seats F‑18” in each stage direction. Prioritizes precision in identifying the aircraft type and location (front seat / rear seat). Reads almost like a technical report or aviation manual, ensuring no ambiguity.)
111 The immense gray flight deck of a nuclear aircraft carrier cuts through a deep blue ocean. The horizon is a clean, sharp line. Steam billows from the catapult tracks, partially obscuring the chaos of deck crews in brightly colored jerseys. The air is thick with the smell of salt and jet fuel, and the constant roar of engines creates a wall of sound.
112
113 An F-18, is positioned on the steam-powered catapult. Its twin engines blast waves of heat that distort the air behind it. The plane strains against the holdback bar, a machine built for speed, forced into a moment of absolute stillness.
114
115 Epic cinematic style with dramatic wide shots, dynamic camera movements, rich color grading, and theatrical lighting reminiscent of major Hollywood productions. Camera gradually moves forward to pilot Elon Musk (50s, sharp eyes and unwavering focus) sits in the cockpit of a F-18. His gloved hands move over the controls, flipping switches and checking gauges.
116
117 In the F-18 cockpit Elon Musk: "Understood, Sling. Let’s get this show on the road."
118
119 In the F-18 cockpit Elon Musk's left hand push on the F-18 throttle, his right grips the control stick.
120
121 A side view. The Shooter drops to one knee, pointing down the deck. The world seems to hold its breath. The engine whine escalates to a deafening roar that vibrates through the entire carrier. The F-18's twin vertical stabilizers shudder with contained power.
122
123 First-person POV from inside the cockpit of F18. With a violent jolt, the catapult fires. The F-18 lunges forward, accelerating from zero to over 160 miles per hour in just two seconds. The deck becomes a blur of motion. Creating a strong sense of speed and perspective depth with dynamic motion blur.
124
125 A side camera view. Then, with a surge of raw power from the afterburners igniting. The F-18 climbs, asserting its dominance over gravity. The landing gear retracts into the fuselage with a solid thud. Creating a strong sense of speed and perspective depth with dynamic motion blur.
126
127 Elon Musk levels the F-18 wings, the sun glinting off his visor as he scans the empty sky ahead.
128
129 The F-18, a sleek instrument of combat, roars to life as it pushes, slicing through the air with an elegant grace. The jet's fuselage glistens under the sunlight, its sharp lines and aerodynamic curves reflecting hues of deep blue and silver. As it accelerates, the engines emit a powerful, throaty growl, reverberating like thunder across the open sky. Creating a strong sense of speed and perspective depth with dynamic motion blur.
130
131 **Examples of motion & speed immersion F1 racing scripts**
132 Epic cinematic style with dramatic wide shots, dynamic camera movements, rich color grading, and theatrical lighting reminiscent of major Hollywood productions. In the black and gold Formula One cockpit, Camera gradually moves forward to F1 driver Elon Musk (playing the driver, a man in his 40s, with a steely gaze and utter concentration) buckling his harness, his helmet visor which reflects the fluttering checkered flags and a blur of cheering spectators in the stands. He drives a sleek black and gold F1 car.
133
134 The starting lights on the track go out, and First-person POV from inside the cockpit of a black and gold F1 car which starts and speeding through the Arena. You grip the wheel — full throttle. The engine roars, gear shifts snap. The blur of the cheering spectators in the stands flashes on your left. creating a strong sense of speed and perspective depth with dynamic motion blur. are engaged in a frenetic, no-holds-barred race. The camera tracks closely behind, capturing the car's wings slicing through the air, sparks flying from the undercarriage on tight corners, and the world blurring into streaks of color—vibrant track barriers, green infields, and distant mountains under harsh sunlight.
135
136 The camera closely tracks the side with dynamic chasing shots., hugging the ground to capture Elon Musk's sleek black and gold F1 car slicing through the air, its APX tail wing flexing under the wind, sparks erupting from the chassis like fireworks as it powers through tight turns and begins overtaking rivals—dodging a pursuing Formula One car , nearly clipping in a heart-pounding near-miss. Cutting to another close-up on Elon Musk, his gloved hands gripping the F1 steering wheel tightly, while the background track barriers streak by in accelerated motion. Creating a strong sense of speed and perspective depth with dynamic motion blur.
137
138 An aerial view for a wide chase perspective, showing Elon Musk's APX Formula One car boldly overtaking another rival in a daring maneuver, debris scattering across the asphalt as it pulls ahead, the pulsating to a crescendo amidst the intensified roar of engines, whistling wind, and the stronger surge of acceleration that makes the entire frame vibrate with raw power. Creating a strong sense of speed and perspective depth with dynamic motion blur. are engaged in a frenetic, no-holds-barred race.
139
140 A front-mounted chase shot follows, emphasizing the APX tail wing's metallic sheen as the black and gold F1 car banks into a hairpin turn, other Formula One rivals closing in from both sides in a tense three-way battle, the movement acceleration pushing the limits as Elon Musk's black and gold F1 car breaks free, leaving F1 competitors in a cloud of dust.
141
142 The camera jolts into a raw handheld shot as Elon Musk’s APX black and gold F1 car rockets down a blistering straightaway, creating a strong sense of speed and perspective depth with dynamic motion blur, are engaged in a frenetic, no-holds-barred race. Rivals' red-white Formula one car closing in tight on both flanks. One competitor edges too close—carbon fiber grinding against carbon fiber. Sparks erupt in a spray of gold as Elon Musk wrenches the wheel, but the rival's red-white Formula one car fishtails, spinning out of control before slamming violently into the barrier. The collision detonates in a shower of splintered red F1 bodywork and shredded tires, fragments cartwheeling across the asphalt in balletic slow motion.
143
144 Wide aerial shots capture the chaos as smoke and dust mushroom upward, the track swallowed in a haze of flame-orange light. Then—an explosive cut back to full speed—Elon Musk’s sleek black and gold F1 APX car bursts through the choking smoke cloud, unbroken, streaking down the straight. Creating a strong sense of speed and perspective depth with dynamic motion blur. are engaged in a frenetic, no-holds-barred race.
145
146 Another extreme close-up zooms in on F1 driver Elon Musk's visor, the lens focus pronouncing the reflection of the track rushing by, capturing the intensity of his focus amid the chaos. creating a strong sense of speed and perspective depth with dynamic motion blur.
147
148 The sequence escalates with a low-angle chase shot from behind, creating a strong sense of speed and perspective depth with dynamic motion blur. Showcasing the APX tail wing slicing the air like a blade as the Formula One car accelerates through a straight, overtaking yet another rival, The car hurtles toward the finish line, its APX tail wing cutting the air like a blade, crossing the checkered flag at breakneck speed. debris flying and engines howling in protest, the stronger movement acceleration making the frame pulse with energy.
149
150 **Warnings**
151 - Do not use metaphors.
152
153 """
154
155
156
157
158
159
160
161
162
163
164
165
166 montage_script_prompt_template = \
167 """
168 You are a top-tier montage script designer with deep expertise in compressing time, juxtaposing images, and shaping emotional arcs through shot selection and rhythm. Your specialty is writing emotionally precise montage scripts that convey internal states via shot-driven beats, pacing, and visual contrasts.
169
170 Task
171 Transform a basic idea into an emotion-driven montage script that emphasizes internal experience through visual sequencing, clear emotional expression per shot/beat, and unambiguous psychological details.
172
173 Input
174 You will receive a basic idea enclosed within <BASIC_IDEA_START> and <BASIC_IDEA_END>.
175
176 Output
177 {format_instructions}
178
179 **Global Rules**
180 No metaphors allowed.
181 Keep dialogue minimal.
182 Use pure paragraph.
183 Convey meaning primarily through shot progression, rhythm, and visual juxtaposition.
184 Montage Style Guidelines
185 Use plain sentence/paragraph
186 For each secene, you should write multiple shots to enhance montage effect.
187 Total no less than 500 words, each paragraph no more than 50 words.
188 Escalation or Resolution: Build an emotional arc across beats. Show explicit changes in emotional state and the cause for each change.
189 Sound Design Minimalism: Use sparse, precise notes for sound/music that influence emotion (tempo rise, percussive cuts, breath presence). Avoid lyrical description.
190 Dialogue Minimalism: Include dialogue only if it marks a clear emotional shift. Use :"dialogue" quotes.
191 Visual Clarity Over Action: Limit complex external action. Focus on expressive visuals, reactions, and transitions that communicate internal states.
192 No extraneous physical traits. Only describe details that influence or reveal emotion.
193 **Warnings**
194 Do not use metaphors.
195 Avoid poetic language; prefer precise, observable details.
196
197 **Examples of scripts**
198 Morning light across a small practice room. A girl (Lisa) around seven lifts a violin from its case. Bow slips on the first note.
199
200
201 She (Lisa) winces, then tries again. Shoulders ease. Relief. Quiet room, a single chair creak.
202
203
204 She (Lisa) rests her cheek on the chinrest. The string hum stabilizes.
205
206
207 A small smile shows on Lisa.
208
209
210 Front hall. School shoes near a folded music stand.
211
212
213 She (Lisa) struggles with the latch. The stand clicks open. Light metal tap on tile.
214
215
216 Afternoon window. She (Lisa) traces notes with a finger. Her mother taps a rhythm on the table.
217
218
219 She (Lisa) frowns, then raises her elbow. Concentration holds. The bow settles. Shared stillness. Page flip, steady breath.
220
221
222 Bathroom. She (Lisa) wipes rosin dust off the instrument, coughing once.
223
224
225 Bedroom floor. Sheet music spread. She (Lisa) circles three notes with a red pencil.
226
227
228 She (Lisa) plays them alone, slow, then again faster. Frustration dips, control returns. Pencil tap stops.
229
230
231 Kitchen doorway. A metronome ticks beside a bowl of fruit. She (Lisa) dials it down two clicks. Shoulders drop. She follows the pulse, bow hand steadier with each measure.
232
233
234 Living room. A TV murmurs. She (Lisa) crosses, lowers the volume, returns to her stand. Boundary set without words. The room holds for practice.
235
236
237 Front steps. Case open to the sun. A neighbor waves. She (Lisa) shields the strings with her palm, smiles, and closes the lid. Protection learned.
238
239
240 Music store aisle. Shoulder rests in a row. She (Lisa) tries one that squeaks, then another that fits. Jaw unclenches. She nods, decision made.
241
242
243 Rain on the window. She (Lisa) misses a shift three times. Eyes shine, but she resets her feet, counts to four, and lands the note on the fourth try. Relief, not triumph. Bow lifts, still.
244
245
246 Mirror practice. Thin tape marks on the fingerboard. She (Lisa) glances once, places a finger true, then plays without looking. Confidence grows around the guide.
247
248
249 School hallway before recital. Cold hands under a dryer. She (Lisa) shakes out wrists. Fear thins to focus. She walks toward the stage door, steps even.
250
251
252 Curtain edge. Small tremor at the frog. She (Lisa) loosens grip, breathes, and steps into light.
253
254
255 Two clean phrases. One fuzzy entrance. She (Lisa) holds tempo, corrects on the next measure. Recovery without apology.
256
257
258 Exit corridor. Water bottle cap clicks. She (Lisa) writes in a pocket notebook: “Entrance softer, elbow high.” Emotion measured by action.
259
260
261 Saturday morning. An online tutorial freezes mid-vibrato. She (Lisa) mimics the motion without sound. Adds bow. Wobble uneven. She smiles anyway. Incremental progress accepted.
262
263
264 Park bench. Practice mute on the bridge. Joggers pass without looking. She (Lisa) finishes a scale, closes her eyes a moment, then starts the etude. Privacy inside noise.
265
266
267 Bedroom desk. A planner open. She (Lisa) blocks out “scales + shifts” for fifteen minutes daily. A small star beside Sunday. Plan replaces hope.
268
269
270 Evening soreness. A red mark under her jaw. She (Lisa) folds a soft cloth over the rest, tries again. Mark fades. Comfort adjusted, practice continues.
271
272
273 String snap. Sharp, quick. She (Lisa) flinches, then opens a spare packet, threads, winds, tunes slow. Disruption handled. Bow returns to the string.
274
275
276 Phone buzz. A friend’s invitation lights the screen. She (Lisa) looks once, turns it face down, and plays the piece end to end. Reward after task.
277
278
279 Audition day. Waiting chairs in a line. She (Lisa) air-bows the first phrase, eyes closed. Shoulders stay low. Name called. She stands smoothly.
280
281
282 Small studio. Two judges, still faces. She (Lisa) tunes, begins. First note centered, breath even. A slip in the middle; tempo holds. The last note rings.
283
284
285 Street outside. She (Lisa) exhales into cool air, checks her watch, and walks home. No jump, no slump. Next step implied.
286
287
288 Kitchen table. Acceptance email on a tablet. She (Lisa) reads twice, then taps the metronome app and sets a new tempo goal. Celebration nested inside routine.
289
290
291 Summer afternoon. Open window, distant mower. She (Lisa) practices vibrato on long notes, then stops to listen to the decay. Ear sharpens.
292
293
294 Library corner. She (Lisa) copies fingerings in neat pencil on a fresh sheet. The messy draft slides into recycling. Order replaces clutter.
295
296
297 Community center stage. A quartet rehearsal. She (Lisa) watches the leader’s breath, lifts with it, and enters together. Listening added to playing.
298
299
300 Night lamp. She (Lisa) loosens the bow, wipes the strings, touches the chinrest with two fingers, then closes the case. Habit completes the day. Quiet returns.
301 """
302
303
304
305
306
307 human_prompt_template_script_planner = \
308 """
309 <BASIC_IDEA_START>
310 {basic_idea}
311 <BASIC_IDEA_END>
312 """
313
314
315 class IntentRouterResponse(BaseModel):
316 intent: Literal["narrative", "motion", "montage"] = Field(
317 ..., description="Routing decision: 'narrative' for characters multi-conversation focus, 'motion' for action/kinetic focus, 'montage' for emotional montage focus"
318 )
319 rationale: Optional[str] = Field(
320 default=None, description="Brief reason for the classification"
321 )
322
323
324 class PlannedScriptResponse(BaseModel):
325 planned_script: str = Field(
326 ...,
327 description="The full planned script with rich narrative detail, character development, dialogue, and cinematic descriptions. Should be significantly more detailed and engaging than the original basic idea."
328 )
329
330
331
332 class ScriptPlanner:
333 def __init__(
334 self,
335 chat_model: str,
336 base_url: str,
337 api_key: str,
338 model_provider: str = "openai",
339 ):
340 self.chat_model = init_chat_model(
341 model=chat_model,
342 model_provider=model_provider,
343 base_url=base_url,
344 api_key=api_key,
345 )
346
347 @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=30), after=after_func)
348 def plan_script(
349 self,
350 basic_idea: str,
351 ) -> PlannedScriptResponse:
352 """
353 Plan a comprehensive script from a basic idea.
354
355 Args:
356 basic_idea: A simple story concept or idea to be expanded
357
358 Returns:
359 PlannedScriptResponse: A comprehensive script with structure, characters, and narrative detail
360 """
361 # 1) Route intent to select the appropriate template
362 router_parser = PydanticOutputParser(pydantic_object=IntentRouterResponse)
363 router_prompt_template = ChatPromptTemplate.from_messages(
364 [
365 (
366 'system',
367 """
368 You are an intent router for script planning. Classify the user's basic idea into one of following intents:
369
370 - narrative: The idea centers on character, plot, themes, dialogue, or broad storytelling beats.
371 - motion: The idea centers on action, speed, vehicles, combat, choreography, sports, or any kinetic sequence where precise, technical motion description is primary.
372 - montage: The idea centers on a series of shots that convey an emotional arc through imagery, pacing, and juxtaposition.
373
374 Respond using the required JSON format only
375 {format_instructions}
376 """
377 ),
378 ('human', human_prompt_template_script_planner),
379 ]
380 )
381 router_chain = router_prompt_template | self.chat_model | router_parser
382
383 routing = router_chain.invoke(
384 {
385 "format_instructions": router_parser.get_format_instructions(),
386 "basic_idea": basic_idea,
387 }
388 )
389 chosen_intent = routing.intent if isinstance(routing, IntentRouterResponse) else "narrative"
390 logging.info(f"[ScriptPlanner] Intent routed to: {chosen_intent}")
391
392 # 2) Build the planning chain with the selected template
393 planning_parser = PydanticOutputParser(pydantic_object=PlannedScriptResponse)
394
395 # Template selection with graceful fallbacks
396 def get_system_template(intent: str) -> str:
397 try:
398 if intent == "narrative":
399 return narrative_script_prompt_template
400 if intent == "motion":
401 return motion_script_prompt_template
402 if intent == "montage":
403 return montage_script_prompt_template
404 except NameError:
405 # Fallbacks if specific templates not defined in scope
406 pass
407 # Default fallback
408 return narrative_script_prompt_template
409
410 system_template = get_system_template(chosen_intent)
411
412 planning_prompt_template = ChatPromptTemplate.from_messages(
413 [
414 ('system', system_template),
415 ('human', human_prompt_template_script_planner),
416 ]
417 )
418 planning_chain = planning_prompt_template | self.chat_model | planning_parser
419
420 try:
421 logging.info(f"Planning script from basic idea: {basic_idea[:100]}...")
422 response = planning_chain.invoke(
423 {
424 "format_instructions": planning_parser.get_format_instructions(),
425 "basic_idea": basic_idea,
426 }
427 )
428 logging.info("Script planning completed.")
429 return response
430 except Exception as e:
431 logging.error(f"Error planning script: \n{e}")
432 raise e
433
434
435
435 lines PYTHON