From a655d738135492a351d02b13d95f8bae027a5adc Mon Sep 17 00:00:00 2001 From: Dani Date: Sat, 3 May 2025 20:00:45 -0400 Subject: [PATCH] Created the project, added files named similar to body parts. --- body.py | 95 +++++++++++++++++++++++++++++++++ headspace/dashboard.py | 40 ++++++++++++++ motor_system/motor_cortex.py | 10 ++++ nervous_system/context.py | 45 ++++++++++++++++ nervous_system/meta_learning.py | 13 +++++ sensory_system/eyes.py | 24 +++++++++ 6 files changed, 227 insertions(+) create mode 100644 body.py create mode 100644 headspace/dashboard.py create mode 100644 motor_system/motor_cortex.py create mode 100644 nervous_system/context.py create mode 100644 nervous_system/meta_learning.py create mode 100644 sensory_system/eyes.py diff --git a/body.py b/body.py new file mode 100644 index 0000000..8f59cc5 --- /dev/null +++ b/body.py @@ -0,0 +1,95 @@ +import os +import torch +import torch.nn.functional as F +import discord +from discord import Intents + +from sensory_system.eyes import Eyes +from nervous_system.cortex import Cortex +from nervous_system.meta_learning import MetaLearner +from memory.hippocampus import Hippocampus +from motor_system.motor_cortex import MotorCortex +from headspace.dashboard import run_dashboard + + +class Organism: + def __init__(self) -> None: + # Device + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + # Sensory organ + self.eyes = Eyes(books_path="content/books") + + # Memory & learning + self.memory = Hippocampus() + self.nervous_system = Cortex(...).to(self.device) + self.meta = MetaLearner(self.nervous_system) + self.motor = MotorCortex() + + # (Optional) Pre-load your 21+ books for future pre-training: + self._load_corpus("content/books") + + def _load_corpus(self, folder_path: str) -> None: + """Read all text files in content/books into memory for later use.""" + self.corpus = [] + for fn in os.listdir(folder_path): + if fn.lower().endswith(".txt"): + with open(os.path.join(folder_path, fn), encoding="utf-8") as f: + self.corpus.append(f.read()) + + def learn_and_respond(self, message: str) -> str: + # 1) Perception via eyes + input_ids = self.eyes.preprocess(message) + input_tensor = torch.tensor([input_ids], dtype=torch.long, device=self.device) + + # 2) Inference + logits = self.nervous_system(input_tensor) + response_ids = logits.argmax(dim=-1)[0].tolist() + response = self.motor.decode(response_ids) + + # 3) Self-supervised loss (predict input back) + loss = F.cross_entropy( + logits.view(-1, logits.size(-1)), + input_tensor.view(-1), + ) + + # 4) Online meta-learning update + self.meta.meta_update(loss) + + # 5) Store interaction + self.memory.store({ + "input_ids": input_tensor.cpu(), + "output_ids": response_ids, + "input_text": message, + "output_text": response + }) + + return response + + +# ————— Discord setup (all in one “body” file) ————— +intents = Intents.default() +intents.message_content = True +client = discord.Client(intents=intents) +organism = Organism() + + +@client.event +async def on_ready() -> None: + print(f"Logged in as {client.user}") + + +@client.event +async def on_message(message: discord.Message) -> None: + if message.author == client.user or not message.content: + return + reply = organism.learn_and_respond(message.content) + await message.channel.send(reply) + + +if __name__ == "__main__": + TOKEN = os.getenv("DISCORD_TOKEN") + if not TOKEN: + raise RuntimeError("DISCORD_TOKEN environment variable not set.") + run_dashboard(organism, host="0.0.0.0", port=5000) + client.run(TOKEN) diff --git a/headspace/dashboard.py b/headspace/dashboard.py new file mode 100644 index 0000000..1d58b71 --- /dev/null +++ b/headspace/dashboard.py @@ -0,0 +1,40 @@ +# dashboard.py + +import threading +from flask import Flask, render_template_string + +app = Flask(__name__) +_organism = None # will be set by run_dashboard() + + +@app.route("/") +def index(): + # Basic stats + total = len(_organism.memory.memory) + # Show up to 10 most recent text interactions + recent = list(_organism.memory.memory)[-10:][::-1] + items = "" + for i in recent: + inp = i.get("input_text", "") + out = i.get("output_text", "") + items += f"
  • In: {inp}
    Out: {out}
  • " + + html = f""" +

    Ruby Dashboard

    +

    Total stored interactions: {total}

    +

    Last {len(recent)} exchanges

    +
      {items}
    + """ + return render_template_string(html) + + +def run_dashboard(organism, host="0.0.0.0", port=5000): + """Call this to launch the dashboard in a background thread.""" + global _organism + _organism = organism + # start Flask in its own thread so it doesn’t block Discord + thread = threading.Thread( + target=lambda: app.run(host=host, port=port, debug=False, use_reloader=False), + daemon=True, + ) + thread.start() diff --git a/motor_system/motor_cortex.py b/motor_system/motor_cortex.py new file mode 100644 index 0000000..4cdbc4e --- /dev/null +++ b/motor_system/motor_cortex.py @@ -0,0 +1,10 @@ +from typing import List + + +class MotorCortex: + """Converts model outputs (char codes) back into a string.""" + def __init__(self) -> None: + pass + + def decode(self, output_ids: List[int]) -> str: + return "".join(chr(i) for i in output_ids) diff --git a/nervous_system/context.py b/nervous_system/context.py new file mode 100644 index 0000000..3bc057e --- /dev/null +++ b/nervous_system/context.py @@ -0,0 +1,45 @@ +import torch +import torch.nn as nn + + +class Cortex(nn.Module): + """The ‘brain’: a char‐level Transformer encoder for self-supervised learning.""" + def __init__( + self, + embed_dim: int = 256, + num_heads: int = 4, + num_layers: int = 4, + ff_dim: int = 512, + max_seq_len: int = 1024, + ) -> None: + super().__init__() + self.vocab_size = 256 # ASCII + self.embed_dim = embed_dim + self.token_embedding = nn.Embedding(self.vocab_size, embed_dim) + self.position_embedding = nn.Embedding(max_seq_len, embed_dim) + + encoder_layer = nn.TransformerEncoderLayer( + d_model=embed_dim, + nhead=num_heads, + dim_feedforward=ff_dim, + ) + self.transformer = nn.TransformerEncoder( + encoder_layer, num_layers=num_layers + ) + self.fc_out = nn.Linear(embed_dim, self.vocab_size) + self.max_seq_len = max_seq_len + + def forward(self, input_ids: torch.Tensor) -> torch.Tensor: + # input_ids: (batch, seq_len) + batch_size, seq_len = input_ids.size() + positions = ( + torch.arange(0, seq_len, device=input_ids.device) + .unsqueeze(0) + .expand(batch_size, -1) + ) + x = self.token_embedding(input_ids) + self.position_embedding(positions) + x = x.permute(1, 0, 2) # (seq_len, batch, embed_dim) + x = self.transformer(x) + x = x.permute(1, 0, 2) # back to (batch, seq_len, embed_dim) + logits = self.fc_out(x) + return logits diff --git a/nervous_system/meta_learning.py b/nervous_system/meta_learning.py new file mode 100644 index 0000000..349c1f3 --- /dev/null +++ b/nervous_system/meta_learning.py @@ -0,0 +1,13 @@ +import torch + + +class MetaLearner: + """Handles online, first-order meta-updates to the cortex.""" + def __init__(self, model: torch.nn.Module, lr: float = 1e-4) -> None: + self.model = model + self.meta_optimizer = torch.optim.Adam(model.parameters(), lr=lr) + + def meta_update(self, loss: torch.Tensor) -> None: + self.meta_optimizer.zero_grad() + loss.backward(retain_graph=True) + self.meta_optimizer.step() diff --git a/sensory_system/eyes.py b/sensory_system/eyes.py new file mode 100644 index 0000000..3139735 --- /dev/null +++ b/sensory_system/eyes.py @@ -0,0 +1,24 @@ +# sensory_system/eyes.py + +import os +from typing import List + + +class Eyes: + """The ‘eyes’ read both live input and, on startup, your book corpus.""" + def __init__(self, books_path: str = None) -> None: + self.corpus: List[str] = [] + if books_path: + self.load_books(books_path) + + def load_books(self, folder_path: str) -> None: + """Load all .txt files from folder_path into self.corpus.""" + for fn in os.listdir(folder_path): + if fn.lower().endswith(".txt"): + full = os.path.join(folder_path, fn) + with open(full, encoding="utf-8") as f: + self.corpus.append(f.read()) + + def preprocess(self, text: str) -> List[int]: + """Turn an input string into a list of char codes (0–255).""" + return [ord(c) % 256 for c in text]