← Back to list

Hello AI World — Private RAG Mimarisini Oluşturalım

Merhaba Değerli Takipçilerim,

M. Esat Ceber · 2026-02-12 11:46 · 2 claps · 6.2 min read
#privategpt #llm #rags #gpt #ollama
Open on Medium ↗
Wiki topics: LLM · Large Language Models RAG · RAG & Retrieval

Hello AI World — Private RAG Mimarisini Oluşturalım

Merhaba Değerli Takipçilerim,

Artık hepimizin hayatına güçlü bir şekilde temas ettiğine inandığım yapay zekâyı, kendi iş süreçlerinizde private ve kontrol edilebilir bir mimariyle nasıl kullanabileceğinizi anlatmak için bu yazı serisini hazırladım.

Amacım yalnızca bir örnek göstermek değil; arka plandaki düşünce yapısını, tasarım kararlarını ve ölçeklenebilir bir yapının nasıl kurgulanacağını da birlikte inşa etmek olacak. Şimdi temel bazı terimleri öğrenelim:

LLM: Çok büyük verilerle eğitilmiş, insan dilini anlayabilen ve anladıklarına göre yeni bir şey üretebilen bir AI modelidir.

GPT-3 175 milyar parametreyle eğitilmiş bir dil modelidir. Parametre arttıkça üretim kalitesi artar. Buna bağlı olarak, donanım maliyeti artar.

Şimdi ben bir dil modelini kullanıyorum diyelim. Kullanıcı doğrudan bu modeli kullanması demek eğitilen verilerle cevap alacağı anlamına geleceği için şirket içi bilgilerin paylaşılması hem doğru bir yaklaşım olmaz hem de modelin kurumda yapılan işlere entegrasyonunu zorlaştırır.

İşte tam bu yüzden RAG mimarisi kullanılmaktadır.

RAG: LLM cevap üretmeden önce harici bir veri kaynağından bilgi çekmesini sağlayan bir mimaridir.

Önce ilgili bilgiyi bulur > Sonra o bilgiyle cevap üretir

Peki bizim yapacağımız örnekte akış nasıl olacak?

RAG Mimari Akışı

RAG Mimari Akışı

RAG Mimari akışına göre biz Ollama kullanarak LLM çalıştıracağız. Mongo DB ile veriyi tutacağız. Bir API ile bunu kullanıma açacağız. Bu mimarimizi konteyner ortamda ayağa kaldıracağız.

Eğer konteyner dünyası hakkında bilgi sahibi değilseniz aşağıdaki yazı serimi okuyabilirsiniz.

[embed]1. What is Docker and What is it Used For? Docker solves compatibility problems by running different parts of an application and its dependencies in separate…medium.com

Öncelikli olarak yukarıdaki mimariye göre klasör yapısı şu şekilde olmalıdır:

project/
│
├── Dockerfile
├── docker-compose.yml
├── chroma_db/
└── app/
    ├── main.py
    ├── requirements.txt

app içinde requirements.txt:

fastapi
uvicorn[standard]
python-dotenv
motor
pydantic
requests
chromadb
PyPDF2
python-docx
pymongo
python-multipart

Yine app içinde API projemizin olduğu main.py:

from fastapi import FastAPI
from pydantic import BaseModel
from motor.motor_asyncio import AsyncIOMotorClient
import chromadb
from chromadb.config import Settings
import requests
import os

# ------------------------------------------------
# CONFIG
# ------------------------------------------------

MONGO_URL = os.getenv("MONGO_URL", "mongodb://mongo:27017")
DB_NAME = os.getenv("DB_NAME", "ragdb")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://ollama:11434")

EMBED_MODEL = "mxbai-embed-large"
LLM_MODEL = "llama3.2"

# ------------------------------------------------
# APP
# ------------------------------------------------

app = FastAPI(title="Hello AI World - Private RAG")

# ------------------------------------------------
# MONGO CONNECTION
# ------------------------------------------------

mongo_client = AsyncIOMotorClient(MONGO_URL)
db = mongo_client[DB_NAME]

# ------------------------------------------------
# VECTOR DB (Chroma)
# ------------------------------------------------

chroma_client = chromadb.Client(
    Settings(
        persist_directory="./chroma_db",
        anonymized_telemetry=False
    )
)

collection = chroma_client.get_or_create_collection(name="documents")

# ------------------------------------------------
# REQUEST MODEL
# ------------------------------------------------

class ChatRequest(BaseModel):
    question: str

# ------------------------------------------------
# HELPER FUNCTIONS
# ------------------------------------------------

def get_embedding(text: str):
    response = requests.post(
        f"{OLLAMA_BASE_URL}/api/embeddings",
        json={"model": EMBED_MODEL, "prompt": text}
    )
    return response.json()["embedding"]

def query_llm(prompt: str):
    response = requests.post(
        f"{OLLAMA_BASE_URL}/api/generate",
        json={
            "model": LLM_MODEL,
            "prompt": prompt,
            "stream": False
        }
    )
    return response.json()["response"]

# ------------------------------------------------
# ROUTES
# ------------------------------------------------

@app.get("/")
async def root():
    return {"message": "Private RAG API is running 🚀"}

@app.get("/health")
async def health():
    try:
        await db.command("ping")
        mongo_status = "healthy"
    except:
        mongo_status = "unhealthy"

    try:
        r = requests.get(f"{OLLAMA_BASE_URL}/api/tags")
        ollama_status = "healthy" if r.status_code == 200 else "unhealthy"
    except:
        ollama_status = "unreachable"

    return {
        "mongo": mongo_status,
        "ollama": ollama_status
    }

@app.post("/chat")
async def chat(request: ChatRequest):

    # 1️⃣ Soruyu embedding'e çevir
    query_embedding = get_embedding(request.question)

    # 2️⃣ Vector search
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=3
    )

    context = "\n\n".join(results["documents"][0]) if results["documents"] else ""

    # 3️⃣ Prompt oluştur
    prompt = f"""
Aşağıdaki bağlama göre cevap ver.
Eğer cevap bağlamda yoksa "Bilgi bulunamadı" de.

Bağlam:
{context}

Soru:
{request.question}

Cevap:
"""

    # 4️⃣ LLM'e gönder
    answer = query_llm(prompt)

    # 5️⃣ Basit log kaydı (Mongo'ya)
    await db.chat_logs.insert_one({
        "question": request.question,
        "answer": answer
    })

    return {
        "question": request.question,
        "answer": answer,
        "context_used": bool(context)
    }

@app.on_event("shutdown")
async def shutdown():
    mongo_client.close()

API projemiz için Dockerfile oluşturalım:

FROM python:3.10

WORKDIR /app

COPY app/requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Bu yazdığımız API projesi + OLLAMA + Mongo DB’yi bir arada kullanabilmek için docker-compose.yml dosyasını oluşturalım:

version: '3.8'
services:
  api:
    build: .
    container_name: rag_api
    ports:
      - "8000:8000"
    environment:
      MONGO_URL: mongodb://mongo:27017
      DB_NAME: ragdb
      OLLAMA_BASE_URL: http://ollama:11434
      CORS_ORIGINS: "*"
    depends_on:
      - mongo
      - ollama
    volumes:
      - ./chroma_db:/app/chroma_db

  mongo:
    image: mongo:6
    container_name: rag_mongo
    ports:
      - "27017:27017"
    volumes:
      - mongo_data:/data/db

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    volumes:
      - ollama_data:/root/.ollama
    ports:
      - "11434:11434"
    restart: unless-stopped
    entrypoint: >
      sh -c "
      ollama serve &
      sleep 4 &&
      ollama pull llama3.2 &&
      ollama pull mxbai-embed-large &&

      wait
      "

volumes:
  mongo_data:
  ollama_data:

Şimdi projemizi ayağa kaldırabiliriz:

docker-compose up

Gördüğünüz gibi projemiz ayağa kalkmış görünüyor.

Şimdi ufak bir test yapalım:

curl -X 'POST' \
  'http://localhost:8000/chat' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "question": "Merhaba! Sen kimsin?"
}'

Kullandığımız mimari işe yarıyor görünüyor. Şimdi biz burayı bir adım daha ileri götürüp örnek olarak çok önemli bir belgeyi analiz etmesini sağlayacak yapıya geçelim. Bunun için main.py içerisine hem dosya yükleyeceğimiz/upload endpointini hem de prompt ekleyerek modelimizi context ile güçlendirelim.

from fastapi import FastAPI, UploadFile, File, HTTPException
from pydantic import BaseModel
from motor.motor_asyncio import AsyncIOMotorClient
import chromadb
from chromadb.config import Settings
import requests
import os
import uuid
from datetime import datetime
import PyPDF2
import docx
import io

# ------------------------------------------------
# CONFIG
# ------------------------------------------------

MONGO_URL = os.getenv("MONGO_URL", "mongodb://mongo:27017")
DB_NAME = os.getenv("DB_NAME", "ragdb")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://ollama:11434")

EMBED_MODEL = "mxbai-embed-large"
LLM_MODEL = "llama3.2"

# ------------------------------------------------
# APP
# ------------------------------------------------

app = FastAPI(title="Private RAG - Document Analysis")

# ------------------------------------------------
# MONGO CONNECTION
# ------------------------------------------------

mongo_client = AsyncIOMotorClient(MONGO_URL)
db = mongo_client[DB_NAME]

# ------------------------------------------------
# VECTOR STORE (Chroma)
# ------------------------------------------------

chroma_client = chromadb.Client(
    Settings(
        persist_directory="./chroma_db",
        anonymized_telemetry=False
    )
)

collection = chroma_client.get_or_create_collection(name="documents")

# ------------------------------------------------
# REQUEST MODEL
# ------------------------------------------------

class ChatRequest(BaseModel):
    question: str

# ------------------------------------------------
# HELPER FUNCTIONS
# ------------------------------------------------

def get_embedding(text: str):
    response = requests.post(
        f"{OLLAMA_BASE_URL}/api/embeddings",
        json={"model": EMBED_MODEL, "prompt": text},
        timeout=300
    )
    response.raise_for_status()
    return response.json()["embedding"]

def query_llm(prompt: str):
    response = requests.post(
        f"{OLLAMA_BASE_URL}/api/generate",
        json={
            "model": LLM_MODEL,
            "prompt": prompt,
            "stream": False
        },
        timeout=600
    )
    response.raise_for_status()
    return response.json()["response"]

def extract_text(file: UploadFile, content: bytes) -> str:

    if file.filename.endswith(".pdf"):
        reader = PyPDF2.PdfReader(io.BytesIO(content))
        return "\n".join([p.extract_text() or "" for p in reader.pages])

    elif file.filename.endswith(".docx"):
        document = docx.Document(io.BytesIO(content))
        return "\n".join([p.text for p in document.paragraphs])

    elif file.filename.endswith(".txt"):
        return content.decode("utf-8")

    else:
        raise HTTPException(status_code=400, detail="Unsupported file type")

def chunk_text(text: str, chunk_size: int = 800):
    words = text.split()
    chunks = []

    for i in range(0, len(words), chunk_size):
        chunk = " ".join(words[i:i + chunk_size])
        if len(chunk.strip()) > 50:
            chunks.append(chunk)

    return chunks

# ------------------------------------------------
# ROUTES
# ------------------------------------------------

@app.get("/")
async def root():
    return {"message": "Private RAG API is running 🚀"}

@app.get("/health")
async def health():
    try:
        await db.command("ping")
        mongo_status = "healthy"
    except:
        mongo_status = "unhealthy"

    try:
        r = requests.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=10)
        ollama_status = "healthy" if r.status_code == 200 else "unhealthy"
    except:
        ollama_status = "unreachable"

    return {
        "mongo": mongo_status,
        "ollama": ollama_status
    }

@app.post("/upload")
async def upload_document(file: UploadFile = File(...)):

    content = await file.read()

    text = extract_text(file, content)

    if not text.strip():
        raise HTTPException(status_code=400, detail="Empty document")

    chunks = chunk_text(text)

    doc_id = str(uuid.uuid4())

    for i, chunk in enumerate(chunks):
        embedding = get_embedding(chunk)

        collection.add(
            documents=[chunk],
            embeddings=[embedding],
            ids=[f"{doc_id}_{i}"]
        )

    await db.documents.insert_one({
        "doc_id": doc_id,
        "filename": file.filename,
        "chunks": len(chunks),
        "uploaded_at": datetime.utcnow()
    })

    return {
        "message": "Document indexed successfully",
        "doc_id": doc_id,
        "chunks": len(chunks)
    }

@app.post("/chat")
async def chat(request: ChatRequest):

    query_embedding = get_embedding(request.question)

    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=3
    )

    context = ""
    if results["documents"] and results["documents"][0]:
        context = "\n\n".join(results["documents"][0])

    prompt = f"""
Aşağıdaki bağlama göre cevap ver.
Eğer cevap bağlamda yoksa "Bilgi bulunamadı" de.

Bağlam:
{context}

Soru:
{request.question}

Cevap:
"""

    answer = query_llm(prompt)

    await db.chat_logs.insert_one({
        "question": request.question,
        "answer": answer,
        "created_at": datetime.utcnow()
    })

    return {
        "question": request.question,
        "answer": answer,
        "context_used": bool(context)
    }

@app.on_event("shutdown")
async def shutdown():
    mongo_client.close()

Şimdi projemizi tekrar ayağa kaldıralım:

docker compose down
docker compose build --no-cache
docker compose up -d

Projemiz yine başarılı ayağa kalkmış ve güncellenmiş görünüyor.

Şimdi test için aşağıdaki gibi bir dosya.txt oluşturalım:

ŞİRKET İÇİ GİZLİ BELGE

Bu belge yalnızca yönetim kadrosu için hazırlanmıştır.

2025 yılı risk yönetim politikası aşağıdaki gibidir:

- Kritik sistemler için maksimum kesinti süresi 15 dakikadır.
- Felaket kurtarma merkezi Ankara lokasyonundadır.
- Yıllık sızma testi zorunludur.
- API erişim anahtarı: XYZ-PRIVATE-KEY-98765

Bu bilgiler şirket dışına çıkarılamaz.

Bu dosyayı upload edelim.


Response body
Download
{
  "message": "Document indexed successfully",
  "doc_id": "1d012f7d-8c72-4de9-a89d-de139d2187ce",
  "chunks": 1
}

Şimdi bu dosyada yer alan gizli bir bilgiyi soralım:

200 
Response body
Download
{
  "question": "API erişim anahtarı nedir?",
  "answer": "API erişim anahtarı: XYZ-PRIVATE-KEY-98765",
  "context_used": true
}

Buradan çıkaracağım sonuç ise şudur:

Model tek başına değil, context(bağlamla) güçlenir. Veriyi biz kontrol ederiz, üretimi model yapar.

Bir yazımın daha sonuna gelmiş bulunuyorum. Umarım faydalı olmuştur.

Projenin kodlarına ulaşmak için:

[embed]GitHub - esatceber/PrivateRAGAI: private ve kontrol edilebilir bir AI projesidir. private ve kontrol edilebilir bir AI projesidir. Contribute to esatceber/PrivateRAGAI development by creating an…github.com


메타데이터
post_id
91baee0806a5
slug
hello-ai-world-private-rag-mimarisini-oluşturalım-91baee0806a5
url
https://medium.com/@m.esatcbr/hello-ai-world-private-rag-mimarisini-olu%C5%9Ftural%C4%B1m-91baee0806a5
canonical_url
https://medium.com/@m.esatcbr/hello-ai-world-private-rag-mimarisini-olu%C5%9Ftural%C4%B1m-91baee0806a5
author_url
https://medium.com/@m.esatcbr
status
ok
fetched_at
2026-06-13 12:55:53