Multi-modal AI in Web Apps — AI Web

Multi-modal AI Multi-modal = model yang memahami lebih dari satu jenis input: teks + gambar, teks + audio, teks + video. Membuka use case baru di web apps…

Model multi-modal adalah model yang bisa memproses dan menghasilkan berbagai jenis data: teks, gambar, audio, juga dokumen seperti PDF. Arahnya gak selalu sama: gpt-6-luna dan semua model Claude menerima gambar tapi menjawab dengan teks, gpt-transcribe mengubah suara jadi teks, gpt-4o-mini-tts mengubah teks jadi suara. Bayangin kasir Toko Buku. Pelanggan datang menyodorkan HP: "Yang ini ada?", isinya foto sampul. Kasir melihat foto, mengenali bukunya, lalu mengecek stok di komputer. Model vision itu mata kasirnya; stok dan harga tetap dari database.

🧪 Tebakan cepat: foto sampul dari HP pelanggan berukuran 1200 × 1600 piksel. Seluruh prompt teks Asisten Toko Buku sekitar 1.200 token. Di Claude, foto itu jadi berapa token: sekitar 300, sekitar 2.500, atau sekitar 25.000? Tebak dulu, jawabannya ada di bagian "Gambar jadi token: hitung dulu".

Gambar masuk: request ke OpenAI dan Claude

Alurnya sama seperti pertanyaan teks: browser mengirim foto ke server-mu (POST /api/assistant/foto), server yang memanggil provider, karena API key cuma ada di server. Foto dikirim sebagai base64: byte file diubah jadi teks, kira-kira 4/3 kali ukuran aslinya. Bentuk request-nya beda per provider (dicek di dokumentasi masing-masing, 11 Oktober 2026):

// OpenAI, Responses API: input_image dengan data URL lengkap
const r = await openai.responses.create({
  model: "gpt-6-luna",
  input: [{ role: "user", content: [
    { type: "input_text", text: "Judul buku di sampul ini apa? Jawab judulnya saja." },
    { type: "input_image", image_url: `data:image/jpeg;base64,${b64}`, detail: "auto" },
  ] }],
});

// Claude, Messages API: blok image, base64 TANPA awalan "data:"
const m = await anthropic.messages.create({
  model: "claude-haiku-5-5",
  max_tokens: 100,
  messages: [{ role: "user", content: [
    { type: "image", source: { type: "base64", media_type: "image/jpeg", data: b64 } },
    { type: "text", text: "Judul buku di sampul ini apa? Jawab judulnya saja." },
  ] }],
});

Gambar jadi token: hitung dulu

Gambar ditagih sebagai token input. Cara menghitungnya ditulis di dokumentasi:

Ukuran foto OpenAI (patch × 1,2) Claude
1200 × 1600 (asli dari HP) 38 × 50 = 1.900 patch → 2.280 token 43 × 58 = 2.494 token
768 × 1024 24 × 32 = 768 patch → 922 token 28 × 37 = 1.036 token
576 × 768 18 × 24 = 432 patch → 519 token 21 × 28 = 588 token

Foto asli di Claude jadi 2.494 token, dua kali seluruh prompt teks. Itu jawaban tebakannya. Dengan harga input $0,10 per 1 juta token (claude-haiku-5-5, juga gpt-6-luna), 1.000 foto asli = $0,25, setelah dikecilkan jadi 576 × 768 = $0,06. Di gpt-6-astra ($10 per 1 juta) bedanya $22,80 lawan $5,19. Untuk membaca judul sampul, 768 piksel sudah lebih dari cukup.

Kecilkan di browser sebelum upload

async function kecilkanFoto(file, sisiPanjang = 768) {
  const bitmap = await createImageBitmap(file); // orientasi dari EXIF sudah diterapkan
  const skala = Math.min(1, sisiPanjang / Math.max(bitmap.width, bitmap.height));
  const canvas = document.createElement("canvas");
  canvas.width = Math.round(bitmap.width * skala);  // 1200 -> 576
  canvas.height = Math.round(bitmap.height * skala); // 1600 -> 768
  canvas.getContext("2d").drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  return new Promise((selesai) => canvas.toBlob(selesai, "image/jpeg", 0.85));
}

Foto membawa lokasi: hapus EXIF

EXIF adalah blok metadata di dalam file JPEG (segmen APP1, penanda byte FF E1): merek kamera, waktu, dan sering kali koordinat GPS tempat foto diambil. Foto sampul yang dipotret di rumah membawa titik rumah pelanggan. Lab membuat JPEG sungguhan byte per byte: 362 byte, dengan blok EXIF 182 byte berisi GPS 6°12'37,2" S, 106°50'56,4" E (-6,210333, 106,849) dan waktu 2026:10:11 08:12:07.

function hapusExif(bytes) {
  const hasil = [0xff, 0xd8];                 // SOI: awal file JPEG
  let i = 2;
  while (i < bytes.length) {
    const penanda = bytes[i + 1];
    if (penanda === 0xda) { hasil.push(...bytes.slice(i)); break; } // SOS: data gambar sampai akhir
    const panjang = (bytes[i + 2] << 8) | bytes[i + 3];
    if (penanda !== 0xe1) hasil.push(...bytes.slice(i, i + 2 + panjang)); // APP1 (EXIF) dilewati
    i += 2 + panjang;
  }
  return Uint8Array.from(hasil);
}

Hasilnya 180 byte, GPS-nya hilang, dan browser masih menggambarnya. Model gak membaca metadata (dokumentasi OpenAI dan Claude sama-sama bilang begitu), tapi file-nya lewat server-mu, tersimpan di storage, dan bisa tampil lagi, misalnya di ulasan. JPEG baru dari toBlob juga gak membawa EXIF (dicek di Chrome 155), tapi gak semua upload lewat canvas, jadi server tetap menghapusnya, misalnya dengan meng-encode ulang gambar.

Suara: dari dan ke teks

const Pengenal = globalThis.SpeechRecognition ?? globalThis.webkitSpeechRecognition;
if (!Pengenal) tampilkanTombolRekam(); // rekam dengan MediaRecorder, kirim ke /api/transkripsi
speechSynthesis.speak(Object.assign(new SpeechSynthesisUtterance("Stok Laskar Pelangi 12."), { lang: "id-ID" }));

Dokumen PDF

Supplier mengirim daftar harga 12 halaman dalam PDF. OpenAI menerimanya sebagai { type: "input_file", filename: "harga.pdf", file_data: "data:application/pdf;base64,..." } (atau file_id, file_url); API mengambil teks dan gambar tiap halaman, dan tiap file harus di bawah 50 MB. Claude memakai { type: "document", source: { type: "base64", media_type: "application/pdf", data } }, maksimal 32 MB per request dan 600 halaman (100 kalau context window di bawah 1 juta token), sekitar 1.500 sampai 3.000 token teks per halaman ditambah token gambarnya. Minta hasilnya sebagai JSON dengan Structured Outputs (pelajaran AI-Powered Forms), lalu Bu Admin memeriksa sebelum katalog diperbarui.

Alt text: draf dari model, keputusan manusia

Model vision bisa menulis draf alt text dari foto produk: "Sampul buku oranye dengan pita judul gelap". Itu titik awal, bukan hasil akhir. Model bisa salah membaca judul, gak tahu konteks halaman, dan gak tahu gambar mana yang cuma hiasan (yang alt=""). Bu Admin membaca drafnya, membetulkannya jadi "Sampul novel Laskar Pelangi karya Andrea Hirata", baru disimpan. Sama untuk transkrip suara: tampilkan sebagai teks yang bisa dikoreksi.

⚠️ Jebakan yang sering ketemu

TL;DR: Model multi-modal memproses dan menghasilkan berbagai jenis data: teks, gambar, audio, PDF. Foto masuk lewat server-mu: OpenAI input_image dengan data URL di image_url (detail low, high, original, auto), Claude blok image dengan source base64 tanpa awalan plus media_type. Gambar dihitung per patch: OpenAI 32 px × 1,2 (perkiraan untuk gpt-6-luna, cek usage), Claude ⌈w/28⌉ × ⌈h/28⌉; foto 1200 × 1600 = 2.280 dan 2.494 token, dikecilkan ke 576 × 768 jadi 519 dan 588. Kecilkan dengan canvas sebelum upload, hapus EXIF (segmen APP1 berisi GPS) di client dan server. Suara: gpt-transcribe ($0,0045 per menit, languages: ["id"]), gpt-4o-mini-tts (beri tahu bahwa suaranya AI), atau Web Speech API di browser (speechSynthesis di mana-mana, SpeechRecognition belum di Firefox). PDF: input_file atau blok document, teks plus gambar tiap halaman. Model membaca, server memutuskan: stok dari SQL, alt text dan hasil ekstraksi diperiksa manusia.

Yang akan kamu pelajari