# Kipinä Agentic Studio — Opas Hajautettu AI-laskentaverkko jossa kielimallit ajavat koodia suoraan selaimessa. Tämä opas selittää miten kielimallit toimivat, miten niitä ohjataan, ja miten tuloksia voi parantaa. --- ## Kielimallit ja niiden koot Kielimalli on neuroverkko joka ennustaa seuraavan sanan (tokenin) edellisten perusteella. Mallin "koko" tarkoittaa parametrien (painojen) määrää: | Malli | Parametrit | Koko levyllä | Nopeus selaimessa | Koodinlaatu | |-------|-----------|-------------|-------------------|-------------| | SmolLM 135M | 135 miljoonaa | ~270 MB | ~5 tok/s | Yksinkertainen teksti | | Qwen2.5-Coder:0.5B | 500 miljoonaa | ~990 MB | ~3-6 tok/s | Pienet funktiot | | Qwen2.5-Coder:3B | 3 miljardia | ~6.2 GB | ~0.4 tok/s | Kokonaiset tiedostot | | GPT-4 (vertailu) | ~1800 miljardia | ~3.6 TB | pilvipalvelu | Kokonaiset projektit | **Parametrien vaikutus:** Jokainen parametri on yksi liukuluku (float16 = 2 tavua) joka tallentaa opittua tietoa. 0.5B-malli tietää perusrakenteet mutta tekee loogisia virheitä. 3B-malli ymmärtää kontekstin paremmin. Ero on kuin sanakirjan ja oppikirjan välillä. **Miksi selaimessa?** Malli ajetaan käyttäjän omalla laitteella WebAssemblyn kautta. Data ei lähde koneelta, eikä tarvita pilvipalvelua. Haittapuoli on hitaus — GPU-palvelimella sama 0.5B-malli tuottaa ~100 tok/s. --- ## Tokenit — kielimallin "sanat" Malli ei näe tekstiä kirjaimina vaan **tokeneina**. Tokeni on yleensä sanan osa, kokonainen sana tai välilyönti. Tokenisaatio tehdään BPE-algoritmilla (Byte Pair Encoding) joka oppii yleisimmät merkkijonot harjoitusdatasta. ### Esimerkki: suomi vs. englanti Alla oikea tokenisointitulos Qwen2.5-Coder-tokenisaattorilla. Jokainen värikoodattu lohko on yksi tokeni — huomaa miten suomi vaatii enemmän tokeneita saman merkityksen välittämiseen: ![Tokenisointivertailu EN/FI](/images/tokenization-example.png) **Huomaa miten:** - Englannin yleiset sanat (`the`, `in`, `a`, `function`) ovat kokonaisia tokeneita - Suomen sanat pilkotaan pienempiin osiin (`Hajautettu` → 4 tokenia, `Distributed` → 2) - Suomi vaatii **30-50% enemmän tokeneita** saman merkityksen välittämiseen - Koodiavainsanat (`function`, `list`, `sort`) ovat tehokkaita molemmilla kielillä ### Miksi tämä merkitsee? **Jokainen tokeni = yksi laskentakierros.** Jos suomi vaatii 50% enemmän tokeneita: 1. **Hitaampi vastaus:** 100 tokenin englanninkielinen vastaus ≈ 150 tokenia suomeksi → 50% pidempi odotusaika 2. **Pienempi konteksti:** Sama merkityssisältö vie enemmän tilaa konteksti-ikkunasta 3. **Huonompi ymmärrys:** Pitkät sanat pilkotaan osiin jotka malli ei välttämättä tunnista → hallusinaatiot lisääntyvät **Siksi tekniset promptit ovat englanniksi** — malli saa enemmän informaatiota samassa token-budjetissa ja ymmärtää ohjeet paremmin. **Token-budjetti tässä järjestelmässä:** | Osa | Tokeneita | Osuus | |-----|-----------|-------| | System prompt | ~30 | kiinteä | | Agent prompt | ~25 | kiinteä | | Konteksti (aiemmat tiedostot) | 0-300 | kasvaa | | Käyttäjän prompti | ~20-50 | vaihtelee | | **Syöte yhteensä** | **~75-400** | | | Generoitu vastaus (max) | 512 | raja | | **Yhteensä** | **~600-900** | /32 768 | Konteksti-ikkuna on reilusti riittävä. Pullonkaula ei ole ikkunan koko vaan **mallin kyky ymmärtää pitkää kontekstia** — 0.5B-malli alkaa "unohtaa" ohjeet kun konteksti kasvaa yli ~200 tokenin. --- ## Promptit — miten mallia ohjataan ### Kolmitasoinen prompttirakenne ```mermaid flowchart TD S["System prompt
You are a coding assistant. Respond with ONLY code.
🔒 Kiinteä, kovakoodattu — malli priorisoi tämän"] A["Agent prompt
Olet kokenut ohjelmistokehittäjä...
✏️ Käyttäjän muokattavissa UI:ssa"] U["User prompt
Write ONLY the file main.py...
📋 Vaihtelee joka kutsussa, sisältää kontekstin"] P["Prefill: ```
🎯 Pakottaa mallin aloittamaan koodilla"] S --> A --> U --> P P -->|malli jatkaa| R["Generoitu koodi"] style S fill:#1a1e2e,stroke:#f85149,color:#c9d1d9 style A fill:#1a1e2e,stroke:#d29922,color:#c9d1d9 style U fill:#1a1e2e,stroke:#3fb950,color:#c9d1d9 style P fill:#1a1e2e,stroke:#a371f7,color:#c9d1d9 style R fill:#0d1117,stroke:#58a6ff,color:#58a6ff ``` ### Miksi promptit ovat englanniksi? Qwen2.5-Coder on harjoitettu pääosin englanninkielisellä koodilla ja dokumentaatiolla. Suomenkielinen ohje kuluttaa enemmän tokeneita JA malli ymmärtää sen huonommin. Agenttien nimet ja käyttöliittymä ovat suomeksi, mutta tekniset ohjeet mallille englanniksi. Poikkeus: agenttipromptit ovat suomeksi koska ne menevät user-blokkiin (ei system-blokkiin) ja niiden tarkoitus on enemmän "persoonallisuus" kuin tekninen ohje. --- ## Prefill-tekniikka Normaalisti malli päättää vapaasti miten vastaa: ``` Ilman prefilliä: Malli: "Sure! Here is a Python program that prints Hello World:\n```python\nprint('Hello')\n```" → 25 tokenia, joista 15 turhia Prefillin kanssa: Me syötämme: ``` Malli jatkaa: python\nprint('Hello')\n``` → 5 tokenia, kaikki hyödyllisiä ``` Prefill on kuin aloittaisit lauseen toisen puolesta — malli jatkaa siitä mihin jäit sen sijaan, että aloittaisi kohteliaalla johdannolla. **Sivuvaikutus:** Malli tuottaa kielitunnisteen (`python`, `rust`) ja sulkevan ` ``` `:n. Nämä siivotaan jälkikäteen `strip_markdown_wrapper`-funktiolla. --- ## Sampling — miten malli valitsee seuraavan tokenin Malli ei "tiedä" oikeaa vastausta. Se laskee jokaiselle mahdolliselle seuraavalle tokenille todennäköisyyden ja valitsee yhden. Valintaa ohjataan kolmella parametrilla: ### Temperature (0.7) Kontrolloi "luovuutta" vs. "varmuutta": ``` Temperature 0.0 (greedy): Aina todennäköisin tokeni → "def fibonacci(n):" Temperature 0.7 (oletus): Painottaa todennäköisiä mutta sallii vaihtelua Temperature 1.5 (luova): Lähes satunnainen → "async lambda fib = ..." ``` 0.7 on kompromissi: tarpeeksi determinististä tuottamaan toimivaa koodia, mutta tarpeeksi vaihtelevaa välttämään toistoa. ### Top-k (40) Rajaa valinnan 40 todennäköisimpään tokeniin. Estää mallia valitsemasta täysin absurdeja vaihtoehtoja: ``` Ilman top-k: 150 936 vaihtoehtoa → voi valita minkä tahansa Top-k 40: 40 vaihtoehtoa → järkevät vaihtoehdot Top-k 1: 1 vaihtoehto → greedy (aina sama vastaus) ``` ### Repetition penalty (1.15) Vähentää jo tuotettujen tokenien todennäköisyyttä. Estää mallia juuttumasta luuppiin: ``` Ilman rangaistusta: "print print print print print..." Penalty 1.15: "print('Hello')\nprint('World')" ``` 1.15 on lievä rangaistus — estää pahimman toiston mutta sallii saman avainsanan (esim. `return`) esiintymisen useasti. --- ## Stop-sekvenssit — milloin generointi loppuu Malli generoi tokeneita kunnes jokin näistä tapahtuu: 1. **EOS-tokeni** (151645): Mallin oma "loppu"-merkki 2. **Max tokens** (512): Kovakoodattu raja 3. **Stop-sekvenssi**: Malli alkaa tuottaa selitystä ``` fn fibonacci(n: usize) -> usize { if n <= 1 { return n; } fibonacci(n-1) + fibonacci(n-2) } ← Tähän asti koodia, ok // Example usage: ← Stop! Tämä ei ole enää vastausta let result = fibonacci(10); ← Ei generoida ``` Tunnistetut stop-sekvenssit: `### `, `Explanation`, `Note:`, `Output:`, `// Example`, `# Example`. Generointi katkaistaan ja teksti trimmataan stop-kohtaan. --- ## Projekti-pipeline — miten agenttitiimi toimii ```mermaid flowchart TD U["Käyttäjä: FastAPI + SQLite REST API for users"] --> M M["🟡 Manageri: Pilko tiedostoiksi"] -->|tiedostolista| C1 C1["🟢 Koodari: models.py"] -->|"konteksti: models.py"| C2 C2["🟢 Koodari: main.py"] -->|"konteksti: models + main"| C3 C3["🟢 Koodari: pyproject.toml"] -->|kaikki tiedostot| T1 T1["🔵 Testaaja: Review"] -->|bugeja löytyi| C4 T1 -->|LGTM| Done["✅ Projekti valmis"] C4["🟡 Koodari: Korjaukset"] --> T2 T2["🔵 Testaaja: Uudelleenarviointi"] --> Done ``` **Kontekstin ketjutus** on kriittistä: kun koodari kirjoittaa `main.py`:tä, se saa `models.py`:n sisällön promptissa. Ilman tätä se ei tietäisi mitä luokkia importata. **Riippuvuusjärjestys:** Manageria pyydetään listaamaan riippuvuudet ensin (models.py ennen main.py) jotta kontekstiketju toimii oikeaan suuntaan. --- ## Rakennuspalaset vs. vapaa generointi Kielimalli voi generoida koodia kahdella perustavanlaatuisesti eri tavalla. Ymmärtäminen milloin kumpikin toimii on avain luotettavaan koodigenerointi-pipelineen. ### Tapa 1: Vapaa generointi (naivi) LLM generoi jokaisen tiedoston tyhjästä. Prompti kuvaa mitä halutaan, malli tuottaa koko tiedoston — importeista lähtien. ```mermaid flowchart LR P["Prompti"] --> LLM1["LLM: models.py"] LLM1 --> V1{"Validointi"} V1 -->|virhe| LLM1 V1 -->|ok| LLM2["LLM: schemas.py"] LLM2 --> V2{"Validointi"} V2 -->|virhe| LLM2 V2 -->|ok| LLM3["LLM: main.py"] LLM3 --> V3{"..."} style V1 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9 style V2 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9 style V3 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9 ``` **Ongelma:** Pieni malli (0.5B–7B) tekee toistuvia rakenteellisia virheitä: | Virhe | Esiintymistiheys | Selitys | |-------|:---:|------| | Puuttuva import | ~60% | `from datetime import date` unohtuu | | SQLite `connect_args` | ~80% | Malli ei muista SQLite-erityisyyttä | | Väärä Enum-käyttö | ~50% | Sekoittaa `sqlalchemy.Enum` ja `enum.Enum` | | Poetry pyproject.toml:ssa | ~40% | Malli suosii Poetryä vaikka ohje sanoo uv | | Testit kopioivat koko appin | ~70% | Malli ei osaa importata, luo uudet reitit | Retry-loopilla (virhe → uusi yritys virheviestin kanssa) osa korjautuu, mutta **sama malli toistaa samoja virheitä** koska ne johtuvat harjoitusdatasta. 7 tiedoston projekti vaatii 7–14 LLM-kutsua ja 80–120 sekuntia. ### Tapa 2: Rakennuspalaset (template pipeline) LLM:ltä pyydetään **vain JSON-speksi** — entiteetit, kentät ja tyypit. Koodi kootaan mekaanisesti valmiista pohjista joiden rakenne on todistettavasti oikein. ```mermaid flowchart LR P["Projektin kuvaus"] --> LLM["LLM: JSON-speksi"] LLM --> S["{ entities: [...] }"] S --> T1["Template: models.py"] S --> T2["Template: schemas.py"] S --> T3["Template: main.py"] S --> T4["Template: test_main.py"] S --> T5["Template: Dockerfile"] T1 & T2 & T3 & T4 & T5 --> D["Docker build + pytest"] style LLM fill:#1a1e2e,stroke:#d29922,color:#c9d1d9 style S fill:#1a1e2e,stroke:#3fb950,color:#c9d1d9 style D fill:#1a1e2e,stroke:#58a6ff,color:#c9d1d9 ``` **Idea:** Malli on hyvä päättämään *mitä* (entiteetit, kentät), mutta huono muistamaan *miten* (importit, engine setup, testikonfiguraatio). Annetaan mallin tehdä se missä se on hyvä, ja hoidetaan loput mekaanisesti. ### LLM:n ainoa tehtävä Malli tuottaa JSON-rakenteen kuten: ```json { "project_name": "todo-app", "entities": [ { "name": "Todo", "table_name": "todos", "fields": [ {"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false}, {"name": "due_date", "sa_type": "Date", "py_type": "date | None", "nullable": true}, {"name": "status", "sa_type": "String(20)", "py_type": "str", "default": "pending"} ] } ], "extra_imports": ["from datetime import date"] } ``` Tämä on yksinkertainen tehtävä jossa pienikin malli onnistuu luotettavasti: entiteettien tunnistus projektin kuvauksesta ja kenttätyyppien valinta. Speksi sisältää myös **taulujen väliset yhteydet** (relationships): ```json { "entities": [ {"name": "Author", "table_name": "authors", "fields": [...]}, {"name": "Book", "table_name": "books", "fields": [ {"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false}, {"name": "author_id", "sa_type": "Integer", "py_type": "int", "nullable": false} ]} ], "relationships": [ {"from": "Book", "field": "author_id", "to": "Author", "type": "many-to-one"} ] } ``` Templateit generoivat yhteyksistä automaattisesti: - `ForeignKey('authors.id')` models.py:hin - `relationship("Book", back_populates="author")` molempiin suuntiin - `BookDetail`-schema jossa author-data mukana - `GET /authors/{id}/books/` nested endpoint - FK-validointi: 404 jos parent-entiteettiä ei ole ### Architect-agentti: speksin laatu ratkaisee Arkkitehti on **kriittisin agentti** koko pipelinessa. Jos speksi on hyvä (oikeat taulut, kentät, yhteydet), kaikki muu seuraa automaattisesti. Jos speksi on huono, templateitkaan eivät pelasta. Arkkitehtia ohjataan: 1. **Chain-of-thought**: "Mieti ensin taulut, sitten kentät, sitten yhteydet" 2. **Domain-esimerkit**: Todo, verkkokauppa, blogi — malli näkee miltä hyvä speksi näyttää 3. **Anti-patternit**: "Ei turhia ID-kenttiä, ei Enumeita, ei suomenkielisiä nimiä koodissa" 4. **Yhteyssäännöt**: "Jokainen `_id`-kenttä tarvitsee vastaavan relationship-merkinnän" Isompi malli (tai API) tässä yhdessä kohdassa parantaa kaikkien projektien laatua koska speksi on ainoa paikka jossa LLM:n ymmärrys vaikuttaa. ### Template täyttää loput Jokainen template on kuin madlib — aukot täytetään speksin datalla: **models.py template (yksinkertaistettu):** ```python from sqlalchemy import create_engine, Column, Integer, {sa_types}, ForeignKey from sqlalchemy.orm import sessionmaker, relationship # ... aina samat importit, engine setup, SessionLocal ... class {entity.name}(Base): __tablename__ = "{entity.table_name}" id = Column(Integer, primary_key=True, index=True) {field.name} = Column({field.sa_type}, nullable={field.nullable}) # FK-kentät: ForeignKey + relationship automaattisesti {fk_field} = Column(Integer, ForeignKey('{parent_table}.id')) {parent_lower} = relationship("{Parent}", back_populates="{children}") ``` Tulos: importit ovat aina oikein, `connect_args` on aina mukana, taulujen yhteydet generoituvat oikein, testit importoivat `main.py`:stä eivätkä kopioi sitä. ### Vertailu: mittaustulokset | | Vapaa generointi | Rakennuspalaset | |---|:---:|:---:| | LLM-kutsuja | 7–14 | **3** (speksi + requirements + README) | | Aika | 80–120s | **~25s** | | Syntaksi OK | ~70% | **100%** | | Docker build | vaihteleva | **100%** | | Pytest läpi | 0% | **100%** | | API toimii | ~30% | **100%** | | Taulujen yhteydet (FK) | ei koskaan | **100%** | | Nested endpointit | ei koskaan | **automaattisesti** | ### Milloin kumpikin toimii **Rakennuspalaset** kun: - Projektin rakenne on tunnettu (FastAPI + SQLAlchemy CRUD) - Laatu ja luotettavuus ovat tärkeitä - Malli on pieni (0.5B–7B) **Vapaa generointi** kun: - Projektin rakenne on epätavallinen - Tarvitaan custom-logiikkaa jota template ei kata - Malli on riittävän iso (>70B tai pilvi-API) Paras lopputulos syntyy yhdistelmällä: **rakennuspalaset perusrakenteelle, vapaa generointi business-logiikalle**. --- ## Laadun parantaminen ### 1. Isompi malli (suurin vaikutus) | | 0.5B | 3B | Pilvi-API | |---|---|---|---| | Fibonacci | Joskus virheitä | Yleensä oikein | Aina oikein | | FastAPI CRUD | Voi käyttää Flaskia | Oikea kirjasto | Täydellinen | | Monimutkainen logiikka | Hallusinoi | Osaa perusasiat | Syvä ymmärrys | | Nopeus (selain) | ~5 tok/s | ~0.4 tok/s | — | | Latauksen koko | 990 MB | 6.2 GB | 0 (API) | **Käytännössä:** `kpn load 2` lataa 3B-mallin. Hitaampi mutta huomattavasti parempi koodinlaatu. Suositus monimutkaisiin projekteihin. ### 2. Paremmat promptit (ilmaista) **Huono:** `"tee fibonacci"` - Malli ei tiedä kieltä, formaattia tai kontekstia **Hyvä:** `"Write a fibonacci function in Rust that returns Vec"` - Kieli, palautustyyppi ja rakenne määritelty **Promptin säännöt:** - Englanniksi (tehokkaampi tokenisointi, parempi ymmärrys) - Konkreettinen (mainitse kieli, kirjastot, palautustyyppi) - Lyhyt (jokainen sana kuluttaa tokenin konteksti-ikkunasta) - Positiivinen ("Write X" ei "Don't write Y") ### 3. Kontekstin hallinta (pipeline-taso) **Ongelma:** 0.5B-malli "unohtaa" promptin alun kun konteksti kasvaa. **Ratkaisu:** Pienet, kohdennetut promptit: - Yksi tiedosto kerrallaan (ei "kirjoita koko projekti") - Vain relevantit aiemmat tiedostot kontekstina - Max 4 tiedostoa per projekti ### 4. Iterointi (review-luuppi) Yksi generointikierros tuottaa harvoin virheetöntä koodia. Pipeline-arkkitehtuuri mahdollistaa: 1. **Generointi** — ensimmäinen versio 2. **Review** — testaaja löytää ongelmat 3. **Korjaus** — koodari saa palautteen ja korjaa 4. **Uusi review** — tarkistetaan korjaukset Nykyinen järjestelmä tekee max 1 korjauskierroksen. Useampi iteraatio parantaisi laatua mutta kasvattaisi laskenta-aikaa. ### 5. Erikoistetut system promptit Oletuspromptit ovat yleiskäyttöisiä. Projektikohtaiset promptit parantavat laatua merkittävästi: ``` Oletus: "Olet kokenut ohjelmistokehittäjä." Parempi: "You are a Python backend developer specializing in FastAPI. Always use Pydantic models for request/response schemas. Always use dependency injection for database sessions. Follow the repository pattern." ``` Agenttikohtaiset promptit voi muokata suoraan UI:ssa. ### 6. Few-shot esimerkit Malli oppii parhaiten esimerkeistä. Sen sijaan, että sanot "kirjoita FastAPI endpoint", näytä miltä haluat tuloksen näyttävän: ``` Write a GET endpoint like this example: @app.get("/items") def list_items(): db = SessionLocal() return db.query(Item).all() Now write a similar endpoint for /users. ``` 0.5B-malli jäljittelee rakennetta tehokkaasti — se on parempi kopioimaan kuin keksimään. Nykyinen pyproject.toml-esimerkki promptissa on tätä tekniikkaa. ### 7. Temperature-säätö tehtävän mukaan Nykyinen temperature 0.7 on kompromissi. Eri tehtävät hyötyisivät eri arvoista: | Tehtävä | Paras temperature | Miksi | |---------|-------------------|-------| | Tarkka koodi (CRUD, boilerplate) | 0.2-0.4 | Determinismi tärkeää | | Luova koodi (algoritmit, arkkitehtuuri) | 0.6-0.8 | Vaihtelu löytää ratkaisuja | | Vapaa teksti (kommentit, dokumentaatio) | 0.8-1.0 | Luonnollisempi kieli | Järjestelmä voisi valita temperaturen automaattisesti tehtävätyypin perusteella. ### 8. Ensemble — sama prompti usealle mallille Lähetetään sama tehtävä kahdelle solmulle ja valitaan parempi vastaus. Nykyinen Proof of Compute -arkkitehtuuri tukee tätä periaatteessa: hub voisi reitittää saman task_id:n kahdelle solmulle ja verrata tuloksia. Käytännössä tämä kaksinkertaistaa laskenta-ajan mutta parantaa laatua merkittävästi — virheellinen vastaus harvoin on sama kahdella ajolla koska sampling on stokastinen. ### 9. Post-processing (nykyinen) Mallin raakavastaus siivotaan: 1. Kielitunniste poistetaan (`python`, `rust`, ...) 2. Sulkeva ` ``` ` poistetaan 3. Johdantolauseet poistetaan ("Sure!", "Here is...") 4. Selityskommentit poistetaan ("# This is a simple...") 5. Stop-sekvenssit katkaisevat generoinnin Tämä ei paranna mallin ajattelua mutta poistaa turhan roskan. ### 10. Mallin hienosäätö (fine-tuning) Qwen2.5-Coder on yleiskäyttöinen koodimalli. Jos sitä hienosäätäisi omalla koodiaineistolla (esim. yrityksen koodikanta, tietty framework), se tuottaisi huomattavasti parempaa koodia juuri siihen kontekstiin. LoRA-hienosäätö 0.5B-mallille vaatii ~4 GB GPU-muistia ja muutaman tunnin harjoittelua. Tulos on erikoistunut malli joka osaa tuottaa esimerkiksi juuri FastAPI + SQLAlchemy -koodia luotettavasti. --- ## Välimuistiarkkitehtuuri — miksi toinen lataus on nopea ``` Ensimmäinen lataus (hidas): Verkko (HuggingFace CDN) → IndexedDB → RAM → Mallin rakennus ~990 MB lataus, ~30-60s Toinen lataus samalla sivulatauksella (nopea): RAM-cache → Mallia ei rakenneta uusiksi, vain KV-cache nollataan ~0ms Refresh jälkeen (keskitaso): IndexedDB → RAM → Mallin rakennus ~0 MB lataus, ~2-5s rakennus Uusi selain/laite (hidas): Verkko → IndexedDB → RAM → Mallin rakennus Kuten ensimmäinen lataus ``` **KV-cache:** Mallin sisäinen muisti joka tallentaa aiempien tokenien laskenta tulokset. Nollataan (`clear_kv_cache()`) jokaisen promptin välillä jotta edellinen vastaus ei vuoda seuraavaan. --- ## Lukuja käytännöstä **Yksittäinen funktio** (esim. fibonacci): - Input: ~80 tokenia - Output: ~50-100 tokenia - Aika: ~10-20s (0.5B, selain) - Laatu: Yleensä toimiva, joskus loogisia virheitä **3 tiedoston projekti** (esim. FastAPI CRUD): - Manageri: ~30 tok out - Koodari (3x): ~100-150 tok out per tiedosto - Testeri: ~50 tok out - Korjaukset: ~100 tok out (jos tarpeen) - **Yhteensä: ~500-700 tokenia, ~3-5 min** - Laatu: Rakenne oikein, yksittäisiä bugeja **Token-kustannus vs. pilvipalvelu:** - Tässä järjestelmässä: 0 euroa (laskenta omalla koneella) - GPT-4 API: ~700 tokenia x $0.03/1K = ~$0.02 per projekti - Claude API: ~700 tokenia x $0.015/1K = ~$0.01 per projekti Selaimessa ajettava malli on ilmainen mutta huomattavasti hitaampi ja heikompilaatuinen kuin pilvi-API. Sopii oppimiseen, prototypointiin ja tilanteisiin joissa data ei saa lähteä omalta koneelta.