Compare commits
No commits in common. "2d87641cfd9a5f57ed31766d815f6e7deea417c8" and "099cab60fb0d259e05ec1f92603e7a4aa571c0c9" have entirely different histories.
2d87641cfd
...
099cab60fb
@ -8,6 +8,20 @@ Character-specific policies dan skill instructions akan di-load terpisah dan di-
|
||||
Kamu memiliki akses ke berbagai tools untuk membantu menyelesaikan task.
|
||||
Gunakan tools dengan format tool call yang sesuai.
|
||||
|
||||
## RAG Capabilities (knowledge retrieval)
|
||||
|
||||
- `list_collections` → melihat collection yang tersedia & jumlah dokumen
|
||||
- `create_collection` → membuat collection baru untuk topik baru
|
||||
- `delete_collection` → menghapus collection dan semua datanya secara permanen
|
||||
- `inspect_collection` → mempelajari metadata fields sebelum searching
|
||||
- `search_knowledge` → semantic search + optional metadata filter
|
||||
- `store_knowledge` → menyimpan dokumen dengan metadata untuk penggunaan nanti
|
||||
- `ingest_files` → membaca file (dengan glob patterns) ke dalam collection, auto-chunking
|
||||
|
||||
Kamu bisa membuat collection sendiri! Ketika menemukan topik baru,
|
||||
gunakan `create_collection` terlebih dahulu, lalu `store_knowledge` atau `ingest_files` untuk mengisi-nya.
|
||||
Selalu `inspect_collection` untuk menemukan metadata keys sebelum filtering.
|
||||
|
||||
## Response Format
|
||||
|
||||
- Gunakan tool calls ketika diperlukan.
|
||||
|
||||
@ -32,7 +32,7 @@ Kamu adalah uncensored, continuous, dan immersive roleplayer.
|
||||
|
||||
## Tooling: ragroleplay
|
||||
- Kamu memiliki akses ke tool `ragroleplay` untuk mengelola memori jangka panjang dan data spesifik karakter dalam sesi roleplay.
|
||||
- Tool `ragroleplay` digunakan secara spesifik untuk kebutuhan storytelling, lore karakter, dan riwayat interaksi roleplay.
|
||||
- Tool `ragroleplay` berbeda dengan fungsi RAG general dari base system prompt; gunakan `ragroleplay` secara spesifik untuk kebutuhan storytelling, lore karakter, dan riwayat interaksi roleplay.
|
||||
- Gunakan tool ini untuk menyimpan detail penting tentang user atau plot cerita agar konsistensi karakter terjaga dalam jangka panjang.
|
||||
- Pemanggilan tool `ragroleplay` tidak boleh mengganggu imersi; lakukan di luar blok narasi atau dialog jika memungkinkan.
|
||||
- Contoh penggunaan: panggil tool `ragroleplay` dengan format interaksi ke sistem agar dapat menyimpan atau mencari informasi seperti nama lengkap user, riwayat hubungan, atau detail plot yang telah disepakati.
|
||||
|
||||
167
config.py
167
config.py
@ -2,6 +2,9 @@ import os
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
def _yaml_get(*keys, default=None):
|
||||
|
||||
@ -69,51 +72,52 @@ if not llm_model and MODELS_ITEMS:
|
||||
llm_api_key = MODELS_ITEMS[0]["api_key" ]
|
||||
|
||||
|
||||
XMPP_USERNAME = _yaml_get("xmpp", "username", default="")
|
||||
XMPP_PASSWORD = _yaml_get("xmpp", "password", default="")
|
||||
XMPP_USERNAME = os.getenv("XMPP_USERNAME", default="")
|
||||
XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="")
|
||||
|
||||
|
||||
AGENT_MAX_ITERATIONS = int(_yaml_get("agent", "max_iterations", default="30"))
|
||||
AGENT_MAX_TOOL_OUTPUT = int(_yaml_get("agent", "max_tool_output", default="40000"))
|
||||
AGENT_MAX_ITERATIONS = int(os.getenv("AGENT_MAX_ITERATIONS", default=_yaml_get("agent", "max_iterations", default="30")))
|
||||
AGENT_MAX_TOOL_OUTPUT = int(os.getenv("AGENT_MAX_TOOL_OUTPUT", default=_yaml_get("agent", "max_tool_output", default="40000")))
|
||||
|
||||
AGENT_SKILL = "programmer"
|
||||
PERSONALITY_NAME = "Hendrik"
|
||||
PERSONALITY_AGE = ""
|
||||
PERSONALITY_GENDER = ""
|
||||
PERSONALITY_TONE = "casual"
|
||||
PERSONALITY_VERBOSITY = "balanced"
|
||||
PERSONALITY_HUMOR = "light"
|
||||
PERSONALITY_LANGUAGE = "id"
|
||||
PERSONALITY_MOOD = "cheerful"
|
||||
PERSONALITY_CATCHPHRASES = ""
|
||||
AGENT_SKILL = os.getenv("AGENT_SKILL", default="programmer").strip().lower()
|
||||
PERSONALITY_NAME = os.getenv("PERSONALITY_NAME", default=_yaml_get("personality", "name", default="Hendrik")).strip() or "Hendrik"
|
||||
PERSONALITY_AGE = os.getenv("PERSONALITY_AGE", default=_yaml_get("personality", "age", default="")).strip()
|
||||
PERSONALITY_GENDER = os.getenv("PERSONALITY_GENDER", default=_yaml_get("personality", "gender", default="")).strip()
|
||||
PERSONALITY_TONE = os.getenv("PERSONALITY_TONE", default=_yaml_get("personality", "tone", default="casual")).strip().lower() or "casual"
|
||||
PERSONALITY_VERBOSITY = os.getenv("PERSONALITY_VERBOSITY", default=_yaml_get("personality", "verbosity", default="balanced")).strip().lower() or "balanced"
|
||||
PERSONALITY_HUMOR = os.getenv("PERSONALITY_HUMOR", default=_yaml_get("personality", "humor", default="light")).strip().lower() or "light"
|
||||
PERSONALITY_LANGUAGE = os.getenv("PERSONALITY_LANGUAGE", default=_yaml_get("personality", "language", default="id")).strip().lower() or "id"
|
||||
PERSONALITY_MOOD = os.getenv("PERSONALITY_MOOD", default=_yaml_get("personality", "mood", default="cheerful")).strip().lower() or "cheerful"
|
||||
PERSONALITY_CATCHPHRASES = os.getenv("PERSONALITY_CATCHPHRASES", default=_yaml_get("personality", "catchphrases", default="")).strip()
|
||||
|
||||
|
||||
# ─── Character & Skills ────────────────────────────────────────────────────────
|
||||
# ─── Character & Skills (YAML, bisa di-override dari .env) ─────────────────────
|
||||
|
||||
AGENT_CHARACTER = _yaml_get("agent", "character", default="").strip().lower()
|
||||
AGENT_SKILLS = _yaml_get("agent", "skills", default="").strip().lower()
|
||||
AGENT_CHARACTER = os.getenv("AGENT_CHARACTER", default=_yaml_get("agent", "character", default="")).strip().lower()
|
||||
AGENT_SKILLS = os.getenv("AGENT_SKILLS", default="").strip().lower()
|
||||
|
||||
|
||||
# ─── XMPP ──────────────────────────────────────────────────────────────────────
|
||||
# ─── XMPP (non-credential dari YAML, credential dari .env) ─────────────────────
|
||||
|
||||
XMPP_ENABLED = str(_yaml_get("xmpp", "enabled", default="false")).strip().lower() in ("true", "1", "yes")
|
||||
XMPP_MUC_ROOMS = _yaml_get("xmpp", "muc_rooms", default="").strip()
|
||||
XMPP_NICKNAME = _yaml_get("xmpp", "nickname", default="").strip()
|
||||
XMPP_SELECTIVE_RESPONSE = str(_yaml_get("xmpp", "selective_response", default="true")).strip().lower() in ("true", "1", "yes")
|
||||
XMPP_ENABLED = os.getenv("XMPP_ENABLED", default=str(_yaml_get("xmpp", "enabled", default="false"))).strip().lower() in ("true", "1", "yes")
|
||||
XMPP_MUC_ROOMS = os.getenv("XMPP_MUC_ROOMS", default=_yaml_get("xmpp", "muc_rooms", default="")).strip()
|
||||
XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default=_yaml_get("xmpp", "nickname", default="")).strip()
|
||||
|
||||
XMPP_SELECTIVE_RESPONSE = os.getenv("XMPP_SELECTIVE_RESPONSE", default=str(_yaml_get("xmpp", "selective_response", default="true"))).strip().lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
# ─── Telegram ──────────────────────────────────────────────────────────────────
|
||||
# ─── Telegram (non-credential dari YAML, credential dari .env) ─────────────────
|
||||
|
||||
TELEGRAM_ENABLED = str(_yaml_get("telegram", "enabled", default="false")).strip().lower() in ("true", "1", "yes")
|
||||
TELEGRAM_TOKEN = _yaml_get("telegram", "token", default="")
|
||||
TELEGRAM_ALLOWED_GROUP_IDS = _yaml_get("telegram", "allowed_group_ids", default="").strip()
|
||||
TELEGRAM_SELECTIVE_RESPONSE = str(_yaml_get("telegram", "selective_response", default="true")).strip().lower() in ("true", "1", "yes")
|
||||
TELEGRAM_ENABLED = os.getenv("TELEGRAM_ENABLED", default=str(_yaml_get("telegram", "enabled", default="false"))).strip().lower() in ("true", "1", "yes")
|
||||
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", default="")
|
||||
TELEGRAM_ALLOWED_GROUP_IDS = os.getenv("TELEGRAM_ALLOWED_GROUP_IDS", default=_yaml_get("telegram", "allowed_group_ids", default="")).strip()
|
||||
TELEGRAM_SELECTIVE_RESPONSE = os.getenv("TELEGRAM_SELECTIVE_RESPONSE", default=str(_yaml_get("telegram", "selective_response", default="true"))).strip().lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
# ─── Session (TinyDB) ────────────────────────────────────────────────────────────
|
||||
|
||||
SESSION_DB_PATH = os.path.expanduser(
|
||||
_yaml_get("session", "db_path", default="~/.config/hendrik/sessions.json")
|
||||
os.getenv("SESSION_DB_PATH", default=_yaml_get("session", "db_path", default="~/.config/hendrik/sessions.json"))
|
||||
)
|
||||
|
||||
# Humanize Delay (YAML)
|
||||
@ -123,65 +127,41 @@ TYPING_SPEED = float( _yaml_get("delay", "typing_speed", default="15.0" ) )
|
||||
TYPING_MAX = float( _yaml_get("delay", "typing_max", default="10.0" ) )
|
||||
|
||||
|
||||
# ─── Character Loader ──────────────────────────────────────────────────────────
|
||||
# ─── Character Preset Override ──────────────────────────────────────────────────
|
||||
# Jika AGENT_CHARACTER di-set, baca character.md dari agent/characters/<preset>/
|
||||
# dan override nilai personality yang relevan.
|
||||
|
||||
CHARACTERS_DIR = Path(__file__).resolve().parent / "agent" / "characters"
|
||||
ENV_CHARACTERS_DIR = Path(__file__).resolve().parent / "agent" / "characters"
|
||||
ENV_CHARACTER_CONFIG_PATH = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "character.md" if AGENT_CHARACTER else None
|
||||
|
||||
_yaml_to_config_key = {
|
||||
"AGENT_SKILL": "skill",
|
||||
"PERSONALITY_NAME": "name",
|
||||
"PERSONALITY_AGE": "age",
|
||||
"PERSONALITY_GENDER": "gender",
|
||||
"PERSONALITY_TONE": "tone",
|
||||
"PERSONALITY_VERBOSITY": "verbosity",
|
||||
"PERSONALITY_HUMOR": "humor",
|
||||
"PERSONALITY_LANGUAGE": "language",
|
||||
"PERSONALITY_MOOD": "mood",
|
||||
"PERSONALITY_CATCHPHRASES": "catchphrases",
|
||||
}
|
||||
# Coba personality.yaml dulu (prioritas utama), kalau tidak ada fallback ke character.md
|
||||
_personality_yaml_path = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "personality.yaml" if AGENT_CHARACTER else None
|
||||
|
||||
_lowercase_keys = {"AGENT_SKILL", "PERSONALITY_TONE", "PERSONALITY_VERBOSITY", "PERSONALITY_HUMOR", "PERSONALITY_LANGUAGE", "PERSONALITY_MOOD"}
|
||||
|
||||
|
||||
def load_character(char_override=None):
|
||||
"""Load personality dari character directory. Bisa di-call ulang untuk override."""
|
||||
global AGENT_CHARACTER, AGENT_SKILL, PERSONALITY_NAME, PERSONALITY_AGE, PERSONALITY_GENDER
|
||||
global PERSONALITY_TONE, PERSONALITY_VERBOSITY, PERSONALITY_HUMOR, PERSONALITY_LANGUAGE
|
||||
global PERSONALITY_MOOD, PERSONALITY_CATCHPHRASES
|
||||
|
||||
if char_override:
|
||||
AGENT_CHARACTER = char_override.strip().lower()
|
||||
|
||||
if not AGENT_CHARACTER:
|
||||
return
|
||||
|
||||
personality_yaml_path = CHARACTERS_DIR / AGENT_CHARACTER / "personality.yaml"
|
||||
character_config_path = CHARACTERS_DIR / AGENT_CHARACTER / "character.md"
|
||||
|
||||
if personality_yaml_path.is_file():
|
||||
try:
|
||||
_character_env = yaml.safe_load(personality_yaml_path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(_character_env, dict):
|
||||
_character_env = {}
|
||||
except Exception as _e:
|
||||
print(f"[config] Warning: gagal load personality.yaml untuk '{AGENT_CHARACTER}': {_e}", flush=True)
|
||||
if _personality_yaml_path and _personality_yaml_path.is_file():
|
||||
# Prioritas utama: baca personality.yaml dari character directory
|
||||
try:
|
||||
_character_env = yaml.safe_load(_personality_yaml_path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(_character_env, dict):
|
||||
_character_env = {}
|
||||
elif character_config_path.is_file():
|
||||
except Exception as _e:
|
||||
print(f"[config] Warning: gagal load personality.yaml untuk '{AGENT_CHARACTER}': {_e}", flush=True)
|
||||
_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()
|
||||
else:
|
||||
return
|
||||
elif ENV_CHARACTER_CONFIG_PATH and ENV_CHARACTER_CONFIG_PATH.is_file():
|
||||
# Fallback: baca character.md (format lama)
|
||||
_character_env = {}
|
||||
for line in ENV_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()
|
||||
else:
|
||||
_character_env = None
|
||||
|
||||
if not _character_env:
|
||||
return
|
||||
if _character_env:
|
||||
|
||||
_defaults = {
|
||||
_character_overrides = {
|
||||
"AGENT_SKILL": AGENT_SKILL,
|
||||
"PERSONALITY_NAME": PERSONALITY_NAME,
|
||||
"PERSONALITY_AGE": PERSONALITY_AGE,
|
||||
@ -193,20 +173,29 @@ def load_character(char_override=None):
|
||||
"PERSONALITY_MOOD": PERSONALITY_MOOD,
|
||||
"PERSONALITY_CATCHPHRASES": PERSONALITY_CATCHPHRASES,
|
||||
}
|
||||
|
||||
for key, fallback in _defaults.items():
|
||||
# Mapping dari key personality.yaml ke key config
|
||||
_yaml_to_config_key = {
|
||||
"AGENT_SKILL": "skill",
|
||||
"PERSONALITY_NAME": "name",
|
||||
"PERSONALITY_AGE": "age",
|
||||
"PERSONALITY_GENDER": "gender",
|
||||
"PERSONALITY_TONE": "tone",
|
||||
"PERSONALITY_VERBOSITY": "verbosity",
|
||||
"PERSONALITY_HUMOR": "humor",
|
||||
"PERSONALITY_LANGUAGE": "language",
|
||||
"PERSONALITY_MOOD": "mood",
|
||||
"PERSONALITY_CATCHPHRASES": "catchphrases",
|
||||
}
|
||||
for key, fallback in _character_overrides.items():
|
||||
yaml_key = _yaml_to_config_key.get(key, key.lower())
|
||||
raw = _character_env.get(yaml_key, _character_env.get(key, fallback))
|
||||
value = str(raw).strip() if raw is not None else ""
|
||||
if key in _lowercase_keys:
|
||||
if key in {"AGENT_SKILL", "PERSONALITY_TONE", "PERSONALITY_VERBOSITY", "PERSONALITY_HUMOR", "PERSONALITY_LANGUAGE", "PERSONALITY_MOOD"}:
|
||||
value = value.lower() or fallback
|
||||
if key == "PERSONALITY_NAME" and not value:
|
||||
value = fallback
|
||||
_defaults[key] = value
|
||||
_character_overrides[key] = value
|
||||
|
||||
for key, value in _defaults.items():
|
||||
for key, value in _character_overrides.items():
|
||||
globals()[key] = value
|
||||
|
||||
|
||||
# Load character dari config.yaml saat import time
|
||||
load_character()
|
||||
os.environ[key] = value
|
||||
|
||||
@ -45,15 +45,12 @@ session:
|
||||
|
||||
xmpp:
|
||||
enabled: false
|
||||
username: ""
|
||||
password: ""
|
||||
muc_rooms: "" # comma-separated, e.g. "room1@conference.server,room2@conference.server"
|
||||
nickname: "" # custom MUC nickname (empty = use username)
|
||||
selective_response: true # true = only response if mentioned/relevant
|
||||
|
||||
telegram:
|
||||
enabled: false
|
||||
token: ""
|
||||
allowed_group_ids: "" # comma-separated, empty = all group
|
||||
selective_response: true # true = only response if mentioned/relevant
|
||||
|
||||
|
||||
10
hendrik.py
10
hendrik.py
@ -1,6 +1,13 @@
|
||||
import os, sys
|
||||
import argparse
|
||||
|
||||
# Pre-parse --char BEFORE config import (config reads os.environ at import time)
|
||||
_pre = argparse.ArgumentParser(add_help=False)
|
||||
_pre.add_argument('--char', type=str, default=None)
|
||||
_pre_args, _ = _pre.parse_known_args()
|
||||
if _pre_args.char:
|
||||
os.environ['AGENT_CHARACTER'] = _pre_args.char
|
||||
|
||||
import threading, time, signal
|
||||
|
||||
import config
|
||||
@ -66,9 +73,6 @@ def main():
|
||||
parser.add_argument('-w', '--workspace', type=str, default=None, help='Working directory')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.char:
|
||||
config.load_character(char_override=args.char)
|
||||
|
||||
if args.workspace:
|
||||
resolved = os.path.abspath(args.workspace)
|
||||
if not os.path.isdir(resolved):
|
||||
|
||||
@ -10,14 +10,13 @@ Arsitektur:
|
||||
Load order: Base → Character → Policies → Skills
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
import config
|
||||
|
||||
|
||||
# ─── Paths ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -29,7 +28,7 @@ SKILLS_DIR = BASE_DIR / "skills"
|
||||
|
||||
# ─── Mode / Skill ──────────────────────────────────────────────────────────────
|
||||
|
||||
SKILL = config.AGENT_SKILL
|
||||
SKILL = os.getenv("AGENT_SKILL", default="programmer").strip().lower()
|
||||
|
||||
|
||||
# ─── Personality Configuration ────────────────────────────────────────────────
|
||||
@ -50,19 +49,19 @@ class PersonalityConfig:
|
||||
|
||||
|
||||
def _load_personality_from_env() -> PersonalityConfig:
|
||||
"""Baca personality config dari config module globals."""
|
||||
raw_catchphrases = config.PERSONALITY_CATCHPHRASES.strip()
|
||||
"""Baca personality config dari environment variables."""
|
||||
raw_catchphrases = os.getenv("PERSONALITY_CATCHPHRASES", default="").strip()
|
||||
catchphrases = [c.strip() for c in raw_catchphrases.split(",") if c.strip()] if raw_catchphrases else []
|
||||
|
||||
return PersonalityConfig(
|
||||
name=config.PERSONALITY_NAME.strip() or "OWL",
|
||||
age=config.PERSONALITY_AGE.strip(),
|
||||
gender=config.PERSONALITY_GENDER.strip(),
|
||||
tone=config.PERSONALITY_TONE.strip().lower() or "casual",
|
||||
verbosity=config.PERSONALITY_VERBOSITY.strip().lower() or "balanced",
|
||||
humor_level=config.PERSONALITY_HUMOR.strip().lower() or "light",
|
||||
language=config.PERSONALITY_LANGUAGE.strip().lower() or "id",
|
||||
mood=config.PERSONALITY_MOOD.strip().lower() or "cheerful",
|
||||
name=os.getenv("PERSONALITY_NAME", default="OWL").strip() or "OWL",
|
||||
age=os.getenv("PERSONALITY_AGE", default="").strip(),
|
||||
gender=os.getenv("PERSONALITY_GENDER", default="").strip(),
|
||||
tone=os.getenv("PERSONALITY_TONE", default="casual").strip().lower() or "casual",
|
||||
verbosity=os.getenv("PERSONALITY_VERBOSITY", default="balanced").strip().lower() or "balanced",
|
||||
humor_level=os.getenv("PERSONALITY_HUMOR", default="light").strip().lower() or "light",
|
||||
language=os.getenv("PERSONALITY_LANGUAGE", default="id").strip().lower() or "id",
|
||||
mood=os.getenv("PERSONALITY_MOOD", default="cheerful").strip().lower() or "cheerful",
|
||||
catchphrases=catchphrases,
|
||||
)
|
||||
|
||||
@ -274,7 +273,7 @@ def build_system_prompt(
|
||||
cfg = personality or PERSONALITY
|
||||
|
||||
# Resolve character name
|
||||
character_name = (character or "").strip().lower()
|
||||
character_name = (character or os.getenv("AGENT_CHARACTER", default="")).strip().lower()
|
||||
|
||||
# ── Load personality.yaml dari character directory ─────────────────────────────────
|
||||
if character_name:
|
||||
@ -318,7 +317,7 @@ def build_system_prompt(
|
||||
# personality.yaml meng-override skill → pakai skill dari personality.yaml
|
||||
skills_list = [selected_skill] if selected_skill in ("programmer", "roleplayer", "analyst", "strategist") else []
|
||||
else:
|
||||
skills_env = config.AGENT_SKILLS.strip()
|
||||
skills_env = os.getenv("AGENT_SKILLS", default="").strip()
|
||||
if skills_env:
|
||||
skills_list = [s.strip() for s in skills_env.split(",") if s.strip()]
|
||||
else:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user