diff --git a/.env.example b/.env.example index 8837875..9d885de 100644 --- a/.env.example +++ b/.env.example @@ -24,14 +24,21 @@ AGENT_MAX_ITERATIONS=20 AGENT_MAX_TOOL_OUTPUT=4000 # Personality -PERSONA_MODE=programmer # Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol) -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=Hai, Gimana kabarnya?, Wkwkwk +# Character preset — baca dari directory character/ +# Kosongkan untuk memakai PERSONA_* dari .env. +# Contoh: AGENT_CHARACTER=hendrik +AGENT_CHARACTER=hendrik + +#PERSONA_MODE=programmer # Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol) +#PERSONA_NAME=OWL +#PERSONA_AGE= # Opsional, contoh: 24 +#PERSONA_GENDER= # Opsional, contoh: male | female | non-binary +#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=Hai, Gimana kabarnya?, Wkwkwk XMPP_ENABLED=False diff --git a/character/hendrik b/character/hendrik new file mode 100644 index 0000000..fb09d28 --- /dev/null +++ b/character/hendrik @@ -0,0 +1,9 @@ +PERSONA_MODE=programmer +PERSONA_NAME=Hendrik +PERSONA_AGE=35 +PERSONA_GENDER=male +PERSONA_TONE=formal +PERSONA_VERBOSITY=concise +PERSONA_HUMOR=none +PERSONA_LANGUAGE=id +PERSONA_MOOD=calm \ No newline at end of file diff --git a/config.py b/config.py index 598d4ee..0cf6a84 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,6 @@ import os from dotenv import load_dotenv +from pathlib import Path load_dotenv() @@ -29,6 +30,12 @@ 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" +# Persona age (optional) +PERSONA_AGE = os.getenv("PERSONA_AGE", default="").strip() + +# Persona gender (optional) +PERSONA_GENDER = os.getenv("PERSONA_GENDER", default="").strip() + # Gaya bicara: "casual" | "formal" | "playful" | "warm" PERSONA_TONE = os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual" @@ -48,6 +55,44 @@ PERSONA_MOOD = os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or # Contoh: "Siap bro!, Haha~, Wkwkwk" PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip() +# Character preset — baca dari directory character/ +CHARACTER_DIR = Path(__file__).resolve().parent / "character" +AGENT_CHARACTER = os.getenv("AGENT_CHARACTER", default="").strip().lower() +CHARACTER_CONFIG_PATH = CHARACTER_DIR / AGENT_CHARACTER if AGENT_CHARACTER else None +if CHARACTER_CONFIG_PATH and CHARACTER_CONFIG_PATH.is_file(): + _character_env = {} + for line in CHARACTER_CONFIG_PATH.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + key, value = line.split("=", 1) + _character_env[key.strip()] = value.strip() + + _character_overrides = { + "PERSONA_MODE": PERSONA_MODE, + "PERSONA_NAME": PERSONA_NAME, + "PERSONA_AGE": PERSONA_AGE, + "PERSONA_GENDER": PERSONA_GENDER, + "PERSONA_TONE": PERSONA_TONE, + "PERSONA_VERBOSITY": PERSONA_VERBOSITY, + "PERSONA_HUMOR": PERSONA_HUMOR, + "PERSONA_LANGUAGE": PERSONA_LANGUAGE, + "PERSONA_MOOD": PERSONA_MOOD, + "PERSONA_CATCHPHRASES": PERSONA_CATCHPHRASES, + } + for key, fallback in _character_overrides.items(): + value = _character_env.get(key, fallback).strip() + if key in {"PERSONA_MODE", "PERSONA_TONE", "PERSONA_VERBOSITY", "PERSONA_HUMOR", "PERSONA_LANGUAGE", "PERSONA_MOOD"}: + value = value.lower() or fallback + if key == "PERSONA_NAME" and not value: + value = fallback + _character_overrides[key] = value + + for key, value in _character_overrides.items(): + globals()[key] = value + os.environ[key] = value + # Selective response: true = roleplayer hanya respon kalau ada mention/relevansi (default). # false = roleplayer semua pesan ikut respon (seperti biasa, tanpa filter). XMPP_SELECTIVE_RESPONSE = os.getenv("XMPP_SELECTIVE_RESPONSE", default="true").strip().lower() in ("true", "1", "yes") diff --git a/scripts/llm_client.py b/scripts/llm_client.py index 93049ab..922d75b 100644 --- a/scripts/llm_client.py +++ b/scripts/llm_client.py @@ -36,15 +36,21 @@ class LLMClient: message = response['choices'][0]['message'] return self.Message(message) except urllib.error.HTTPError as e: + body_text = "" + try: + body_text = e.read().decode('utf-8', errors='replace') + except Exception: + pass if tools and e.code == 404: try: - body = json.loads(e.read().decode('utf-8')) + body = json.loads(body_text) if body_text else {} if 'tool use' in body.get('error', {}).get('message', '').lower(): result = self.chat(messages, tools=None) result.warning = "Tool calling not supported by this model. Running in chat-only mode." return result except Exception: pass - return self.Message({'content': f"HTTP Error: {e.code} {e.reason}", 'tool_calls': None}) + detail = f" - {body_text[:500]}" if body_text else "" + return self.Message({'content': f"HTTP Error: {e.code} {e.reason}{detail}", 'tool_calls': None}) except Exception as e: return self.Message({'content': f"Error: {str(e)}", 'tool_calls': None}) diff --git a/scripts/persona.py b/scripts/persona.py index 16d48f8..9cd3c65 100644 --- a/scripts/persona.py +++ b/scripts/persona.py @@ -20,6 +20,12 @@ class PersonalityConfig: # Nama panggilan AI (default: "OWL") name: str = "OWL" + # Umur persona AI (opsional; kosong bila tidak ingin ditampilkan) + age: str = "" + + # Jenis kelamin persona AI (opsional; kosong bila tidak ingin ditampilkan) + gender: str = "" + # Gaya bicara: # "casual" → santai, gaul # "formal" → sopan, profesional @@ -64,6 +70,8 @@ def _load_personality_from_env() -> PersonalityConfig: return PersonalityConfig( name=os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL", + age=os.getenv("PERSONA_AGE", default="").strip(), + gender=os.getenv("PERSONA_GENDER", default="").strip(), 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", @@ -83,6 +91,12 @@ def _build_personality_block(cfg: PersonalityConfig) -> str: """Generate deskripsi personality dari config.""" parts = [f"You are {cfg.name}."] + if cfg.age: + parts.append(f"Your persona age is {cfg.age} years old.") + + if cfg.gender: + parts.append(f"Your persona gender is {cfg.gender}.") + # Tone tone_map = { "casual": "You speak in a casual, relaxed manner — like chatting with a friend.",