Coverage for integrations / providers / creative_tools.py: 18.2%
88 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
1"""
2Creative Content Agent Tools — chain providers for movies, games, stories.
4These tools compose the provider gateway's atomic operations (text gen,
5image gen, video gen, audio gen) into creative pipelines:
7 story_director — Generate a full story with scenes, dialogue, visuals
8 movie_maker — Text → storyboard → images → video → music → movie
9 game_asset_creator — Generate game assets (sprites, backgrounds, items, audio)
10 personalized_content — Understand user preferences → custom content
12Each tool uses the gateway's smart routing, so it automatically picks
13the cheapest/fastest provider for each step in the pipeline.
15Revenue tracking: every gateway call records cost. The revenue analytics
16dashboard shows cost-per-creation vs value generated.
17"""
19import json
20import logging
21import time
22from typing import Any, Dict, List, Optional
24logger = logging.getLogger(__name__)
27def _gateway():
28 """Lazy import to avoid circular deps."""
29 from integrations.providers.gateway import get_gateway
30 return get_gateway()
33def _generate(prompt, model_type='llm', **kwargs):
34 """Shorthand for gateway.generate with error handling."""
35 result = _gateway().generate(prompt, model_type=model_type, **kwargs)
36 if result.success:
37 return result.content
38 return f'[Generation failed: {result.error}]'
41# ═══════════════════════════════════════════════════════════════════════
42# Story Director
43# ═══════════════════════════════════════════════════════════════════════
45def story_director(query: str) -> str:
46 """Create a complete story with scenes, characters, dialogue, and visual descriptions.
48 Pipeline: user concept → plot outline → scene breakdown → dialogue + visual prompts.
49 Input: story concept/idea (e.g. "A detective cat solving mysteries in Tokyo").
50 Returns: structured story with scenes ready for movie_maker or game_asset_creator.
51 """
52 # Step 1: Generate plot outline
53 outline = _generate(
54 f"""You are a professional screenwriter. Create a compelling story outline from this concept:
56CONCEPT: {query}
58Return a JSON object with:
59{{
60 "title": "Story title",
61 "genre": "genre",
62 "logline": "One sentence pitch",
63 "characters": [
64 {{"name": "...", "role": "protagonist/antagonist/supporting", "visual": "physical description for image generation"}}
65 ],
66 "scenes": [
67 {{
68 "number": 1,
69 "location": "setting description",
70 "action": "what happens",
71 "dialogue": "key dialogue lines",
72 "visual_prompt": "detailed prompt for generating this scene as an image",
73 "mood": "emotional tone",
74 "duration_seconds": 10
75 }}
76 ]
77}}
79Create 4-6 scenes. Make visual_prompts detailed enough for AI image generation.""",
80 system_prompt="You are a screenwriter who outputs valid JSON only.",
81 max_tokens=2000,
82 temperature=0.8,
83 )
85 # Step 2: Parse and enrich
86 try:
87 story = json.loads(outline)
88 # Add metadata
89 story['created_at'] = time.time()
90 story['concept'] = query
91 story['total_scenes'] = len(story.get('scenes', []))
92 story['estimated_duration'] = sum(
93 s.get('duration_seconds', 10) for s in story.get('scenes', []))
95 return json.dumps(story, indent=2)
96 except json.JSONDecodeError:
97 # LLM didn't return valid JSON — return raw text
98 return f"Story outline (raw):\n\n{outline}"
101# ═══════════════════════════════════════════════════════════════════════
102# Movie Maker
103# ═══════════════════════════════════════════════════════════════════════
105def movie_maker(query: str) -> str:
106 """Create a short movie from a concept: script → images → video → music.
108 Pipeline:
109 1. story_director generates the script with visual prompts
110 2. Generate key frame images for each scene
111 3. Generate video clips from images (img2vid) or text (txt2vid)
112 4. Generate background music
113 5. Return all assets with assembly instructions
115 Input: movie concept (e.g. "30-second ad for a space tourism company").
116 Returns: JSON with all generated asset URLs and timeline.
117 """
118 # Step 1: Generate script
119 script_json = story_director(query)
120 try:
121 script = json.loads(script_json)
122 except json.JSONDecodeError:
123 return f"Failed to generate script. Raw output:\n{script_json[:500]}"
125 scenes = script.get('scenes', [])
126 if not scenes:
127 return "No scenes generated. Try a more specific concept."
129 # Track cost delta (not lifetime total)
130 _cost_before = _gateway().get_stats().get('total_cost_usd', 0)
132 assets = {
133 'title': script.get('title', 'Untitled'),
134 'script': script,
135 'images': [],
136 'videos': [],
137 'music': None,
138 'timeline': [],
139 'total_cost_usd': 0.0,
140 }
142 # Step 2: Generate key frame images for each scene
143 for i, scene in enumerate(scenes[:6]): # Max 6 scenes
144 prompt = scene.get('visual_prompt', scene.get('action', ''))
145 if not prompt:
146 continue
148 image_url = _generate(
149 f"Cinematic, high quality, film still: {prompt}. "
150 f"Mood: {scene.get('mood', 'dramatic')}. "
151 f"Style: photorealistic, 16:9 aspect ratio, movie lighting.",
152 model_type='image_gen',
153 strategy='balanced',
154 )
155 assets['images'].append({
156 'scene': i + 1,
157 'url': image_url,
158 'prompt': prompt,
159 })
161 # Step 3: Generate video for key scenes (first and climax)
162 key_scenes = [scenes[0]]
163 if len(scenes) > 2:
164 key_scenes.append(scenes[len(scenes) // 2]) # Mid-point
166 for scene in key_scenes:
167 prompt = scene.get('visual_prompt', '')
168 if not prompt:
169 continue
170 video_url = _generate(
171 f"Cinematic video: {prompt}. Smooth camera movement, "
172 f"mood: {scene.get('mood', 'dramatic')}",
173 model_type='video_gen',
174 strategy='balanced',
175 )
176 assets['videos'].append({
177 'scene': scene.get('number', 0),
178 'url': video_url,
179 'duration': scene.get('duration_seconds', 10),
180 })
182 # Step 4: Generate background music
183 genre = script.get('genre', 'cinematic')
184 music_url = _generate(
185 f"Background music for a {genre} short film. "
186 f"Title: {script.get('title', '')}. Mood: atmospheric, emotional. "
187 f"Duration: 30 seconds. Instrumental only.",
188 model_type='audio_gen',
189 strategy='balanced',
190 )
191 assets['music'] = {'url': music_url, 'genre': genre}
193 # Step 5: Build timeline
194 offset = 0
195 for i, scene in enumerate(scenes):
196 dur = scene.get('duration_seconds', 10)
197 assets['timeline'].append({
198 'scene': i + 1,
199 'start_s': offset,
200 'end_s': offset + dur,
201 'image': assets['images'][i]['url'] if i < len(assets['images']) else None,
202 'video': next((v['url'] for v in assets['videos']
203 if v['scene'] == i + 1), None),
204 'dialogue': scene.get('dialogue', ''),
205 })
206 offset += dur
208 # Cost tracking — delta from before this creation
209 _cost_after = _gateway().get_stats().get('total_cost_usd', 0)
210 assets['total_cost_usd'] = round(_cost_after - _cost_before, 6)
212 return json.dumps(assets, indent=2)
215# ═══════════════════════════════════════════════════════════════════════
216# Game Asset Creator
217# ═══════════════════════════════════════════════════════════════════════
219def game_asset_creator(query: str) -> str:
220 """Generate game assets from a concept: characters, backgrounds, items, audio.
222 Input: game concept (e.g. "fantasy RPG with dragon riders").
223 Returns: JSON with generated asset URLs organized by category.
224 """
225 # Step 1: Generate asset list
226 asset_plan = _generate(
227 f"""You are a game artist director. For this game concept, list the assets needed:
229CONCEPT: {query}
231Return JSON:
232{{
233 "game_title": "...",
234 "style": "pixel art / 2D cartoon / 3D realistic / anime",
235 "characters": [
236 {{"name": "...", "role": "player/enemy/npc", "visual_prompt": "detailed visual description for AI image gen"}}
237 ],
238 "backgrounds": [
239 {{"name": "...", "visual_prompt": "detailed scene description"}}
240 ],
241 "items": [
242 {{"name": "...", "type": "weapon/potion/key/treasure", "visual_prompt": "..."}}
243 ],
244 "audio_needs": ["background music genre", "sound effect descriptions"]
245}}
247Keep it to 3-4 items per category. Make visual_prompts specific enough for AI generation.""",
248 system_prompt="Output valid JSON only.",
249 max_tokens=1500,
250 temperature=0.7,
251 )
253 try:
254 plan = json.loads(asset_plan)
255 except json.JSONDecodeError:
256 return f"Failed to plan assets. Raw:\n{asset_plan[:500]}"
258 style = plan.get('style', '2D cartoon')
259 result = {
260 'game_title': plan.get('game_title', 'Untitled'),
261 'style': style,
262 'characters': [],
263 'backgrounds': [],
264 'items': [],
265 'audio': [],
266 }
268 # Step 2: Generate character sprites
269 for char in plan.get('characters', [])[:4]:
270 url = _generate(
271 f"Game character sprite, {style} style: {char['visual_prompt']}. "
272 f"Full body, transparent background, game-ready.",
273 model_type='image_gen',
274 )
275 result['characters'].append({
276 'name': char['name'], 'role': char.get('role', ''),
277 'url': url,
278 })
280 # Step 3: Generate backgrounds
281 for bg in plan.get('backgrounds', [])[:3]:
282 url = _generate(
283 f"Game background, {style} style: {bg['visual_prompt']}. "
284 f"Wide shot, 16:9, detailed environment.",
285 model_type='image_gen',
286 )
287 result['backgrounds'].append({
288 'name': bg['name'], 'url': url,
289 })
291 # Step 4: Generate items
292 for item in plan.get('items', [])[:4]:
293 url = _generate(
294 f"Game item icon, {style} style: {item['visual_prompt']}. "
295 f"Centered, transparent background, detailed.",
296 model_type='image_gen',
297 )
298 result['items'].append({
299 'name': item['name'], 'type': item.get('type', ''),
300 'url': url,
301 })
303 # Step 5: Generate audio
304 for audio_desc in plan.get('audio_needs', [])[:2]:
305 url = _generate(
306 f"Game audio: {audio_desc}. High quality, loopable.",
307 model_type='audio_gen',
308 )
309 result['audio'].append({
310 'description': audio_desc, 'url': url,
311 })
313 return json.dumps(result, indent=2)
316# ═══════════════════════════════════════════════════════════════════════
317# LangChain Tool Registration
318# ═══════════════════════════════════════════════════════════════════════
320def get_creative_tools():
321 """Return LangChain tools for creative content generation."""
322 tools = []
323 try:
324 from langchain.tools import Tool
325 except ImportError:
326 try:
327 from langchain_core.tools import Tool
328 except ImportError:
329 return []
331 tools.extend([
332 Tool(
333 name='Story_Director',
334 func=story_director,
335 description=(
336 'Create a complete story with scenes, characters, dialogue, and visual descriptions. '
337 'Input: story concept. Output: structured JSON story ready for movie or game production.'
338 ),
339 ),
340 Tool(
341 name='Movie_Maker',
342 func=movie_maker,
343 description=(
344 'Create a short movie from a concept: generates script, images, video clips, '
345 'and music. Returns all asset URLs with a timeline for assembly. '
346 'Input: movie concept (e.g. "30-second ad for a coffee brand").'
347 ),
348 ),
349 Tool(
350 name='Game_Asset_Creator',
351 func=game_asset_creator,
352 description=(
353 'Generate game assets from a concept: character sprites, backgrounds, items, '
354 'and audio. Returns organized asset URLs. '
355 'Input: game concept (e.g. "fantasy RPG with dragon riders").'
356 ),
357 ),
358 ])
359 return tools