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"
|
"- 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:
|
def parse_image_layout(response: str) -> str:
|
||||||
"""Parse an LLM response into a valid image layout.
|
"""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)
|
logger.info("Doodlebob selected layout %r for %s", layout, ctx.author.name)
|
||||||
await ctx.send(f"**Doodlebob selected {layout}**")
|
await ctx.send(f"**Doodlebob selected {layout}**")
|
||||||
|
|
||||||
system_prompt = (
|
system_prompt = IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE.format(layout=layout)
|
||||||
"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."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wait for the generated image prompt
|
# Wait for the generated image prompt
|
||||||
image_prompt = llama_wrapper.chat_completion_instruct(
|
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"
|
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(
|
def test_doodlebob_selects_portrait(
|
||||||
mock_ctx: MagicMock,
|
mock_ctx: MagicMock,
|
||||||
mock_llama_wrapper: MagicMock,
|
mock_llama_wrapper: MagicMock,
|
||||||
|
|||||||
Reference in New Issue
Block a user