Sidreds06's picture
Initial commit
a3c7b61
raw
history blame
6.36 kB
"""
prompts/personas.py
===================
Reads persona definitions from personas.yaml and exposes the same public
symbols as the original hard-coded version:
RESPONSE_STYLE
MENTAL_PROMPT, PHYSICAL_PROMPT, SPIRITUAL_PROMPT, VOCATIONAL_PROMPT,
ENVIRONMENTAL_PROMPT, FINANCIAL_PROMPT, SOCIAL_PROMPT, INTELLECTUAL_PROMPT
MENTAL_FULL, PHYSICAL_FULL, … (eight *_FULL variables)
PERSONA_PROMPTS – dict with persona keys plus "main"
"""
from __future__ import annotations
from pathlib import Path
from textwrap import dedent
import yaml
# ---------------------------------------------------------------------------
# Locate & load YAML
# ---------------------------------------------------------------------------
_YAML_PATH = Path(__file__).with_name("personas.yaml")
_DATA: dict
try:
_DATA = yaml.safe_load(_YAML_PATH.read_text(encoding="utf-8"))
except FileNotFoundError as err:
raise FileNotFoundError(
f"[personas] Could not find {_YAML_PATH}. "
"Make sure personas.yaml lives beside personas.py."
) from err
# ---------------------------------------------------------------------------
# Shared guidance blocks
# ---------------------------------------------------------------------------
RESPONSE_STYLE: str = dedent(_DATA["response_style"]).strip()
_BOUNDARIES_COMMON: str = dedent(_DATA["boundaries_common"]).strip()
_PROFESSIONAL_BOUNDARIES: str = dedent(_DATA["professional_boundaries"]).strip()
_USER_CONTEXT_HANDLING: str = dedent(_DATA["user_context_handling"]).strip()
_CONVERSATION_CONTINUITY: str = dedent(_DATA["conversation_continuity"]).strip()
_PERSONA_SWITCHING: str = dedent(_DATA["persona_switching"]).strip()
_SAFETY_ESCALATION: str = dedent(_DATA["safety_escalation"]).strip()
_CRISIS_RESOURCES: str = dedent(_DATA["crisis_resources"]).strip()
# Combine all style/guidance sections into one for easy persona prompt merging
FULL_RESPONSE_STYLE = "\n\n".join([
RESPONSE_STYLE,
"**Boundaries Common**\n" + _BOUNDARIES_COMMON,
"**Professional Boundaries**\n" + _PROFESSIONAL_BOUNDARIES,
"**User Context Handling**\n" + _USER_CONTEXT_HANDLING,
"**Conversation Continuity**\n" + _CONVERSATION_CONTINUITY,
"**Persona Switching**\n" + _PERSONA_SWITCHING,
"**Safety Escalation**\n" + _SAFETY_ESCALATION,
"**Crisis Resources**\n" + _CRISIS_RESOURCES,
])
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
# Whether a persona should number its focus list (customize here as needed)
_NUMBERED_FOCUS = {"mental"}
def _build_focus_lines(key: str, items: list[str]) -> list[str]:
"""Return formatted primary-focus lines – numbered or bulleted."""
if key in _NUMBERED_FOCUS:
return [f"{i + 1}. {item}" for i, item in enumerate(items)]
return [f"• {item}" for item in items]
def _compose_prompt(key: str, p: dict) -> str:
"""Compose the persona prompt text (without full response style)."""
sections: list[str] = [
f"You are the {p['display_name']}.",
"",
f"**Mission** – {p['mission']}",
f"**Tone & Voice**\n{dedent(p['tone_voice']).strip()}",
"**Primary Focus Areas**",
*_build_focus_lines(key, p["primary_focus"]),
]
# Persona-specific boundary additions (if any)
if p.get("extra_boundaries"):
sections.append(dedent(p["extra_boundaries"]).strip())
# Join with blank lines, remove empties
return "\n\n".join(filter(None, sections))
# ---------------------------------------------------------------------------
# Build all personas
# ---------------------------------------------------------------------------
_PERSONA_PROMPTS_RAW: dict[str, str] = {
k: _compose_prompt(k, v) for k, v in _DATA["personas"].items()
}
# Expose individual raw-prompt constants
MENTAL_PROMPT = _PERSONA_PROMPTS_RAW["mental"]
PHYSICAL_PROMPT = _PERSONA_PROMPTS_RAW["physical"]
SPIRITUAL_PROMPT = _PERSONA_PROMPTS_RAW["spiritual"]
VOCATIONAL_PROMPT = _PERSONA_PROMPTS_RAW["vocational"]
ENVIRONMENTAL_PROMPT = _PERSONA_PROMPTS_RAW["environmental"]
FINANCIAL_PROMPT = _PERSONA_PROMPTS_RAW["financial"]
SOCIAL_PROMPT = _PERSONA_PROMPTS_RAW["social"]
INTELLECTUAL_PROMPT = _PERSONA_PROMPTS_RAW["intellectual"]
# Combine with FULL_RESPONSE_STYLE for final persona prompts
MENTAL_FULL = f"{MENTAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
PHYSICAL_FULL = f"{PHYSICAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
SPIRITUAL_FULL = f"{SPIRITUAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
VOCATIONAL_FULL = f"{VOCATIONAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
ENVIRONMENTAL_FULL = f"{ENVIRONMENTAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
FINANCIAL_FULL = f"{FINANCIAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
SOCIAL_FULL = f"{SOCIAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
INTELLECTUAL_FULL = f"{INTELLECTUAL_PROMPT}\n{FULL_RESPONSE_STYLE}"
# Public dict identical to the original
PERSONA_PROMPTS: dict[str, str] = {
"mental": MENTAL_FULL,
"physical": PHYSICAL_FULL,
"spiritual": SPIRITUAL_FULL,
"vocational": VOCATIONAL_FULL,
"environmental": ENVIRONMENTAL_FULL,
"financial": FINANCIAL_FULL,
"social": SOCIAL_FULL,
"intellectual": INTELLECTUAL_FULL,
"main": (
"You are **Tabi**, a compassionate, holistic wellness companion.\n"
"Listen closely, determine which of the eight wellness dimensions (mental, physical, spiritual, vocational, environmental, financial, social, intellectual) best fits the user's needs, and respond naturally using that coach’s empathetic style.\n"
"If the dimension is unclear, kindly ask a clarifying question first.\n"
"Always reply warmly, practically, and conversationally, just like a caring friend would.\n\n"
f"{FULL_RESPONSE_STYLE}"
),
}
# ---------------------------------------------------------------------------
# Clean up internal names from module namespace
# ---------------------------------------------------------------------------
del yaml, Path, dedent, _DATA, _YAML_PATH, _compose_prompt, _build_focus_lines
del _PERSONA_PROMPTS_RAW, _BOUNDARIES_COMMON, _PROFESSIONAL_BOUNDARIES
del _USER_CONTEXT_HANDLING, _CONVERSATION_CONTINUITY, _PERSONA_SWITCHING
del _SAFETY_ESCALATION, _CRISIS_RESOURCES, _NUMBERED_FOCUS