Реалізація автоматичної генерації alt-текстів для зображень товарів (AI)
Alt-текст у зображення виконує дві функції: допомагає пошуковику зрозуміти вміст сторінки та робить сайт доступним для незрячих користувачів. У більшості інтернет-магазинів alt-теги або пусті, або містять лише назву файлу типу IMG_4821.jpg. Це упущений SEO-сигнал та порушення доступності.
При наявності тисяч товарних зображень завдання вирішується автоматично — або на основі даних про товар, або через мультимодальний аналіз самого зображення.
Два підходи
Підхід 1: генерація за метаданими товару — швидко, дешево, не потребує завантаження зображень у модель. Підходить, коли фотографії стандартні (білий фон, один ракурс).
Підхід 2: мультимодальний аналіз зображення — модель бачить саме фото та описує, що на ньому. Потрібен для lifestyle-фотографій, зображень з кількома об'єктами або коли важливі візуальні деталі.
Підхід 1: за метаданими
function buildAltFromMetadata(
product: Product,
imageIndex: number,
imageType: "main" | "detail" | "lifestyle"
): string {
const base = `${product.brand} ${product.name}`;
if (imageType === "main") {
return `${base} — фото ${imageIndex + 1}`;
}
if (imageType === "detail") {
const feature = product.attributes.detailFeatures?.[imageIndex] ?? "деталь";
return `${base}: ${feature}`;
}
return `${base} у використанні`;
}
Простий, детерміністичний варіант. Не потребує API-вызовів, працює миттєво при імпорті товарів.
Підхід 2: мультимодальний (GPT-4o Vision)
import { openai } from "../lib/openai";
async function generateAltFromImage(
imageUrl: string,
product: Product
): Promise<string> {
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "user",
content: [
{
type: "image_url",
image_url: { url: imageUrl, detail: "low" }, // low — дешевше, достатньо для alt
},
{
type: "text",
text: `Напиши короткий alt-текст для цього зображення товару українською.
Товар: ${product.name}, бренд: ${product.brand}, категорія: ${product.category}.
Правила:
- 60–120 символів
- Опиши, що видно в зображенні, а не товар загалом
- Починай з назви товару тільки якщо вона ясно видна на фото
- Без слів "фото", "зображення", "картинка" на початку
- Без markdown, просто простий текст`,
},
],
},
],
max_tokens: 80,
temperature: 0.3,
});
return response.choices[0].message.content?.trim() ?? "";
}
detail: "low" знижує вартість з ~$0.002 до ~$0.0003 за зображення — достатньо для генерації alt, не потрібна висока роздільна здатність.
Пакетна обробка зображень
import { Queue, Worker } from "bullmq";
const altQueue = new Queue("alt-generation");
export async function queueImagesForAltGeneration(
productIds: string[]
) {
const products = await db.products.findMany({
where: { id: { in: productIds } },
include: { images: true },
});
const jobs = products.flatMap((product) =>
product.images
.filter((img) => !img.altText || img.altText === "")
.map((img) => ({
name: "generate-alt",
data: { imageId: img.id, productId: product.id, imageUrl: img.url },
}))
);
await altQueue.addBulk(jobs);
}
const altWorker = new Worker(
"alt-generation",
async (job) => {
const { imageId, productId, imageUrl } = job.data;
const product = await db.products.findById(productId);
// Визначаємо, потрібен ли мультимодальний аналіз
const useVision = product.imageType === "lifestyle" || product.useVisionForAlt;
let altText: string;
if (useVision) {
altText = await generateAltFromImage(imageUrl, product);
} else {
const imageIndex = product.images.findIndex((i: any) => i.id === imageId);
altText = buildAltFromMetadata(product, imageIndex, "main");
}
await db.productImages.update({
where: { id: imageId },
data: { altText, altGeneratedAt: new Date() },
});
},
{ connection: redisConnection, concurrency: 20 }
);
Вбудовування в CMS
Після генерації alt-текст зберігається в таблиці зображень та рендеруються в шаблоні:
<img
src="{{ image.url }}"
alt="{{ image.altText }}"
loading="lazy"
width="{{ image.width }}"
height="{{ image.height }}"
/>
Якщо alt пустий (генерація ще не пройшла або провалилася) — fallback на назву товару, але не порожній рядок:
const alt = image.altText || `${product.brand} ${product.name}`;
Аудит існуючого каталогу
Перед запуском генерації корисно зрозуміти масштаб проблеми:
SELECT
COUNT(*) FILTER (WHERE alt_text IS NULL OR alt_text = '') AS missing_alt,
COUNT(*) FILTER (WHERE alt_text = file_name) AS filename_as_alt,
COUNT(*) total
FROM product_images;
Це покаже, скільки зображень потребують обробки і дозволить спланувати навантаження на API. 50 000 зображень при мультимодальній генерації — близько $15, при генерації за метаданими — безкоштовно.







