Laravel AI SDK ile Akilli Asistan Gelistirme
Laravel AI SDK ciktiginda ilk dusuncem "yine bir wrapper mi?" oldu. Ama derinlemesine inceleyince gercekten farkli bir sey oldugunu gordum. Bu yazida sifirdan bir AI belge analizci olusturacagiz: dosya yukle, AI analiz etsin, yapilandirilmis cikti versin. Agent class'lari, structured output, streaming ve test konularini uygulama uzerinden gorecegiz.
Ne Yapacagiz?
Senaryo su: Kullanici bir PDF veya metin dosyasi yukluyor, AI asistan bu dosyayi okuyor ve su bilgileri cikariyor:
- Belgenin ozeti (3 cumle)
- Anahtar kelimeler
- Duygu analizi (pozitif/negatif/notr)
- Kategori (fatura, sozlesme, rapor, diger)
- Onem derecesi (dusuk/orta/yuksek/kritik)
Tum bunlar yapilandirilmis (structured) cikti olarak donecek -- yani JSON, string degil.
Kurulum
Laravel 12 ve PHP 8.4 gerekiyor. Oncelikle paketi kuralim:
composer require laravel/ai
Sonra konfigurasyonu yayinlayalim:
php artisan vendor:publish --tag=ai-config
Bu config/ai.php dosyasini olusturuyor. API key'leri .env dosyasina ekleyelim:
# Ana provider
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o
# Yedek provider
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-4-20250514
# Lokal gelistirme (opsiyonel)
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=llama3.2
Agent Olusturma
Agent, AI SDK'nin temel yapisi. Her agent belirli bir gorevi yerine getiren, kendi system prompt'u ve konfigurasyonu olan bir sinif.
php artisan make:agent DocumentAnalyzer
Bu app/AI/Agents/DocumentAnalyzer.php dosyasini olusturuyor:
// app/AI/Agents/DocumentAnalyzer.php
<?php
namespace App\AI\Agents;
use Laravel\AI\Agent;
use Laravel\AI\Attributes\Description;
use Laravel\AI\Attributes\Provider;
use Laravel\AI\Attributes\Model;
#[Description('Belgeleri analiz eden ve yapilandirilmis rapor ureten ajan')]
#[Provider('openai')]
#[Model('gpt-4o')]
class DocumentAnalyzer extends Agent
{
/**
* Agent'in system prompt'u
*/
protected function systemPrompt(): string
{
return <<<PROMPT
Sen bir belge analiz uzmanisin. Sana verilen belgeleri dikkatli bir sekilde oku ve analiz et.
Kurallarin:
- Ozeti Turkce yaz, 3 cumleyi gecme
- Anahtar kelimeleri Turkce ver (en fazla 10 adet)
- Duygu analizinde belgenin genel tonunu degerlendir
- Kategoriyi belgenin icerigine gore belirle
- Onem derecesini belgenin icerigine ve aciliyetine gore degerlendir
- Her zaman objektif ve dogru ol
PROMPT;
}
}
Structured Output
AI'dan string yerine yapilandirilmis veri almak icin schema class'lari tanimliyoruz. Bu cok onemli bir ozellik cunku AI ciktisini direkt PHP object'i olarak kullanabiliyoruz.
// app/AI/Schemas/DocumentAnalysis.php
<?php
namespace App\AI\Schemas;
use Laravel\AI\Schema;
use Laravel\AI\Attributes\Description;
class DocumentAnalysis extends Schema
{
public function __construct(
#[Description('Belgenin 3 cumlelik Turkce ozeti')]
public string $summary,
#[Description('En fazla 10 adet Turkce anahtar kelime')]
/** @var string[] */
public array $keywords,
#[Description('Belgenin genel duygu tonu')]
public Sentiment $sentiment,
#[Description('Belge kategorisi')]
public DocumentCategory $category,
#[Description('Onem derecesi')]
public Priority $priority,
#[Description('Belgedeki onemli tarihler (varsa)')]
/** @var string[] */
public array $importantDates = [],
#[Description('Belgedeki para tutarlari (varsa)')]
/** @var string[] */
public array $amounts = [],
#[Description('Analizin guvenilirlik yuzdesi (0-100)')]
public int $confidence = 0,
) {}
}
Enum'lar:
// app/AI/Schemas/Sentiment.php
<?php
namespace App\AI\Schemas;
enum Sentiment: string
{
case Positive = 'positive';
case Negative = 'negative';
case Neutral = 'neutral';
case Mixed = 'mixed';
}
// app/AI/Schemas/DocumentCategory.php
<?php
namespace App\AI\Schemas;
enum DocumentCategory: string
{
case Invoice = 'invoice';
case Contract = 'contract';
case Report = 'report';
case Letter = 'letter';
case Legal = 'legal';
case Technical = 'technical';
case Other = 'other';
}
// app/AI/Schemas/Priority.php
<?php
namespace App\AI\Schemas;
enum Priority: string
{
case Low = 'low';
case Medium = 'medium';
case High = 'high';
case Critical = 'critical';
}
Agent'i Structured Output ile Kullanma
Simdi agent'imizi schema ile birlikte kullanalim:
// app/AI/Agents/DocumentAnalyzer.php - guncellenmis hali
<?php
namespace App\AI\Agents;
use Laravel\AI\Agent;
use Laravel\AI\Attributes\Description;
use Laravel\AI\Attributes\Provider;
use Laravel\AI\Attributes\Model;
use App\AI\Schemas\DocumentAnalysis;
#[Description('Belgeleri analiz eden ve yapilandirilmis rapor ureten ajan')]
#[Provider('openai')]
#[Model('gpt-4o')]
class DocumentAnalyzer extends Agent
{
protected function systemPrompt(): string
{
return <<<PROMPT
Sen bir belge analiz uzmanisin. Sana verilen belgeleri dikkatli bir sekilde oku ve analiz et.
Ozeti ve anahtar kelimeleri Turkce yaz.
Her zaman objektif ve dogru ol.
PROMPT;
}
/**
* Yapilandirilmis analiz ciktisi
*/
public function analyze(string $content): DocumentAnalysis
{
return $this->structured(DocumentAnalysis::class)
->send("Bu belgeyi analiz et:\n\n{$content}")
->result();
}
/**
* Dosya eki ile analiz
*/
public function analyzeFile(string $filePath): DocumentAnalysis
{
return $this->structured(DocumentAnalysis::class)
->attach($filePath)
->send('Ekteki belgeyi analiz et.')
->result();
}
}
Controller
// app/Http/Controllers/DocumentAnalysisController.php
<?php
namespace App\Http\Controllers;
use App\AI\Agents\DocumentAnalyzer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DocumentAnalysisController extends Controller
{
public function analyze(Request $request, DocumentAnalyzer $agent): JsonResponse
{
$request->validate([
'document' => 'required|file|mimes:pdf,txt,doc,docx|max:5120',
]);
$file = $request->file('document');
$path = $file->store('temp-analysis', 'local');
$fullPath = Storage::disk('local')->path($path);
try {
$analysis = $agent->analyzeFile($fullPath);
return response()->json([
'summary' => $analysis->summary,
'keywords' => $analysis->keywords,
'sentiment' => $analysis->sentiment->value,
'category' => $analysis->category->value,
'priority' => $analysis->priority->value,
'important_dates' => $analysis->importantDates,
'amounts' => $analysis->amounts,
'confidence' => $analysis->confidence,
]);
} finally {
// Gecici dosyayi temizle
Storage::disk('local')->delete($path);
}
}
}
Streaming
Uzun analizlerde kullaniciyi bekletmemek icin streaming kullanabiliriz:
// Streaming ile analiz
public function analyzeStream(Request $request, DocumentAnalyzer $agent)
{
$request->validate([
'content' => 'required|string|max:50000',
]);
return response()->stream(function () use ($request, $agent) {
$stream = $agent->send("Bu belgeyi detayli analiz et:\n\n{$request->content}")
->stream();
foreach ($stream as $chunk) {
echo "data: " . json_encode(['text' => $chunk]) . "\n\n";
ob_flush();
flush();
}
echo "data: [DONE]\n\n";
ob_flush();
flush();
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
]);
}
Frontend'de Server-Sent Events ile dinliyoruz:
async function streamAnalysis(content) {
const resultDiv = document.getElementById('analysis-result');
resultDiv.textContent = '';
const response = await fetch('/api/documents/analyze-stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ content }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
const data = JSON.parse(line.slice(6));
resultDiv.textContent += data.text;
}
}
}
}
Test
AI SDK'nin en guzel ozelliklerinden biri fake() metodu. Gercek API'ye istek atmadan test yazabiliyoruz:
// tests/Feature/DocumentAnalysisTest.php
<?php
namespace Tests\Feature;
use App\AI\Agents\DocumentAnalyzer;
use App\AI\Schemas\DocumentAnalysis;
use App\AI\Schemas\DocumentCategory;
use App\AI\Schemas\Priority;
use App\AI\Schemas\Sentiment;
use Laravel\AI\Facades\AI;
use Tests\TestCase;
class DocumentAnalysisTest extends TestCase
{
public function test_document_analysis_returns_structured_output(): void
{
// AI cevabini taklit et
AI::fake([
DocumentAnalyzer::class => new DocumentAnalysis(
summary: 'Bu bir test faturasi ozeti.',
keywords: ['fatura', 'odeme', 'test'],
sentiment: Sentiment::Neutral,
category: DocumentCategory::Invoice,
priority: Priority::Medium,
importantDates: ['2026-03-01'],
amounts: ['1.500,00 TL'],
confidence: 85,
),
]);
$response = $this->actingAs($this->createUser())
->postJson('/api/documents/analyze', [
'document' => $this->createFakeDocument(),
]);
$response->assertOk()
->assertJson([
'category' => 'invoice',
'priority' => 'medium',
'sentiment' => 'neutral',
'confidence' => 85,
]);
// Agent'in cagrildigini dogrula
AI::assertAgentUsed(DocumentAnalyzer::class);
}
public function test_analysis_handles_empty_document(): void
{
AI::fake([
DocumentAnalyzer::class => new DocumentAnalysis(
summary: 'Belge bos veya okunamiyor.',
keywords: [],
sentiment: Sentiment::Neutral,
category: DocumentCategory::Other,
priority: Priority::Low,
confidence: 10,
),
]);
$response = $this->actingAs($this->createUser())
->postJson('/api/documents/analyze', [
'document' => $this->createFakeDocument(''),
]);
$response->assertOk()
->assertJson(['confidence' => 10]);
}
}
Queue ile Arka Plan Islem
Buyuk belgelerde veya toplu analizlerde queue kullanmak sart:
// app/Jobs/AnalyzeDocumentJob.php
<?php
namespace App\Jobs;
use App\AI\Agents\DocumentAnalyzer;
use App\Models\Document;
use App\Notifications\AnalysisCompleteNotification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class AnalyzeDocumentJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60; // 60 saniye bekle
public function __construct(
private Document $document
) {}
public function handle(DocumentAnalyzer $agent): void
{
$analysis = $agent->analyzeFile(
$this->document->getFullPath()
);
$this->document->update([
'summary' => $analysis->summary,
'keywords' => $analysis->keywords,
'category' => $analysis->category->value,
'priority' => $analysis->priority->value,
'sentiment' => $analysis->sentiment->value,
'confidence' => $analysis->confidence,
'analyzed_at' => now(),
]);
// Kullaniciya bildirim gonder
$this->document->user->notify(
new AnalysisCompleteNotification($this->document)
);
}
public function failed(\Throwable $exception): void
{
$this->document->update([
'analysis_error' => $exception->getMessage(),
]);
}
}
Dispatch etme:
// Controller'dan
AnalyzeDocumentJob::dispatch($document);
// Veya toplu islem
$documents->each(function ($doc) {
AnalyzeDocumentJob::dispatch($doc)->onQueue('ai-analysis');
});
Provider Soyutlamasi ve Failover
Tek bir provider'a bagimli kalmak tehlikeli. OpenAI down olursa ne olacak? AI SDK bunu failover ile cozuyor:
// config/ai.php
'agents' => [
'document-analyzer' => [
'providers' => [
['provider' => 'openai', 'model' => 'gpt-4o'],
['provider' => 'anthropic', 'model' => 'claude-sonnet-4-20250514'],
['provider' => 'ollama', 'model' => 'llama3.2'],
],
'failover' => true,
],
],
Bu konfigurasyonla once OpenAI denenir, basarisiz olursa Anthropic'e, o da olmazsa yerel Ollama'ya duser.
Lokal Gelistirme: Ollama
Gelistirme ortaminda API masrafi yapmamak icin Ollama kullanabilirsiniz:
# Ollama'yi kur (macOS)
brew install ollama
# Modeli indir
ollama pull llama3.2
# Calistir
ollama serve
.env.local dosyasinda:
AI_DEFAULT_PROVIDER=ollama
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=llama3.2
Tabii Ollama'nin structured output kalitesi OpenAI veya Anthropic kadar iyi degil. Gelistirme ve test icin yeterli ama production'da buyuk provider'lari kullanmanizi oneririm.
Sonuc
Laravel AI SDK, AI entegrasyonunu Laravel'e ozgu bir sekilde cozuyor. Agent class'lari ile is mantigini temiz tutabiliyorsunuz, structured output ile AI ciktisini guvenirlir sekilde kullanabiliyorsunuz, fake() ile test yazabiliyorsunuz ve failover ile provider bagimsiz calisabiliyorsunuz.
Bu yazidaki belge analizci ornegini kendi ihtiyaciniza uyarlayabilirsiniz: musteri destek asistani, icerik moderasyonu, otomatik etiketleme, ceviri servisi... Temel yapi ayni kaliyor, sadece prompt ve schema degisiyor.
Onemli uyari: AI ciktilarina kor kor guvenmeyin. Ozellikle kritik islemlerde (odeme, hukuki karar vs.) AI ciktisini insan kontrolunden gecirin. confidence skoru bu konuda yardimci olabilir ama tek basina yeterli degil.