AI Gateway Pribadi: Satu Endpoint untuk Banyak Model AI


M

asalah yang Diselesaikan Gateway

Begitu aplikasi Anda memakai lebih dari satu penyedia model AI, konfigurasi mulai menyebar. Setiap aplikasi menyimpan base URL dan API key masing-masing penyedia, dan setiap kali ada model yang diganti atau key yang dirotasi, semua aplikasi harus diubah satu per satu. Belum lagi soal pencatatan: tanpa titik pusat, Anda tidak tahu aplikasi mana yang paling banyak menghabiskan kuota.

Gateway menyelesaikan ini dengan satu pola sederhana: aplikasi hanya mengenal satu endpoint, yaitu gateway Anda. Gateway yang memutuskan request diteruskan ke penyedia mana, berdasarkan nama model yang diminta. Aplikasi tidak pernah memegang key penyedia secara langsung.

Arsitektur Singkatnya

Alurnya begini:

Aplikasi Anda → https://gateway-domain-anda/v1 → Gateway → Penyedia A / Penyedia B / Penyedia C

Gateway melakukan empat hal:

  1. Autentikasi — memeriksa API key yang dikirim aplikasi.
  2. Routing — memetakan nama model ke upstream yang tepat.
  3. Forwarding — meneruskan request ke upstream dan mengembalikan responsnya.
  4. Logging — mencatat siapa memanggil model apa, kapan, dan berapa token yang dipakai (bila upstream mengembalikan angka pemakaian).

Satu catatan penting sejak awal: gateway ini bukan penambah kecerdasan. Ia tidak membuat model menjadi lebih baik. Nilainya ada di keterpusatan: konfigurasi, rotasi key, dan audit pemakaian.

Persiapan Server

Semua perintah di artikel ini untuk Ubuntu 24.04. Siapkan VPS dengan Python 3.12 (sudah bawaan Ubuntu 24.04) dan buat direktori kerja:

sudo apt update && sudo apt install -y python3-venv
mkdir -p ~/ai-gateway && cd ~/ai-gateway
python3 -m venv .venv
source .venv/bin/activate
pip install flask requests gunicorn

Buat file environment untuk key upstream. File ini hanya dibaca server, tidak pernah dikirim ke klien dan tidak pernah masuk ke repository:

cat > .env <<'EOF'
UPSTREAM_A_BASE_URL=https://api.penyedia-a.example/v1
UPSTREAM_A_KEY=key-penyedia-a-anda
UPSTREAM_B_BASE_URL=https://api.penyedia-b.example/v1
UPSTREAM_B_KEY=key-penyedia-b-anda
GATEWAY_KEYS=key-aplikasi-1,key-aplikasi-2
EOF
chmod 600 .env

Ganti semua nilai contoh di atas dengan nilai asli Anda. GATEWAY_KEYS adalah daftar key yang boleh dipakai aplikasi Anda untuk memanggil gateway, berbeda dari key upstream.

Tabel Routing

Inti gateway adalah tabel routing: nama model yang diminta aplikasi dipetakan ke upstream. Misalnya model model-cepat diarahkan ke penyedia A, sedangkan model-pintar ke penyedia B. Nama model di sini bebas Anda tentukan; yang penting konsisten antara aplikasi dan gateway.

Buat file gateway.py:

import json
import os
import time
from datetime import datetime, timezone

import requests
from flask import Flask, request, Response

app = Flask(__name__)

def load_env(path=".env"):
    values = {}
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#") and "=" in line:
                key, val = line.split("=", 1)
                values[key] = val
    return values

ENV = load_env()
GATEWAY_KEYS = set(ENV.get("GATEWAY_KEYS", "").split(","))

# Nama model -> (base URL upstream, key upstream)
ROUTES = {
    "model-cepat": (ENV["UPSTREAM_A_BASE_URL"], ENV["UPSTREAM_A_KEY"]),
    "model-pintar": (ENV["UPSTREAM_B_BASE_URL"], ENV["UPSTREAM_B_KEY"]),
}

@app.route("/v1/chat/completions", methods=["POST"])
def chat_completions():
    auth = request.headers.get("Authorization", "")
    key = auth.replace("Bearer ", "", 1)
    if key not in GATEWAY_KEYS:
        return {"error": "API key gateway tidak valid"}, 401

    body = request.get_json(force=True, silent=True) or {}
    model = body.get("model", "")
    if model not in ROUTES:
        return {"error": f"Model tidak dikenal: {model}"}, 404

    base_url, upstream_key = ROUTES[model]
    started = time.time()
    upstream = requests.post(
        f"{base_url}/chat/completions",
        json=body,
        headers={
            "Authorization": f"Bearer {upstream_key}",
            "Content-Type": "application/json",
        },
        timeout=120,
    )

    # Catat pemakaian ke file log (satu baris JSON per request)
    try:
        usage = upstream.json().get("usage", {})
    except ValueError:
        usage = {}
    with open("usage.log", "a") as f:
        f.write(json.dumps({
            "waktu": datetime.now(timezone.utc).isoformat(),
            "key": key[:8] + "...",
            "model": model,
            "status": upstream.status_code,
            "durasi_detik": round(time.time() - started, 2),
            "usage": usage,
        }) + "\n")

    return Response(
        upstream.content,
        status=upstream.status_code,
        content_type=upstream.headers.get("Content-Type", "application/json"),
    )

@app.route("/v1/models", methods=["GET"])
def models():
    return {"data": [{"id": m} for m in sorted(ROUTES)]}

Beberapa keputusan di kode ini layak dicatat. Pertama, key upstream tidak pernah dikembalikan ke klien; yang kembali hanya respons upstream. Kedua, log hanya menyimpan delapan karakter pertama key gateway, cukup untuk identifikasi tanpa membocorkan key utuh. Ketiga, endpoint /v1/models sengaja dibuat agar aplikasi bisa mengecek daftar model yang tersedia lewat gateway yang sama.

Dari direktori ~/ai-gateway, jalankan gateway dengan Gunicorn:

./.venv/bin/gunicorn --bind 127.0.0.1:8080 gateway:app

Gateway hanya mendengarkan di localhost. Akses dari internet diatur lewat reverse proxy di langkah berikutnya, bukan dengan membuka port aplikasi langsung.

Menjalankannya sebagai Service

Agar gateway hidup terus dan restart otomatis saat server reboot, buat unit systemd:

sudo tee /etc/systemd/system/ai-gateway.service > /dev/null <<'EOF'
[Unit]
Description=AI Gateway Pribadi
After=network-online.target
Wants=network-online.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/ai-gateway
ExecStart=/home/ubuntu/ai-gateway/.venv/bin/gunicorn --bind 127.0.0.1:8080 gateway:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now ai-gateway
sudo systemctl status ai-gateway --no-pager

Sesuaikan User dan WorkingDirectory dengan user dan lokasi instalasi Anda. Jika status menunjukkan active (running), gateway sudah jalan.

Membuka ke Internet dengan Aman

Pasang Nginx sebagai reverse proxy dan amankan dengan HTTPS. Tanpa HTTPS, API key akan terkirim dalam bentuk yang bisa dibaca siapa pun di jalur jaringan.

sudo apt install -y nginx certbot python3-certbot-nginx
sudo tee /etc/nginx/sites-available/ai-gateway > /dev/null <<'EOF'
server {
    listen 80;
    server_name gateway-domain-anda;

    location /v1/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_read_timeout 150;
    }
}
EOF
sudo ln -s /etc/nginx/sites-available/ai-gateway /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d gateway-domain-anda

Setelah sertifikat aktif, uji dari mesin lain:

curl https://gateway-domain-anda/v1/models
curl https://gateway-domain-anda/v1/chat/completions \
  -H "Authorization: Bearer key-aplikasi-1" \
  -H "Content-Type: application/json" \
  -d '{"model": "model-cepat", "messages": [{"role": "user", "content": "Halo"}]}'

Jika respons pertama menampilkan daftar model dan yang kedua mengembalikan jawaban model, gateway Anda sudah berfungsi penuh.

Hal yang Sering Terlewat

Rotasi key jadi murah. Saat key upstream bocor atau kedaluwarsa, Anda hanya mengubah satu baris di .env lalu me-restart service dengan sudo systemctl restart ai-gateway. Aplikasi tidak perlu diubah sama sekali, karena mereka hanya memegang key gateway.

Satu model, satu upstream — tapi siapkan jalan keluar. Bila satu penyedia sedang gangguan, gateway minimal seperti di atas akan ikut gagal. Untuk kebutuhan yang lebih serius, tambahkan daftar upstream cadangan per model dan coba satu per satu saat upstream utama mengembalikan error 5xx. Jangan menambahkan kerumitan ini sebelum Anda benar-benar membutuhkannya.

Streaming butuh penanganan khusus. Contoh di atas meneruskan respons setelah upstream selesai. Bila aplikasi Anda memakai respons streaming, Anda perlu meneruskan potongan respons begitu diterima, bukan menunggu selesai. Itu satu langkah lanjutan yang layak ditambahkan setelah versi dasar ini stabil.

Log adalah data sensitif. File usage.log berisi potongan key dan pola pemakaian. Batasi izinnya (chmod 600 usage.log), rotasi secara berkala, dan jangan pernah mengunggahnya ke repository atau membagikannya mentah-mentah.

Jangan mencatat isi percakapan. Contoh ini hanya mencatat model, status, durasi, dan angka pemakaian. Jangan menambahkan isi pesan pengguna ke log kecuali Anda punya alasan kuat dan dasar hukum yang jelas untuk menyimpannya.

Checklist Penutup

  • Gateway hanya mendengarkan di localhost, akses luar lewat Nginx + HTTPS
  • Key upstream tersimpan di .env berizin 600, tidak ada di repository
  • Key gateway berbeda dari key upstream, dan bisa dirotasi tanpa mengubah aplikasi
  • Tabel routing memetakan setiap nama model ke tepat satu upstream
  • Service systemd aktif dan restart otomatis setelah reboot
  • Endpoint /v1/models dan satu panggilan chat berhasil dari luar server
  • File log pemakaian dicek berkala dan izinnya dibatasi

Dengan fondasi ini, menambah model baru berarti menambah satu baris di tabel routing, dan menambah aplikasi baru berarti membagikan satu key gateway — bukan satu set kredensial penyedia.

Komentar