Originally published at hafiz.dev
Bu yazıda, üretim ortamına uygun bir müşteri destek botu geliştireceğiz. Bot, konuşma geçmişini hatırlayacak, yardım belgelerinizi vektör gömülüleri kullanarak arayacak, veritabanınızdan sipariş detaylarını sorgulayacak ve dahili olarak cevap bulamadığında web aramasına geçiş yapacak. Tüm bunlar Laravel AI SDK ile gerçekleştirilecek.
Üretmekte Olduğumuz Destek Botu
Üretmekte Olduğumuz Destek Botu
Destek botumuz neler yapacak:
Kimle konuştuğunu hatırlıyor. Bir müşteri siparişiyle ilgili bir şey sorduğunda, üç mesaj sonra “kargo durumu ne?” dediğinde bot, hangi siparişi kastettiğini bilecek. Bu,
RemembersConversationstrait’i tarafından otomatik olarak yönetilen konuşma sürekliliğidir.Bilgi tabanınızı arar. Yardım belgelerinizi, SSS sayfalarınızı ve ürün kılavuzlarınızı yüklersiniz. Bot, önerilen cevapları bulmak için bunları vektör gömülüleri kullanarak arar. Bu, Retrieval-Augmented Generation (RAG) olarak bilinir ve SDK bu süreci oldukça kolaylaştırıyor.
Veritabanınızı sorgular. Müşteri “Siparişim nerede?” dediğinde, bot özelleştirilmiş bir aracı çağırır ve sipariş tablonuzu kontrol eder. Hayali bilgiler yok, gerçek veriler.
Web aramasına geçer. Eğer bilgi tabanınızda cevap yoksa, bot, dahili
WebSearchsağlayıcı aracını kullanarak web araması yapabilir.
Adım 1: Gömülü Veriler İçin Veritabanını Ayarlama
Adım 1: Gömülü Veriler İçin Veritabanını Ayarlama
RAG için vektör gömülüleri gerekir ve bu gömülülerin PostgreSQL ile pgvector kullanması gerekir. Farklı arama yaklaşımlarını Laravel’de nasıl kullanacağınızı öğrenmek istiyorsanız, daha derin bir incelemeyaptım. Bu eğitim için PostgreSQL kullandığımızı varsayıyoruz.
Öncelikle bilgi tabanınız için migration’ı oluşturun:
php artisan make:model Article -m
// database/migrations/xxxx_create_articles_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::ensureVectorExtensionExists();
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string();
$table->text();
$table->string()->nullable();
$table->vector( dimensions: 1536)->index();
$table->timestamps();
});
}
};
vector sütun türü, PostgreSQL üzerinde pgvector sayesinde Laravel’e özeldir. ->index() çağrısı hızlı benzerlik aramaları için bir HNSW indeksi oluşturur. 1536 boyut, OpenAI’nın text-embedding-3-small modelini eşleştirir, bu varsayılan olanıdır.
Adım 2: Gömülü Veriler Boru Hattını Kurma
Adım 2: Gömülü Veriler Boru Hattını Kurma
Makaleler oluşturulurken veya güncellenirken gömülü verileri üretecek bir yol gereklidir. İşte bilgi tabanınızı besleyecek bir artisan komutu:
// app/Console/Commands/EmbedArticles.php
namespace App\Console\Commands;
use App\Models\Article;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class EmbedArticles extends Command
{
protected $signature =
protected $description =
public function handle(): void
{
$articles = Article::whereNull(->get();
$this->info({$articles->count()}' articles...');
foreach ($articles as $article) {
$text = {$article->title\n\n$article->content';
$article->update([
Str::of($text)->toEmbeddings(),
]);
$this->line({$article->title');
}
$this->info();
}
}
Str::of($text)->toEmbeddings() çağrısı, bu SDK’nın en sevdiğim özelliklerinden biridir. Bir satır, hiçbir HTTP istemci ayarı, JSON ayrıştırma veya hata yönetimi bilgisi yok.
Adım 3: Özelleştirilmiş Araçları Oluşturma
Adım 3: Özelleştirilmiş Araçları Oluşturma
Bu noktada, araçlar botun metin üretiminden daha fazlasını yapabilmesini sağlar; veritabanınızı sorgulayabilir, API’leri çağırabilir veya istediğiniz herhangi bir PHP kodunu çalıştırabilirsiniz.
Sipariş Arama Aracı
Sipariş Arama Aracı
php artisan make:tool LookupOrder
// app/Ai/Tools/LookupOrder.php
namespace App\Ai\Tools;
use App\Models\Order;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class LookupOrder implements Tool
{
public function description(): Stringable|string
{
return . ;
}
public function handle(Request $request): Stringable|string
{
$order = Order::with()
->where(, $request[])
->orWhere(, $request[] ?? )
->first();
if (! $order) {
return ;
}
return json_encode([
=> $order->order_number,
=> $order->status,
=> $order->created_at->format(),
=> $order->items->map(fn ($item) => [
=> $item->product_name,
=> $item->quantity,
=> $item->price,
])->toArray(),
=> $order->tracking_number,
=> $order->estimated_delivery?->format(),
]);
}
public function schema(JsonSchema $schema): array
{
return [
=> $schema->string()
->description()
->required(),
=> $schema->string()
->description(),
];
}
}
Araç, AI modeline ne tür parametreler kabul edileceğini bildirir. Açıklama ise aracın ne zaman kullanılacağı hakkında bilgi verir. Burada kesin olmak kritik önem taşır; belirsiz açıklamalar yanlış zamanda araç çağrısına yol açabilir.
Adım 4: Destek Botunu Oluşturma
Adım 4: Destek Botunu Oluşturma
Şimdi tüm parçaları birleştirip aracı oluşturalım:
php artisan make:agent SupportBot
// app/Ai/Agents/SupportBot.php
namespace App\Ai\Agents;
use App\Ai\Tools\LookupOrder;
use App\Models\Article;
use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
use Laravel\Ai\Providers\Tools\WebSearch;
use Laravel\Ai\Tools\SimilaritySearch;
use Stringable;
#[Provider(Lab::Anthropic)]
#[Temperature(0.3)]
#[MaxSteps(5)]
class SupportBot implements Agent, Conversational, HasTools
{
use Promptable, RemembersConversations;
public function instructions(): Stringable|string
{
return
}
public function tools(): iterable
{
return [
SimilaritySearch::usingModel(
model: Article::class,
column:
minSimilarity: 0.5,
limit: 5,
)->withDescription(
),
new LookupOrder,
(new WebSearch)->max(3),
];
}
}
Burada olanları anlamlandıralım. RemembersConversations hafıza işlevini üstleniyor. Kullanıcının ve asistanın tüm mesajlarını otomatik olarak agent_conversations ve agent_conversation_messages tablolarına kaydediyor. Manuel bir veritabanı çalışması yok.
Adım 5: Yönlendirmeleri Ayarlama
Adım 5: Yönlendirmeleri Ayarlama
// routes/web.php
use App\Ai\Agents\SupportBot;
use Illuminate\Http\Request;
// Yeni bir konuşma başlat
Route::post(, function (Request $request) {
$response = (new SupportBot)
->forUser($request->user())
->prompt($request->input(
return response()->json([
=> $response->conversationId,
(string) $response,
]);
});
// Mevcut bir konuşmayı sürdür
Route::post(, function (Request $request) {
$response = (new SupportBot)
->continue($request->input(), as: $request->user())
->prompt($request->input(
return response()->json([
(string) $response,
]);
});
forUser() metodu, doğrulanmış kullanıcıyla ilişkilendirilmiş yeni bir konuşma başlatır. continue() metodu, son mesajın kaldığı yerden devam eder. Tüm önceki mesajlar otomatik olarak yüklenir.
Adım 6: Ajan Middleware’ini Ekleme
Adım 6: Ajan Middleware’ini Ekleme
Middleware, talimatları sağlayıcıya ulaşmadan önce kesmenizi sağlar. Bu, günlük kaydı, maliyet takibi, oran sınırlaması veya dinamik bağlam eklemek için son derece kullanışlıdır.
php artisan make:agent-middleware TrackUsage
// app/Ai/Middleware/TrackUsage.php
namespace App\Ai\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Prompts\AgentPrompt;
use Laravel\Ai\Responses\AgentResponse;
class TrackUsage
{
public function handle(AgentPrompt $prompt, Closure $next)
{
$startTime = microtime(true);
return $next($prompt)->then(function (AgentResponse $response) use ($startTime) {
$duration = round(microtime(true) - $startTime, 2);
Log::channel()->info(, [
=> $response->usage->inputTokens ?? null,
=> $response->usage->outputTokens ?? null,
=> $duration,
]);
});
}
}
Aracın middleware fonksiyonu, aracı sınıfı içerisinde tanımlanır.
use App\Ai\Middleware\TrackUsage;
use Laravel\Ai\Contracts\HasMiddleware;
class SupportBot implements Agent, Conversational, HasTools, HasMiddleware
{
// ... mevcut kod ...
public function middleware(): array
{
return [
new TrackUsage,
];
}
}
then geri çağrısı, yanıt tamamlandıktan sonra çalışır (hem senkron hem de akış halinde). Tüm yanıtın tam erişimine sahip olursunuz ve bu, konuşma başına maliyet takibi yapmak için tam olarak ihtiyaç duyduğunuz bilgiyi sağlar.
Adım 7: Sağlayıcı Failover’u Ekleme
Adım 7: Sağlayıcı Failover’u Ekleme
Üretim uygulamaları, OpenAI’nin kötü bir gün geçirmesi nedeniyle aksaklık yaşayamaz. Failover bir satır alır:
// Ajan sınıfında
#[Provider([Lab::Anthropic, Lab::OpenAI])]
class SupportBot implements Agent
Eğer Anthropic bir hata döndürür veya bir oran sınırlaması gelirse, SDK otomatik olarak OpenAI ile yeniden denemeler yapar. Hiçbir try/catch, hiçbir yeniden deneme mantığı, hiçbir devre kesici yok. Kullanıcılarınız hata görmez.
Adım 8: Her Şeyi Test Etme
Adım 8: Her Şeyi Test Etme
Burası, kendi başınıza yazmaktan çok daha iyi bir şekilde SDK’nın parlayacağı yerdir. Her bir bileşen taklit edilebilir.
// tests/Feature/SupportBotTest.php
namespace Tests\Feature;
use App\Ai\Agents\SupportBot;
use App\Models\Article;
use App\Models\Order;
use App\Models\User;
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\AgentPrompt;
use Tests\TestCase;
class SupportBotTest extends TestCase
{
public function test_bot_responds_to_general_questions(): void
{
SupportBot::fake([]);
Embeddings::fake();
$user = User::factory()->create();
$response = (new SupportBot)
->forUser($user)
->prompt();
$this->assertStringContainsString(, () $response);
SupportBot::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->contains();
});
}
public function test_bot_looks_up_orders(): void
{
SupportBot::fake(function (AgentPrompt $prompt) {
return . ;
});
$user = User::factory()->create();
$order = Order::factory()->create([
=> ,
=> ,
=> ,
]);
$response = (new SupportBot)
->forUser($user)
->prompt();
$this->assertStringContainsString(, () $response);
}
public function test_bot_continues_conversations(): void
{
SupportBot::fake(,
,
]);
$user = User::factory()->create();
$first = (new SupportBot)
->forUser($user)
->prompt();
$second = (new SupportBot)
->continue($first->conversationId, as: $user)
->prompt();
$this->assertStringContainsString(, () $second);
}
public function test_no_stray_prompts(): void
{
SupportBot::fake()->preventStrayPrompts();
// Eğer bot, eşleşen bir sahte olmadan yönlendirilirse, test başarısız olur.
// Bu, diğer testlerde istenmeyen AI çağrılarını önler.
}
}
SupportBot::fake() tüm AI sağlayıcısına yapılan çağrıları keser. Gerçek API istekleri yapılmaz. Testleriniz hızlı çalışır, hiçbir maliyet oluşturmaz ve deterministiktir.
preventStrayPrompts() metodunun, test suite’inizin temel sınıfına eklenmesini öneririm. Bu, CI pipeline’ınızı yavaşlatabilecek yanlışlıkla yapılan AI çağrılarını yakalar ve maliyetlerden korur.
Ayrıca, gömülü veriler boru hattını tetikleyen herhangi bir testte, gömülü verileri taklit etmelisiniz. Embeddings::fake(), gerçek API çağrılarını önler ve CI’nizi hızlı ve ücretsiz tutar.
Her Şey Nasıl Bir Araya Geliyor?
Her Şey Nasıl Bir Araya Geliyor?
Müşteri “siparişim ORD-12345 nerede?” dediğinde, SDK, veritabanından konuşma geçmişini yükler, ajanın talimatlarını okur ve bunun bir sipariş sorgusu olduğunu anlamasına yardımcı olur, LookupOrder çağrısı yapar ve veritabanını sorgulayıp JSON döner, ajan dostça bir cevap hazırlar ve middleware her şeyi kaydeder. Her iki mesajda gelecek için bağlamı korur.
Eğer müşteri “iade durumu ne?” diye sorarsa, ajan hangi siparişi kastettiğini önceden bilir. Hiçbir oturum karmaşasına gerek yok. Hiçbir ID geçişine gerek yok.
Alternatifler ve Dikkat Edilmesi Gerekenler
Alternatifler ve Dikkat Edilmesi Gerekenler
Bu kurulum hakkında oldukça iyimserim, ancak bazı zorluklar da var.
PostgreSQL, gömülü veriler için gereklidir. whereVectorSimilarTo metodu ve vektör sütunları yalnızca pgvector ile PostgreSQL’de çalışır. MySQL kullanıyorsanız, OpenAI’nın vektör depolarını kullanarak FileSearch sağlayıcı aracını kullanabilirsiniz. Bu farklı bir yaklaşımdır (dosyalar sağlayıcı ile depolanır), ancak PostgreSQL olmadan çalışır.
Konuşma geçmişi büyüyor. RemembersConversations her konuşma için tüm önceki mesajları yükler. Uzun destek diyalogları için bu bağlam pencerenizi yiyebilir. 20-30 mesajı aşan konuşmalar için bir yuvarlanan pencere veya özetleme stratejisi uygulamanız gerekir.
Araç açıklamaları düşündüğünüzden daha fazla önem taşır. Ajan yanlış aracı sürekli olarak çağırıyorsa (veya çağırması gereken durumlarda çağırmıyorsa), sorun genellikle açıklama ile ilgilidir. Araçların ne zaman kullanılacağını ve ne tür sonuçlar vereceğini belirtin.
Maliyet farkındalığı. Her araç çağrısı, AI sağlayıcısına giden bir gidiş dönüş yoludur. 5 araca sahip bir ajan ve MaxSteps(10) ayarı, teorik olarak her bir kullanıcı mesajında 10 API çağrısı yapabilir. Yaptığımız middleware bunu izler, bu yüzden bunu kullanın. Günlüklerinizi kontrol edin.
Tavsiyem: İlk sağlayıcı olarak Anthropic ile başlayın ve OpenAI için bir failover ayarlayın. Claude, araç kullanımı ve uzun konuşmalar için oldukça uygundur. MaxSteps değerini (çoğu kullanım durumu için 3-5) temkinli bir şekilde ayarlayın ve daima testlerinize preventStrayPrompts() ekleyin.
Sıkça Sorulan Sorular
Sıkça Sorulan Sorular
RAG için PostgreSQL yerine MySQL kullanabilir miyim?
RAG için PostgreSQL yerine MySQL kullanabilir miyim?
Yerel vektör gömülüleri için hayır. whereVectorSimilarTo metodu pgvector ile PostgreSQL gerektirir. Ancak, OpenAI’nın barındırılan vektör depoları ile FileSearch sağlayıcı aracını kullanabilirsiniz, bu veritabanınızdan bağımsız çalışır. Dosyaları OpenAI’ya yüklersiniz, onlar gömülü verileri ve aramayı yönetir.
Konuşma geçmişini nasıl sınırlayabilirim, böylece bağlam penceresine ulaşmam?
Konuşma geçmişini nasıl sınırlayabilirim, böylece bağlam penceresine ulaşmam?
RemembersConversations trait’i varsayılan olarak tüm mesajları yükler. Üretimde, Conversational arayüzünü manuel olarak uygulayın ve mesaj sorgunuzda ->limit(30) ekleyin. Ayrıca, daha eski mesajları bir özet mesajına sıkıştıracak bir özetleme adımı oluşturabilirsiniz.
SimilaritySearch aracı hiçbir sonuç döndürdüğünde ne olur?
SimilaritySearch aracı hiçbir sonuç döndürdüğünde ne olur?
Ajan “hiçbir sonuç bulunamadı” yanıtını alır ve diğer araçlarına (WebSearch gibi) veya eğitim bilgilerinden yanıt vermeye geçer. Bu nedenle sistem talimatlarınızda araç önceliğinin önemi büyüktür. Ajanınıza: önce bilgi tabanını arayın, sonra web aramasını yapın, en son genel bilgiye başvurun, diyebilirsiniz.
Bunu API yönlendirmeleri yerine Livewire ile kullanabilir miyim?
Bunu API yönlendirmeleri yerine Livewire ile kullanabilir miyim?
Evet. Ajan nasıl çağrıldığına aldırmaz. Bunu bir Livewire bileşeninden, bir denetleyiciden, bir kuyruk işinden veya bir Artisan komutundan yönlendirebilirsiniz. Livewire ile akış için, SSE yerine yayınlama yaklaşımını kullanın.
Konuşma başına ne kadar maliyetle çalıştırılabilir?
Konuşma başına ne kadar maliyetle çalıştırılabilir?
Bu, sağlayıcıya ve konuşma uzunluğuna bağlıdır. Genellikle 5 mesajlı bir destek konuşması, Anthropic (Claude Haiku) ve 2-3 araç çağrısı ile yaklaşık $0.01-0.03 maliyet uygular. #[UseCheapestModel] özelliğini kullanmak, basit destek sorguları için maliyetleri en aza indirmek için kolay bir yol sunar.
Sonuç
Sonuç
Bu eğitimde bir şeyi daha önemsiz gösterdik. Ancak artık hafıza, RAG, özelleştirilmiş araçlar, middleware, failover ve uygun testler ile bir destek botu inşa ettik. Bunları üretim için kabul edebiliriz.
Laravel AI SDK şu anda v0.2.1 seviyesinde ve hızla gelişiyor. Ajan orkestrasyonu (ajanların başka ajanlara devretmesi) gibi özellikler yol haritasında bulunmakta. Ancak şu an var olanlar, gerçek ürünler oluşturmak için yeterince sağlam.
Kaynak: Orijinal Makale
- Üretmekte Olduğumuz Destek Botu
- Adım 1: Gömülü Veriler İçin Veritabanını Ayarlama
- Adım 2: Gömülü Veriler Boru Hattını Kurma
- Adım 3: Özelleştirilmiş Araçları Oluşturma
- Adım 4: Destek Botunu Oluşturma
- Adım 5: Yönlendirmeleri Ayarlama
- Adım 6: Ajan Middleware’ini Ekleme
- Adım 7: Sağlayıcı Failover’u Ekleme
- Adım 8: Her Şeyi Test Etme
- Her Şey Nasıl Bir Araya Geliyor?
- Alternatifler ve Dikkat Edilmesi Gerekenler
- Sıkça Sorulan Sorular
- RAG için PostgreSQL yerine MySQL kullanabilir miyim?
- Konuşma geçmişini nasıl sınırlayabilirim, böylece bağlam penceresine ulaşmam?
- SimilaritySearch aracı hiçbir sonuç döndürdüğünde ne olur?
- Bunu API yönlendirmeleri yerine Livewire ile kullanabilir miyim?
- Konuşma başına ne kadar maliyetle çalıştırılabilir?
- Sonuç


