AI Pipeline
Dokumentasjon av Impulse AI sin AI-pipeline — fra workload-registrering og modellvalg til prompt-design og kostnadsovervaking. All AI-funksjonalitet drives av Anthropic Claude (via Vercel AI SDK) for tekstgenerering og Voyage AI for embedding-generering.
Arkitekturoversikt
heading.anchorLabelapps/api/src/ai/├── infrastructure/ # AI SDK, Anthropic-konfigurasjon, Voyage-klient│ ├── ai-sdk.ts # executeWorkload() -- eneste chokepoint for alle AI-kall│ ├── anthropic/│ │ └── config.ts # Modell-IDer, priser, timeouts, token-grenser│ ├── voyage/│ │ ├── client.ts # Voyage AI HTTP-klient│ │ └── embedding.ts # Embedding-tjeneste (generering + sok)│ ├── access/ # Tier-validering + kostnadstaksjekk│ ├── tracking/│ │ ├── tracker.ts # trackUsage() -- brukssporing til ai_usage│ │ └── fingerprint.ts # Prompt-fingerprinting + versjonsregistrering│ └── safety/ # Innholdsvalidering├── registry/│ ├── workloads.ts # Konfigurasjon for registrerte workloads│ └── types.ts # SubscriptionTier, WorkloadConfig, ModelTier├── shared/│ ├── prompt-builder.ts # PromptStructure DSL + buildPrompt()│ ├── conventions.ts # Tone, vokabular, grammatikk, skrivestil│ ├── concept-types.ts # Formula/Bottling-monstre│ ├── person-context.ts # AI-personalisering (opt-in)│ ├── static-labels.ts # Statiske stegnavn (fallback)│ └── schemas/ # Zod-skjemaer (single source of truth)├── impulses/│ └── classification/ # Impulsklassifisering og anrikning├── sessions/│ ├── step-guidance.ts # 5-stegs veiledningsgenerering (batch + enkelt)│ └── session-summary.ts # Oppsummeringsgenerering├── check-ins/│ └── assessment/ # Sjekk-inn AI-vurdering└── insights/ ├── pulse.ts # Pulsanalyse (rask regenerering) ├── narrative.ts # Narrativ innsikt (dypere analyse) ├── embedding-analytics.ts # Embedding-basert monstergjenkjenning └── transformation-distillation.ts # TransformasjonsekstraktModellvalg
heading.anchorLabelAI-konfigurasjon er sentralisert i apps/api/src/ai/infrastructure/anthropic/config.ts. Alle modell-tier defaulter til Claude Sonnet 4.6, med mulighet for override via miljøvariabler.
Tre modell-tier
heading.anchorLabel| Tier | Standard modell | Formaal | Override-variabel | Doppler-verdi |
|---|---|---|---|---|
| fast | claude-haiku-4-5 | Strukturert klassifisering der presisjon > prosekvalitet | AI_MODEL_FAST | claude-haiku-4-5 |
| balanced | claude-sonnet-4-6 | Brukervennlig kreativ tekst der sprakkvalitet ER produktet | AI_MODEL_BALANCED | claude-sonnet-4-6 |
| quality | claude-sonnet-4-6 | Hoyinnsats langform-innhold som krever dyp resonnering | AI_MODEL_QUALITY | claude-sonnet-4-6 |
Alle tre miljøvariabler styres via Doppler (dev/stg/prd). Kode-default er Sonnet for alle tier — Haiku aktiveres via Doppler.
Konfigurasjon
heading.anchorLabelexport const AI_CONFIG = { models: { fast: process.env.AI_MODEL_FAST || 'claude-sonnet-4-6', balanced: process.env.AI_MODEL_BALANCED || 'claude-sonnet-4-6', quality: process.env.AI_MODEL_QUALITY || 'claude-sonnet-4-6', }, tokenLimits: { classification: 1024, // Impulsanrikning guidance: 500, // Sesjonsveiledning insight: 1500, // Innsiktgenerering summary: 3000, // Sesjonsdokument }, temperature: { classification: 0.4, // Balansert for anrikningskvalitet guidance: 0.7, // Moderat for naturlig sprak creative: 0.8, // Hoy for dokumentgenerering }, defaultTimeout: 30000, // 30 sekunder longRunningTimeout: 60000, // 60 sekunder maxRetries: 3,}Workloads
heading.anchorLabelAlle AI-kall gar gjennom executeWorkload() i ai-sdk.ts — det eneste chokepoint-et for AI-operasjoner. Workloads sender konfigurasjon direkte til executeWorkload().
Alle workloads
heading.anchorLabel| Workload | Kategori | Modell | Maks tokens | Temp | Kostnad/kall |
|---|---|---|---|---|---|
classify_enrichment | impulse | Sonnet | 400 | 0.4 | ~$0.006 |
classify_analytical | impulse | Haiku | 1024 | 0.2 | ~$0.002 |
check_in_assess | check_in | Sonnet | 512 | 0.4 | ~$0.006 |
batch_step_guidance_* | session | Sonnet | 2500 | 0.8 | ~$0.007 |
step_guidance_* | session | Sonnet | 500 | 0.8 | ~$0.005 |
session_summary_* | session | Sonnet | 800 | 0.7 | ~$0.012 |
insight_narrative | insight | Sonnet | 250 | 0.7 | ~$0.006 |
pulse | insight | Sonnet | 250 | 0.8 | ~$0.005 |
transformation_distillation | insight | Sonnet | 120 | 0.6 | ~$0.005 |
Formula/Bottling — konseptuell integritet
heading.anchorLabelArkitektonisk separasjon av HVA (concept) fra HVORDAN (style) i prompt-design:
| Lag | Formaal | Endringsrisiko |
|---|---|---|
| Formula (Concept) | Hva feltet betyr — essence, pakrevde elementer, forbudt drift | Last — forretningskritisk moat |
| Bottling (Style) | Hvordan det leveres — tone, formulering | Trygt a iterere |
| Variation | Freshness — roterbare apninger, dedupliseringsregler | Dynamisk |
Definert i concept-types.ts, brukt i sesjonssteg-definisjonene (step-guidance.ts).
Impulsklassifisering
heading.anchorLabelNar en bruker fanger en impuls, kjores to AI-workloads asynkront via BullMQ:
1. Analytisk klassifisering (classify_analytical)
heading.anchorLabelStrukturert analyse med classification, confidence, lag og omrader. Bruker fast-tier.
Output:
- Classification (contraction/expansion) med confidence score
- Primaert og sekundaert transformasjonslag
- Relevante livsomrader med relevans-score
- Innholdsvalidering (valid/low_confidence/gibberish)
2. Kreativ anrikning (classify_enrichment)
heading.anchorLabelBrukervennlig tekst: tittel, sammendrag og sesjonshook. Bruker balanced-tier.
Output:
- Tittel (3-8 ord)
- Sammendrag (3-5 setninger, empatisk)
- Sesjonshook (kort invitasjon til prosessering)
Begge resultater kombineres i impulses.ai_analysis JSONB-kolonnen.
Sesjonsveiledning
heading.anchorLabelSteg-for-steg AI-veiledning (guidance)
heading.anchorLabelHver av de 5 sesjonsstegene far personlig AI-veiledning basert pa brukerens impuls.
Prompt-spesifikasjoner:
| Egenskap | Verdi |
|---|---|
| Lengde | 50-80 ord, 4-6 setninger |
| Tone | Varm-direkte (“klok venn”) |
| Stil | Flytende prosa, ingen punktlister |
| Tilnærming | Invitasjoner (“Legg merke til…”, “Kjenn…”) — IKKE kommandoer |
| Sprak | Brukerens sprakpreferanse eller detektert fra impulstekst |
Forbudte monstre:
- Sporsmaalstegn i veiledningstekst
- Sjekklister eller sekvensielle instruksjoner
- Klinisk/terapeutisk sprak
- Tall, vurderinger, skalaer
Contraction-steg
heading.anchorLabel| Steg | Formaal | Kjernemekanisme |
|---|---|---|
| C1: Feel | Somatisk bevissthet | Apent invitasjon til a kjenne kroppen |
| C2: Own | Erkjenn energi-investering | ”Jeg innrommer at jeg nyter folelsen av a tenke at…” |
| C3: Shift | Tilstand definerer virkelighet | ”Din tilstand definerer din virkelighet” (MA vaere med) |
| C4: Envision | Visualiser ny tilstand | Kroppsforankring — forankre ny folelse i kroppen |
| C5: Release | Slipp og affirmer | Personlig affirmering + slipp gammel moenster |
Expansion-steg
heading.anchorLabel| Steg | Formaal | Kjernemekanisme |
|---|---|---|
| E1: Feel | Kjenn intensjonen | Kroppen som sannhetsdetektor — baerer dette ekte energi? |
| E2: Own | Klar motivasjon | Apent sporsmaal “Hvorfor er dette viktig for deg?” |
| E3: Shift | Tilstand definerer virkelighet | Identisk med C3 (bevisst designvalg) |
| E4: Envision | Identitetsvisjon | ”Se hvem du ER nar dette allerede er sant” |
| E5: Release | Slipp med tillit | Losrivelse med overveldende selvtillit |
Sesjonoppsummering (summary)
heading.anchorLabelEtter fullfort sesjon genererer quality-tier en komplett oppsummering med:
- Tittel og sammendrag
- Utgangspunkt og ny forstaelse
- Gjennombrudd (lista)
- Neste steg (handlingsanbefalinger)
- Steg-for-steg prosessgjennomgang
- Refleksjon
Sjekk-inn vurdering
heading.anchorLabelBE->DO->RECEIVE filosofi
heading.anchorLabelSjekk-inn-funksjonen (check_in_assess) vurderer brukerens navaerende tilstand basert pa BE->DO->RECEIVE-rammeverket:
- BE (vaere): Endre indre tilstand
- DO (gjore): Handle fra riktig tilstand
- RECEIVE (motta): Motta det du onsker
Vurderingens jobb: Avsløre hva brukerens GJORING (DO) skaper i deres VAEREN (BE) — tilstanden de ikke kan se fordi de er inni den.
Tre utfall
heading.anchorLabel| Klassifisering | Farge | Beskrivelse |
|---|---|---|
| Positive | .brand | Brukeren er i en god tilstand |
| Contraction | .classContraction | Sammentrekning oppdaget — kan konverteres til impuls |
| Expansion | .classExpansion | Utvidelse oppdaget — kan konverteres til impuls |
Contraction/expansion-sjekk-inn kan konverteres til impulser via convertToImpulse-funksjonen, som kobler den nye impulsen til sjekk-innen via source_check_in_id.
Innsiktgenerering
heading.anchorLabelInnsikter genereres i to faser:
Pulse (rask regenerering)
heading.anchorLabelpulse.ts genererer raske oppdateringer nar nye impulser fanges. Bruker quality-tier.
Narrative (dypere analyse)
heading.anchorLabelnarrative.ts genererer dypere narrative innsikter basert pa embedding-analytics. Krever at impulse-embedding er ferdig for denne kjores (to-fase regenerering).
Embedding-basert analytikk
heading.anchorLabelembedding-analytics.ts bruker Voyage AI embeddings og pgvector for:
- Omradefordeling (hvilke livsomrader dominerer)
- Lagfordeling (fysisk, emosjonell, mental, spirituell)
- Balanse mellom contraction og expansion
- Semantisk klynging av lignende impulser
Transformasjons-destillering
heading.anchorLabeltransformation-distillation.ts ekstra vekstmonstre fra fullforte sesjoner for dypere innsikter.
Embedding-arkitektur
heading.anchorLabelVoyage AI
heading.anchorLabelEmbedding-generering bruker Voyage AI (voyage-3-lite) for 512-dimensjonale vektorer.
| Egenskap | Verdi |
|---|---|
| Modell | voyage-3-lite |
| Dimensjoner | 512 |
| Lagring | pgvector vector(512) |
| Likhetsmatrise | Cosine similarity |
| Standard terskel | 0.7 |
Hva embeddes?
heading.anchorLabel| Innholdstype | Tabell | Kolonne |
|---|---|---|
| Impulstekst | impulses | embedding |
| Sesjonsrefleksjon + nokkelfunn | sessions | embedding |
| Sjekk-inn-innhold | check_ins | embedding |
To-fase regenerering
heading.anchorLabelNar en ny impuls fanges, kjores embedding og innsiktregenerering i sekvens:
- Embedding-fase:
embedImpulsegenererer vektor via Voyage AI - Regen-fase: Narrative-innsikter regenereres ETTER embedding er komplett
AI SDK-integrasjon
heading.anchorLabelImpulse AI bruker Vercel AI SDK (@ai-sdk/anthropic) som tynt lag over Anthropic API.
Hva AI SDK handterer
heading.anchorLabel- Zod-til-JSON-Schema konvertering
- Responsvalidering mot skjema
- Anthropic prompt caching (via
cache_controlpa system-meldinger) - Automatisk retry pa HTTP-feil (429, 500, 503)
Hva vi handterer selv
heading.anchorLabel- Timeout-retry: AI SDK retrier ikke pa
AbortError— vi implementerer egen retry (2 ekstra forsok) - Auto-tracking: Nar
trackinger satt i options, kallestrackUsage()automatisk med tokens, kostnad, varighet og fingerprint - Prompt fingerprinting: SHA-256 hash av system prompt + modell + temperatur + maxTokens — lagres med hvert kall
- Versjonsregistrering: Forste gang en ny fingerprint dukker opp, lagres full konfig-snapshot i
ai_workload_versions - Tier-validering: Sjekk av abonnement + kostnadstaksjekk for AI-tilgang
- Cache-splitting: System prompts splittes i statisk (cached) og dynamisk (per-bruker) del
// Workload med cache-splitting og auto-trackingconst system = buildSplitPrompt(promptConfig); // { static, dynamic }
const result = await executeWorkload(OutputSchema, { prompt: userPrompt, system, // string eller { static, dynamic } maxTokens: 500, temperature: 0.7, workloadType: 'check_in_assess', tracking: { userId, impulseId, metadata: (r) => ({ outcome: r.data.outcome, contentLength: input.content.length, }), },});Prompt Caching
heading.anchorLabelAnthropic tilbyr prompt caching som reduserer input-kostnaden med opptil 90% for gjentatte system-instruksjoner. Vi bruker dette med en cache-splitting arkitektur som separerer statisk og dynamisk innhold.
Arkitektur
heading.anchorLabelHver AI-workload sitt system prompt splittes i to deler:
| Del | Innhold | Caching |
|---|---|---|
| Statisk prefix | Rolle, konsepter, konvensjoner, eksempler, grammatikkregler | Cached med cache_control: ephemeral (5 min TTL) |
| Dynamisk suffix | PersonContext, impulsehistorikk, energiniva, temporalt | Aldri cached — varierer per bruker/request |
Vercel AI SDK sender disse som to separate system-meldinger til Anthropic. Den statiske delen er identisk for alle brukere av samme workload-type, sa cache-treff er hoy.
export function buildSplitPrompt(config: PromptStructure): SplitSystemPrompt { return { static: buildPrompt({ ...config, personContext: undefined, dynamicContext: undefined }), dynamic: renderDynamicParts(config), // personContext + dynamicContext };}Cache-okonomi (5m TTL)
heading.anchorLabel| Operasjon | Pris vs. standard input | Sonnet-pris per MTok |
|---|---|---|
| Standard input | 1.00x | $3.00 |
| Cache write | 1.25x (+25%) | $3.75 |
| Cache read | 0.10x (-90%) | $0.30 |
Break-even: 2 requests med samme statiske prefix innen 5 minutter.
Cache auto-fornyes ved treff — med jevn trafikk utloper cachen aldri.
Adaptiv cache-aktivering
heading.anchorLabelIkke alle workloads har nok trafikk til a rettferdiggjore cache-write-premien. Modulen cache-strategy.ts sporer request-frekvens per workload-type og aktiverer caching kun nar det lonner seg.
| Strategi | Workloads | Oppforsel |
|---|---|---|
| alwaysCache | classify_enrichment, classify_analytical, check_in_assessment | Alltid cachet — hoy frekvens, burst-monster |
| adaptiv | batch_step_guidance_*, session_summary_*, pulse, narrative, distillation | Cacher nar >= 2 requests innen 5 min |
| neverCache | name_validation | Aldri cachet — for sjelden og liten |
Statisk/dynamisk split per workload
heading.anchorLabel| Workload | Statisk (cached) | Dynamisk (ikke cached) |
|---|---|---|
| Enrichment | Konsepter, eksempler, konvensjoner | EnergeticLevel, priorContext |
| Analytical | Klassifiseringskonsepter | AvailableAreas |
| Check-in | CHECK_IN_CONCEPTS, eksempler | ImpulseHistory |
| Step Guidance | 5 steg-definisjoner | PreviousGuidanceOpenings |
| Session Summary | SUMMARY_CONCEPTS, konvensjoner | (personContext via separat felt) |
| Pulse/Narrative/Distillation | Konsepter, eksempler | (personContext via separat felt) |
Overvaking
heading.anchorLabelCache-metrikkene lagres i ai_usage-tabellen:
| Kolonne | Beskrivelse |
|---|---|
cache_read_tokens | Tokens lest fra cache (90% rabatt) |
cache_write_tokens | Tokens skrevet til cache (25% premium) |
service_tier | Anthropic service tier (standard/priority/batch) |
inference_geo | Geografisk region for inferens |
-- Cache hit rate per workloadSELECT workload_type, COUNT(*) as calls, SUM(CASE WHEN cache_read_tokens > 0 THEN 1 ELSE 0 END) as cache_hits, ROUND(100.0 * SUM(CASE WHEN cache_read_tokens > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as hit_rate_pct, SUM(cache_read_tokens) as total_cached_tokensFROM ai_usageWHERE created_at > NOW() - INTERVAL '24 hours'GROUP BY workload_typeORDER BY calls DESC;Kostnadsmodell
heading.anchorLabelPriser per modell
heading.anchorLabel| Modell | Input (per 1M tokens) | Output (per 1M tokens) |
|---|---|---|
claude-sonnet-4-6 | $3.00 | $15.00 |
claude-haiku-4-5 | $1.00 | $5.00 |
Estimert kostnad per brukerhandling
heading.anchorLabel| Handling | Workloads | Estimert kostnad |
|---|---|---|
| Fange en impuls | enrichment + analytical (parallelt) | ~$0.008 |
| Sjekk-inn | check_in_assess | ~$0.006 |
| Sjekk-inn med konvertering | assess + enrichment + analytical | ~$0.014 |
| Full sesjon (5 steg batch) | batch_step_guidance + summary | ~$0.019 |
| Impuls + full sesjon | klassifisering + guidance + summary | ~$0.027 |
| Innsiktregenerering | narrative + pulse + distillation | ~$0.016 |
Typisk aktiv bruker: ~$0.55/mnd (4-9% av revenue).
Cache-bevisst kostnadsberegning
heading.anchorLabelcalculateAICost() i ai-cost-service.ts tar hensyn til cache-metrikkene:
// Tokens fordeles pa tre kategorier med ulike priserconst standardInput = promptTokens - cacheRead - cacheWrite;const inputCost = (standardInput / 1M) * pricing.input; // 1.00xconst cacheWriteCost = (cacheWrite / 1M) * pricing.input * 1.25; // 1.25xconst cacheReadCost = (cacheRead / 1M) * pricing.input * 0.10; // 0.10xKostnadsovervaking
heading.anchorLabelAll AI-bruk logges automatisk i ai_usage-tabellen (via auto-tracking i executeWorkload):
workload_type: Type operasjonmodel: Modell bruktprompt_tokens/completion_tokens: Tokenforbrukcache_read_tokens/cache_write_tokens: Cache-metrikkeneestimated_cost: Cache-bevisst beregnet kostnad i USDduration_ms: Tidsforbrukprompt_fingerprint: Hash av prompt-konfigurasjonservice_tier: Anthropic service tierinference_geo: Inferensregionuser_id,impulse_id,session_id: Kontekstkobling
Prompt-versjonering
heading.anchorLabelTabellen ai_workload_versions lagrer en konfig-snapshot forste gang en ny fingerprint dukker opp. Dette gir sporbarhet uten manuell versjonering:
-- Se versjonshistorikk for et workloadSELECT fingerprint, first_seen, modelFROM ai_workload_versionsWHERE workload_type = 'classify_enrichment'ORDER BY first_seen DESC;
-- Se nar en prompt-endring ble deployetSELECT prompt_fingerprint, count(*), min(created_at)FROM ai_usageWHERE workload_type = 'guidance'GROUP BY prompt_fingerprint;Cache-ytelse (verifisert apr 2026)
heading.anchorLabelMalinger fra staging med reelle brukerdata viser faktisk besparelse per workload-type:
| Workload | Uten cache | Med cache-hit | Besparelse | Cachede tokens |
|---|---|---|---|---|
pulse | $0.0122 | $0.0054 | 56% | ~2 695 av ~3 700 |
classify_enrichment | $0.0127 | $0.0056 | 56% | ~2 495 av ~3 060 |
insight_narrative | $0.0139 | $0.0068 | 51% | ~2 803 av ~4 000 |
session_summary_* | $0.0177 | $0.0102 | 42% | ~2 659 av ~3 560 |
batch_step_guidance_* | $0.0198 | $0.0136 | 31% | ~2 842 av ~3 500 |
classify_analytical (Haiku) | $0.0040 | $0.0040 | 0% | Under Haiku-minimum |
System-promptene utgjor 70-82% av total prompt og er identiske pa tvers av brukere. Cache-write skjer pa forste kall, etterfølgende kall innen 5 min far cache-read (90% rabatt).
Observert write/read-kjede (staging)
heading.anchorLabel17:52:06 batch_step_guidance cache_write=2842 cost=$0.02317:53:17 batch_step_guidance cache_read=2842 cost=$0.013 (71s senere)17:53:33 batch_step_guidance cache_read=2842 cost=$0.014 (87s senere)Skaleringseffekt
heading.anchorLabelCache-hitrate oker med brukervolum (flere samtidige brukere = varmere cache):
| Skala | Cache-hitrate (est.) | AI-kostnad/bruker/mnd | AI % av revenue | Arlig besparelse vs. uten cache |
|---|---|---|---|---|
| Uten cache | 0% | $1.20 | 16.9% | — |
| 1 000 brukere | ~50% | $0.95 | 13.4% | ~$3 000 |
| 5 000 brukere | ~75% | $0.83 | 11.7% | ~$22 000 |
| 10 000 brukere | ~85% | $0.77 | 10.9% | ~$50 000 |
Rate Limiting
heading.anchorLabelAI-bruk begrenses per bruker basert pa abonnementstier:
| Egenskap | Verdi |
|---|---|
| HTTP rate limit | 100 req/min (global), 20 req/min (AI-endepunkter) |
| Impulskvoter | Telles via impulse-quota-service mot subscription_config |
Kvoter for impulsfangst og sjekk-inn konfigureres via subscription_config-tabellen.