0%

Loading Experience...

Building Enterprise-Grade AI Chatbots with Laravel and OpenAI

Complete guide to building sophisticated AI chatbots with Laravel. Learn conversational AI, context management, multi-language support, and platform integrations.

Building Enterprise-Grade AI Chatbots with Laravel and OpenAI
Read Time28 min read
Written on
By 0xAquaWolf
Last updated

Building Enterprise-Grade AI Chatbots with Laravel and OpenAI#

AI-powered chatbots are transforming customer service, support, and user engagement across industries. With Laravel's robust backend capabilities and OpenAI's advanced language models, you can build sophisticated, context-aware chatbots that handle complex conversations, maintain context, and integrate seamlessly with multiple platforms. This comprehensive guide will show you how to build an enterprise-grade AI chatbot system from scratch.

Prerequisites#

Before we start, ensure you have:

  • Laravel 10+ application
  • OpenAI API key with access to GPT-4
  • Redis for caching and real-time features
  • MySQL or PostgreSQL database
  • Basic understanding of Laravel queues and events
  • Node.js for frontend development
  • Knowledge of WebSocket concepts

Architecture Overview#

Our chatbot system will include:

  • Core Conversation Engine: Context management and response generation
  • Multi-Platform Integration: Web, Slack, Discord, and WhatsApp
  • Knowledge Base System: Training data and FAQ management
  • Analytics Dashboard: Conversation insights and performance metrics
  • Multi-Language Support: Internationalization capabilities
  • Rate Limiting & Security: Protection against abuse

Database Design#

1. Conversations Table#

database/migrations/2024_01_01_000001_create_conversations_table.php
<?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::create('conversations', function (Blueprint $table) {
            $table->id();
            $table->string('session_id')->unique();
            $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
            $table->string('platform')->default('web'); // web, slack, discord, whatsapp
            $table->json('platform_data')->nullable(); // Platform-specific user data
            $table->enum('status', ['active', 'paused', 'closed', 'escalated'])->default('active');
            $table->json('context')->nullable(); // Conversation context
            $table->json('user_profile')->nullable(); // User preferences and history
            $table->string('language', 10)->default('en');
            $table->timestamp('last_activity_at')->nullable();
            $table->json('metadata')->nullable(); // Additional conversation data
            $table->timestamps();
            
            $table->index(['session_id']);
            $table->index(['user_id']);
            $table->index(['platform']);
            $table->index(['status']);
            $table->index(['last_activity_at']);
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('conversations');
    }
};

2. Messages Table#

database/migrations/2024_01_01_000002_create_messages_table.php
<?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::create('messages', function (Blueprint $table) {
            $table->id();
            $table->foreignId('conversation_id')->constrained()->onDelete('cascade');
            $table->enum('role', ['user', 'assistant', 'system', 'tool']);
            $table->longText('content');
            $table->json('metadata')->nullable(); // Message metadata, token counts, etc.
            $table->json('tool_calls')->nullable(); // Function/tool calls made
            $table->json('tool_results')->nullable(); // Results from tool calls
            $table->decimal('confidence_score', 3, 2)->nullable(); // AI confidence in response
            $table->json('analytics')->nullable(); // Engagement metrics
            $table->timestamp('processed_at')->nullable();
            $table->timestamps();
            
            $table->index(['conversation_id', 'created_at']);
            $table->index(['role']);
            $table->fullText(['content']);
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('messages');
    }
};

3. Knowledge Base Tables#

database/migrations/2024_01_01_000003_create_knowledge_base_tables.php
<?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
    {
        // Categories
        Schema::create('kb_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->foreignId('parent_id')->nullable()->constrained('kb_categories')->onDelete('cascade');
            $table->integer('sort_order')->default(0);
            $table->boolean('is_active')->default(true);
            $table->timestamps();
            
            $table->index(['slug']);
            $table->index(['parent_id']);
        });
 
        // Articles
        Schema::create('kb_articles', function (Blueprint $table) {
            $table->id();
            $table->foreignId('category_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->string('slug')->unique();
            $table->longText('content');
            $table->text('excerpt')->nullable();
            $table->json('keywords')->nullable(); // Search keywords
            $table->json('embeddings')->nullable(); // Vector embeddings
            $table->string('language', 10)->default('en');
            $table->enum('status', ['draft', 'published', 'archived'])->default('draft');
            $table->integer('view_count')->default(0);
            $table->decimal('helpfulness_score', 3, 2)->nullable(); // User feedback
            $table->integer('helpfulness_votes')->default(0);
            $table->timestamps();
            
            $table->index(['slug']);
            $table->index(['category_id']);
            $table->index(['status']);
            $table->index(['language']);
            $table->fullText(['title', 'content']);
        });
 
        // FAQs
        Schema::create('kb_faqs', function (Blueprint $table) {
            $table->id();
            $table->string('question');
            $table->longText('answer');
            $table->json('keywords')->nullable();
            $table->json('embeddings')->nullable();
            $table->string('language', 10)->default('en');
            $table->integer('priority')->default(0);
            $table->boolean('is_active')->default(true);
            $table->integer('hit_count')->default(0);
            $table->timestamps();
            
            $table->fullText(['question', 'answer']);
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('kb_faqs');
        Schema::dropIfExists('kb_articles');
        Schema::dropIfExists('kb_categories');
    }
};

4. Analytics and Metrics Tables#

database/migrations/2024_01_01_000004_create_analytics_tables.php
<?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
    {
        // Conversation Analytics
        Schema::create('conversation_analytics', function (Blueprint $table) {
            $table->id();
            $table->foreignId('conversation_id')->constrained()->onDelete('cascade');
            $table->integer('message_count')->default(0);
            $table->integer('user_message_count')->default(0);
            $table->integer('assistant_message_count')->default(0);
            $table->decimal('avg_response_time', 8, 2)->nullable(); // in seconds
            $table->decimal('satisfaction_score', 3, 2)->nullable(); // User rating
            $table->string('resolved_by')->nullable(); // AI, Human, or Escalated
            $table->json('topics_discussed')->nullable(); // Extracted topics
            $table->json('sentiment_analysis')->nullable(); // Conversation sentiment
            $table->timestamp('first_response_at')->nullable();
            $table->timestamp('last_message_at')->nullable();
            $table->timestamp('resolved_at')->nullable();
            $table->timestamps();
        });
 
        // Platform Usage Stats
        Schema::create('platform_stats', function (Blueprint $table) {
            $table->id();
            $table->string('platform');
            $table->date('date');
            $table->integer('conversations_started')->default(0);
            $table->integer('conversations_completed')->default(0);
            $table->integer('messages_exchanged')->default(0);
            $table->integer('unique_users')->default(0);
            $table->decimal('avg_session_duration', 8, 2)->nullable();
            $table->decimal('satisfaction_score', 3, 2)->nullable();
            $table->timestamps();
            
            $table->unique(['platform', 'date']);
        });
 
        // AI Performance Metrics
        Schema::create('ai_performance_metrics', function (Blueprint $table) {
            $table->id();
            $table->date('date');
            $table->string('model_used');
            $table->integer('total_requests')->default(0);
            $table->integer('successful_responses')->default(0);
            $table->integer('failed_responses')->default(0);
            $table->decimal('avg_response_time', 8, 2)->nullable();
            $table->integer('total_tokens_used')->default(0);
            $table->decimal('total_cost', 10, 6)->default(0);
            $table->json('error_types')->nullable();
            $table->timestamps();
            
            $table->unique(['date', 'model_used']);
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('ai_performance_metrics');
        Schema::dropIfExists('platform_stats');
        Schema::dropIfExists('conversation_analytics');
    }
};

Core Models and Relationships#

1. Conversation Model#

app/Models/Conversation.php
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
 
class Conversation extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'session_id',
        'user_id',
        'platform',
        'platform_data',
        'status',
        'context',
        'user_profile',
        'language',
        'last_activity_at',
        'metadata',
    ];
 
    protected $casts = [
        'platform_data' => 'array',
        'context' => 'array',
        'user_profile' => 'array',
        'metadata' => 'array',
        'last_activity_at' => 'datetime',
    ];
 
    protected $dates = [
        'last_activity_at',
    ];
 
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
 
    public function messages(): HasMany
    {
        return $this->hasMany(Message::class)->orderBy('created_at');
    }
 
    public function analytics(): HasMany
    {
        return $this->hasMany(ConversationAnalytic::class);
    }
 
    public function latestAnalytics(): BelongsTo
    {
        return $this->belongsTo(ConversationAnalytic::class, 'id', 'conversation_id')
                    ->latest('created_at');
    }
 
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }
 
    public function scopeByPlatform($query, string $platform)
    {
        return $query->where('platform', $platform);
    }
 
    public function getMessagesSummary(): array
    {
        $totalMessages = $this->messages()->count();
        $userMessages = $this->messages()->where('role', 'user')->count();
        $assistantMessages = $this->messages()->where('role', 'assistant')->count();
 
        return [
            'total' => $totalMessages,
            'user' => $userMessages,
            'assistant' => $assistantMessages,
            'system' => $totalMessages - $userMessages - $assistantMessages,
        ];
    }
 
    public function updateLastActivity(): void
    {
        $this->update(['last_activity_at' => now()]);
    }
 
    public function getContext(string $key, mixed $default = null): mixed
    {
        return data_get($this->context, $key, $default);
    }
 
    public function setContext(string $key, mixed $value): void
    {
        $context = $this->context ?? [];
        data_set($context, $key, $value);
        $this->update(['context' => $context]);
    }
 
    public function getUserProfile(string $key, mixed $default = null): mixed
    {
        return data_get($this->user_profile, $key, $default);
    }
 
    public function setUserProfile(string $key, mixed $value): void
    {
        $profile = $this->user_profile ?? [];
        data_set($profile, $key, $value);
        $this->update(['user_profile' => $profile]);
    }
 
    public function escalate(string $reason = ''): void
    {
        $this->update([
            'status' => 'escalated',
            'metadata' => array_merge($this->metadata ?? [], [
                'escalated_at' => now()->toISOString(),
                'escalation_reason' => $reason,
            ]),
        ]);
 
        // Notify human agents
        event(new ConversationEscalated($this, $reason));
    }
 
    public function close(): void
    {
        $this->update(['status' => 'closed']);
        
        // Generate analytics
        $this->generateAnalytics();
    }
 
    protected function generateAnalytics(): void
    {
        $messages = $this->messages;
        $firstMessage = $messages->first();
        $lastMessage = $messages->last();
 
        ConversationAnalytic::create([
            'conversation_id' => $this->id,
            'message_count' => $messages->count(),
            'user_message_count' => $messages->where('role', 'user')->count(),
            'assistant_message_count' => $messages->where('role', 'assistant')->count(),
            'avg_response_time' => $this->calculateAvgResponseTime($messages),
            'first_response_at' => $firstMessage?->created_at,
            'last_message_at' => $lastMessage?->created_at,
            'resolved_at' => now(),
        ]);
    }
 
    protected function calculateAvgResponseTime($messages): float
    {
        $responseTimes = [];
        
        for ($i = 0; $i < $messages->count() - 1; $i++) {
            $current = $messages[$i];
            $next = $messages[$i + 1];
            
            if ($current->role === 'user' && $next->role === 'assistant') {
                $responseTimes[] = $current->created_at->diffInSeconds($next->created_at);
            }
        }
        
        return count($responseTimes) > 0 ? array_sum($responseTimes) / count($responseTimes) : 0;
    }
}

2. Message Model#

app/Models/Message.php
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
 
class Message extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'conversation_id',
        'role',
        'content',
        'metadata',
        'tool_calls',
        'tool_results',
        'confidence_score',
        'analytics',
        'processed_at',
    ];
 
    protected $casts = [
        'metadata' => 'array',
        'tool_calls' => 'array',
        'tool_results' => 'array',
        'analytics' => 'array',
        'processed_at' => 'datetime',
        'confidence_score' => 'decimal:2',
    ];
 
    protected $dates = [
        'processed_at',
    ];
 
    public function conversation(): BelongsTo
    {
        return $this->belongsTo(Conversation::class);
    }
 
    public function isUserMessage(): bool
    {
        return $this->role === 'user';
    }
 
    public function isAssistantMessage(): bool
    {
        return $this->role === 'assistant';
    }
 
    public function isSystemMessage(): bool
    {
        return $this->role === 'system';
    }
 
    public function isToolMessage(): bool
    {
        return $this->role === 'tool';
    }
 
    public function getTokenCount(): int
    {
        return $this->metadata['token_count'] ?? 0;
    }
 
    public function getProcessingTime(): float
    {
        return $this->metadata['processing_time'] ?? 0;
    }
 
    public function markAsProcessed(): void
    {
        $this->update(['processed_at' => now()]);
    }
 
    public function addAnalytics(string $key, mixed $value): void
    {
        $analytics = $this->analytics ?? [];
        data_set($analytics, $key, $value);
        $this->update(['analytics' => $analytics]);
    }
 
    public static function boot()
    {
        parent::boot();
 
        static::created(function ($message) {
            // Update conversation's last activity
            $message->conversation->updateLastActivity();
            
            // Trigger message processing if it's a user message
            if ($message->isUserMessage()) {
                ProcessMessageJob::dispatch($message);
            }
        });
    }
}

AI Service Implementation#

1. OpenAI Service with Advanced Features#

app/Services/AI/ChatbotService.php
<?php
 
namespace App\Services\AI;
 
use App\Models\Conversation;
use App\Models\Message;
use App\Models\KbArticle;
use App\Models\KbFaq;
use App\Jobs\ProcessMessageJob;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
 
class ChatbotService
{
    private string $apiKey;
    private string $model;
    private array $defaultOptions;
 
    public function __construct()
    {
        $this->apiKey = config('services.openai.api_key');
        $this->model = config('chatbot.default_model', 'gpt-4');
        $this->defaultOptions = [
            'model' => $this->model,
            'temperature' => 0.7,
            'max_tokens' => 1000,
            'top_p' => 1,
            'frequency_penalty' => 0,
            'presence_penalty' => 0,
        ];
    }
 
    public function processMessage(Message $message): string
    {
        $conversation = $message->conversation;
        
        // Build conversation context
        $context = $this->buildContext($conversation);
        
        // Retrieve relevant knowledge base content
        $knowledgeContext = $this->retrieveKnowledgeBase($message->content, $conversation->language);
        
        // Generate system message
        $systemMessage = $this->buildSystemMessage($conversation, $knowledgeContext);
        
        // Prepare messages for API
        $apiMessages = [
            ['role' => 'system', 'content' => $systemMessage],
            ...$context,
            ['role' => 'user', 'content' => $message->content],
        ];
 
        // Make API call
        $response = $this->callOpenAI($apiMessages);
        
        // Store response metrics
        $this->storeMessageMetrics($message, $response);
        
        return $response['content'];
    }
 
    protected function buildContext(Conversation $conversation): array
    {
        $messages = $conversation->messages()
            ->where('role', '!=', 'system')
            ->orderBy('created_at')
            ->take(20) // Keep last 20 messages for context
            ->get();
 
        return $messages->map(function ($message) {
            return [
                'role' => $message->role,
                'content' => $message->content,
            ];
        })->toArray();
    }
 
    protected function retrieveKnowledgeBase(string $query, string $language = 'en'): array
    {
        // Generate query embedding
        $queryEmbedding = $this->generateEmbedding($query);
        
        if (!$queryEmbedding) {
            return [];
        }
 
        // Search for relevant articles
        $relevantArticles = $this->searchKnowledgeBase($queryEmbedding, $language, 3);
        
        // Search for relevant FAQs
        $relevantFaqs = $this->searchFaqs($queryEmbedding, $language, 2);
 
        return [
            'articles' => $relevantArticles,
            'faqs' => $relevantFaqs,
        ];
    }
 
    protected function searchKnowledgeBase(array $embedding, string $language, int $limit): array
    {
        $articles = KbArticle::where('language', $language)
            ->where('status', 'published')
            ->whereNotNull('embeddings')
            ->get();
 
        $similarities = $articles->map(function ($article) use ($embedding) {
            $similarity = $this->cosineSimilarity($embedding, $article->embeddings);
            return [
                'article' => $article,
                'similarity' => $similarity,
            ];
        })->sortByDesc('similarity')->take($limit);
 
        return $similarities->filter(function ($item) {
            return $item['similarity'] > 0.7; // Similarity threshold
        })->map(function ($item) {
            return [
                'title' => $item['article']->title,
                'content' => Str::limit($item['article']->content, 500),
                'url' => route('kb.article', $item['article']->slug),
                'similarity' => $item['similarity'],
            ];
        })->toArray();
    }
 
    protected function searchFaqs(array $embedding, string $language, int $limit): array
    {
        $faqs = KbFaq::where('language', $language)
            ->where('is_active', true)
            ->whereNotNull('embeddings')
            ->get();
 
        $similarities = $faqs->map(function ($faq) use ($embedding) {
            $similarity = $this->cosineSimilarity($embedding, $faq->embeddings);
            return [
                'faq' => $faq,
                'similarity' => $similarity,
            ];
        })->sortByDesc('similarity')->take($limit);
 
        return $similarities->filter(function ($item) {
            return $item['similarity'] > 0.8; // Higher threshold for FAQs
        })->map(function ($item) {
            return [
                'question' => $item['faq']->question,
                'answer' => $item['faq']->answer,
                'similarity' => $item['similarity'],
            ];
        })->toArray();
    }
 
    protected function buildSystemMessage(Conversation $conversation, array $knowledgeContext): string
    {
        $basePrompt = $this->getBasePrompt($conversation->language);
        
        $contextInfo = $this->formatKnowledgeContext($knowledgeContext);
        
        $userProfile = $conversation->user_profile ?? [];
        $personalization = $this->buildPersonalization($userProfile);
        
        return "{$basePrompt}\n\n{$personalization}\n\n{$contextInfo}";
    }
 
    protected function getBasePrompt(string $language): string
    {
        $prompts = [
            'en' => "You are an intelligent and helpful AI assistant. Provide accurate, concise, and friendly responses. Use the provided knowledge base information when relevant. If you don't know something, admit it honestly. Always maintain a professional and supportive tone.",
            
            'es' => "Eres un asistente de IA inteligente y servicial. Proporciona respuestas precisas, concisas y amigables. Utiliza la información de la base de conocimientos cuando sea relevante. Si no sabes algo, admítelo honestamente. Mantén siempre un tono profesional y de apoyo.",
            
            'fr' => "Vous êtes un assistant IA intelligent et utile. Fournissez des réponses précises, concises et amicales. Utilisez les informations de la base de connaissances lorsque cela est pertinent. Si vous ne savez pas quelque chose, admettez-le honnêtement. Maintenez toujours un ton professionnel et de soutien.",
        ];
 
        return $prompts[$language] ?? $prompts['en'];
    }
 
    protected function formatKnowledgeContext(array $knowledgeContext): string
    {
        $context = "Relevant Information:\n\n";
        
        if (!empty($knowledgeContext['articles'])) {
            $context .= "Articles:\n";
            foreach ($knowledgeContext['articles'] as $article) {
                $context .= "- {$article['title']}: {$article['content']}\n";
            }
            $context .= "\n";
        }
        
        if (!empty($knowledgeContext['faqs'])) {
            $context .= "FAQs:\n";
            foreach ($knowledgeContext['faqs'] as $faq) {
                $context .= "Q: {$faq['question']}\nA: {$faq['answer']}\n\n";
            }
        }
        
        return $context;
    }
 
    protected function buildPersonalization(array $userProfile): string
    {
        $personalization = '';
        
        if (isset($userProfile['name'])) {
            $personalization .= "The user's name is {$userProfile['name']}. ";
        }
        
        if (isset($userProfile['preferences'])) {
            $prefs = $userProfile['preferences'];
            if (isset($prefs['communication_style'])) {
                $personalization .= "The user prefers a {$prefs['communication_style']} communication style. ";
            }
            if (isset($prefs['technical_level'])) {
                $personalization .= "The user's technical level is: {$prefs['technical_level']}. ";
            }
        }
        
        if (isset($userProfile['previous_topics'])) {
            $topics = implode(', ', array_slice($userProfile['previous_topics'], -5));
            $personalization .= "Previously discussed topics: {$topics}. ";
        }
        
        return $personalization;
    }
 
    protected function callOpenAI(array $messages): array
    {
        $startTime = microtime(true);
        
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ])->post('https://api.openai.com/v1/chat/completions', [
                'model' => $this->defaultOptions['model'],
                'messages' => $messages,
                'temperature' => $this->defaultOptions['temperature'],
                'max_tokens' => $this->defaultOptions['max_tokens'],
                'top_p' => $this->defaultOptions['top_p'],
                'frequency_penalty' => $this->defaultOptions['frequency_penalty'],
                'presence_penalty' => $this->defaultOptions['presence_penalty'],
            ]);
 
            if (!$response->successful()) {
                throw new \Exception("OpenAI API Error: {$response->body()}");
            }
 
            $data = $response->json();
            $processingTime = microtime(true) - $startTime;
 
            return [
                'content' => $data['choices'][0]['message']['content'],
                'usage' => $data['usage'],
                'processing_time' => $processingTime,
                'model' => $data['model'],
            ];
 
        } catch (\Exception $e) {
            Log::error('OpenAI API call failed', [
                'error' => $e->getMessage(),
                'messages' => $messages,
            ]);
            
            throw $e;
        }
    }
 
    protected function generateEmbedding(string $text): ?array
    {
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ])->post('https://api.openai.com/v1/embeddings', [
                'model' => 'text-embedding-ada-002',
                'input' => $text,
            ]);
 
            if (!$response->successful()) {
                throw new \Exception("Embedding generation failed: {$response->body()}");
            }
 
            $data = $response->json();
            return $data['data'][0]['embedding'];
 
        } catch (\Exception $e) {
            Log::error('Embedding generation failed', [
                'error' => $e->getMessage(),
                'text' => Str::limit($text, 100),
            ]);
            
            return null;
        }
    }
 
    protected function cosineSimilarity(array $vectorA, array $vectorB): float
    {
        $dotProduct = 0;
        $magnitudeA = 0;
        $magnitudeB = 0;
 
        for ($i = 0; $i < count($vectorA); $i++) {
            $dotProduct += $vectorA[$i] * $vectorB[$i];
            $magnitudeA += $vectorA[$i] ** 2;
            $magnitudeB += $vectorB[$i] ** 2;
        }
 
        $magnitudeA = sqrt($magnitudeA);
        $magnitudeB = sqrt($magnitudeB);
 
        if ($magnitudeA == 0 || $magnitudeB == 0) {
            return 0;
        }
 
        return $dotProduct / ($magnitudeA * $magnitudeB);
    }
 
    protected function storeMessageMetrics(Message $message, array $response): void
    {
        $metadata = [
            'token_count' => $response['usage']['total_tokens'] ?? 0,
            'prompt_tokens' => $response['usage']['prompt_tokens'] ?? 0,
            'completion_tokens' => $response['usage']['completion_tokens'] ?? 0,
            'processing_time' => $response['processing_time'],
            'model' => $response['model'],
        ];
 
        $message->update(['metadata' => $metadata]);
 
        // Store performance metrics
        $this->storePerformanceMetrics($response);
    }
 
    protected function storePerformanceMetrics(array $response): void
    {
        $today = now()->toDateString();
        $model = $response['model'];
 
        Cache::lock("ai_metrics_{$today}_{$model}", 10)->block(5, function () use ($today, $model, $response) {
            $metrics = \App\Models\AiPerformanceMetric::firstOrCreate(
                ['date' => $today, 'model_used' => $model],
                [
                    'total_requests' => 0,
                    'successful_responses' => 0,
                    'failed_responses' => 0,
                    'total_tokens_used' => 0,
                    'total_cost' => 0,
                ]
            );
 
            $metrics->increment('total_requests');
            $metrics->increment('successful_responses');
            $metrics->increment('total_tokens_used', $response['usage']['total_tokens'] ?? 0);
            
            // Calculate cost (approximate)
            $cost = $this->calculateCost($response['usage'], $model);
            $metrics->increment('total_cost', $cost);
            
            // Update average response time
            $newAvgTime = (($metrics->avg_response_time * ($metrics->total_requests - 1)) + $response['processing_time']) / $metrics->total_requests;
            $metrics->avg_response_time = $newAvgTime;
            
            $metrics->save();
        });
    }
 
    protected function calculateCost(array $usage, string $model): float
    {
        // Approximate pricing (adjust based on actual OpenAI pricing)
        $pricing = [
            'gpt-4' => ['prompt' => 0.03, 'completion' => 0.06],
            'gpt-4-32k' => ['prompt' => 0.06, 'completion' => 0.12],
            'gpt-3.5-turbo' => ['prompt' => 0.0015, 'completion' => 0.002],
        ];
 
        $modelPricing = $pricing[$model] ?? $pricing['gpt-3.5-turbo'];
        
        $promptCost = ($usage['prompt_tokens'] / 1000) * $modelPricing['prompt'];
        $completionCost = ($usage['completion_tokens'] / 1000) * $modelPricing['completion'];
        
        return $promptCost + $completionCost;
    }
 
    public function detectIntent(string $message): array
    {
        $prompt = "Analyze the following user message and detect the primary intent. Respond with a JSON object containing:
{
  \"intent\": \"main_intent\",
  \"confidence\": 0.95,
  \"entities\": [
    {\"type\": \"entity_type\", \"value\": \"entity_value\"}
  ],
  \"sentiment\": \"positive|negative|neutral\"
}
 
Common intents: greeting, question, complaint, request_help, feedback, small_talk, technical_support, billing_inquiry
 
Message: {$message}";
 
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ])->post('https://api.openai.com/v1/chat/completions', [
                'model' => 'gpt-3.5-turbo',
                'messages' => [
                    ['role' => 'system', 'content' => 'You are an intent detection expert. Always respond with valid JSON.'],
                    ['role' => 'user', 'content' => $prompt],
                ],
                'temperature' => 0.1,
                'max_tokens' => 200,
            ]);
 
            if ($response->successful()) {
                $content = $response->json()['choices'][0]['message']['content'];
                return json_decode($content, true) ?? [];
            }
        } catch (\Exception $e) {
            Log::error('Intent detection failed', ['error' => $e->getMessage()]);
        }
 
        return [
            'intent' => 'unknown',
            'confidence' => 0.5,
            'entities' => [],
            'sentiment' => 'neutral',
        ];
    }
}

2. Multi-Platform Integration Service#

app/Services/Platform/PlatformManager.php
<?php
 
namespace App\Services\Platform;
 
use App\Models\Conversation;
use App\Models\Message;
use App\Services\Platform\Platforms\WebPlatform;
use App\Services\Platform\Platforms\SlackPlatform;
use App\Services\Platform\Platforms\DiscordPlatform;
use App\Services\Platform\Platforms\WhatsAppPlatform;
 
class PlatformManager
{
    protected array $platforms = [];
 
    public function __construct()
    {
        $this->platforms = [
            'web' => new WebPlatform(),
            'slack' => new SlackPlatform(),
            'discord' => new DiscordPlatform(),
            'whatsapp' => new WhatsAppPlatform(),
        ];
    }
 
    public function getPlatform(string $platform): ?PlatformInterface
    {
        return $this->platforms[$platform] ?? null;
    }
 
    public function sendMessage(Conversation $conversation, string $content, array $options = []): bool
    {
        $platform = $this->getPlatform($conversation->platform);
        
        if (!$platform) {
            Log::error("Unknown platform: {$conversation->platform}");
            return false;
        }
 
        try {
            // Create message in database
            $message = Message::create([
                'conversation_id' => $conversation->id,
                'role' => 'assistant',
                'content' => $content,
                'metadata' => $options,
            ]);
 
            // Send message through platform
            $success = $platform->sendMessage($conversation, $content, $options);
 
            if ($success) {
                $message->markAsProcessed();
            }
 
            return $success;
 
        } catch (\Exception $e) {
            Log::error("Failed to send message via {$conversation->platform}", [
                'error' => $e->getMessage(),
                'conversation_id' => $conversation->id,
            ]);
            
            return false;
        }
    }
 
    public function handleIncomingMessage(string $platform, array $data): ?Conversation
    {
        $platformService = $this->getPlatform($platform);
        
        if (!$platformService) {
            Log::error("Unknown platform: {$platform}");
            return null;
        }
 
        try {
            // Validate and process incoming message
            $messageData = $platformService->validateIncomingMessage($data);
            
            // Find or create conversation
            $conversation = $this->findOrCreateConversation($platform, $messageData);
            
            // Create message
            Message::create([
                'conversation_id' => $conversation->id,
                'role' => 'user',
                'content' => $messageData['content'],
                'metadata' => $messageData['metadata'] ?? [],
            ]);
 
            return $conversation;
 
        } catch (\Exception $e) {
            Log::error("Failed to handle incoming message from {$platform}", [
                'error' => $e->getMessage(),
                'data' => $data,
            ]);
            
            return null;
        }
    }
 
    protected function findOrCreateConversation(string $platform, array $messageData): Conversation
    {
        // Try to find existing conversation
        $conversation = Conversation::where('platform', $platform)
            ->where('session_id', $messageData['session_id'])
            ->active()
            ->first();
 
        if ($conversation) {
            $conversation->updateLastActivity();
            return $conversation;
        }
 
        // Create new conversation
        return Conversation::create([
            'session_id' => $messageData['session_id'],
            'user_id' => $messageData['user_id'] ?? null,
            'platform' => $platform,
            'platform_data' => $messageData['platform_data'] ?? [],
            'language' => $messageData['language'] ?? 'en',
            'status' => 'active',
        ]);
    }
 
    public function getPlatformUser(string $platform, string $userId): ?array
    {
        $platformService = $this->getPlatform($platform);
        
        if (!$platformService) {
            return null;
        }
 
        return $platformService->getUserInfo($userId);
    }
 
    public function isTyping(Conversation $conversation, bool $isTyping): void
    {
        $platform = $this->getPlatform($conversation->platform);
        
        if ($platform && method_exists($platform, 'sendTypingIndicator')) {
            $platform->sendTypingIndicator($conversation, $isTyping);
        }
    }
}

3. Slack Platform Integration#

app/Services/Platform/Platforms/SlackPlatform.php
<?php
 
namespace App\Services\Platform\Platforms;
 
use App\Models\Conversation;
use App\Services\Platform\PlatformInterface;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
 
class SlackPlatform implements PlatformInterface
{
    private string $botToken;
    private string $signingSecret;
 
    public function __construct()
    {
        $this->botToken = config('services.slack.bot_token');
        $this->signingSecret = config('services.slack.signing_secret');
    }
 
    public function sendMessage(Conversation $conversation, string $content, array $options = []): bool
    {
        $channel = $conversation->getContext('slack_channel');
        
        if (!$channel) {
            Log::error('No Slack channel found for conversation', [
                'conversation_id' => $conversation->id,
            ]);
            return false;
        }
 
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->botToken,
                'Content-Type' => 'application/json',
            ])->post('https://slack.com/api/chat.postMessage', [
                'channel' => $channel,
                'text' => $content,
                'blocks' => $options['blocks'] ?? null,
                'thread_ts' => $conversation->getContext('slack_thread_ts'),
            ]);
 
            return $response->json()['ok'] ?? false;
 
        } catch (\Exception $e) {
            Log::error('Failed to send Slack message', [
                'error' => $e->getMessage(),
                'conversation_id' => $conversation->id,
            ]);
            
            return false;
        }
    }
 
    public function validateIncomingMessage(array $data): array
    {
        // Verify Slack signature
        if (!$this->verifySignature($data)) {
            throw new \Exception('Invalid Slack signature');
        }
 
        // Handle different event types
        if ($data['type'] === 'url_verification') {
            return [
                'type' => 'verification',
                'challenge' => $data['challenge'],
            ];
        }
 
        if ($data['type'] === 'event_callback') {
            $event = $data['event'];
            
            if ($event['type'] === 'message' && !isset($event['bot_id'])) {
                return [
                    'type' => 'message',
                    'session_id' => $event['channel'],
                    'content' => $event['text'],
                    'user_id' => $event['user'],
                    'platform_data' => [
                        'channel' => $event['channel'],
                        'thread_ts' => $event['thread_ts'] ?? null,
                        'user' => $event['user'],
                    ],
                    'metadata' => [
                        'slack_event_type' => $event['type'],
                        'timestamp' => $event['ts'],
                    ],
                ];
            }
        }
 
        throw new \Exception('Unsupported event type');
    }
 
    public function getUserInfo(string $userId): ?array
    {
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->botToken,
            ])->get("https://slack.com/api/users.info", [
                'user' => $userId,
            ]);
 
            if ($response->json()['ok']) {
                $user = $response->json()['user'];
                
                return [
                    'id' => $user['id'],
                    'name' => $user['name'],
                    'display_name' => $user['profile']['display_name'],
                    'email' => $user['profile']['email'] ?? null,
                    'avatar' => $user['profile']['image_72'],
                ];
            }
        } catch (\Exception $e) {
            Log::error('Failed to fetch Slack user info', [
                'error' => $e->getMessage(),
                'user_id' => $userId,
            ]);
        }
 
        return null;
    }
 
    public function sendTypingIndicator(Conversation $conversation, bool $isTyping): void
    {
        // Slack doesn't have a specific typing indicator API
        // We can send a temporary message instead
        if ($isTyping) {
            $this->sendMessage($conversation, '_Typing..._', [
                'blocks' => [
                    [
                        'type' => 'context',
                        'elements' => [
                            [
                                'type' => 'image',
                                'image_url' => 'https://slack.com/img/typing-indicator.gif',
                                'alt_text' => 'Bot is typing...',
                            ],
                        ],
                    ],
                ],
            ]);
        }
    }
 
    protected function verifySignature(array $data): bool
    {
        if (!isset($data['headers']['X-Slack-Signature']) || 
            !isset($data['headers']['X-Slack-Request-Timestamp'])) {
            return false;
        }
 
        $signature = $data['headers']['X-Slack-Signature'];
        $timestamp = $data['headers']['X-Slack-Request-Timestamp'];
        
        // Check if timestamp is recent (within 5 minutes)
        if (abs(time() - $timestamp) > 300) {
            return false;
        }
 
        $baseString = "v0:{$timestamp}:" . json_encode($data['body']);
        $expectedSignature = 'v0=' . hash_hmac('sha256', $baseString, $this->signingSecret);
 
        return hash_equals($expectedSignature, $signature);
    }
}

Advanced Features#

1. Sentiment Analysis Service#

app/Services/Analytics/SentimentAnalysisService.php
<?php
 
namespace App\Services\Analytics;
 
use App\Models\Conversation;
use App\Models\Message;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
 
class SentimentAnalysisService
{
    private string $apiKey;
 
    public function __construct()
    {
        $this->apiKey = config('services.openai.api_key');
    }
 
    public function analyzeMessage(Message $message): array
    {
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ])->post('https://api.openai.com/v1/chat/completions', [
                'model' => 'gpt-3.5-turbo',
                'messages' => [
                    [
                        'role' => 'system',
                        'content' => 'Analyze the sentiment of the following message. Respond with a JSON object containing:
{
  "sentiment": "positive|negative|neutral",
  "confidence": 0.95,
  "emotions": ["joy", "anger", "sadness", "fear", "surprise"],
  "urgency": "low|medium|high"
}',
                    ],
                    [
                        'role' => 'user',
                        'content' => $message->content,
                    ],
                ],
                'temperature' => 0.1,
                'max_tokens' => 100,
            ]);
 
            if ($response->successful()) {
                $content = $response->json()['choices'][0]['message']['content'];
                $analysis = json_decode($content, true);
                
                if ($analysis) {
                    $message->addAnalytics('sentiment', $analysis);
                    return $analysis;
                }
            }
        } catch (\Exception $e) {
            Log::error('Sentiment analysis failed', [
                'error' => $e->getMessage(),
                'message_id' => $message->id,
            ]);
        }
 
        return [
            'sentiment' => 'neutral',
            'confidence' => 0.5,
            'emotions' => [],
            'urgency' => 'low',
        ];
    }
 
    public function analyzeConversation(Conversation $conversation): array
    {
        $messages = $conversation->messages()->where('role', 'user')->get();
        
        if ($messages->isEmpty()) {
            return [
                'overall_sentiment' => 'neutral',
                'sentiment_trend' => [],
                'emotional_summary' => [],
                'urgency_level' => 'low',
            ];
        }
 
        $sentiments = [];
        $emotions = [];
        $urgencies = [];
 
        foreach ($messages as $message) {
            $analysis = $this->analyzeMessage($message);
            $sentiments[] = $analysis['sentiment'];
            $emotions = array_merge($emotions, $analysis['emotions'] ?? []);
            $urgencies[] = $analysis['urgency'] ?? 'low';
        }
 
        // Calculate overall sentiment
        $sentimentCounts = array_count_values($sentiments);
        $overallSentiment = array_keys($sentimentCounts, max($sentimentCounts))[0];
 
        // Calculate emotion frequency
        $emotionCounts = array_count_values($emotions);
        arsort($emotionCounts);
        $emotionalSummary = array_slice($emotionCounts, 0, 5, true);
 
        // Determine urgency level
        $urgencyCounts = array_count_values($urgencies);
        $urgencyLevel = array_keys($urgencyCounts, max($urgencyCounts))[0];
 
        return [
            'overall_sentiment' => $overallSentiment,
            'sentiment_trend' => $sentiments,
            'emotional_summary' => $emotionalSummary,
            'urgency_level' => $urgencyLevel,
        ];
    }
 
    public function detectEscalationTriggers(Conversation $conversation): array
    {
        $recentMessages = $conversation->messages()
            ->where('role', 'user')
            ->where('created_at', '>', now()->subMinutes(30))
            ->get();
 
        $triggers = [];
        $angerCount = 0;
        $urgencyCount = 0;
        $complaintKeywords = ['frustrated', 'angry', 'terrible', 'worst', 'unacceptable', 'disappointed'];
 
        foreach ($recentMessages as $message) {
            $analysis = $this->analyzeMessage($message);
            
            if ($analysis['sentiment'] === 'negative') {
                $angerCount++;
            }
            
            if ($analysis['urgency'] === 'high') {
                $urgencyCount++;
            }
 
            // Check for complaint keywords
            $content = strtolower($message->content);
            foreach ($complaintKeywords as $keyword) {
                if (strpos($content, $keyword) !== false) {
                    $triggers[] = "Complaint keyword detected: {$keyword}";
                }
            }
        }
 
        if ($angerCount >= 2) {
            $triggers[] = 'Multiple negative messages detected';
        }
 
        if ($urgencyCount >= 2) {
            $triggers[] = 'High urgency in recent messages';
        }
 
        return $triggers;
    }
}

2. Analytics Dashboard Service#

app/Services/Analytics/AnalyticsService.php
<?php
 
namespace App\Services\Analytics;
 
use App\Models\Conversation;
use App\Models\Message;
use App\Models\ConversationAnalytic;
use App\Models\PlatformStat;
use App\Models\AiPerformanceMetric;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
 
class AnalyticsService
{
    public function getDashboardStats(string $period = '7d'): array
    {
        $dateRange = $this->getDateRange($period);
        
        return [
            'conversations' => $this->getConversationStats($dateRange),
            'messages' => $this->getMessageStats($dateRange),
            'performance' => $this->getPerformanceStats($dateRange),
            'platforms' => $this->getPlatformStats($dateRange),
            'satisfaction' => $this->getSatisfactionStats($dateRange),
        ];
    }
 
    public function getConversationStats(array $dateRange): array
    {
        $conversations = Conversation::whereBetween('created_at', $dateRange);
        
        return [
            'total' => $conversations->count(),
            'active' => $conversations->where('status', 'active')->count(),
            'closed' => $conversations->where('status', 'closed')->count(),
            'escalated' => $conversations->where('status', 'escalated')->count(),
            'avg_duration' => $this->getAverageConversationDuration($dateRange),
            'completion_rate' => $this->getCompletionRate($dateRange),
        ];
    }
 
    public function getMessageStats(array $dateRange): array
    {
        $messages = Message::whereBetween('created_at', $dateRange);
        
        return [
            'total' => $messages->count(),
            'user_messages' => $messages->where('role', 'user')->count(),
            'assistant_messages' => $messages->where('role', 'assistant')->count(),
            'avg_response_time' => $this->getAverageResponseTime($dateRange),
            'total_tokens' => $this->getTotalTokensUsed($dateRange),
        ];
    }
 
    public function getPerformanceStats(array $dateRange): array
    {
        $metrics = AiPerformanceMetric::whereBetween('date', $dateRange);
        
        return [
            'total_requests' => $metrics->sum('total_requests'),
            'success_rate' => $this->calculateSuccessRate($dateRange),
            'avg_response_time' => $metrics->avg('avg_response_time'),
            'total_cost' => $metrics->sum('total_cost'),
            'most_used_model' => $this->getMostUsedModel($dateRange),
        ];
    }
 
    public function getPlatformStats(array $dateRange): array
    {
        return PlatformStat::whereBetween('date', $dateRange)
            ->groupBy('platform')
            ->selectRaw('
                platform,
                SUM(conversations_started) as conversations,
                SUM(messages_exchanged) as messages,
                AVG(satisfaction_score) as avg_satisfaction
            ')
            ->get()
            ->toArray();
    }
 
    public function getSatisfactionStats(array $dateRange): array
    {
        $analytics = ConversationAnalytic::whereBetween('created_at', $dateRange)
            ->whereNotNull('satisfaction_score');
        
        return [
            'average_score' => $analytics->avg('satisfaction_score'),
            'total_ratings' => $analytics->count(),
            'distribution' => $this->getSatisfactionDistribution($analytics),
        ];
    }
 
    public function getTopTopics(array $dateRange, int $limit = 10): array
    {
        return Message::whereBetween('created_at', $dateRange)
            ->where('role', 'user')
            ->selectRaw('
                content,
                COUNT(*) as frequency
            ')
            ->groupBy('content')
            ->orderByDesc('frequency')
            ->limit($limit)
            ->get()
            ->toArray();
    }
 
    public function getHourlyActivity(array $dateRange): array
    {
        return Message::whereBetween('created_at', $dateRange)
            ->selectRaw('
                HOUR(created_at) as hour,
                COUNT(*) as message_count,
                SUM(CASE WHEN role = "user" THEN 1 ELSE 0 END) as user_messages,
                SUM(CASE WHEN role = "assistant" THEN 1 ELSE 0 END) as assistant_messages
            ')
            ->groupBy('hour')
            ->orderBy('hour')
            ->get()
            ->keyBy('hour')
            ->toArray();
    }
 
    protected function getDateRange(string $period): array
    {
        $now = now();
        
        switch ($period) {
            case '1d':
                return [$now->copy()->subDay(), $now];
            case '7d':
                return [$now->copy()->subWeek(), $now];
            case '30d':
                return [$now->copy()->subMonth(), $now];
            case '90d':
                return [$now->copy()->subQuarter(), $now];
            default:
                return [$now->copy()->subWeek(), $now];
        }
    }
 
    protected function getAverageConversationDuration(array $dateRange): float
    {
        return ConversationAnalytic::whereBetween('created_at', $dateRange)
            ->whereNotNull('first_response_at')
            ->whereNotNull('resolved_at')
            ->selectRaw('AVG(TIMESTAMPDIFF(SECOND, first_response_at, resolved_at)) as avg_duration')
            ->value('avg_duration') ?? 0;
    }
 
    protected function getCompletionRate(array $dateRange): float
    {
        $total = Conversation::whereBetween('created_at', $dateRange)->count();
        $closed = Conversation::whereBetween('created_at', $dateRange)
            ->where('status', 'closed')
            ->count();
        
        return $total > 0 ? ($closed / $total) * 100 : 0;
    }
 
    protected function getAverageResponseTime(array $dateRange): float
    {
        return Message::whereBetween('created_at', $dateRange)
            ->whereNotNull('processed_at')
            ->selectRaw('AVG(TIMESTAMPDIFF(MICROSECOND, created_at, processed_at) / 1000000) as avg_response_time')
            ->value('avg_response_time') ?? 0;
    }
 
    protected function getTotalTokensUsed(array $dateRange): int
    {
        return Message::whereBetween('created_at', $dateRange)
            ->whereNotNull('metadata->token_count')
            ->sum('metadata->token_count') ?? 0;
    }
 
    protected function calculateSuccessRate(array $dateRange): float
    {
        $metrics = AiPerformanceMetric::whereBetween('date', $dateRange);
        
        $total = $metrics->sum('total_requests');
        $successful = $metrics->sum('successful_responses');
        
        return $total > 0 ? ($successful / $total) * 100 : 0;
    }
 
    protected function getMostUsedModel(array $dateRange): string
    {
        return AiPerformanceMetric::whereBetween('date', $dateRange)
            ->selectRaw('model_used, SUM(total_requests) as total_requests')
            ->groupBy('model_used')
            ->orderByDesc('total_requests')
            ->value('model_used') ?? 'unknown';
    }
 
    protected function getSatisfactionDistribution($analytics): array
    {
        $distribution = [
            '5' => 0,
            '4' => 0,
            '3' => 0,
            '2' => 0,
            '1' => 0,
        ];
 
        $analytics->each(function ($item) use (&$distribution) {
            $score = (int) round($item->satisfaction_score);
            if (isset($distribution[$score])) {
                $distribution[$score]++;
            }
        });
 
        return $distribution;
    }
}

API Controllers#

1. Chatbot API Controller#

app/Http/Controllers/API/ChatbotController.php
<?php
 
namespace App\Http\Controllers\API;
 
use App\Http\Controllers\Controller;
use App\Models\Conversation;
use App\Models\Message;
use App\Services\AI\ChatbotService;
use App\Services\Platform\PlatformManager;
use App\Services\Analytics\SentimentAnalysisService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
 
class ChatbotController extends Controller
{
    private ChatbotService $chatbotService;
    private PlatformManager $platformManager;
    private SentimentAnalysisService $sentimentService;
 
    public function __construct(
        ChatbotService $chatbotService,
        PlatformManager $platformManager,
        SentimentAnalysisService $sentimentService
    ) {
        $this->chatbotService = $chatbotService;
        $this->platformManager = $platformManager;
        $this->sentimentService = $sentimentService;
    }
 
    public function startConversation(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'platform' => 'required|in:web,slack,discord,whatsapp',
            'language' => 'nullable|string|max:10',
            'user_data' => 'nullable|array',
        ]);
 
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
 
        try {
            $conversation = Conversation::create([
                'session_id' => 'web_' . uniqid(),
                'user_id' => Auth::id(),
                'platform' => $request->input('platform'),
                'language' => $request->input('language', 'en'),
                'platform_data' => $request->input('user_data', []),
                'status' => 'active',
            ]);
 
            // Send welcome message
            $welcomeMessage = $this->getWelcomeMessage($conversation->language);
            $this->platformManager->sendMessage($conversation, $welcomeMessage);
 
            return response()->json([
                'message' => 'Conversation started successfully',
                'conversation' => $conversation->load('messages'),
            ], 201);
 
        } catch (\Exception $e) {
            Log::error('Failed to start conversation', [
                'error' => $e->getMessage(),
                'user_id' => Auth::id(),
            ]);
 
            return response()->json([
                'message' => 'Failed to start conversation',
            ], 500);
        }
    }
 
    public function sendMessage(Request $request, string $sessionId): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'message' => 'required|string|max:2000',
            'platform' => 'nullable|in:web,slack,discord,whatsapp',
        ]);
 
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
 
        try {
            $conversation = Conversation::where('session_id', $sessionId)
                ->active()
                ->firstOrFail();
 
            // Check if user is authorized
            if (Auth::id() && $conversation->user_id !== Auth::id()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
 
            // Create user message
            $userMessage = Message::create([
                'conversation_id' => $conversation->id,
                'role' => 'user',
                'content' => $request->input('message'),
            ]);
 
            // Detect intent and sentiment
            $intent = $this->chatbotService->detectIntent($userMessage->content);
            $sentiment = $this->sentimentService->analyzeMessage($userMessage);
 
            // Check for escalation triggers
            $escalationTriggers = $this->sentimentService->detectEscalationTriggers($conversation);
            
            if (!empty($escalationTriggers)) {
                $conversation->escalate(implode(', ', $escalationTriggers));
                
                return response()->json([
                    'message' => 'This conversation has been escalated to a human agent.',
                    'escalated' => true,
                ]);
            }
 
            // Process message and generate response
            $response = $this->chatbotService->processMessage($userMessage);
            
            // Send response
            $this->platformManager->sendMessage($conversation, $response);
 
            return response()->json([
                'message' => 'Message processed successfully',
                'response' => $response,
                'intent' => $intent,
                'sentiment' => $sentiment,
            ]);
 
        } catch (\Exception $e) {
            Log::error('Failed to process message', [
                'error' => $e->getMessage(),
                'session_id' => $sessionId,
            ]);
 
            return response()->json([
                'message' => 'Failed to process message',
            ], 500);
        }
    }
 
    public function getConversation(string $sessionId): JsonResponse
    {
        try {
            $conversation = Conversation::where('session_id', $sessionId)
                ->with(['messages' => function ($query) {
                    $query->orderBy('created_at');
                }])
                ->firstOrFail();
 
            // Check authorization
            if (Auth::id() && $conversation->user_id !== Auth::id()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
 
            return response()->json([
                'conversation' => $conversation,
            ]);
 
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Conversation not found',
            ], 404);
        }
    }
 
    public function endConversation(string $sessionId): JsonResponse
    {
        try {
            $conversation = Conversation::where('session_id', $sessionId)
                ->active()
                ->firstOrFail();
 
            // Check authorization
            if (Auth::id() && $conversation->user_id !== Auth::id()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
 
            $conversation->close();
 
            return response()->json([
                'message' => 'Conversation ended successfully',
            ]);
 
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Conversation not found or already closed',
            ], 404);
        }
    }
 
    public function rateConversation(Request $request, string $sessionId): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'rating' => 'required|integer|min:1|max:5',
            'feedback' => 'nullable|string|max:500',
        ]);
 
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
 
        try {
            $conversation = Conversation::where('session_id', $sessionId)
                ->firstOrFail();
 
            // Check authorization
            if (Auth::id() && $conversation->user_id !== Auth::id()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
 
            // Update analytics with rating
            $analytics = $conversation->analytics()->firstOrCreate([
                'conversation_id' => $conversation->id,
            ]);
 
            $analytics->update([
                'satisfaction_score' => $request->input('rating'),
                'resolved_at' => now(),
            ]);
 
            // Store feedback
            $conversation->setContext('user_feedback', [
                'rating' => $request->input('rating'),
                'feedback' => $request->input('feedback'),
                'rated_at' => now()->toISOString(),
            ]);
 
            return response()->json([
                'message' => 'Thank you for your feedback!',
            ]);
 
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to submit rating',
            ], 500);
        }
    }
 
    protected function getWelcomeMessage(string $language): string
    {
        $messages = [
            'en' => "Hello! I'm your AI assistant. How can I help you today?",
            'es' => "¡Hola! Soy tu asistente de IA. ¿Cómo puedo ayudarte hoy?",
            'fr' => "Bonjour ! Je suis votre assistant IA. Comment puis-je vous aider aujourd'hui ?",
            'de' => "Hallo! Ich bin Ihr KI-Assistent. Wie kann ich Ihnen heute helfen?",
            'it' => "Ciao! Sono il tuo assistente AI. Come posso aiutarti oggi?",
        ];
 
        return $messages[$language] ?? $messages['en'];
    }
}

Testing and Quality Assurance#

1. Chatbot Service Tests#

tests/Feature/ChatbotServiceTest.php
<?php
 
namespace Tests\Feature;
 
use Tests\TestCase;
use App\Models\User;
use App\Models\Conversation;
use App\Models\Message;
use App\Services\AI\ChatbotService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
 
class ChatbotServiceTest extends TestCase
{
    use RefreshDatabase;
 
    private ChatbotService $chatbotService;
    private User $user;
 
    protected function setUp(): void
    {
        parent::setUp();
        
        $this->chatbotService = new ChatbotService();
        $this->user = User::factory()->create();
    }
 
    public function test_process_message_generates_response()
    {
        $conversation = Conversation::factory()->create([
            'user_id' => $this->user->id,
            'language' => 'en',
        ]);
 
        $message = Message::factory()->create([
            'conversation_id' => $conversation->id,
            'role' => 'user',
            'content' => 'Hello, how are you?',
        ]);
 
        // Mock the OpenAI API call
        $mockResponse = [
            'content' => 'Hello! I am doing well, thank you for asking. How can I assist you today?',
            'usage' => [
                'total_tokens' => 25,
                'prompt_tokens' => 15,
                'completion_tokens' => 10,
            ],
            'processing_time' => 1.5,
            'model' => 'gpt-4',
        ];
 
        $service = Mockery::mock(ChatbotService::class)->makePartial();
        $service->shouldReceive('callOpenAI')->once()->andReturn($mockResponse);
 
        $response = $service->processMessage($message);
 
        $this->assertIsString($response);
        $this->assertNotEmpty($response);
        $this->assertEquals($mockResponse['content'], $response);
    }
 
    public function test_detect_intent_returns_valid_structure()
    {
        $intent = $this->chatbotService->detectIntent('I need help with my account');
 
        $this->assertIsArray($intent);
        $this->assertArrayHasKey('intent', $intent);
        $this->assertArrayHasKey('confidence', $intent);
        $this->assertArrayHasKey('entities', $intent);
        $this->assertArrayHasKey('sentiment', $intent);
 
        $this->assertContains($intent['sentiment'], ['positive', 'negative', 'neutral']);
        $this->assertGreaterThanOrEqual(0, $intent['confidence']);
        $this->assertLessThanOrEqual(1, $intent['confidence']);
    }
 
    public function test_build_context_includes_relevant_messages()
    {
        $conversation = Conversation::factory()->create([
            'user_id' => $this->user->id,
        ]);
 
        // Create messages
        Message::factory()->count(5)->create([
            'conversation_id' => $conversation->id,
            'role' => 'user',
        ]);
 
        Message::factory()->count(5)->create([
            'conversation_id' => $conversation->id,
            'role' => 'assistant',
        ]);
 
        $reflection = new \ReflectionClass($this->chatbotService);
        $method = $reflection->getMethod('buildContext');
        $method->setAccessible(true);
 
        $context = $method->invoke($this->chatbotService, $conversation);
 
        $this->assertIsArray($context);
        $this->assertNotEmpty($context);
        $this->assertLessThanOrEqual(20, count($context)); // Should limit to last 20 messages
    }
 
    public function test_retrieve_knowledge_base_finds_relevant_content()
    {
        // Mock knowledge base search
        $service = Mockery::mock(ChatbotService::class)->makePartial();
        $service->shouldReceive('generateEmbedding')->andReturn([0.1, 0.2, 0.3]);
        $service->shouldReceive('searchKnowledgeBase')->andReturn([
            [
                'title' => 'Test Article',
                'content' => 'Test content',
                'url' => 'http://example.com/test',
                'similarity' => 0.85,
            ],
        ]);
 
        $reflection = new \ReflectionClass($service);
        $method = $reflection->getMethod('retrieveKnowledgeBase');
        $method->setAccessible(true);
 
        $knowledge = $method->invoke($service, 'How to reset password?', 'en');
 
        $this->assertIsArray($knowledge);
        $this->assertArrayHasKey('articles', $knowledge);
        $this->assertArrayHasKey('faqs', $knowledge);
    }
}

Deployment and Monitoring#

1. Queue Configuration for Message Processing#

// Add custom connection for chatbot processing
'chatbot' => [
    'driver' => 'redis',
    'connection' => 'default',
    'queue' => 'chatbot-messages',
    'retry_after' => 90,
    'block_for' => null,
    'after_commit' => false,
],

2. Monitoring and Health Checks#

app/Http/Controllers/API/HealthController.php
<?php
 
namespace App\Http\Controllers\API;
 
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
 
class HealthController extends Controller
{
    public function check(): JsonResponse
    {
        $health = [
            'status' => 'healthy',
            'timestamp' => now()->toISOString(),
            'version' => config('app.version', '1.0.0'),
            'services' => [],
        ];
 
        // Check database
        try {
            DB::connection()->getPdo();
            $health['services']['database'] = 'healthy';
        } catch (\Exception $e) {
            $health['services']['database'] = 'unhealthy';
            $health['status'] = 'degraded';
        }
 
        // Check Redis
        try {
            Redis::ping();
            $health['services']['redis'] = 'healthy';
        } catch (\Exception $e) {
            $health['services']['redis'] = 'unhealthy';
            $health['status'] = 'degraded';
        }
 
        // Check OpenAI API
        try {
            $response = \Http::withHeaders([
                'Authorization' => 'Bearer ' . config('services.openai.api_key'),
            ])->get('https://api.openai.com/v1/models', [
                'timeout' => 5,
            ]);
 
            $health['services']['openai'] = $response->successful() ? 'healthy' : 'unhealthy';
            
            if (!$response->successful()) {
                $health['status'] = 'degraded';
            }
        } catch (\Exception $e) {
            $health['services']['openai'] = 'unhealthy';
            $health['status'] = 'degraded';
        }
 
        // Check queue workers
        try {
            $queueSize = \Queue::size('chatbot-messages');
            $health['services']['queue'] = $queueSize < 100 ? 'healthy' : 'unhealthy';
            $health['queue_size'] = $queueSize;
            
            if ($queueSize >= 100) {
                $health['status'] = 'degraded';
            }
        } catch (\Exception $e) {
            $health['services']['queue'] = 'unhealthy';
            $health['status'] = 'degraded';
        }
 
        $statusCode = $health['status'] === 'healthy' ? 200 : 503;
 
        return response()->json($health, $statusCode);
    }
}

Conclusion#

This enterprise-grade AI chatbot system demonstrates the power of combining Laravel's robust backend architecture with OpenAI's advanced language models. Here's what we've built:

Key Features Implemented:#

  1. Advanced Conversation Management

    • Context-aware responses with memory
    • Multi-turn conversation handling
    • Session persistence across platforms
  2. Multi-Platform Integration

    • Web chat interface
    • Slack, Discord, and WhatsApp integration
    • Unified conversation management
  3. Intelligent Features

    • Intent detection and analysis
    • Sentiment analysis
    • Automatic escalation triggers
    • Knowledge base integration
  4. Enterprise Analytics

    • Real-time performance metrics
    • Conversation insights
    • User satisfaction tracking
    • Cost monitoring
  5. Scalability & Security

    • Queue-based message processing
    • Rate limiting and abuse protection
    • Comprehensive error handling
    • Health monitoring

Best Practices Demonstrated:#

  • Modular Architecture: Clean separation of concerns
  • Error Handling: Comprehensive error management
  • Performance Optimization: Efficient caching and queuing
  • Security: Input validation and authorization
  • Testing: Unit and feature test coverage
  • Monitoring: Health checks and analytics

Potential Enhancements:#

  • Voice support integration
  • Advanced analytics dashboard
  • Machine learning for response optimization
  • Multi-language support expansion
  • Custom AI model fine-tuning
  • Integration with CRM systems

This chatbot system provides a solid foundation for enterprise-grade conversational AI that can scale with your business needs while maintaining high performance and reliability. 🚀