server/docs/infra/ai_gateway.md
vegard 024a91e1b3 Dokumentasjon: arkitekturvurdering — sikkerhet, backup, kostnad, nye forslag
Oppdaterer dokumentasjon basert på tre eksterne arkitekturvurderinger:

- RLS Leak Hunter med CI-test og audit-trigger (migration_safety.md)
- pgvector-migrasjon flyttet til Lag 2, WAL-arkivering med pgBackRest (ARCHITECTURE.md, produksjon.md)
- Off-site backup med rclone, Docker cgroups for workers (ARCHITECTURE.md, produksjon.md)
- Kostnadskontroll i AI Gateway: workspace-budsjett, auto-fallback (ai_gateway.md)
- Gjeste-token sikkerhetsdybde: ClamAV, rate limiting, auto-revoke (den_asynkrone_gjesten.md)
- SpacetimeDB fase 1-vurdering: PG LISTEN/NOTIFY som mellomsteg (synkronisering.md)
- Kritiske events (Aha-markører) flushes umiddelbart (synkronisering.md)
- Ekstern helsesjekk, observability-utvidelser (ARCHITECTURE.md)
- Tre nye forslag: Contradiction Detector, Auto-Highlight Reel, Audience Voice Memo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 03:56:21 +01:00

200 lines
8.7 KiB
Markdown

# 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, Research-Klipper, 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
Modellvalg styres av to mekanismer:
### 3.1 Standard ruting (config.yaml)
LiteLLM konfigureres med modellaliaser som mapper til billigste egnede leverandør:
```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"
- 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"
router_settings:
routing_strategy: "simple-shuffle" # prøv første, fallback til neste
num_retries: 2
timeout: 60
general_settings:
master_key: "os.environ/LITELLM_MASTER_KEY"
```
### 3.2 Jobbkø-styrt modellvalg
Jobbkøen (se `jobbkø.md`) spesifiserer modellalias per jobbtype:
| Jobbtype | Modellalias | 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 |
Modellalias lagres som felt på jobben i PG — kan overstyres manuelt per jobb ved behov.
## 4. Docker-oppsett
```yaml
# docker-compose.dev.yml / docker-compose.yml
ai-gateway:
image: ghcr.io/berriai/litellm:main
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](https://promptfoo.dev) 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:
```yaml
# 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 eval` mot 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. Kostnadskontroll
LiteLLM har innebygd logging, men mangler workspace-nivå budsjettering. For å forhindre kostnadssprekk:
### 6.1 Workspace-budsjett
Hver workspace har et månedlig AI-budsjett lagret i `workspaces.settings` (JSONB):
```json
{
"ai_budget": {
"monthly_limit_usd": 50,
"alert_threshold_pct": 80,
"auto_fallback": true
}
}
```
- **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.
### 6.2 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. ARCHITECTURE.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. 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 eval` fø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