Agentic RAG Nedir? Python ve LangGraph ile Kendini Düzelten RAG Pipeline Kurulumu

Klasik RAG pipeline'larının sınırlarını aşan Agentic RAG mimarisini keşfedin. CRAG ve Self-RAG kalıplarıyla Python ve LangGraph kullanarak kendini düzelten bir pipeline kurmayı adım adım öğrenin.

Neden Klasik RAG Artık Yetmiyor?

RAG (Retrieval-Augmented Generation) kavramı ilk ortaya çıktığında amaç gayet açıktı: büyük dil modellerinin yanıtlarını dış kaynaklara dayandırarak halüsinasyonları azaltmak. Çoğu ekip bunu basit bir pipeline olarak kurdu — bir kez ara, belgeleri al, cevabı üret. Kulağa mantıklı geliyor, değil mi? Ama pratikte bu "tek geçişli" yaklaşımın ciddi sınırları olduğu çok çabuk ortaya çıktı.

Klasik RAG pipeline'ının kırıldığı üç kritik senaryo var:

  • Belirsiz sorgular: Kullanıcı sorusu yeterince net değilse, ilk getirme genellikle ilgisiz belgeler döndürür ve model yanlış bağlamla cevap üretir. Bu, düşündüğünüzden daha sık karşılaşılan bir durum.
  • Dağınık bilgi: Cevap birden fazla belgeye yayılmışsa, tek bir getirme adımı tüm parçaları toplayamaz.
  • Çelişkili veya eksik sonuçlar: Getirilen belgeler birbiriyle çelişiyor ya da soruyu tam karşılamıyorsa, model kontrolsüzce halüsinasyon yapar — üstelik bunu çok ikna edici bir dille yapar.

İşte tam bu noktada Agentic RAG devreye giriyor.

2026 itibarıyla yapılan araştırmalar (A-RAG, arXiv Şubat 2026), ajansal yaklaşımın sabit getirme algoritmalarından tutarlı biçimde daha iyi performans gösterdiğini kanıtladı. Üstelik daha az token tüketerek daha yüksek doğruluk elde ediyor — ki bu pek çok mühendis için sürpriz oldu.

Agentic RAG Tam Olarak Ne Demek?

Agentic RAG, klasik RAG'ın üzerine planlama, yansıtma (reflection) ve araç kullanımı katmanları ekleyen bir mimari. Temel farkı şöyle düşünebilirsiniz: klasik RAG bir pipeline'dır — veri tek yönde akar. Agentic RAG ise bir kontrol döngüsüdür. LLM yalnızca metin üreten bir motor değil, iş akışını yöneten bir akıl yürütme motoru olarak çalışır.

Pratikte bu şu anlama geliyor:

  1. Router (Yönlendirici): Gelen sorgunun getirme gerektirip gerektirmediğine karar verir.
  2. Retriever (Getirici): Vektör deposundan ilgili belgeleri çeker.
  3. Grader (Değerlendirici): LLM getirilen belgeleri değerlendirir. İlgiliyse üretim aşamasına geçer, ilgisiz bulursa sorguyu yeniden yazar ve tekrar arar.
  4. Generator (Üretici): Doğrulanmış bağlamla yanıtı sentezler.
  5. Hallucination Checker (Halüsinasyon Denetçisi): Üretilen cevabın belgeler tarafından gerçekten desteklenip desteklenmediğini kontrol eder. Desteklenmiyorsa döngüye geri döner.

Bu "başarısızlıkta döngü" mekanizması, sistemi gerçek anlamda ajansal yapan temel özellik. Açıkçası, bu mekanizmayı ilk kez gördüğümde basit ama dahice bulmuştum.

Corrective RAG (CRAG) ve Self-RAG Arasındaki Fark

Agentic RAG şemsiyesi altında iki temel yaklaşım var ve bunları anlamak, doğru mimariyi seçmeniz açısından oldukça kritik.

Corrective RAG (CRAG)

CRAG, getirilen belgelerin kalitesini değerlendiren hafif bir getirme değerlendiricisi kullanır. Her belge için bir güven skoru döndürür. Eğer belgeler belirsiz veya ilgisiz bulunursa, vektör deposu yerine web aramasına düşer (fallback). Güzel tarafı şu: belgeleri "bilgi şeritlerine" ayırıp her şeridi ayrı ayrı puanlayarak ilgisiz olanları filtreler.

Self-RAG

Self-RAG, CRAG'ın yaptığı her şeyin üzerine bir katman daha ekler: üretilen cevabı da değerlendirir. LLM'in yanıtı hem halüsinasyon kontrolünden hem de soruyu yanıtlama yeterliliği kontrolünden geçer. Yanıt zayıfsa süreç en baştan tekrarlanır.

Adaptive RAG

Sorguları karmaşıklıklarına göre farklı RAG stratejilerine yönlendirir. Basit bir soru doğrudan klasik RAG ile yanıtlanırken, çok adımlı bir soru Agentic RAG döngüsüne girer. Yani her sorgu için tam döngüyü çalıştırmak zorunda değilsiniz — bu hem maliyet hem gecikme açısından akıllıca bir tercih.

Geliştirme Ortamının Hazırlanması

Haydi, ellerimizi kirletelim. Agentic RAG pipeline'ımızı kurmak için aşağıdaki araçlara ihtiyacımız var. Python 3.10 veya üstü gerekli:

pip install langgraph langchain langchain-openai langchain-community \
    chromadb tiktoken python-dotenv tavily-python

API anahtarlarını .env dosyasında saklayın:

# .env
OPENAI_API_KEY=sk-your-openai-key
TAVILY_API_KEY=tvly-your-tavily-key

Ortam değişkenlerini yüklemek de basit:

import os
from dotenv import load_dotenv

load_dotenv()

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY")

Adım 1: Vektör Deposunu Oluşturma

İlk adımda belgelerimizi yükleyip, embedding'lere dönüştürüp ChromaDB'ye kaydedeceğiz. Burada geliştirme ortamı için ChromaDB kullanıyoruz ama üretim ortamında Pinecone, Weaviate veya Qdrant gibi yönetilen çözümleri tercih etmenizi öneririm.

from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# Belgeleri yükle
urls = [
    "https://docs.example.com/api-reference",
    "https://docs.example.com/getting-started",
    "https://docs.example.com/troubleshooting",
]

docs = []
for url in urls:
    loader = WebBaseLoader(url)
    docs.extend(loader.load())

# Belgeleri parçalara ayır
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""]
)
splits = text_splitter.split_documents(docs)

# Vektör deposunu oluştur
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
    collection_name="agentic-rag-docs",
    persist_directory="./chroma_db"
)

# Retriever oluştur
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4}
)

Adım 2: Durum (State) Tanımlama

LangGraph'da her şey bir durum makinesi üzerinden yönetilir. Bu biraz alışılmadık gelebilir ama aslında oldukça sezgisel bir yaklaşım. Ajanlar arası veri aktarımı bu paylaşılan durum nesnesi üzerinden gerçekleşir:

from typing import List, Literal
from typing_extensions import TypedDict


class GraphState(TypedDict):
    """Agentic RAG pipeline durumu."""
    question: str           # Kullanıcı sorusu
    generation: str         # LLM tarafından üretilen yanıt
    documents: List[str]    # Getirilen belgeler
    web_search: str         # Web araması gerekli mi? "Yes" / "No"
    retry_count: int        # Yeniden deneme sayacı

Adım 3: Pipeline Düğümlerini (Nodes) Oluşturma

Şimdi işin eğlenceli kısmına geliyoruz. Her düğüm, pipeline'daki belirli bir görevi temsil eder. Sırasıyla getirici, değerlendirici, üretici, halüsinasyon denetçisi ve sorgu dönüştürücü düğümlerini oluşturacağız.

Getirme Düğümü (Retrieve Node)

from langchain_core.documents import Document


def retrieve(state: GraphState) -> GraphState:
    """Vektör deposundan ilgili belgeleri getirir."""
    question = state["question"]
    documents = retriever.invoke(question)
    return {
        **state,
        "documents": [doc.page_content for doc in documents]
    }

Belge Değerlendirme Düğümü (Grader Node)

Bu düğüm, Agentic RAG'ın kalbi diyebiliriz. LLM her belgeyi sorguyla ilişkisi açısından puanlar ve ilgisiz olanları acımasızca ayıklar:

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field


class GradeDocuments(BaseModel):
    """Getirilen belgenin sorguyla ilgili olup olmadığını değerlendirir."""
    binary_score: str = Field(
        description="Belge ilgiliyse 'yes', değilse 'no'"
    )


llm = ChatOpenAI(model="gpt-4o", temperature=0)
structured_llm_grader = llm.with_structured_output(GradeDocuments)

grading_prompt = ChatPromptTemplate.from_messages([
    ("system", """Sen bir belge değerlendirme uzmanısın.
    Getirilen belgenin kullanıcı sorusuyla ilgili olup olmadığını belirle.
    Belge soruyla ilgili anahtar kelimeler veya anlamsal bağlam içeriyorsa
    'yes' olarak değerlendir. Katı bir eşik uygulama —
    amacımız ilgisiz belgeleri filtrelemek."""),
    ("human", "Belge:\n{document}\n\nSoru: {question}")
])

grader_chain = grading_prompt | structured_llm_grader


def grade_documents(state: GraphState) -> GraphState:
    """Getirilen belgelerin ilgililiğini değerlendirir."""
    question = state["question"]
    documents = state["documents"]

    filtered_docs = []
    web_search_needed = "No"

    for doc in documents:
        score = grader_chain.invoke({
            "question": question,
            "document": doc
        })
        if score.binary_score == "yes":
            filtered_docs.append(doc)

    if len(filtered_docs) == 0:
        web_search_needed = "Yes"

    return {
        **state,
        "documents": filtered_docs,
        "web_search": web_search_needed
    }

Sorgu Dönüştürme Düğümü (Query Rewrite Node)

def transform_query(state: GraphState) -> GraphState:
    """Daha iyi getirme için sorguyu yeniden yazar."""
    question = state["question"]
    retry_count = state.get("retry_count", 0)

    rewrite_prompt = ChatPromptTemplate.from_messages([
        ("system", """Verilen soruyu, bir vektör deposunda daha iyi
        sonuçlar getirecek şekilde optimize edilmiş bir arama sorgusuna
        dönüştür. Anlamı koru ama anahtar kelimeleri ve ifadeyi iyileştir."""),
        ("human", "Orijinal soru: {question}")
    ])

    chain = rewrite_prompt | llm
    better_question = chain.invoke({"question": question})

    return {
        **state,
        "question": better_question.content,
        "retry_count": retry_count + 1
    }

Web Arama Düğümü (Web Search Fallback)

from langchain_community.tools.tavily_search import TavilySearchResults


def web_search(state: GraphState) -> GraphState:
    """Vektör deposu yetersizse web aramasına düşer."""
    question = state["question"]

    search_tool = TavilySearchResults(max_results=3)
    results = search_tool.invoke({"query": question})

    web_docs = [result["content"] for result in results]
    existing_docs = state.get("documents", [])

    return {
        **state,
        "documents": existing_docs + web_docs,
        "web_search": "Done"
    }

Üretim Düğümü (Generate Node)

def generate(state: GraphState) -> GraphState:
    """Doğrulanmış bağlam kullanarak yanıt üretir."""
    question = state["question"]
    documents = state["documents"]

    context = "\n\n---\n\n".join(documents)

    generate_prompt = ChatPromptTemplate.from_messages([
        ("system", """Sen yardımcı bir asistansın. Soruyu yanıtlamak için
        YALNIZCA sağlanan bağlamı kullan. Bağlamda cevap yoksa,
        bunu açıkça belirt — tahmin yapma.

        Bağlam:
        {context}"""),
        ("human", "{question}")
    ])

    chain = generate_prompt | llm
    generation = chain.invoke({
        "context": context,
        "question": question
    })

    return {
        **state,
        "generation": generation.content
    }

Halüsinasyon Denetim Düğümü (Hallucination Checker)

Bu düğüm belki de en kritik parça. Üretilen yanıtın belgeler tarafından desteklenip desteklenmediğini kontrol eder. Üretim ortamında güven eşiğini 0.85 gibi bir değerde tutmanızı öneririm — özellikle hassas verilerle çalışıyorsanız:

class HallucinationCheck(BaseModel):
    """Yanıtın belgeler tarafından desteklenip desteklenmediğini kontrol eder."""
    binary_score: str = Field(
        description="Yanıt belgelere dayalıysa 'yes', değilse 'no'"
    )


hallucination_prompt = ChatPromptTemplate.from_messages([
    ("system", """Sen bir doğruluk değerlendirme uzmanısın.
    Verilen yanıtın, sağlanan belgelerdeki bilgilere dayalı olup
    olmadığını belirle. Yanıt tamamen belgelerden türetilmişse 'yes',
    belgeler dışı bilgi içeriyorsa 'no' olarak değerlendir."""),
    ("human", "Belgeler:\n{documents}\n\nYanıt: {generation}")
])

hallucination_grader = hallucination_prompt | llm.with_structured_output(
    HallucinationCheck
)


def check_hallucination(state: GraphState) -> GraphState:
    """Üretilen yanıtın halüsinasyon içerip içermediğini denetler."""
    documents = state["documents"]
    generation = state["generation"]

    score = hallucination_grader.invoke({
        "documents": "\n\n".join(documents),
        "generation": generation
    })

    return {
        **state,
        "hallucination_score": score.binary_score
    }

Adım 4: LangGraph ile Grafı Birleştirme

Tüm düğümlerimiz hazır — şimdi bunları bir LangGraph durum makinesinde birleştirme zamanı. Bu kısım, tüm parçaların bir araya geldiği yer:

from langgraph.graph import StateGraph, START, END

# Yönlendirme fonksiyonları
def decide_to_generate(state: GraphState) -> str:
    """Üretim mi yoksa web araması mı yapılacağına karar verir."""
    if state.get("web_search") == "Yes":
        return "web_search"
    return "generate"


def check_generation_quality(state: GraphState) -> str:
    """Üretim kalitesini kontrol eder, gerekirse döngüye geri döner."""
    if state.get("hallucination_score") == "no":
        retry_count = state.get("retry_count", 0)
        if retry_count < 3:
            return "transform_query"
        return "end"  # Maksimum deneme aşıldı
    return "end"


# Grafı oluştur
workflow = StateGraph(GraphState)

# Düğümleri ekle
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("generate", generate)
workflow.add_node("transform_query", transform_query)
workflow.add_node("web_search", web_search)
workflow.add_node("check_hallucination", check_hallucination)

# Kenarları tanımla
workflow.add_edge(START, "retrieve")
workflow.add_edge("retrieve", "grade_documents")

# Koşullu kenarlar
workflow.add_conditional_edges(
    "grade_documents",
    decide_to_generate,
    {
        "web_search": "web_search",
        "generate": "generate",
    }
)

workflow.add_edge("web_search", "generate")
workflow.add_edge("generate", "check_hallucination")

workflow.add_conditional_edges(
    "check_hallucination",
    check_generation_quality,
    {
        "transform_query": "transform_query",
        "end": END,
    }
)

workflow.add_edge("transform_query", "retrieve")

# Derle ve çalıştır
app = workflow.compile()

# Test
result = app.invoke({
    "question": "API rate limiting nasıl çalışır?",
    "generation": "",
    "documents": [],
    "web_search": "No",
    "retry_count": 0
})

print(result["generation"])

Adım 5: Checkpoint ile Üretim Dayanıklılığı

Gerçek dünya uygulamalarında pipeline'ın herhangi bir adımda başarısız olması çok olası. Ağ zaman aşımı, API limiti, sunucu hatası... Bu durumda kaldığı yerden devam edebilmesi gerekir. LangGraph'ın checkpoint mekanizması tam da bunu sağlıyor:

from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.postgres import PostgresSaver

# Geliştirme için bellek tabanlı checkpoint
memory_checkpointer = MemorySaver()

# Üretim için PostgreSQL tabanlı checkpoint
# postgres_checkpointer = PostgresSaver.from_conn_string(
#     "postgresql://user:pass@localhost/langgraph_checkpoints"
# )

app = workflow.compile(checkpointer=memory_checkpointer)

# Thread ID ile çalıştır — her konuşma kendi durumunu korur
config = {"configurable": {"thread_id": "user-session-001"}}

result = app.invoke(
    {
        "question": "Veritabanı bağlantı havuzunu nasıl yapılandırırım?",
        "generation": "",
        "documents": [],
        "web_search": "No",
        "retry_count": 0
    },
    config=config
)

Klasik RAG vs. Agentic RAG: Performans Karşılaştırması

Peki, tüm bu ek karmaşıklık gerçekten karşılığını veriyor mu? 2026 yılında yayınlanan araştırmalara göre cevap kesinlikle evet:

ÖzellikKlasik RAGAgentic RAG
Getirme YöntemiTek geçişliİteratif, çok adımlı
Gecikme (Latency)DüşükOrta-Yüksek
Karmaşık SorgularZayıfGüçlü
Halüsinasyon OranıYüksekDüşük
Token MaliyetiDüşükOrta
Çok Adımlı Akıl YürütmeDesteklemiyorTam destek
Hata KurtarmaYokOtomatik (CRAG/Self-RAG)

A-RAG araştırması (arXiv, Şubat 2026), ajansal yaklaşımın geleneksel Graph-RAG ve Workflow RAG yöntemlerinden daha az token tüketerek daha yüksek doğruluk sağladığını gösterdi. Özellikle GPT-5-mini gibi güçlü akıl yürütme yeteneklerine sahip modellerde bu fark belirgin şekilde artıyor.

Üretim Ortamı İçin Kontrol Listesi

Pipeline'ınız yerel ortamda çalışıyor, harika. Ama üretim ortamına taşırken birkaç şeye dikkat etmeniz lazım — bunları göz ardı etmek acı verici hatalara yol açabilir:

  • Maksimum döngü sınırı koyun: retry_count ile sonsuz döngüleri önleyin. Üç deneme makul bir başlangıç değeri, ama kullanım senaryonuza göre ayarlayın.
  • Zaman aşımı ekleyin: Her düğüm için bir zaman aşımı süresi belirleyin. Bir LLM çağrısı 30 saniyeyi geçerse, fallback mekanizmasına düşsün.
  • İzlenebilirlik (Observability) kurun: LangSmith veya Datadog LLM Observability ile her düğümün giriş/çıkışlarını ve halüsinasyon skorlarını izleyin. Bu olmadan hata ayıklamak gerçekten kabusa dönüşür.
  • Güven eşiklerini ayarlayın: Halüsinasyon denetçisindeki eşik değerini kullanım senaryonuza göre kalibre edin. Sağlık ve finans gibi kritik alanlarda 0.90+ gibi yüksek bir eşik kullanın.
  • Hibrit arama kullanın: Vektör aramasını BM25 (anahtar kelime araması) ile birleştirin. langchain'in EnsembleRetriever sınıfı bunu kolaylaştırır.
  • Reranker ekleyin: Cohere Rerank veya BGE Reranker gibi bir çapraz kodlayıcı (cross-encoder) ile getirilen belgeleri yeniden sıralayın. Bu tek adım, getirme kalitesini ciddi oranda artırabilir.
  • JSONL logları tutun: Her pipeline çalışmasının giriş sorusu, getirilen belgeler, değerlendirme skorları ve nihai yanıtını JSONL formatında kaydedin. Bu loglar ileride değerlendirme (eval) veri setlerinizi oluşturur.

Sıkça Sorulan Sorular

Agentic RAG ve klasik RAG arasındaki temel fark nedir?

Klasik RAG tek yönlü bir pipeline'dır: getir ve üret. Agentic RAG ise bir kontrol döngüsüdür — LLM getirilen belgeleri değerlendirir, gerekirse sorguyu yeniden yazar, farklı kaynaklara başvurur ve ürettiği yanıtı halüsinasyon açısından denetler. Bu iteratif yaklaşım sayesinde karmaşık, çok adımlı sorularda çok daha güvenilir sonuçlar üretir.

Agentic RAG hangi durumlarda klasik RAG'dan daha iyidir?

Cevabın birden fazla kaynağa yayıldığı, sorgunun belirsiz veya karmaşık olduğu ve yüksek doğruluk gerektiren senaryolarda Agentic RAG tercih edilmeli. Ama her şeyde olduğu gibi burada da bir denge var: basit "belge arama" sorguları için — mesela "bu yapılandırma parametresi ne işe yarar?" — klasik RAG gayet yeterli ve daha düşük gecikme ile maliyet sunuyor.

Halüsinasyon denetçisi (Hallucination Checker) nasıl çalışır?

Halüsinasyon denetçisi, üretilen yanıtı kaynak belgelerle karşılaştıran bir LLM tabanlı değerlendirme düğümü. Doğal dil çıkarımı (NLI) mantığıyla çalışarak, yanıttaki her iddianın belgeler tarafından desteklenip desteklenmediğini kontrol eder. Desteklenmeyen iddialar tespit edildiğinde pipeline otomatik olarak yeniden getirme döngüsüne girer veya "bilgi bulunamadı" yanıtı döndürür.

LangGraph ile Agentic RAG kurmak için hangi Python sürümü gerekir?

Python 3.10 veya üstü gerekli. Temel bağımlılıklar langgraph, langchain, langchain-openai ve bir vektör veritabanı kütüphanesi (geliştirme için ChromaDB, üretim için Pinecone veya Weaviate önerilir).

Agentic RAG'ın maliyeti ne kadar artar?

Agentic RAG, döngüsel yapısı nedeniyle klasik RAG'a göre 2-4 kat daha fazla LLM çağrısı yapabilir. Ancak Şubat 2026 A-RAG araştırması, ajansal yaklaşımın genellikle daha az toplam token tüketerek daha yüksek doğruluk elde ettiğini gösterdi. Maliyeti optimize etmek için değerlendirme düğümlerinde GPT-4o-mini gibi daha hafif modeller kullanabilir, maksimum döngü sayısını sınırlayabilirsiniz.

Yazar Hakkında Editorial Team

Our team of expert writers and editors.