Classic RAG
A straight line. No check on whether retrieval helped.
question → vector search → answer
Agentic RAG
Classic RAG always retrieves. Agentic RAG adds control flow — so the model retrieves only when needed, checks relevance, and rewrites weak queries before answering.
Classic vs Agentic
Most RAG apps do the same thing every time: retrieve, then generate. Agentic RAG asks better questions first.
A straight line. No check on whether retrieval helped.
question → vector search → answer
Control flow with loops, relevance grading, and a rewrite budget.
Should I retrieve at all?
Are these docs relevant?
If not — rewrite and try again (not forever).
The map
Every query enters decide. From there the graph either answers directly or runs retrieve → grade → generate / rewrite. Click a node to inspect its role.
flowchart TD
START([START]) --> Decision["decide\nanswer or retrieve"]
Decision -->|tool_calls| Retriever["retrieve\nChroma tool"]
Decision -->|no tool_calls| END1([END])
Retriever --> Grade{"grade_documents\nrelevant?"}
Grade -->|yes| Generate["generate\ngrounded answer"]
Grade -->|no + rewrites left| Rewrite["rewrite\nrephrase question"]
Grade -->|no + max rewrites| Generate
Generate --> END2([END])
Rewrite -->|loop| Decision
classDef decision fill:#E6F4F1,stroke:#0F766E,color:#134E4A;
classDef terminal fill:#F1F5F9,stroke:#64748B,color:#334155;
classDef node fill:#FFFFFF,stroke:#334155,color:#0F172A;
class Decision,Retriever,Generate,Rewrite node;
class Grade decision;
class START,END1,END2 terminal;
| From | Condition | To |
|---|---|---|
| START | always | decide |
| decide | tools_condition = tools | retrieve |
| decide | no tool calls | END |
| retrieve | grade = yes | generate |
| retrieve | grade = no & rewrites left | rewrite |
| retrieve | grade = no & max rewrites | generate |
| generate | always | END |
| rewrite | always | decide |
Notebook path
Follow the same sequence as agentic-rag-langgraph.ipynb.
Finish one lesson, then advance.
An agentic RAG pipeline where the LLM decides whether to retrieve,
grades document relevance, generates an answer, or rewrites the query
and loops — with a max-rewrite guard (MAX_REWRITES = 2).
Requirements: Python 3.10+, OPENAI_API_KEY in .env
(or Colab userdata).
Install the LangChain / LangGraph stack, then load your API key.
# Install once %pip install -q langchain_core langchain-openai \ langchain-community langgraph chromadb \ langchain-text-splitters beautifulsoup4 \ python-dotenv lxml tiktoken # Load OPENAI_API_KEY from .env or Colab from dotenv import load_dotenv load_dotenv() os.environ.setdefault("USER_AGENT", "agentic-rag-langgraph-notebook")
gpt-4o-mini (cheap & fast for demos)text-embedding-3-large (better retrieval)Index three public posts on different topics so “should I retrieve?” is a real decision — not a toy with one document.
| Topic | Source |
|---|---|
| Reward hacking | Lilian Weng |
| Hallucination | Lilian Weng |
| Transformers | Jay Alammar — Illustrated Transformer |
WebBaseLoader, prefer <article> / <main>agentic-rag-corpus, retriever k=4text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=400,
chunk_overlap=80,
)
vectorstore = Chroma.from_documents(
documents=doc_splits,
collection_name="agentic-rag-corpus",
embedding=embeddings,
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
The tool description is not decoration — it teaches the model when to call retrieval.
retriever_tool = create_retriever_tool(
retriever,
name="retrieve_knowledge_corpus",
description=(
"Search the local knowledge corpus about: reward hacking, "
"LLM hallucination, and Transformer architecture. "
"Use ONLY for those topics. Do NOT use for greetings or trivia."
),
)
MAX_REWRITES = 2
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
rewrite_count: int
rewrite_count + max rewrites prevent infinite loopsEach node prints a banner so you can watch the path in notebook output.
| Node | One-line job |
|---|---|
decide | Bind tools → RETRIEVE or DIRECT ANSWER |
retrieve | ToolNode runs Chroma search |
grade_documents | Structured yes/no with Pydantic |
generate | Grounded answer from latest context |
rewrite | Improved HumanMessage + bump counter |
def grade_documents(state: AgentState) -> Literal["generate", "rewrite"]: result = grader.invoke({...}) if score == "yes": return "generate" if rewrite_count >= MAX_REWRITES: return "generate" # best-effort return "rewrite"
Connect nodes with edges that match the architecture diagram.
workflow = StateGraph(AgentState) workflow.add_node("decide", decide) workflow.add_node("retrieve", retriever_node) workflow.add_node("generate", generate) workflow.add_node("rewrite", rewrite) workflow.add_edge(START, "decide") workflow.add_conditional_edges( "decide", tools_condition, {"tools": "retrieve", END: END}, ) workflow.add_conditional_edges("retrieve", grade_documents) workflow.add_edge("generate", END) workflow.add_edge("rewrite", "decide") app = workflow.compile()
ask()
Wrap app.invoke and print a clean summary: tools called,
rewrite count, final answer.
def ask(question: str): result = app.invoke({ "messages": [HumanMessage(content=question)], "rewrite_count": 0, }) # print tools / rewrite_count / FINAL ANSWER return result ask("What types of reward hacking does Lilian Weng describe?")
Next: explore the four live paths below — corpus, greeting, trivia, multi-topic.
Live paths
Same agent, different questions — watch which route the graph takes.
“What types of reward hacking does Lilian Weng describe?”
Expect banners showing RETRIEVE → grade: YES → grounded answer from the reward-hacking post.
“Hi! How are you today?”
No tool call. If your model still retrieves, tighten the tool description — that is a teaching moment, not a failure.
“What is the capital of India?”
The agent should not waste vector search on trivia outside the corpus.
Self-attention plus hallucination causes — one agent across a multi-topic corpus.
May pull transformers and/or hallucination chunks, then grade and generate (or rewrite if weak).
Design tips
Production-minded habits from the notebook and teaching script.
Multi-topic corpora make “should I retrieve?” meaningful.
Summarize docs and context — don’t dump raw Document objects.
rewrite_count + max rewrites stop infinite loops.
Tell the model when not to retrieve (hi / trivia).
For production: larger k, metadata filters, log every grade/rewrite.
If grading still fails after max rewrites, call a web search tool, grade again, then generate. That is the natural upgrade from this notebook to CRAG.
Ship it
This file is a single static HTML page — no build step. Pair it with the notebook in your repo.
Push this file to your repo, then enable Pages on the branch (or /docs folder).
agentic-rag-langgraph-guide.html
Drag-and-drop the file (or the repo root). Set publish directory to / and open the HTML URL.
Publish directory: .
Point learners to the runnable notebook and architecture notes.
agentic-rag-langgraph.ipynb
agentic-rag-langgraph-architecture.md
LangGraph · LangChain · Chroma · OpenAI embeddings · gpt-4o-mini