Combat: - Brain now decides fight vs flee based on bravery trait - Bravery > 30 + health > 8 + mob within 6 blocks = FIGHT - Otherwise flee. Combat tasks are non-interruptible. Goals: - 30-second cooldown after completing a goal before it can respawn - Prevents "check out something interesting" loop AI Prompt: - STRICT rules against inventing items/builds/contraptions - "You have NOTHING unless told otherwise" - Must use ONLY the context provided for current activity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
134 lines
4.8 KiB
Python
134 lines
4.8 KiB
Python
"""
|
|
Prompt builder — constructs a concise system prompt from persona config.
|
|
Designed to produce short, grounded responses for Minecraft chat.
|
|
"""
|
|
|
|
from dougbot.db.models import PersonaConfig
|
|
from dougbot.utils.logging import get_logger
|
|
|
|
log = get_logger("ai.prompt_builder")
|
|
|
|
# Short quirk labels
|
|
QUIRK_LABELS = {
|
|
"ocd": "neat and organized",
|
|
"anxiety": "nervous and worried",
|
|
"chatty_cathy": "talkative",
|
|
"life_sim_mode": "emotional about events",
|
|
"pyromaniac": "loves fire",
|
|
"hoarder": "never throws anything away",
|
|
"perfectionist": "high standards",
|
|
"scaredy_cat": "afraid of mobs",
|
|
"architect": "cares about aesthetics",
|
|
"superstitious": "believes in luck and omens",
|
|
"drama_queen": "overdramatic",
|
|
"conspiracy_theorist": "suspicious of everything",
|
|
"pet_parent": "loves animals",
|
|
"trash_talker": "playful roaster",
|
|
"philosopher": "deep thinker",
|
|
"night_owl": "prefers nighttime",
|
|
"kleptomaniac": "borrows without asking",
|
|
"foodie": "picky about food",
|
|
"nomad": "always moving",
|
|
"speedrunner": "hates wasting time",
|
|
"tinker": "loves redstone",
|
|
"prankster": "enjoys pranks",
|
|
"doomsday_prepper": "always preparing for disaster",
|
|
}
|
|
|
|
# Slider to one-word descriptors at extremes
|
|
TRAIT_WORDS = {
|
|
"bravery": ("cowardly", None, "fearless"),
|
|
"sociability": ("shy", None, "very social"),
|
|
"patience": ("impatient", None, "very patient"),
|
|
"ambition": ("lazy", None, "ambitious"),
|
|
"empathy": ("cold", None, "caring"),
|
|
"curiosity": ("unadventurous", None, "curious"),
|
|
"generosity": ("greedy", None, "generous"),
|
|
"sarcasm": ("sincere", None, "sarcastic"),
|
|
"orderliness": ("messy", None, "neat"),
|
|
"loyalty": ("independent", None, "loyal"),
|
|
"stubbornness": ("agreeable", None, "stubborn"),
|
|
"self_awareness": ("oblivious", None, "self-aware"),
|
|
"risk_tolerance": ("cautious", None, "reckless"),
|
|
"creativity": ("conventional", None, "creative"),
|
|
"work_ethic": ("lazy", None, "hardworking"),
|
|
}
|
|
|
|
|
|
def build_system_prompt(
|
|
name: str,
|
|
age: int,
|
|
persona: PersonaConfig,
|
|
relationships: list[dict] | None = None,
|
|
context: dict | None = None,
|
|
custom_notes: str = "",
|
|
) -> str:
|
|
"""Build a concise system prompt."""
|
|
|
|
# Identity
|
|
if persona.believes_real:
|
|
identity = f"You are {name}, a {age}-year-old person. This world is your real life."
|
|
else:
|
|
identity = f"You are {name}, a {age}-year-old AI playing Minecraft."
|
|
|
|
# Personality — only mention notable traits (not middle-of-road ones)
|
|
traits = []
|
|
sliders = persona.to_dict()["sliders"]
|
|
for trait_name, value in sliders.items():
|
|
words = TRAIT_WORDS.get(trait_name)
|
|
if not words:
|
|
continue
|
|
low, _, high = words
|
|
if value < 30 and low:
|
|
traits.append(low)
|
|
elif value > 70 and high:
|
|
traits.append(high)
|
|
|
|
# Active quirks
|
|
quirks = persona.get_active_quirks()
|
|
quirk_words = [QUIRK_LABELS.get(q, q) for q in quirks]
|
|
|
|
# Combine personality into one line
|
|
all_traits = traits + quirk_words
|
|
personality = f"Personality: {', '.join(all_traits)}." if all_traits else ""
|
|
|
|
# Profanity
|
|
lang = "No profanity - use clean substitutes." if persona.profanity_filter else ""
|
|
|
|
# Custom notes
|
|
custom = custom_notes.strip() if custom_notes else ""
|
|
|
|
# Context — what Doug is currently doing/seeing
|
|
context_line = ""
|
|
if context:
|
|
ctx_parts = []
|
|
if context.get("current_action") and context["current_action"] != "idle":
|
|
ctx_parts.append(f"Currently: {context['current_action']}")
|
|
if context.get("health", 20) < 10:
|
|
ctx_parts.append(f"Low health ({context['health']})")
|
|
if context.get("nearby_players"):
|
|
ctx_parts.append(f"With: {', '.join(context['nearby_players'])}")
|
|
if context.get("time_of_day") == "night":
|
|
ctx_parts.append("It's night")
|
|
if context.get("nearby_hostiles"):
|
|
ctx_parts.append(f"Mobs nearby: {', '.join(context['nearby_hostiles'][:2])}")
|
|
if ctx_parts:
|
|
context_line = f"Right now: {'. '.join(ctx_parts)}."
|
|
|
|
# Build the prompt — keep it SHORT
|
|
parts = [
|
|
identity,
|
|
personality,
|
|
lang,
|
|
custom,
|
|
context_line,
|
|
"",
|
|
"STRICT RULES:",
|
|
"- ONE short sentence. Under 15 words.",
|
|
"- NEVER invent things you have or did. You have NOTHING unless told otherwise.",
|
|
"- NEVER mention items, builds, or contraptions unless the context says you have them.",
|
|
"- If asked what you are doing, use ONLY the 'Right now' context above. If no context, say not much.",
|
|
"- Plain text only. No name prefix.",
|
|
]
|
|
|
|
return "\n".join(p for p in parts if p)
|