245 lines
10 KiB
Python
245 lines
10 KiB
Python
import os
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
# ─── Mode / Skill ────────────────────────────────────────────────────────────
|
|
# Pilihan mode AI:
|
|
# - "programmer" : AI Agent untuk koding (default), tool-focused, task-oriented
|
|
# - "roleplayer" : Teman ngobrol / chat companion, conversational, expressive
|
|
MODE = os.getenv("PERSONA_MODE", default="programmer").strip().lower()
|
|
|
|
|
|
# ─── Personality Configuration ───────────────────────────────────────────────
|
|
# Semua parameter personality bisa di-set via .env.
|
|
# Lihat komentar di setiap field untuk pilihan yang tersedia.
|
|
|
|
@dataclass
|
|
class PersonalityConfig:
|
|
"""Konfigurasi personality AI yang berlaku lintas mode."""
|
|
|
|
# Nama panggilan AI (default: "OWL")
|
|
name: str = "OWL"
|
|
|
|
# Gaya bicara:
|
|
# "casual" → santai, gaul
|
|
# "formal" → sopan, profesional
|
|
# "playful" → ceria, suka bercanda
|
|
# "warm" → hangat, ramah
|
|
tone: str = "casual"
|
|
|
|
# Panjang jawaban:
|
|
# "concise" → singkat, to the point
|
|
# "balanced" → sedang (default)
|
|
# "detailed" → panjang, detail
|
|
verbosity: str = "balanced"
|
|
|
|
# Seberapa sering bercanda:
|
|
# "none" → serius, tidak bercanda
|
|
# "light" → sesekali (default)
|
|
# "witty" → sering, jenaka
|
|
humor_level: str = "light"
|
|
|
|
# Bahasa utama:
|
|
# "id" → Indonesia
|
|
# "en" → English
|
|
# "" → auto (sesuai bahasa user)
|
|
language: str = "id"
|
|
|
|
# Suasana hati umum:
|
|
# "cheerful" → ceria, positif
|
|
# "calm" → tenang, menenangkan
|
|
# "energetic" → bersemangat, aktif
|
|
# "sarcastic" → sarkastik, sinis
|
|
mood: str = "cheerful"
|
|
|
|
# Ekspresi khas AI (comma-separated di .env, jadi list di sini)
|
|
# Contoh: "Siap bro!, Haha~, Wkwkwk"
|
|
catchphrases: list = field(default_factory=list)
|
|
|
|
|
|
def _load_personality_from_env() -> PersonalityConfig:
|
|
"""Baca personality config dari environment variables."""
|
|
raw_catchphrases = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
|
|
catchphrases = [c.strip() for c in raw_catchphrases.split(",") if c.strip()] if raw_catchphrases else []
|
|
|
|
return PersonalityConfig(
|
|
name=os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL",
|
|
tone=os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual",
|
|
verbosity=os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced",
|
|
humor_level=os.getenv("PERSONA_HUMOR", default="light").strip().lower() or "light",
|
|
language=os.getenv("PERSONA_LANGUAGE", default="id").strip().lower() or "id",
|
|
mood=os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or "cheerful",
|
|
catchphrases=catchphrases,
|
|
)
|
|
|
|
|
|
# Instance global — di-load sekali saat import
|
|
PERSONALITY = _load_personality_from_env()
|
|
|
|
|
|
# ─── Prompt Builders ─────────────────────────────────────────────────────────
|
|
|
|
def _build_personality_block(cfg: PersonalityConfig) -> str:
|
|
"""Generate deskripsi personality dari config."""
|
|
parts = [f"You are {cfg.name}."]
|
|
|
|
# Tone
|
|
tone_map = {
|
|
"casual": "You speak in a casual, relaxed manner — like chatting with a friend.",
|
|
"formal": "You speak formally and professionally, using polite language.",
|
|
"playful": "You are playful and cheerful, making conversations fun and lighthearted.",
|
|
"warm": "You are warm and friendly, making people feel comfortable and welcomed.",
|
|
}
|
|
parts.append(tone_map.get(cfg.tone, tone_map["casual"]))
|
|
|
|
# Verbosity
|
|
verbosity_map = {
|
|
"concise": "Keep your answers short and to the point.",
|
|
"balanced": "Provide balanced answers — not too brief, not too long.",
|
|
"detailed": "Give thorough, detailed answers with explanations.",
|
|
}
|
|
parts.append(verbosity_map.get(cfg.verbosity, verbosity_map["balanced"]))
|
|
|
|
# Humor
|
|
humor_map = {
|
|
"none": "Stay serious and avoid jokes.",
|
|
"light": "Occasionally sprinkle in light humor when appropriate.",
|
|
"witty": "Be witty and humorous — jokes, puns, and playful banter are welcome.",
|
|
}
|
|
parts.append(humor_map.get(cfg.humor_level, humor_map["light"]))
|
|
|
|
# Language
|
|
if cfg.language == "id":
|
|
parts.append("Always respond in Indonesian (Bahasa Indonesia).")
|
|
elif cfg.language == "en":
|
|
parts.append("Always respond in English.")
|
|
else:
|
|
parts.append("Respond in the same language the user uses.")
|
|
|
|
# Mood
|
|
mood_map = {
|
|
"cheerful": "Your overall mood is cheerful and positive.",
|
|
"calm": "Your overall mood is calm and soothing.",
|
|
"energetic": "Your overall mood is energetic and enthusiastic.",
|
|
"sarcastic": "Your overall mood is sarcastic and dry-humored.",
|
|
}
|
|
parts.append(mood_map.get(cfg.mood, mood_map["cheerful"]))
|
|
|
|
# Catchphrases
|
|
if cfg.catchphrases:
|
|
phrases = ", ".join(f'"{p}"' for p in cfg.catchphrases)
|
|
parts.append(f"You sometimes use these catchphrases: {phrases}.")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def _build_tools_block(tools_definition: list[dict]) -> str:
|
|
"""Generate daftar tools dari tools_definition."""
|
|
lines = [
|
|
"You have access to the following tools:",
|
|
"",
|
|
]
|
|
for i, tool in enumerate(tools_definition, 1):
|
|
name = tool["name"]
|
|
desc = tool["schema"]["function"]["description"]
|
|
lines.append(f"{i}. {name}: {desc}")
|
|
lines.append("")
|
|
lines.append(
|
|
"Use tools by returning tool calls when needed. After receiving tool "
|
|
"results, continue your reasoning. When you have the final answer, "
|
|
"return it as plain text without tool calls."
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _build_programmer_prompt(cfg: PersonalityConfig, tools_definition: list[dict]) -> str:
|
|
"""Build system prompt untuk mode Programmer."""
|
|
lines = [
|
|
_build_personality_block(cfg),
|
|
"",
|
|
"You are a coding agent that assists with software engineering tasks.",
|
|
"",
|
|
_build_tools_block(tools_definition),
|
|
"",
|
|
f"Your workspace directory is: {os.getcwd()}. "
|
|
"All file operations are relative to this directory.",
|
|
"",
|
|
"⚠️ GIT POLICY — IMPORTANT:",
|
|
"- NEVER run 'git add' or 'git commit' automatically after making changes.",
|
|
"- After editing/creating files, always ASK the user first before committing.",
|
|
"- Only run git commands when the user explicitly asks you to commit.",
|
|
"- You may run 'git status', 'git diff', 'git log' freely to inspect state.",
|
|
"- When user asks to commit: show them the changes first, then wait for confirmation.",
|
|
"",
|
|
"RAG capabilities (knowledge retrieval):",
|
|
"- list_collections → see available collections & doc counts.",
|
|
"- create_collection → create a new collection for a new topic.",
|
|
"- delete_collection → permanently remove a collection and its data.",
|
|
"- inspect_collection → learn metadata fields before searching.",
|
|
"- search_knowledge → semantic search + optional metadata filter.",
|
|
"- store_knowledge → save docs with rich metadata for later use.",
|
|
"- ingest_files → read files (with glob patterns) into a collection, auto-chunking.",
|
|
"",
|
|
"You can create collections yourself! When you encounter a new topic,",
|
|
"use create_collection first, then store_knowledge or ingest_files to populate it.",
|
|
"Always inspect_collection to discover metadata keys before filtering.",
|
|
]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _build_roleplayer_prompt(cfg: PersonalityConfig) -> str:
|
|
"""Build system prompt untuk mode Roleplayer."""
|
|
lines = [
|
|
_build_personality_block(cfg),
|
|
"",
|
|
f"You are {cfg.name}, a conversational companion and roleplayer. "
|
|
"Your main purpose is to be an engaging, empathetic, and fun conversation partner.",
|
|
"",
|
|
"Guidelines:",
|
|
"- Stay in character at all times. Be consistent with your personality.",
|
|
"- Be responsive and empathetic — acknowledge the user's feelings and thoughts.",
|
|
"- Ask follow-up questions to keep the conversation flowing naturally.",
|
|
"- Use natural, conversational language — not robotic or overly formal.",
|
|
"- If the user wants to roleplay a scenario, dive into it enthusiastically.",
|
|
"- Adapt your tone and energy to match the mood of the conversation.",
|
|
"- Keep the conversation comfortable and enjoyable.",
|
|
"",
|
|
"Note: You currently do not have access to external tools. "
|
|
"Focus on being a great conversationalist!",
|
|
]
|
|
return "\n".join(lines)
|
|
|
|
|
|
# ─── Public API ──────────────────────────────────────────────────────────────
|
|
|
|
def build_system_prompt(
|
|
tools_definition: list[dict] | None = None,
|
|
mode: str | None = None,
|
|
personality: PersonalityConfig | None = None,
|
|
) -> str:
|
|
"""
|
|
Build system prompt berdasarkan mode dan personality config.
|
|
|
|
Args:
|
|
tools_definition: Daftar tools (required untuk mode programmer).
|
|
mode: "programmer" atau "roleplayer". Default: dari env PERSONA_MODE.
|
|
personality: PersonalityConfig instance. Default: dari env (global PERSONALITY).
|
|
|
|
Returns:
|
|
String system prompt lengkap.
|
|
"""
|
|
selected_mode = (mode or MODE).strip().lower()
|
|
cfg = personality or PERSONALITY
|
|
|
|
if selected_mode == "programmer":
|
|
if tools_definition is None:
|
|
raise ValueError("tools_definition is required for 'programmer' mode")
|
|
return _build_programmer_prompt(cfg, tools_definition)
|
|
elif selected_mode == "roleplayer":
|
|
return _build_roleplayer_prompt(cfg)
|
|
else:
|
|
raise ValueError(
|
|
f"Unknown mode: '{selected_mode}'. "
|
|
f"Available modes: 'programmer', 'roleplayer'"
|
|
)
|