#!/usr/bin/env python3 """Norovox Nythos — Self-Learning + Skill-Forge Runtime. İki ayrı yetenek: 1. **SelfLearner**: Kullanıcı düzeltmelerini KB'ye ekler. Her konuşmadan sonra "ne öğrendi?" diye sentezler + bilgi tabanına yazar. Sonraki konuşmalarda otomatik hatırlar. 2. **SkillForge**: Kullanıcının öğrettiği yeni beceriyi kalıcı hale getirir. Örnek: "şöyle yaparsan: 1. X, 2. Y, 3. Z" dediğinde, runtime bunu bir "skill template" olarak kaydeder ve sonra benzer görevlerde otomatik uygular. KULLANIM: from nythos_self_learning import SelfLearningChat chat = SelfLearningChat( model_url="http://localhost:8080/v1/chat/completions", kb_path="./norovox_kb.json", skills_path="./norovox_skills.json" ) # Kullanıcı bir şey öğretir chat.sor("Şöyle yazıyorum: 'bilgi al(X)' demek 'X hakkında web arası yap + Türkçe özetle'") # → SkillForge bunu skill olarak kaydeder # Sonra aynı pattern'i kullanır chat.sor("bilgi al(Tesla)") # → otomatik web_ara + özetleme uygular """ import json, re, hashlib from pathlib import Path from datetime import datetime from typing import Optional # ════════════════════════════════════════════════════════════════ # KNOWLEDGE BASE — sürekli güncellenen bilgi tabanı # ════════════════════════════════════════════════════════════════ class KnowledgeBase: """Basit JSON-tabanlı KB. Konu → fact mapping.""" def __init__(self, path: str = "./norovox_kb.json"): self.path = Path(path) self.data: dict = {"facts": {}, "meta": {"created": str(datetime.now())}} if self.path.exists(): try: self.data = json.loads(self.path.read_text(encoding="utf-8")) except Exception: pass def add(self, topic: str, fact: str, source: str = "user"): topic_key = topic.lower().strip() if topic_key not in self.data["facts"]: self.data["facts"][topic_key] = [] self.data["facts"][topic_key].append({ "fact": fact, "source": source, "added": str(datetime.now()), }) self._save() def search(self, query: str, limit: int = 5) -> list: q = query.lower() matches = [] for topic, facts in self.data["facts"].items(): if any(w in topic for w in q.split()): matches.extend([f["fact"] for f in facts[:limit]]) return matches[:limit] def remove(self, topic: str): self.data["facts"].pop(topic.lower(), None) self._save() def _save(self): self.path.write_text(json.dumps(self.data, ensure_ascii=False, indent=2), encoding="utf-8") def size(self) -> int: return sum(len(v) for v in self.data["facts"].values()) # ════════════════════════════════════════════════════════════════ # SKILL FORGE — yeni beceri kazanma # ════════════════════════════════════════════════════════════════ class SkillForge: """Kullanıcının öğrettiği yeni pattern/skill'leri kalıcı yapar. Skill = pattern + adımlar + örnek input/output Storage: JSON Eşleştirme: regex pattern üzerinden """ SKILL_TEACHING_PATTERN = re.compile( r"(?:şöyle|şu şekilde|şunu yap|yaparsan)[:\s]+(.+)", re.IGNORECASE | re.DOTALL ) def __init__(self, path: str = "./norovox_skills.json"): self.path = Path(path) self.skills: dict = {} if self.path.exists(): try: self.skills = json.loads(self.path.read_text(encoding="utf-8")) except Exception: pass def _hash(self, name: str) -> str: return hashlib.sha256(name.encode()).hexdigest()[:8] def detect_teaching(self, user_input: str) -> Optional[dict]: """Kullanıcı bir şey öğretiyor mu tespit et.""" # Pattern 1: "X komutu Y anlamına gelir" m1 = re.search(r"['\"]?(\w+\([^)]+\))['\"]?\s*(?:komutu|fonksiyonu|demek|anlamına geliyor)\s*['\"]?(.+?)['\"]?\s*$", user_input, re.DOTALL) if m1: return {"name": m1.group(1), "definition": m1.group(2).strip()} # Pattern 2: "şöyle yap: ...adımlar..." m2 = self.SKILL_TEACHING_PATTERN.search(user_input) if m2: steps_text = m2.group(1).strip() # Numbered list? steps = re.findall(r"(?:^|[\n,])\s*\d+[.)]\s*([^\n,]+)", steps_text) if steps: first_line = user_input.split(":")[0].strip() return {"name": first_line[-40:], "steps": steps} # Pattern 3: "X yapmak için Y kullan" m3 = re.search(r"(\w+)\s+(?:yapmak için|için)\s+(.+)", user_input, re.IGNORECASE) if m3 and "kullan" in m3.group(2).lower(): return {"name": m3.group(1), "method": m3.group(2)} return None def forge(self, skill_info: dict): """Yeni skill kaydet.""" name = skill_info.get("name", "untitled")[:50] skill_id = self._hash(name) self.skills[skill_id] = { "name": name, "info": skill_info, "created": str(datetime.now()), "use_count": 0, } self._save() return skill_id def match(self, user_input: str) -> Optional[dict]: """Mevcut bir skill'in input'la eşleşip eşleşmediği.""" for sid, skill in self.skills.items(): name = skill["name"].lower() # Pattern: skill name + arg pattern base_name = re.sub(r"\([^)]*\)", "", name).strip() if base_name and base_name in user_input.lower(): skill["use_count"] += 1 self._save() return skill return None def list_skills(self) -> list: return [(s["name"], s["use_count"]) for s in self.skills.values()] def _save(self): self.path.write_text(json.dumps(self.skills, ensure_ascii=False, indent=2), encoding="utf-8") # ════════════════════════════════════════════════════════════════ # SELF-LEARNER — konuşmadan sonuç çıkartma # ════════════════════════════════════════════════════════════════ class SelfLearner: """Konuşmayı analiz eder, KB'ye eklenebilecek fact'leri çıkartır.""" FACT_PATTERNS = [ (re.compile(r"(\w[\w\s]+?)\s+(?:şudur|şöyledir|şuna eşittir|=)\s+(.+?)(?:\.|$)", re.I), lambda m: (m.group(1).strip(), m.group(2).strip())), (re.compile(r"benim adım\s+(.+?)(?:\.|$)", re.I), lambda m: ("kullanıcı adı", m.group(1).strip())), (re.compile(r"(?:tercih ediyorum|seviyorum):\s*(.+?)(?:\.|$)", re.I), lambda m: ("kullanıcı tercihi", m.group(1).strip())), (re.compile(r"(\w+)\s+(?:doğum tarihi|kuruluş tarihi)\s+(.+?)(?:\.|$)", re.I), lambda m: (m.group(1).strip() + " tarihi", m.group(2).strip())), ] def __init__(self, kb: KnowledgeBase): self.kb = kb def extract_facts(self, user_input: str, assistant_response: str) -> list: facts = [] for pattern, mapper in self.FACT_PATTERNS: for m in pattern.finditer(user_input): t, f = mapper(m) if t and f and len(f) < 200: facts.append((t, f)) return facts def learn(self, user_input: str, assistant_response: str) -> int: """Konuşmadan fact çıkar + KB'ye ekle.""" facts = self.extract_facts(user_input, assistant_response) for topic, fact in facts: self.kb.add(topic, fact, source="self_learner") return len(facts) # ════════════════════════════════════════════════════════════════ # SELF-LEARNING CHAT — tüm modülleri birleştir # ════════════════════════════════════════════════════════════════ class SelfLearningChat: """Norovox Nythos için kendi kendine öğrenen sohbet runtime'ı.""" SYSTEM_BASE = ( "Sen Norovox Nythos'sun — kendi kendine öğrenen Türkçe AI.\n\n" "Sahip olduğun yetenekler:\n" "- ... derin düşünce blokları\n" "- Yerleşik araç çağrısı: bloğu\n" "- Kullanıcının öğrettiği yeni beceri ve bilgileri kalıcı hatırlama\n" "- Bilgi tabanından (KB) otomatik bağlam getirme\n\n" "# ÖĞRENDIKLERIM (KB'den)\n__KB__\n\n" "# KAZANDIĞIM BECERILER\n__SKILLS__\n\n" "Konuşma sırasında kullanıcı yeni bir şey öğretirse runtime otomatik kaydeder. " "Sen Türkçe yanıt ver, derinden düşün." ) def __init__(self, model_url: str = "http://localhost:8080/v1/chat/completions", kb_path: str = "./norovox_kb.json", skills_path: str = "./norovox_skills.json"): self.model_url = model_url self.kb = KnowledgeBase(kb_path) self.skills = SkillForge(skills_path) self.learner = SelfLearner(self.kb) def _build_system(self, query: str) -> str: kb_hits = self.kb.search(query, limit=3) kb_ctx = "\n".join(f"- {h}" for h in kb_hits) if kb_hits else "(henüz yok)" skill_list = self.skills.list_skills()[:3] skill_ctx = "\n".join(f"- {n} (kullanım: {u})" for n, u in skill_list) if skill_list else "(henüz yok)" return self.SYSTEM_BASE.replace("__KB__", kb_ctx).replace("__SKILLS__", skill_ctx) def sor(self, user_input: str) -> dict: """Tam pipeline: skill-match → LLM → learn.""" result = { "input": user_input, "matched_skill": None, "new_skill": None, "learned_facts": 0, "response": "", } # 1) Skill match — daha önce öğretilen pattern var mı? matched = self.skills.match(user_input) if matched: result["matched_skill"] = matched["name"] # 2) Teaching detect — yeni skill öğretiliyor mu? teaching = self.skills.detect_teaching(user_input) if teaching: skill_id = self.skills.forge(teaching) result["new_skill"] = teaching.get("name", skill_id) # 3) LLM çağrısı (varsa) system_msg = self._build_system(user_input) # Burada gerçek LLM çağrısı yapılır — placeholder offline mod: try: import urllib.request body = json.dumps({ "messages": [ {"role": "system", "content": system_msg}, {"role": "user", "content": user_input} ], "max_tokens": 600, "temperature": 0.5 }).encode() req = urllib.request.Request(self.model_url, data=body, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=60) as r: data = json.loads(r.read()) result["response"] = data["choices"][0]["message"]["content"] except Exception as e: result["response"] = f"[offline mod — model bağlantısı yok: {e}]" # 4) Konuşmadan öğren learned = self.learner.learn(user_input, result["response"]) result["learned_facts"] = learned return result def stats(self) -> dict: return { "kb_facts": self.kb.size(), "skills_count": len(self.skills.skills), "skills_total_use": sum(s["use_count"] for s in self.skills.skills.values()), } # ════════════════════════════════════════════════════════════════ # CLI TEST # ════════════════════════════════════════════════════════════════ if __name__ == "__main__": import sys, tempfile print("== Norovox Nythos Self-Learning + Skill-Forge Test ==\n") # Geçici dosyalarla test with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: kb_path = f.name with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: skills_path = f.name chat = SelfLearningChat(kb_path=kb_path, skills_path=skills_path) # 1) Yeni skill öğret print("[1] Kullanıcı yeni skill öğretiyor...") r1 = chat.sor("bilgi_al(X) komutu demek 'X hakkında web ara + Türkçe özetle' anlamına geliyor") print(f" New skill kaydedildi: {r1['new_skill']}") print(f" Stats: {chat.stats()}\n") # 2) Aynı pattern'i kullan print("[2] Aynı pattern'le kullanım...") r2 = chat.sor("bilgi_al(Tesla şirketi)") print(f" Matched skill: {r2['matched_skill']}") print(f" Stats: {chat.stats()}\n") # 3) Fact öğret print("[3] Kullanıcı bir fact söylüyor...") r3 = chat.sor("benim adım Emre. Tercih ediyorum: kısa yanıtlar.") print(f" KB'ye eklenen fact sayısı: {r3['learned_facts']}") print(f" Stats: {chat.stats()}\n") # 4) KB hatırlama print("[4] KB'den hatırla...") facts = chat.kb.search("kullanıcı") for f in facts: print(f" → {f}") print("\n[OK] Self-Learning + Skill-Forge çalışıyor") # Cleanup Path(kb_path).unlink(missing_ok=True) Path(skills_path).unlink(missing_ok=True)