- 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 <noreply@anthropic.com>
12 KiB
Infrastruktur: AI Gateway (LiteLLM)
Filsti: docs/infra/ai_gateway.md
1. Konsept
Sidelinja bruker en sentralisert AI Gateway (LiteLLM) som eneste kontaktpunkt for alle AI-kall i systemet. All kode — Rust-workers, SvelteKit server-side — snakker med http://ai-gateway:4000/v1. Aldri direkte til leverandør-APIer.
Fordeler:
- BYOK (Bring Your Own Key): Direkte API-nøkler til Anthropic, Google, xAI — ingen markup
- OpenRouter som fallback: Tilgang til alle modeller vi ikke har direkte nøkler til, og sikkerhetsventil ved nedetid
- Kostnadskontroll: Rutineoppgaver rutes til gratisnivå (Gemini), dyre modeller kun når det trengs
- Sentralisert logging: Token-bruk per funksjon (Podcastfabrikken, Editor AI-behandling, Live-assistent) på ett sted
- Redundans: Automatisk failover mellom leverandører — redaksjonen merker ikke nedetid
2. Leverandører og bruksmønster
| Leverandør | Nøkkeltype | Primært bruksområde |
|---|---|---|
| Google Gemini | BYOK (gratisnivå) | Rutineoppgaver: transkripsjonsvasking, research-oppsummering, metadata-uttrekk |
| Anthropic (Claude) | BYOK | Oppgaver som krever høy resonneringsevne: live-assistent faktoid-vurdering, kompleks analyse |
| xAI (Grok) | BYOK | Alternativ for analyse, sanntidssøk (når tilgjengelig) |
| OpenRouter | BYOK | Fallback for alle modeller, sikkerhetsventil ved leverandør-nedetid |
Merk: Kvaliteten på norsk tekst varierer mellom modeller. Test alltid med norsk innhold før en modell tildeles en produksjonsoppgave.
3. Modellruting
3.1 Arkitekturprinsipp: PG eier config, LiteLLM er stateløs
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:
- 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
3.2 Datamodell
-- 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)
);
-- 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.3 Config-generering
SvelteKit-serveren genererer config.yaml fra PG ved oppstart og ved endringer i admin-panelet:
- Les aktive aliaser og deres providers (sortert etter priority)
- Skriv
config.yamltil volum delt med LiteLLM-containeren - Restart LiteLLM (
docker restart ai-gateway) eller sendSIGHUP
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 |
|---|---|---|
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
# docker-compose.dev.yml / docker-compose.yml
ai-gateway:
image: ghcr.io/berriai/litellm:main-stable
restart: unless-stopped
command: --config /etc/litellm/config.yaml
environment:
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
GEMINI_API_KEY: ${GEMINI_API_KEY}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
XAI_API_KEY: ${XAI_API_KEY}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
volumes:
- ./config/litellm/config.yaml:/etc/litellm/config.yaml:ro
ports:
- "127.0.0.1:4000:4000" # kun localhost (dev), ingen port i prod
networks:
- sidelinja-dev # eller sidelinja-net i prod
5. Prompt-kvalitetssikring (Promptfoo)
Alle LLM-prompts i Sidelinja testes systematisk med Promptfoo før de brukes i produksjon. Dette er spesielt viktig fordi vi jobber med norsk tekst, der modellkvaliteten varierer kraftig mellom leverandører.
5.1 Hva vi tester
Hver jobbtype som bruker LLM har et tilhørende testsett:
| Jobbtype | Testsett | Eksempler på assertions |
|---|---|---|
whisper_postprocess |
Norske transkripsjoner med kjente feil | Egennavn korrigert, setningsflyt bevart |
openrouter_analyze |
Episoder med kjent metadata | Riktig tittel, kapitler matcher innhold |
research_clip |
Nyhetsartikler med kjente aktører/fakta | Aktører identifisert, faktoider korrekte |
live_factoid_eval |
Transkripsjons-chunks med kjente entiteter | Riktig entity-match, lav falsk-positiv-rate |
5.2 Hva vi sammenligner
Promptfoo kjøres mot alle kandidatmodeller via AI Gateway:
# promptfoo-config.yaml
providers:
- id: "openai:chat:sidelinja/rutine"
config:
apiBaseUrl: "http://localhost:4000/v1"
apiKey: "${LITELLM_MASTER_KEY}"
- id: "openai:chat:sidelinja/resonering"
config:
apiBaseUrl: "http://localhost:4000/v1"
apiKey: "${LITELLM_MASTER_KEY}"
Dette lar oss svare på:
- Klarer Gemini (gratis) denne oppgaven like bra som Claude (betalt)?
- Fungerer prompten på norsk, eller trenger vi en annen formulering?
- Har en modelloppgradering hos leverandøren degradert kvaliteten?
5.3 Når vi kjører tester
- Ved ny prompt: Før den tas i bruk i produksjon
- Ved modellbytte: Før en leverandør/modell settes som primær for en jobbtype
- Periodisk (CI): Månedlig cron-jobb i Forgejo Actions kjører
promptfoo evalmot alle testsett. Resultater postes som issue ved regresjoner. Leverandører oppdaterer modeller uten varsel — automatisk regresjonssjekk fanger dette opp. - Ved kvalitetsklager: Når redaksjonen rapporterer dårlig output
5.4 Lagring av testsett
Testsett og promptfoo-config versjonskontrolleres i Git under tests/prompts/. Testdata er norske eksempler fra faktiske episoder og artikler.
tests/prompts/
├── promptfooconfig.yaml
├── whisper_postprocess/
│ ├── prompt.txt
│ └── dataset.json
├── metadata_extract/
│ ├── prompt.txt
│ └── dataset.json
└── research_clip/
├── prompt.txt
└── dataset.json
6. Tokenregnskap og kostnadskontroll
6.1 Token-logging per workspace
Rust-workeren logger tokenforbruk etter hvert AI-kall. Dataen lagres i PG:
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);
Flyten:
- Rust-worker sender AI-kall via gateway, får tilbake
usagei responsen - Worker skriver rad til
ai_usage_logmed workspace_id, tokens og modellinfo - Estimert kostnad beregnes fra en enkel prisliste i config (oppdateres manuelt)
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
pausedmed 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).
7. Dataklassifisering (ref. docs/arkitektur.md 2.2)
| Data | Kategori | Detaljer |
|---|---|---|
| LiteLLM config.yaml | Gjenskapbar (Git) | Versjonskontrollert |
| API-nøkler | Kritisk (.env) | Aldri i Git |
| Token-bruk-logger | Flyktig (TTL 90 dager) | For kostnadsoversikt, ryddes automatisk |
| Promptfoo testsett | Gjenskapbar (Git) | tests/prompts/ — versjonskontrollert |
| Promptfoo testresultater | Flyktig (lokal) | Kjøres on-demand, ikke lagret permanent |
8. Kildevern-modus (proposal)
For sensitive redaksjonelle diskusjoner kan en lokal LLM-leverandør (Ollama/vLLM) registreres som sidelinja/lokal i config. Channels/møter med kildevern: true ruter all AI-prosessering til denne modellen — data forlater aldri serveren. Se docs/proposals/kildevern_modus.md.
9. Instruks for Claude Code
- All AI-kode skal peke på
http://ai-gateway:4000/v1— aldri direkte til leverandør - Bruk modellaliaser (
sidelinja/rutine,sidelinja/resonering) — aldri hardkod leverandør-spesifikke modellnavn i applikasjonskode - API-nøkler i
.env, aldri i config-filer eller kode - Test alltid med norsk innhold før en ny modell/leverandør tas i bruk for en produksjonsoppgave
- Kjør
promptfoo evalfør du endrer prompts eller bytter modell for en jobbtype - Nye jobbtyper som bruker LLM skal ha et tilhørende testsett i
tests/prompts/før de merges