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):
- Utilizatorul pune o întrebare
- Sistemul caută documente în baza vectorială
- Documentele sunt trimise LLM-ului ca context
- LLM-ul generează un răspuns
- Gata — indiferent dacă răspunsul e corect sau nu
Cam trist, nu? Acum compară cu asta:
Agentic RAG (cu buclă de auto-corecție):
- Utilizatorul pune o întrebare
- Un Router decide dacă întrebarea necesită retrieval sau poate fi răspunsă direct
- Dacă e nevoie de retrieval, Retriever-ul aduce documente din baza vectorială
- Un Grader (evaluator) analizează fiecare document — este relevant pentru întrebare?
- Dacă documentele sunt irelevante, sistemul rescrie întrebarea și reia căutarea
- Generator-ul produce răspunsul pe baza documentelor relevante
- Un Hallucination Checker verifică dacă răspunsul este susținut de documente
- 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-4osauclaude-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_countmaxim. 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.