Kembali ke Blog
Tutorial11 menit baca

Tutorial Integrasi AI API ke Laravel (PHP) — OpenAI Compatible

Panduan step-by-step integrasi GPT-5/Claude/Gemini ke aplikasi Laravel dengan client OpenAI PHP, queue async, error handling, rate limiting. Production-ready code lengkap.

Stack yang Dipakai

  • Laravel 11+
  • openai-php/client v0.10+ (kompatibel dengan OpenAI-compatible gateway)
  • Laravel Queue (Redis driver)
  • ExtPeak sebagai gateway (1 endpoint untuk GPT-5/Claude/Gemini)

Step 1: Install Package

composer require openai-php/client

Step 2: Config

Tambah ke .env:

EXTPEAK_API_KEY=snfx-xxxx
EXTPEAK_BASE_URL=https://extpeak.com/v1
EXTPEAK_DEFAULT_MODEL=anthropic/claude-sonnet-4.6

config/services.php:

'extpeak' => [
    'api_key' => env('EXTPEAK_API_KEY'),
    'base_url' => env('EXTPEAK_BASE_URL', 'https://extpeak.com/v1'),
    'model' => env('EXTPEAK_DEFAULT_MODEL', 'anthropic/claude-sonnet-4.6'),
],

Step 3: Service Class

app/Services/AIService.php:

<?php

namespace App\Services;

use OpenAI;
use OpenAI\Client;
use Illuminate\Support\Facades\Log;

class AIService
{
    protected Client $client;
    protected string $model;

    public function __construct()
    {
        $this->client = OpenAI::factory()
            ->withApiKey(config('services.extpeak.api_key'))
            ->withBaseUri(config('services.extpeak.base_url'))
            ->make();

        $this->model = config('services.extpeak.model');
    }

    public function chat(array $messages, ?string $model = null, array $opts = []): string
    {
        try {
            $response = $this->client->chat()->create([
                'model' => $model ?? $this->model,
                'messages' => $messages,
                'temperature' => $opts['temperature'] ?? 0.7,
                'max_tokens' => $opts['max_tokens'] ?? 2048,
            ]);

            return $response->choices[0]->message->content;
        } catch (\Exception $e) {
            Log::error('AI chat failed', ['error' => $e->getMessage()]);
            throw $e;
        }
    }

    public function stream(array $messages, callable $onChunk, ?string $model = null): void
    {
        $stream = $this->client->chat()->createStreamed([
            'model' => $model ?? $this->model,
            'messages' => $messages,
        ]);

        foreach ($stream as $response) {
            $delta = $response->choices[0]->delta->content ?? '';
            if ($delta !== '') {
                $onChunk($delta);
            }
        }
    }
}

Step 4: Queue Job (untuk Long-Running)

php artisan make:job ProcessAIRequest

app/Jobs/ProcessAIRequest.php:

<?php

namespace App\Jobs;

use App\Services\AIService;
use App\Models\AIConversation;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessAIRequest implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 5;

    public function __construct(public int $conversationId, public string $userMessage) {}

    public function handle(AIService $ai): void
    {
        $conv = AIConversation::findOrFail($this->conversationId);

        $messages = [
            ['role' => 'system', 'content' => 'You are a helpful assistant for an Indonesian fintech app.'],
            ...$conv->messages()->get()->map(fn($m) => ['role' => $m->role, 'content' => $m->content])->toArray(),
            ['role' => 'user', 'content' => $this->userMessage],
        ];

        $reply = $ai->chat($messages);

        $conv->messages()->create(['role' => 'user', 'content' => $this->userMessage]);
        $conv->messages()->create(['role' => 'assistant', 'content' => $reply]);
    }
}

Step 5: Controller

public function ask(Request $request)
{
    $request->validate([
        'message' => 'required|string|max:4000',
        'conversation_id' => 'required|exists:ai_conversations,id',
    ]);

    ProcessAIRequest::dispatch($request->conversation_id, $request->message);

    return response()->json(['status' => 'queued']);
}

Step 6: Streaming via SSE (Real-Time Chat UI)

public function stream(Request $request)
{
    $messages = $request->input('messages');

    return response()->stream(function () use ($messages) {
        app(AIService::class)->stream($messages, function ($chunk) {
            echo "data: " . json_encode(['delta' => $chunk]) . "\n\n";
            ob_flush();
            flush();
        });
        echo "data: [DONE]\n\n";
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'X-Accel-Buffering' => 'no',
    ]);
}

Step 7: Rate Limiting

routes/web.php:

Route::middleware(['auth', 'throttle:60,1'])->group(function () {
    Route::post('/api/chat', [ChatController::class, 'ask']);
    Route::post('/api/chat/stream', [ChatController::class, 'stream']);
});

Cost Estimation

Dengan Claude Sonnet 4.6 dan rata-rata 500 input + 300 output token per request:

  • Cost per request: $0.00255 (~Rp 40)
  • 1000 chat/hari: Rp 40,000
  • 30 hari: Rp 1,200,000

Switch ke GPT-5 Mini untuk turunkan ke Rp 6/request → Rp 180,000/bulan.

Deploy Checklist

  1. EXTPEAK_API_KEY di production env (jangan commit ke git)
  2. ✅ Queue worker running: php artisan queue:work --queue=default
  3. ✅ Redis driver untuk queue dan cache
  4. ✅ Error logging ke Sentry/Bugsnag
  5. ✅ Rate limit di nginx layer juga (defense-in-depth)
  6. ✅ Monitoring usage via ExtPeak dashboard

Generate API key di ExtPeak →

Mulai pakai ExtPeak gratis

Akses 21+ model AI premium dengan harga termurah. OpenAI-compatible. Bayar pakai QRIS.

Daftar gratis