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

1""" 

2Creative Content Agent Tools — chain providers for movies, games, stories. 

3 

4These tools compose the provider gateway's atomic operations (text gen, 

5image gen, video gen, audio gen) into creative pipelines: 

6 

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 

11 

12Each tool uses the gateway's smart routing, so it automatically picks 

13the cheapest/fastest provider for each step in the pipeline. 

14 

15Revenue tracking: every gateway call records cost. The revenue analytics 

16dashboard shows cost-per-creation vs value generated. 

17""" 

18 

19import json 

20import logging 

21import time 

22from typing import Any, Dict, List, Optional 

23 

24logger = logging.getLogger(__name__) 

25 

26 

27def _gateway(): 

28 """Lazy import to avoid circular deps.""" 

29 from integrations.providers.gateway import get_gateway 

30 return get_gateway() 

31 

32 

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}]' 

39 

40 

41# ═══════════════════════════════════════════════════════════════════════ 

42# Story Director 

43# ═══════════════════════════════════════════════════════════════════════ 

44 

45def story_director(query: str) -> str: 

46 """Create a complete story with scenes, characters, dialogue, and visual descriptions. 

47 

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: 

55 

56CONCEPT: {query} 

57 

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}} 

78 

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 ) 

84 

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', [])) 

94 

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}" 

99 

100 

101# ═══════════════════════════════════════════════════════════════════════ 

102# Movie Maker 

103# ═══════════════════════════════════════════════════════════════════════ 

104 

105def movie_maker(query: str) -> str: 

106 """Create a short movie from a concept: script → images → video → music. 

107 

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 

114 

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]}" 

124 

125 scenes = script.get('scenes', []) 

126 if not scenes: 

127 return "No scenes generated. Try a more specific concept." 

128 

129 # Track cost delta (not lifetime total) 

130 _cost_before = _gateway().get_stats().get('total_cost_usd', 0) 

131 

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 } 

141 

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 

147 

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 }) 

160 

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 

165 

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 }) 

181 

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} 

192 

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 

207 

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) 

211 

212 return json.dumps(assets, indent=2) 

213 

214 

215# ═══════════════════════════════════════════════════════════════════════ 

216# Game Asset Creator 

217# ═══════════════════════════════════════════════════════════════════════ 

218 

219def game_asset_creator(query: str) -> str: 

220 """Generate game assets from a concept: characters, backgrounds, items, audio. 

221 

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: 

228 

229CONCEPT: {query} 

230 

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}} 

246 

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 ) 

252 

253 try: 

254 plan = json.loads(asset_plan) 

255 except json.JSONDecodeError: 

256 return f"Failed to plan assets. Raw:\n{asset_plan[:500]}" 

257 

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 } 

267 

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 }) 

279 

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 }) 

290 

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 }) 

302 

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 }) 

312 

313 return json.dumps(result, indent=2) 

314 

315 

316# ═══════════════════════════════════════════════════════════════════════ 

317# LangChain Tool Registration 

318# ═══════════════════════════════════════════════════════════════════════ 

319 

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 [] 

330 

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