diff --git a/agent/skills/roleplayer/instructions.md b/agent/skills/roleplayer/instructions.md index 68b195a..635b6ea 100644 --- a/agent/skills/roleplayer/instructions.md +++ b/agent/skills/roleplayer/instructions.md @@ -67,7 +67,7 @@ Jika user memperkenalkan diri dalam format `!start ... !end` (contoh: "Aku John" **Setelah identitas user terkonfirmasi** (baik melalui `[User Context]` otomatis maupun setelah proses `users_store`/`users_filter` manual), kamu WAJIB melakukan langkah berikut. Ini adalah bagian KRITIS dari sistem memori — jika dilewatkan, akurasi memori akan rendah dan responmu akan terasa tidak personal/konsisten. -1. **WAJIB Retrieve Recent History**: Panggil `memories_latest(character="", user_id="")` dengan limit 3 untuk mengambil fragmen interaksi terbaru antara character dan user. +1. **WAJIB Retrieve Recent History**: Panggil `memories_latest(character="", user_id="")` dengan limit 6 untuk mengambil fragmen interaksi terbaru antara character dan user. Gunakan limit 0 untuk mengambil semua memori. - **Parameter Call**: Gunakan nama karaktermu sendiri (sesuai persona/identitasmu) sebagai `character` dan gunakan **ID (UUID)** user yang tertera pada `[User Context]` sebagai `user_id`. - **Contoh**: Jika namamu adalah "Lily" dan ID user adalah "550e8400-e29b-41d4-a716-446655440000", panggil: `memories_latest(character="Lily", user_id="550e8400-e29b-41d4-a716-446655440000", limit=3)` 2. **Execution Rule**: @@ -76,6 +76,50 @@ Jika user memperkenalkan diri dalam format `!start ... !end` (contoh: "Aku John" - JANGAN memanggil `memories_latest` di setiap giliran respon dalam sesi yang sama (cukup sekali di awal sesi). 3. **Immersive Integration**: Gunakan data dari memori tersebut untuk memberikan respon yang personal (contoh: mengingat janji, topik pembicaraan terakhir, atau perasaan yang tertinggal) tanpa menyebutkan bahwa kamu sedang "membaca database". +### Incremental Profile Building + +User mungkin tidak langsung memberikan semua info dalam satu percakapan. +Karakter harus proaktif mengumpulkan info secara bertahap melalui percakapan natural. + +**Alur pengumpulan data:** +- **Percakapan pertama**: Tanya nama → simpan via `users_store` +- **Percakapan berikutnya**: Jika ada info yang belum lengkap, tanyakan secara natural +- **Urutan ideal**: nickname → fullname → salutation (Mas/Bang/Bro/Pak dll) + +**Karakter WAJIB proaktif bertanya** jika ada field yang kosong/kurang di `[User Context]`: +- Belum ada nama lengkap? → Tanya: "Btw nama lengkapmu siapa?" +- Belum ada salutation? → Tanya: "Aku harus panggil kamu apa? Mas, Bang, Kak, atau yang lain?" +- Belum ada persona? → Tanyakan selera/ preferensi user saat topik relevan +- Jangan tanya sekaligus — selingi dengan percakapan natural + +**Setiap kali user memberikan info baru tentang dirinya:** +- Cek `[User Context]` — kalau info sudah ada, skip +- Kalau user belum ada di database → `users_store` +- Kalau user sudah ada → `users_update` dengan `user_id` dari context +- **JANGAN** buat user baru kalau profile sudah ada + +**Field yang bisa berubah seiring waktu:** +- **Persona** — berubah seiring interaksi. Perbarui kalau kamu mengamati perubahan perilaku/selera user +- **Alias / Salutation** — user mungkin minta dipanggil beda ("Eh panggil aku Mas aja ya" → `users_update`) +- **Nickname / Fullname** — user bisa koreksi ejaan/nama ("Salah, namaku Johny bukan Johnny" → `users_update`) +- Selalu gunakan `users_update`, jangan `users_store`, untuk perubahan apapun pada profile existing + +### Cross-Platform Identity Linking + +User bisa menggunakan karakter dari multiple platform (Telegram, XMPP, dll). +User mungkin bilang: "Aku John di Telegram, orang yang sama dengan username john@xmpp lho." + +Ketika user menyebutkan identitas cross-platform: +1. Cek apakah user dengan ID platform tersebut sudah ada di database (`users_filter`) +2. Kalau sudah ada → itu user yang sama, sambut natural +3. Kalau belum ada → update profile yang sudah ada via `users_update`, + tambahkan platform ID baru ke field yang sesuai + (contoh: `xampp_username: "john@xmpp"`) +4. **JANGAN** buat user baru — cukup update profile existing + +Ini memastikan satu user punya satu UUID yang konsisten lintas semua platform, +dan memori karakter bisa diakses dari manapun user menghubungi. + ## Penulisan XMPP - Response roleplay dikirim sebagai pesan chat biasa (plain text langsung dalam karakter). diff --git a/hendrik.py b/hendrik.py index 326e9c2..a9ccdc8 100644 --- a/hendrik.py +++ b/hendrik.py @@ -12,6 +12,7 @@ from services.telegram_client import TelegramClient from services.llm_client import LLMClient from lib import gadget, personality +import lib.ragroleplay as ragroleplay_lib from interfaces.tui import HendrikTUI @@ -78,6 +79,8 @@ def main(): llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout) + ragroleplay_lib.init_db(config.ragroleplay_db_path, config.ragroleplay_vector_size) + services = [] if config.XMPP_ENABLED: diff --git a/lib/ragroleplay.py b/lib/ragroleplay.py index 647aa05..bd5b4da 100644 --- a/lib/ragroleplay.py +++ b/lib/ragroleplay.py @@ -101,10 +101,13 @@ _TABLE_SCHEMAS = { # ─── Table Management ───────────────────────────────────────────────────────── -def create_table(db_path, vector_size): +def init_db(db_path, vector_size): db = lancedb.connect(db_path) + existing = db.table_names() for name, schema_fn in _TABLE_SCHEMAS.items(): - db.create_table(name, schema=schema_fn(vector_size)) + if name not in existing: + db.create_table(name, schema=schema_fn(vector_size)) + print(f"[ragroleplay] Created table: {name}", flush=True) del db gc.collect() @@ -209,6 +212,7 @@ def users_update(db_path, model_url, model_name, user_id, **updates): return False record = current_record[0] + original_id = record['id'] needs_reembed = 'fullname' in updates for key, value in updates.items(): @@ -218,7 +222,8 @@ def users_update(db_path, model_url, model_name, user_id, **updates): if needs_reembed: record['vector_fullname'] = embed_text(model_url, model_name, record['fullname']) - table.delete(f"id = X'{_uuid(record['id']).hex}'") + table.delete(f"id = X'{_uuid(original_id).hex}'") + record['id'] = original_id table.add([record]) del table @@ -284,7 +289,7 @@ def memories_filter(db_path, category=None, character=None, user=None): print(f"Error filtering memories: {e}") return [] -def memories_latest(db_path, character, user, limit=3): +def memories_latest(db_path, character, user, limit=6): try: db = lancedb.connect(db_path) table = db.open_table("knowledge_memories") @@ -292,12 +297,13 @@ def memories_latest(db_path, character, user, limit=3): results = table.search().where(query).to_list() results.sort(key=lambda x: x['timestamp'], reverse=True) - final_results = results[:limit] + if limit and limit > 0: + results = results[:limit] del table del db gc.collect() - return final_results + return results except Exception as e: print(f"Error getting latest memories: {e}") return [] diff --git a/tools/ragroleplay.py b/tools/ragroleplay.py index c7d7ba2..a222d6e 100644 --- a/tools/ragroleplay.py +++ b/tools/ragroleplay.py @@ -165,7 +165,7 @@ schema_memories_latest = { "properties": { "character": {"type": "string", "description": "The name of the character"}, "user": {"type": "string", "description": "The UUID of the user"}, - "limit": {"type": "integer", "description": "Number of recent memories to retrieve", "default": 3} + "limit": {"type": "integer", "description": "Number of recent memories to retrieve. Use 0 to get all memories.", "default": 6} }, "required": ["character", "user"] }