Pengaturan

Bahasa

Membangun AI Chatbot dengan Satu API Key: Dari Nol hingga Produksi dalam 30 Menit

T
TokenLab
·26 Februari 2026·9 menit baca·Diperbarui 14 Juli 2026·1440 tampilan
#chatbot#tutorial#python#fastapi#streaming
Membangun AI Chatbot dengan Satu API Key: Dari Nol hingga Produksi dalam 30 Menit

Tutorial ini akan membangun layanan chatbot kecil namun siap produksi dengan FastAPI, streaming SSE, memori percakapan, dan pengalihan model. Tujuannya bukan sekadar demo mainan. Tujuannya adalah backend yang bisa Anda tempatkan di balik antarmuka produk nyata dan diiterasi dengan aman, tanpa harus menulis ulang integrasi setiap kali Anda mengganti model.

Jika Anda sudah mengarahkan satu SDK yang kompatibel dengan OpenAI ke TokenLab, artikel ini akan melanjutkan dari sana. Jika Anda belum melakukan pertukaran base URL, baca panduan migrasi terlebih dahulu. Jika kekhawatiran utama Anda adalah pembentukan request dan backoff saat beban tinggi, pasangkan panduan ini dengan panduan rate limiting AI API.

Poin Penting

  • Chatbot siap produksi membutuhkan enam bagian: endpoint sinkron, endpoint streaming, status percakapan sisi server, daftar izin (allowlist) model, penanganan error yang nyata, dan jalur peningkatan penyimpanan yang jelas.
  • Buktikan kunci, base URL, dan routing Anda dengan satu endpoint chat kecil sebelum menambahkan streaming, memori, atau pemanggilan tool.
  • Streaming SSE mencakup sebagian besar produk chat dan memiliki overhead operasional yang lebih kecil daripada websocket.
  • Ekspos model melalui daftar izin backend, bukan kolom teks bebas, agar frontend tidak dapat meminta ID model yang sembarangan atau sudah tidak berlaku.
  • Ketersediaan dan jajaran model sering berubah. Periksa direktori model TokenLab (diamati 07-07-2026) sebelum mengunci daftar izin Anda ke produksi.

Apa yang Kita Bangun

Layanan yang sudah jadi memiliki enam bagian yang saling bekerja:

  1. Endpoint /chat sinkron untuk pengujian dasar (smoke test).
  2. Endpoint /chat/stream streaming untuk UI nyata.
  3. Status percakapan yang dikunci berdasarkan conversation_id.
  4. Daftar izin model agar frontend tidak dapat meminta ID sembarangan.
  5. Penanganan error yang tidak langsung tumbang saat terjadi 429 pertama.
  6. Jalur yang jelas dari prototipe dalam memori ke Redis atau PostgreSQL.

Itu sudah cukup untuk mendukung bot dukungan, asisten internal, atau versi pertama dari widget chat tertanam.

Instal Stack Minimum

pip install fastapi uvicorn openai pydantic redis

Anda bisa melewati redis untuk percobaan pertama, tetapi menyertakan impornya sekarang akan membuat peningkatan di kemudian hari menjadi hal yang mudah, bukan sebuah refaktor besar.

Langkah 1: Mulai dengan Endpoint Chat yang Kecil dan Sederhana

Cara tercepat untuk tersesat dalam pengerjaan chatbot adalah memulai dengan websocket, penggunaan tool, dan orkestrasi agen sebelum jalur request dasar stabil. Mulailah dengan satu endpoint kecil yang membuktikan bahwa kunci, base URL, dan routing model Anda berfungsi.

from fastapi import FastAPI
from openai import OpenAI
from pydantic import BaseModel

app = FastAPI()

client = OpenAI(
    api_key="sk-tokenlab-xxx",
    base_url="https://api.tokenlab.sh/v1"
)

class ChatRequest(BaseModel):
    message: str
    model: str = "deepseek-v4-flash"
    conversation_id: str | None = None

@app.post("/chat")
async def chat(req: ChatRequest):
    response = client.chat.completions.create(
        model=req.model,
        messages=[{"role": "user", "content": req.message}]
    )
    return {"reply": response.choices[0].message.content}

Jalankan satu smoke test. Jika ini gagal, jangan terus membangun di atasnya.

Langkah 2: Tambahkan Streaming Karena Pengguna Merasakan Latensi Sebelum Mereka Mengukurnya

Kebanyakan produk chatbot terasa lambat bukan karena modelnya lambat, tetapi karena UI tetap kosong sampai respons penuh muncul. SSE sudah cukup untuk sebagian besar produk chat dan memiliki beban operasional yang lebih rendah daripada websocket.

from fastapi.responses import StreamingResponse

@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
    def generate():
        stream = client.chat.completions.create(
            model=req.model,
            messages=[{"role": "user", "content": req.message}],
            stream=True
        )
        for chunk in stream:
            delta = chunk.choices[0].delta
            if delta.content:
                yield f"data: {delta.content}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

Di sisi frontend, pembaca fetch standar masih cukup:

async function sendMessage(payload) {
  const response = await fetch('/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const chunk = decoder.decode(value, { stream: true });
    console.log(chunk);
  }
}

Jika produk Anda sudah menjalankan klien browser melalui HTTP standar, SSE menjaga arsitektur tetap lebih sederhana daripada lapisan websocket.

Langkah 3: Pindahkan Status Percakapan Keluar dari Body Request

Demo chatbot pertama biasanya menyimpan transkrip lengkap di browser dan mengirimnya kembali setiap kali ada giliran bicara. Itu berhasil untuk prototipe. Namun, akan menjadi berantakan dengan cepat begitu Anda membutuhkan percobaan ulang (retry), sesi yang dapat dilanjutkan, atau alat sisi server.

Penyimpanan dalam memori sudah cukup untuk memulai:

from collections import defaultdict
import uuid

conversations: dict[str, list] = defaultdict(list)
SYSTEM_PROMPT = "You are a helpful assistant. Be concise and direct."

def build_messages(conv_id: str, user_msg: str) -> list:
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    history = conversations[conv_id][-20:]
    messages.extend(history)
    messages.append({"role": "user", "content": user_msg})
    conversations[conv_id].append({"role": "user", "content": user_msg})
    return messages

Jalur peningkatan ke Redis sebagian besar adalah masalah teknis penyimpanan, bukan perubahan logika:

import json
import redis

redis_client = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)

def load_history(conv_id: str) -> list:
    raw = redis_client.get(f"chat:{conv_id}")
    return json.loads(raw) if raw else []

def save_history(conv_id: str, history: list) -> None:
    redis_client.setex(f"chat:{conv_id}", 60 * 60 * 24, json.dumps(history))

Gunakan Redis saat percakapan membutuhkan TTL, kemampuan untuk dilanjutkan, atau deployment multi-instance. Gunakan PostgreSQL saat transkrip itu sendiri adalah data produk yang perlu Anda kueri, audit, atau laporkan nanti.

Langkah 4: Perlakukan Error sebagai Perilaku Produk, Bukan Sekadar Pengecualian

Jika chatbot Anda berhadapan dengan pelanggan, jalur kegagalan sama pentingnya dengan jalur keberhasilan. Pengguna tidak peduli apakah kegagalan berasal dari rate limiting, saldo habis, atau pemadaman model di sisi upstream. Mereka hanya peduli apakah UI membeku.

from openai import APIConnectionError, APIError, RateLimitError

@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
    conv_id = req.conversation_id or str(uuid.uuid4())
    messages = build_messages(conv_id, req.message)

    def generate():
        full_response = []
        try:
            stream = client.chat.completions.create(
                model=req.model,
                messages=messages,
                stream=True
            )
            for chunk in stream:
                delta = chunk.choices[0].delta
                if delta.content:
                    full_response.append(delta.content)
                    yield f"data: {delta.content}\n\n"
        except RateLimitError:
            yield "data: [error: rate limited, please retry shortly]\n\n"
        except APIConnectionError:
            yield "data: [error: connection issue, please retry]\n\n"
        except APIError:
            yield "data: [error: something went wrong on our end]\n\n"
        finally:
            if full_response:
                conversations[conv_id].append(
                    {"role": "assistant", "content": "".join(full_response)}
                )
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

Langkah 5: Kunci Model yang Dapat Diminta oleh Frontend

Jangan pernah membiarkan frontend mengirim string model sembarangan langsung ke API. Kolom teks bebas mengundang permintaan untuk model yang sudah pensiun, salah ketik, atau model yang tidak pernah Anda niatkan untuk diekspos. Gunakan daftar izin backend sebagai gantinya.

ALLOWED_MODELS = {
    "default": "deepseek-v4-flash",
    "flagship": "gpt-5.5",
    "balanced": "claude-sonnet-5",
}

class ChatRequest(BaseModel):
    message: str
    tier: str = "default"
    conversation_id: str | None = None

@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
    model = ALLOWED_MODELS.get(req.tier, ALLOWED_MODELS["default"])
    # sisa logika streaming menggunakan `model` alih-alih string mentah dari klien

Ini memberi Anda satu tempat untuk mengganti model saat penyedia menghentikan dukungan untuk salah satu model, tanpa menyentuh kode frontend atau mengirim pembaruan klien.

Langkah 6: Tangani Sisa Produksi, Bukan Hanya Jalur Keberhasilan

Backend chatbot dianggap siap produksi ketika sisi-sisi luarnya tertangani, bukan saat panggilan chat intinya menjadi canggih.

Daftar periksanya singkat:

  • tambahkan ID request agar Anda dapat menghubungkan kegagalan frontend ke log backend
  • batasi konkurensi per pengguna dan ukuran request
  • pangkas riwayat panjang sebelum menghabiskan anggaran token Anda
  • catat model, latensi, ukuran input, dan alasan penyelesaian
  • pisahkan pesan error yang terlihat oleh pengguna dari detail error internal
  • uji satu model alternatif agar Anda tahu fallback berfungsi sebelum pemadaman nyata pertama terjadi

Pemangkasan riwayat bisa tetap sederhana:

def trim_history(messages: list, max_tokens: int = 8000) -> list:
    system = messages[0]
    history = messages[1:]
    total_chars = len(system["content"])
    trimmed = []

    for msg in reversed(history):
        msg_chars = len(msg["content"])
        if total_chars + msg_chars > max_tokens * 4:
            break
        trimmed.insert(0, msg)
        total_chars += msg_chars

    return [system] + trimmed

Intinya bukan akuntansi token yang sempurna. Intinya adalah menghentikan ledakan konteks yang jelas sebelum memengaruhi tagihan atau latensi Anda.

Dari Demo ke Produk

Setelah backend ini stabil, peningkatan berikutnya jarang berupa "lebih banyak AI." Biasanya itu adalah infrastruktur yang membosankan:

  • autentikasi agar satu pengguna tidak dapat membaca percakapan pengguna lain
  • persistensi agar sesi tetap ada setelah deployment
  • rate limiting agar satu pengguna yang berisik tidak menghabiskan kuota Anda
  • penagihan atau atribusi penggunaan jika chatbot berhadapan dengan pelanggan
  • ringkasan latar belakang jika percakapan membutuhkan memori jangka panjang

Gateway terpadu membantu sebagian besar hal ini. Setelah migrasi base URL selesai, perubahan model berhenti menjadi penulisan ulang platform dan menjadi sekadar edit konfigurasi.

Smoke Test

uvicorn main:app --reload --port 8000

curl -N -X POST http://localhost:8000/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!", "model": "deepseek-v4-flash"}'

Jika Anda dapat melakukan streaming satu giliran, menyimpan satu percakapan, dan mengembalikan error yang bersih pada kegagalan paksa, Anda memiliki fondasi yang tepat.

Estimasi Biaya

Buat API key di TokenLab, arahkan SDK OpenAI Anda ke https://api.tokenlab.sh/v1, dan Anda dapat mengirimkan versi produksi pertama chatbot Anda tanpa mengelola akun terpisah di berbagai penyedia.

Model Tier Umum Catatan
DeepSeek V4 Flash Cepat / default Default yang baik untuk volume tinggi, giliran latensi rendah
GPT-5.5 Flagship Gunakan untuk giliran yang membutuhkan penalaran lebih kuat
Claude Sonnet 5 Seimbang Pilihan kuat untuk coding dan balasan gaya tinjauan
Gemini 3.5 Flash Budget / alt cepat Alternatif berbiaya rendah dan cepat untuk routing volume tinggi

Harga per-token yang tepat sering berubah di seluruh penyedia dan tidak direproduksi di sini sebagai angka tetap. Periksa tarif saat ini di direktori model (diamati 07-07-2026) sebelum Anda membuat anggaran. Dalam praktiknya, mengarahkan sebagian besar percakapan ke tier cepat/default seperti DeepSeek V4 Flash atau Gemini 3.5 Flash dan mencadangkan GPT-5.5 atau Claude Sonnet 5 untuk giliran yang membutuhkannya menjaga sebagian besar aplikasi tetap pada tagihan bulanan yang rendah, tetapi konfirmasikan tarif per-juta-token yang sebenarnya untuk akun Anda sebelum berkomitmen pada anggaran.

FAQ

Apakah saya memerlukan websocket untuk membangun chatbot AI? Tidak. Streaming SSE, yang ditunjukkan pada Langkah 2, mencakup sebagian besar produk chat. Websocket memberikan nilai nyata saat Anda memerlukan push dua arah di luar request/response, seperti kolaborasi langsung atau event yang dimulai oleh server. Untuk UI chat standar, SSE lebih sederhana untuk dideploy, di-debug, dan diskalakan.

Bagaimana saya tahu model mana yang harus dijadikan default? Mulailah dengan model yang cepat dan berbiaya rendah seperti DeepSeek V4 Flash atau Gemini 3.5 Flash untuk tier default, dan tambahkan tier seimbang atau penalaran pada Claude Sonnet 5 atau GPT-5.5 di balik daftar izin yang ditunjukkan pada Langkah 5. Periksa direktori model (diamati 07-07-2026) untuk opsi saat ini, karena model baru dirilis dan yang lama tidak lagi didukung sesuai jadwal di luar kendali Anda.

Apa yang rusak pertama kali saat chatbot beralih dari demo ke lalu lintas nyata? Hampir selalu jalur error, bukan jalur keberhasilan. Percobaan ulang tanpa batas, kurangnya batas konkurensi per pengguna, dan riwayat percakapan tanpa batas adalah tiga penyebab paling umum backend chatbot tumbang di bawah beban nyata. Langkah 4 dan 6 di atas membahas ketiganya secara langsung.


Mulai dengan API key Anda: tokenlab.sh menyediakan 300+ model melalui satu endpoint. Kredit gratis $1 untuk mulai membangun.

Sumber

Harga diamati pada 2026-07-07

Bagikan:

Model terkait

Model publik terbaru

Bangun dengan model dalam panduan ini

Bandingkan harga, uji rute, dan ubah riset menjadi panggilan API yang berjalan.