diff --git a/.env.example b/.env.example index feb5092..b912fe9 100644 --- a/.env.example +++ b/.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 + diff --git a/config.py b/config.py index 846050a..4c0b2a0 100644 --- a/config.py +++ b/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) + diff --git a/hendrik.py b/hendrik.py index a3e5ecd..1009782 100644 --- a/hendrik.py +++ b/hendrik.py @@ -1,39 +1,42 @@ 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 ), + + 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 ), gadget.tools_mapping( schema = rag.schema_create_collection, handler = rag.create_collection ), 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 ` atau `--workspace ` - workspace = None - i = 1 + workspace = None + i = 1 while i < len(sys.argv): if sys.argv[i] in ('-w', '--workspace') and i + 1 < len(sys.argv): workspace = sys.argv[i + 1] @@ -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,24 +52,21 @@ 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, - llm_client = llm_client, - tools_definition = tools_definition, - TOOLS = TOOLS, - TOOL_HANDLERS = TOOL_HANDLERS, - build_system_prompt = gadget.build_system_prompt, - agent_max_iterations= config.AGENT_MAX_ITERATIONS, - muc_rooms = muc_rooms, + jid = config.XMPP_USERNAME, + password = config.XMPP_PASSWORD, + llm_client = llm_client, + tools_definition = tools_definition, + TOOLS = TOOLS, + TOOL_HANDLERS = TOOL_HANDLERS, + 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() + diff --git a/scripts/gadget.py b/scripts/gadget.py index 4a6991a..ddeebae 100644 --- a/scripts/gadget.py +++ b/scripts/gadget.py @@ -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) - diff --git a/scripts/persona.py b/scripts/persona.py new file mode 100644 index 0000000..c9e1fb4 --- /dev/null +++ b/scripts/persona.py @@ -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'" + ) diff --git a/services/xmpp_client.py b/services/xmpp_client.py index 4e0f823..5dfb664 100644 --- a/services/xmpp_client.py +++ b/services/xmpp_client.py @@ -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,7 +293,8 @@ class XMPPClient(ClientXMPP): session.add_message('user', body) - self._schedule_send(jid, f'> {body}\nThinking...') + if self._mode != 'roleplayer': + self._schedule_send(jid, f'> {body}\nThinking...') # Delay 1: simulasi membaca pesan user if self._loop and not self._loop.is_closed(): @@ -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,7 +319,8 @@ class XMPPClient(ClientXMPP): prefixed = f'[{nick}] {body}' session.add_message('user', prefixed) - self._schedule_send(room, f'> [{nick}] {body}\nThinking...', mtype='groupchat') + if self._mode != 'roleplayer': + self._schedule_send(room, f'> [{nick}] {body}\nThinking...', mtype='groupchat') # Delay 1: simulasi membaca pesan user if self._loop and not self._loop.is_closed(): @@ -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,7 +347,10 @@ class XMPPClient(ClientXMPP): tnames = [tc['function']['name'] for tc in response.tool_calls] print(f'[{_ts()}] Using tools: {", ".join(tnames)}') - self._schedule_send(to, f'> {quote}\nUsing: {", ".join(tnames)}', mtype) + + # 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: result = self._execute_tool(tc) @@ -354,7 +363,12 @@ class XMPPClient(ClientXMPP): if response.content: print(f'[{_ts()}] Response sent ({len(response.content)} chars)') session.messages.append({'role': 'assistant', 'content': response.content}) - self._schedule_send(to, f'> {quote}\n{response.content}', mtype) + + # 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 print(f'[{_ts()}] Max iterations ({self._max_iterations}) reached') @@ -362,7 +376,11 @@ class XMPPClient(ClientXMPP): 'role': 'assistant', 'content': 'Max iterations reached without final answer.', }) - self._schedule_send(to, f'> {quote}\nMax iterations reached without final answer.', mtype) + + 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): tname = tool_call['function']['name'] diff --git a/tui/app.py b/tui/app.py index 6936db1..63260ed 100644 --- a/tui/app.py +++ b/tui/app.py @@ -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: