This commit is contained in:
+53
-10
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user