Text Generation
PyTorch
English
French
hyperdimensional-computing
spiking-neural-networks
hdc
snn
lif
stdp
r-stdp
brain-inspired
cognitive-architecture
agentic
cpu-only
no-transformer
no-gpu
non-transformer
sparse-distributed-memory
kanerva
attractor-networks
global-workspace-theory
predictive-coding
neuromodulators
consciousness
kuramoto
vector-symbolic-architecture
vsa
one-shot-learning
instant-learning
pure-python
numpy
scipy
fastapi
web-dashboard
multi-modal
bpe
benchmark
beam-search
attention
reinforcement-learning
n-gram
kneser-ney
generative-ai
reasoning
creative-writing
research
prototype
| """ | |
| SPIKE Web — serveur FastAPI + WebSocket pour visualisation temps réel. | |
| Endpoints: | |
| GET / — dashboard HTML | |
| GET /api/stats — stats JSON du cerveau | |
| POST /api/chat — envoie un message, retourne la réponse | |
| POST /api/learn — apprentissage explicite | |
| POST /api/dream — déclenche le mode rêve | |
| POST /api/reward — applique une récompense R-STDP | |
| POST /api/reset — reset le réseau | |
| WS /ws/spikes — stream temps réel des spikes | |
| WS /ws/chat — stream temps réel d'une conversation | |
| Le dashboard se connecte aux WebSockets et affiche: | |
| - Raster plot des spikes (sensory/assoc/motor) | |
| - Compteur d'activité par population | |
| - Poids synaptiques (heatmap) | |
| - Log de conversation | |
| - Stats en temps réel | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| import json | |
| import asyncio | |
| import time | |
| import numpy as np | |
| from typing import Optional | |
| # Ajoute le répertoire parent au path | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request | |
| from fastapi.responses import HTMLResponse, JSONResponse, FileResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from spike import SpikeBrain, SpikeConfig | |
| from nova import Nova, NovaConfig | |
| from hybrid import HybridBrain, HybridConfig | |
| # ---------------------------------------------------------------------- # | |
| # Modèles de requête | |
| # ---------------------------------------------------------------------- # | |
| class ChatRequest(BaseModel): | |
| message: str | |
| brain: str = "spike" # "spike" | "nova" | "hybrid" | |
| class LearnRequest(BaseModel): | |
| fact: str | |
| value: Optional[str] = None | |
| brain: str = "spike" | |
| class DreamRequest(BaseModel): | |
| n_replays: int = 5 | |
| ticks_per_replay: int = 20 | |
| brain: str = "spike" | |
| class RewardRequest(BaseModel): | |
| reward: float = 1.0 | |
| class ResetRequest(BaseModel): | |
| brain: str = "spike" | |
| # ---------------------------------------------------------------------- # | |
| # Cerveaux globaux (partagés entre les requêtes) | |
| # ---------------------------------------------------------------------- # | |
| class BrainManager: | |
| """Gère les instances des 3 cerveaux.""" | |
| def __init__(self): | |
| print("Initialisation des cerveaux...") | |
| t0 = time.time() | |
| # SPIKE — config par défaut (rapide) | |
| self.spike = SpikeBrain(SpikeConfig( | |
| n_sensory=400, n_associative=1000, n_motor=400, | |
| sim_ticks=30, rstdp_enabled=True, | |
| )) | |
| # NOVA — config moyenne | |
| self.nova = Nova(NovaConfig(D=5000, sdm_locations=10000)) | |
| # HYBRID — combine les deux | |
| self.hybrid = HybridBrain(HybridConfig( | |
| spike=SpikeConfig(n_sensory=300, n_associative=800, n_motor=300, sim_ticks=25), | |
| nova=NovaConfig(D=3000, sdm_locations=5000), | |
| )) | |
| print(f"Prêt en {time.time()-t0:.2f}s") | |
| def get(self, name: str): | |
| if name == "spike": | |
| return self.spike | |
| if name == "nova": | |
| return self.nova | |
| if name == "hybrid": | |
| return self.hybrid | |
| raise ValueError(f"Unknown brain: {name}") | |
| brains: Optional[BrainManager] = None | |
| # ---------------------------------------------------------------------- # | |
| # FastAPI app | |
| # ---------------------------------------------------------------------- # | |
| app = FastAPI(title="SPIKE Web", version="1.0.0") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Static files | |
| STATIC_DIR = os.path.join(os.path.dirname(__file__), "static") | |
| app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") | |
| async def startup(): | |
| global brains | |
| brains = BrainManager() | |
| # ---------------------------------------------------------------------- # | |
| # Routes | |
| # ---------------------------------------------------------------------- # | |
| async def index(): | |
| """Dashboard HTML.""" | |
| index_path = os.path.join(STATIC_DIR, "index.html") | |
| with open(index_path, "r", encoding="utf-8") as f: | |
| return HTMLResponse(f.read()) | |
| async def get_stats(): | |
| """Stats JSON de tous les cerveaux.""" | |
| return { | |
| "spike": brains.spike.stats(), | |
| "nova": brains.nova.stats(), | |
| "hybrid": brains.hybrid.stats(), | |
| "time": time.time(), | |
| } | |
| async def chat(req: ChatRequest): | |
| """Envoie un message au cerveau choisi.""" | |
| brain = brains.get(req.brain) | |
| t0 = time.time() | |
| response = brain.chat(req.message) | |
| t1 = time.time() | |
| return { | |
| "input": req.message, | |
| "response": response, | |
| "time_ms": (t1 - t0) * 1000, | |
| "brain": req.brain, | |
| } | |
| async def learn(req: LearnRequest): | |
| """Apprentissage explicite.""" | |
| brain = brains.get(req.brain) | |
| result = brain.learn(req.fact, req.value) | |
| return {"brain": req.brain, "result": result} | |
| async def dream(req: DreamRequest): | |
| """Déclenche le mode rêve.""" | |
| brain = brains.get(req.brain) | |
| if hasattr(brain, "dream"): | |
| result = brain.dream(req.n_replays, req.ticks_per_replay) | |
| else: | |
| result = {"error": "brain does not support dream"} | |
| return {"brain": req.brain, "result": result} | |
| async def reward(req: RewardRequest): | |
| """Applique une récompense R-STDP (SPIKE seulement).""" | |
| if hasattr(brains.spike, "give_reward"): | |
| result = brains.spike.give_reward(req.reward) | |
| else: | |
| result = {"error": "no reward method"} | |
| return {"result": result} | |
| async def reset(req: ResetRequest): | |
| """Reset le réseau.""" | |
| brain = brains.get(req.brain) | |
| if hasattr(brain, "net"): | |
| brain.net.reset() | |
| elif hasattr(brain, "resonator"): | |
| brain.resonator.reset() | |
| return {"status": "reset", "brain": req.brain} | |
| async def list_tools(): | |
| """Liste les outils disponibles.""" | |
| return {"tools": [t.name for t in brains.spike.agent.tools]} | |
| # ---------------------------------------------------------------------- # | |
| # WebSocket — stream des spikes en temps réel | |
| # ---------------------------------------------------------------------- # | |
| async def ws_spikes(ws: WebSocket): | |
| """ | |
| Stream temps réel des spikes du cerveau SPIKE. | |
| Le client peut envoyer des messages pour: | |
| - {"cmd": "start", "input": "calcule 2+2"} — démarre une simulation | |
| - {"cmd": "stop"} — arrête | |
| - {"cmd": "status"} — demande l'état | |
| Le serveur envoie à chaque tick: | |
| - {"type": "tick", "t": 42, "sensory": [0,1,0,...], | |
| "assoc": [...], "motor": [...]} | |
| - {"type": "done", "response": "..."} | |
| """ | |
| await ws.accept() | |
| try: | |
| while True: | |
| msg = await ws.receive_text() | |
| try: | |
| data = json.loads(msg) | |
| except json.JSONDecodeError: | |
| await ws.send_json({"error": "invalid json"}) | |
| continue | |
| cmd = data.get("cmd") | |
| if cmd == "start": | |
| input_text = data.get("input", "") | |
| # Lance la simulation tick par tick | |
| brain = brains.spike | |
| brain.net.reset(soft=False) | |
| I_static = brain.coder.encode_text_to_current(input_text, | |
| gain=brain.cfg.input_gain) | |
| # Patrons d'apprentissage | |
| import re | |
| learn_match = None | |
| for pat in [r"apprends?\s+(?:que\s+)?(.+)", | |
| r"mémorise\s+(?:que\s+)?(.+)"]: | |
| m = re.search(pat, input_text, re.IGNORECASE) | |
| if m: | |
| learn_match = m.group(1) | |
| break | |
| if learn_match: | |
| if " est " in learn_match: | |
| k, v = learn_match.split(" est ", 1) | |
| result = brain.learn(k.strip(), v.strip()) | |
| else: | |
| result = brain.learn(learn_match.strip()) | |
| await ws.send_json({ | |
| "type": "learn", | |
| "result": {k: v for k, v in result.items() | |
| if not isinstance(v, list)}, | |
| }) | |
| continue | |
| n_ticks = brain.cfg.sim_ticks | |
| for tick in range(n_ticks): | |
| mask = (brain.rng.random(brain.cfg.n_sensory) < brain.cfg.poisson_rate).astype(np.float32) | |
| I_tick = I_static * mask | |
| brain.net.tick(I_tick) | |
| if brain.cfg.stdp_enabled: | |
| brain._apply_stdp() | |
| # Envoie l'état (sous-échantillonné pour ne pas saturer) | |
| sensory = brain.net.last_spikes["sensory"].astype(np.int8).tolist() | |
| # Pour l'associative, on sous-échantillonne (trop grand sinon) | |
| assoc = brain.net.last_spikes["associative"].astype(np.int8) | |
| # On envoie seulement les premiers 200 neurones | |
| assoc_sample = assoc[:200].tolist() | |
| motor = brain.net.last_spikes["motor"].astype(np.int8).tolist() | |
| # Poids moyens | |
| w_sens = float(brain.net.syn_sens_to_assoc.W.data.mean()) if brain.net.syn_sens_to_assoc.W.nnz > 0 else 0 | |
| w_motor = float(brain.net.syn_assoc_to_motor.W.data.mean()) if brain.net.syn_assoc_to_motor.W.nnz > 0 else 0 | |
| w_direct = float(brain.syn_sens_to_motor.W.data.mean()) if (brain.syn_sens_to_motor and brain.syn_sens_to_motor.W.nnz > 0) else 0 | |
| await ws.send_json({ | |
| "type": "tick", | |
| "t": tick, | |
| "sensory": sensory, | |
| "assoc": assoc_sample, | |
| "motor": motor, | |
| "counts": { | |
| "sensory": int(brain.net.last_spikes["sensory"].sum()), | |
| "assoc": int(brain.net.last_spikes["associative"].sum()), | |
| "motor": int(brain.net.last_spikes["motor"].sum()), | |
| }, | |
| "weights": { | |
| "sens_assoc": w_sens, | |
| "assoc_motor": w_motor, | |
| "sens_motor_direct": w_direct, | |
| }, | |
| }) | |
| await asyncio.sleep(0.02) # 50 fps max | |
| # Réponse finale | |
| brain.n_calls += 1 | |
| response = brain.chat(input_text) | |
| await ws.send_json({ | |
| "type": "done", | |
| "response": response, | |
| "stats": brain.stats(), | |
| }) | |
| elif cmd == "stop": | |
| await ws.send_json({"type": "stopped"}) | |
| elif cmd == "status": | |
| await ws.send_json({ | |
| "type": "status", | |
| "stats": brains.spike.stats(), | |
| }) | |
| else: | |
| await ws.send_json({"error": f"unknown cmd: {cmd}"}) | |
| except WebSocketDisconnect: | |
| return | |
| except Exception as e: | |
| try: | |
| await ws.send_json({"error": str(e)}) | |
| except Exception: | |
| pass | |
| # ---------------------------------------------------------------------- # | |
| # WebSocket — chat stream | |
| # ---------------------------------------------------------------------- # | |
| async def ws_chat(ws: WebSocket): | |
| """Chat bidirectionnel — envoie le texte, reçoit la réponse en stream.""" | |
| await ws.accept() | |
| try: | |
| while True: | |
| msg = await ws.receive_text() | |
| data = json.loads(msg) | |
| input_text = data.get("input", "") | |
| brain_name = data.get("brain", "spike") | |
| brain = brains.get(brain_name) | |
| t0 = time.time() | |
| response = brain.chat(input_text) | |
| t1 = time.time() | |
| await ws.send_json({ | |
| "input": input_text, | |
| "response": response, | |
| "time_ms": (t1 - t0) * 1000, | |
| "brain": brain_name, | |
| }) | |
| except WebSocketDisconnect: | |
| return | |
| except Exception as e: | |
| try: | |
| await ws.send_json({"error": str(e)}) | |
| except Exception: | |
| pass | |
| # ---------------------------------------------------------------------- # | |
| # Main | |
| # ---------------------------------------------------------------------- # | |
| def main(): | |
| import uvicorn | |
| print("\n" + "=" * 60) | |
| print(" SPIKE WEB — Dashboard temps réel") | |
| print("=" * 60) | |
| print(" http://localhost:4141") | |
| print("=" * 60 + "\n") | |
| uvicorn.run(app, host="0.0.0.0", port=4141, log_level="info") | |
| if __name__ == "__main__": | |
| main() | |