Persona
This commit is contained in:
parent
dc5fb67ac1
commit
8a0363b985
13
.env.example
13
.env.example
@ -14,3 +14,16 @@ XMPP_USERNAME=
|
||||
XMPP_PASSWORD=
|
||||
# XMPP_MUC_ROOMS=room1@conference.server,room2@conference.server
|
||||
|
||||
# ─── Persona / Mode ──────────────────────────────────────────────────────────
|
||||
# Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol)
|
||||
PERSONA_MODE=programmer
|
||||
|
||||
# Personality
|
||||
PERSONA_NAME=OWL
|
||||
PERSONA_TONE=casual # casual | formal | playful | warm
|
||||
PERSONA_VERBOSITY=balanced # concise | balanced | detailed
|
||||
PERSONA_HUMOR=light # none | light | witty
|
||||
PERSONA_LANGUAGE=id # id | en | (kosong = auto)
|
||||
PERSONA_MOOD=cheerful # cheerful | calm | energetic | sarcastic
|
||||
# PERSONA_CATCHPHRASES=Siap bro!, Haha~, Wkwkwk
|
||||
|
||||
|
||||
55
config.py
55
config.py
@ -3,27 +3,54 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# LLM Configuration
|
||||
llm_baseurl = os.getenv("LLM_BASE_URL", default="http://localhost:11434/v1" )
|
||||
llm_model = os.getenv("LLM_MODEL", default="granite4.1:8b" )
|
||||
llm_api_key = os.getenv("LLM_API_KEY", default="ollama" )
|
||||
llm_timeout = int( os.getenv("LLM_TIMEOUT", default="600" ) )
|
||||
# Agent Configuration
|
||||
AGENT_MAX_ITERATIONS = int( os.getenv("AGENT_MAX_ITERATIONS", default="10" ) )
|
||||
# Tool Configuration (for future use)
|
||||
MAX_TOOL_OUTPUT = int( os.getenv("MAX_TOOL_OUTPUT", default="4000" ) )
|
||||
# RAG Configuration
|
||||
RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default="chroma_db" )
|
||||
# Embedding: ChromaDB ONNX default (all-MiniLM-L6-v2, lokal, tidak perlu API call)
|
||||
# XMPP Configuration
|
||||
|
||||
AGENT_MAX_ITERATIONS = int( os.getenv("AGENT_MAX_ITERATIONS", default="30" ) )
|
||||
|
||||
MAX_TOOL_OUTPUT = int( os.getenv("MAX_TOOL_OUTPUT", default="40000" ) )
|
||||
|
||||
RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default="chroma_db" ) # Embedding: ChromaDB ONNX default (all-MiniLM-L6-v2, lokal, tidak perlu API call)
|
||||
|
||||
XMPP_ENABLED = os.getenv("XMPP_ENABLED", default="False" ).strip().lower() in ("true", "1", "yes")
|
||||
XMPP_USERNAME = os.getenv("XMPP_USERNAME", default="" )
|
||||
XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="" )
|
||||
XMPP_MUC_ROOMS = os.getenv("XMPP_MUC_ROOMS", default="" )
|
||||
XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default="" ) # custom nick MUC (kosong = pakai username)
|
||||
XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default="" ) # custom nick MUC (empty = use username)
|
||||
|
||||
# ─── Persona / Mode Configuration ────────────────────────────────────────────
|
||||
# Pilihan mode AI:
|
||||
# "programmer" → AI Agent untuk koding (default), tool-focused
|
||||
# "roleplayer" → Teman ngobrol / chat companion, conversational
|
||||
PERSONA_MODE = os.getenv("PERSONA_MODE", default="programmer").strip().lower()
|
||||
|
||||
# Personality — nama panggilan AI (default: "OWL")
|
||||
PERSONA_NAME = os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL"
|
||||
|
||||
# Gaya bicara: "casual" | "formal" | "playful" | "warm"
|
||||
PERSONA_TONE = os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual"
|
||||
|
||||
# Panjang jawaban: "concise" | "balanced" | "detailed"
|
||||
PERSONA_VERBOSITY = os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced"
|
||||
|
||||
# Humor: "none" | "light" | "witty"
|
||||
PERSONA_HUMOR = os.getenv("PERSONA_HUMOR", default="light").strip().lower() or "light"
|
||||
|
||||
# Bahasa: "id" | "en" | "" (auto)
|
||||
PERSONA_LANGUAGE = os.getenv("PERSONA_LANGUAGE", default="id").strip().lower() or "id"
|
||||
|
||||
# Mood: "cheerful" | "calm" | "energetic" | "sarcastic"
|
||||
PERSONA_MOOD = os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or "cheerful"
|
||||
|
||||
# Catchphrases khas AI (comma-separated)
|
||||
# Contoh: "Siap bro!, Haha~, Wkwkwk"
|
||||
PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
|
||||
|
||||
# Humanize Delay Configuration (anti-bot detection)
|
||||
READ_DELAY_MIN = float( os.getenv("READ_DELAY_MIN", default="1.0" ) ) # min reading delay (detik)
|
||||
READ_DELAY_MAX = float( os.getenv("READ_DELAY_MAX", default="2.0" ) ) # max reading delay (detik)
|
||||
TYPING_SPEED = float( os.getenv("TYPING_SPEED", default="15.0" ) ) # karakter per detik (kecepatan mengetik)
|
||||
TYPING_MAX = float( os.getenv("TYPING_MAX", default="10.0" ) ) # batas max typing delay (detik)
|
||||
READ_DELAY_MIN = float( os.getenv("READ_DELAY_MIN", default="1.0" ) ) # min reading delay (second)
|
||||
READ_DELAY_MAX = float( os.getenv("READ_DELAY_MAX", default="2.0" ) ) # max reading delay (second)
|
||||
TYPING_SPEED = float( os.getenv("TYPING_SPEED", default="15.0" ) ) # characters per second
|
||||
TYPING_MAX = float( os.getenv("TYPING_MAX", default="10.0" ) ) # max typing delay limit (second)
|
||||
|
||||
|
||||
23
hendrik.py
23
hendrik.py
@ -1,18 +1,22 @@
|
||||
import os, sys
|
||||
import config
|
||||
|
||||
from services.xmpp_client import XMPPClient
|
||||
|
||||
from scripts.llm_client import LLMClient
|
||||
from tools import coder, rag, carrack
|
||||
from scripts import gadget
|
||||
from scripts.persona import build_system_prompt, PERSONALITY, MODE
|
||||
|
||||
# Daftar tools yang tersedia
|
||||
tools_definition = [
|
||||
|
||||
gadget.tools_mapping( schema = coder.schema_read_file, handler = coder.read_file ),
|
||||
gadget.tools_mapping( schema = coder.schema_write_file, handler = coder.write_file ),
|
||||
gadget.tools_mapping( schema = coder.schema_edit_file, handler = coder.edit_file ),
|
||||
gadget.tools_mapping( schema = coder.schema_run_bash, handler = coder.run_bash ),
|
||||
gadget.tools_mapping( schema = coder.schema_search_code, handler = coder.search_code ),
|
||||
gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ),
|
||||
|
||||
gadget.tools_mapping( schema = rag.schema_ingest_files, handler = rag.ingest_files ),
|
||||
gadget.tools_mapping( schema = rag.schema_store_knowledge, handler = rag.store_knowledge ),
|
||||
gadget.tools_mapping( schema = rag.schema_search_knowledge, handler = rag.search_knowledge ),
|
||||
@ -20,18 +24,17 @@ tools_definition = [
|
||||
gadget.tools_mapping( schema = rag.schema_delete_collection, handler = rag.delete_collection ),
|
||||
gadget.tools_mapping( schema = rag.schema_list_collections, handler = rag.list_collections ),
|
||||
gadget.tools_mapping( schema = rag.schema_inspect_collection, handler = rag.inspect_collection ),
|
||||
|
||||
gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ),
|
||||
|
||||
]
|
||||
|
||||
# Ekstrak dari tools_definition ke dua format berbeda
|
||||
TOOLS = gadget.tool_schemas (tools_definition)
|
||||
TOOL_HANDLERS = gadget.tool_handlers (tools_definition)
|
||||
|
||||
|
||||
def main():
|
||||
llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout)
|
||||
|
||||
# Parsing arguments `-w <dir>` atau `--workspace <dir>`
|
||||
workspace = None
|
||||
i = 1
|
||||
while i < len(sys.argv):
|
||||
@ -41,7 +44,6 @@ def main():
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Apply workspace jika ada
|
||||
if workspace:
|
||||
resolved = os.path.abspath(workspace)
|
||||
if not os.path.isdir(resolved):
|
||||
@ -50,12 +52,9 @@ def main():
|
||||
os.chdir(resolved)
|
||||
|
||||
if config.XMPP_ENABLED:
|
||||
from services.xmpp_client import XMPPClient
|
||||
|
||||
muc_rooms = []
|
||||
if config.XMPP_MUC_ROOMS.strip():
|
||||
muc_rooms = [r.strip() for r in config.XMPP_MUC_ROOMS.split(',') if r.strip()]
|
||||
|
||||
client = XMPPClient(
|
||||
jid = config.XMPP_USERNAME,
|
||||
password = config.XMPP_PASSWORD,
|
||||
@ -63,11 +62,11 @@ def main():
|
||||
tools_definition = tools_definition,
|
||||
TOOLS = TOOLS,
|
||||
TOOL_HANDLERS = TOOL_HANDLERS,
|
||||
build_system_prompt = gadget.build_system_prompt,
|
||||
build_system_prompt = build_system_prompt,
|
||||
agent_max_iterations = config.AGENT_MAX_ITERATIONS,
|
||||
muc_rooms = muc_rooms,
|
||||
)
|
||||
client.start() # blocking, headless
|
||||
client.start()
|
||||
else:
|
||||
from tui import HendrikTUI
|
||||
HendrikTUI(
|
||||
@ -75,10 +74,10 @@ def main():
|
||||
tools_definition = tools_definition,
|
||||
TOOLS = TOOLS,
|
||||
TOOL_HANDLERS = TOOL_HANDLERS,
|
||||
build_system_prompt = gadget.build_system_prompt,
|
||||
build_system_prompt = build_system_prompt,
|
||||
agent_max_iterations = config.AGENT_MAX_ITERATIONS,
|
||||
).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@ -1,57 +1,12 @@
|
||||
import os
|
||||
|
||||
from .persona import build_system_prompt
|
||||
|
||||
def tools_mapping(schema, handler, name=None):
|
||||
tool_name = name or schema["function"]["name"]
|
||||
return {"name": tool_name, "schema": schema, "handler": handler}
|
||||
|
||||
|
||||
def tool_schemas(tools_definition):
|
||||
return [t["schema"] for t in tools_definition]
|
||||
|
||||
|
||||
def tool_handlers(tools_definition):
|
||||
return {t["name"]: t["handler"] for t in tools_definition}
|
||||
|
||||
|
||||
def build_system_prompt(tools_definition):
|
||||
lines = [
|
||||
"You are a coding agent that assists with software engineering tasks. "
|
||||
"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.extend([
|
||||
"",
|
||||
"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.",
|
||||
"",
|
||||
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)
|
||||
|
||||
|
||||
244
scripts/persona.py
Normal file
244
scripts/persona.py
Normal file
@ -0,0 +1,244 @@
|
||||
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'"
|
||||
)
|
||||
@ -9,6 +9,7 @@ from slixmpp import ClientXMPP
|
||||
from services.session_manager import SessionManager
|
||||
|
||||
import config
|
||||
from scripts.persona import MODE as PERSONA_MODE
|
||||
|
||||
# Anti-ban: delay constants for MUC rejoin behavior
|
||||
MUC_REJOIN_INITIAL_DELAY = 5.0 # detik, delay awal sebelum rejoin
|
||||
@ -47,6 +48,7 @@ class XMPPClient(ClientXMPP):
|
||||
self._TOOL_HANDLERS = TOOL_HANDLERS
|
||||
self._build_system_prompt = build_system_prompt
|
||||
self._max_iterations = agent_max_iterations
|
||||
self._mode = PERSONA_MODE
|
||||
self._muc_rooms = muc_rooms or []
|
||||
# Custom nick dari config, fallback ke username JID
|
||||
self._muc_nick = config.XMPP_NICKNAME.strip() or jid.split('@')[0]
|
||||
@ -277,7 +279,7 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
def _process_dm(self, jid, body):
|
||||
session = self._session_mgr.get_or_create(
|
||||
jid, self._build_system_prompt(self._tools_def)
|
||||
jid, self._build_system_prompt(tools_definition=self._tools_def)
|
||||
)
|
||||
session.cancel_timer()
|
||||
|
||||
@ -291,6 +293,7 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
session.add_message('user', body)
|
||||
|
||||
if self._mode != 'roleplayer':
|
||||
self._schedule_send(jid, f'> {body}\nThinking...')
|
||||
|
||||
# Delay 1: simulasi membaca pesan user
|
||||
@ -303,7 +306,7 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
def _process_muc(self, room, nick, body):
|
||||
session = self._session_mgr.get_or_create(
|
||||
room, self._build_system_prompt(self._tools_def)
|
||||
room, self._build_system_prompt(tools_definition=self._tools_def)
|
||||
)
|
||||
session.cancel_timer()
|
||||
|
||||
@ -316,6 +319,7 @@ class XMPPClient(ClientXMPP):
|
||||
prefixed = f'[{nick}] {body}'
|
||||
session.add_message('user', prefixed)
|
||||
|
||||
if self._mode != 'roleplayer':
|
||||
self._schedule_send(room, f'> [{nick}] {body}\nThinking...', mtype='groupchat')
|
||||
|
||||
# Delay 1: simulasi membaca pesan user
|
||||
@ -327,6 +331,8 @@ class XMPPClient(ClientXMPP):
|
||||
session.start_timer(300, self._timeout_session, room, 'groupchat')
|
||||
|
||||
def _agent_loop(self, session, to, quote, mtype):
|
||||
is_roleplayer = self._mode == 'roleplayer'
|
||||
|
||||
for step in range(self._max_iterations):
|
||||
print(f'[{_ts()}] Step {step + 1} — calling LLM...')
|
||||
response = self._llm.chat(session.messages, tools=self._TOOLS)
|
||||
@ -341,6 +347,9 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
tnames = [tc['function']['name'] for tc in response.tool_calls]
|
||||
print(f'[{_ts()}] Using tools: {", ".join(tnames)}')
|
||||
|
||||
# Roleplayer tidak perlu kirim status "Using: ..."
|
||||
if not is_roleplayer:
|
||||
self._schedule_send(to, f'> {quote}\nUsing: {", ".join(tnames)}', mtype)
|
||||
|
||||
for tc in response.tool_calls:
|
||||
@ -354,6 +363,11 @@ class XMPPClient(ClientXMPP):
|
||||
if response.content:
|
||||
print(f'[{_ts()}] Response sent ({len(response.content)} chars)')
|
||||
session.messages.append({'role': 'assistant', 'content': response.content})
|
||||
|
||||
# Roleplayer: kirim langsung isinya, tanpa prefix
|
||||
if is_roleplayer:
|
||||
self._schedule_send(to, response.content, mtype)
|
||||
else:
|
||||
self._schedule_send(to, f'> {quote}\n{response.content}', mtype)
|
||||
return
|
||||
|
||||
@ -362,6 +376,10 @@ class XMPPClient(ClientXMPP):
|
||||
'role': 'assistant',
|
||||
'content': 'Max iterations reached without final answer.',
|
||||
})
|
||||
|
||||
if is_roleplayer:
|
||||
self._schedule_send(to, 'Max iterations reached without final answer.', mtype)
|
||||
else:
|
||||
self._schedule_send(to, f'> {quote}\nMax iterations reached without final answer.', mtype)
|
||||
|
||||
def _execute_tool(self, tool_call):
|
||||
|
||||
@ -40,8 +40,7 @@ class HendrikTUI:
|
||||
stdscr.keypad(True)
|
||||
stdscr.refresh()
|
||||
|
||||
self.messages = [{"role": "system",
|
||||
"content": self.build_system_prompt(self.tools_def)}]
|
||||
self.messages = [{"role": "system", "content": self.build_system_prompt(tools_definition=self.tools_def)}]
|
||||
log(self, "welcome", WELCOME_ART)
|
||||
|
||||
while self.running:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user