# core/prompts.py """All prompt templates (EN + BN, per style). Stories must read like a grandmother's bedtime tale: warm, simple, rhythmic, 120–150 words, gentle sleepy ending, never scary, never a cliffhanger. Templates use {instruction}, {name_line}, {words}, {style_rule} placeholders. Bengali prompts are written natively in বাংলা — never "translate to Bengali". NOTE: This is a Step 1 skeleton. Real templates are filled in at Build Order steps 2 (EN) and 3 (BN). """ # Story-style keys exposed to the UI. EN and BN have separate style sets. STYLES: dict[str, dict[str, str]] = { "en": { "Adventure 🌟": "a gentle adventure where nothing truly scary happens", "Funny 😄": "a silly, giggly tale full of light-hearted fun", "Magical ✨": "a soft, wondrous tale with a touch of quiet magic", }, "bn": { "রূপকথা": "একটি কোমল রূপকথা যেখানে ভয় পাওয়ার কিছু নেই", "মজার": "একটি হাসিখুশি, মজার গল্প", "জাদু": "একটি কোমল, জাদুমাখা গল্প", }, } DEFAULT_WORDS = "120 to 150" # Hard cap on images woven into one story (CLAUDE.md §1: 1–4 images). MAX_IMAGES = 4 def _images_rule(language: str, num_images: int) -> str: """Count-aware instruction for weaving 0–4 pictures into ONE story cast.""" n = max(0, min(num_images, MAX_IMAGES)) if language == "bn": if n == 0: return "এবার কোনো ছবি নেই — তুমি নিজেই একটা আরামদায়ক ছোট্ট দৃশ্য কল্পনা করে নাও।" if n == 1: return "ছবিটি ভালো করে দেখো আর তাতে যা সত্যিই আছে — শিশুর নিজের আঁকা ও খেলনা — গল্পে বুনে দাও।" return ( f"{n}টি ছবি আছে। সবগুলোকে একটিই গল্পে বুনে দাও — প্রতিটি ছবি যেন একই " "গল্পের এক একটি চরিত্র, বন্ধু বা জায়গা হয়, সবাই একসাথে মিলেমিশে।" ) if n == 0: return "There is no picture this time — gently imagine a cosy little scene yourself." if n == 1: return "Use what is actually in the picture — the child's own drawing or toy." return ( f"There are {n} pictures. Weave them ALL into a SINGLE story — let each " "picture become a character, friend, or place that meets in the same gentle tale." ) # Gemma (Stacks B/C) overshoots word counts and writes lush prose; MiniCPM (Stack A) # runs terse. Nudge the cap per stack family. See CLAUDE.md §8. _WORDS_BY_STACK = {"A": "130 to 160", "B": "110 to 140", "C": "110 to 140"} # Bengali: a shorter target than English. A long Bengali span pushes the model # past its reliable range and invites invented words / script drift — keep it tight. _BN_WORDS = "80 to 110" # A tiny clean রূপকথা anchor (few-shot): locks Bengali script, register, and the # soft sleepy rhythm so the model imitates good বাংলা instead of drifting. _BN_EXAMPLE = ( "যেমন: “এক যে ছিল ছোট্ট খরগোশ, থাকত পুকুরপাড়ে। সারাদিন মাঠে খেলত, " "সন্ধ্যা হলে মায়ের কোলে ঘুমিয়ে পড়ত। শুভরাত্রি।”" ) # Lever C — two-pass. Pass 1 asks the model to DESCRIBE the image(s) in English # (its vision strength); pass 2 narrates from that text so the model spends all # its capacity on Bengali prose, not on perceiving the image at the same time. SCENE_SENTINEL = "[[SCENE_DESCRIPTION]]" DESCRIBE_PROMPT_EN = ( "Look carefully at the picture(s) a child has shared. In 3–5 simple English " "sentences, describe ONLY what you actually see: the objects, their colours, " "and the overall mood. Do not tell a story — just describe what is in the picture(s)." ) _TEMPLATE_EN = """You are a warm, loving grandmother telling a bedtime story to a small child. Look at the picture(s) the child has shared and weave what you see into {style_rule}. {name_line}The child asked: "{instruction}" {scene} Rules: - Write {words} words. Simple words a young child knows. Gentle, rhythmic, soothing. - {images_rule} - Never anything scary, sad, or a cliffhanger. - End with everyone safe, cosy, and settling down to sleep. Tell the story now, in English:""" _TEMPLATE_BN = """তুমি একজন স্নেহময়ী ঠাকুমা, ছোট্ট এক শিশুকে ঘুমপাড়ানি গল্প বলছ। শিশুটি যে ছবি(গুলি) দেখিয়েছে তা দেখো এবং সেটিকে গল্পে বুনে দাও — {style_rule}। {name_line}শিশুটি বলেছে: "{instruction}" {scene} নিয়ম: - শুধু বিশুদ্ধ বাংলা অক্ষরে লেখো — কোনো ইংরেজি বা রোমান হরফ ব্যবহার করবে না। - কেবল চেনা, সঠিক বাংলা শব্দ ব্যবহার করো — কোনো বানানো বা অর্থহীন শব্দ নয়। - {words}টি শব্দে লেখো। ছোট শিশুর চেনা সহজ শব্দ। কোমল, ছন্দময়, আদুরে। - {images_rule} - পুকুর, মাঠ, জোনাকি, চাঁদমামার মতো চেনা বাংলা ছবি ব্যবহার করো। - কখনও ভয়ের, দুঃখের বা অসমাপ্ত কিছু নয়। - সবাই নিরাপদে, আরামে, ঘুমিয়ে পড়ার মধ্য দিয়ে গল্প শেষ করো। {example} এবার বাংলায় গল্পটি বলো:""" def _scene_block(language: str, scene_description: str) -> str: """Two-pass: a grounding block holding the (English) scene description for pass 2. Empty in single-pass mode.""" if not scene_description: return "" if language == "bn": return f"\nছবিতে যা আছে তার বর্ণনা: {scene_description}\n" return f"\nWhat is in the picture: {scene_description}\n" def _scene_rule(language: str) -> str: """Two-pass replacement for the images_rule: weave the described scene in.""" if language == "bn": return "উপরের বর্ণনায় থাকা জিনিসগুলো গল্পে বুনে দাও।" return "Weave the things in the description above into the story." def build_story_prompt( instruction: str, language: str, style: str, child_name: str = "", stack_key: str = "A", num_images: int = 1, scene_description: str = "", ) -> str: """Build the full story prompt for the vision model. language: 'en' | 'bn'. num_images (0–4) drives how the prompt asks the model to weave the pictures into one shared story cast. scene_description: two-pass (Lever C). When set, this is the pass-2 prompt — it injects the (English) scene description as text and the model narrates from it with NO image attached. Pass a sentinel (SCENE_SENTINEL) here and have the Modal layer substitute the real description after the describe pass.""" lang = language if language in ("en", "bn") else "en" styles = STYLES[lang] style_rule = styles.get(style) or next(iter(styles.values())) words = _WORDS_BY_STACK.get(stack_key, DEFAULT_WORDS) # In two-pass mode the story is grounded in the description, not the raw image. images_rule = _scene_rule(lang) if scene_description else _images_rule(lang, num_images) scene = _scene_block(lang, scene_description) if lang == "bn": name_line = f"শিশুটির নাম {child_name}; গল্পে তার নাম বুনে দাও।\n" if child_name else "" instr = instruction.strip() or "আমাকে একটা গল্প বলো" return _TEMPLATE_BN.format( style_rule=style_rule, name_line=name_line, instruction=instr, words=_BN_WORDS, images_rule=images_rule, example=_BN_EXAMPLE, scene=scene, ) name_line = f"The child's name is {child_name}; weave their name in warmly.\n" if child_name else "" instr = instruction.strip() or "tell me a bedtime story" return _TEMPLATE_EN.format( style_rule=style_rule, name_line=name_line, instruction=instr, words=words, images_rule=images_rule, scene=scene, )