LocalAI ile Metin Gömme Sunucusu Kurulumu: Semantic Search ve RAG Pipeline
Semantic search ve RAG pipeline kurmak isteyip de nereden başlayacağını bilmeyenler için bu yazı biraz uzun ama sonunda her şey yerli yerine oturmuş olacak. LocalAI, OpenAI API’siyle uyumlu çalışan açık kaynaklı bir sunucu ve biz bugün bunu metin gömme (embedding) servisi olarak kullanacağız. Kendi verinizin üzerinde arama yapmak, doküman tabanlı soru-cevap sistemi kurmak ya da sadece “bunu nasıl yapıyorlar” merakını gidermek için okumaya devam edin.
LocalAI Nedir, Neden Embedding için Kullanıyoruz?
LocalAI, Go ile yazılmış, OpenAI API formatını taklit eden bir çıkarım sunucusu. LLaMA, Whisper, Stable Diffusion gibi modelleri çalıştırabildiği gibi embedding modelleri için de gayet iyi iş çıkarıyor. Bizim için en önemli avantajı şu: Veri gizliliği kritikse, kurumsal dökümanları dışarı göndermek istemiyorsanız ya da sadece aylık API faturasından kurtulmak istiyorsanız, LocalAI tam anlamıyla ihtiyacınıza cevap veriyor.
Embedding, metni vektör uzayında bir noktaya dönüştürme işlemi. “Kedi” ile “köpek” kelimeleri anlam olarak yakın olduğu için vektör uzayında birbirine yakın konumlanır. Bunu semantic search ve RAG (Retrieval-Augmented Generation) için kullanıyoruz. RAG’ı kısaca anlatmak gerekirse: LLM’e soru sorduğunuzda, model önce ilgili dökümanları bulup sonra bu bağlamla cevap üretiyor. Embedding bu “ilgili dökümanları bul” kısmının omurgasını oluşturuyor.
Sunucu Gereksinimleri
GPU olması güzel ama zorunlu değil. CPU ile de embedding çalıştırabilirsiniz, inference hızı biraz düşük olur ama production’da makul düzeyde kalır.
Önerilen minimum donanım:
- RAM: 8 GB (model için 4 GB, sistem için 4 GB)
- CPU: 4 çekirdek, AVX2 desteği olan modern bir işlemci
- Disk: 10 GB boş alan (model dosyaları için)
- İşletim Sistemi: Ubuntu 22.04 LTS veya RHEL/Rocky Linux 9
GPU kullanacaksanız CUDA 11.8+ kurulu NVIDIA kartı gerekiyor. Embedding modelleri LLM’lere göre çok daha hafif olduğu için 6-8 GB VRAM yeterli.
LocalAI Kurulumu
Docker ile kurmak en temiz yol. Önce Docker ve Docker Compose olmadığı durumlar için binary kurulumu göstereyim, sonra Docker’a geçelim.
Binary ile Kurulum (Ubuntu 22.04)
# Gerekli bağımlılıkları yükle
sudo apt update
sudo apt install -y build-essential cmake git wget curl
# LocalAI son sürümünü indir
LOCALAI_VERSION=$(curl -s https://api.github.com/repos/go-skynet/LocalAI/releases/latest | grep tag_name | cut -d'"' -f4)
wget https://github.com/go-skynet/LocalAI/releases/download/${LOCALAI_VERSION}/local-ai-Linux-x86_64 -O local-ai
chmod +x local-ai
sudo mv local-ai /usr/local/bin/
# Dizin yapısını oluştur
sudo mkdir -p /opt/localai/{models,config}
sudo chown -R $USER:$USER /opt/localai
Docker Compose ile Kurulum
Pratikte Docker ile çalışmak çok daha rahat, özellikle model yönetimi ve restart politikaları açısından.
# /opt/localai/docker-compose.yml
version: '3.8'
services:
localai:
image: quay.io/go-skynet/local-ai:latest-aio-cpu
container_name: localai
ports:
- "8080:8080"
volumes:
- ./models:/build/models
- ./config:/build/config
environment:
- THREADS=4
- CONTEXT_SIZE=4096
- DEBUG=false
- GALLERIES=github:go-skynet/model-gallery/index.yaml
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"]
interval: 30s
timeout: 10s
retries: 5
GPU desteği istiyorsanız image’ı değiştirip deploy kısmını ekleyin:
# GPU destekli versiyon için ek konfigürasyon
services:
localai:
image: quay.io/go-skynet/local-ai:latest-aio-gpu-nvidia-cuda-12
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
- CUDA_VISIBLE_DEVICES=0
Servisi başlatın:
cd /opt/localai
docker compose up -d
# Logları kontrol et
docker compose logs -f localai
# Sağlık kontrolü
curl http://localhost:8080/readyz
Embedding Modeli İndirme ve Yapılandırma
LocalAI, bert.cpp ve llama.cpp backend’lerini destekliyor. Embedding için en çok kullanılan modeller all-MiniLM-L6-v2 ve nomic-embed-text. Türkçe metin için multilingual-e5-large veya paraphrase-multilingual-mpnet-base-v2 daha iyi sonuç veriyor.
Model Gallery ile Otomatik İndirme
# Mevcut embedding modellerini listele
curl http://localhost:8080/models/available | python3 -m json.tool | grep -A2 "embed"
# Model gallery üzerinden yükle
curl http://localhost:8080/models/apply
-H "Content-Type: application/json"
-d '{"id": "bert-embeddings"}'
# İndirme durumunu takip et
curl http://localhost:8080/models/jobs
Manuel Model Yapılandırması
Gallery’de bulamadığınız modeller için ya da Hugging Face’den kendi modelinizi kullanmak istiyorsanız manuel yapılandırma gerekiyor.
# Multilingual model için dizin oluştur
mkdir -p /opt/localai/models/multilingual-e5
# Hugging Face'den GGUF formatında model indir
# intfloat/multilingual-e5-large modelinin quantize edilmiş versiyonu
wget -P /opt/localai/models/
"https://huggingface.co/ChristianAzinn/multilingual-e5-large-GGUF/resolve/main/multilingual-e5-large-q4_k_m.gguf"
Model konfigürasyon dosyasını oluşturun:
# /opt/localai/config/multilingual-e5.yaml
name: multilingual-e5
backend: llama-cpp
embeddings: true
parameters:
model: multilingual-e5-large-q4_k_m.gguf
context_size: 512
f16: true
threads: 4
Konfigürasyonu yüklemek için servisi yeniden başlatın veya API üzerinden tetikleyin:
# Config dosyasını yükle (restart gerekmez)
curl http://localhost:8080/v1/models
Embedding API’sini Test Etme
Her şey ayakta mı kontrol edelim. LocalAI, OpenAI’nin /v1/embeddings endpoint’ini taklit ediyor, bu yüzden mevcut OpenAI client’ları doğrudan çalışıyor.
# Basit embedding testi
curl http://localhost:8080/v1/embeddings
-H "Content-Type: application/json"
-d '{
"input": "LocalAI ile embedding sunucusu kuruyoruz",
"model": "multilingual-e5"
}' | python3 -c "
import json, sys
data = json.load(sys.stdin)
vec = data['data'][0]['embedding']
print(f'Vektör boyutu: {len(vec)}')
print(f'İlk 5 değer: {vec[:5]}')
print(f'Token kullanımı: {data["usage"]}')
"
Birden fazla metin için batch işlem:
curl http://localhost:8080/v1/embeddings
-H "Content-Type: application/json"
-d '{
"input": [
"Türkiye büyük bir ülkedir",
"Ankara başkentimizdir",
"Python programlama dilidir"
],
"model": "multilingual-e5"
}' | python3 -c "
import json, sys
data = json.load(sys.stdin)
for i, item in enumerate(data['data']):
print(f'Metin {i}: {len(item["embedding"])} boyutlu vektör')
"
Semantic Search Pipeline Kurma
Şimdi asıl işe gelelim. Basit bir semantic search pipeline kuracağız. Vektör veritabanı olarak Qdrant kullanacağız, çünkü Docker ile çok kolay kurulabiliyor ve production’da sağlam durumda.
Qdrant Kurulumu
# Docker Compose dosyasına Qdrant ekle
cat >> /opt/localai/docker-compose.yml << 'EOF'
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- ./qdrant_storage:/qdrant/storage
restart: unless-stopped
EOF
docker compose up -d qdrant
Python ile End-to-End Pipeline
Gerekli kütüphaneleri yükleyin:
pip install openai qdrant-client python-dotenv tqdm
Şimdi gerçek dünya senaryosu: Bir şirketin iç wiki dökümanlarını indeksleyip semantic arama yapabilir hale getireceğiz.
#!/usr/bin/env python3
"""
LocalAI + Qdrant ile Semantic Search Pipeline
Kullanım: python3 semantic_search.py
"""
import os
import json
import uuid
from pathlib import Path
from typing import List, Dict
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct, Filter, FieldCondition, MatchValue
)
# LocalAI istemcisini yapılandır (OpenAI uyumlu)
ai_client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="localai-dummy-key" # LocalAI API key gerektirmiyor
)
# Qdrant istemcisi
qdrant = QdrantClient(host="localhost", port=6333)
COLLECTION_NAME = "wiki_docs"
EMBEDDING_MODEL = "multilingual-e5"
VECTOR_SIZE = 1024 # multilingual-e5-large için
def get_embedding(text: str) -> List[float]:
"""Tek metin için embedding al"""
response = ai_client.embeddings.create(
input=text,
model=EMBEDDING_MODEL
)
return response.data[0].embedding
def get_embeddings_batch(texts: List[str]) -> List[List[float]]:
"""Batch embedding - daha verimli"""
response = ai_client.embeddings.create(
input=texts,
model=EMBEDDING_MODEL
)
return [item.embedding for item in response.data]
def setup_collection():
"""Qdrant collection oluştur"""
existing = [c.name for c in qdrant.get_collections().collections]
if COLLECTION_NAME not in existing:
qdrant.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=VectorParams(
size=VECTOR_SIZE,
distance=Distance.COSINE
)
)
print(f"Collection oluşturuldu: {COLLECTION_NAME}")
else:
print(f"Collection zaten mevcut: {COLLECTION_NAME}")
def index_documents(documents: List[Dict]):
"""Dökümanları vektör veritabanına ekle"""
setup_collection()
# Batch olarak işle (15'erli gruplar)
batch_size = 15
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
texts = [doc["content"] for doc in batch]
print(f"Embedding alınıyor: {i+1}-{min(i+batch_size, len(documents))}/{len(documents)}")
embeddings = get_embeddings_batch(texts)
points = [
PointStruct(
id=str(uuid.uuid4()),
vector=embedding,
payload={
"content": doc["content"],
"title": doc.get("title", ""),
"category": doc.get("category", "genel"),
"source": doc.get("source", "")
}
)
for doc, embedding in zip(batch, embeddings)
]
qdrant.upsert(collection_name=COLLECTION_NAME, points=points)
print(f"Toplam {len(documents)} döküman indekslendi.")
def semantic_search(query: str, top_k: int = 5, category: str = None) -> List[Dict]:
"""Semantic arama yap"""
query_embedding = get_embedding(query)
# Opsiyonel kategori filtresi
search_filter = None
if category:
search_filter = Filter(
must=[FieldCondition(key="category", match=MatchValue(value=category))]
)
results = qdrant.search(
collection_name=COLLECTION_NAME,
query_vector=query_embedding,
limit=top_k,
query_filter=search_filter,
with_payload=True
)
return [
{
"score": result.score,
"title": result.payload.get("title", ""),
"content": result.payload.get("content", ""),
"category": result.payload.get("category", ""),
"source": result.payload.get("source", "")
}
for result in results
]
# Test verisi
sample_docs = [
{
"title": "VPN Kurulum Rehberi",
"content": "Şirket VPN'ine bağlanmak için OpenVPN client kurmanız gerekmektedir. IT departmanından config dosyasını alın.",
"category": "it-destek",
"source": "wiki/vpn"
},
{
"title": "Uzaktan Çalışma Politikası",
"content": "Uzaktan çalışma talepleri İK departmanına 3 iş günü öncesinden bildirilmelidir.",
"category": "hr",
"source": "wiki/hr-policy"
},
{
"title": "Sunucu Yedekleme Prosedürü",
"content": "Kritik sunucular her gece 02:00'de otomatik yedeklenir. Yedekler 30 gün boyunca saklanır.",
"category": "it-ops",
"source": "wiki/backup"
}
]
if __name__ == "__main__":
# Dökümanları indeksle
index_documents(sample_docs)
# Arama yap
query = "evden çalışma izni nasıl alırım"
results = semantic_search(query, top_k=3)
print(f"nSorgu: '{query}'")
print("-" * 50)
for i, r in enumerate(results, 1):
print(f"{i}. [{r['score']:.3f}] {r['title']}")
print(f" {r['content'][:100]}...")
RAG Pipeline: LLM ile Entegrasyon
Semantic search’ü bulduk, şimdi bunu bir LLM ile birleştirip gerçek bir RAG sistemi kuralım. LocalAI üzerinde hem embedding hem de LLM çalıştırabilirsiniz.
#!/usr/bin/env python3
"""
Basit RAG Pipeline - LocalAI + Qdrant
"""
from openai import OpenAI
# Aynı LocalAI sunucusu hem embedding hem LLM için
ai_client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy"
)
LLM_MODEL = "llama-3.2-3b" # LocalAI'da yüklü olan modeliniz
def rag_query(user_question: str, top_k: int = 3) -> str:
"""
RAG sorgusu:
1. Soruyu embedding'e çevir
2. İlgili dökümanları bul
3. LLM'e bağlam ile sor
"""
# Adım 1 & 2: Semantic search (önceki fonksiyonu kullanıyoruz)
relevant_docs = semantic_search(user_question, top_k=top_k)
if not relevant_docs:
return "İlgili bilgi bulunamadı."
# Adım 3: Bağlamı hazırla
context_parts = []
for i, doc in enumerate(relevant_docs, 1):
context_parts.append(f"[Kaynak {i}] {doc['title']}n{doc['content']}")
context = "nn".join(context_parts)
system_prompt = """Sen bir şirket asistanısın. Sana verilen şirket wiki belgelerine dayanarak soruları yanıtla.
Eğer belgeler soruyu yanıtlamak için yeterli bilgi içermiyorsa, bunu açıkça belirt.
Yanıtını Türkçe ver."""
user_prompt = f"""Aşağıdaki şirket belgelerini kullanarak soruyu yanıtla:
{context}
Soru: {user_question}"""
response = ai_client.chat.completions.create(
model=LLM_MODEL,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.3,
max_tokens=500
)
return response.choices[0].message.content
# Kullanım
if __name__ == "__main__":
sorular = [
"VPN'e nasıl bağlanabilirim?",
"Yedeklemeler ne zaman yapılıyor?",
"Uzaktan çalışmak istiyorum, ne yapmalıyım?"
]
for soru in sorular:
print(f"nSoru: {soru}")
print(f"Cevap: {rag_query(soru)}")
print("-" * 60)
Performans İzleme ve Optimizasyon
Production’a almadan önce birkaç şeyi ayarlamak lazım.
LocalAI Prometheus Metrikleri
LocalAI, /metrics endpoint’i üzerinden Prometheus metriklerini açıyor. Bu metrikler arasında inference latency, token throughput ve model load süreleri bulunuyor. Bunu Grafana ile görselleştirmek standart bir yapıya dönüştürebilir.
# Metriklere bak
curl http://localhost:8080/metrics | grep -E "localai|embedding|latency"
# Systemd service olarak çalıştırıyorsanız log rotasyonu
cat > /etc/logrotate.d/localai << 'EOF'
/var/log/localai/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
}
EOF
Embedding Cache ile Hızlanma
Aynı metinleri defalarca embed etmek gereksiz. Basit bir Redis cache ekleyelim:
import hashlib
import json
import redis
cache = redis.Redis(host='localhost', port=6379, db=0)
CACHE_TTL = 86400 # 24 saat
def get_embedding_cached(text: str) -> list:
"""Redis cache'li embedding"""
cache_key = f"emb:{hashlib.md5(text.encode()).hexdigest()}"
cached = cache.get(cache_key)
if cached:
return json.loads(cached)
# Cache'de yoksa LocalAI'dan al
embedding = get_embedding(text)
cache.setex(cache_key, CACHE_TTL, json.dumps(embedding))
return embedding
Nginx ile Reverse Proxy ve Rate Limiting
# /etc/nginx/sites-available/localai
upstream localai_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name ai.sirketiniz.com;
ssl_certificate /etc/letsencrypt/live/ai.sirketiniz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.sirketiniz.com/privkey.pem;
# Embedding endpoint için rate limiting
limit_req_zone $binary_remote_addr zone=embedding:10m rate=30r/m;
location /v1/embeddings {
limit_req zone=embedding burst=10 nodelay;
proxy_pass http://localai_backend;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}
location / {
proxy_pass http://localai_backend;
proxy_set_header Host $host;
}
}
Yaygın Sorunlar ve Çözümleri
- Model yüklenmiyor: Config YAML’daki
backenddeğerinin doğru olduğundan emin olun. GGUF modelleri içinllama-cpp, BERT modelleri içinbertyazılmalı. - Vektör boyutu uyuşmazlığı: Qdrant collection’ı oluştururken
sizedeğeri modelinizin gerçek çıktısıyla eşleşmeli. Test ederek boyutu doğrulayın. - Yavaş embedding:
threadsdeğerini sunucunuzdaki fiziksel çekirdek sayısının yarısına ayarlayın, HyperThreading’i hesaba katmayın. - OOM hatası: Aynı anda birden fazla model yüklüyse bellek tükenebilir.
PRELOAD_MODELSortam değişkeni ile sadece ihtiyacınız olanı önyükleyin. - Docker network sorunu: LocalAI ve Qdrant ayrı container’lardaysa Qdrant’a
localhostyerine container adıyla bağlanın (qdrant:6333).
Sonuç
Bu yazıda LocalAI’yi bir embedding sunucusu olarak sıfırdan kurup Qdrant ile entegre ettik, semantic search ve RAG pipeline inşa ettik. Tüm bu altyapı tamamen kendi sunucunuzda, dışarıya bir byte bile göndermeden çalışıyor.
Gerçek dünyada bu kurulumu genişletmek için birkaç öneri: Qdrant’ın koleksiyon bazlı yetkilendirmesini mutlaka açın, embedding modelinizi düzenli aralıklarla daha yeni versiyonlarla değiştirip performansı karşılaştırın ve büyük döküman kümeleri için indeksleme işlemini Celery ya da basit bir job queue ile asenkron hale getirin. Türkçe içerik ağırlıklı çalışıyorsanız multilingual-e5-large modeli gerçekten iyi sonuçlar veriyor, all-MiniLM gibi İngilizce ağırlıklı modellere göre belirgin fark var.
LocalAI ekibi aktif geliştirme yapıyor, özellikle v2.x serisi ile model yönetimi çok daha olgunlaştı. GitHub sayfasını takip etmek ve release note’ları okumak iyi bir alışkanlık.
