benchmark-jd-voc-image-review.ts 2.6 KB

12345678910111213141516171819202122232425262728293031
  1. import { FmodeGeminiImageReviewProvider } from '../src/modules/listing-ai/image-review/gemini-image-review.provider.js';
  2. const baseUrl = (process.env.LAB_URL ?? 'http://127.0.0.1:4410').replace(/\/+$/, '');
  3. const provider = new FmodeGeminiImageReviewProvider({
  4. baseUrl: process.env.FMODE_LLM_BASE_URL ?? '', token: process.env.FMODE_LLM_API_KEY ?? '', timeoutMs: 45_000,
  5. });
  6. const cases = [
  7. ['10020722928820', ['去皮', '削皮', '土豆']],
  8. ['10020518260748', ['开水器', '开水机', '烧水']],
  9. ['10020422869649', ['消毒柜', '茶具']],
  10. ['10020670145859', ['烤箱', '烤炉']],
  11. ['10020770480513', ['置物架', '货架', '储物架']],
  12. ] as const;
  13. const rows: Array<{ productId: string; matched: boolean; facts: number; inferences: number; latencyMs: number; error: string | null }> = [];
  14. for (const [productId, expected] of cases) {
  15. try {
  16. const response = await fetch(`${baseUrl}/lab/products/${productId}`, { signal: AbortSignal.timeout(30_000) });
  17. if (!response.ok) throw new Error(`source_http_${response.status}`);
  18. const payload = await response.json() as { source?: { images?: Array<{ url?: string }>; title?: string } };
  19. const source = payload.source;
  20. const urls = (source?.images ?? []).map((image) => image.url ?? '').filter(Boolean).slice(0, 3);
  21. if (!source || !urls.length) throw new Error('image_asset_missing');
  22. const result = await provider.analyze(urls, source as never);
  23. const haystack = result.visibleFacts.map((fact) => `${fact.field} ${fact.value}`).join(' ');
  24. rows.push({ productId, matched: expected.some((term) => haystack.includes(term)), facts: result.visibleFacts.length, inferences: result.visualInferences.length, latencyMs: result.latencyMs, error: null });
  25. } catch (error) { rows.push({ productId, matched: false, facts: 0, inferences: 0, latencyMs: 0, error: error instanceof Error ? error.message.slice(0, 80) : 'image_review_failed' }); }
  26. }
  27. const matched = rows.filter((row) => row.matched).length;
  28. const report = { generatedAt: new Date().toISOString(), model: 'gemini-3.1-flash-image-preview', requested: rows.length, completed: rows.filter((row) => row.error === null).length, matched, accuracy: matched / rows.length, averageMs: rows.filter((row) => row.error === null).reduce((sum, row) => sum + row.latencyMs, 0) / Math.max(1, rows.filter((row) => row.error === null).length), rows: rows.map(({ productId, matched: rowMatched, facts, inferences, latencyMs, error }) => ({ productId, matched: rowMatched, facts, inferences, latencyMs, error })) };
  29. console.log(JSON.stringify(report, null, 2));
  30. if (rows.some((row) => row.error !== null)) process.exitCode = 2;