From 89a8f99766ccfb2f47fd20e986452ebeadb4d516 Mon Sep 17 00:00:00 2001 From: vegard Date: Mon, 16 Mar 2026 09:18:50 +0100 Subject: [PATCH] Docs: oppdater CLAUDE.md arbeidsflyt, ai_gateway og chat-docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: lagt til standard arbeidsmodus, testmiljø og browser-testing - ai_gateway.md: PG-eier-config arkitektur, datamodell, config-generering, admin-panel - chat.md: trådvisning, meldingskollaps, AI-behandling, konvertering Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 3 + docs/features/chat.md | 12 ++- docs/infra/ai_gateway.md | 162 +++++++++++++++++++++++++-------------- 3 files changed, 116 insertions(+), 61 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 01ab1e1..a5ddbe3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,6 +5,9 @@ Sidelinja er et redaksjonelt operativsystem og kunnskapsgraf for podcast-produks Self-hosted på Hetzner VPS med full datakontroll. ## Arbeidsflyt +- **Standard arbeidsmodus:** Start i planleggingsmodus. Lag en grundig plan, få godkjenning, deretter implementer. Jobbene er ment å kunne kjøre lenge autonomt uten input underveis. +- **Testmiljø:** `./dev.sh` er den kanoniske måten å starte utviklingsmiljøet. Når nye tjenester, steg eller oppsett-quirks oppdages, oppdater alltid `dev.sh` slik at kunnskapen bevares i scriptet — ikke bare i hodet. Før Vegard tester i browser: kjør `./dev.sh`, verifiser med `cargo check`/`svelte-check`/`curl`, og meld tilbake at det er klart. +- **Browser-testing:** Claude har ikke tilgang til browser. Visuell testing og interaksjon gjøres av Vegard. Claude kan verifisere backend (kompilering, API-kall, database-state) men ikke frontend-rendering. - **Commit og push:** Bruk egen vurdering. Commit når arbeidet er logisk komplett, push til Forgejo når det gir mening. Ingen grunn til å spørre — det er trygt og reverserbart. - **Deploy til produksjon:** Krever alltid eksplisitt godkjenning fra Vegard. Deploy = SSH til server + pull + docker compose up. Aldri gjør dette uten å spørre først. - **Diskusjon:** Forklar og diskuter før arkitekturendringer eller uvanlige valg. For implementering innenfor eksisterende spec — bare kjør. diff --git a/docs/features/chat.md b/docs/features/chat.md index 88f3f23..a54a854 100644 --- a/docs/features/chat.md +++ b/docs/features/chat.md @@ -86,13 +86,16 @@ Kun aktive når `config.mentions = true`. * **Mobil-optimalisert:** Autocomplete-listen er tappbar og tilpasset mindre skjermer. ## 5. Tråder -Kun aktive når `config.threads = true`. Meldinger kan ha en `reply_to`-referanse. Frontend viser tråder som innrykk eller ekspanderbare grupper. +Kun aktive når `config.threads = true`. Meldinger kan ha en `reply_to`-referanse. Frontend grupperer meldinger i tråder (rot + svar) med visuell skillelinje mellom hver tråd. Svar vises med innrykk og vertikal linje under rot-meldingen, uten ekstra skillelinje mellom rot og svar. ## 6. Vedlegg Kun aktive når `config.attachments = true`. Meldinger kan ha vedlegg via `message_attachments` → `media_files`. Whiteboard-eksport kan knyttes som vedlegg. ## 7. Versjonshistorikk -Alle meldinger støtter redigering med full historikk via `message_revisions`. Original tekst bevares alltid. +Alle meldinger støtter redigering med full historikk via `message_revisions`. Original tekst bevares alltid. AI-behandlede meldinger har en revisjons-toggle i UI — brukeren kan veksle mellom AI-versjon og original tekst. AI-output rendres som Markdown via `marked`. + +## 7.1 Meldingsvisning +Lange meldinger (mer enn 2 linjer) kollapses automatisk med en "Vis mer"-knapp. Ved ekspandering vises "Vis mindre" både over og under meldingen, slik at man slipper å scrolle for å kollapse igjen. ## 8. Tale-til-tekst (Voice-to-text) Mobilvennlig diktering for situasjoner der tastatur er upraktisk. Brukeren trykker en mikrofon-knapp, snakker, og får teksten tilbake som en vanlig melding klar til redigering og sending. @@ -124,8 +127,11 @@ Channels med `config.ttl_days` satt til et tall får sine meldinger automatisk s - **Worker warmup (`worker/src/warmup.rs`):** PG → SpacetimeDB ved oppstart. Per-kanal konfig (all/messages/days/none). Trådbasert henting. - **Worker sync (`worker/src/sync.rs`):** SpacetimeDB → PG hvert sekund. Insert/delete/update meldinger + reaksjoner. - **Admin-side (`/admin/channels`):** Per-kanal warmup-konfigurasjon. -- **Tråder:** Komplett trådvisning med datogruppering og autoscroll. +- **Tråder:** Komplett trådvisning med datogruppering, autoscroll og visuell skillelinje mellom tråder. - **Reaksjoner:** Via SpacetimeDB-reducers, synket til PG. +- **Meldingskollaps:** Lange meldinger begrenses til 2 linjer med "Vis mer"/"Vis mindre". +- **AI-behandling:** Meldinger kan AI-behandles (✨-knapp). Revisjons-toggle viser original vs. AI-versjon. Markdown-rendering for AI-output. +- **Konvertering:** Meldinger kan opprettes som kanban-kort eller kalenderhendelse (dialog sier "Opprett", ikke "Konverter" — meldingen beholdes i chatten). ### Gjenstår - **Vedlegg, TTL** — avventer implementering. diff --git a/docs/infra/ai_gateway.md b/docs/infra/ai_gateway.md index b6de3f1..c8e046d 100644 --- a/docs/infra/ai_gateway.md +++ b/docs/infra/ai_gateway.md @@ -24,59 +24,86 @@ Fordeler: ## 3. Modellruting -Modellvalg styres av to mekanismer: +### 3.1 Arkitekturprinsipp: PG eier config, LiteLLM er stateløs -### 3.1 Standard ruting (config.yaml) -LiteLLM konfigureres med modellaliaser som mapper til billigste egnede leverandør: +PostgreSQL er single source of truth for all modellkonfigurasjon. LiteLLM er en stateløs proxy som får generert `config.yaml` fra PG-data. Dette gir: -```yaml -model_list: - # Ruting: billigste først, fallback til dyrere - - model_name: "sidelinja/rutine" - litellm_params: - model: "gemini/gemini-2.0-flash" - api_key: "os.environ/GEMINI_API_KEY" - - model_name: "sidelinja/rutine" - litellm_params: - model: "openrouter/google/gemini-2.0-flash-001" - api_key: "os.environ/OPENROUTER_API_KEY" +* **Ingen avhengighet til LiteLLM sitt admin API** — de endrer API mellom versjoner +* **All konfig i samme backup/migrasjon** som resten av systemet +* **Enkel bytte** — hvis LiteLLM erstattes, er all konfig intakt i PG +* **Admin-UI i SvelteKit** — gjenbruker eksisterende `/admin/`-mønster - - model_name: "sidelinja/resonering" - litellm_params: - model: "anthropic/claude-sonnet-4-20250514" - api_key: "os.environ/ANTHROPIC_API_KEY" - - model_name: "sidelinja/resonering" - litellm_params: - model: "openrouter/anthropic/claude-sonnet-4-20250514" - api_key: "os.environ/OPENROUTER_API_KEY" +### 3.2 Datamodell -router_settings: - routing_strategy: "simple-shuffle" # prøv første, fallback til neste - num_retries: 2 - timeout: 60 +```sql +-- Globale modellaliaser (server-nivå, ikke per workspace) +CREATE TABLE ai_model_aliases ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + alias TEXT NOT NULL, -- 'sidelinja/rutine', 'sidelinja/resonering' + description TEXT, -- 'Billig, høyt volum' + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE(alias) +); -general_settings: - master_key: "os.environ/LITELLM_MASTER_KEY" +-- Leverandør-modeller med prioritert fallback per alias +CREATE TABLE ai_model_providers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + alias_id UUID NOT NULL REFERENCES ai_model_aliases(id) ON DELETE CASCADE, + provider TEXT NOT NULL, -- 'gemini', 'openrouter', 'anthropic' + model TEXT NOT NULL, -- 'gemini/gemini-2.5-flash', 'openrouter/anthropic/claude-sonnet-4' + api_key_env TEXT NOT NULL, -- 'GEMINI_API_KEY', 'OPENROUTER_API_KEY' + priority SMALLINT NOT NULL, -- lavere = prøves først + is_active BOOLEAN NOT NULL DEFAULT true, + UNIQUE(alias_id, model) +); + +-- Jobbtype → modellalias mapping +CREATE TABLE ai_job_routing ( + job_type TEXT PRIMARY KEY, -- 'ai_text_process', 'whisper_postprocess', etc. + alias TEXT NOT NULL, -- 'sidelinja/rutine' + description TEXT +); ``` -### 3.2 Jobbkø-styrt modellvalg -Jobbkøen (se `jobbkø.md`) spesifiserer modellalias per jobbtype: +### 3.3 Config-generering -| Jobbtype | Modellalias | Begrunnelse | +SvelteKit-serveren genererer `config.yaml` fra PG ved oppstart og ved endringer i admin-panelet: + +1. Les aktive aliaser og deres providers (sortert etter priority) +2. Skriv `config.yaml` til volum delt med LiteLLM-containeren +3. Restart LiteLLM (`docker restart ai-gateway`) eller send `SIGHUP` + +Generert config inkluderer alltid `router_settings` og `general_settings` fra faste verdier — kun `model_list` er dynamisk. + +### 3.4 Jobbkø-styrt modellvalg + +Jobbkøen bruker `ai_job_routing` for å bestemme modellalias per jobbtype: + +| Jobbtype | Standard alias | Begrunnelse | |---|---|---| -| `whisper_postprocess` (transkripsjonsvasking) | `sidelinja/rutine` | Høyt volum, lav kompleksitet | -| `openrouter_analyze` (metadata-uttrekk) | `sidelinja/rutine` | Strukturert output, lav kompleksitet | -| `research_clip` (research-oppsummering) | `sidelinja/rutine` | Høyt volum | -| `live_factoid_eval` (live-assistent) | `sidelinja/resonering` | Krever presis vurdering under tidspress | +| `ai_text_process` (✨-behandling) | `sidelinja/rutine` | Tekstvasking, høyt volum | +| `whisper_postprocess` | `sidelinja/rutine` | Transkripsjonsvasking, høyt volum | +| `research_clip` | `sidelinja/rutine` | Research-oppsummering, høyt volum | +| `live_factoid_eval` | `sidelinja/resonering` | Krever presis vurdering under tidspress | Modellalias lagres som felt på jobben i PG — kan overstyres manuelt per jobb ved behov. +### 3.5 Admin-panel (`/admin/ai`) + +Admin-panelet lar administrator: +* Se og redigere modellaliaser og deres fallback-liste (drag-and-drop prioritering) +* Aktivere/deaktivere individuelle leverandør-modeller +* Endre jobbtype → alias mapping +* Se live-status: hvilke leverandører som svarer, responstider +* Trigge config-regenerering og LiteLLM-restart + ## 4. Docker-oppsett ```yaml # docker-compose.dev.yml / docker-compose.yml ai-gateway: - image: ghcr.io/berriai/litellm:main + image: ghcr.io/berriai/litellm:main-stable restart: unless-stopped command: --config /etc/litellm/config.yaml environment: @@ -151,36 +178,55 @@ tests/prompts/ └── dataset.json ``` -## 6. Kostnadskontroll +## 6. Tokenregnskap og kostnadskontroll -LiteLLM har innebygd logging, men mangler workspace-nivå budsjettering. For å forhindre kostnadssprekk: +### 6.1 Token-logging per workspace -### 6.1 Workspace-budsjett -Hver workspace har et månedlig AI-budsjett lagret i `workspaces.settings` (JSONB): +Rust-workeren logger tokenforbruk etter hvert AI-kall. Dataen lagres i PG: -```json -{ - "ai_budget": { - "monthly_limit_usd": 50, - "alert_threshold_pct": 80, - "auto_fallback": true - } -} +```sql +CREATE TABLE ai_usage_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, + job_id UUID REFERENCES job_queue(id) ON DELETE SET NULL, + model_alias TEXT NOT NULL, -- 'sidelinja/rutine' + model_actual TEXT, -- 'gemini/gemini-2.5-flash' (fra LiteLLM-respons) + prompt_tokens INT NOT NULL, + completion_tokens INT NOT NULL, + total_tokens INT NOT NULL, + estimated_cost NUMERIC(10, 6), -- USD, beregnet fra kjente priser + job_type TEXT, -- 'ai_text_process', etc. + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX idx_ai_usage_workspace_month ON ai_usage_log (workspace_id, created_at); ``` -- **Sporing:** SvelteKit logger token-bruk per AI-kall med workspace_id og jobbtype i `ai_usage_log`-tabellen (flyktig, TTL 90 dager). -- **Alert:** Når 80 % av budsjettet er brukt, postes varsel i workspace-chat (system-channel). -- **Auto-fallback:** Når budsjettet er nådd og `auto_fallback: true`, rutes alle kall til `sidelinja/rutine` (billigste modell). Ellers blokkeres AI-kall med feilmelding. +**Flyten:** +1. Rust-worker sender AI-kall via gateway, får tilbake `usage` i responsen +2. Worker skriver rad til `ai_usage_log` med workspace_id, tokens og modellinfo +3. Estimert kostnad beregnes fra en enkel prisliste i config (oppdateres manuelt) -### 6.2 Per-episode maks-kostnad +### 6.2 Visning — to nivåer + +**Admin (`/admin/ai`):** +Aggregert oversikt over alle workspaces. Tabell med totaler per workspace/modell/periode. Identifiserer kostnadsdrivere. + +**Workspace (sidebar-widget):** +Enkel tekst-indikator i workspace-sidebar: `✨ 12.4k tokens denne uken`. Klikk åpner detaljert visning med fordeling per jobbtype og modell. Ingen speedometer — det krever et definert budsjett for å gi mening, og det er overkill for MVP. + +### 6.3 Workspace-budsjett (fase 2) + +Når token-logging er på plass, kan budsjett-tak legges til: + +- Budsjett lagres i `workspaces.settings` (JSONB): `{ "ai_budget": { "monthly_limit_usd": 50 } }` +- Rust-worker sjekker aggregert forbruk før AI-kall +- Ved budsjett nær: fall tilbake til `sidelinja/rutine` (billigste) +- Ved budsjett nådd: sett jobb i `paused` med varsel i workspace-chat + +### 6.4 Per-episode maks-kostnad Podcastfabrikken-jobber (whisper + metadata + oppsummering) kan estimere totalkostnad basert på lydlengde. Jobben avbrytes med varsel hvis estimert kostnad overstiger `max_cost_per_episode` (default: $5). -### 6.3 Modell-nedgradering -Jobbkøen støtter automatisk modell-nedgradering ved kostnadsmål: -1. Prøv `sidelinja/resonering` (Claude) -2. Ved budsjett-nær: fall tilbake til `sidelinja/rutine` (Gemini gratis) -3. Ved budsjett-nådd: sett jobb i `paused`-status med varsel - ## 7. Dataklassifisering (ref. docs/arkitektur.md 2.2) | Data | Kategori | Detaljer |