feat(deploy): Gemini som committat prod-läge + hermetiska eval-fixtures

- config.ts: tillåt AAMOS_MODE=http|gemini i prod; mock förbjudet
- first-deploy.sh: Grind 1 accepterar gemini (kräv GEMINI_API_KEY);
  Grind 5 kör eval:scan mot gemini; Grind 8 behandlar gemini som riktig AI
- gemini.ts: stöd för data-URLer (inline base64-bilder) i fetchOneImage
- eval-fixtures: 4 lokala bilder (fridge-1, fridge-2, label-1, receipt-1)
- scan-eval.ts: läser lokala fixtures, inga externa hämtningar
- capture-eval.ts: använder lokal fixture som data-URL
This commit is contained in:
Sven (AAMOS AI)
2026-08-12 04:10:10 +07:00
parent 3259f09f7d
commit 659d0f9290
9 changed files with 69 additions and 35 deletions
+16 -12
View File
@@ -291,6 +291,20 @@ export class GeminiAamosClient implements AamosClient {
}
}
private async fetchOneImage(url: string, timeoutMs: number): Promise<{ inline_data: { mime_type: string; data: string } }> {
if (url.startsWith("data:")) {
const match = url.match(/^data:([^;]+);base64,(.+)$/);
if (match && match[1] && match[2]) {
return { inline_data: { mime_type: match[1], data: match[2] } };
}
throw new Error(`Ogiltig data-URL: ${url.slice(0, 50)}`);
}
const res = await this.cfg.fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
if (!res.ok) throw new Error(`Kunde inte hämta bild: ${res.status} ${url}`);
const buf = Buffer.from(await res.arrayBuffer());
return { inline_data: { mime_type: this.mimeType(buf), data: buf.toString("base64") } };
}
private async analyzeStorageImage(
taskType: "ANALYZE_FRIDGE_IMAGE" | "ANALYZE_PANTRY_IMAGE",
input: TaskInput<"ANALYZE_FRIDGE_IMAGE">,
@@ -300,12 +314,7 @@ export class GeminiAamosClient implements AamosClient {
const started = Date.now();
const imageParts = await Promise.all(
input.imageUrls.slice(0, 6).map(async (url) => {
const res = await this.cfg.fetchImpl(url, { signal: AbortSignal.timeout(this.cfg.timeoutMs) });
if (!res.ok) throw new Error(`Kunde inte hämta bild: ${res.status} ${url}`);
const buf = Buffer.from(await res.arrayBuffer());
return { inline_data: { mime_type: this.mimeType(buf), data: buf.toString("base64") } };
}),
input.imageUrls.slice(0, 6).map((url) => this.fetchOneImage(url, this.cfg.timeoutMs)),
);
const estimatedCostUsd = imageParts.length * COST_ESTIMATE_PER_IMAGE_USD;
@@ -552,12 +561,7 @@ export class GeminiAamosClient implements AamosClient {
timeoutMs: number,
): Promise<Array<{ inline_data: { mime_type: string; data: string } }>> {
return Promise.all(
imageUrls.slice(0, 6).map(async (url) => {
const res = await this.cfg.fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
if (!res.ok) throw new Error(`Kunde inte hämta bild: ${res.status} ${url}`);
const buf = Buffer.from(await res.arrayBuffer());
return { inline_data: { mime_type: this.mimeType(buf), data: buf.toString("base64") } };
}),
imageUrls.slice(0, 6).map((url) => this.fetchOneImage(url, timeoutMs)),
);
}