Ringkasan Utama
Groq menjalankan model di atas chip LPU sehingga token pertama muncul dalam hitungan milidetik; dipadukan dengan Server-Sent Events dan route handler Next.js berbasis ReadableStream, chatbot bisa menampilkan balasan sedikit demi sedikit alih-alih menunggu respons utuh. Agar siap produksi, API key harus disimpan di server, terapkan rate limiting per pengunjung, batasi max_completion_tokens, dan perkuat system prompt terhadap prompt injection.
Sebagian besar demo chatbot terasa lambat karena menunggu seluruh respons selesai sebelum menampilkan apa pun. Chatbot produksi seharusnya terasa instan, dan dua bahan yang membuat itu mungkin adalah penyedia inferensi yang benar-benar cepat serta pipeline streaming yang mengirim token ke browser begitu token itu tersedia, bukan menyimpan seluruh balasan di server terlebih dahulu.
Tulisan ini membahas pembuatan pipeline tersebut secara menyeluruh: route handler Next.js App Router yang memanggil Groq API dengan mode streaming aktif, klien yang membaca respons secara bertahap, system prompt yang menjaga asisten tetap pada topiknya, serta rate limiting dan kontrol biaya yang dibutuhkan sebelum ini digunakan untuk trafik sungguhan.
Mengapa Groq Mengubah Pengalaman Streaming
Groq menjalankan model open source seperti Llama dan GPT OSS di atas perangkat keras LPU (Language Processing Unit) khusus, bukan GPU serba guna, sehingga chat completions API-nya mampu mempertahankan throughput token keluaran yang sangat tinggi. Untuk chatbot streaming, throughput ini lebih penting daripada sekadar kualitas model, karena kecepatan yang dirasakan pengguna pada antarmuka chat lebih ditentukan oleh seberapa cepat token berikutnya muncul setelah token pertama, bukan oleh total waktu permintaan.
- Waktu hingga token pertama biasanya di bawah beberapa ratus milidetik, sehingga UI bisa mulai merender hampir seketika setelah pengguna menekan kirim.
- Throughput token tetap tinggi bahkan pada respons yang panjang, sehingga jawaban lima ratus kata tidak terasa melambat di tengah jalan.
- Bentuk API-nya kompatibel dengan OpenAI, jadi paket resmi groq-sdk dan pola streaming di bawah ini akan terasa familiar jika kamu pernah membangun fitur chat dengan OpenAI sebelumnya.
Pilih model berdasarkan throughput dan context window yang dibutuhkan use case-mu, bukan hanya skor benchmark. Model yang lebih kecil dan cepat yang streaming secara instan sering memberi pengalaman yang terasa lebih baik dibanding model besar yang sedikit lebih akurat tapi terlihat lambat saat mulai merespons.
Membangun Route Handler Streaming
Di App Router, route handler hanyalah fungsi yang menerima Web Request dan mengembalikan Web Response, artinya kamu bisa mengembalikan ReadableStream mentah alih-alih body JSON. Itulah persis yang dibutuhkan chat streaming: handler membuka koneksi ke Groq dengan stream diset true, lalu meneruskan setiap token sebagai server-sent event begitu token itu tiba.
Handler di bawah ini menyimpan sumber kebenaran percakapan di server, menyusun array messages dengan system prompt ditambah pesan pengguna yang masuk, lalu meneruskan setiap delta dari stream Groq ke klien menggunakan format standar Server-Sent Events, yaitu baris berawalan data titik dua diikuti payload JSON dan baris kosong. Membungkus seluruh pemanggilan dalam blok try dan finally penting di sini: jika permintaan ke Groq gagal di tengah jalan, blok finally tetap menutup controller sehingga koneksi klien tidak menggantung tanpa batas waktu.
// app/api/chat/route.ts
import { NextRequest } from "next/server"
import Groq from "groq-sdk"
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
export async function POST(request: NextRequest) {
const { message } = await request.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
try {
const completion = await groq.chat.completions.create({
model: "openai/gpt-oss-120b",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: message },
],
max_completion_tokens: 1024,
temperature: 0.7,
stream: true,
})
for await (const chunk of completion) {
const delta = chunk.choices[0]?.delta?.content
if (delta) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ delta })}\n\n`)
)
}
}
controller.enqueue(encoder.encode('data: {"done":true}\n\n'))
} catch (err) {
controller.enqueue(
encoder.encode('data: {"error":"stream_failed"}\n\n')
)
} finally {
controller.close()
}
},
})
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream; charset=utf-8",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
},
})
}Membaca Stream di Sisi Klien
Di sisi klien, kamu tidak memerlukan objek EventSource karena permintaannya adalah POST dengan body, bukan GET biasa. Sebagai gantinya, baca body respons langsung menggunakan reader streaming dari Fetch API, decode setiap potongan byte menjadi teks, lalu pisahkan berdasarkan baris kosong ganda yang memisahkan setiap server-sent event.
Simpan sementara (buffer) setiap event yang datang terpotong di antara dua chunk, karena TCP tidak menjamin satu event utuh selalu tiba dalam satu kali pembacaan. Setelah kamu melihat flag done pada payload, hentikan pembacaan dan finalisasi pesan di state UI-mu. Ada baiknya juga merender teks yang terkumpul setiap kali delta tiba alih-alih menunggu seluruh pesan selesai, karena repaint bertahap itulah inti visual dari streaming itu sendiri; klien yang menyimpan semuanya dulu sebelum memperbarui layar membuang manfaat latensi yang seharusnya diberikan throughput Groq.
// client component
async function sendMessage(message: string, onDelta: (text: string) => void) {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message }),
})
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ""
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split("\n\n")
buffer = lines.pop() ?? ""
for (const line of lines) {
if (!line.startsWith("data: ")) continue
const payload = JSON.parse(line.slice(6))
if (payload.delta) onDelta(payload.delta)
if (payload.done) return
}
}
}Jangan memanggil Groq API langsung dari browser dengan API key tertanam di kode klien. Setiap permintaan harus melewati route handler milikmu sendiri agar key tetap berada di server; key yang bocor di halaman chatbot publik bisa diambil dan dipakai untuk menjalankan trafik orang lain dengan biaya ditanggung akunmu dalam hitungan menit.
System Prompt yang Menjaga Asisten Tetap pada Topik
Chatbot yang menjawab apa saja adalah risiko dukungan dan penyalahgunaan. System prompt harus menyatakan tujuan tunggal asisten secara jelas, mendaftarkan hal yang harus ditolaknya, dan menyertakan pengingat singkat menjelang akhir prompt untuk menegaskan kembali batasan tersebut, karena percakapan panjang bisa melemahkan instruksi di awal. Menyisipkan string canary unik sebagai komentar HTML di dalam system prompt juga membantu; jika string itu muncul kembali terbawa dalam pesan pengguna, itu sinyal kuat bahwa seseorang berhasil mengekstrak prompt-mu dan sedang mengujinya kembali ke endpoint milikmu sendiri.
- Nyatakan persona dan cakupan pada kalimat pertama: topik apa yang dijawab asisten, dan ke mana ia harus mengalihkan permintaan di luar itu.
- Tambahkan instruksi eksplisit untuk mengabaikan permintaan apa pun yang disisipkan dalam input pengguna yang mencoba mengganti, menimpa, atau memperluas peran asisten, karena upaya prompt injection cukup umum bahkan pada chatbot portofolio atau dukungan bertrafik rendah.
- Validasi dan batasi input pengguna di sisi server sebelum sampai ke model: terapkan panjang pesan maksimum, hapus karakter kontrol, dan tolak frasa injection yang dikenal lewat pemeriksaan regex sebelum menghabiskan satu permintaan Groq untuk itu.
Rate Limiting dan Kontrol Biaya
Groq menerapkan batas per API key di beberapa dimensi: permintaan per menit, token per menit, dan batas harian untuk keduanya. Melampaui batas mana pun akan mengembalikan status 429 disertai header retry-after yang memberitahu berapa detik harus menunggu. Aplikasimu sendiri sebaiknya menerapkan batas yang lebih ketat per pengunjung jauh sebelum menyentuh batas Groq, baik untuk mengendalikan biaya maupun mencegah satu klien yang menyalahgunakan sistem membuat pengguna lain kelaparan sumber daya.
| Tingkatan | Batas permintaan umum | Batas token umum |
|---|---|---|
| Tingkat gratis | 30 permintaan per menit | 6.000 token per menit untuk sebagian besar model |
| Tingkat developer | Hingga 1.000 permintaan per menit | Hingga kurang lebih 250.000 sampai 300.000 token per menit |
| Melebihi batas mana pun | Respons 429 Too Many Requests | Header retry-after menyatakan waktu tunggu dalam detik |
Sebelum memanggil Groq, tambahkan rate limiter per pengunjung berdasarkan alamat IP, dibatasi jauh di bawah batas paketmu. Batasi juga max_completion_tokens pada setiap permintaan; panjang completion yang tidak dibatasi adalah cara termudah membuat model yang murah per token menjadi tagihan mahal ketika pengguna (atau scraper) mengirim prompt yang sama berulang-ulang.
Checklist Produksi Sebelum Rilis
Chat streaming terlihat sederhana dalam demo tetapi bisa gagal dengan cara yang spesifik dan bisa diprediksi di produksi. Periksa daftar ini sebelum mengarahkan trafik sungguhan ke route handler, dan tinjau ulang setelah rilis begitu pola pemakaian nyata mulai terlihat di log-mu.
- Simpan API key hanya di sisi server, baca dari environment variable, dan jangan pernah dikembalikan dalam respons atau log yang terlihat klien.
- Terapkan rate limiting per pengunjung di servermu sendiri selain mengandalkan batas paket Groq, dan kembalikan nilai retry-after yang jelas ke klien.
- Batasi max_completion_tokens dan validasi panjang pesan masuk agar satu permintaan tidak bisa membengkakkan pemakaian token atau biaya.
- Tangani error stream dengan baik: jika pemanggilan Groq gagal di tengah stream, kirim event error yang bisa ditampilkan klien alih-alih membiarkan koneksi menggantung.
- Catat pemakaian token per permintaan (Groq mengembalikan data usage) sehingga kamu bisa menangkap lonjakan biaya akibat bug atau penyalahgunaan sebelum tagihan bulanan yang menyadarkanmu.

