complete restructure

This commit is contained in:
2026-08-19 13:16:43 -04:00
parent d7b6f28cbd
commit f87e1d51ef
60 changed files with 8176 additions and 5143 deletions
+209
View File
@@ -0,0 +1,209 @@
"""Prompt constants and system-prompt assembly helpers.
Holds every prompt string the bot sends to the LLM (image layout selection,
image-prompt engineering, prompt verification) plus the shared response-length
hint and the ``build_system_prompt`` assembler used by the chat and
speak-with-bot paths.
"""
from __future__ import annotations
import re
from typing import TYPE_CHECKING
from vibe_bot.config import (
IMAGE_GEN_SIZE_LANDSCAPE,
IMAGE_GEN_SIZE_PORTRAIT,
IMAGE_GEN_SIZE_SQUARE,
)
if TYPE_CHECKING:
import discord
# Image layout (canvas orientation) selection for doodlebob.
DEFAULT_IMAGE_LAYOUT = "square"
VALID_IMAGE_LAYOUTS = ("portrait", "landscape", "square")
LAYOUT_SIZES: dict[str, str] = {
"portrait": IMAGE_GEN_SIZE_PORTRAIT,
"landscape": IMAGE_GEN_SIZE_LANDSCAPE,
"square": IMAGE_GEN_SIZE_SQUARE,
}
# Shared response-length hint appended to bot system prompts.
RESPONSE_LENGTH_HINT = "Keep your responses under 2-3 sentences."
IMAGE_LAYOUT_SYSTEM_PROMPT = (
"You decide the aspect ratio (layout) of an image that will be generated "
"from a user's request. Choose exactly ONE layout from these three options:\n"
"- portrait: a tall, vertical image (taller than wide). Use for subjects that "
"are taller than they are wide, such as a single standing person or animal, "
"a full-body character, a tall building, a skyscraper, a tree, a rocket, or "
"any vertical composition.\n"
"- landscape: a wide, horizontal image (wider than tall). Use for scenes that "
"are wider than they are tall, such as wide landscapes, panoramas, cityscapes, "
"seas and horizons, battle or group scenes spread out horizontally, or any "
"horizontal composition.\n"
"- square: an image that is as wide as it is tall. Use for balanced subjects, "
"close-ups, faces, single objects, logos, emblems, or whenever no strong tall "
"or wide orientation is implied.\n"
"Rules:\n"
"- Base your choice ONLY on the orientation the content implies.\n"
"- Respond with ONLY the single word portrait, landscape, or square.\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. Begin with the main subject, described completely in the very "
"first sentence:\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. If the subject is a fusion, hybrid, or anything "
"unusual, the first sentence must state in full what is joined to what "
"and exactly how it looks, and that description must be repeated near "
"the end of the passage.\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"
"- Be literal. Interpret the user's request exactly as written. Never "
'rationalize, normalize, or "improve" it: surreal, absurd, or '
'anthropomorphic requests are intentional, not mistakes. A "fountain '
'pen wearing pants" is an anthropomorphized fountain pen character '
"wearing pants, not a pen lying next to a pair of pants.\n"
"- Preserve everything the user specified. Fill in details the user did "
"not specify with coherent choices that fit the request, but never "
"alter, drop, or reinterpret what the user did specify.\n"
"- Decompose concepts. The image model lacks world knowledge, so never "
"rely on a name alone for anything it might misrender (mythical "
"creatures, fictional characters, cultural items, animal breeds, "
"instruments, vehicles). Spell out the visual anatomy: silhouette, body "
"parts, materials, and distinguishing features, with explicit "
"disambiguation. A centaur is a single creature with a human torso, "
"arms, and head seamlessly fused to a horse's front half, the horse's "
"four legs extending from the human's waist - one fused body, not a "
"person riding a horse.\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."
)
IMAGE_PROMPT_VERIFY_SYSTEM_PROMPT = (
"You are the final quality check for an image generation prompt. The "
"image model that will use it has no world knowledge: it renders only "
"what is described literally and silently drops anything it does not "
"understand - a prompt that merely names a centaur without describing "
"the fused human-animal body will produce a plain horse. "
"Given the user's original request and the drafted prompt, judge "
"strictly: would the drafted prompt, taken completely literally, "
"produce exactly what the user asked for, including every unusual, "
"mythical, surreal, or anthropomorphic element? "
"Respond with ONLY the single word PASS if it would. Otherwise respond "
"with ONLY a corrected version of the prompt that would produce exactly "
"what the user asked for: one flowing descriptive passage, the "
"subject's full anatomy and every unusual element described explicitly "
"in the first sentence and repeated near the end, no other text."
)
def parse_image_layout(response: str) -> str:
"""Parse an LLM response into a valid image layout.
Args:
response: The raw LLM response text.
Returns:
One of "portrait", "landscape", or "square". Falls back to "square"
when the response is empty or does not contain a valid layout.
"""
text = response.strip().lower()
for layout in VALID_IMAGE_LAYOUTS:
if re.search(rf"\b{layout}\b", text):
return layout
return DEFAULT_IMAGE_LAYOUT
def build_system_prompt(personality: str, user_info: str) -> str:
"""Assemble a custom-bot system prompt with the length hint and user info.
Args:
personality: The base bot personality / system prompt.
user_info: Preformatted user information to append.
Returns:
The assembled system prompt: personality, a response-length hint, and
the user information block.
"""
return f"{personality}\n{RESPONSE_LENGTH_HINT}\n\nUser Information:\n{user_info}"
def get_user_info(user: discord.User | discord.Member) -> str:
"""Format user information for inclusion in bot prompts.
Reads only presentation attributes off the (User or Member) object, so it
has no runtime dependency on the discord package.
"""
parts: list[str] = []
if user.global_name:
parts.append(f"Global Name: {user.global_name}")
nick = getattr(user, "nick", None)
if nick:
parts.append(f"Nickname: {nick}")
top_role = getattr(user, "top_role", None)
if top_role and top_role.name != "@everyone":
parts.append(f"Top Role: {top_role.name}")
activities = getattr(user, "activities", None)
if activities:
activity_names = [
getattr(a, "name", str(a))
for a in activities
if getattr(a, "name", "") != "custom_status"
]
if activity_names:
parts.append(f"Activities: {', '.join(activity_names)}")
joined_at = getattr(user, "joined_at", None)
if joined_at:
parts.append(f"Joined: {joined_at.strftime('%Y-%m-%d')}")
parts.append(f"Username: {user.name}")
parts.append(f"User ID: {user.id}")
parts.append(
f"Account Created: {user.created_at.strftime('%Y-%m-%d') if user.created_at else 'Unknown'}"
)
return "\n".join(parts)