RAG Nedir ve Neden Yerelde Kurulur?
RAG (Retrieval-Augmented Generation), bir dil modelinin cevabı “uydurmak” yerine önce güvenilir bir kaynaktan ilgili içerikleri bulup (retrieval), ardından bu içeriklerle zenginleştirilmiş bir yanıt üretmesi yaklaşımıdır. Son dönemde kurum içi dokümanlarla çalışan asistanlar, teknik arama kutuları ve destek botları RAG ile daha tutarlı sonuçlar verebiliyor. Yerel (local) kurulum ise hem gizlilik hem de maliyet açısından önemli: veriniz üçüncü taraf servislere gitmez, altyapı kontrolü sizde olur ve test/deneme döngüsü hızlanır.
Bu yazıda Docker Compose kullanarak güncel ve pratik bir RAG altyapısının çekirdeğini kuracağız: PostgreSQL üzerinde pgvector eklentisi ile vektör arama, ve bu veritabanına bağlanan örnek bir API katmanı. Amaç; doküman parçalarını (chunk) vektör olarak saklamak, benzerlik araması yapmak ve uygulamalar için tekrar kullanılabilir bir iskelet oluşturmaktır.
Mimari: Hangi Parçalar Var?
Kurulum üç ana parçadan oluşur: (1) PostgreSQL + pgvector: embedding’leri saklar ve benzerlik araması yapar. (2) API: doküman ekleme ve arama uçlarını sunar. (3) İsterseniz ileride LLM entegrasyonu: burada odak veritabanı ve retrieval tarafı olduğu için LLM kısmını opsiyonel bırakıyoruz. Bu iskelet, ister kendi LLM’inizle ister bir harici model API’si ile kolayca genişletilebilir.
Ön Koşullar
Bilgisayarınızda Docker ve Docker Compose kurulu olmalı. Windows’ta Docker Desktop, macOS’ta Docker Desktop, Linux’ta Docker Engine + Compose eklentisi yeterlidir. Ayrıca komut satırı kullanımı ve temel PostgreSQL kavramlarına aşinalık işinizi kolaylaştırır.
1) Docker Compose Dosyasını Hazırlama
Projeniz için bir klasör açın (ör. rag-local) ve içine docker-compose.yml dosyası oluşturun. Aşağıdaki yapı PostgreSQL’i pgvector etkin şekilde başlatır ve örnek bir API servisini ayağa kaldırır. API tarafını kendi projenize göre değiştirebilirsiniz; burada amaç, servislerin bir arada çalıştığı “omurga”yı kurmak.
docker-compose.yml içeriği:
Not: Aşağıdaki bloğu olduğu gibi ekleyebilirsiniz.
YAML:
version: "3.9"
services:
db:
image: pgvector/pgvector:pg16
container_name: rag_db
environment:
POSTGRES_USER: rag
POSTGRES_PASSWORD: ragpass
POSTGRES_DB: ragdb
ports:
- "5432:5432"
volumes:
- rag_pgdata:/var/lib/postgresql/data
api:
image: node:20-alpine
container_name: rag_api
working_dir: /app
volumes:
- ./api:/app
environment:
DATABASE_URL: postgres://rag:ragpass@db:5432/ragdb
command: sh -c "npm i && npm run dev"
depends_on:
- db
ports:
- "3000:3000"
volumes:
rag_pgdata:
Burada kritik kısım, veritabanı imajının pgvector destekli olmasıdır. Böylece ayrı bir eklenti kurma süreciyle uğraşmadan vektör tiplerini ve indeksleri kullanabilirsiniz. API servisi için Node.js seçtik; ancak aynı yaklaşımı Python/FastAPI ya da Go ile de uygulayabilirsiniz.
2) Veritabanında Tablo ve İndeks Oluşturma
Şimdi PostgreSQL tarafında pgvector’ı etkinleştirelim ve RAG için temel bir tablo kuralım. Terminalden Compose’u başlatın:
Komut: docker compose up -d
Ardından veritabanı kabuğuna girip SQL çalıştırın:
Komut: docker exec -it rag_db psql -U rag -d ragdb
SQL adımları:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS documents (
id BIGSERIAL PRIMARY KEY,
source TEXT NOT NULL,
chunk TEXT NOT NULL,
embedding vector(768),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Benzerlik araması için ivfflat indeks (embedding dolduktan sonra daha verimli olur)
CREATE INDEX IF NOT EXISTS documents_embedding_idx
ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Burada vector(768) boyutu örnek bir değerdir. Kullandığınız embedding modeline göre 384, 768, 1024 gibi farklı boyutlar seçebilirsiniz. Boyut ile model çıktısı tutarlı olmak zorunda; aksi halde insert sırasında hata alırsınız.
3) API Katmanını Hızlıca Ayağa Kaldırma
Proje kökünde api adlı klasör oluşturun. İçine basit bir Node.js API koyarak iki uç hazırlayacağız: /ingest (doküman ekleme) ve /search (benzerlik araması). Örnek olması açısından sade tutuldu; prod ortamda doğrulama, rate limit, kuyruk (queue) ve gözlemlenebilirlik eklemek gerekir.
api/package.json:
{
"name": "rag-api",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node index.js"
},
"dependencies": {
"express": "^4.19.2",
"pg": "^8.12.0"
}
}
api/index.js:
import express from "express";
import pg from "pg";
const app = express();
app.use(express.json({ limit: "2mb" }));
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
// ingest: { source, chunk, embedding: [..] }
app.post("/ingest", async (req, res) => {
const { source, chunk, embedding } = req.body;
if (!source || !chunk || !Array.isArray(embedding)) {
return res.status(400).json({ error: "source, chunk ve embedding (array) zorunlu" });
}
const emb = `[${embedding.join(",")}]`;
await pool.query(
"INSERT INTO documents(source, chunk, embedding) VALUES ($1, $2, $3::vector)",
[source, chunk, emb]
);
res.json({ ok: true });
});
// search: { embedding: [..], topK: 5 }
app.post("/search", async (req, res) => {
const { embedding, topK = 5 } = req.body;
if (!Array.isArray(embedding)) {
return res.status(400).json({ error: "embedding (array) zorunlu" });
}
const emb = `[${embedding.join(",")}]`;
const { rows } = await pool.query(
`SELECT id, source, chunk,
1 - (embedding <=> $1::vector) AS score
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT $2`,
[emb, topK]
);
res.json({ results: rows });
});
app.listen(3000, () => console.log("RAG API 3000 portunda çalışıyor"));
Bu API, embedding üretmez; embedding’i dışarıdan bekler. Böylece hangi embedding modelini kullanacağınızı (yerel bir model, bir CLI aracı veya bir bulut servisi) siz belirlersiniz. RAG projelerinde bu ayrım pratik bir avantaj sağlar: depolama/arama altyapısı sabit kalırken model katmanı kolay değişir.
4) Test: Veri Ekleme ve Arama
Servisler ayaktayken basit bir test yapabilirsiniz. Örnek olarak 768 boyut yerine kısa bir vektörle test etmek istiyorsanız tablo boyutunu da ona göre ayarlamanız gerekir. Gerçek kullanımda modelin ürettiği boyutla aynı vektörü göndermelisiniz. Uygulamada tipik akış şudur: (1) dokümanı parçalara böl, (2) her parça için embedding üret, (3) /ingest ile kaydet, (4) kullanıcı sorusu için embedding üret, (5) /search ile en alakalı parçaları çek, (6) bu parçaları LLM’e bağlam olarak ver.
Benzerlik metriklerinde en sık kullanılan seçenekler cosine ve L2’dir. Burada vector_cosine_ops ve <=> operatörü ile cosine mesafesine dayalı sıralama yapıyoruz. Sonuçtaki score alanı basit bir dönüştürme ile “yakınlık” hissi verir; uygulamanızda eşik (threshold) tanımlayarak düşük skorlu sonuçları elemek iyi bir pratik olabilir.
Performans ve Üretim Notları
Yerel RAG ortamı hızlı sonuç verir ama birkaç noktaya dikkat etmek gerekir. ivfflat indeks, veri büyüdükçe anlamlı hale gelir; küçük veri setlerinde tam tarama bazen daha tutarlı sonuç verebilir. Ayrıca ivfflat için ANALYZE çalıştırmak ve “lists” parametresini veri boyutuna göre ayarlamak önemlidir. Daha ileri seviyede hnsw gibi indeks türlerini ve PostgreSQL ayarlarını (work_mem, maintenance_work_mem) incelemek performansı ciddi şekilde artırabilir.
Güvenlik tarafında ise API’ye kimlik doğrulama eklemek, veritabanı şifresini .env dosyasına almak, ağ izolasyonu yapmak ve logları izlemek temel gereksinimlerdir. Bu iskeleti kendi projelerinize uyarlarken en büyük kazanım, retrieval katmanını standardize etmeniz olur; çünkü RAG projelerinde asıl fark genellikle doğru chunking, iyi embedding seçimi ve doğru indeks/parametre kombinasyonunda ortaya çıkar.