diff --git a/network-poc/BUILDING_BLOCKS.md b/network-poc/BUILDING_BLOCKS.md new file mode 100644 index 0000000..c41b83f --- /dev/null +++ b/network-poc/BUILDING_BLOCKS.md @@ -0,0 +1,525 @@ +# Kipinä Agentic Studio — Rakennuspalaset + +Tämä dokumentti kuvaa projektin UI-komponentit, arkkitehtuuripatternit ja työnkulut niin, että vastaavan hajautetun AI-laskentaverkon ja agenttipohjaisen käyttöliittymän voi rakentaa alusta asti. + +## Yleiskuva + +``` +┌─────────────────────────────────────────────────────┐ +│ Selain (käyttäjä) │ +│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │ +│ │ Verkko- │ │ Koodi- │ │ Agents-näkymä │ │ +│ │ näkymä │ │ labra │ │ ┌───────────────┐ │ │ +│ │ │ │ │ │ │ Terminaali │ │ │ +│ │ Stats │ │ Editor │ │ │ Tab-complete │ │ │ +│ │ Chat │ │ Pipeline │ │ │ Dropdown │ │ │ +│ │ Tokenit │ │ Tulokset │ │ │ Historia │ │ │ +│ └────┬─────┘ └────┬─────┘ │ └───────────────┘ │ │ +│ │ │ └────────┬──────────┘ │ +│ └──────────┬───┘ │ │ +│ UI WebSocket HTTP API │ +│ │ /api/v1/chat │ +│ ┌───────────────┴──────────────┐ │ │ +│ │ Wasm Compute Node │ │ │ +│ │ (Candle + Burn) │ │ │ +│ │ ┌─────────┐ ┌────────────┐ │ │ │ +│ │ │ RAM │ │ IndexedDB │ │ │ │ +│ │ │ Cache │ │ Cache │ │ │ │ +│ │ └─────────┘ └────────────┘ │ │ │ +│ │ ┌─────────────────────────┐ │ │ │ +│ │ │ Model Cache (QwenModel) │ │ │ │ +│ │ └─────────────────────────┘ │ │ │ +│ └──────────────┬───────────────┘ │ │ +│ │ WS │ │ +└─────────────────┼──────────────────────┼─────────────┘ + │ │ + ┌────────┴──────────────────────┴──┐ + │ Hub (Axum + Tokio) │ + │ ┌────────────┐ ┌─────────────┐ │ + │ │ Broadcast │ │ Node │ │ + │ │ Channel │ │ Registry │ │ + │ └────────────┘ └─────────────┘ │ + │ ┌────────────┐ ┌─────────────┐ │ + │ │ Busy-State │ │ Rate Limit │ │ + │ │ Tracker │ │ + Auth │ │ + │ └────────────┘ └─────────────┘ │ + │ ┌─────────────────────────────┐ │ + │ │ SQLite (sessiot, tulokset) │ │ + │ └─────────────────────────────┘ │ + └──────────────────────────────────┘ +``` + +--- + +## 1. WebSocket-reaaliaikakommunikaatio + +### 1.1 Hub ↔ Node broadcast-kanava + +**Tarkoitus:** Jakaa tehtäviä ja vastaanottaa tuloksia kaikilta laskentasolmuilta. + +**Työnkulku:** +1. Hub luo `tokio::sync::broadcast::channel(100)` +2. Jokainen solmu saa oman `rx = stats_tx.subscribe()` +3. Hub broadcastaa tehtävät: `stats_tx.send(json)` +4. Solmut suodattavat viestin tyypin ja `selected_task`:n perusteella + +**Viestityupit:** + +| Tyyppi | Suunta | Sisältö | +|--------|--------|---------| +| `stats` | Hub → kaikki | nodes, vram_gb, tasks | +| `pair_task` | Hub → tokenize-solmut | en, fi tekstiparit | +| `llm_prompt` | Hub → valittu solmu | prompt, model, task_id | +| `llm_chunk` | Solmu → Hub → UI | token (1 kerrallaan) | +| `llm_done` | Solmu → Hub → UI | response, tokens_generated, duration_ms | +| `llm_error` | Solmu → Hub → UI | error, task_id | +| `task_routed` | Hub → UI | status (routed/queued), node_id, message | + +**Lagged-viestien käsittely:** +```rust +match rx.recv().await { + Ok(msg) => { /* käsittele */ } + Err(broadcast::error::RecvError::Lagged(n)) => { + // Ohitetaan vanhat viestit, ei katkaista yhteyttä + continue; + } + Err(_) => break, // Kanava suljettu +} +``` + +### 1.2 Kohdennettu reititys (Direct Channel) + +**Tarkoitus:** Lähetä tehtävä yhdelle tietylle solmulle broadcastin sijaan. + +**Työnkulku:** +1. Jokainen solmu saa `mpsc::unbounded_channel` yhdistyessään +2. Hub tallentaa `node_channels: HashMap` +3. API-pyyntö → valitaan vapaa solmu → lähetetään suoraan kanavaan +4. Broadcast-kanavaa käytetään vain tuloksen välittämiseen UI:lle + +```rust +let channels = state.node_channels.read().await; +if let Some(tx) = channels.get(&target_node_id) { + tx.send(msg.to_string()); +} +``` + +### 1.3 Busy-state ja työjono + +**Tarkoitus:** Estä tehtävien reititys varatuille solmuille. + +**Rakenne:** +- `node_busy: HashSet` — solmut joilla on aktiivinen tehtävä +- Asetetaan kun tehtävä reititetään, vapautetaan `llm_done`/`llm_error`:ssa +- Jos kaikki solmut varattuja → pollaa 500ms välein, max 30s + +**UI-palaute:** +```json +{"type": "task_routed", "status": "queued", "message": "Kaikki 2 solmua varattuja — odotetaan..."} +{"type": "task_routed", "status": "routed", "node_id": 3, "message": "Solmu #3 vapautui (2.5s jonossa)"} +``` + +--- + +## 2. Wasm-laskentasolmu + +### 2.1 Elinkaari + +``` +init() → start_agent_node(ws_url, has_webgpu, device_info, task_id) + │ + ├─ Avaa WebSocket hubiin + ├─ Lähettää auth-viestin (laitetiedot, selected_task) + ├─ Rekisteröityy onmessage-käsittelijä + │ ├─ pair_task → tokenize + │ ├─ llm_prompt → inference + │ └─ ai_task → tensor matmul + └─ Odottaa tehtäviä loopissa +``` + +**Globaali tila (atominen, lukitsematon):** +```rust +static GPU_LOAD_PERCENT: AtomicU32 = AtomicU32::new(50); +static LLM_BUSY: AtomicBool = AtomicBool::new(false); +static SELECTED_TASK: AtomicU32 = AtomicU32::new(0); +``` + +### 2.2 Kolmitasoinen cache + +``` +Pyyntö → [1] RAM-cache (thread_local HashMap) + │ miss + ▼ + [2] IndexedDB (selaimen pysyvä tallennus) + │ miss + ▼ + [3] Verkko (HuggingFace CDN, streaming + 5% progressi) + │ + ▼ + Tallenna → IndexedDB → RAM-cache +``` + +| Taso | Nopeus | Koko | Pysyvyys | +|------|--------|------|----------| +| RAM | ~0ms | Rajaton | Sivulataus | +| IndexedDB | ~50ms | ~50GB | Pysyvä | +| Verkko | ~10s/100MB | ∞ | — | + +**Malliinstanssin cache (neljäs taso):** +```rust +thread_local! { + static MODEL_CACHE: RefCell> = RefCell::new(None); +} +// clear_kv_cache() promptien välillä — ei tarvitse rakentaa mallia uusiksi +``` + +### 2.3 Warmup-esilataus + +**Tarkoitus:** Lataa malli valmiiksi ennen ensimmäistä oikeaa promptia. + +```javascript +// Lähetetään 1 tokenin warmup heti kun WS on auki +uiSocket.send(JSON.stringify({ + type: 'user_text', + text: '{"prompt":"warmup","max_tokens":1}', + task_type: 'qwen-coder' +})); +``` + +--- + +## 3. LLM-inferenssipipeline + +### 3.1 Prompt-formaatti (ChatML + prefill) + +``` +<|im_start|>system +You are a coding assistant. Respond with ONLY code.<|im_end|> +<|im_start|>user +hello world in python<|im_end|> +<|im_start|>assistant +``` ← PREFILL: pakottaa mallin aloittamaan koodilla +``` + +**Prefill-tekniikka:** Lisäämällä ` ``` ` assistantin vastauksen alkuun malli jatkaa suoraan koodilla eikä tuota "Sure! Here is..." -johdantoa. Säästää 10-20 tokenia per vastaus. + +### 3.2 Sampling-parametrit + +| Parametri | Arvo | Tarkoitus | +|-----------|------|-----------| +| `temperature` | 0.7 | Pehmentää jakaumaa, vähentää toistoa | +| `top_k` | 40 | Rajaa valinnan 40 todennäköisimpään tokeniin | +| `repetition_penalty` | 1.15 | Rankaisee jo generoitujen tokenien uudelleenvalintaa | +| `max_tokens` | 128 | Oletusraja, JSON-promptilla konfiguroitavissa | + +**Sampling-funktio (top-k + temperature + repetition penalty):** +```rust +fn sample_top_k_with_penalty(logits, k, temperature, generated_tokens, penalty) -> u32 { + // 1. Repetition penalty: vähennä aiempien tokenien logitteja + // 2. Temperature scaling: jaa logitit temperaturella + // 3. Top-k: ota k suurinta + // 4. Softmax top-k:lle + // 5. Satunnaisvalinta kumulatiivisella todennäköisyydellä (XorShift RNG) +} +``` + +### 3.3 Stop-sekvenssit + +Generointi katkaistaan ja teksti trimmataan kun malli alkaa selittää: + +```rust +let stop_patterns = ["\n###", "\nExplanation", "\nNote:", "\nOutput:", "\n```\n\n"]; +``` + +### 3.4 Vastauksen siivous + +``` +Raakavastaus: "Sure! Here is...\n```python\n# This is a simple program\nprint('hi')\n```" + │ +strip_markdown: "# This is a simple program\nprint('hi')" + │ +strip_preamble: "print('hi')" +``` + +**Tunnistettavat selityskommentit:** `# This is`, `# simple`, `# program that`, `# here is`, `# the following`, `# below` + +### 3.5 Streaming + +Jokainen generoitu token lähetetään heti `llm_chunk`-viestinä: +```json +{"type": "llm_chunk", "token": "print", "prompt": "...", "model": "Qwen2.5-Coder", "task_id": "uuid"} +``` + +UI päivittää streaming-korttia reaaliaikaisesti appendaamalla tokeneita. + +--- + +## 4. Terminaaliemulaattori + +### 4.1 Rakenne + +```html +
+
+
+ $ + +
+
+``` + +### 4.2 Komentojen käsittely + +```javascript +function termExec(cmd) { + // Parsitaan: "kpn" + alikomento + argumentit + // Tuetut: help, run, pipeline, load, status, models, hello, clear + // Agenttinimi → malli-mapping: "coder" → "qwen-coder" +} +``` + +### 4.3 Tab-completion (kolmitasoinen) + +```javascript +const kpnCommands = { + 'kpn': ['help', 'run', 'pipeline', 'load', ...], + 'kpn run': ['coder', 'manager', 'qwen-coder', ...], +}; +const kpnExamples = { + 'kpn run coder': ['"hello world in python"', ...], +}; +``` + +**Käyttö:** + +| Näppäin | Toiminto | +|---------|----------| +| TAB | Täydennä seuraava sana tai avaa dropdown | +| Shift-TAB | Poista viimeinen sana (lainausmerkit kokonaisuutena) | +| ↑ / ↓ | Navigoi dropdownissa (tai komentohistoriassa) | +| Enter | Valitse dropdownista tai suorita komento | +| Esc | Sulje dropdown | + +### 4.4 Dropdown-valikko + +```javascript +function showDropdown(items, prefix) { + // Luo div.term-dd-item per vaihtoehto + // Positio: absolute, bottom: 100% (inputin yläpuolella) + // Mouseenter → highlight, click → valinta +} +``` + +### 4.5 Komentohistoria + +```javascript +const termHistory = []; // Kaikki ajetut komennot (viimeisin ensin) +let termHistIdx = -1; // Nykyinen positio historiassa +// ArrowUp: termHistIdx++, ArrowDown: termHistIdx-- +``` + +--- + +## 5. Status-palkit ja tilaindikaattorit + +### 5.1 Hub-yhteyden tila + +| Tila | Väri | Teksti | Tooltip | +|------|------|--------|---------| +| Yhdistetään | 🟡 | "Yhdistetään..." | WebSocket-yhteys Kipinä Hubiin | +| Yhdistetty | 🟢 | "Yhdistetty" | Tehtävien jakelu aktiivinen | +| Katkennut | 🔴 | "Yhteys katkennut" | Tarkista verkko, lataa uudelleen | + +### 5.2 Laskentasolmun tila + +| Tila | Väri | Teksti | Nappi | +|------|------|--------|-------| +| Ei käynnissä | ⚫ | "—" | `[Alusta laskentasolmu]` sininen | +| Lataa | 🟡 | "Ladataan..." | `[Peruuta]` punainen | +| Valmis | 🟢 | "Qwen2.5-Coder" | `[✓ Valmis]` vihreä | + +### 5.3 Pipeline-tilakone (Codelab) + +``` +Step 1: WebAssembly-ytimen lataus [◯ → ◷ → ✓] +Step 2: Tokenizer (7 MB) [◯ → ◷ → ✓] +Step 3: Mallipainot (990 MB) [◯ → ◷ 45% → ✓ cache] +Step 4: Mallin rakentaminen [◯ → ◷ → ✓] +Step 5: Valmis generoimaan [◯ → ✓] +``` + +**Seuranta console.log-viesteistä:** +```javascript +if (msg.includes('[Coder]') && msg.includes('Malli ladattu')) { + // Merkkaa kaikki vaiheet valmiiksi (myös cache-hitillä) + setStep('step-wasm', 'done'); + setStep('step-tokenizer', 'done'); + setStep('step-model', 'done', 'cache'); + setStep('step-build', 'done'); + setStep('step-ready', 'done'); +} +``` + +--- + +## 6. Tietoturva + +### 6.1 XSS-suojaus + +```javascript +function esc(str) { + return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); +} +``` + +**Käyttöpaikat:** Kaikki `innerHTML`-insertoinnit joissa on käyttäjä- tai backend-dataa. + +### 6.2 System prompt -piilotus + +```javascript +function stripSystemPrompt(prompt) { + const parts = prompt.split('\n\n'); + return parts[parts.length - 1] || prompt; +} +``` + +### 6.3 Viestityyppivalidointi (backend) + +```rust +const ALLOWED_MSG_TYPES: &[&str] = &[ + "auth", "result", "pair_done", "llm_chunk", "llm_done", + "llm_error", "download_progress", "user_text", "single_tokenize_done" +]; + +fn validate_message(text: &str) -> Result { + // 1. JSON-parsinta + // 2. "type"-kenttä pakollinen + // 3. Tyyppi sallittujen listalla + // 4. Tyyppikohtainen validointi (esim. pair_done: token_count <= 10000) +} +``` + +### 6.4 Rate limiting + +```rust +// Per-IP liukuva ikkuna: max 10 pyyntöä per 60s +let entry = limits.entry(addr.ip()).or_insert((now, 0)); +if now.duration_since(entry.0).as_secs() >= 60 { + *entry = (now, 1); +} else { + entry.1 += 1; + if entry.1 > 10 { return 429 Too Many Requests; } +} +``` + +### 6.5 Gamification-huijauksen esto + +```rust +// Hub jakaa task_id:n → tallentaa pending_task_ids:hen +// Merkkejä jaetaan VAIN jos llm_done sisältää validin task_id:n +let valid_task = state.pending_task_ids.lock().unwrap().remove(tid); +if active_incentives && valid_task { + *balance += 20; +} +``` + +--- + +## 7. Syntaksikorostus + +### 7.1 Highlight.js-integraatio + +```html + + +``` + +```javascript +function highlightCode(code) { + if (typeof hljs !== 'undefined') { + return hljs.highlightAuto(code).value; // Automaattinen kielentunnistus + } + return esc(code); // Fallback +} +``` + +**Käyttöpaikat:** Codelab-tulokset, agents-terminaalin vastaukset, network-chat. + +--- + +## 8. Agenttien orkestrointi + +### 8.1 Multi-agent pipeline + +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Manageri │ ──→ │ Koodari │ ──→ │ Testaaja │ +│ Analysoi │ │ Koodaa │ │ Arvioi │ +│ tehtävä │ │ ratkaisu │ │ koodi │ +└──────────┘ └──────────┘ └──────────┘ +``` + +```javascript +async function kpnPipeline(task) { + const plan = await kpnRun('qwen-coder', `Analysoi: ${task}`); + if (!plan) return; + const code = await kpnRun('qwen-coder', `Koodaa: ${plan}`); + if (!code) return; + await kpnRun('smollm-135m', `Arvioi: ${code}`); +} +``` + +### 8.2 Agenttien promptien hallinta + +```javascript +const agentPrompts = { + manager: { model: 'qwen-coder', prompt: 'Olet projektipäällikkö...' }, + coder: { model: 'qwen-coder', prompt: 'Olet ohjelmistokehittäjä...' }, + // ... +}; +// Tallennetaan localStorage:en per agentti +localStorage.setItem('kpn-agent-prompt-coder', customPrompt); +``` + +### 8.3 Yhteinen promptikonteksti + +```javascript +async function kpnRun(model, prompt) { + const parts = []; + if (sharedPrompt) parts.push(sharedPrompt); // Kaikille yhteinen + if (agent.prompt) parts.push(agent.prompt); // Agenttikohtainen + parts.push(prompt); // Käyttäjän pyyntö + const fullPrompt = parts.join('\n\n'); + // → HTTP POST /api/v1/chat/completions +} +``` + +--- + +## 9. Teknologiapino + +| Kerros | Teknologia | Tarkoitus | +|--------|------------|-----------| +| Frontend | Vanilla JS + HTML + CSS | Ei build-steppiä, toimii suoraan | +| Wasm | Rust + wasm-bindgen | Inferenssi selaimessa | +| LLM | Candle (Rust) | Transformer-inferenssi CPU:lla | +| Tensorit | Burn (Rust) | GPU-tensorilaskenta (WebGPU/NdArray) | +| Backend | Axum + Tokio (Rust) | Async WebSocket + HTTP -palvelin | +| Tietokanta | SQLite (rusqlite) | Sessiot ja tulokset | +| Cache | IndexedDB | Mallipainot selaimen pysyvässä muistissa | +| Korostus | Highlight.js (CDN) | Syntaksikorostus, automaattinen kielentunnistus | +| Tokenizer | HuggingFace tokenizers | BPE-tokenisaatio Wasmissa | + +--- + +## 10. Jatkokehitysideoita + +Näiden rakennuspalasten pohjalta voi rakentaa: + +- **Oma chat-UI:** WebSocket + streaming + syntaksikorostus +- **Hajautettu laskentaverkko:** Hub + node-rekisteri + busy-state + työjono +- **Selain-LLM:** Wasm + Candle + IndexedDB-cache + warmup +- **Agenttipohjainen työnkulku:** Pipeline + prompt-orkestrointi + reititys +- **Terminaaliemulasttori:** Input + historia + tab-completion + dropdown +- **Reaaliaikadashboard:** WebSocket broadcast + tilaindikaattorit + metriikat