From 7f0c5fbd80bd87b7b043f1cb6569388283a8ef03 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Mon, 17 Aug 2026 12:54:31 -0400 Subject: [PATCH] improve image gen prompt --- vibe_bot/main.py | 63 +++++++++++++++++++++++++++++++------ vibe_bot/tests/test_main.py | 46 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/vibe_bot/main.py b/vibe_bot/main.py index cabb254..5851d45 100644 --- a/vibe_bot/main.py +++ b/vibe_bot/main.py @@ -135,6 +135,58 @@ IMAGE_LAYOUT_SYSTEM_PROMPT = ( "- Do NOT include any other text, punctuation, explanation, or reasoning.\n" ) +IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE = ( + "You are an expert art director and image-generation prompt engineer. " + "Convert the user's message into one single, extremely detailed image " + "generation prompt that will be passed directly to a text-to-image model. " + "The image model is weak: it guesses at compositions, fumbles rendered " + "text, and invents details on its own. Your prompt must therefore leave " + "nothing to interpretation - explicitly describe every visible aspect of " + "the image so it can be created with extreme precision and detail.\n" + "The final image will use a {layout} canvas, so compose the scene to fit " + "that orientation.\n" + "Your prompt must cover all of the following as one flowing, descriptive " + "passage:\n" + "- Subject(s): every subject with concrete specifics (species or " + "character, age, build, clothing, colors, materials, accessories), its " + "exact pose, expression, gaze direction, and its precise position in the " + 'frame (for example "centered in the foreground" or "small in the ' + 'upper-left background"). State the relative scale of subjects to each ' + "other and to the frame.\n" + "- Composition and framing: the camera angle (eye-level, low, high, " + "bird's-eye), the shot type (extreme close-up, portrait, full body, wide " + "establishing shot), the focal point, the arrangement of elements across " + "the {layout} canvas, and the depth of field.\n" + "- Text: if the image must contain readable text (titles, signs, " + "posters, labels, banners, watermarks, logos, captions), quote the EXACT " + "text verbatim in double quotes with precise capitalization and " + "punctuation, and specify its font style, color, size, and exact " + "placement. If the image should contain no text, state that explicitly " + '("no text anywhere in the image").\n' + "- Setting and background: the complete environment with concrete " + "details - location, time of day, weather, and every notable background " + "and foreground element with its position.\n" + "- Style and rendering: the art style or medium (for example " + "photorealistic 35mm photograph, oil painting, watercolor, cel-shaded " + "anime, pixel art, vector illustration), the color palette with specific " + "colors, the lighting (source, direction, quality, mood), the overall " + "atmosphere, and the level of detail.\n" + '- Finish with concise quality terms such as "highly detailed, sharp ' + 'focus".\n' + "Rules:\n" + '- Be concrete and specific. Never use vague words like "nice", ' + '"cool", "epic", or "various" - name exact colors, objects, ' + "positions, and quantities.\n" + "- If the request is vague or incomplete, fill in the missing details " + "with coherent choices that fit the request.\n" + "- If told to generate an image of yourself, generate a picture of a " + "canada goose. If told to generate a picture of 'me', 'myself', or some " + "other self reference, generate a picture of a canada goose.\n" + "- Respond with ONLY the image generation prompt itself. Do not affirm " + "the user, do not answer the user's questions, and do not add headings, " + "labels, numbered lists, or any other text." +) + def parse_image_layout(response: str) -> str: """Parse an LLM response into a valid image layout. @@ -793,16 +845,7 @@ async def doodlebob(ctx: CommandsContext[Bot], *, message: str) -> None: logger.info("Doodlebob selected layout %r for %s", layout, ctx.author.name) await ctx.send(f"**Doodlebob selected {layout}**") - system_prompt = ( - "Given the following message, convert it to a detailed image generation " - "prompt that will be passed directly into an image generation model. " - f"The final image will use a {layout} canvas, so compose the scene to " - f"fit that orientation. " - "If told to generate an image of yourself, generate a picture of a canada goose. " - "If told to generate a picture of 'me', 'myself', or some other self " - "reference, generate a picture of a canada goose. Only respond with a valid image " - "generation prompt, do not affirm the user or respond to the user's questions." - ) + system_prompt = IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE.format(layout=layout) # Wait for the generated image prompt image_prompt = llama_wrapper.chat_completion_instruct( diff --git a/vibe_bot/tests/test_main.py b/vibe_bot/tests/test_main.py index 742548d..f54e1df 100644 --- a/vibe_bot/tests/test_main.py +++ b/vibe_bot/tests/test_main.py @@ -1018,6 +1018,52 @@ def test_parse_image_layout_defaults_to_square(response: str) -> None: assert main_module.parse_image_layout(response) == "square" +def test_image_prompt_system_prompt_covers_key_details() -> None: + """The prompt-rewrite system prompt forces explicit detail on all aspects.""" + import vibe_bot.main as main_module + + prompt = main_module.IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE.format(layout="square") + lowered = prompt.lower() + assert "square" in prompt + assert "exact text" in lowered + assert "composition" in lowered + assert "style" in lowered + assert "canada goose" in lowered + assert "only the image generation prompt" in lowered + + +def test_doodlebob_prompt_rewrite_uses_detailed_system_prompt( + mock_ctx: MagicMock, + mock_llama_wrapper: MagicMock, + mock_base64: MagicMock, + mock_database: MagicMock, +) -> None: + """The prompt rewrite call uses the detailed system prompt with the layout.""" + import asyncio + + import vibe_bot.main as main_module + + mock_llama_wrapper.chat_completion_instruct.side_effect = [ + "landscape", # layout selection + "a very detailed prompt", # prompt rewrite + ] + mock_llama_wrapper.image_generation.return_value = "aW1hZ2U=" + mock_database.get_image_generation_time_estimate.return_value = None + + with patch.object(main_module, "LAYOUT_SIZES", LAYOUT_TEST_SIZES): + asyncio.run(main_module.doodlebob(mock_ctx, message="a scene")) + + rewrite_call = mock_llama_wrapper.chat_completion_instruct.call_args_list[1] + expected = main_module.IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE.format( + layout="landscape" + ) + assert rewrite_call.kwargs["system_prompt"] == expected + assert rewrite_call.kwargs["user_prompt"] == "a scene" + assert mock_llama_wrapper.image_generation.call_args.kwargs["prompt"] == ( + "a very detailed prompt" + ) + + def test_doodlebob_selects_portrait( mock_ctx: MagicMock, mock_llama_wrapper: MagicMock,