AI Chatbot in Drupal: ChatGPT Integration für Customer Support
AI Chatbot in Drupal: ChatGPT Integration für Customer Support
Mit dem Drupal AI Module können Sie intelligente Chatbots direkt in Ihre Website integrieren. Dieser Guide zeigt die komplette Implementation von der Basis-Integration bis zum fortgeschrittenen RAG-System (Retrieval Augmented Generation) mit Drupal-Content-Training.
Architektur-Übersicht
┌─────────────────────────────────────────────┐
│ Drupal Frontend (Twig) │
│ Chatbot Widget │
└───────────────────┬─────────────────────────┘
│
┌───────────────────▼─────────────────────────┐
│ Custom Module (PHP/Service) │
│ - Message Routing │
│ - Session Management │
│ - Context Building │
└───────────────────┬─────────────────────────┘
│
┌───────────┴──────────┐
│ │
┌───────▼────────┐ ┌────────▼───────┐
│ AI Module │ │ Vector Store │
│ (OpenAI) │ │ (Embeddings) │
└────────────────┘ └────────────────┘
Setup: Basis-Chatbot
Module Installation
composer require drupal/ai
composer require drupal/ai_openai
composer require drupal/key
drush en ai ai_openai key -y
Custom Chatbot Module erstellen
drush generate module
# Module name: AI Chatbot
# Machine name: ai_chatbot
# Description: Intelligent chatbot for customer support
ai_chatbot.info.yml:
name: 'AI Chatbot'
type: module
description: 'Intelligent chatbot powered by OpenAI'
core_version_requirement: ^10 || ^11
package: 'AI'
dependencies:
- drupal:node
- ai:ai
- ai:ai_openai
- key:key
Chatbot Service
<?php
// src/Service/ChatbotService.php
namespace Drupal\ai_chatbot\Service;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Database\Connection;
/**
* Chatbot service for AI conversations.
*/
class ChatbotService {
protected $aiProvider;
protected $database;
protected $currentUser;
protected $conversationHistory = [];
public function __construct($ai_provider, Connection $database, AccountProxyInterface $current_user) {
$this->aiProvider = $ai_provider;
$this->database = $database;
$this->currentUser = $current_user;
}
/**
* Process user message and get AI response.
*/
public function chat($message, $session_id = NULL) {
// Session ID generieren falls nicht vorhanden
if (!$session_id) {
$session_id = $this->generateSessionId();
}
// Conversation History laden
$this->loadConversationHistory($session_id);
// System Context mit Drupal-Informationen
$system_context = $this->buildSystemContext();
// User Message zur History hinzufügen
$this->conversationHistory[] = [
'role' => 'user',
'content' => $message,
'timestamp' => time(),
];
// Relevanten Drupal Content abrufen (RAG)
$relevant_content = $this->findRelevantContent($message);
// AI Request mit Context
$messages = array_merge(
[['role' => 'system', 'content' => $system_context]],
$relevant_content ? [['role' => 'system', 'content' => "Relevanter Content:\n" . $relevant_content]] : [],
array_map(function($msg) {
return [
'role' => $msg['role'],
'content' => $msg['content']
];
}, $this->conversationHistory)
);
try {
$response = $this->aiProvider->chat([
'model' => 'gpt-4o-mini',
'messages' => $messages,
'temperature' => 0.7,
'max_tokens' => 500,
]);
$ai_message = $response->getNormalized();
// AI Response zur History hinzufügen
$this->conversationHistory[] = [
'role' => 'assistant',
'content' => $ai_message,
'timestamp' => time(),
];
// Conversation speichern
$this->saveConversationHistory($session_id);
// Metriken tracken
$this->trackMetrics($session_id, $message, $ai_message);
return [
'response' => $ai_message,
'session_id' => $session_id,
'timestamp' => time(),
];
}
catch (\Exception $e) {
\Drupal::logger('ai_chatbot')->error('Chatbot error: @error', [
'@error' => $e->getMessage(),
]);
return [
'response' => 'Entschuldigung, es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.',
'session_id' => $session_id,
'error' => TRUE,
];
}
}
/**
* Build system context with Drupal information.
*/
protected function buildSystemContext() {
$site_name = \Drupal::config('system.site')->get('name');
$site_slogan = \Drupal::config('system.site')->get('slogan');
return <<<CONTEXT
Du bist ein hilfreicher Assistent für die Website "{$site_name}".
Slogan: {$site_slogan}
Deine Aufgaben:
- Beantworte Fragen zu unseren Produkten und Services
- Hilf bei Navigation und Seitensuche
- Gib hilfreiche Tipps und Empfehlungen
- Sei freundlich, professionell und präzise
- Antworte auf Deutsch
- Verweise bei Bedarf auf relevante Seiten
Wenn du etwas nicht weißt, gib das ehrlich zu und biete an, den User an einen menschlichen Support-Mitarbeiter weiterzuleiten.
CONTEXT;
}
/**
* Find relevant Drupal content for context (RAG).
*/
protected function findRelevantContent($query) {
// Einfache Keyword-basierte Suche (kann mit Vector Search erweitert werden)
$keywords = $this->extractKeywords($query);
if (empty($keywords)) {
return NULL;
}
// Suche in Nodes
$nids = \Drupal::entityQuery('node')
->condition('status', 1)
->condition('title', $keywords, 'IN')
->range(0, 3)
->execute();
if (empty($nids)) {
return NULL;
}
$nodes = \Drupal::entityTypeManager()
->getStorage('node')
->loadMultiple($nids);
$content_snippets = [];
foreach ($nodes as $node) {
$body = $node->get('body')->value;
$snippet = substr(strip_tags($body), 0, 300);
$content_snippets[] = sprintf(
"Titel: %s\nURL: %s\nInhalt: %s...",
$node->getTitle(),
$node->toUrl()->setAbsolute()->toString(),
$snippet
);
}
return implode("\n\n---\n\n", $content_snippets);
}
/**
* Extract keywords from query.
*/
protected function extractKeywords($text) {
// Stopwords entfernen
$stopwords = ['der', 'die', 'das', 'und', 'oder', 'ist', 'wie', 'was', 'wo'];
$words = str_word_count(strtolower($text), 1, 'äöüß');
$keywords = array_diff($words, $stopwords);
return array_slice($keywords, 0, 5);
}
/**
* Load conversation history from database.
*/
protected function loadConversationHistory($session_id) {
$result = $this->database->select('ai_chatbot_history', 'h')
->fields('h', ['role', 'content', 'timestamp'])
->condition('session_id', $session_id)
->condition('timestamp', time() - 3600, '>') // Letzte Stunde
->orderBy('timestamp', 'ASC')
->execute();
$this->conversationHistory = [];
foreach ($result as $row) {
$this->conversationHistory[] = [
'role' => $row->role,
'content' => $row->content,
'timestamp' => $row->timestamp,
];
}
}
/**
* Save conversation history to database.
*/
protected function saveConversationHistory($session_id) {
foreach ($this->conversationHistory as $message) {
// Nur neue Messages (ohne ID) speichern
$this->database->insert('ai_chatbot_history')
->fields([
'session_id' => $session_id,
'user_id' => $this->currentUser->id(),
'role' => $message['role'],
'content' => $message['content'],
'timestamp' => $message['timestamp'],
])
->execute();
}
}
/**
* Generate unique session ID.
*/
protected function generateSessionId() {
return uniqid('chat_', TRUE);
}
/**
* Track chatbot metrics.
*/
protected function trackMetrics($session_id, $user_message, $ai_response) {
$this->database->insert('ai_chatbot_metrics')
->fields([
'session_id' => $session_id,
'user_id' => $this->currentUser->id(),
'message_length' => strlen($user_message),
'response_length' => strlen($ai_response),
'timestamp' => time(),
])
->execute();
}
}
Database Schema
<?php
/**
* Implements hook_schema().
*/
function ai_chatbot_schema() {
$schema['ai_chatbot_history'] = [
'description' => 'Stores chatbot conversation history',
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
],
'session_id' => [
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
],
'user_id' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
],
'role' => [
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
],
'content' => [
'type' => 'text',
'not null' => TRUE,
],
'timestamp' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id'],
'indexes' => [
'session_id' => ['session_id'],
'timestamp' => ['timestamp'],
],
];
$schema['ai_chatbot_metrics'] = [
'description' => 'Stores chatbot metrics',
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
],
'session_id' => [
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
],
'user_id' => [
'type' => 'int',
'not null' => TRUE,
],
'message_length' => [
'type' => 'int',
'not null' => TRUE,
],
'response_length' => [
'type' => 'int',
'not null' => TRUE,
],
'timestamp' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id'],
];
return $schema;
}
REST API Controller
<?php
// src/Controller/ChatbotApiController.php
namespace Drupal\ai_chatbot\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Chatbot API controller.
*/
class ChatbotApiController extends ControllerBase {
/**
* Chat endpoint.
*/
public function chat(Request $request) {
$data = json_decode($request->getContent(), TRUE);
$message = $data['message'] ?? '';
$session_id = $data['session_id'] ?? NULL;
if (empty($message)) {
return new JsonResponse(['error' => 'Message required'], 400);
}
/** @var \Drupal\ai_chatbot\Service\ChatbotService $chatbot */
$chatbot = \Drupal::service('ai_chatbot.chatbot');
$response = $chatbot->chat($message, $session_id);
return new JsonResponse($response);
}
/**
* Clear conversation history.
*/
public function clearHistory(Request $request) {
$data = json_decode($request->getContent(), TRUE);
$session_id = $data['session_id'] ?? NULL;
if (!$session_id) {
return new JsonResponse(['error' => 'Session ID required'], 400);
}
\Drupal::database()->delete('ai_chatbot_history')
->condition('session_id', $session_id)
->execute();
return new JsonResponse(['success' => TRUE]);
}
}
Routing
# ai_chatbot.routing.yml
ai_chatbot.api.chat:
path: '/api/chatbot/chat'
defaults:
_controller: '\Drupal\ai_chatbot\Controller\ChatbotApiController::chat'
_title: 'Chatbot API'
requirements:
_permission: 'access content'
methods: [POST]
ai_chatbot.api.clear:
path: '/api/chatbot/clear'
defaults:
_controller: '\Drupal\ai_chatbot\Controller\ChatbotApiController::clearHistory'
_title: 'Clear Chat History'
requirements:
_permission: 'access content'
methods: [POST]
Frontend Widget (JavaScript)
// js/chatbot-widget.js
(function (Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.aiChatbot = {
attach: function (context, settings) {
const widget = document.getElementById('ai-chatbot-widget');
if (!widget || widget.dataset.initialized) return;
widget.dataset.initialized = 'true';
let sessionId = localStorage.getItem('chatbot_session_id') || null;
const sendButton = widget.querySelector('.chatbot-send');
const input = widget.querySelector('.chatbot-input');
const messages = widget.querySelector('.chatbot-messages');
sendButton.addEventListener('click', () => sendMessage());
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
async function sendMessage() {
const message = input.value.trim();
if (!message) return;
// User Message anzeigen
appendMessage('user', message);
input.value = '';
// Loading indicator
const loadingId = appendMessage('assistant', '<em>Tippt...</em>');
try {
const response = await fetch('/api/chatbot/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
session_id: sessionId,
}),
});
const data = await response.json();
// Session ID speichern
if (data.session_id) {
sessionId = data.session_id;
localStorage.setItem('chatbot_session_id', sessionId);
}
// Loading entfernen und Response anzeigen
document.getElementById(loadingId).remove();
appendMessage('assistant', data.response);
} catch (error) {
document.getElementById(loadingId).remove();
appendMessage('assistant', 'Fehler bei der Kommunikation. Bitte versuchen Sie es erneut.');
console.error('Chatbot error:', error);
}
}
function appendMessage(role, content) {
const messageId = 'msg-' + Date.now();
const messageEl = document.createElement('div');
messageEl.id = messageId;
messageEl.className = `chatbot-message chatbot-message--${role}`;
messageEl.innerHTML = `
<div class="chatbot-message__avatar"></div>
<div class="chatbot-message__content">${content}</div>
`;
messages.appendChild(messageEl);
messages.scrollTop = messages.scrollHeight;
return messageId;
}
}
};
})(Drupal, drupalSettings);
Twig Template
{# templates/chatbot-widget.html.twig #}
<div id="ai-chatbot-widget" class="chatbot-widget">
<div class="chatbot-header">
<h3>{{ 'Chat Support'|t }}</h3>
<button class="chatbot-close" aria-label="{{ 'Close'|t }}">×</button>
</div>
<div class="chatbot-messages">
<div class="chatbot-message chatbot-message--assistant">
<div class="chatbot-message__avatar"></div>
<div class="chatbot-message__content">
{{ 'Hallo! Wie kann ich Ihnen helfen?'|t }}
</div>
</div>
</div>
<div class="chatbot-input-wrapper">
<input
type="text"
class="chatbot-input"
placeholder="{{ 'Ihre Nachricht...'|t }}"
aria-label="{{ 'Chat message'|t }}"
/>
<button class="chatbot-send" aria-label="{{ 'Send'|t }}">
{{ 'Senden'|t }}
</button>
</div>
</div>
CSS Styling
/* css/chatbot-widget.css */
.chatbot-widget {
position: fixed;
bottom: 20px;
right: 20px;
width: 380px;
max-height: 600px;
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
z-index: 9999;
}
.chatbot-header {
padding: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.chatbot-header h3 {
margin: 0;
font-size: 18px;
}
.chatbot-close {
background: none;
border: none;
color: #fff;
font-size: 24px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.chatbot-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
max-height: 400px;
}
.chatbot-message {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.chatbot-message--user {
flex-direction: row-reverse;
}
.chatbot-message__avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: #e0e0e0;
flex-shrink: 0;
}
.chatbot-message--assistant .chatbot-message__avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.chatbot-message--user .chatbot-message__avatar {
background: #4caf50;
}
.chatbot-message__content {
background: #f5f5f5;
padding: 10px 14px;
border-radius: 18px;
max-width: 70%;
}
.chatbot-message--user .chatbot-message__content {
background: #4caf50;
color: #fff;
}
.chatbot-input-wrapper {
padding: 16px;
border-top: 1px solid #e0e0e0;
display: flex;
gap: 8px;
}
.chatbot-input {
flex: 1;
padding: 10px 14px;
border: 1px solid #e0e0e0;
border-radius: 20px;
font-size: 14px;
}
.chatbot-send {
padding: 10px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 20px;
cursor: pointer;
font-weight: 600;
}
Advanced: Vector Search (RAG)
Vector Embeddings für Drupal Content
<?php
/**
* Erstelle Embeddings für bessere Content-Suche.
*/
function create_node_embeddings($node) {
$ai_provider = \Drupal::service('ai.provider');
// Content für Embedding vorbereiten
$text = $node->getTitle() . "\n\n" .
strip_tags($node->get('body')->value);
// Embedding erstellen
$response = $ai_provider->embeddings([
'model' => 'text-embedding-3-small',
'input' => substr($text, 0, 8000), // Token limit
]);
$embedding = $response->getEmbedding();
// Speichern
\Drupal::database()->merge('ai_chatbot_embeddings')
->key(['entity_type' => 'node', 'entity_id' => $node->id()])
->fields([
'embedding' => json_encode($embedding),
'content_hash' => md5($text),
'updated' => time(),
])
->execute();
}
/**
* Semantische Suche mit Vector Similarity.
*/
function semantic_content_search($query, $limit = 3) {
$ai_provider = \Drupal::service('ai.provider');
// Query Embedding
$response = $ai_provider->embeddings([
'model' => 'text-embedding-3-small',
'input' => $query,
]);
$query_embedding = $response->getEmbedding();
// Cosine Similarity berechnen (PostgreSQL mit pgvector oder custom PHP)
// Vereinfachtes Beispiel - in Production PostgreSQL pgvector nutzen
$results = \Drupal::database()->query("
SELECT entity_id, embedding
FROM {ai_chatbot_embeddings}
WHERE entity_type = 'node'
")->fetchAll();
$scored_results = [];
foreach ($results as $row) {
$embedding = json_decode($row->embedding, TRUE);
$similarity = cosine_similarity($query_embedding, $embedding);
$scored_results[] = [
'entity_id' => $row->entity_id,
'similarity' => $similarity,
];
}
// Sortieren nach Similarity
usort($scored_results, function($a, $b) {
return $b['similarity'] <=> $a['similarity'];
});
return array_slice($scored_results, 0, $limit);
}
/**
* Cosine Similarity berechnen.
*/
function cosine_similarity(array $a, array $b) {
$dot_product = 0;
$magnitude_a = 0;
$magnitude_b = 0;
for ($i = 0; $i < count($a); $i++) {
$dot_product += $a[$i] * $b[$i];
$magnitude_a += $a[$i] * $a[$i];
$magnitude_b += $b[$i] * $b[$i];
}
$magnitude_a = sqrt($magnitude_a);
$magnitude_b = sqrt($magnitude_b);
if ($magnitude_a == 0 || $magnitude_b == 0) {
return 0;
}
return $dot_product / ($magnitude_a * $magnitude_b);
}
Analytics & Reporting
<?php
/**
* Chatbot Analytics Dashboard.
*/
function get_chatbot_analytics($start_date, $end_date) {
$db = \Drupal::database();
// Total Conversations
$total_conversations = $db->query("
SELECT COUNT(DISTINCT session_id)
FROM {ai_chatbot_history}
WHERE timestamp BETWEEN :start AND :end
", [':start' => $start_date, ':end' => $end_date])->fetchField();
// Total Messages
$total_messages = $db->query("
SELECT COUNT(*)
FROM {ai_chatbot_history}
WHERE timestamp BETWEEN :start AND :end
", [':start' => $start_date, ':end' => $end_date])->fetchField();
// Average Messages per Conversation
$avg_messages = $total_conversations > 0
? round($total_messages / $total_conversations, 2)
: 0;
// Peak Hours
$peak_hours = $db->query("
SELECT HOUR(FROM_UNIXTIME(timestamp)) as hour, COUNT(*) as count
FROM {ai_chatbot_history}
WHERE timestamp BETWEEN :start AND :end
GROUP BY hour
ORDER BY count DESC
LIMIT 5
", [':start' => $start_date, ':end' => $end_date])->fetchAll();
return [
'total_conversations' => $total_conversations,
'total_messages' => $total_messages,
'avg_messages_per_conversation' => $avg_messages,
'peak_hours' => $peak_hours,
];
}
Fazit
Ein AI-Chatbot in Drupal ist mehr als nur ein Gimmick – mit der richtigen Implementation wird er zum wertvollen Customer-Support-Tool, das 24/7 verfügbar ist und kontinuierlich dazulernt.
Die Kombination aus Drupal Content (RAG) und OpenAI ChatGPT ermöglicht kontextbezogene, präzise Antworten basierend auf Ihrem eigenen Content.
Interesse an einem intelligenten Chatbot für Ihre Drupal-Website? Kontakt: mail@stevenschulz.net oder 04037420859
Häufig gestellte Fragen (FAQ)
Was ist RAG und warum ist es wichtig für Drupal Chatbots?
Wie speichere ich Chatbot-Konversationen in Drupal?
Welches OpenAI Modell sollte ich für einen Drupal Chatbot verwenden?
Wie kann ich meinen Chatbot mit Drupal-Inhalten trainieren?
Was kostet ein AI Chatbot für eine Drupal-Website?
Das könnte Sie auch interessieren
AI Content-Generierung in Drupal: Automatische Texterstellung mit ChatGPT
Praktischer Guide zur automatischen Content-Generierung in Drupal mit dem AI Module und OpenAI: Blog-Posts, Produktbesch...
Drupal AI Module: KI-Integration in Drupal 11 und Drupal 10
Umfassender Guide zum Drupal AI Module: OpenAI ChatGPT Integration, Content-Generierung, AI-Services, Provider-Setup und...
Fast 18 Jahre Drupal-Expertise: Warum Spezialisierung der Schlüssel zum Erfolg ist
Seit fast 18 Jahren entwickeln wir ausschließlich Drupal-Projekte. Erfahren Sie, warum diese konsequente Spezialisierung...