Banyak yang mengira memakai AI di Laravel butuh paket khusus. Kenyataannya cukup satu HTTP client bawaan Laravel. API-nya kompatibel OpenAI, jadi semua contoh di artikel ini berlaku untuk provider mana pun yang memakai format yang sama — termasuk gateway ADVANCE.
1. Siapkan kredensial
API key dan base URL tidak boleh ditulis di kode. Simpan di .env:
# .env
OPENAI_BASE_URL=https://api.advance.my.id/api/openai/v1
OPENAI_API_KEY=sk-adv-xxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-5.6Lalu di config/services.php:
'ai' => [
'base_url' => env('OPENAI_BASE_URL'),
'key' => env('OPENAI_API_KEY'),
'model' => env('OPENAI_MODEL', 'gpt-5.6'),
],Jangan pernah commit .env. Kalau key sudah terlanjur masuk repo publik, anggap bocor dan buat key baru.2. Buat service class
Satu class kecil membuat pemanggilan konsisten di seluruh aplikasi:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class AiService
{
public function chat(array $messages, ?string $model = null): array
{
$res = Http::withToken(config('services.ai.key'))
->timeout(60)
->post(config('services.ai.base_url') . '/chat/completions', [
'model' => $model ?? config('services.ai.model'),
'messages' => $messages,
]);
if ($res->failed()) {
// sertakan pesan asli provider supaya tidak buta saat debug
throw new \RuntimeException(
'AI error ' . $res->status() . ': ' . $res->body()
);
}
return $res->json();
}
}3. Pakai di controller
public function ringkas(Request $request, AiService $ai)
{
$request->validate(['teks' => 'required|string|max:20000']);
$data = $ai->chat([
['role' => 'system', 'content' => 'Ringkas dalam 5 poin, maksimal 20 kata per poin.'],
['role' => 'user', 'content' => $request->teks],
]);
return response()->json([
'hasil' => $data['choices'][0]['message']['content'],
'token' => $data['usage'] ?? null,
]);
}Bagian usage selalu ada di respons dan berisi prompt_tokens, completion_tokens, total_tokens. Simpan angka ini — inilah dasar hitungan biaya.
4. Streaming supaya tidak terasa lambat
Jawaban panjang terasa lambat kalau ditunggu utuh. Dengan stream: true, teks muncul kata demi kata:
return response()->stream(function () use ($messages) {
$body = Http::withToken(config('services.ai.key'))
->withOptions(['stream' => true])
->post(config('services.ai.base_url') . '/chat/completions', [
'model' => config('services.ai.model'),
'messages' => $messages,
'stream' => true,
])->toPsrResponse()->getBody();
while (! $body->eof()) {
echo $body->read(1024);
flush();
}
}, 200, ['Content-Type' => 'text/event-stream']);Formatnya SSE: tiap potongan berawalan data: dan diakhiri [DONE]. Di sisi browser, baca dengan EventSource atau fetch + ReadableStream.
5. Masalah yang paling sering muncul
| Gejala | Penyebab | Solusi |
|---|---|---|
401 Unauthorized | key salah atau ada spasi | trim key, cek prefix sk- |
404 Not Found | base URL kurang /v1 | pastikan berakhir /api/openai/v1 |
429 | kuota habis atau salah menembak endpoint | baca pesan error asli, cek sisa kuota |
| Jawaban kosong | max_tokens terlalu kecil | naikkan, cek finish_reason |
6. Cek sisa kuota dari terminal
Kalau gateway-mu menyediakan endpoint saldo, cek tanpa membuka dashboard:
curl -s https://api.advance.my.id/api/balance \
-H "Authorization: Bearer $OPENAI_API_KEY"Responsnya berisi kuota terpakai dan sisa. Pasang di cron malam supaya kamu dapat notifikasi email sebelum kuota habis — jauh lebih tenang daripada menebak.
Ringkas
- Satu
Http::withToken()cukup, tidak perlu paket tambahan. - Simpan base URL + key di
.env, jangan di kode. - Selalu baca
usagedan simpan untuk perhitungan biaya. - Streaming mengubah pengalaman dari "menunggu" jadi "mengetik".
