diff --git a/config.py b/config.py index d2388cc..5913912 100644 --- a/config.py +++ b/config.py @@ -22,6 +22,11 @@ def _yaml_get(*keys, default=None): return default return current if current is not None else default +ragroleplay_db_path = _yaml_get("ragroleplay", "db_path", default="./.ragroleplay" ) +ragroleplay_vector_size = _yaml_get("ragroleplay", "vector_size", default=768 ) # used on table create only +ragroleplay_model_url = _yaml_get("ragroleplay", "model_url", default="http://localhost:11434/api/embed" ) +ragroleplay_model_name = _yaml_get("ragroleplay", "model_name", default="nomic-embed-text" ) # Need to download model from local ollama + llm_timeout = int(_yaml_get("llm", "timeout", default=600)) _providers = _yaml_get("llm", "providers", default=[]) MODELS_ITEMS = [] diff --git a/default-config.yaml b/default-config.yaml index 8c43bdb..f480cd8 100644 --- a/default-config.yaml +++ b/default-config.yaml @@ -38,6 +38,12 @@ rag: persist_dir: "~/.config/hendrik/rag" # LanceDB Vector Store (all-MiniLM-L6-v2, local) model_path: "~/.config/hendrik/models" # Custom path to store/load embedding model. +ragroleplay: + db_path : "./.ragroleplay" + vector_size : 768 # used on table create only + model_url : "http://localhost:11434/api/embed" + model_name : "nomic-embed-text" # Need to download model from local ollama + session: db_path: "~/.config/hendrik/sessions.json" diff --git a/lib/ragroleplay.py b/lib/ragroleplay.py new file mode 100644 index 0000000..891191e --- /dev/null +++ b/lib/ragroleplay.py @@ -0,0 +1,542 @@ +import requests, gc, lancedb, pyarrow, uuid +from datetime import datetime + +def create_table(db_path, vector_size): + db = lancedb.connect(db_path) + + schema_user = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('fullname', pyarrow.string(), metadata={'description': 'Fullname'}), + pyarrow.field('nickname', pyarrow.string(), metadata={'description': 'Nickname'}), + pyarrow.field('alias', pyarrow.string(), nullable=True, metadata={'description': 'Another call name'}), + pyarrow.field('persona', pyarrow.string(), nullable=True, metadata={'description': 'Persona'}), + pyarrow.field('telegram_id', pyarrow.string(), nullable=True, metadata={'description': 'Telegram ID'}), + pyarrow.field('telegram_username', pyarrow.string(), nullable=True, metadata={'description': 'Telegram username'}), + pyarrow.field('xampp_username', pyarrow.string(), nullable=True, metadata={'description': 'XAMPP username'}), + pyarrow.field('vector_fullname', pyarrow.list_(pyarrow.float32(), vector_size) metadata={'description': 'Vector data of fullname'}), + ]) + db.create_table("knowledge_user", schema=schema_user) + + schema_memories = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('character', pyarrow.string(), metadata={'description': 'Active Character'}), + pyarrow.field('user', pyarrow.string(), metadata={'description': 'Active User'}), + pyarrow.field('relative_time', pyarrow.string(), metadata={'description': 'Explaining when it happen'}), + pyarrow.field('event', pyarrow.string(), metadata={'description': 'What event'}), + pyarrow.field('category', pyarrow.string(), metadata={'description': 'Event category'}), + pyarrow.field('detail', pyarrow.string(), metadata={'description': 'Event detail'}), + pyarrow.field('physical', pyarrow.string(), metadata={'description': 'Character physical state'}), + pyarrow.field('emotional', pyarrow.string(), metadata={'description': 'Character emotional state'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size) metadata={'description': 'Vector data of combined data'}), + ]) + db.create_table("knowledge_memories", schema=schema_memories) + + schema_scenario = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('character', pyarrow.string(), metadata={'description': 'Active Character'}), + pyarrow.field('user', pyarrow.string(), metadata={'description': 'Active User'}), + pyarrow.field('scenario', pyarrow.string(), metadata={'description': 'Detailed scenario description'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size), metadata={'description': 'Vector data of scenario'}), + ]) + db.create_table("knowledge_scenario", schema=schema_scenario) + + schema_todo = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('keyword', pyarrow.string(), metadata={'description': 'Keywords to trigger to-do'}), + pyarrow.field('when', pyarrow.string(), metadata={'description': 'When'}), + pyarrow.field('do', pyarrow.string(), metadata={'description': 'Do'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size), metadata={'description': 'Vector data of keyword+when'}), + ]) + db.create_table("knowledge_todo", schema=schema_todo) + + schema_outfit_set = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('keyword', pyarrow.string(), metadata={'description': 'Keywords of outfit set'}), + pyarrow.field('when', pyarrow.string(), metadata={'description': 'When it better to use'}), + pyarrow.field('outfit', pyarrow.string(), metadata={'description': 'Outfit set description'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size), metadata={'description': 'Vector data of keyword+when'}), + ]) + db.create_table("knowledge_outfit_set", schema=schema_outfit_set) + + schema_world = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('category', pyarrow.string(), metadata={'description': 'Category of world'}), + pyarrow.field('location', pyarrow.string(), metadata={'description': 'Name of the place or location in the world'}), + pyarrow.field('description', pyarrow.string(), metadata={'description': 'Detailed world description'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size), metadata={'description': 'Vector data of combined content'}), + ]) + db.create_table("knowledge_world", schema=schema_world) + + schema_object = pyarrow.schema([ + pyarrow.field('id', pyarrow.uuid(), metadata={'description': 'Unique identifier (UUID)'}), + pyarrow.field('timestamp', pyarrow.timestamp('ms'), metadata={'description': 'When record created'}), + pyarrow.field('keyword', pyarrow.string(), metadata={'description': 'Keywords of object'}), + pyarrow.field('when', pyarrow.string(), metadata={'description': 'When it better to use'}), + pyarrow.field('where', pyarrow.string(), metadata={'description': 'Where the object can be found'}), + pyarrow.field('name', pyarrow.string(), metadata={'description': 'Object name'}), + pyarrow.field('description', pyarrow.string(), metadata={'description': 'Object description'}), + pyarrow.field('shape', pyarrow.string(), metadata={'description': 'Object shape'}), + pyarrow.field('usage', pyarrow.string(), metadata={'description': 'Object usage'}), + pyarrow.field('vector_context', pyarrow.list_(pyarrow.float32(), vector_size), metadata={'description': 'Vector data of keyword+when'}), + ]) + db.create_table("knowledge_object", schema=schema_object) + + # parameters: eat, drink, sleep + # Time: morning, afternoon, evening, night + + del db + gc.collect() + + +def embed_text(url, model, text): + response = requests.post(url=url, json={"model": model, "input": text} ) + data = response.json() + return data["embeddings"][0] + +def users_store(db_path, model_url, model_name, fullname, nickname, alias, persona, telegram_id, telegram_username, xampp_username): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_user") + vector = embed_text(model_url, model_name, fullname) + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "fullname": fullname, + "nickname": nickname, + "alias": alias, + "persona": persona, + "telegram_id": telegram_id, + "telegram_username": telegram_username, + "xampp_username": xampp_username, + "vector_fullname": vector + } + + table.add([record]) + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error storing user: {e}") + return False + +def users_filter(db_path, query_name=None, unique_id=None, persona_keyword=None, user_id=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_user") + filters = [] + if user_id: + filters.append(f"id = '{user_id}'") + if unique_id: + filters.append(f"(telegram_id = '{unique_id}' OR telegram_username = '{unique_id}' OR xampp_username = '{unique_id}')") + if query_name: + filters.append(f"(fullname = '{query_name}' OR nickname = '{query_name}' OR alias = '{query_name}')") + if persona_keyword: + filters.append(f"persona LIKE '%{persona_keyword}%'") + + query = " AND ".join(filters) + results = table.search().where(query).to_list() if query else table.to_list() + + del table + del db + gc.collect() + return results + except Exception as e: + print(f"Error filtering users: {e}") + return [] + +def users_delete(db_path, user_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_user") + + table.delete(f"id = '{user_id}'") + + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error deleting user: {e}") + return False + +def users_update(db_path, model_url, model_name, user_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_user") + + current_record = table.search().where(f"id = '{user_id}'").to_list() + + if not current_record: + return False + + record = current_record[0] + needs_reembed = 'fullname' in updates + + for key, value in updates.items(): + if key in record: + record[key] = value + + if needs_reembed: + record['vector_fullname'] = embed_text(model_url, model_name, record['fullname']) + + table.upsert([record]) + + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error updating user: {e}") + return False + +def memories_store(db_path, model_url, model_name, character, user, relative_time, event, category, detail, physical, emotional): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_memories") + + context_text = f"Event: {event}\nDetail: {detail}\nCategory: {category}\nTime: {relative_time}" + vector = embed_text(model_url, model_name, context_text) + + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "character": character, + "user": user, + "relative_time": relative_time, + "event": event, + "category": category, + "detail": detail, + "physical": physical, + "emotional": emotional, + "vector_context": vector + } + table.add([record]) + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error storing memory: {e}") + return False + +def memories_filter(db_path, category=None, character=None, user=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_memories") + query = "" + filters = [] + + if category: + filters.append(f"category = '{category}'") + if character: + filters.append(f"character = '{character}'") + if user: + filters.append(f"user = '{user}'") + if filters: + query = " AND ".join(filters) + + results = table.search().where(query).to_list() if query else table.to_list() + + del table + del db + gc.collect() + return results + except Exception as e: + print(f"Error filtering memories: {e}") + return [] + +def memories_summarize(db_path, prompt_text, model_url, model_name, search_limit=5): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_memories") + vector = embed_text(model_url, model_name, prompt_text) + results = table.search(vector, vector_column_name="vector_context").limit(search_limit).to_list() + + if not results: + return "Tidak ada memori relevan untuk diringkas." + + summaries = [] + for res in results: + summaries.append(f"[{res['category']}] {res['event']}: {res['detail']}") + + result_text = "Ringkasan Memori Relevan:\n" + "\n".join(summaries) + + del table + del db + gc.collect() + return result_text + except Exception as e: + print(f"Error summarizing memories: {e}") + return f"Gagal membuat ringkasan: {str(e)}" + +def memories_delete(db_path, memory_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_memories") + table.delete(f"id = '{memory_id}'") + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error deleting memory: {e}") + return False + +def memories_update(db_path, model_url, model_name, memory_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_memories") + current_record = table.search().where(f"id = '{memory_id}'").to_list() + if not current_record: + return False + + record = current_record[0] + affected_fields = ['event', 'detail', 'category', 'relative_time'] + needs_reembed = any(field in updates for field in affected_fields) + + for key, value in updates.items(): + if key in record: + record[key] = value + + if needs_reembed: + context_text = f"Event: {record['event']}\nDetail: {record['detail']}\nCategory: {record['category']}\nTime: {record['relative_time']}" + record['vector_context'] = embed_text(model_url, model_name, context_text) + + table.upsert([record]) + del table + del db + gc.collect() + return True + except Exception as e: + print(f"Error updating memory: {e}") + return False + +def objects_store(db_path, model_url, model_name, keyword, when, where, name, description, shape, usage): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_object") + context_text = f"Keyword: {keyword}\nWhen: {when}\nName: {name}" + vector = embed_text(model_url, model_name, context_text) + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "keyword": keyword, "when": when, "where": where, "name": name, "description": description, "shape": shape, "usage": usage, + "vector_context": vector + } + table.add([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def objects_filter(db_path, keyword=None, object_id=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_object") + query = "" + if object_id: query = f"id = '{object_id}'" + elif keyword: query = f"keyword = '{keyword}'" + results = table.search().where(query).to_list() if query else table.to_list() + del table; del db; gc.collect() + return results + except Exception as e: return [] + +def objects_update(db_path, model_url, model_name, object_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_object") + current = table.search().where(f"id = '{object_id}'").to_list() + if not current: return False + record = current[0] + needs_reembed = any(f in updates for f in ['keyword', 'when', 'name']) + for k, v in updates.items(): + if k in record: record[k] = v + if needs_reembed: + record['vector_context'] = embed_text(model_url, model_name, f"Keyword: {record['keyword']}\nWhen: {record['when']}\nName: {record['name']}") + table.upsert([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def objects_delete(db_path, object_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_object") + table.delete(f"id = '{object_id}'") + del table; del db; gc.collect() + return True + except Exception as e: return False + +def outfits_store(db_path, model_url, model_name, keyword, when, outfit): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_outfit_set") + context_text = f"Keyword: {keyword}\nWhen: {when}" + vector = embed_text(model_url, model_name, context_text) + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "keyword": keyword, "when": when, "outfit": outfit, + "vector_context": vector + } + table.add([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def outfits_filter(db_path, keyword=None, outfit_id=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_outfit_set") + query = "" + if outfit_id: query = f"id = '{outfit_id}'" + elif keyword: query = f"keyword = '{keyword}'" + results = table.search().where(query).to_list() if query else table.to_list() + del table; del db; gc.collect() + return results + except Exception as e: return [] + +def outfits_update(db_path, model_url, model_name, outfit_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_outfit_set") + current = table.search().where(f"id = '{outfit_id}'").to_list() + if not current: return False + record = current[0] + needs_reembed = any(f in updates for f in ['keyword', 'when']) + for k, v in updates.items(): + if k in record: record[k] = v + if needs_reembed: + record['vector_context'] = embed_text(model_url, model_name, f"Keyword: {record['keyword']}\nWhen: {record['when']}") + table.upsert([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def outfits_delete(db_path, outfit_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_outfit_set") + table.delete(f"id = '{outfit_id}'") + del table; del db; gc.collect() + return True + except Exception as e: return False + +def todos_store(db_path, model_url, model_name, keyword, when, do): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_todo") + context_text = f"Keyword: {keyword}\nWhen: {when}" + vector = embed_text(model_url, model_name, context_text) + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "keyword": keyword, "when": when, "do": do, + "vector_context": vector + } + table.add([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def todos_filter(db_path, keyword=None, todo_id=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_todo") + query = "" + if todo_id: query = f"id = '{todo_id}'" + elif keyword: query = f"keyword = '{keyword}'" + results = table.search().where(query).to_list() if query else table.to_list() + del table; del db; gc.collect() + return results + except Exception as e: return [] + +def todos_update(db_path, model_url, model_name, todo_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_todo") + current = table.search().where(f"id = '{todo_id}'").to_list() + if not current: return False + record = current[0] + needs_reembed = any(f in updates for f in ['keyword', 'when']) + for k, v in updates.items(): + if k in record: record[k] = v + if needs_reembed: + record['vector_context'] = embed_text(model_url, model_name, f"Keyword: {record['keyword']}\nWhen: {record['when']}") + table.upsert([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def todos_delete(db_path, todo_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_todo") + table.delete(f"id = '{todo_id}'") + del table; del db; gc.collect() + return True + except Exception as e: return False + +def worlds_store(db_path, model_url, model_name, category, location, description): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_world") + context_text = f"Category: {category}\nLocation: {location}\nDescription: {description}" + vector = embed_text(model_url, model_name, context_text) + record = { + "id": str(uuid.uuid4()), + "timestamp": datetime.now(), + "category": category, "location": location, "description": description, + "vector_context": vector + } + table.add([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def worlds_filter(db_path, category=None, location=None, world_id=None): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_world") + filters = [] + if world_id: filters.append(f"id = '{world_id}'") + if category: filters.append(f"category = '{category}'") + if location: filters.append(f"location = '{location}'") + query = " AND ".join(filters) + results = table.search().where(query).to_list() if query else table.to_list() + del table; del db; gc.collect() + return results + except Exception as e: return [] + +def worlds_update(db_path, model_url, model_name, world_id, **updates): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_world") + current = table.search().where(f"id = '{world_id}'").to_list() + if not current: return False + record = current[0] + needs_reembed = any(f in updates for f in ['category', 'location', 'description']) + for k, v in updates.items(): + if k in record: record[k] = v + if needs_reembed: + record['vector_context'] = embed_text(model_url, model_name, f"Category: {record['category']}\nLocation: {record['location']}\nDescription: {record['description']}") + table.upsert([record]) + del table; del db; gc.collect() + return True + except Exception as e: return False + +def worlds_delete(db_path, world_id): + try: + db = lancedb.connect(db_path) + table = db.open_table("knowledge_world") + table.delete(f"id = '{world_id}'") + del table; del db; gc.collect() + return True + except Exception as e: return False + diff --git a/ragroleplay_table_create.py b/ragroleplay_table_create.py new file mode 100644 index 0000000..a89394c --- /dev/null +++ b/ragroleplay_table_create.py @@ -0,0 +1,4 @@ +import config, lib.ragroleplay as ragroleplay + +if __name__ == "__main__": + ragroleplay.create_table(config.ragroleplay_db_path, config.ragroleplay_vector_size) diff --git a/tools/ragroleplay.py b/tools/ragroleplay.py new file mode 100644 index 0000000..36d6698 --- /dev/null +++ b/tools/ragroleplay.py @@ -0,0 +1,763 @@ +import gc, sys, lancedb +import config, lib.ragroleplay as ragroleplay_lib + +# --- USER TOOLS --- +schema_users_store = { + "type": "function", + "function": { + "name": "users_store", + "description": "Create and store a new user profile in the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "fullname": {"type": "string", "description": "Full name of the user"}, + "nickname": {"type": "string", "description": "Nickname of the user"}, + "alias": {"type": "string", "description": "Alias of the user"}, + "persona": {"type": "string", "description": "Detailed persona or description of the user"}, + "telegram_id": {"type": "string", "description": "Telegram user ID"}, + "telegram_username": {"type": "string", "description": "Telegram username"}, + "xampp_username": {"type": "string", "description": "XAMPP username"} + }, + "required": ["fullname", "nickname", "telegram_id"] + } + } +} + +schema_users_filter = { + "type": "function", + "function": { + "name": "users_filter", + "description": "Retrieve user profiles based on flexible filters like names, IDs, or persona keywords.", + "parameters": { + "type": "object", + "properties": { + "query_name": {"type": "string", "description": "Search by fullname, nickname, or alias"}, + "unique_id": {"type": "string", "description": "Search by telegram_id, telegram_username, or xampp_username"}, + "persona_keyword": {"type": "string", "description": "Search for a keyword within the user's persona"}, + "user_id": {"type": "string", "description": "Search by absolute user UUID"} + } + } + } +} + +schema_users_update = { + "type": "function", + "function": { + "name": "users_update", + "description": "Update an existing user profile using their unique user ID.", + "parameters": { + "type": "object", + "properties": { + "user_id": {"type": "string", "description": "The unique UUID of the user to update"}, + "fullname": {"type": "string", "description": "Updated full name"}, + "nickname": {"type": "string", "description": "Updated nickname"}, + "alias": {"type": "string", "description": "Updated alias"}, + "persona": {"type": "string", "description": "Updated persona description"}, + "telegram_id": {"type": "string", "description": "Updated telegram ID"}, + "telegram_username": {"type": "string", "description": "Updated telegram username"}, + "xampp_username": {"type": "string", "description": "Updated xampp username"} + }, + "required": ["user_id"] + } + } +} + +schema_users_delete = { + "type": "function", + "function": { + "name": "users_delete", + "description": "Permanently delete a user profile from the database using their unique user ID.", + "parameters": { + "type": "object", + "properties": { + "user_id": {"type": "string", "description": "The unique UUID of the user to delete"} + }, + "required": ["user_id"] + } + } +} + +# --- MEMORIES TOOLS --- +schema_memories_check = { + "type": "function", + "function": { + "name": "memories_check", + "description": "Search and retrieve relevant memories from the knowledge database based on the provided prompt text.", + "parameters": { + "type": "object", + "properties": { + "prompt_text": {"type": "string", "description": "The text prompt to search for in memories"}, + "search_limit": {"type": "integer", "description": "The maximum number of relevant memories to return"} + }, + "required": ["prompt_text", "search_limit"] + } + } +} + +schema_memories_store = { + "type": "function", + "function": { + "name": "memories_store", + "description": "Store a new memory entry into the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "character": {"type": "string", "description": "The active character name"}, + "user": {"type": "string", "description": "The active user name"}, + "relative_time": {"type": "string", "description": "Description of when the event happened (e.g., '2 hours ago', 'yesterday')"}, + "event": {"type": "string", "description": "Brief summary of the event"}, + "category": {"type": "string", "description": "Category of the memory (e.g., 'Emotional', 'Physical', 'Relationship')"}, + "detail": {"type": "string", "description": "Detailed description of the event"}, + "physical": {"type": "string", "description": "Physical state of the character during the event"}, + "emotional": {"type": "string", "description": "Emotional state of the character during the event"} + }, + "required": ["character", "user", "relative_time", "event", "category", "detail", "physical", "emotional"] + } + } +} + +schema_memories_filter = { + "type": "function", + "function": { + "name": "memories_filter", + "description": "Retrieve memories based on specific filters such as category, character, or user.", + "parameters": { + "type": "object", + "properties": { + "category": {"type": "string", "description": "Filter by memory category"}, + "character": {"type": "string", "description": "Filter by character name"}, + "user": {"type": "string", "description": "Filter by user name"} + } + } + } +} + +schema_memories_summarize = { + "type": "function", + "function": { + "name": "memories_summarize", + "description": "Get a condensed summary of the most relevant memories based on the prompt text.", + "parameters": { + "type": "object", + "properties": { + "prompt_text": {"type": "string", "description": "The text prompt to summarize relevant memories for"}, + "search_limit": {"type": "integer", "description": "The number of memories to include in the summary", "default": 5} + }, + "required": ["prompt_text"] + } + } +} + +schema_memories_update = { + "type": "function", + "function": { + "name": "memories_update", + "description": "Update an existing memory entry. Use the memory ID to identify the record.", + "parameters": { + "type": "object", + "properties": { + "memory_id": {"type": "string", "description": "The unique ID of the memory to update"}, + "relative_time": {"type": "string", "description": "Updated relative time"}, + "event": {"type": "string", "description": "Updated event summary"}, + "category": {"type": "string", "description": "Updated category"}, + "detail": {"type": "string", "description": "Updated detail"}, + "physical": {"type": "string", "description": "Updated physical state"}, + "emotional": {"type": "string", "description": "Updated emotional state"} + }, + "required": ["memory_id"] + } + } +} + +schema_memories_delete = { + "type": "function", + "function": { + "name": "memories_delete", + "description": "Permanently delete a memory entry from the database using its unique ID.", + "parameters": { + "type": "object", + "properties": { + "memory_id": {"type": "string", "description": "The unique ID of the memory to delete"} + }, + "required": ["memory_id"] + } + } +} + +# --- WORLD TOOLS --- +schema_worlds_store = { + "type": "function", + "function": { + "name": "worlds_store", + "description": "Store a new world location/description in the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "category": {"type": "string", "description": "Category of the world/location"}, + "location": {"type": "string", "description": "Name of the place or location"}, + "description": {"type": "string", "description": "Detailed description of the area"} + }, + "required": ["category", "location", "description"] + } + } +} + +schema_worlds_filter = { + "type": "function", + "function": { + "name": "worlds_filter", + "description": "Search for world locations by category, name, or unique ID.", + "parameters": { + "type": "object", + "properties": { + "category": {"type": "string", "description": "Filter by category"}, + "location": {"type": "string", "description": "Filter by location name"}, + "world_id": {"type": "string", "description": "Filter by absolute UUID"} + } + } + } +} + +schema_worlds_update = { + "type": "function", + "function": { + "name": "worlds_update", + "description": "Update an existing world location entry.", + "parameters": { + "type": "object", + "properties": { + "world_id": {"type": "string", "description": "The unique UUID of the world entry"}, + "category": {"type": "string", "description": "Updated category"}, + "location": {"type": "string", "description": "Updated location name"}, + "description": {"type": "string", "description": "Updated description"} + }, + "required": ["world_id"] + } + } +} + +schema_worlds_delete = { + "type": "function", + "function": { + "name": "worlds_delete", + "description": "Delete a world location from the database.", + "parameters": { + "type": "object", + "properties": { + "world_id": {"type": "string", "description": "The unique UUID of the world entry"} + }, + "required": ["world_id"] + } + } +} + +# --- OBJECT TOOLS --- +schema_objects_store = { + "type": "function", + "function": { + "name": "objects_store", + "description": "Store a new object in the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "Keyword to trigger the object"}, + "when": {"type": "string", "description": "When the object is used or appears"}, + "where": {"type": "string", "description": "Where the object can be found"}, + "name": {"type": "string", "description": "Name of the object"}, + "description": {"type": "string", "description": "Detailed description of the object"}, + "shape": {"type": "string", "description": "Physical description or shape"}, + "usage": {"type": "string", "description": "How the object is used"} + }, + "required": ["keyword", "when", "name"] + } + } +} + +schema_objects_filter = { + "type": "function", + "function": { + "name": "objects_filter", + "description": "Search for objects by keyword or unique ID.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "The object's keyword"}, + "object_id": {"type": "string", "description": "The unique UUID of the object"} + } + } + } +} + +schema_objects_update = { + "type": "function", + "function": { + "name": "objects_update", + "description": "Update an existing object entry.", + "parameters": { + "type": "object", + "properties": { + "object_id": {"type": "string", "description": "The unique UUID of the object"}, + "keyword": {"type": "string", "description": "Updated keyword"}, + "when": {"type": "string", "description": "Updated usage context"}, + "where": {"type": "string", "description": "Updated location"}, + "name": {"type": "string", "description": "Updated name"}, + "description": {"type": "string", "description": "Updated description"}, + "shape": {"type": "string", "description": "Updated shape"}, + "usage": {"type": "string", "description": "Updated usage"} + }, + "required": ["object_id"] + } + } +} + +schema_objects_delete = { + "type": "function", + "function": { + "name": "objects_delete", + "description": "Delete an object from the database.", + "parameters": { + "type": "object", + "properties": { + "object_id": {"type": "string", "description": "The unique UUID of the object"}, + }, + "required": ["object_id"] + } + } +} + +# --- OUTFIT TOOLS --- +schema_outfits_store = { + "type": "function", + "function": { + "name": "outfits_store", + "description": "Store a new outfit set in the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "Keyword for the outfit"}, + "when": {"type": "string", "description": "When this outfit is worn"}, + "outfit": {"type": "string", "description": "Description of the outfit set"} + }, + "required": ["keyword", "when", "outfit"] + } + } +} + +schema_outfits_filter = { + "type": "function", + "function": { + "name": "outfits_filter", + "description": "Search for outfits by keyword or unique ID.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "The outfit keyword"}, + "outfit_id": {"type": "string", "description": "The unique UUID of the outfit"} + } + } + } +} + +schema_outfits_update = { + "type": "function", + "function": { + "name": "outfits_update", + "description": "Update an existing outfit entry.", + "parameters": { + "type": "object", + "properties": { + "outfit_id": {"type": "string", "description": "The unique UUID of the outfit"}, + "keyword": {"type": "string", "description": "Updated keyword"}, + "when": {"type": "string", "description": "Updated usage context"}, + "outfit": {"type": "string", "description": "Updated outfit description"} + }, + "required": ["outfit_id"] + } + } +} + +schema_outfits_delete = { + "type": "function", + "function": { + "name": "outfits_delete", + "description": "Delete an outfit from the database.", + "parameters": { + "type": "object", + "properties": { + "outfit_id": {"type": "string", "description": "The unique UUID of the outfit"}, + }, + "required": ["outfit_id"] + } + } +} + +# --- TODO TOOLS --- +schema_todos_store = { + "type": "function", + "function": { + "name": "todos_store", + "description": "Store a new to-do item in the knowledge database.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "Trigger keyword for the to-do"}, + "when": {"type": "string", "description": "When the to-do should be performed"}, + "do": {"type": "string", "description": "Action to perform"} + }, + "required": ["keyword", "when", "do"] + } + } +} + +schema_todos_filter = { + "type": "function", + "function": { + "name": "todos_filter", + "description": "Search for to-dos by keyword or unique ID.", + "parameters": { + "type": "object", + "properties": { + "keyword": {"type": "string", "description": "The to-do keyword"}, + "todo_id": {"type": "string", "description": "The unique UUID of the to-do"} + } + } + } +} + +schema_todos_update = { + "type": "function", + "function": { + "name": "todos_update", + "description": "Update an existing to-do entry.", + "parameters": { + "type": "object", + "properties": { + "todo_id": {"type": "string", "description": "The unique UUID of the to-do"}, + "keyword": {"type": "string", "description": "Updated keyword"}, + "when": {"type": "string", "description": "Updated when context"}, + "do": {"type": "string", "description": "Updated action"} + }, + "required": ["todo_id"] + } + } +} + +schema_todos_delete = { + "type": "function", + "function": { + "name": "todos_delete", + "description": "Delete a to-do from the database.", + "parameters": { + "type": "object", + "properties": { + "todo_id": {"type": "string", "description": "The unique UUID of the to-do"}, + }, + "required": ["todo_id"] + } + } +} + + + +def memories_check(prompt_text, search_limit): + try: + db = lancedb.connect(config.ragroleplay_db_path) + table = db.open_table("knowledge_memories") + query_vector = ragroleplay_lib.embed_text(config.ragroleplay_model_url, config.ragroleplay_model_name, prompt_text) + results = table.search(query_vector, vector_column_name="vector_context").limit(search_limit).to_list() + + if not results: + return "Tidak ada memori yang ditemukan." + + output = [] + output.append(f"Prompt: {prompt_text}\n") + output.append(f"Ditemukan {len(results)} memori yang relevan berdasarkan KONTEKS:\n") + + for i, row in enumerate(results, 1): + mem_info = ( + f"Memori #{i}\n" + f"Kapan: {row['relative_time']}\n" + f"Kejadian: {row['event']}\n" + f"Detail: {row['detail']}\n" + f"Kondisi Fisik: {row['physical']}\n" + f"Emosi: {row['emotional']}\n" + f"Kategori: {row['category']}\n" + f"Skor Jarak: {row.get('_distance', 'N/A')}\n" + ) + output.append(mem_info) + + result_text = "\n".join(output) + + del table + del db + gc.collect() + return result_text + except Exception as e: + return f"Error searching memories: {str(e)}" + +def memories_store(character, user, relative_time, event, category, detail, physical, emotional): + try: + success = ragroleplay_lib.memories_store( + config.ragroleplay_db_path, + config.ragroleplay_model_url, + config.ragroleplay_model_name, + character, + user, + relative_time, + event, + category, + detail, + physical, + emotional + ) + if success: + return "Berhasil menyimpan memori baru ke dalam database." + else: + return "Gagal menyimpan memori. Silakan periksa log." + except Exception as e: + return f"Error while storing memory: {str(e)}" + +def memories_filter(category=None, character=None, user=None): + try: + results = ragroleplay_lib.memories_filter(config.ragroleplay_db_path, category, character, user) + if not results: + return "Tidak ada memori yang sesuai dengan filter tersebut." + + output = [] + output.append(f"Ditemukan {len(results)} memori berdasarkan filter:\n") + for i, row in enumerate(results, 1): + mem_info = ( + f"Memori #{i}\n" + f"Kapan: {row['relative_time']}\n" + f"Kejadian: {row['event']}\n" + f"Detail: {row['detail']}\n" + f"Kategori: {row['category']}\n" + ) + output.append(mem_info) + + return "\n".join(output) + except Exception as e: + return f"Error filtering memories: {str(e)}" + +def memories_summarize(prompt_text, search_limit=5): + try: + result = ragroleplay_lib.memories_summarize( + config.ragroleplay_db_path, + prompt_text, + config.ragroleplay_model_url, + config.ragroleplay_model_name, + search_limit + ) + return result + except Exception as e: + return f"Error summarizing memories: {str(e)}" + +def memories_update(memory_id, **updates): + try: + success = ragroleplay_lib.memories_update( + config.ragroleplay_db_path, + config.ragroleplay_model_url, + config.ragroleplay_model_name, + memory_id, + **updates + ) + if success: + return "Berhasil memperbarui memori." + else: + return "Gagal memperbarui memori. Pastikan ID memori benar." + except Exception as e: + return f"Error while updating memory: {str(e)}" + +def memories_delete(memory_id): + try: + success = ragroleplay_lib.memories_delete(config.ragroleplay_db_path, memory_id) + if success: + return "Berhasil menghapus memori dari database." + else: + return "Gagal menghapus memori." + except Exception as e: + return f"Error while deleting memory: {str(e)}" + +def users_store(fullname, nickname, alias=None, persona=None, telegram_id=None, telegram_username=None, xampp_username=None): + try: + success = ragroleplay_lib.users_store( + config.ragroleplay_db_path, + config.ragroleplay_model_url, + config.ragroleplay_model_name, + fullname, + nickname, + alias, + persona, + telegram_id, + telegram_username, + xampp_username + ) + if success: + return "Berhasil menyimpan profil user baru." + else: + return "Gagal menyimpan profil user." + except Exception as e: + return f"Error while storing user: {str(e)}" + +def users_filter(query_name=None, unique_id=None, persona_keyword=None, user_id=None): + try: + results = ragroleplay_lib.users_filter(config.ragroleplay_db_path, query_name, unique_id, persona_keyword, user_id) + if not results: + return "Tidak ada user yang sesuai dengan filter tersebut." + + output = [] + output.append(f"Ditemukan {len(results)} user berdasarkan filter:\n") + for i, row in enumerate(results, 1): + user_info = ( + f"User #{i}\n" + f"ID: {row['id']}\n" + f"Nama: {row['fullname']} ({row['nickname']})\n" + f"Persona: {row['persona']}\n" + ) + output.append(user_info) + + return "\n".join(output) + except Exception as e: + return f"Error filtering users: {str(e)}" + +def users_update(user_id, **updates): + try: + success = ragroleplay_lib.users_update( + config.ragroleplay_db_path, + config.ragroleplay_model_url, + config.ragroleplay_model_name, + user_id, + **updates + ) + if success: + return "Berhasil memperbarui profil user." + else: + return "Gagal memperbarui profil user. Pastikan ID benar." + except Exception as e: + return f"Error while updating user: {str(e)}" + +def users_delete(user_id): + try: + success = ragroleplay_lib.users_delete(config.ragroleplay_db_path, user_id) + if success: + return "Berhasil menghapus profil user." + else: + return "Gagal menghapus profil user." + except Exception as e: + return f"Error while deleting user: {str(e)}" + +# --- OBJECTS IMPLEMENTATION --- +def objects_store(keyword, when, where, name, description, shape, usage): + try: + success = ragroleplay_lib.objects_store(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, keyword, when, where, name, description, shape, usage) + return "Berhasil menyimpan objek baru." if success else "Gagal menyimpan objek." + except Exception as e: return f"Error: {str(e)}" + +def objects_filter(keyword=None, object_id=None): + try: + results = ragroleplay_lib.objects_filter(config.ragroleplay_db_path, keyword, object_id) + if not results: return "Tidak ada objek yang ditemukan." + output = [f"Ditemukan {len(results)} objek:"] + for i, row in enumerate(results, 1): + output.append(f"Objek #{i}\nID: {row['id']}\nNama: {row['name']}\nKeyword: {row['keyword']}\nPenggunaan: {row['usage']}") + return "\n".join(output) + except Exception as e: return f"Error: {str(e)}" + +def objects_update(object_id, **updates): + try: + success = ragroleplay_lib.objects_update(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, object_id, **updates) + return "Berhasil memperbarui objek." if success else "Gagal memperbarui objek." + except Exception as e: return f"Error: {str(e)}" + +def objects_delete(object_id): + try: + success = ragroleplay_lib.objects_delete(config.ragroleplay_db_path, object_id) + return "Berhasil menghapus objek." if success else "Gagal menghapus objek." + except Exception as e: return f"Error: {str(e)}" + +# --- OUTFIT IMPLEMENTATION --- +def outfits_store(keyword, when, outfit): + try: + success = ragroleplay_lib.outfits_store(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, keyword, when, outfit) + return "Berhasil menyimpan outfit baru." if success else "Gagal menyimpan outfit." + except Exception as e: return f"Error: {str(e)}" + +def outfits_filter(keyword=None, outfit_id=None): + try: + results = ragroleplay_lib.outfits_filter(config.ragroleplay_db_path, keyword, outfit_id) + if not results: return "Tidak ada outfit yang ditemukan." + output = [f"Ditemukan {len(results)} outfit:"] + for i, row in enumerate(results, 1): + output.append(f"Outfit #{i}\nID: {row['id']}\nKeyword: {row['keyword']}\nDeskripsi: {row['outfit']}") + return "\n".join(output) + except Exception as e: return f"Error: {str(e)}" + +def outfits_update(outfit_id, **updates): + try: + success = ragroleplay_lib.outfits_update(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, outfit_id, **updates) + return "Berhasil memperbarui outfit." if success else "Gagal memperbarui outfit." + except Exception as e: return f"Error: {str(e)}" + +def outfits_delete(outfit_id): + try: + success = ragroleplay_lib.outfits_delete(config.ragroleplay_db_path, outfit_id) + return "Berhasil menghapus outfit." if success else "Gagal menghapus outfit." + except Exception as e: return f"Error: {str(e)}" + +# --- TODO IMPLEMENTATION --- +def todos_store(keyword, when, do): + try: + success = ragroleplay_lib.todos_store(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, keyword, when, do) + return "Berhasil menyimpan to-do baru." if success else "Gagal menyimpan to-do." + except Exception as e: return f"Error: {str(e)}" + +def todos_filter(keyword=None, todo_id=None): + try: + results = ragroleplay_lib.todos_filter(config.ragroleplay_db_path, keyword, todo_id) + if not results: return "Tidak ada to-do yang ditemukan." + output = [f"Ditemukan {len(results)} to-do:"] + for i, row in enumerate(results, 1): + output.append(f"To-do #{i}\nID: {row['id']}\nKeyword: {row['keyword']}\nAction: {row['do']}") + return "\n".join(output) + except Exception as e: return f"Error: {str(e)}" + +def todos_update(todo_id, **updates): + try: + success = ragroleplay_lib.todos_update(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, todo_id, **updates) + return "Berhasil memperbarui to-do." if success else "Gagal memperbarui to-do." + except Exception as e: return f"Error: {str(e)}" + +def todos_delete(todo_id): + try: + success = ragroleplay_lib.todos_delete(config.ragroleplay_db_path, todo_id) + return "Berhasil menghapus to-do." if success else "Gagal menghapus to-do." + except Exception as e: return f"Error: {str(e)}" + +# --- WORLD IMPLEMENTATION --- +def worlds_store(category, location, description): + try: + success = ragroleplay_lib.worlds_store(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, category, location, description) + return "Berhasil menyimpan lokasi dunia baru." if success else "Gagal menyimpan lokasi dunia." + except Exception as e: return f"Error: {str(e)}" + +def worlds_filter(category=None, location=None, world_id=None): + try: + results = ragroleplay_lib.worlds_filter(config.ragroleplay_db_path, category, location, world_id) + if not results: return "Tidak ada lokasi dunia yang ditemukan." + output = [f"Ditemukan {len(results)} lokasi:"] + for i, row in enumerate(results, 1): + output.append(f"Lokasi #{i}\nID: {row['id']}\nNama: {row['location']}\nKategori: {row['category']}\nDeskripsi: {row['description']}") + return "\n".join(output) + except Exception as e: return f"Error: {str(e)}" + +def worlds_update(world_id, **updates): + try: + success = ragroleplay_lib.worlds_update(config.ragroleplay_db_path, config.ragroleplay_model_url, config.ragroleplay_model_name, world_id, **updates) + return "Berhasil memperbarui lokasi dunia." if success else "Gagal memperbarui lokasi dunia." + except Exception as e: return f"Error: {str(e)}" + +def worlds_delete(world_id): + try: + success = ragroleplay_lib.worlds_delete(config.ragroleplay_db_path, world_id) + return "Berhasil menghapus lokasi dunia." if success else "Gagal menghapus lokasi dunia." + except Exception as e: return f"Error: {str(e)}"