Laravel’de OpenAI API Bağlantısı: Eksiksiz Rehber
Laravel’de OpenAI API bağlantısını API key, config, service class, controller, Blade form, Responses API, JSON çıktı, hata yönetimi, rate limit, queue ve test örnekleriyle anlatıyorum.
Laravel’de OpenAI API bağlantısı kurmak, sadece controller içine bir HTTP isteği yazmak değildir. API key güvenliği, `.env` ayarı, config dosyası, servis sınıfı, controller, validation, Blade form, hata yönetimi, rate limit, loglama, queue ve test akışı birlikte düşünülmelidir.
Bu yazıda Laravel ile OpenAI API entegrasyonunu adım adım anlatacağım. Örnekleri Laravel HTTP Client üzerinden yapacağız. Çünkü bu yöntem Laravel’in kendi yapısıyla uyumlu, sade ve paket bağımlılığı olmadan anlaşılırdır. Ana örnekte OpenAI Responses API kullanacağız.
OpenAI API ile Laravel’de Ne Yapabiliriz?
OpenAI API ile Laravel projelerinde birçok özellik geliştirilebilir:
- Blog başlığı, özet ve SEO açıklaması üretme
- Ürün açıklaması yazdırma
- Müşteri mesajlarını sınıflandırma
- Destek talebine otomatik cevap taslağı üretme
- Metin özetleme ve çeviri
- Form verisinden JSON çıktısı oluşturma
- Panel içinde içerik asistanı hazırlama
Burada amaç yapay zekayı kontrolsüz şekilde her yere koymak değil. Temiz bir servis katmanı kurup Laravel projesine yönetilebilir bir AI özelliği eklemek.
1. OpenAI API Key Alma
Önce OpenAI platform hesabından API key oluşturman gerekir:
https://platform.openai.com/api-keys
API key oluşturduktan sonra bunu asla GitHub’a göndermemelisin. Blade dosyasına, JavaScript içine veya public config’e yazmak çok tehlikelidir. Key sadece backend tarafında `.env` içinde durmalı.
2. Laravel .env Ayarı
Laravel projesinde `.env` dosyasına şu değerleri ekle:
OPENAI_API_KEY=sk-proj_xxxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-5.5
OPENAI_BASE_URL=https://api.openai.com/v1
Model adını config üzerinden yönetmek iyi olur. Böylece ileride modeli değiştirmek için kodu kurcalamazsın.
Config cache kullanıyorsan temizle:
php artisan config:clear
3. config/services.php Ayarı
`config/services.php` içine OpenAI ayarlarını ekleyelim:
'openai' => [
'key' => env('OPENAI_API_KEY'),
'model' => env('OPENAI_MODEL', 'gpt-5.5'),
'base_url' => env('OPENAI_BASE_URL', 'https://api.openai.com/v1'),
],
Bu şekilde uygulama içinde `config('services.openai.key')` ile güvenli şekilde backend tarafında key’e ulaşabiliriz.
4. Laravel HTTP Client Kontrolü
Laravel’in HTTP Client özelliği zaten framework içinde gelir. Kullanımı için şu facade yeterli:
use Illuminate\Support\Facades\Http;
Bu rehberde ayrıca bir PHP SDK kurmuyoruz. İstersen ileride özel SDK kullanabilirsin ama başlangıç ve öğretici anlatım için Laravel HTTP Client daha şeffaf olur.
5. OpenAI Service Class Oluşturma
Controller içine API isteği yazmak hızlı görünür ama proje büyüyünce dağılır. Bunun yerine servis sınıfı oluşturalım:
mkdir -p app/Services
touch app/Services/OpenAiService.php
`app/Services/OpenAiService.php`:
<?php
namespace App\Services;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class OpenAiService
{
public function createText(string $prompt, ?string $systemMessage = null): string
{
$input = [];
if ($systemMessage) {
$input[] = [
'role' => 'system',
'content' => $systemMessage,
];
}
$input[] = [
'role' => 'user',
'content' => $prompt,
];
try {
$response = Http::timeout(60)
->retry(2, 500)
->withToken(config('services.openai.key'))
->acceptJson()
->post(config('services.openai.base_url').'/responses', [
'model' => config('services.openai.model'),
'input' => $input,
])
->throw()
->json();
return data_get($response, 'output_text', '');
} catch (RequestException $exception) {
Log::warning('OpenAI API isteği başarısız oldu.', [
'status' => optional($exception->response)->status(),
'body' => optional($exception->response)->body(),
]);
throw $exception;
}
}
}
Bu sınıfta birkaç önemli nokta var:
- `withToken()` Authorization header’ını otomatik ekler.
- `timeout(60)` isteğin sonsuza kadar beklemesini engeller.
- `retry(2, 500)` geçici hatalarda tekrar dener.
- `throw()` 4xx/5xx cevaplarda exception fırlatır.
- `output_text` modelin düz metin cevabını pratik şekilde almaya yarar.
6. İlk Test İçin Route Yazma
Hızlı deneme için `routes/web.php` içine geçici bir route ekleyebilirsin:
use App\Services\OpenAiService;
use Illuminate\Support\Facades\Route;
Route::get('/openai-test', function (OpenAiService $openAi) {
$answer = $openAi->createText(
prompt: 'Laravel için 3 maddelik kısa SEO önerisi yaz.',
systemMessage: 'Kısa, net ve Türkçe cevap ver.'
);
return nl2br(e($answer));
});
Sunucuyu çalıştır:
php artisan serve
Tarayıcıda aç:
http://127.0.0.1:8000/openai-test
Cevap geliyorsa API bağlantısı çalışıyor demektir. Bu route test bittikten sonra kaldırılmalı; canlı projede böyle açık test endpoint’i bırakmak doğru değildir.
7. Controller Oluşturma
Şimdi daha gerçek bir örnek yapalım. Kullanıcı bir konu yazsın, OpenAI ona blog başlığı ve özet üretsin.
php artisan make:controller AiContentController
`routes/web.php`:
use App\Http\Controllers\AiContentController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'throttle:10,1'])->group(function () {
Route::get('/ai/icerik', [AiContentController::class, 'create'])
->name('ai-content.create');
Route::post('/ai/icerik', [AiContentController::class, 'store'])
->name('ai-content.store');
});
`throttle:10,1` aynı kullanıcı/IP için bir dakikada çok fazla istek atılmasını sınırlar. OpenAI API ücretli olabileceği için rate limit çok önemlidir.
8. Controller Kodları
`app/Http/Controllers/AiContentController.php`:
<?php
namespace App\Http\Controllers;
use App\Services\OpenAiService;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Request;
class AiContentController extends Controller
{
public function create()
{
return view('ai-content.create');
}
public function store(Request $request, OpenAiService $openAi)
{
$validated = $request->validate([
'topic' => ['required', 'string', 'min:5', 'max:180'],
'tone' => ['required', 'in:sade,teknik,satis'],
]);
$prompt = sprintf(
'Konu: %s. Bu konu için 5 blog başlığı ve her başlık için 1 cümlelik özet üret. Ton: %s.',
$validated['topic'],
$validated['tone']
);
try {
$result = $openAi->createText(
prompt: $prompt,
systemMessage: 'Sen Laravel projelerinde çalışan Türkçe içerik asistanısın. Net ve düzenli cevap ver.'
);
} catch (RequestException) {
return back()
->withInput()
->withErrors([
'topic' => 'OpenAI servisine şu an ulaşılamadı. Lütfen biraz sonra tekrar deneyin.',
]);
}
return back()
->withInput()
->with('ai_result', $result);
}
}
Burada controller sadece request doğruluyor, prompt hazırlıyor ve servisi çağırıyor. API bağlantı detayları controller içinde dağılmıyor.
9. Blade Formu
`resources/views/ai-content/create.blade.php`:
@extends('layouts.app')
@section('title', 'AI İçerik Asistanı')
@section('content')
<h1>AI İçerik Asistanı</h1>
<form method="POST" action="{{ route('ai-content.store') }}">
@csrf
<div>
<label for="topic">Konu</label>
<input id="topic" type="text" name="topic" value="{{ old('topic') }}">
@error('topic')
<p>{{ $message }}</p>
@enderror
</div>
<div>
<label for="tone">Ton</label>
<select id="tone" name="tone">
<option value="sade" @selected(old('tone') === 'sade')>Sade</option>
<option value="teknik" @selected(old('tone') === 'teknik')>Teknik</option>
<option value="satis" @selected(old('tone') === 'satis')>Satış Odaklı</option>
</select>
@error('tone')
<p>{{ $message }}</p>
@enderror
</div>
<button type="submit">Üret</button>
</form>
@if (session('ai_result'))
<section>
<h2>AI Cevabı</h2>
<pre>{{ session('ai_result') }}</pre>
</section>
@endif
@endsection
AI cevabını `{{ }}` ile basıyoruz. Böylece cevap içinde HTML benzeri metin gelse bile direkt çalışmaz. Güvenlik açısından güzel bir alışkanlık.
10. JSON Çıktı İstemek
Bazen metin değil, düzenli JSON almak isteriz. Örneğin panelde otomatik başlık, açıklama ve etiket doldurmak için JSON iyi olur.
Service içine ayrı bir metod ekleyebilirsin:
public function createJson(string $prompt): array
{
$response = Http::timeout(60)
->retry(2, 500)
->withToken(config('services.openai.key'))
->acceptJson()
->post(config('services.openai.base_url').'/responses', [
'model' => config('services.openai.model'),
'input' => [
[
'role' => 'system',
'content' => 'Sadece geçerli JSON döndür. Markdown kullanma.',
],
[
'role' => 'user',
'content' => $prompt,
],
],
])
->throw()
->json();
$text = data_get($response, 'output_text', '{}');
return json_decode($text, true, flags: JSON_THROW_ON_ERROR);
}
Controller içinde:
$data = $openAi->createJson(
'Laravel dosya yükleme konusu için title, meta_description ve tags alanlarını JSON olarak üret.'
);
Yapay zeka JSON döndürecek diye yine de kör güvenme. `json_decode` hata verebilir, alanlar eksik olabilir. Production projede schema kontrolü ve fallback mesajı eklemek gerekir.
11. Daha Güvenli JSON Kontrolü
Basit doğrulama için Laravel Validator kullanabilirsin:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($data, [
'title' => ['required', 'string', 'max:120'],
'meta_description' => ['required', 'string', 'max:180'],
'tags' => ['required', 'array', 'max:8'],
'tags.*' => ['string', 'max:40'],
]);
if ($validator->fails()) {
// AI cevabı beklenen formata uymadı.
}
Bu yaklaşım özellikle otomatik veritabanı kaydı yapılacaksa önemlidir. AI cevabını kullanıcı girdisi gibi doğrulamak gerekir.
12. Prompt Hazırlama Mantığı
OpenAI API’den iyi cevap almak için prompt net olmalı. Kötü örnek:
Blog yaz.
Daha iyi örnek:
Laravel 12 ile dosya yükleme konusunda, başlangıç seviyesi geliştiriciler için
5 blog başlığı üret. Her başlık maksimum 70 karakter olsun. Cevabı numaralı liste olarak ver.
Prompt içinde şunları belirtmek cevabı iyileştirir:
- Hedef kitle
- Dil ve ton
- Çıktı formatı
- Uzunluk sınırı
- Yasaklı veya istenmeyen şeyler
- Örnek çıktı yapısı
13. API Key Güvenliği
API key konusunda çok dikkatli olmak gerekir:
- `OPENAI_API_KEY` sadece `.env` içinde durmalı.
- Key asla Blade, JavaScript veya mobil uygulama içine koyulmamalı.
- Git’e yanlışlıkla key gittiyse key’i hemen iptal edip yenisini oluştur.
- Canlı sunucuda `.env` dosyası public erişime kapalı olmalı.
- Loglara kullanıcı promptu ve API cevabı yazarken kişisel veri var mı kontrol edilmeli.
14. Rate Limit ve Maliyet Kontrolü
AI endpoint’leri ücretli olabileceği için kullanıcıya sınırsız istek hakkı vermek iyi değildir. Route tarafında throttle kullandık. Daha gelişmiş projede kullanıcı bazlı limit de tutabilirsin.
Basit migration fikri:
Schema::create('ai_requests', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->string('feature');
$table->unsignedInteger('prompt_length');
$table->unsignedInteger('output_length')->nullable();
$table->timestamps();
});
Bu tabloyla hangi kullanıcı kaç istek atmış, hangi özellik daha çok kullanılmış görebilirsin.
15. Queue ile Arka Planda Çalıştırma
Uzun süren AI işlemlerini request içinde bekletmek kullanıcı deneyimini bozabilir. İçerik üretimi, uzun özetleme veya toplu analiz gibi işler queue’ya alınabilir.
php artisan make:job GenerateAiContentJob
Job içinde service çağırma:
<?php
namespace App\Jobs;
use App\Services\OpenAiService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class GenerateAiContentJob implements ShouldQueue
{
use Queueable;
public function __construct(
public int $userId,
public string $topic
) {
}
public function handle(OpenAiService $openAi): void
{
$result = $openAi->createText(
prompt: 'Bu konu için blog taslağı üret: '.$this->topic,
systemMessage: 'Türkçe, sade ve düzenli cevap ver.'
);
// Sonucu veritabanına kaydedebilir veya kullanıcıya bildirim gönderebilirsin.
}
}
Queue çalıştırmak için:
php artisan queue:work
16. Timeout, Retry ve Hata Mesajları
OpenAI API bazen yoğunluk, ağ problemi veya hatalı istek nedeniyle cevap vermeyebilir. Kullanıcıya teknik hata dökmek yerine anlaşılır mesaj vermeliyiz.
try {
$result = $openAi->createText($prompt);
} catch (RequestException $exception) {
report($exception);
return back()->withErrors([
'topic' => 'AI cevabı şu an üretilemedi. Lütfen kısa süre sonra tekrar deneyin.',
]);
}
Log içine API key yazdırmamaya dikkat et. Header veya config değerlerini komple loglamak riskli olur.
17. Test Yazma
Feature testlerde gerçek OpenAI API’ye istek atmak doğru değildir. Laravel `Http::fake()` ile API cevabını taklit edebiliriz.
use Illuminate\Support\Facades\Http;
public function test_ai_content_can_be_generated(): void
{
Http::fake([
'api.openai.com/v1/responses' => Http::response([
'output_text' => "1. Laravel API Rehberi\nKısa açıklama.",
], 200),
]);
$user = User::factory()->create();
$response = $this->actingAs($user)->post(route('ai-content.store'), [
'topic' => 'Laravel API bağlantısı',
'tone' => 'sade',
]);
$response->assertSessionHasNoErrors();
$response->assertSessionHas('ai_result');
}
Servis testinde de aynı mantık kullanılabilir:
public function test_openai_service_returns_output_text(): void
{
Http::fake([
'api.openai.com/v1/responses' => Http::response([
'output_text' => 'Test cevabı',
], 200),
]);
$service = app(OpenAiService::class);
$this->assertSame('Test cevabı', $service->createText('Merhaba'));
}
18. Streaming Gerekir mi?
Chat benzeri arayüzlerde cevabı parça parça göstermek isteyebilirsin. Buna streaming denir. Streaming kullanıcı deneyimini güzelleştirir ama Laravel tarafında SSE, frontend event dinleme ve bağlantı yönetimi gibi ekstra işler getirir.
Başlangıç için normal request-response daha kolaydır. Önce standart API bağlantısını sağlam kur, sonra ihtiyaç varsa streaming ekle. Admin panel içerik üretimi gibi alanlarda streaming şart değildir.
19. Canlı Ortam Kontrol Listesi
- `.env` içinde `OPENAI_API_KEY` doğru mu?
- `php artisan config:clear` veya production’da `php artisan config:cache` çalıştı mı?
- OpenAI endpoint’i dış ağa çıkabiliyor mu?
- Route’larda auth ve throttle var mı?
- Kullanıcı girdisi validation’dan geçiyor mu?
- API hatalarında kullanıcıya düzgün mesaj dönüyor mu?
- Loglarda API key veya hassas veri yok mu?
- Testlerde gerçek API çağrısı yerine `Http::fake()` kullanılıyor mu?
20. Sık Yapılan Hatalar
API key’i frontend’e koymak: En tehlikeli hatalardan biridir. Key backend tarafında kalmalı.
Controller içinde her şeyi yapmak: İlk gün hızlıdır, üçüncü hafta karmaşa olur. Service class kullan.
Rate limit koymamak: Maliyet ve kötüye kullanım riski doğar.
AI cevabına direkt güvenmek: JSON bile dönse doğrula. Eksik veya hatalı alan gelebilir.
Gerçek API ile test çalıştırmak: Testler yavaşlar, maliyet oluşur ve dış servise bağımlı hale gelir.
Hata body’sini kullanıcıya göstermek: Teknik detayları kullanıcıya dökme, logla ve sade hata göster.
Hızlı Özet
OPENAI_API_KEY=sk-proj_xxx
OPENAI_MODEL=gpt-5.5
OPENAI_BASE_URL=https://api.openai.com/v1
php artisan config:clear
php artisan make:controller AiContentController
Http::withToken(config('services.openai.key'))
->post(config('services.openai.base_url').'/responses', [
'model' => config('services.openai.model'),
'input' => [
['role' => 'user', 'content' => 'Laravel için kısa öneri yaz.'],
],
]);
Sonuç
Laravel’de OpenAI API bağlantısı kurarken en temiz yaklaşım, API çağrısını ayrı bir servis sınıfında toplamak ve controller’ı sade tutmaktır. `.env`, `config/services.php`, Laravel HTTP Client, validation, throttle, hata yönetimi ve testler doğru kurulduğunda proje daha güvenli ve sürdürülebilir olur.
Benim önerim şu: İlk aşamada Responses API ile düz metin üretimini kur. Sonra JSON çıktı, queue, kullanıcı bazlı limit ve loglama gibi parçaları ekle. Böyle ilerlersen AI özelliği projeye sonradan yapıştırılmış gibi durmaz; Laravel mimarisinin normal bir parçası haline gelir.
Kaynak notu: Bu yazı OpenAI Responses API dokümantasyonu, OpenAI API quickstart yaklaşımı ve Laravel HTTP Client pratikleri temel alınarak hazırlanmıştır.