Agentic RAG¶
Standard RAG is a fixed pipeline: retrieve once, then generate. Agentic RAG replaces that pipeline with a reasoning loop where an LLM decides when to retrieve, what to retrieve, whether the retrieved documents are good enough, and whether to try again — turning RAG into a goal-directed process.
What you'll learn¶
- Core agentic patterns: tool-use, query routing, self-querying, and iterative retrieval
- Corrective RAG (CRAG): grade retrieved docs, re-retrieve if they are insufficient
- How to model the agent loop with a Mermaid diagram
- A pseudocode sketch of an agentic RAG system using Ollama tool-calling concepts
Core patterns¶
Tool-using retrieval agent¶
The LLM is given a search tool. It calls the tool when it decides retrieval is needed, inspects the results, and either answers or calls the tool again with a refined query.
Query routing¶
A classifier (or the LLM itself) routes queries to the right retriever: vector store for semantic questions, SQL database for structured data, web search for current events.
Self-querying¶
The LLM parses a natural-language query into a structured filter + semantic query. Example: "papers about attention mechanisms published after 2022" becomes {"year": {">": 2022}, "query": "attention mechanism"}.
Corrective RAG (CRAG)¶
CRAG adds a grading step after retrieval. If retrieved documents are deemed irrelevant, the agent reformulates and re-retrieves — possibly from a different source.
The agentic loop¶
flowchart TD
Start([User query]) --> Reason[Agent reasons:\ndo I need to retrieve?]
Reason -->|Yes| Retrieve[Call search tool]
Retrieve --> Grade{Are docs\nrelevant?}
Grade -->|Yes| Generate[Generate answer]
Grade -->|No, retry < max| Rewrite[Rewrite query]
Rewrite --> Retrieve
Grade -->|No, max retries| Fallback[Answer from\nLLM knowledge\nor escalate]
Generate --> Answer([Final answer])
Fallback --> Answer
Pseudocode — agentic RAG with Ollama¶
Ollama supports tool/function calling with models like llama3.2. The pattern below shows the structure; adapt to your retrieval backend.
# agentic_rag.py
import ollama
import json
import chromadb
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction
# --- Retriever setup ---
ef = SentenceTransformerEmbeddingFunction("all-MiniLM-L6-v2")
client = chromadb.Client()
col = client.create_collection("agent_demo", embedding_function=ef)
col.add(
documents=[
"HNSW indexes trade memory for fast approximate nearest-neighbor search.",
"Reciprocal Rank Fusion combines multiple ranked lists without score normalization.",
"Cross-encoders score query-document pairs jointly, improving reranking quality.",
],
ids=["d0", "d1", "d2"],
)
# --- Tool definition ---
search_tool = {
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "Search the local knowledge base for relevant documents.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query."},
"top_k": {"type": "integer", "description": "Number of results.", "default": 3},
},
"required": ["query"],
},
},
}
def search_knowledge_base(query: str, top_k: int = 3) -> list[str]:
results = col.query(query_texts=[query], n_results=top_k)
return results["documents"][0]
def grade_docs(query: str, docs: list[str]) -> bool:
"""Ask the LLM to grade whether docs are relevant to query."""
joined = "\n".join(f"- {d}" for d in docs)
prompt = (
f"Query: {query}\n\nDocuments:\n{joined}\n\n"
"Are these documents relevant to the query? Answer YES or NO only."
)
r = ollama.chat(model="llama3.2",
messages=[{"role": "user", "content": prompt}])
return "YES" in r["message"]["content"].upper()
def run_agentic_rag(user_query: str, max_retries: int = 2) -> str:
messages = [
{"role": "system", "content": "You are a helpful assistant with access to a knowledge base. Use the search tool when you need information."},
{"role": "user", "content": user_query},
]
for attempt in range(max_retries + 1):
response = ollama.chat(
model="llama3.2",
messages=messages,
tools=[search_tool],
)
msg = response["message"]
# If no tool call, model answered from context
if not msg.get("tool_calls"):
return msg["content"]
# Execute tool calls
all_docs = []
for tc in msg["tool_calls"]:
args = tc["function"]["arguments"]
if isinstance(args, str):
args = json.loads(args)
docs = search_knowledge_base(**args)
all_docs.extend(docs)
# Grade retrieved docs
if not grade_docs(user_query, all_docs) and attempt < max_retries:
# Rewrite and retry
messages.append({"role": "assistant", "content": "Retrieved documents were not relevant. Reformulating query."})
messages.append({"role": "user", "content": f"The previous search was insufficient. Try a different angle: {user_query}"})
continue
# Inject docs as tool result and let model generate
context = "\n".join(all_docs)
messages.append(msg)
messages.append({
"role": "tool",
"content": context,
})
final = ollama.chat(model="llama3.2", messages=messages)
return final["message"]["content"]
return "Could not retrieve sufficient information."
if __name__ == "__main__":
answer = run_agentic_rag("How does HNSW handle memory usage?")
print(answer)
Model support
Tool-calling works best with instruction-tuned models. llama3.2 supports it natively in Ollama. Check ollama list and ensure you have a recent version.
Limit your retry loops
Always set a hard cap (max_retries) on retrieval loops. An unconstrained agent can spiral into repeated retrievals, driving up latency and cost. Two to three retries is usually sufficient.
Query routing example¶
# query_router.py
import ollama
ROUTE_PROMPT = """Classify the query into one of: [vector_search, sql_query, web_search, direct_answer].
Output ONLY the label.
Query: {query}"""
def route_query(query: str) -> str:
r = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": ROUTE_PROMPT.format(query=query)}]
)
return r["message"]["content"].strip().lower()
print(route_query("What is the capital of France?")) # direct_answer
print(route_query("Find papers about transformers post 2023")) # web_search
print(route_query("How many orders were placed last week?")) # sql_query
Next steps¶
- Query transformation — pre-process queries before the agent retrieves
- Graph RAG — extend the agent's retrieval with structured knowledge graphs