Hello AI World — Private RAG Mimarisini Oluşturalım
Merhaba Değerli Takipçilerim,
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ışı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.
Ö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:
메타데이터
- 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