Building AI Chatbots with Laravel: From Simple to Sophisticated
Beyond Simple Q&A
Modern AI chatbots can maintain context across conversations, remember user preferences, and handle complex multi-turn dialogues. Building one requires thoughtful architecture around conversation state, prompt management, and response handling.
Conversation Architecture
class Conversation extends Model
{
protected $fillable = ['user_id', 'context', 'metadata'];
protected $casts = ['context' => 'array', 'metadata' => 'array'];
public function messages(): HasMany
{
return $this->hasMany(Message::class)->orderBy('created_at');
}
public function addMessage(string $role, string $content): Message
{
return $this->messages()->create([
'role' => $role,
'content' => $content,
]);
}
public function getMessagesForContext(int $limit = 20): array
{
return $this->messages()
->latest()
->take($limit)
->get()
->reverse()
->map(fn ($m) => ['role' => $m->role, 'content' => $m->content])
->values()
->toArray();
}
}
The Chat Service
class ChatService
{
public function __construct(
private AIClient $ai,
private string $systemPrompt
) {}
public function respond(Conversation $conversation, string $userMessage): string
{
// Store user message
$conversation->addMessage('user', $userMessage);
// Build context
$messages = array_merge(
[['role' => 'system', 'content' => $this->systemPrompt]],
$conversation->getMessagesForContext()
);
// Get AI response
$response = $this->ai->chat($messages);
// Store and return
$conversation->addMessage('assistant', $response);
return $response;
}
}
Context Window Management
AI models have token limits. Manage context intelligently:
class ContextManager
{
private int $maxTokens = 4000;
public function buildContext(Conversation $conversation): array
{
$messages = [];
$tokenCount = $this->countTokens($this->systemPrompt);
foreach ($conversation->messages()->latest()->cursor() as $message) {
$messageTokens = $this->countTokens($message->content);
if ($tokenCount + $messageTokens > $this->maxTokens) {
break;
}
array_unshift($messages, [
'role' => $message->role,
'content' => $message->content,
]);
$tokenCount += $messageTokens;
}
return $messages;
}
private function countTokens(string $text): int
{
// Rough estimate: 1 token ≈ 4 characters
return (int) ceil(strlen($text) / 4);
}
}
Real-Time Streaming
For better UX, stream responses as they're generated:
public function streamResponse(Conversation $conversation, string $message): StreamedResponse
{
return response()->stream(function () use ($conversation, $message) {
$conversation->addMessage('user', $message);
$fullResponse = '';
$this->ai->streamChat(
$conversation->getMessagesForContext(),
function ($chunk) use (&$fullResponse) {
$fullResponse .= $chunk;
echo "data: " . json_encode(['content' => $chunk]) . "\n\n";
ob_flush();
flush();
}
);
$conversation->addMessage('assistant', $fullResponse);
echo "data: [DONE]\n\n";
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no',
]);
}
Adding Memory and Personalization
class ConversationMemory
{
public function extractAndStoreFacts(Conversation $conversation, string $message): void
{
$facts = $this->ai->extract($message, [
'user_name',
'preferences',
'mentioned_topics',
]);
$conversation->update([
'metadata' => array_merge(
$conversation->metadata ?? [],
array_filter($facts)
),
]);
}
public function buildPersonalizedPrompt(Conversation $conversation): string
{
$base = "You are a helpful assistant.";
if ($name = $conversation->metadata['user_name'] ?? null) {
$base .= " The user's name is {$name}.";
}
if ($prefs = $conversation->metadata['preferences'] ?? null) {
$base .= " Their preferences: {$prefs}.";
}
return $base;
}
}
Function Calling / Tool Use
Modern AI APIs support function calling for structured interactions:
$tools = [
[
'type' => 'function',
'function' => [
'name' => 'search_products',
'description' => 'Search for products in the catalog',
'parameters' => [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string'],
'category' => ['type' => 'string'],
'max_price' => ['type' => 'number'],
],
'required' => ['query'],
],
],
],
];
// Handle tool calls in your response processing
if ($response['tool_calls'] ?? false) {
foreach ($response['tool_calls'] as $call) {
$result = $this->executeFunction($call['function']);
// Feed result back to AI for final response
}
}
Conclusion
Building a sophisticated chatbot requires more than just API calls. Design for conversation state, manage context windows, stream responses for better UX, and leverage function calling for structured interactions. Start simple and add complexity as your requirements grow.
Related Articles
Need Help With Your Project?
I respond to all inquiries within 24 hours. Let's discuss how I can help build your production-ready system.
Get In Touch