Agentic RAG cu LangGraph în Python: Cum Construiești Sisteme RAG Care Se Corectează Singure

Învață cum să construiești un sistem Agentic RAG cu LangGraph în Python — cu evaluare automată a documentelor, verificare a halucinărilor și rescriere inteligentă a întrebărilor. Ghid complet cu cod funcțional.

De ce RAG-ul clasic nu mai e suficient în 2026?

Dacă ai construit vreodată un pipeline RAG tradițional — retrieval, augmentare, generare — probabil știi exact despre ce vorbesc. Modelul halucinează când răspunsul nu se află în baza de documente. Documentele recuperate sunt complet pe lângă subiect, dar sistemul le folosește oricum. Iar când întrebarea utilizatorului este complexă sau ambiguă? Totul se prăbușește spectaculos.

Pipeline-urile RAG liniare funcționează ca o bandă de producție cu un singur sens: documentul intră, răspunsul iese. Atât. Nu există niciun mecanism de verificare, nicio buclă de corecție, nicio decizie inteligentă. Sincer, în 2026 asta nu mai e acceptabil — mai ales când construiești aplicații AI care trebuie să fie fiabile în producție.

Aici intervine Agentic RAG.

E o abordare în care modelul LLM nu mai este un simplu generator de text, ci devine un motor de raționament care poate decide ce instrumente să folosească, poate evalua calitatea documentelor recuperate și poate relua căutarea dacă rezultatele nu sunt satisfăcătoare. Practic, modelul „gândește" înainte să răspundă.

Și cel mai bun framework pentru a construi astfel de sisteme în Python? LangGraph — biblioteca de la LangChain care transformă workflow-urile AI în grafuri cu stări, noduri și muchii condiționale. Hai să vedem cum funcționează.

Ce este Agentic RAG și cum diferă de RAG-ul tradițional?

Pentru a înțelege diferența, gândește-te la două scenarii:

RAG tradițional (liniar):

  1. Utilizatorul pune o întrebare
  2. Sistemul caută documente în baza vectorială
  3. Documentele sunt trimise LLM-ului ca context
  4. LLM-ul generează un răspuns
  5. Gata — indiferent dacă răspunsul e corect sau nu

Cam trist, nu? Acum compară cu asta:

Agentic RAG (cu buclă de auto-corecție):

  1. Utilizatorul pune o întrebare
  2. Un Router decide dacă întrebarea necesită retrieval sau poate fi răspunsă direct
  3. Dacă e nevoie de retrieval, Retriever-ul aduce documente din baza vectorială
  4. Un Grader (evaluator) analizează fiecare document — este relevant pentru întrebare?
  5. Dacă documentele sunt irelevante, sistemul rescrie întrebarea și reia căutarea
  6. Generator-ul produce răspunsul pe baza documentelor relevante
  7. Un Hallucination Checker verifică dacă răspunsul este susținut de documente
  8. Dacă răspunsul nu este fundamentat, sistemul regenerează sau caută din nou

Diferența esențială? Agentic RAG nu este un pipeline — este o buclă. Sistemul nu eșuează silențios; încearcă activ să repare problema. E ca diferența dintre un student care predă lucrarea fără să o citească și unul care o revizuiește de trei ori.

De ce LangGraph pentru Agentic RAG?

LangGraph este o bibliotecă Python din ecosistemul LangChain, proiectată pentru a construi aplicații AI cu stări persistente și fluxuri condiționale. Spre deosebire de LangChain-ul clasic (care funcționează cu chain-uri liniare), LangGraph tratează workflow-urile ca grafuri direcționate cu:

  • Noduri — fiecare nod execută o operație specifică (retrieval, evaluare, generare)
  • Muchii — conectează nodurile și controlează fluxul de date
  • Muchii condiționale — permit ramificarea dinamică bazată pe rezultate
  • Stare persistentă — un dicționar partajat între toate nodurile, care păstrează contextul

Această arhitectură pe bază de graf face LangGraph perfect pentru Agentic RAG. Ai nevoie de bucle, condiții și reîncercări — exact lucrurile pe care un chain liniar nu le poate oferi. Odată ce înțelegi modelul mental de „nod + muchie + condiție", totul devine surprinzător de intuitiv.

Pregătirea mediului de dezvoltare

Cerințe preliminare

Înainte de a începe, asigură-te că ai Python 3.11 sau mai nou instalat și un API key OpenAI (sau alt provider LLM compatibil). Dacă folosești deja un environment virtual, cu atât mai bine.

Instalarea dependențelor

pip install langgraph langchain langchain-openai langchain-chroma chromadb langchain-text-splitters tiktoken

Configurarea variabilelor de mediu

import os

os.environ["OPENAI_API_KEY"] = "sk-your-api-key-here"

Un sfat rapid: pentru producție, folosește un fișier .env și python-dotenv în loc să hardcodezi cheia API. Pare evident, dar am văzut chei API uitate pe GitHub mai des decât mi-ar plăcea să recunosc.

Arhitectura completă: Nod cu nod

Sistemul nostru Agentic RAG va avea mai multe componente, fiecare implementată ca un nod în graful LangGraph. O să le luăm pe rând.

Pasul 1: Definirea stării grafului

Starea este „creierul" agentului — un dicționar care urmărește întrebarea curentă, documentele recuperate, răspunsul generat și numărul de reîncercări. Simplă, dar esențială:

from typing import List, TypedDict


class GraphState(TypedDict):
    """Starea grafului Agentic RAG."""
    question: str
    generation: str
    documents: List[str]
    retry_count: int

Pasul 2: Configurarea bazei de date vectoriale cu ChromaDB

ChromaDB este o bază de date vectorială open-source care se integrează nativ cu LangChain. Vom încărca documente, le vom împărți în fragmente și le vom indexa. Iată cum arată:

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader

# Încarcă documentele
loader = TextLoader("documentatie.txt")
docs = loader.load()

# Împarte în fragmente de 500 de caractere cu suprapunere de 100
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=100
)
splits = text_splitter.split_documents(docs)

# Creează baza de date vectorială
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    collection_name="agentic-rag-docs"
)

# Creează retriever-ul
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 4}
)

Câteva observații importante aici. Am folosit RecursiveCharacterTextSplitter care încearcă să păstreze integritatea semantică a textului — împarte mai întâi pe paragrafe, apoi pe propoziții, apoi pe cuvinte. Tipul de căutare mmr (Maximum Marginal Relevance) asigură diversitate în rezultate, evitând documente prea similare între ele. E o diferență subtilă față de căutarea simplă pe similaritate, dar contează mult în practică.

Pasul 3: Nodul Retriever

Nodul de retrieval e cel mai simplu din tot graful — doar preia documente din baza vectorială:

def retrieve(state: GraphState) -> GraphState:
    """Recuperează documente relevante din baza vectorială."""
    question = state["question"]
    documents = retriever.invoke(question)
    return {
        "documents": [doc.page_content for doc in documents],
        "question": question,
    }

Pasul 4: Nodul Grader (Evaluator de relevanță)

Ăsta e primul element care face sistemul cu adevărat „agentic". Grader-ul folosește un LLM pentru a evalua dacă fiecare document recuperat este relevant pentru întrebare. Fără el, ai trimite orice document la generator și ai spera că totul iese bine (spoiler: de obicei nu iese).

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


class GradeDocuments(BaseModel):
    """Scor binar de relevanță."""
    binary_score: str = Field(
        description="Documentul este relevant: 'yes' sau 'no'"
    )

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

grade_prompt = ChatPromptTemplate.from_messages([
    ("system", """Ești un evaluator de relevanță. Analizează dacă un document
    conține informații relevante pentru întrebarea utilizatorului.
    Răspunde cu 'yes' dacă documentul este relevant, 'no' dacă nu este.
    Nu fi prea strict — dacă documentul conține cuvinte cheie sau concepte
    legate de întrebare, consideră-l relevant."""),
    ("human", "Document: {document}\n\nÎntrebare: {question}"),
])

doc_grader = grade_prompt | structured_llm_grader


def grade_documents(state: GraphState) -> GraphState:
    """Evaluează relevanța documentelor recuperate."""
    question = state["question"]
    documents = state["documents"]

    relevant_docs = []
    for doc in documents:
        score = doc_grader.invoke(
            {"question": question, "document": doc}
        )
        if score.binary_score == "yes":
            relevant_docs.append(doc)

    return {
        "documents": relevant_docs,
        "question": question,
    }

Pasul 5: Nodul de rescriere a întrebării

Când Grader-ul determină că documentele nu sunt relevante, sistemul nu renunță — rescrie întrebarea pentru o formulare mai bună. E ca atunci când cauți ceva pe Google și reformulezi query-ul după ce primele rezultate nu sunt utile:

rewrite_prompt = ChatPromptTemplate.from_messages([
    ("system", """Ești un specialist în reformularea întrebărilor pentru
    căutare semantică. Rescrie întrebarea pentru a obține rezultate
    mai bune din baza de documente. Păstrează sensul original, dar
    folosește termeni mai specifici și descriptivi."""),
    ("human", "Întrebarea originală: {question}\n\nÎntrebare reformulată:"),
])

question_rewriter = rewrite_prompt | ChatOpenAI(
    model="gpt-4o-mini", temperature=0.3
)


def rewrite_question(state: GraphState) -> GraphState:
    """Rescrie întrebarea pentru rezultate mai bune."""
    question = state["question"]
    retry_count = state.get("retry_count", 0)

    better_question = question_rewriter.invoke({"question": question})

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

Pasul 6: Nodul Generator

Generator-ul este cel care produce efectiv răspunsul. Observă că prompt-ul îi cere explicit să nu inventeze informații și să citeze din context:

generate_prompt = ChatPromptTemplate.from_messages([
    ("system", """Ești un asistent AI care răspunde la întrebări STRICT
    pe baza contextului furnizat. Nu inventa informații. Dacă contextul
    nu conține răspunsul, spune clar acest lucru. Citează pasajele
    relevante din context."""),
    ("human", "Context:\n{context}\n\nÎntrebare: {question}"),
])

generator = generate_prompt | ChatOpenAI(model="gpt-4o", temperature=0)


def generate(state: GraphState) -> GraphState:
    """Generează răspunsul pe baza documentelor relevante."""
    question = state["question"]
    documents = state["documents"]
    context = "\n\n---\n\n".join(documents)

    generation = generator.invoke({
        "context": context,
        "question": question,
    })

    return {
        "generation": generation.content,
        "documents": documents,
        "question": question,
    }

Pasul 7: Nodul Hallucination Checker

Ultimul strat de protecție — și, sincer, cel mai important. Verifică dacă răspunsul generat este fundamentat pe documentele furnizate. Fără această verificare, întregul efort de mai sus ar fi incomplet:

class HallucinationGrade(BaseModel):
    """Evaluare halucinare."""
    binary_score: str = Field(
        description="Răspunsul este susținut de documente: 'yes' sau 'no'"
    )

hallucination_prompt = ChatPromptTemplate.from_messages([
    ("system", """Ești un evaluator de halucinări. Verifică dacă răspunsul
    LLM-ului este fundamentat pe documentele furnizate.
    Răspunde 'yes' dacă TOATE afirmațiile din răspuns sunt susținute
    de documente. Răspunde 'no' dacă răspunsul conține informații
    care nu se regăsesc în documente."""),
    ("human", "Documente:\n{documents}\n\nRăspuns LLM:\n{generation}"),
])

hallucination_grader = hallucination_prompt | llm.with_structured_output(
    HallucinationGrade
)


def check_hallucination(state: GraphState) -> str:
    """Verifică halucinările și decide următorul pas."""
    documents = state["documents"]
    generation = state["generation"]
    question = state["question"]
    retry_count = state.get("retry_count", 0)

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

    if score.binary_score == "yes":
        return "supported"
    elif retry_count < 3:
        return "not_supported"
    else:
        return "max_retries"

Asamblarea grafului LangGraph

Bun, acum vine partea cea mai satisfăcătoare — conectarea tuturor nodurilor într-un graf cu fluxuri condiționale. E momentul în care totul capătă sens:

from langgraph.graph import StateGraph, END

workflow = StateGraph(GraphState)

# Adăugarea nodurilor
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("rewrite_question", rewrite_question)
workflow.add_node("generate", generate)

# Definirea punctului de intrare
workflow.set_entry_point("retrieve")

# Fluxul principal: retrieve -> grade_documents
workflow.add_edge("retrieve", "grade_documents")


# Decizie condițională după evaluare
def decide_after_grading(state: GraphState) -> str:
    """Decide dacă are suficiente documente relevante."""
    relevant_docs = state["documents"]
    retry_count = state.get("retry_count", 0)

    if len(relevant_docs) > 0:
        return "generate"
    elif retry_count < 3:
        return "rewrite"
    else:
        return "generate"  # Generează cu ce are


workflow.add_conditional_edges(
    "grade_documents",
    decide_after_grading,
    {
        "generate": "generate",
        "rewrite": "rewrite_question",
    }
)

# După rescriere, reia retrieval-ul
workflow.add_edge("rewrite_question", "retrieve")

# După generare, verifică halucinările
workflow.add_conditional_edges(
    "generate",
    check_hallucination,
    {
        "supported": END,
        "not_supported": "rewrite_question",
        "max_retries": END,
    }
)

# Compilarea grafului
app = workflow.compile()

Observă cum graful implementează exact fluxul despre care am discutat mai devreme — retrieval, evaluare, decizie, generare, verificare halucinări, și posibilă revenire la rescriere. Totul e declarativ și ușor de urmărit.

Rularea sistemului Agentic RAG

Cu graful compilat, poți rula întrebări și observa cum sistemul se auto-corectează în timp real:

# Executarea unei întrebări
result = app.invoke({
    "question": "Cum funcționează mecanismul de atenție în transformeri?",
    "documents": [],
    "generation": "",
    "retry_count": 0,
})

print("Răspuns:", result["generation"])
print("Documente folosite:", len(result["documents"]))
print("Reîncercări:", result["retry_count"])

Poți și vizualiza graful generat pentru a înțelege fluxul complet (e util mai ales când depanezi):

# Vizualizarea grafului (necesită pygraphviz sau mermaid)
from IPython.display import Image, display

display(Image(app.get_graph().draw_mermaid_png()))

Extinderi avansate pentru producție

Adăugarea căutării web ca fallback

Când baza de documente nu conține răspunsul, poți adăuga un nod de căutare web folosind Tavily. E o extensie naturală — dacă documentele tale nu au informația, de ce să nu cauți și pe web?

from langchain_community.tools.tavily_search import TavilySearchResults

web_search = TavilySearchResults(max_results=3)


def web_search_node(state: GraphState) -> GraphState:
    """Caută pe web când baza de documente nu are răspuns."""
    question = state["question"]
    results = web_search.invoke({"query": question})
    web_docs = [r["content"] for r in results]
    return {
        "documents": state.get("documents", []) + web_docs,
        "question": question,
    }

Human-in-the-Loop pentru decizii critice

LangGraph permite oprirea execuției pentru aprobare umană — esențial în aplicații cu risc ridicat (medicale, financiare, juridice). O singură linie de cod schimbă totul:

# Compilare cu punct de întrerupere
app = workflow.compile(
    interrupt_before=["generate"]
)

Cu această configurare, agentul va recupera și evalua documentele, apoi va face o pauză și va aștepta confirmarea umană înainte de a genera răspunsul. Simplu și elegant.

Optimizarea costurilor cu modele diferite pe noduri

O strategie pe care o recomand din experiență: folosește modele mai mici și rapide pentru nodurile de rutare și evaluare, rezervând modelele puternice doar pentru generare. Diferența de cost poate fi enormă:

  • Router și Grader: gpt-4o-mini — rapid, ieftin, suficient pentru clasificare binară
  • Generator: gpt-4o sau claude-sonnet-4-6 — calitate superioară pentru răspunsul final
  • Hallucination Checker: gpt-4o-mini — evaluare binară, nu necesită model mare

Greșeli frecvente și cum să le eviți

Am adunat câteva dintre cele mai frecvente probleme pe care le întâlnesc dezvoltatorii cu Agentic RAG. Unele par evidente în retrospectivă, dar în practică sunt surprinzător de ușor de trecut cu vederea:

  • Bucle infinite — Întotdeauna setează un retry_count maxim. Fără această limită, sistemul poate re-evalua și rescrie la nesfârșit (și bugetul tău de API dispare pe loc)
  • Chunk-uri prea mari sau prea mici — Chunk-uri de 500-1000 caractere cu suprapunere de 100-200 oferă cel mai bun echilibru între context și precizie. Am testat cu diferite dimensiuni și ăsta e sweet spot-ul pentru majoritatea cazurilor
  • Grader prea strict — Un evaluator prea exigent respinge documente parțial relevante. Prompt-ul trebuie să fie flexibil: „dacă documentul conține concepte legate de întrebare, consideră-l relevant"
  • Lipsa observabilității — Adaugă logging la fiecare nod pentru a înțelege deciziile sistemului. LangSmith oferă tracing nativ pentru LangGraph și sincer, e aproape indispensabil în debugging
  • Costuri necontrolate — Fiecare buclă de reîncercare costă tokeni. Monitorizează consumul și setează limite de buget înainte de a pune sistemul în producție

Agentic RAG vs. alte abordări: Când să-l folosești?

Agentic RAG nu este întotdeauna soluția optimă. Iată un ghid rapid de decizie care te poate ajuta să alegi abordarea potrivită:

  • RAG tradițional — potrivit pentru întrebări simple cu documente de calitate ridicată și bine structurate. Dacă datele tale sunt curate, nu complica lucrurile inutil
  • Agentic RAG — ideal când documentele sunt eterogene, întrebările sunt complexe sau fiabilitatea răspunsurilor este critică
  • Fine-tuning — preferabil când ai un domeniu foarte specific și suficiente date de antrenament etichetate
  • Agentic RAG + căutare web — cea mai robustă opțiune pentru aplicații care necesită atât cunoștințe interne, cât și informații actualizate din exterior

Întrebări frecvente

Ce diferență este între RAG și Agentic RAG?

RAG-ul tradițional urmează un flux liniar: recuperează documente, le trimite LLM-ului și generează un răspuns. Agentic RAG adaugă bucle de auto-corecție — evaluarea relevanței documentelor, verificarea halucinărilor și rescrierea automată a întrebărilor. Pe scurt, Agentic RAG transformă pipeline-ul pasiv într-un agent activ care poate lua decizii și se poate corecta singur.

LangGraph este gratuit?

Da, LangGraph este open-source și gratuit. Biblioteca principală (langgraph) este disponibilă pe PyPI sub licență MIT. Serviciile adiționale precum LangGraph Cloud și LangSmith (pentru monitoring) au planuri gratuite cu limite și planuri plătite pentru producție.

Pot folosi Agentic RAG cu modele locale în loc de OpenAI?

Absolut. Poți înlocui OpenAI cu modele locale prin Ollama (Llama 3, Mistral, Phi-3) sau cu alte API-uri precum Claude de la Anthropic. LangGraph este agnostic față de provider-ul LLM — schimbi doar inițializarea modelului, restul codului rămâne identic.

Câte reîncercări ar trebui să permită sistemul?

Recomandarea generală este maximum 3 reîncercări. Dincolo de această limită, costurile cresc semnificativ fără îmbunătățiri proporționale ale calității. Dacă sistemul nu poate genera un răspuns satisfăcător după 3 încercări, problema e cel mai probabil în datele sursă, nu în logica de retrieval.

Cum monitorizez performanța unui sistem Agentic RAG în producție?

Folosește LangSmith pentru tracing nativ al fiecărui nod din graf — vei vedea exact ce documente au fost recuperate, cum au fost evaluate și de ce sistemul a decis să rescrie sau să regenereze. Ca metrici de urmărit: rata de halucinare, numărul mediu de reîncercări per query, latența end-to-end și costul per interogare.

Despre Autor Editorial Team

Our team of expert writers and editors.