feat(gemini): Skiva 1 – Gemini 2.5 Flash som lärar-tier för kylskåpsskanning
- Gemini-adapter bakom AamosClient-interface (AAMOS_MODE=gemini) - Serversida/worker: hämtar bild, anropar Gemini, mappar mot canonical_ingredients - Kostnad/tokens bokförs i ai_usage_counters; global dagsbudget via BudgetStore - Redis-backed budget i worker, in-memory i tester - Migration 0019: ai_cost_usd_microcents - Hermetiska tester med inspelad fixture; separat pnpm eval:scan - docs/09 uppdaterad ärligt: AAMOS-status, Gemini-flöde, säkerhet/kostnad - REQUIRE_REAL=1 stödjer AAMOS_MODE=gemini; deploy-grind uppdaterad
This commit is contained in:
+102
-59
@@ -1,60 +1,111 @@
|
||||
# Del 9 – AAMOS-integration
|
||||
# Del 9 – AI-integration (AAMOS + Gemini-lärar-tier)
|
||||
|
||||
AAMOS är er befintliga AI-plattform och nås **uteslutande via API** från appens
|
||||
backend (beslut 2026-08-02). Mobilappen har aldrig direktkontakt och innehåller inga
|
||||
AI-hemligheter (spec §31, §61.13).
|
||||
> Uppdaterad 2026-08-08 efter scouting: AAMOS Core v1 är autentiseringsmässigt
|
||||
> nåbar men saknar konfigurerad matmodell (`/v1/detect` faller tillbaka på YuNet
|
||||
> ansiktsdetektering). Tills vidare används **Gemini 2.5 Flash** som
|
||||
> lärar-tier för kylskåpsskanning, bakom samma `AamosClient`-gränssnitt.
|
||||
> AAMOS/prod rörs inte.
|
||||
|
||||
Appen når AI **uteslutande via backend** (beslut 2026-08-02). Mobilappen har
|
||||
aldrig direktkontakt och innehåller inga AI-hemligheter (spec §31, §61.13).
|
||||
|
||||
## Kontraktet (`packages/ai-contracts`)
|
||||
|
||||
Appen definierar ett versionerat, Zod-validerat kontrakt per uppgiftstyp. Input
|
||||
valideras FÖRE nätverksanropet, output valideras EFTER – ett svar som bryter kontraktet
|
||||
behandlas som fel och når aldrig användardata. AAMOS kan därmed byta modeller,
|
||||
prompts och leverantörer fritt bakom kontraktet ("Food API ska få stabila kontrakt
|
||||
oavsett modell").
|
||||
behandlas som fel och når aldrig användardata. Modeller, prompts och leverantörer kan
|
||||
bytas fritt bakom kontraktet ("Food API ska få stabila kontrakt oavsett modell").
|
||||
|
||||
Antaget transport-API (justeras mot er AAMOS-dokumentation – endast
|
||||
`HttpAamosClient` berörs):
|
||||
|
||||
```text
|
||||
POST {AAMOS_API_URL}/v1/tasks Authorization: Bearer {AAMOS_API_KEY}
|
||||
→ AamosRequestEnvelope { taskId, taskType, contractVersion, input, metadata }
|
||||
← AamosResponseEnvelope { taskId, status: ok|uncertain|failed, output,
|
||||
modelVersion, promptVersion, latencyMs, costUsd }
|
||||
GET {AAMOS_API_URL}/v1/health
|
||||
```
|
||||
┌─────────┐ signed image URL ┌──────────┐ AamosClient ┌─────────────────┐
|
||||
│ App │ ───────────────────→ │ Food API │ ───────────────→│ Worker adapter │
|
||||
└─────────┘ └──────────┘ └─────────────────┘
|
||||
│
|
||||
┌──────────────────┬──────────┴──────────┐
|
||||
│ AAMOS_MODE=http │ AAMOS_MODE=gemini │
|
||||
│ HttpAamosClient │ GeminiAamosClient │
|
||||
│ → AAMOS Core v1 │ → Gemini 2.5 Flash │
|
||||
└──────────────────┴─────────────────────┘
|
||||
```
|
||||
|
||||
Metadata per anrop: `correlationId` (spårbarhet §58), `subjectRef`
|
||||
(**pseudonymiserat** id – aldrig e-post/namn, §56), `priority`, samt
|
||||
`consentFlags {personalization, anonymizedImprovement, imageTraining}` så att AAMOS
|
||||
kan upprätthålla samtyckesreglerna på sin sida också.
|
||||
`createAamosClient()` väljer adapter utifrån `AAMOS_MODE`. Inga anropare behöver
|
||||
ändras. App→Food API→leverantörsgränsen är oförändrad.
|
||||
|
||||
**Behövs från er:** AAMOS bas-URL + API-nyckel per miljö, och er endpoint-/schema-
|
||||
dokumentation. Avviker kuvertformatet mappas det i `HttpAamosClient` – kontrakten
|
||||
utåt ändras inte.
|
||||
## AAMOS Core v1-status
|
||||
|
||||
## Uppgiftstyper (15)
|
||||
Reella endpoints (ingen `/v1/tasks` fiktion):
|
||||
|
||||
Bild/OCR: `ANALYZE_FRIDGE_IMAGE`, `ANALYZE_PANTRY_IMAGE`, `ANALYZE_MEAL_IMAGE`
|
||||
(kcal-INTERVALL, aldrig exakt påstående), `READ_RECEIPT`, `READ_NUTRITION_LABEL`
|
||||
(värden från etiketten – inte modellens gissning), `READ_EXPIRY_DATE`.
|
||||
Text/struktur: `NORMALIZE_PRODUCTS`, `DEDUPLICATE_INVENTORY`, `STRUCTURE_RECIPE_TEXT`,
|
||||
`PARSE_CRAVING`, `MODERATE_RECIPE`.
|
||||
Rådgivande: `GENERATE_RECIPE_OPTIONS` (granskas redaktionellt, §15), `RANK_RECIPES`
|
||||
(får ordna om – aldrig lägga till), `GENERATE_WEEK_PLAN`, `UPDATE_USER_MEMORY`.
|
||||
```text
|
||||
POST /v1/detect POST /v1/verify POST /v1/compare
|
||||
POST /v1/segment POST /v1/classify POST /v1/authenticate
|
||||
POST /v1/score POST /v1/explain POST /v1/extract
|
||||
POST /v1/track GET /v1/health
|
||||
```
|
||||
|
||||
Alla outputs bär `confidence`, och "osäker/okänd" är förstklassiga svar
|
||||
(`canonicalIngredientId: null`, `requiresConfirmation: true`) – spec §10/§61.4.
|
||||
Scouting 2026-08-08 visade:
|
||||
|
||||
## Routing & specialister (spec §33)
|
||||
- `amos-core.service` på server-2 anropar `http://localhost:3207`.
|
||||
- Värdport `3207` ägs av `aamos-identity-rust`, inte AI-inferens.
|
||||
- AI-inferenscontainern `amos-ai-inference:latest` exponerar **värdport 3209**.
|
||||
- Port mismatch gör att AAMOS Core aldrig når inferens.
|
||||
- Även efter hypotetisk port-fix saknas `FOOD`/`GROCERY`/`VISION_MODEL`
|
||||
konfiguration i `.env.amos-core`; `/v1/detect` defaultar till `yunet`.
|
||||
|
||||
Rekommenderad routing inne i AAMOS: specialistmodell först (vanliga livsmedel,
|
||||
nordiska förpackningar, kvitto-OCR, datum-OCR, portionssegmentering) → hög confidence:
|
||||
svara; låg: extern generalist (Haiku 4.5-klass för volym, Sonnet-klass för svåra fall);
|
||||
fortsatt osäkert: `status: "uncertain"` → appen frågar användaren. Appen skickar
|
||||
`modelVersion`/`promptVersion` vidare in i `scan_jobs` och `ai_corrections` så att
|
||||
varje datapunkt är spårbar till modellversion (§9).
|
||||
**Beslut:** rör inte AAMOS-prod. AAMOS kan återaktiveras i framtiden när en
|
||||
matmodell finns konfigurerad, via `HttpAamosClient`.
|
||||
|
||||
## Feedback → träning (spec §33)
|
||||
## Gemini-lärar-tier (Skiva 1)
|
||||
|
||||
Aktiveras med `AAMOS_MODE=gemini` + `GEMINI_API_KEY`. Endast workern anropar
|
||||
Gemini; API:et når aldrig nyckeln.
|
||||
|
||||
### Hanterade uppgiftstyper
|
||||
|
||||
- `ANALYZE_FRIDGE_IMAGE`
|
||||
- `ANALYZE_PANTRY_IMAGE`
|
||||
|
||||
Övriga task types (`READ_RECEIPT`, `READ_NUTRITION_LABEL`, etc.) svarar
|
||||
`failed` i Skiva 1 och faller tillbaka på appens befintliga flöden.
|
||||
|
||||
### Flöde
|
||||
|
||||
1. App laddar upp bild → Food API skapar `scan_job` (status `queued`).
|
||||
2. Workern plockar jobbet, hämtar bilden från lagring (S3/mock).
|
||||
3. `GeminiAamosClient` skickar bilden + svensk livsmedelsprompt till
|
||||
`generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent`.
|
||||
4. Gemini svarar med JSON: produkt, varumärke, kvantitet, enhet, kategori,
|
||||
bäst-före/sista-förbruk (om läsbart), konfidens, imageQualityIssues.
|
||||
5. Workern mappar detekterade namn mot `canonical_ingredients` (mjuk matchning).
|
||||
6. Resultatet sparas i `scan_jobs` som `awaiting_confirmation`.
|
||||
7. Användaren granskar och bekräftar i appen. Aldrig auto-commit.
|
||||
8. AI är aldrig facit för allergener/näring — mjölkprincipen orörd.
|
||||
|
||||
### Kostnadskontroll
|
||||
|
||||
- `scan_jobs.costUsd` lagrar verklig kostnad per anrop.
|
||||
- `ai_usage_counters.ai_cost_usd_microcents` ackumulerar månadskostnad per
|
||||
användare (utan PII).
|
||||
- Hushållskvoten `aiScans` respekteras (befintlig entitlements-logik).
|
||||
- Global dagsbudget `GEMINI_DAILY_BUDGET_USD` via `BudgetStore`. Workern använder
|
||||
Redis-backed budget; tester använder in-memory budget. När budgeten är
|
||||
förbrukad nekar adaptern nya anrop.
|
||||
- Loggning: status, latens, tokens, kostnad, taskType. Ingen bild, inget
|
||||
fritext, inga PII-fält.
|
||||
|
||||
### Säkerhet
|
||||
|
||||
- Nyckeln finns i `.env` på workerservrar (SSM i produktion) och checkas aldrig
|
||||
in.
|
||||
- App/API validerar att `AAMOS_MODE=gemini` kräver `GEMINI_API_KEY` i produktion.
|
||||
- `REQUIRE_REAL=1` i deploy-grinden gör att start failar om nyckel saknas.
|
||||
|
||||
## Routing & specialister (framtida)
|
||||
|
||||
Skiva 2 kommer lägga premium-eskaleringsstier (Gemini Pro / GPT-4o / Claude)
|
||||
internt i adaptern vid låg konfidens, med eval/guldset som beslutar billig vs
|
||||
dyr modell. Fram till dess: Gemini 2.5 Flash för allt i Skiva 1.
|
||||
|
||||
## Feedback → träning (Skiva 3)
|
||||
|
||||
Varje användarkorrigering i granska-flödet sparas som `ai_corrections`
|
||||
(AI-utdata + korrigering + modell/promptversion + **samtyckessnapshot**).
|
||||
@@ -62,25 +113,17 @@ Veckojobbet `BUILD_TRAINING_SAMPLE` exporterar endast rader där
|
||||
`anonymized_improvement = granted` vid korrigeringstillfället; bilder kräver
|
||||
separat `image_training`-samtycke. Personligt minne är aldrig träningsdata (§32).
|
||||
|
||||
## Memory (spec §32)
|
||||
|
||||
Appens DB är den användarsynliga sanningen (`memory_items`). Nattjobbet skickar
|
||||
veckans domänhändelser + befintliga minnesnycklar till `UPDATE_USER_MEMORY`; AAMOS
|
||||
returnerar förslag (kind/key/sammanfattning/confidence/expires) som skrivs in – men
|
||||
poster som användaren verifierat eller pausat skrivs **aldrig** över. Användarens
|
||||
rättelser blir `user_stated` med confidence 1 och vinner alltid (§30).
|
||||
|
||||
## Evals (spec §34)
|
||||
|
||||
`ai_eval_cases` (fast testbibliotek: kyl/frys/skafferi/tallrik/kvitton/etiketter/
|
||||
allergifall/mörka bilder/överlapp/orimliga recept) + `ai_eval_runs` (precision, recall,
|
||||
missade produkter, hallucinationer, allergifel, latens, kostnad, korrigeringsgrad).
|
||||
Regel: **ingen modell- eller promptändring i AAMOS tas i produktion för appen utan
|
||||
grön eval-körning**, synlig i adminpanelen (`/admin/v1/ai/eval-runs`). Corpus byggs
|
||||
under beta ur anonymiserade, samtyckta exempel.
|
||||
- `pnpm test`: hermetiska tester med inspelad Gemini-fixture, når aldrig nätet.
|
||||
- `pnpm --filter @app/worker eval:scan`: live-eval mot litet guldset. Rapporterar
|
||||
precision, recall, kostnad, latens. Kräver `AAMOS_MODE=gemini` + nyckel.
|
||||
- Regel: **ingen modell-/promptändring tas i produktion utan grön eval**.
|
||||
|
||||
## Kostnadskontroll
|
||||
## Lägen
|
||||
|
||||
`costUsd` per anrop loggas på scan_jobs → daglig kostnad per uppgiftstyp i admin;
|
||||
larm vid spik (§58). Fair use-kvoterna (Del 10/13) begränsar exponeringen per användare.
|
||||
Mock-läget (`AAMOS_MODE=mock`) finns för dev/test; produktion vägrar starta i mock.
|
||||
| `AAMOS_MODE` | Användning |
|
||||
|---|---|
|
||||
| `mock` | dev/test utan nätverk. Produkten vägrar starta i mock. |
|
||||
| `http` | Riktig AAMOS Core när matmodell finns. |
|
||||
| `gemini` | Gemini 2.5 Flash som lärar-tier för kylskåpsskanning (Skiva 1). |
|
||||
|
||||
Reference in New Issue
Block a user