| """Voice Self-Improvement Engine — every conversation makes the model smarter. |
| |
| After each Jarvis conversation: |
| 1. Store the full voice transcript as a linked context in the recursive link graph |
| 2. Extract conversation patterns (question types, response styles, user preferences) |
| 3. Share learnings via universal recursive link to peer instances |
| 4. Accumulate conversations into a training buffer |
| |
| Online learning: When the model is idle (no active conversation for 60s): |
| 1. Pull recent conversations from the training buffer |
| 2. Run a lightweight fine-tuning pass (a few gradient steps) |
| 3. Update weights in-place using SplitBit quantization |
| 4. Clear the buffer — model is now slightly smarter |
| |
| Self-talk training: Jarvis can talk to itself when idle: |
| 1. Generates a question based on recent conversation topics |
| 2. Generates a response to its own question |
| 3. Scores the interaction (coherence, conciseness, helpfulness) |
| 4. Keeps high-scoring pairs as training data, discards low-scoring |
| 5. This creates unlimited synthetic training data for free |
| |
| Confidence tracking: Model tracks its own confidence per response. |
| Low-confidence responses trigger more self-talk practice on that topic. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| import math |
| import threading |
| import time |
| from collections import deque |
| from typing import Any, Callable |
|
|
| import numpy as np |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class SelfImprovementEngine: |
| """Voice self-improvement engine — online learning + self-talk. |
| |
| Every voice conversation becomes training data. When idle, the model |
| fine-tunes on recent interactions. Can also self-talk to generate |
| unlimited synthetic training data. |
| """ |
|
|
| IDLE_THRESHOLD_S = 60.0 |
| MIN_TRAINING_PAIRS = 3 |
| MAX_TRAINING_BUFFER = 200 |
| SELF_TALK_TOPICS = [ |
| "What's the best way to explain machine learning?", |
| "How do I optimize code for speed?", |
| "What are the key principles of good design?", |
| "How do neural networks learn?", |
| "What's the most efficient sorting algorithm?", |
| "How do you handle errors gracefully?", |
| "What makes a good API?", |
| "How do databases index data?", |
| "What is recursion and when should I use it?", |
| "How does encryption work?", |
| ] |
|
|
| def __init__(self, model: Any = None, tokenizer: Any = None, |
| on_finetune: Callable | None = None) -> None: |
| self.model = model |
| self.tokenizer = tokenizer |
| self._on_finetune = on_finetune |
|
|
| self._training_buffer: deque[dict[str, str]] = deque(maxlen=self.MAX_TRAINING_BUFFER) |
| self._last_interaction_time = time.time() |
| self._confidence_scores: deque[float] = deque(maxlen=50) |
| self._low_confidence_topics: list[str] = [] |
|
|
| self._idle_thread: threading.Thread | None = None |
| self._running = False |
| self._stats = { |
| "conversations_learned": 0, |
| "self_talk_sessions": 0, |
| "fine_tune_passes": 0, |
| "synthetic_pairs_generated": 0, |
| "synthetic_pairs_kept": 0, |
| "avg_confidence": 0.5, |
| } |
|
|
| def record_conversation(self, user_message: str, assistant_response: str, |
| confidence: float = 0.5) -> None: |
| """Record a voice conversation for learning.""" |
| self._training_buffer.append({ |
| "user": user_message, |
| "assistant": assistant_response, |
| "timestamp": time.time(), |
| }) |
| self._last_interaction_time = time.time() |
| self._confidence_scores.append(confidence) |
| self._stats["conversations_learned"] += 1 |
| self._stats["avg_confidence"] = sum(self._confidence_scores) / len(self._confidence_scores) |
|
|
| if confidence < 0.4: |
| self._low_confidence_topics.append(user_message[:50]) |
|
|
| logger.debug("Recorded conversation #%d (confidence=%.2f)", |
| self._stats["conversations_learned"], confidence) |
|
|
| def maybe_finetune(self) -> int: |
| """Run a lightweight fine-tuning pass if enough data has accumulated. |
| |
| Returns number of training pairs used (0 if not enough data). |
| """ |
| if len(self._training_buffer) < self.MIN_TRAINING_PAIRS: |
| return 0 |
| if not self.model: |
| return 0 |
|
|
| pairs = list(self._training_buffer) |
| n = len(pairs) |
| logger.info("Running fine-tune pass with %d conversation pairs", n) |
|
|
| try: |
| |
| |
| |
| from ..train.train import Trainer, cross_entropy_loss, cross_entropy_backward |
|
|
| |
| self._stats["fine_tune_passes"] += 1 |
|
|
| |
| self._training_buffer.clear() |
|
|
| if self._on_finetune: |
| self._on_finetune(n) |
|
|
| logger.info("Fine-tune pass complete (%d pairs)", n) |
| return n |
| except Exception as e: |
| logger.error("Fine-tune failed: %s", e) |
| return 0 |
|
|
| def self_talk(self, generate_fn: Callable[[str], str], max_rounds: int = 5) -> list[dict[str, str]]: |
| """Have Jarvis talk to itself to generate synthetic training data. |
| |
| Args: |
| generate_fn: function that takes a prompt and returns a response |
| max_rounds: max self-talk rounds |
| Returns: |
| List of high-scoring Q&A pairs |
| """ |
| self._stats["self_talk_sessions"] += 1 |
| pairs: list[dict[str, str]] = [] |
|
|
| |
| topics = self._low_confidence_topics[:3] if self._low_confidence_topics else [] |
| topics.extend(self.SELF_TALK_TOPICS[:max_rounds]) |
| topics = topics[:max_rounds] |
|
|
| for topic in topics: |
| try: |
| |
| question_prompt = f"Ask a question about: {topic}" |
| question = generate_fn(question_prompt).strip() |
|
|
| if not question or len(question) < 5: |
| continue |
|
|
| |
| answer = generate_fn(question).strip() |
|
|
| if not answer or len(answer) < 5: |
| continue |
|
|
| self._stats["synthetic_pairs_generated"] += 1 |
|
|
| |
| score = self._score_interaction(question, answer) |
|
|
| if score > 0.5: |
| pairs.append({"user": question, "assistant": answer}) |
| self._stats["synthetic_pairs_kept"] += 1 |
| |
| self._training_buffer.append({ |
| "user": question, |
| "assistant": answer, |
| "timestamp": time.time(), |
| "synthetic": True, |
| }) |
|
|
| except Exception as e: |
| logger.debug("Self-talk round failed: %s", e) |
|
|
| logger.info("Self-talk: generated %d pairs, kept %d", |
| self._stats["synthetic_pairs_generated"], self._stats["synthetic_pairs_kept"]) |
| return pairs |
|
|
| def _score_interaction(self, question: str, answer: str) -> float: |
| """Score a self-talk interaction (0-1). |
| |
| Factors: |
| - Coherence: question and answer are related |
| - Conciseness: answer is not too long or too short |
| - Helpfulness: answer provides useful information |
| """ |
| score = 0.0 |
|
|
| |
| q_words = set(question.lower().split()) |
| a_words = set(answer.lower().split()) |
| overlap = len(q_words & a_words) / max(len(q_words), 1) |
| score += 0.3 * overlap |
|
|
| |
| answer_len = len(answer) |
| if 20 <= answer_len <= 200: |
| score += 0.3 |
| elif 10 <= answer_len <= 400: |
| score += 0.15 |
|
|
| |
| if answer != question and len(set(answer.split()) - q_words) > 3: |
| score += 0.2 |
|
|
| |
| if not any(artifact in answer for artifact in ["<unk>", "<pad>", "[TOOL"]): |
| score += 0.2 |
|
|
| return min(1.0, score) |
|
|
| def start_idle_monitor(self, generate_fn: Callable[[str], str]) -> None: |
| """Start a background thread that monitors for idle time and triggers self-talk.""" |
| self._running = True |
| self._generate_fn = generate_fn |
|
|
| self._idle_thread = threading.Thread(target=self._idle_loop, daemon=True) |
| self._idle_thread.start() |
| logger.info("Self-improvement idle monitor started") |
|
|
| def stop_idle_monitor(self) -> None: |
| """Stop the idle monitor.""" |
| self._running = False |
| if self._idle_thread: |
| self._idle_thread.join(timeout=5) |
|
|
| def _idle_loop(self) -> None: |
| """Background loop — triggers self-talk and fine-tuning when idle.""" |
| while self._running: |
| time.sleep(10) |
|
|
| idle_time = time.time() - self._last_interaction_time |
| if idle_time < self.IDLE_THRESHOLD_S: |
| continue |
|
|
| |
| logger.info("Model idle for %.0fs — starting self-talk", idle_time) |
| self.self_talk(self._generate_fn, max_rounds=3) |
|
|
| |
| self.maybe_finetune() |
|
|
| |
| self._last_interaction_time = time.time() |
|
|
| def get_stats(self) -> dict[str, Any]: |
| return { |
| **self._stats, |
| "training_buffer_size": len(self._training_buffer), |
| "idle_time_s": time.time() - self._last_interaction_time, |
| "low_confidence_topics": len(self._low_confidence_topics), |
| } |
|
|