LocalAI ile Konuşmalı Müşteri Destek Botu: Özel Bilgi Tabanı ve Fine-Tuned Model Dağıtımı

Müşteri destek ekibinin her gece mesai yapması, aynı soruları tekrar tekrar yanıtlaması ve bilgi tabanının sürekli güncellenmesi gerektiğinde ne kadar yorucu bir süreç olduğunu hepimiz biliyoruz. Bu sorunu çözmek için büyük bulut sağlayıcılarına aylık yüzlerce dolar ödemek zorunda değilsiniz. LocalAI kullanarak kendi sunucunuzda çalışan, özel bilgi tabanınızla desteklenmiş ve ihtiyacınıza göre fine-tune edilmiş bir müşteri destek botu kurabilirsiniz. Üstelik verileriniz hiçbir zaman dışarı çıkmaz.

Mimariye Genel Bakış

Bu yazıda kuracağımız sistem üç ana katmandan oluşuyor. LocalAI sunucu tarafında model inference işini yapıyor, ChromaDB ya da basit bir vektör veritabanı bilgi tabanını indeksliyor, ve bir Python FastAPI servisi bunları birleştirerek kullanıcıya sunum yapıyor. Tüm bunlar Docker Compose ile ayağa kaldırılacak, yönetimi kolay olacak.

Senaryo olarak şunu düşünelim: Orta ölçekli bir e-ticaret şirketinin destek botu. Ürün iade politikası, kargo bilgileri, üyelik sorunları ve sık sorulan sorulara otomatik yanıt vermesi gerekiyor. Şu an 3 kişilik bir destek ekibi bu işi yapıyor ve gecenin 2’sinde gelen “siparişim nerede?” sorularına kimse bakmıyor.

Sunucu Gereksinimleri ve LocalAI Kurulumu

Önce donanım tarafını konuşalım. GPU olmadan da çalışabilirsiniz ama performans farkı ciddi olur.

  • GPU’lu senaryo: NVIDIA RTX 3090 veya A100, 7B model için yeterli
  • CPU’lu senaryo: En az 16 çekirdek, 32GB RAM, Mistral 7B Q4 quantized model çalıştırılabilir
  • Disk: Model dosyaları için en az 20GB boş alan

Sunucuya Docker ve Docker Compose kurulu olduğunu varsayıyorum. LocalAI için proje dizinini hazırlayalım:

mkdir -p /opt/support-bot/{models,config,knowledge-base,api}
cd /opt/support-bot

# Model dizini için alt klasörler
mkdir -p models/mistral-7b
mkdir -p knowledge-base/raw
mkdir -p knowledge-base/processed

Şimdi Docker Compose dosyasını oluşturalım:

cat > /opt/support-bot/docker-compose.yml << 'EOF'
version: '3.8'

services:
  localai:
    image: quay.io/go-skynet/local-ai:latest-aio-cpu
    container_name: localai
    ports:
      - "8080:8080"
    volumes:
      - ./models:/models
      - ./config:/config
    environment:
      - MODELS_PATH=/models
      - CONTEXT_SIZE=4096
      - THREADS=8
      - PARALLEL_REQUESTS=2
    restart: unless-stopped
    networks:
      - botnet

  chromadb:
    image: chromadb/chroma:latest
    container_name: chromadb
    ports:
      - "8000:8000"
    volumes:
      - ./chroma-data:/chroma/chroma
    environment:
      - IS_PERSISTENT=TRUE
      - ANONYMIZED_TELEMETRY=FALSE
    restart: unless-stopped
    networks:
      - botnet

  bot-api:
    build: ./api
    container_name: bot-api
    ports:
      - "9000:9000"
    volumes:
      - ./knowledge-base:/knowledge-base
    environment:
      - LOCALAI_URL=http://localai:8080
      - CHROMA_URL=http://chromadb:8000
      - MODEL_NAME=mistral-7b-support
    depends_on:
      - localai
      - chromadb
    restart: unless-stopped
    networks:
      - botnet

networks:
  botnet:
    driver: bridge
EOF

Model İndirme ve LocalAI Konfigürasyonu

Mistral 7B’nin quantized versiyonunu kullanacağız. Q4_K_M formatı hem boyut hem kalite dengesi açısından müşteri destek senaryosu için ideal:

cd /opt/support-bot/models/mistral-7b

# Mistral 7B Instruct Q4_K_M modelini indir
wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf 
  -O mistral-7b-instruct.gguf

# Dosya boyutunu kontrol et, yaklaşık 4.4GB olmalı
ls -lh mistral-7b-instruct.gguf

LocalAI için model konfigürasyon dosyasını hazırlayalım. Bu dosya modelin nasıl davranacağını belirliyor:

cat > /opt/support-bot/config/mistral-7b-support.yaml << 'EOF'
name: mistral-7b-support
backend: llama
model: mistral-7b/mistral-7b-instruct.gguf
context_size: 4096
threads: 8
f16: true
mmap: true
mmlock: false

parameters:
  temperature: 0.3
  top_p: 0.9
  top_k: 40
  repeat_penalty: 1.1
  max_new_tokens: 512

template:
  chat: |
    <s>[INST] <<SYS>>
    Sen bir e-ticaret şirketinin müşteri destek asistanısın. Adın Asistan.
    Sadece şirketin politikaları ve ürünleri hakkında bilgi ver.
    Bilgi tabanında olmayan konularda "Bu konuda size yardımcı olamıyorum, lütfen destek ekibimizle iletişime geçin" de.
    Kısa, net ve yardımsever ol. Türkçe yanıt ver.
    <<SYS>>
    
    Bağlam: {{.Context}}
    
    Kullanıcı sorusu: {{.Input}} [/INST]
EOF

Bilgi Tabanı Hazırlama ve İndeksleme

Bilgi tabanı, botun “beyni” olan kısım. Ham belgeleri hazırlayıp vektör veritabanına yükleyeceğiz. Önce örnek dökümanları oluşturalım:

# İade politikası dökümanı
cat > /opt/support-bot/knowledge-base/raw/iade-politikasi.txt << 'EOF'
İADE VE DEĞİŞİM POLİTİKASI

Ürün iade süresi: Teslimat tarihinden itibaren 30 gün içinde iade yapılabilir.
Kullanılmamış ve orijinal ambalajında olan ürünler iade edilebilir.
Kozmetik ürünler açıldıktan sonra iade edilemez.
İndirimli ürünlerde iade süresi 14 gündür.

İade prosedürü:
1. Web sitesindeki "İadelerim" bölümünden talep oluşturun
2. Size özel iade kodu e-posta ile gönderilir
3. Kodu kargo görevlisine verin, ücretsiz teslim edin
4. Para iadesi 3-5 iş günü içinde hesabınıza yansır
5. Kredi kartı iadeleri bankanıza göre 7-10 gün sürebilir

Hasar/ayıplı ürün: 2 yıl garanti kapsamındadır, ücretsiz değişim yapılır.
EOF

# Kargo bilgileri
cat > /opt/support-bot/knowledge-base/raw/kargo-bilgileri.txt << 'EOF'
KARGO VE TESLİMAT BİLGİLERİ

Standart kargo: 2-4 iş günü, 29.90 TL
Hızlı kargo: 1 iş günü, 49.90 TL  
500 TL ve üzeri alışverişlerde kargo ücretsiz

Kargo takibi: Sipariş onayı e-postasındaki takip linki ile anlık takip yapılabilir.
SMS bildirimleri: Kargo çıkışında ve teslimat günü SMS gönderilir.

Hafta sonu teslimat: Cumartesi günleri teslim yapılmaktadır, Pazar yapılmamaktadır.
Saat aralığı: 09:00-21:00 arası teslimat gerçekleşir.
Kapıda bulunamazsanız: Komşuya teslim seçeneği mevcuttur veya şubeye bırakılır.
EOF

Şimdi bu dökümanları işleyip ChromaDB’ye yükleyecek Python scriptini yazalım:

cat > /opt/support-bot/api/ingest.py << 'EOF'
import os
import glob
import chromadb
from chromadb.utils import embedding_functions
import hashlib

CHROMA_URL = os.getenv("CHROMA_URL", "http://localhost:8000")
KNOWLEDGE_PATH = "/knowledge-base/raw"

def chunk_text(text, chunk_size=500, overlap=50):
    """Metni örtüşen parçalara böl"""
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = min(start + chunk_size, len(words))
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start += chunk_size - overlap
    return chunks

def ingest_documents():
    client = chromadb.HttpClient(
        host=CHROMA_URL.replace("http://", "").split(":")[0],
        port=int(CHROMA_URL.split(":")[-1])
    )
    
    # Sentence transformer ile embedding, CPU'da da çalışır
    ef = embedding_functions.SentenceTransformerEmbeddingFunction(
        model_name="paraphrase-multilingual-MiniLM-L12-v2"
    )
    
    collection = client.get_or_create_collection(
        name="support_knowledge",
        embedding_function=ef,
        metadata={"hnsw:space": "cosine"}
    )
    
    txt_files = glob.glob(f"{KNOWLEDGE_PATH}/*.txt")
    total_chunks = 0
    
    for filepath in txt_files:
        filename = os.path.basename(filepath)
        print(f"İşleniyor: {filename}")
        
        with open(filepath, "r", encoding="utf-8") as f:
            content = f.read()
        
        chunks = chunk_text(content)
        
        for i, chunk in enumerate(chunks):
            doc_id = hashlib.md5(f"{filename}_{i}".encode()).hexdigest()
            collection.upsert(
                documents=[chunk],
                ids=[doc_id],
                metadatas=[{"source": filename, "chunk_index": i}]
            )
            total_chunks += 1
    
    print(f"Toplam {total_chunks} chunk yüklendi.")

if __name__ == "__main__":
    ingest_documents()
EOF

Ana Bot API’sini Yazma

FastAPI ile ana servisi oluşturalım. Bu servis kullanıcıdan gelen soruyu alıyor, bilgi tabanından ilgili bağlamı çekiyor ve LocalAI’ya gönderiyor:

cat > /opt/support-bot/api/main.py << 'EOF'
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import httpx
import chromadb
from chromadb.utils import embedding_functions
import os
import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI(title="Müşteri Destek Botu API")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

LOCALAI_URL = os.getenv("LOCALAI_URL", "http://localhost:8080")
CHROMA_URL = os.getenv("CHROMA_URL", "http://localhost:8000")
MODEL_NAME = os.getenv("MODEL_NAME", "mistral-7b-support")

chroma_host = CHROMA_URL.replace("http://", "").split(":")[0]
chroma_port = int(CHROMA_URL.split(":")[-1])
chroma_client = chromadb.HttpClient(host=chroma_host, port=chroma_port)

ef = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="paraphrase-multilingual-MiniLM-L12-v2"
)

class ChatRequest(BaseModel):
    message: str
    session_id: str = "default"
    history: list = []

class ChatResponse(BaseModel):
    response: str
    sources: list
    session_id: str
    timestamp: str

def get_relevant_context(query: str, n_results: int = 3) -> tuple:
    try:
        collection = chroma_client.get_collection(
            name="support_knowledge",
            embedding_function=ef
        )
        results = collection.query(
            query_texts=[query],
            n_results=n_results
        )
        
        context_parts = results["documents"][0]
        sources = [m["source"] for m in results["metadatas"][0]]
        context = "nn".join(context_parts)
        return context, list(set(sources))
    except Exception as e:
        logger.error(f"ChromaDB hatası: {e}")
        return "", []

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    context, sources = get_relevant_context(request.message)
    
    messages = [{"role": "system", "content": f"Bilgi tabanı bağlamı:n{context}"}]
    
    for h in request.history[-4:]:
        messages.append(h)
    
    messages.append({"role": "user", "content": request.message})
    
    payload = {
        "model": MODEL_NAME,
        "messages": messages,
        "temperature": 0.3,
        "max_tokens": 512,
        "stream": False
    }
    
    async with httpx.AsyncClient(timeout=60.0) as client:
        try:
            response = await client.post(
                f"{LOCALAI_URL}/v1/chat/completions",
                json=payload
            )
            response.raise_for_status()
            result = response.json()
            bot_response = result["choices"][0]["message"]["content"]
        except Exception as e:
            logger.error(f"LocalAI hatası: {e}")
            raise HTTPException(status_code=503, detail="Model servisi şu an kullanılamıyor")
    
    return ChatResponse(
        response=bot_response,
        sources=sources,
        session_id=request.session_id,
        timestamp=datetime.now().isoformat()
    )

@app.get("/health")
async def health():
    return {"status": "ok", "model": MODEL_NAME}
EOF

Fine-Tuning ile Modeli Özelleştirme

Eğer elinizde geçmiş müşteri destek konuşmaları varsa, modeli bu verilerle fine-tune ederek çok daha iyi sonuçlar alabilirsiniz. Bunun için PEFT/LoRA yöntemi kullanacağız:

# Fine-tuning için gerekli paketleri kurun (GPU sunucusunda)
pip install transformers peft datasets trl torch bitsandbytes

# Eğitim verisini hazırlama scripti
cat > /opt/support-bot/finetune/prepare_data.py << 'EOF'
import json
import csv

# Örnek: Eski destek konuşmalarını JSONL formatına çevir
def convert_csv_to_training_data(input_csv, output_jsonl):
    training_samples = []
    
    with open(input_csv, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            sample = {
                "instruction": row["soru"],
                "input": "",
                "output": row["cevap"]
            }
            
            # Alpaca formatına dönüştür
            formatted = {
                "text": f"<s>[INST] {sample['instruction']} [/INST] {sample['output']}</s>"
            }
            training_samples.append(formatted)
    
    with open(output_jsonl, "w", encoding="utf-8") as f:
        for sample in training_samples:
            f.write(json.dumps(sample, ensure_ascii=False) + "n")
    
    print(f"{len(training_samples)} örnek hazırlandı")

convert_csv_to_training_data(
    "destek-konusmalari.csv",
    "training-data.jsonl"
)
EOF

Fine-tuning tamamlandıktan sonra LoRA adaptörünü base model ile birleştirip GGUF formatına export edin. Bu işlem için llama.cpp’nin convert araçlarını kullanabilirsiniz. Birleştirilmiş modeli LocalAI’ın models dizinine kopyalamanız yeterli.

Monitoring ve Log Yönetimi

Production ortamında botun ne kadar iyi çalıştığını takip etmek kritik:

cat > /opt/support-bot/monitoring/log-analyzer.sh << 'EOF'
#!/bin/bash
LOG_DIR="/opt/support-bot/logs"
DATE=$(date +%Y-%m-%d)

echo "=== Günlük Bot Raporu: $DATE ==="

# Toplam konuşma sayısı
TOTAL=$(grep -c "POST /chat" $LOG_DIR/access.log 2>/dev/null || echo 0)
echo "Toplam sorgu: $TOTAL"

# Hata oranı
ERRORS=$(grep -c "503|500" $LOG_DIR/access.log 2>/dev/null || echo 0)
echo "Hata sayısı: $ERRORS"

# Ortalama yanıt süresi (nginx log formatından)
if [ -f "$LOG_DIR/response-times.log" ]; then
    AVG_TIME=$(awk '{sum+=$1; count++} END {printf "%.2f", sum/count}' $LOG_DIR/response-times.log)
    echo "Ortalama yanıt süresi: ${AVG_TIME}s"
fi

# En çok sorulan kategoriler
echo ""
echo "En çok sorulan kaynak dökümanlar:"
grep "sources" $LOG_DIR/bot.log | grep -oP '"[^"]+.txt"' | sort | uniq -c | sort -rn | head -5
EOF

chmod +x /opt/support-bot/monitoring/log-analyzer.sh

# Cron job ekle
echo "0 8 * * * /opt/support-bot/monitoring/log-analyzer.sh >> /var/log/bot-daily-report.log 2>&1" | crontab -

Widget Entegrasyonu

Botu mevcut web sitenize entegre etmek için basit bir JavaScript widget’ı:

cat > /opt/support-bot/widget/chat-widget.js << 'EOF'
const SupportBot = {
  sessionId: Math.random().toString(36).substring(7),
  history: [],
  apiUrl: "https://bot.sirketiniz.com/chat",

  async sendMessage(message) {
    const response = await fetch(this.apiUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        message: message,
        session_id: this.sessionId,
        history: this.history
      })
    });

    if (!response.ok) {
      return "Üzgünüm, şu an yanıt veremiyorum. Lütfen destek ekibimizi arayın.";
    }

    const data = await response.json();
    
    // Konuşma geçmişini güncelle
    this.history.push({ role: "user", content: message });
    this.history.push({ role: "assistant", content: data.response });
    
    // Son 6 mesajı tut
    if (this.history.length > 6) {
      this.history = this.history.slice(-6);
    }

    return data.response;
  }
};
EOF

Güvenlik ve Rate Limiting

Botu herkese açık yapıyorsanız mutlaka rate limiting ekleyin:

# Nginx konfigürasyonu ile rate limiting
cat > /etc/nginx/conf.d/support-bot.conf << 'EOF'
limit_req_zone $binary_remote_addr zone=bot_limit:10m rate=10r/m;

server {
    listen 443 ssl;
    server_name bot.sirketiniz.com;

    ssl_certificate /etc/letsencrypt/live/bot.sirketiniz.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/bot.sirketiniz.com/privkey.pem;

    location /chat {
        limit_req zone=bot_limit burst=5 nodelay;
        limit_req_status 429;
        
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 90s;
        
        # LocalAI'ya direkt erişimi engelle
        # 8080 portu sadece iç ağdan erişilebilir olmalı
    }
    
    location /health {
        proxy_pass http://127.0.0.1:9000;
        allow 10.0.0.0/8;
        deny all;
    }
}
EOF

nginx -t && systemctl reload nginx

Sistemi Ayağa Kaldırma

Her şey hazır olduğunda sırasıyla başlatın:

cd /opt/support-bot

# Tüm servisleri başlat
docker-compose up -d

# LocalAI'ın hazır olmasını bekle (model yükleme 2-3 dakika sürebilir)
echo "LocalAI hazırlanıyor..."
until curl -s http://localhost:8080/readyz > /dev/null 2>&1; do
    sleep 5
    echo "Bekleniyor..."
done
echo "LocalAI hazır!"

# Bilgi tabanını yükle
docker-compose exec bot-api python ingest.py

# Test sorusu gönder
curl -X POST http://localhost:9000/chat 
  -H "Content-Type: application/json" 
  -d '{"message": "İade politikanız nedir?", "session_id": "test-001"}'

Gerçek Dünyada Karşılaşılan Sorunlar ve Çözümleri

Yavaş yanıt süresi: CPU üzerinde Mistral 7B Q4 yaklaşık 8-15 saniye yanıt üretiyor. Bunu kabul edilebilir kılmak için streaming response kullanın, kullanıcı ilk tokenları hemen görsün. Alternatif olarak Phi-2 veya TinyLlama gibi daha küçük modelleri deneyin.

Halüsinasyon sorunları: Model bilgi tabanında olmayan şeyleri uydurabiliyor. Sistem prompt’una “Eğer bağlamda bilgi yoksa kesinlikle cevap verme” gibi katı yönergeler ekleyin ve ChromaDB’den dönen similarity score’u threshold ile filtreleyin. Score 0.7’nin altındaysa bağlamı modele göndermeyin.

Bellek tüketimi: Her model yükleme yaklaşık 4-5GB RAM istiyor. Birden fazla model kullanmak zorundaysanız LocalAI’ın model unloading özelliğini aktif edin. PRELOAD_MODELS=false ve WATCHDOG_STABILITY=5 ayarları ile kullanılmayan modeller bellekten temizlenir.

Türkçe karakter sorunları: GGUF modellerde bazen Türkçe karakterler bozulabiliyor. Embedding modelinin çok dilli versiyon olduğundan emin olun. paraphrase-multilingual-MiniLM-L12-v2 bu iş için test edilmiş ve güvenilir bir seçenek.

Sonuç

Bu mimarinin en güzel yanı tamamen kontrolünüzde olması. Veriler sunucunuzda kalıyor, aylık API faturası yok ve ihtiyaca göre ölçeklenebilir. Bizim test ettiğimiz 8 çekirdek, 32GB RAM’li bir sunucuda günlük 500-600 konuşmayı sorunsuz kaldırabiliyor.

Bir sonraki adım olarak bilgi tabanını otomatik güncelleyen bir pipeline ekleyebilirsiniz. Destek ekibinin yeni bir politika dökümanı yüklediğinde ingest.py scriptinin otomatik çalışması, veya müşteri konuşmalarından beğenilen yanıtların fine-tuning verisine eklenmesi sistemi zamanla daha da iyi hale getirir. Fine-tuning döngüsünü kurduğunuzda model gerçekten şirketinizin sesini ve tarzını öğreniyor, genel bir modelden çok daha tutarlı yanıtlar üretiyor.

Sunucunuzda GPU varsa modeli GPU’ya aldığınız anda yanıt süreleri 1-2 saniyeye düşüyor ve kullanıcı deneyimi tamamen değişiyor. O noktada bu sistemi telefon hattınızdaki IVR sistemiyle de entegre edebilirsiniz ama bu başka bir yazının konusu.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir