Character script

This commit is contained in:
Dita Aji Pratama 2026-06-11 15:17:26 +07:00
parent 432a8b2059
commit 9643b95059
5 changed files with 91 additions and 10 deletions

View File

@ -24,14 +24,21 @@ AGENT_MAX_ITERATIONS=20
AGENT_MAX_TOOL_OUTPUT=4000 AGENT_MAX_TOOL_OUTPUT=4000
# Personality # Personality
PERSONA_MODE=programmer # Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol) # Character preset — baca dari directory character/<AGENT_CHARACTER>
PERSONA_NAME=OWL # Kosongkan untuk memakai PERSONA_* dari .env.
PERSONA_TONE=casual # casual | formal | playful | warm # Contoh: AGENT_CHARACTER=hendrik
PERSONA_VERBOSITY=balanced # concise | balanced | detailed AGENT_CHARACTER=hendrik
PERSONA_HUMOR=light # none | light | witty
PERSONA_LANGUAGE=id # id | en | (kosong = auto) #PERSONA_MODE=programmer # Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol)
PERSONA_MOOD=cheerful # cheerful | calm | energetic | sarcastic #PERSONA_NAME=OWL
PERSONA_CATCHPHRASES=Hai, Gimana kabarnya?, Wkwkwk #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 XMPP_ENABLED=False

9
character/hendrik Normal file
View File

@ -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

View File

@ -1,5 +1,6 @@
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from pathlib import Path
load_dotenv() load_dotenv()
@ -29,6 +30,12 @@ PERSONA_MODE = os.getenv("PERSONA_MODE", default="programmer").strip().lower()
# Personality — nama panggilan AI (default: "OWL") # Personality — nama panggilan AI (default: "OWL")
PERSONA_NAME = os.getenv("PERSONA_NAME", default="OWL").strip() or "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" # Gaya bicara: "casual" | "formal" | "playful" | "warm"
PERSONA_TONE = os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual" 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" # Contoh: "Siap bro!, Haha~, Wkwkwk"
PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip() PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
# Character preset — baca dari directory character/<AGENT_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). # Selective response: true = roleplayer hanya respon kalau ada mention/relevansi (default).
# false = roleplayer semua pesan ikut respon (seperti biasa, tanpa filter). # 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") XMPP_SELECTIVE_RESPONSE = os.getenv("XMPP_SELECTIVE_RESPONSE", default="true").strip().lower() in ("true", "1", "yes")

View File

@ -36,15 +36,21 @@ class LLMClient:
message = response['choices'][0]['message'] message = response['choices'][0]['message']
return self.Message(message) return self.Message(message)
except urllib.error.HTTPError as e: 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: if tools and e.code == 404:
try: 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(): if 'tool use' in body.get('error', {}).get('message', '').lower():
result = self.chat(messages, tools=None) result = self.chat(messages, tools=None)
result.warning = "Tool calling not supported by this model. Running in chat-only mode." result.warning = "Tool calling not supported by this model. Running in chat-only mode."
return result return result
except Exception: except Exception:
pass 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: except Exception as e:
return self.Message({'content': f"Error: {str(e)}", 'tool_calls': None}) return self.Message({'content': f"Error: {str(e)}", 'tool_calls': None})

View File

@ -20,6 +20,12 @@ class PersonalityConfig:
# Nama panggilan AI (default: "OWL") # Nama panggilan AI (default: "OWL")
name: str = "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: # Gaya bicara:
# "casual" → santai, gaul # "casual" → santai, gaul
# "formal" → sopan, profesional # "formal" → sopan, profesional
@ -64,6 +70,8 @@ def _load_personality_from_env() -> PersonalityConfig:
return PersonalityConfig( return PersonalityConfig(
name=os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL", 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", tone=os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual",
verbosity=os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced", verbosity=os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced",
humor_level=os.getenv("PERSONA_HUMOR", default="light").strip().lower() or "light", 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.""" """Generate deskripsi personality dari config."""
parts = [f"You are {cfg.name}."] 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
tone_map = { tone_map = {
"casual": "You speak in a casual, relaxed manner — like chatting with a friend.", "casual": "You speak in a casual, relaxed manner — like chatting with a friend.",