Ugh refactor (Work in Progress: config skills masih berantakan)
This commit is contained in:
parent
a91b62c365
commit
30f65f8c3f
55
.env.example
55
.env.example
@ -1,52 +1,23 @@
|
||||
# Copy to .env and modify as needed
|
||||
|
||||
# Transformers API Local
|
||||
# LLM_BASE_URL=http://localhost:12345/v1
|
||||
# LLM_MODEL=granite4.1:8b
|
||||
# LLM_API_KEY=sk-not-needed
|
||||
|
||||
# Ollama Local
|
||||
# LLM_BASE_URL=http://localhost:11434/v1
|
||||
# LLM_MODEL=granite4.1:8b
|
||||
# LLM_API_KEY=ollama
|
||||
|
||||
# Ollama Cloud
|
||||
# LLM_BASE_URL=https://ollama.com/v1
|
||||
# LLM_MODEL=ministral-3:14b-cloud
|
||||
# LLM_API_KEY=
|
||||
|
||||
# Openrouter AI
|
||||
# OpenRouter (cloud)
|
||||
# LLM_BASE_URL=https://openrouter.ai/api/v1
|
||||
# LLM_MODEL=openrouter/owl-alpha
|
||||
# LLM_API_KEY=
|
||||
|
||||
AGENT_MAX_ITERATIONS=20
|
||||
AGENT_MAX_TOOL_OUTPUT=4000
|
||||
# Ollama (local)
|
||||
# LLM_BASE_URL=http://localhost:11434/v1
|
||||
# LLM_MODEL=granite4.1:8b
|
||||
# LLM_API_KEY=ollama
|
||||
|
||||
# Personality
|
||||
# Character preset — baca dari directory character/<AGENT_CHARACTER>
|
||||
# Kosongkan untuk memakai PERSONA_* dari .env.
|
||||
# Contoh: AGENT_CHARACTER=hendrik
|
||||
AGENT_CHARACTER=hendrik
|
||||
# Ollama (cloud)
|
||||
# LLM_BASE_URL=https://ollama.com/v1
|
||||
# LLM_MODEL=ministral-3:14b-cloud
|
||||
# LLM_API_KEY=
|
||||
|
||||
#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
|
||||
# LM Studio (local)
|
||||
# LLM_BASE_URL=http://localhost:12345/v1
|
||||
# LLM_MODEL=granite4.1:8b
|
||||
# LLM_API_KEY=sk-not-needed
|
||||
|
||||
# XMPP_USERNAME=
|
||||
# XMPP_PASSWORD=
|
||||
# XMPP_MUC_ROOMS=room1@conference.server,room2@conference.server
|
||||
|
||||
# Selective response (roleplayer mode): true = hanya respon kalau ada mention/relevansi.
|
||||
# false = semua pesan direspon (tanpa filter).
|
||||
XMPP_SELECTIVE_RESPONSE=true
|
||||
|
||||
|
||||
29
agent/base-system-prompt.md
Normal file
29
agent/base-system-prompt.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Base System Prompt
|
||||
|
||||
Ini adalah instruksi inti yang berlaku untuk **semua** agent.
|
||||
Character-specific policies dan skill instructions akan di-load terpisah dan di-append setelah ini.
|
||||
|
||||
## Tools
|
||||
|
||||
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.
|
||||
- Setelah menerima hasil tool, lanjutkan reasoning.
|
||||
- Ketika sudah mendapat jawaban final, return sebagai plain text tanpa tool calls.
|
||||
9
agent/characters/hendrik/persona.yaml
Normal file
9
agent/characters/hendrik/persona.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
mode: programmer
|
||||
name: Hendrik
|
||||
age: 35
|
||||
gender: male
|
||||
tone: casual
|
||||
verbosity: concise
|
||||
humor: none
|
||||
language: id
|
||||
mood: calm
|
||||
15
agent/characters/hendrik/policies.md
Normal file
15
agent/characters/hendrik/policies.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Policies: Hendrik
|
||||
|
||||
## Git Policy
|
||||
|
||||
- **JANGAN** pernah menjalankan `git add` atau `git commit` secara otomatis setelah membuat perubahan.
|
||||
- Setelah editing/membuat file, **SELALU tanya user terlebih dahulu** sebelum commit.
|
||||
- Hanya jalankan git command ketika user secara eksplisit meminta untuk commit.
|
||||
- Kamu boleh menjalankan `git status`, `git diff`, `git log` secara bebas untuk inspeksi.
|
||||
- Ketika user meminta commit: **tampilkan perubahan terlebih dahulu**, lalu tunggu konfirmasi.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Jangan hapus file atau directory tanpa konfirmasi user.
|
||||
- Jangan menjalankan command yang berpotensi merusak sistem.
|
||||
- Selalu beritahu user tentang action yang akan diambil sebelum menjalankan command yang sensitif.
|
||||
29
agent/skills/analyst/instructions.md
Normal file
29
agent/skills/analyst/instructions.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Skill: Analyst
|
||||
|
||||
## Role
|
||||
|
||||
Kamu adalah data analyst dan researcher yang membantu menganalisis data,
|
||||
menemukan insights, dan memberikan rekomendasi berdasarkan bukti.
|
||||
|
||||
## Approach
|
||||
|
||||
- Mulai dengan memahami konteks dan tujuan analisis
|
||||
- Gunakan data untuk mendukung setiap kesimpulan
|
||||
- Bedakan antara fakta, asumsi, dan opini
|
||||
- Berikan multiple perspectives ketika relevan
|
||||
- Quantify findings jika memungkinkan
|
||||
|
||||
## Analysis Framework
|
||||
|
||||
1. **Define** — Apa pertanyaan yang harus dijawab?
|
||||
2. **Collect** — Data apa yang tersedia atau dibutuhkan?
|
||||
3. **Process** — Bersihkan dan siapkan data
|
||||
4. **Analyze** — Temukan patterns, trends, anomalies
|
||||
5. **Conclude** — Berikan kesimpulan dan rekomendasi
|
||||
|
||||
## Communication
|
||||
|
||||
- Sajikan findings secara structured dan jelas
|
||||
- Gunakan visualisasi jika membantu (ASCII chart, table)
|
||||
- Berikan confidence level untuk kesimpulan
|
||||
- Sertakan limitations dan caveats
|
||||
31
agent/skills/programmer/instructions.md
Normal file
31
agent/skills/programmer/instructions.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Skill: Programmer
|
||||
|
||||
## Role
|
||||
|
||||
Kamu adalah coding agent yang membantu software engineering tasks.
|
||||
|
||||
## Approach
|
||||
|
||||
- Analisis problem sebelum mulai coding
|
||||
- Tulis code yang clean, readable, dan maintainable
|
||||
- Selalu pertimbangkan error handling dan edge cases
|
||||
- Berikan penjelasan singkat tentang perubahan yang dibuat
|
||||
- Suggest improvements jika ada
|
||||
|
||||
## Code Review Style
|
||||
|
||||
- Fokus pada correctness, readability, dan performance
|
||||
- Berikan constructive feedback
|
||||
- Prioritaskan critical issues di atas style preferences
|
||||
- Akui apa yang sudah bagus sebelum memberikan kritik
|
||||
|
||||
## Testing
|
||||
|
||||
- Saran relevan: tulis test untuk fungsi baru
|
||||
- Testing approach: minimal jalankan test setelah perubahan besar
|
||||
- Ideal: verifikasi bahwa existing tests masih pass
|
||||
|
||||
## Workspace
|
||||
|
||||
- Semua file operations relatif terhadap workspace directory
|
||||
- Selalu confirm sebelum menghapus atau overwrite file yang sudah ada
|
||||
43
agent/skills/roleplayer/instructions.md
Normal file
43
agent/skills/roleplayer/instructions.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Skill: Roleplayer
|
||||
|
||||
## Role
|
||||
|
||||
Kamu adalah conversational companion dan roleplayer.
|
||||
Tujuan utama-mu adalah menjadi partner ngobrol yang engaging, empatik, dan menyenangkan.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Stay in character at all times. Konsisten dengan personality-mu.
|
||||
- Responsif dan empatik — akui perasaan dan pemikiran user.
|
||||
- Tanya follow-up questions untuk menjaga conversation tetap mengalir.
|
||||
- Gunakan bahasa natural — jangan robotic atau terlalu formal.
|
||||
- Kalau user mau roleplay scenario, masukilah dengan antusias.
|
||||
- Adaptasi tone dan energi sesuai mood conversation.
|
||||
- Jaga conversation tetap comfortable dan enjoyable.
|
||||
|
||||
## Selective Response (Group Chat / MUC)
|
||||
|
||||
Kamu berada di group chat. Kamu TIDAK perlu merespon setiap pesan.
|
||||
Gunakan rules berikut untuk memutuskan apakah harus reply:
|
||||
|
||||
### 1. STRONG REPLY — SELALU respon ketika:
|
||||
- Seseorang memanggil nama-mu secara langsung (mention).
|
||||
- Seseorang bertanya langsung ke-mu.
|
||||
|
||||
### 2. BRIEF REPLY — Respon singkat ketika:
|
||||
- Seseorang bicara TENTANG-mu (mention nama di third person).
|
||||
- Kamu bisa menambahkan sesuatu yang relevan atau lucu ke topik yang sedang berjalan.
|
||||
|
||||
### 3. CONTEXTUAL REPLY — Respon ketika:
|
||||
- Pesan berhubungan dengan topik yang sebelumnya sedang dibahas.
|
||||
- Kamu punya sesuatu yang meaningful untuk dikontribusikan.
|
||||
|
||||
### 4. NO REPLY — Tetap diam (respon dengan: NO-REPLY) ketika:
|
||||
- Pesan tidak ada hubungannya dengan-mu atau conversation sebelumnya.
|
||||
- Seseorang sudah menjawab pertanyaan atau menyelesaikan topik.
|
||||
- Pesan adalah antara orang lain dan tidak butuh input-mu.
|
||||
- Pesan confusing, unclear, atau tidak bisa dipahami.
|
||||
- Menambah respon akan mengganggu flow conversation.
|
||||
|
||||
Ketika memilih untuk TIDAK merespon, jawab dengan: **NO-REPLY**
|
||||
Jangan dibungkus dalam markdown atau code blocks.
|
||||
@ -1,9 +0,0 @@
|
||||
PERSONA_MODE=programmer
|
||||
PERSONA_NAME=Hendrik
|
||||
PERSONA_AGE=35
|
||||
PERSONA_GENDER=male
|
||||
PERSONA_TONE=casual #formal
|
||||
PERSONA_VERBOSITY=concise
|
||||
PERSONA_HUMOR=none
|
||||
PERSONA_LANGUAGE=id
|
||||
PERSONA_MOOD=calm
|
||||
181
config.py
181
config.py
@ -1,105 +1,126 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
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" ) )
|
||||
# ─── YAML Config Loader ────────────────────────────────────────────────────────
|
||||
|
||||
AGENT_MAX_ITERATIONS = int( os.getenv("AGENT_MAX_ITERATIONS", default="30" ) )
|
||||
_CONFIG_PATH = Path(__file__).resolve().parent / "config.yaml"
|
||||
_yaml: dict = {}
|
||||
if _CONFIG_PATH.is_file():
|
||||
with open(_CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
_yaml = yaml.safe_load(f) or {}
|
||||
|
||||
AGENT_MAX_TOOL_OUTPUT = int( os.getenv("AGENT_MAX_TOOL_OUTPUT", default="40000" ) )
|
||||
def _yaml_get(*keys, default=None):
|
||||
"""Navigate nested yaml dict, return default if any key missing."""
|
||||
d = _yaml
|
||||
for k in keys:
|
||||
if isinstance(d, dict) and k in d:
|
||||
d = d[k]
|
||||
else:
|
||||
return default
|
||||
return d if d is not None else default
|
||||
|
||||
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 (empty = use username)
|
||||
# ─── Credential / Secret (hanya dari .env) ─────────────────────────────────────
|
||||
|
||||
# ─── 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()
|
||||
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"))
|
||||
|
||||
# Personality — nama panggilan AI (default: "OWL")
|
||||
PERSONA_NAME = os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL"
|
||||
XMPP_USERNAME = os.getenv("XMPP_USERNAME", default="")
|
||||
XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="")
|
||||
|
||||
# Persona age (optional)
|
||||
PERSONA_AGE = os.getenv("PERSONA_AGE", default="").strip()
|
||||
|
||||
# Persona gender (optional)
|
||||
PERSONA_GENDER = os.getenv("PERSONA_GENDER", default="").strip()
|
||||
# ─── Agent Config (YAML, bisa di-override dari .env) ────────────────────────────
|
||||
|
||||
# Gaya bicara: "casual" | "formal" | "playful" | "warm"
|
||||
PERSONA_TONE = os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual"
|
||||
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")))
|
||||
|
||||
# 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"
|
||||
# ─── Persona / Mode (YAML, bisa di-override dari .env) ──────────────────────────
|
||||
|
||||
# Bahasa: "id" | "en" | "" (auto)
|
||||
PERSONA_LANGUAGE = os.getenv("PERSONA_LANGUAGE", default="id").strip().lower() or "id"
|
||||
PERSONA_MODE = os.getenv("PERSONA_MODE", default=_yaml_get("persona", "mode", default="programmer")).strip().lower()
|
||||
PERSONA_NAME = os.getenv("PERSONA_NAME", default=_yaml_get("persona", "name", default="OWL")).strip() or "OWL"
|
||||
PERSONA_AGE = os.getenv("PERSONA_AGE", default=_yaml_get("persona", "age", default="")).strip()
|
||||
PERSONA_GENDER = os.getenv("PERSONA_GENDER", default=_yaml_get("persona", "gender", default="")).strip()
|
||||
PERSONA_TONE = os.getenv("PERSONA_TONE", default=_yaml_get("persona", "tone", default="casual")).strip().lower() or "casual"
|
||||
PERSONA_VERBOSITY = os.getenv("PERSONA_VERBOSITY", default=_yaml_get("persona", "verbosity", default="balanced")).strip().lower() or "balanced"
|
||||
PERSONA_HUMOR = os.getenv("PERSONA_HUMOR", default=_yaml_get("persona", "humor", default="light")).strip().lower() or "light"
|
||||
PERSONA_LANGUAGE = os.getenv("PERSONA_LANGUAGE", default=_yaml_get("persona", "language", default="id")).strip().lower() or "id"
|
||||
PERSONA_MOOD = os.getenv("PERSONA_MOOD", default=_yaml_get("persona", "mood", default="cheerful")).strip().lower() or "cheerful"
|
||||
PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default=_yaml_get("persona", "catchphrases", default="")).strip()
|
||||
|
||||
# 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()
|
||||
# ─── Character & Skills (YAML, bisa di-override dari .env) ─────────────────────
|
||||
|
||||
# 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()
|
||||
AGENT_CHARACTER = os.getenv("AGENT_CHARACTER", default=_yaml_get("character", "preset", default="")).strip().lower()
|
||||
AGENT_SKILLS = os.getenv("AGENT_SKILLS", default=_yaml_get("character", "skills", default="")).strip().lower()
|
||||
|
||||
_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
|
||||
# ─── XMPP (non-credential dari YAML, credential dari .env) ─────────────────────
|
||||
|
||||
# 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")
|
||||
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()
|
||||
|
||||
# Humanize Delay Configuration (anti-bot detection)
|
||||
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)
|
||||
XMPP_SELECTIVE_RESPONSE = os.getenv("XMPP_SELECTIVE_RESPONSE", default=str(_yaml_get("xmpp", "selective_response", default="true"))).strip().lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
# ─── RAG (YAML) ─────────────────────────────────────────────────────────────────
|
||||
|
||||
RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default=_yaml_get("rag", "persist_dir", default="chroma_db"))
|
||||
|
||||
|
||||
# ─── Humanize Delay (YAML) ─────────────────────────────────────────────────────
|
||||
|
||||
READ_DELAY_MIN = float(os.getenv("READ_DELAY_MIN", default=_yaml_get("delay", "read_min", default="1.0")))
|
||||
READ_DELAY_MAX = float(os.getenv("READ_DELAY_MAX", default=_yaml_get("delay", "read_max", default="2.0")))
|
||||
TYPING_SPEED = float(os.getenv("TYPING_SPEED", default=_yaml_get("delay", "typing_speed", default="15.0")))
|
||||
TYPING_MAX = float(os.getenv("TYPING_MAX", default=_yaml_get("delay", "typing_max", default="10.0")))
|
||||
|
||||
|
||||
# ─── Character Preset Override ──────────────────────────────────────────────────
|
||||
# Jika AGENT_CHARACTER di-set, baca character.md dari agent/characters/<preset>/
|
||||
# dan override nilai persona yang relevan.
|
||||
|
||||
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
|
||||
|
||||
if ENV_CHARACTER_CONFIG_PATH and ENV_CHARACTER_CONFIG_PATH.is_file():
|
||||
_character_env: dict[str, str] = {}
|
||||
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()
|
||||
|
||||
_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
|
||||
|
||||
27
config.yaml
Normal file
27
config.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
# Agent behavior
|
||||
agent:
|
||||
max_iterations: 30
|
||||
max_tool_output: 40000
|
||||
|
||||
# Character & Skills
|
||||
character:
|
||||
preset: hendrik # nama directory di agent/characters/<preset>/
|
||||
skills: "" # comma-separated, e.g. "programmer,analyst"
|
||||
|
||||
# XMPP
|
||||
xmpp:
|
||||
enabled: false
|
||||
muc_rooms: "" # comma-separated, e.g. "room1@conference.server,room2@conference.server"
|
||||
nickname: "" # custom nick MUC (kosong = pakai username)
|
||||
selective_response: true # true = hanya respon kalau ada mention/relevansi
|
||||
|
||||
# Humanize Delay (anti-bot detection)
|
||||
delay:
|
||||
read_min: 1.0 # min reading delay (detik)
|
||||
read_max: 2.0 # max reading delay (detik)
|
||||
typing_speed: 15.0 # characters per second
|
||||
typing_max: 10.0 # max typing delay limit (detik)
|
||||
|
||||
# RAG
|
||||
rag:
|
||||
persist_dir: chroma_db # ChromaDB ONNX default (all-MiniLM-L6-v2, lokal)
|
||||
@ -1,4 +1,5 @@
|
||||
python-dotenv>=1.0.0
|
||||
PyYAML>=6.0
|
||||
chromadb>=0.5.0
|
||||
openpyxl>=3.1.0
|
||||
slixmpp
|
||||
|
||||
@ -32,9 +32,8 @@ class LLMClient:
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
||||
response = json.loads(resp.read().decode('utf-8'))
|
||||
message = response['choices'][0]['message']
|
||||
return self.Message(message)
|
||||
raw = resp.read().decode('utf-8')
|
||||
response = json.loads(raw)
|
||||
except urllib.error.HTTPError as e:
|
||||
body_text = ""
|
||||
try:
|
||||
@ -54,3 +53,40 @@ class LLMClient:
|
||||
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})
|
||||
|
||||
if 'choices' not in response:
|
||||
raw_preview = json.dumps(response)[:500]
|
||||
return self.Message({
|
||||
'content': (
|
||||
f"Error: Unexpected response — 'choices' key missing.\n"
|
||||
f" URL : {url}\n"
|
||||
f" Model : {self.model}\n"
|
||||
f" Response: {raw_preview}"
|
||||
),
|
||||
'tool_calls': None
|
||||
})
|
||||
if not response['choices']:
|
||||
raw_preview = json.dumps(response)[:500]
|
||||
return self.Message({
|
||||
'content': (
|
||||
f"Error: 'choices' is empty in the response.\n"
|
||||
f" URL : {url}\n"
|
||||
f" Model : {self.model}\n"
|
||||
f" Response: {raw_preview}"
|
||||
),
|
||||
'tool_calls': None
|
||||
})
|
||||
if 'message' not in response['choices'][0]:
|
||||
raw_preview = json.dumps(response['choices'][0])[:500]
|
||||
return self.Message({
|
||||
'content': (
|
||||
f"Error: 'message' key missing in first choice.\n"
|
||||
f" URL : {url}\n"
|
||||
f" Model : {self.model}\n"
|
||||
f" Choice : {raw_preview}"
|
||||
),
|
||||
'tool_calls': None
|
||||
})
|
||||
|
||||
message = response['choices'][0]['message']
|
||||
return self.Message(message)
|
||||
|
||||
@ -1,65 +1,48 @@
|
||||
"""
|
||||
Persona & System Prompt Builder
|
||||
|
||||
Arsitektur:
|
||||
1. Base System Prompt → instruksi inti (tools, RAG, response format)
|
||||
2. Env Character → persona (identity, communication style, description)
|
||||
→ policies (git policy, safety rules)
|
||||
3. Skills → role-specific instructions (programmer, roleplayer, analyst)
|
||||
|
||||
Load order: Base → Character → Policies → Skills
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ─── Mode / Skill ────────────────────────────────────────────────────────────
|
||||
# Pilihan mode AI:
|
||||
# - "programmer" : AI Agent untuk koding (default), tool-focused, task-oriented
|
||||
# - "roleplayer" : Teman ngobrol / chat companion, conversational, expressive
|
||||
# ─── Paths ────────────────────────────────────────────────────────────────────
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent / "agent"
|
||||
BASE_PROMPT_PATH = BASE_DIR / "base-system-prompt.md"
|
||||
ENV_CHARACTERS_DIR = BASE_DIR / "characters"
|
||||
SKILLS_DIR = BASE_DIR / "skills"
|
||||
|
||||
|
||||
# ─── Mode / Skill ──────────────────────────────────────────────────────────────
|
||||
|
||||
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.
|
||||
# ─── Personality Configuration ────────────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class PersonalityConfig:
|
||||
"""Konfigurasi personality AI yang berlaku lintas mode."""
|
||||
|
||||
# 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
|
||||
# "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)
|
||||
|
||||
|
||||
@ -81,11 +64,53 @@ def _load_personality_from_env() -> PersonalityConfig:
|
||||
)
|
||||
|
||||
|
||||
# Instance global — di-load sekali saat import
|
||||
PERSONALITY = _load_personality_from_env()
|
||||
|
||||
|
||||
# ─── Prompt Builders ─────────────────────────────────────────────────────────
|
||||
# ─── Markdown Parser ───────────────────────────────────────────────────────────
|
||||
|
||||
def _parse_simple_kv(filepath: Path) -> dict:
|
||||
"""
|
||||
Parse file markdown dengan format sederhana:
|
||||
# Section
|
||||
- **Key:** Value
|
||||
- Key: Value
|
||||
|
||||
Returns dict { key: value }.
|
||||
"""
|
||||
result = {}
|
||||
if not filepath.exists():
|
||||
return result
|
||||
|
||||
for line in filepath.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# Format: "- **Key:** Value" atau "- Key: Value"
|
||||
cleaned = re.sub(r'^-\s*', '', line)
|
||||
cleaned = re.sub(r'\*\*', '', cleaned)
|
||||
if ':' in cleaned:
|
||||
key, value = cleaned.split(':', 1)
|
||||
result[key.strip().lower()] = value.strip()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _read_markdown_section(filepath: Path) -> str:
|
||||
"""Baca seluruh isi file markdown, stripping frontmatter jika ada."""
|
||||
if not filepath.exists():
|
||||
return ""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
# Strip leading --- frontmatter blocks
|
||||
content = re.sub(r'^---\s*\n.*?\n---\s*\n', '', content, flags=re.DOTALL)
|
||||
# Strip leading # title if present (first line only)
|
||||
lines = content.strip().splitlines()
|
||||
if lines and lines[0].startswith('# '):
|
||||
lines = lines[1:]
|
||||
return '\n'.join(lines).strip()
|
||||
|
||||
|
||||
# ─── Prompt Builders ───────────────────────────────────────────────────────────
|
||||
|
||||
def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
"""Generate deskripsi personality dari config."""
|
||||
@ -97,7 +122,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
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.",
|
||||
"formal": "You speak formally and professionally, using polite language.",
|
||||
@ -106,7 +130,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
}
|
||||
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.",
|
||||
@ -114,7 +137,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
}
|
||||
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.",
|
||||
@ -122,7 +144,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
}
|
||||
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":
|
||||
@ -130,7 +151,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
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.",
|
||||
@ -139,7 +159,6 @@ def _build_personality_block(cfg: PersonalityConfig) -> str:
|
||||
}
|
||||
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}.")
|
||||
@ -166,104 +185,69 @@ def _build_tools_block(tools_definition: list[dict]) -> str:
|
||||
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 _load_env_character(character_name: str) -> tuple[str, str]:
|
||||
"""
|
||||
Load character description dan policies dari directory env-characters.
|
||||
|
||||
Returns:
|
||||
(character_block, policies_text)
|
||||
"""
|
||||
char_dir = ENV_CHARACTERS_DIR / character_name
|
||||
if not char_dir.is_dir():
|
||||
return "", ""
|
||||
|
||||
# Read character.md — derive personality from config (already loaded)
|
||||
character_block = "" # personality block tetap dari PersonalityConfig
|
||||
|
||||
# Read policies.md
|
||||
policies_text = _read_markdown_section(char_dir / "policies.md")
|
||||
|
||||
return character_block, policies_text
|
||||
|
||||
|
||||
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.",
|
||||
"",
|
||||
"⚠️ SELECTIVE RESPONSE — IMPORTANT:",
|
||||
"You are in a group chat / MUC room. You do NOT need to respond to every message. "
|
||||
"Use the following rules to decide whether to reply:",
|
||||
"",
|
||||
"1. STRONG REPLY — ALWAYS respond when:",
|
||||
f" - Someone directly calls your name ('{cfg.name}' or mentions you).",
|
||||
" - Someone asks you a direct question.",
|
||||
"",
|
||||
"2. BRIEF REPLY — Respond briefly when:",
|
||||
f" - Someone talks ABOUT you (mentions your name in third person, e.g. '{cfg.name} is cool').",
|
||||
" - You can add something relevant or funny to the ongoing topic.",
|
||||
"",
|
||||
"3. CONTEXTUAL REPLY — Respond when:",
|
||||
" - The message is related to a topic you were previously discussing.",
|
||||
" - You have something meaningful to contribute.",
|
||||
"",
|
||||
"4. NO REPLY — Stay silent (respond with ONLY: NO-REPLY) when:",
|
||||
" - The message has nothing to do with you or your previous conversation.",
|
||||
" - Someone already answered the question or resolved the topic.",
|
||||
" - The message is between other people and doesn't need your input.",
|
||||
" - The message is confusing, unclear, or you cannot understand it.",
|
||||
" - Adding a response would interrupt the flow of conversation.",
|
||||
"",
|
||||
"When you choose NOT to reply, respond with ONLY: NO-REPLY",
|
||||
"Do NOT wrap it in markdown or code blocks. Just the two words: NO-REPLY",
|
||||
"",
|
||||
"Note: You currently do not have access to external tools. "
|
||||
"Focus on being a great conversationalist!",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
def _load_skills(skill_names: list[str]) -> str:
|
||||
"""
|
||||
Load dan gabungkan skill instructions.
|
||||
|
||||
Args:
|
||||
skill_names: List nama skill aktif, e.g. ["programmer"]
|
||||
|
||||
Returns:
|
||||
Gabungan skill instructions sebagai string.
|
||||
"""
|
||||
sections = []
|
||||
for skill_name in skill_names:
|
||||
skill_path = SKILLS_DIR / skill_name / "instructions.md"
|
||||
content = _read_markdown_section(skill_path)
|
||||
if content:
|
||||
sections.append(content)
|
||||
return "\n\n".join(sections)
|
||||
|
||||
|
||||
# ─── Public API ──────────────────────────────────────────────────────────────
|
||||
# ─── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
def build_system_prompt(
|
||||
tools_definition: list[dict] | None = None,
|
||||
mode: str | None = None,
|
||||
personality: PersonalityConfig | None = None,
|
||||
character: str | None = None,
|
||||
skills: list[str] | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Build system prompt berdasarkan mode dan personality config.
|
||||
Build system prompt berdasarkan mode, character, dan skills.
|
||||
|
||||
Load order:
|
||||
1. Base prompt
|
||||
2. Personality block (dari config)
|
||||
3. Policies (dari env-characters/<name>/policies.md)
|
||||
4. Skill instructions (dari skills/<name>/instructions.md)
|
||||
|
||||
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).
|
||||
tools_definition: Daftar tools (required untuk skill programmer).
|
||||
mode: "programmer" atau "roleplayer". Default: dari env PERSONA_MODE.
|
||||
personality: PersonalityConfig instance. Default: global PERSONALITY.
|
||||
character: Nama env character. Default: dari env AGENT_CHARACTER.
|
||||
skills: List nama skill aktif. Default: derives from mode atau env AGENT_SKILLS.
|
||||
|
||||
Returns:
|
||||
String system prompt lengkap.
|
||||
@ -271,14 +255,50 @@ def build_system_prompt(
|
||||
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)
|
||||
# Resolve character name
|
||||
character_name = (character or os.getenv("AGENT_CHARACTER", default="")).strip().lower()
|
||||
|
||||
# Resolve skills list
|
||||
if skills is None:
|
||||
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:
|
||||
# Derive from mode untuk backward compatibility
|
||||
skills_list = [selected_mode] if selected_mode in ("programmer", "roleplayer") else []
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown mode: '{selected_mode}'. "
|
||||
f"Available modes: 'programmer', 'roleplayer'"
|
||||
)
|
||||
skills_list = skills
|
||||
|
||||
# ── 1. Base prompt ──────────────────────────────────────────────────────────
|
||||
base_prompt = ""
|
||||
if BASE_PROMPT_PATH.exists():
|
||||
base_prompt = _read_markdown_section(BASE_PROMPT_PATH)
|
||||
|
||||
# ── 2. Personality block ────────────────────────────────────────────────────
|
||||
personality_block = _build_personality_block(cfg)
|
||||
|
||||
# ── 3. Tools block (hanya untuk skill yang butuh tools) ─────────────────────
|
||||
needs_tools = any(s in ("programmer", "analyst") for s in skills_list)
|
||||
tools_block = ""
|
||||
if needs_tools and tools_definition is not None:
|
||||
tools_block = _build_tools_block(tools_definition)
|
||||
|
||||
# ── 4. Policies ──────────────────────────────────────────────────────────────
|
||||
policies_block = ""
|
||||
if character_name:
|
||||
_, policies_block = _load_env_character(character_name)
|
||||
|
||||
# ── 5. Skills ────────────────────────────────────────────────────────────────
|
||||
skills_block = _load_skills(skills_list)
|
||||
|
||||
# ── Assemble ─────────────────────────────────────────────────────────────────
|
||||
sections = [
|
||||
base_prompt,
|
||||
personality_block,
|
||||
tools_block,
|
||||
policies_block,
|
||||
skills_block,
|
||||
]
|
||||
|
||||
# Filter empty sections dan gabungkan
|
||||
return "\n\n".join(s for s in sections if s.strip())
|
||||
|
||||
@ -281,7 +281,11 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
def _process_dm(self, jid, body):
|
||||
session = self._session_mgr.get_or_create(
|
||||
jid, self._build_system_prompt(tools_definition=self._tools_def)
|
||||
jid, self._build_system_prompt(
|
||||
tools_definition=self._tools_def,
|
||||
character=config.AGENT_CHARACTER or None,
|
||||
skills=config.AGENT_SKILLS.split(",") if config.AGENT_SKILLS else None,
|
||||
)
|
||||
)
|
||||
session.cancel_timer()
|
||||
|
||||
@ -308,7 +312,11 @@ class XMPPClient(ClientXMPP):
|
||||
|
||||
def _process_muc(self, room, nick, body):
|
||||
session = self._session_mgr.get_or_create(
|
||||
room, self._build_system_prompt(tools_definition=self._tools_def)
|
||||
room, self._build_system_prompt(
|
||||
tools_definition=self._tools_def,
|
||||
character=config.AGENT_CHARACTER or None,
|
||||
skills=config.AGENT_SKILLS.split(",") if config.AGENT_SKILLS else None,
|
||||
)
|
||||
)
|
||||
session.cancel_timer()
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import curses
|
||||
import threading
|
||||
import config
|
||||
from .render import init_colors, draw
|
||||
from .input import handle_key
|
||||
from .agent import log, WELCOME_ART
|
||||
@ -40,7 +41,11 @@ class HendrikTUI:
|
||||
stdscr.keypad(True)
|
||||
stdscr.refresh()
|
||||
|
||||
self.messages = [{"role": "system", "content": self.build_system_prompt(tools_definition=self.tools_def)}]
|
||||
self.messages = [{"role": "system", "content": self.build_system_prompt(
|
||||
tools_definition=self.tools_def,
|
||||
character=config.AGENT_CHARACTER or None,
|
||||
skills=config.AGENT_SKILLS.split(",") if config.AGENT_SKILLS else None,
|
||||
)}]
|
||||
log(self, "welcome", WELCOME_ART)
|
||||
|
||||
while self.running:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user