Mevcut OpenAI Kodunu Değiştirmeden LocalAI’e Geçiş

OpenAI API’sine bağımlı bir uygulama geliştirdiniz, her şey güzel çalışıyor ama her ay gelen fatura giderek kabarıyor. Ya da şirket politikası gereği hassas verileri dışarıya göndermemeniz gerekiyor. Belki de internet bağlantısı olmayan bir ortamda AI özelliklerini çalıştırmanız şart. İşte tam bu noktada LocalAI devreye giriyor ve güzel bir haber var: mevcut kodunuzda neredeyse hiçbir şeyi değiştirmenize gerek yok.

LocalAI, OpenAI API’siyle birebir uyumlu bir arayüz sunuyor. Yani openai.ChatCompletion.create() çağrılarınız, embedding istekleriniz, hatta whisper ile ses tanıma işlemleriniz aynen çalışmaya devam ediyor. Tek yapmanız gereken endpoint URL’ini değiştirmek.

LocalAI Nedir ve Neden Önemlidir

LocalAI, Go ile yazılmış, tamamen açık kaynaklı bir AI inference sunucusudur. Arkasında llama.cpp, whisper.cpp, stable-diffusion.cpp gibi native kütüphaneleri kullanır. OpenAI’nin REST API formatını taklit ettiği için mevcut SDK’larla, kütüphanelerle ve araçlarla doğrudan çalışır.

Temel avantajları şunlardır:

  • Veri mahremiyeti: Hiçbir veri dışarıya çıkmaz, tüm işlem kendi sunucunuzda döner
  • Maliyet: Model bir kez indirildikten sonra sınırsız istek ücretsizdir
  • Özelleştirme: Kendi fine-tune ettiğiniz modelleri kolayca yükleyebilirsiniz
  • Çevrimdışı çalışma: İnternet bağlantısı gerektirmez
  • OpenAI uyumluluğu: API formatı aynıdır, SDK değişikliği gerekmez

LocalAI Kurulumu

Docker ile Hızlı Başlangıç

En pratik yol Docker kullanmaktır. GPU’nuz varsa CUDA destekli imajı, yoksa CPU imajını kullanabilirsiniz.

# CPU kullanımı için
docker run -p 8080:8080 
  -v /opt/localai/models:/build/models 
  -e THREADS=4 
  -e CONTEXT_SIZE=4096 
  --name localai 
  quay.io/go-skynet/local-ai:latest

# NVIDIA GPU için
docker run -p 8080:8080 
  --gpus all 
  -v /opt/localai/models:/build/models 
  -e THREADS=8 
  --name localai-gpu 
  quay.io/go-skynet/local-ai:latest-gpu-nvidia-cuda-12

Binary ile Kurulum (Ubuntu/Debian)

Doğrudan binary indirip çalıştırmak isteyenler için:

# Son sürümü indir
curl -Lo localai https://github.com/mudler/LocalAI/releases/latest/download/local-ai-Linux-x86_64
chmod +x localai

# Model dizini oluştur
mkdir -p /opt/localai/models

# Çalıştır
./localai --models-path /opt/localai/models --address 0.0.0.0:8080

Systemd Servisi Olarak Yapılandırma

Canlı ortamlarda LocalAI’yi bir servis olarak çalıştırmak isteyeceksiniz:

# /etc/systemd/system/localai.service dosyası
cat > /etc/systemd/system/localai.service << 'EOF'
[Unit]
Description=LocalAI Service
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=localai
Group=localai
WorkingDirectory=/opt/localai
ExecStart=/usr/local/bin/localai 
  --models-path /opt/localai/models 
  --address 0.0.0.0:8080 
  --threads 8 
  --context-size 4096
Restart=always
RestartSec=5
LimitNOFILE=65536
Environment=GOMAXPROCS=8

[Install]
WantedBy=multi-user.target
EOF

# Kullanıcı oluştur ve servisi etkinleştir
useradd -r -s /bin/false -d /opt/localai localai
chown -R localai:localai /opt/localai
systemctl daemon-reload
systemctl enable --now localai

Model Yapılandırması

LocalAI’de modeller YAML yapılandırma dosyalarıyla tanımlanır. Bu yaklaşım hem esneklik sağlar hem de farklı modelleri farklı ayarlarla sunmanıza olanak tanır.

# Model dizinine config dosyası oluştur
mkdir -p /opt/localai/models

cat > /opt/localai/models/gpt-3.5-turbo.yaml << 'EOF'
name: gpt-3.5-turbo
parameters:
  model: mistral-7b-instruct-v0.2.Q4_K_M.gguf
  temperature: 0.9
  top_p: 0.9
  top_k: 40
  max_new_tokens: 512
context_size: 4096
roles:
  user: "[INST]"
  assistant: "[/INST]"
  system: "<<SYS>>"
template:
  chat_message: "{{.RoleName}} {{.Content}}"
  chat: |
    {{.Input}}
EOF

Bu yapılandırmanın güzelliği şu: uygulamanız gpt-3.5-turbo model adını istediğinde, LocalAI arkada gerçekte Mistral 7B modelini çalıştırıyor. Kod tarafında hiçbir değişiklik gerekmez.

GGUF Formatında Model İndirme

# Hugging Face'den model indir
cd /opt/localai/models

# Mistral 7B Instruct (yaklaşık 4GB, Q4 quantization)
wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf

# Daha küçük model istiyorsanız (Phi-2, yaklaşık 1.6GB)
wget https://huggingface.co/TheBloke/phi-2-GGUF/resolve/main/phi-2.Q4_K_M.gguf

# Embedding için (nomic-embed-text)
wget https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf

# İzinleri ayarla
chown -R localai:localai /opt/localai/models/

Mevcut OpenAI Kodunu Değiştirmeden Geçiş

İşte asıl büyü burada başlıyor. Python, Node.js veya başka bir dilde OpenAI SDK kullanıyor olun, yapmanız gereken tek şey base_url parametresini değiştirmek.

Python OpenAI SDK

# ÖNCE (OpenAI API)
from openai import OpenAI

client = OpenAI(api_key="sk-xxxx")

response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "Sen yardımsever bir asistansın."},
        {"role": "user", "content": "Python'da decorator nedir?"}
    ]
)
print(response.choices[0].message.content)

# SONRA (LocalAI - sadece iki satır değişti)
from openai import OpenAI

client = OpenAI(
    api_key="localai",        # Herhangi bir string olabilir, LocalAI doğrulamaz
    base_url="http://localhost:8080/v1"  # LocalAI endpoint'i
)

# Geri kalan KOD TAMAMEN AYNI kalıyor!
response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "Sen yardımsever bir asistansın."},
        {"role": "user", "content": "Python'da decorator nedir?"}
    ]
)
print(response.choices[0].message.content)

Ortam değişkeni ile yönetmek daha temiz bir yaklaşımdır:

import os
from openai import OpenAI

# .env dosyasından veya sistem ortam değişkenlerinden oku
client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY", "localai"),
    base_url=os.getenv("OPENAI_BASE_URL", "http://localhost:8080/v1")
)

# Production'da OPENAI_BASE_URL=https://api.openai.com/v1 set edersiniz
# Development/Staging'de OPENAI_BASE_URL=http://localai-server:8080/v1 set edersiniz
# Kod hiç değişmiyor!

Node.js / TypeScript Uygulamaları

// ÖNCE
const OpenAI = require('openai');
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// SONRA - sadece baseURL eklendi
const OpenAI = require('openai');
const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY || 'localai',
  baseURL: process.env.OPENAI_BASE_URL || 'http://localhost:8080/v1'
});

// Streaming dahil tüm özellikler çalışır
async function streamResponse() {
  const stream = await client.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content: 'Bana kısa bir hikaye anlat.' }],
    stream: true,
  });

  for await (const chunk of stream) {
    process.stdout.write(chunk.choices[0]?.delta?.content || '');
  }
}

streamResponse();

LangChain ile Kullanım

LangChain kullananlar için de değişiklik minimaldır:

from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage

# LocalAI endpoint'ini işaret et
llm = ChatOpenAI(
    model_name="gpt-3.5-turbo",
    openai_api_key="localai",
    openai_api_base="http://localhost:8080/v1",
    temperature=0.7,
    max_tokens=1024
)

# LangChain chain'leri, agent'ları, RAG pipeline'ları aynen çalışır
messages = [
    SystemMessage(content="Sen bir Linux uzmanısın."),
    HumanMessage(content="Disk kullanımını izlemek için hangi araçları kullanırsın?")
]

response = llm.invoke(messages)
print(response.content)

Gerçek Dünya Senaryosu: Müşteri Destek Botu

Bir e-ticaret şirketinde OpenAI tabanlı müşteri destek botu çalıştırdığınızı düşünelim. Aylık 500 dolar API maliyeti var ve KVKK gereği müşteri verilerini yurt içinde tutmanız gerekiyor.

import os
from openai import OpenAI
from flask import Flask, request, jsonify

app = Flask(__name__)

# Ortam değişkeniyle tam esneklik
client = OpenAI(
    api_key=os.getenv("AI_API_KEY", "localai"),
    base_url=os.getenv("AI_BASE_URL", "http://localhost:8080/v1")
)

SYSTEM_PROMPT = """Sen bir e-ticaret şirketinin müşteri destek asistanısın.
Sipariş takibi, iade işlemleri ve ürün bilgileri konularında yardımcı oluyorsun.
Türkçe cevap veriyorsun ve samimi bir dil kullanıyorsun."""

@app.route('/chat', methods=['POST'])
def chat():
    data = request.json
    user_message = data.get('message', '')
    conversation_history = data.get('history', [])

    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    messages.extend(conversation_history)
    messages.append({"role": "user", "content": user_message})

    try:
        response = client.chat.completions.create(
            model=os.getenv("AI_MODEL", "gpt-3.5-turbo"),
            messages=messages,
            temperature=0.7,
            max_tokens=512
        )

        assistant_message = response.choices[0].message.content
        return jsonify({
            "response": assistant_message,
            "model_used": response.model,
            "tokens_used": response.usage.total_tokens
        })

    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Bu uygulamayı LocalAI’ye geçirmek için .env dosyasını değiştirmek yeterli:

# .env dosyası
AI_API_KEY=localai
AI_BASE_URL=http://localai-server:8080/v1
AI_MODEL=gpt-3.5-turbo

Embedding API Desteği

RAG (Retrieval Augmented Generation) pipeline’larında embedding API de kullanılıyorsa o da aynı şekilde çalışır:

from openai import OpenAI
import numpy as np

client = OpenAI(
    api_key="localai",
    base_url="http://localhost:8080/v1"
)

def get_embedding(text: str) -> list[float]:
    """OpenAI embedding formatıyla birebir uyumlu."""
    response = client.embeddings.create(
        model="text-embedding-ada-002",  # LocalAI'de nomic-embed-text ile maplenebilir
        input=text
    )
    return response.data[0].embedding

def cosine_similarity(vec1: list, vec2: list) -> float:
    a = np.array(vec1)
    b = np.array(vec2)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Test
embedding1 = get_embedding("Linux sunucu yönetimi")
embedding2 = get_embedding("Server administration on Linux")
embedding3 = get_embedding("Pasta tarifi")

similarity_1_2 = cosine_similarity(embedding1, embedding2)
similarity_1_3 = cosine_similarity(embedding1, embedding3)

print(f"İlgili konular benzerliği: {similarity_1_2:.3f}")
print(f"Alakasız konular benzerliği: {similarity_1_3:.3f}")

Nginx ile Ters Proxy ve Yük Dengeleme

Production ortamında LocalAI’yi Nginx arkasına almak isteyeceksiniz:

# /etc/nginx/sites-available/localai
upstream localai_backends {
    # Birden fazla LocalAI instance çalıştırıyorsanız
    server 127.0.0.1:8080 weight=1;
    server 127.0.0.1:8081 weight=1;
    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;

    # API anahtarı doğrulaması (basit bir güvenlik katmanı)
    location /v1/ {
        if ($http_authorization = "") {
            return 401 '{"error": "Authorization header required"}';
        }

        proxy_pass http://localai_backends;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # Streaming için önemli
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;
        chunked_transfer_encoding on;
    }
}

Performans Tuning

CPU bound çalışıyorsanız thread sayısını optimize etmek önemlidir:

# Sisteminizdeki fiziksel CPU çekirdek sayısını öğrenin
nproc --all

# NUMA node yapısını kontrol edin
numactl --hardware

# LocalAI'yi NUMA farkındalığıyla başlatın
numactl --cpunodebind=0 --membind=0 ./localai 
  --models-path /opt/localai/models 
  --threads $(nproc) 
  --address 0.0.0.0:8080 
  --context-size 2048

# Swap kullanımını izle, model RAM'e sığmıyorsa sorun çıkar
watch -n 1 'free -h && cat /proc/meminfo | grep -E "MemFree|SwapFree"'

Hangi Quantization Seviyesini Seçmeli

Model dosyası boyutu ve kalitesi arasındaki dengeyi kurmanız gerekir:

  • Q2_K: En küçük boyut (~2.9GB/7B model), belirgin kalite kaybı
  • Q4_K_M: Dengeli seçim (~4.1GB/7B model), kalite/boyut oranı en iyi
  • Q5_K_M: Daha iyi kalite (~4.8GB/7B model), Q4’e göre %15 daha büyük
  • Q8_0: Neredeyse kayıpsız (~7.7GB/7B model), yeterli RAM’iniz varsa tercih edin
  • F16: Tam hassasiyet (~14GB/7B model), GPU inference için

Genel tavsiye: RAM’iniz yeterliyse Q4_K_M ile başlayın, kalite yetersizse Q5_K_M deneyin.

Sık Karşılaşılan Sorunlar

Model yükleme hatası alıyorsanız:

# Log'ları kontrol et
journalctl -u localai -f

# Model dosyasının bütünlüğünü doğrula
md5sum /opt/localai/models/mistral-7b-instruct-v0.2.Q4_K_M.gguf

# LocalAI'nin modeli tanıyıp tanımadığını test et
curl http://localhost:8080/v1/models

Context uzunluğu hatası alıyorsanız, YAML config dosyasındaki context_size değerini düşürün ya da isteğin max_tokens değerini azaltın.

Yanıt çok yavaşsa:

# CPU kullanımını izle
htop

# LocalAI process'inin kaç thread kullandığını kontrol et
ps aux | grep localai
cat /proc/$(pgrep localai)/status | grep -E "Threads|VmRSS"

Sonuç

LocalAI geçişi göründüğü kadar karmaşık değil. Özetlemek gerekirse, Python’da sadece base_url parametresini eklemek, Node.js’de baseURL eklemek, LangChain’de openai_api_base değiştirmek yeterli. Bunların dışında uygulama koduna dokunan tek şey ortam değişkenleri.

Production’a geçmeden önce önerdiğim test süreci şöyle: Önce geliştirme ortamında LocalAI’yi kurun, model olarak mevcut gpt-3.5-turbo adını kullanacak şekilde Mistral veya benzeri bir modeli yapılandırın. Uygulamanızı bu endpoint’e yönlendirin ve mevcut testlerinizi çalıştırın. Çıktı kalitesi kabul edilebilir düzeydeyse staging’e taşıyın, oradan da production’a geçin.

Maliyet ve mahremiyet avantajlarının yanı sıra, kendi altyapınızda çalışan bir AI servisine sahip olmak operasyonel kontrol açısından da çok değerli. API kotası aşmak yok, rate limit hatası yok, dışarıya giden veri yok. Doğru model seçimi ve yeterli donanımla OpenAI kalitesine yakın sonuçlar almak artık oldukça mümkün.

Bir yanıt yazın

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