How Retrieval Augmented Generation (RAG) Powers AI Agent Systems: A Deep Dive into AI-Agent-Book
Retrieval Augmented Generation (RAG) in AI agent systems fuses external knowledge retrieval with LLM reasoning through a modular ReAct architecture that supports multiple backends including local vector stores, Dify, RAPTOR, and GraphRAG.
The bojieli/ai-agent-book repository demonstrates a production-ready implementation of Retrieval Augmented Generation (RAG) within AI agents. This codebase showcases how to build extensible retrieval pipelines that separate orchestration logic from storage backends, enabling agents to dynamically decide when and how to retrieve external information.
The Modular RAG Architecture
The repository implements RAG through a clean, three-layer architecture following the ReAct pattern (reason-act-observe-think). This design allows developers to swap retrieval strategies without touching the agent's reasoning logic.
The architecture consists of:
AgenticRAG(chapter3/contextual-retrieval/agent.py): The orchestrator that maintains conversation history and runs the ReAct loopKnowledgeBaseTools(chapter3/contextual-retrieval/tools.py): The abstraction layer implementing concrete retrieval operations- Configuration layer (
chapter3/contextual-retrieval/config.py): DefinesKnowledgeBaseTypeenum values (LOCAL, DIFY, RAPTOR, GRAPHRAG) that switch backends via environment variables
Core Components of the Agentic RAG System
AgenticRAG: The ReAct Orchestrator
The AgenticRAG class (lines 73-115 in agent.py) serves as the central coordinator. It holds the LLM client, manages conversation state, and implements the decision loop that determines when to retrieve information versus when to generate answers.
When processing a query, the agent wraps user input with a system prompt (lines 73-99) that instructs the LLM to use the knowledge base exclusively. The query() method runs the iterative ReAct loop where the model may output tool calls like knowledge_base_search to fetch relevant context before synthesizing a response.
KnowledgeBaseTools: Abstracting Retrieval Operations
The KnowledgeBaseTools class (lines 58-147 in tools.py) implements the concrete retrieval interface. It exposes two primary operations:
knowledge_base_search: Performs semantic search across the configured backendget_document: Retrieves full documents by ID when the LLM needs additional context
The tool layer inspects self.config.type and routes requests to the appropriate private method: _search_local(), _search_dify(), _search_raptor(), or the GraphRAG equivalent.
Configuration-Driven Backend Selection
The KnowledgeBaseConfig dataclass and KnowledgeBaseType enum (in chapter3/contextual-retrieval/config.py) enable one-line backend swaps. Changing the KNOWLEDGE_BASE_TYPE environment variable switches from a local vector store to GraphRAG without modifying agent code.
Supported Retrieval Backends
Local Hybrid Retrieval
The default backend (KnowledgeBaseType.LOCAL) runs a self-hosted FastAPI service that performs hybrid dense and sparse retrieval on a local vector store. The _search_local() method (lines 70-84 in tools.py) sends POST requests to the /search endpoint and normalizes responses into a standard SearchResult format containing doc_id, chunk_id, text, and score.
Dify API Integration
For cloud-hosted retrieval, the _search_dify() method (lines 58-79 in tools.py) calls the Dify /datasets/search endpoint using the configured API key. This backend suits teams wanting managed vector storage without infrastructure overhead.
RAPTOR: Hierarchical Tree Retrieval
The RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval) backend enables multi-step retrieval through a tree-structured index. While the specific implementation follows the same pattern as other backends in KnowledgeBaseTools, it supports hierarchical document traversal that standard flat retrieval cannot achieve.
GraphRAG: Structured Knowledge Graphs
GraphRAG (chapter3/structured-index/graphrag_indexer.py) moves beyond semantic similarity to structured graph traversal. The GraphRAGIndexer class builds knowledge graphs by:
- Chunking documents (using
chunk_textwith configurablechunk_sizeandchunk_overlap) - Extracting entities and relationships via LLM calls (
extract_entities_relationships) - Constructing traversable graphs using community detection algorithms like Leiden
This backend supports multi-hop reasoning essential for complex, compositional queries where relationships between entities matter more than keyword overlap.
The ReAct Loop: How Retrieval Augmented Generation Works in Practice
The RAG workflow executes through seven distinct steps:
-
Query Processing: The user question enters
AgenticRAG.query()and gets wrapped with the system prompt enforcing knowledge base usage. -
Tool Decision: In the ReAct loop (lines 94-115 in
agent.py), the LLM decides whether to answer directly or callknowledge_base_search. -
Backend Routing:
AgenticRAG._execute_toolforwards the call toKnowledgeBaseTools, which dispatches to the appropriate_search_*method based onconfig.type. -
Data Retrieval: The selected backend queries vector stores, Dify APIs, or traverses knowledge graphs to fetch relevant chunks.
-
Result Formatting: Search results return as standardized dictionaries containing document metadata and relevance scores.
-
Iterative Refinement: If needed, the LLM issues secondary calls (
get_document) to fetch complete documents rather than chunks. -
Answer Synthesis: The LLM generates final responses with inline citations (e.g.,
[Doc: 123]) based on retrieved evidence.
Contextual Chunking for Improved Accuracy
Standard RAG often loses contextual meaning when splitting documents. The repository addresses this through ContextualChunker (in chapter3/contextual-retrieval/contextual_chunking.py).
This class enriches each retrieved chunk with surrounding context:
from chapter3.contextual_retrieval.contextual_chunking import ContextualChunker
chunker = ContextualChunker(window_size=2) # keep 2 preceding/following chunks
chunks = chunker.chunk_document(full_document_text)
# Each chunk contains a context field for disambiguation
for ch in chunks:
print(f"{ch.context} {ch.text}")
By preserving surrounding sentences, the system resolves ambiguous references like "the company" to specific entities (e.g., "ACME Corp"), significantly improving answer fidelity.
Implementation Examples
Instantiating the Agentic RAG System
from chapter3.contextual_retrieval.agent import AgenticRAG
from chapter3.contextual_retrieval.config import Config
# Load from environment variables (.env)
cfg = Config.from_env()
# Initialize with configured backend
rag = AgenticRAG(cfg)
# Query triggers automatic retrieval and ReAct reasoning
answer = rag.query("What are the revenue growth figures for ACME Corp in 2023?")
print(answer)
The Config.from_env() method reads KNOWLEDGE_BASE_TYPE, LOCAL_BASE_URL, and related parameters to instantiate the correct pipeline.
Building a GraphRAG Index
from chapter3.structured_index.graphrag_indexer import GraphRAGIndexer
from chapter3.structured_index.config import GraphRAGConfig
graphrag_cfg = GraphRAGConfig(
llm_api_key="YOUR_API_KEY",
base_url="https://api.openrouter.ai/v1",
llm_model="gpt-5.6-luna",
index_dir="data/graph_index",
cache_dir="data/graph_cache",
chunk_size=1200,
chunk_overlap=100,
max_knowledge_triples=10,
community_detection_algorithm="leiden",
)
graphrag = GraphRAGIndexer(graphrag_cfg)
# Build graph from technical documentation
with open("docs/technical_spec.md") as f:
sample_text = f.read()
graphrag.build_knowledge_graph(sample_text)
# Query supports multi-hop reasoning
results = graphrag.query("Which x86 instruction modifies the FLAGS register?")
Adding Context to Retrieved Chunks
from chapter3.contextual_retrieval.contextual_chunking import ContextualChunker
chunker = ContextualChunker(window_size=2)
contextualized = chunker.chunk_document(technical_manual)
# Results include surrounding sentences for LLM context
for chunk in contextualized[:3]:
print(f"Context: {chunk.context}\nContent: {chunk.text}\n")
Summary
- Modular Architecture: The
AgenticRAGclass separates reasoning logic from retrieval implementation, enabling backend swaps via configuration changes. - Multiple Backends: The system supports local hybrid retrieval, Dify cloud APIs, RAPTOR hierarchical indexes, and GraphRAG structured graphs through a unified
KnowledgeBaseToolsinterface. - ReAct Pattern: The implementation uses a ReAct loop where the LLM decides when to retrieve information, enabling iterative, multi-step reasoning workflows.
- Context Preservation: The
ContextualChunkerclass adds surrounding context to retrieved segments, solving ambiguity problems common in naive RAG implementations. - File Locations: Core logic resides in
chapter3/contextual-retrieval/agent.py(orchestration),tools.py(retrieval abstraction), andchapter3/structured-index/graphrag_indexer.py(graph construction).
Frequently Asked Questions
What makes Agentic RAG different from standard RAG pipelines?
Standard RAG retrieves documents once before generation begins. Agentic RAG, as implemented in AgenticRAG.query(), uses the ReAct pattern to let the LLM decide iteratively when to search, what to search for, and whether to fetch additional documents based on intermediate reasoning. This enables multi-hop queries and self-correction workflows impossible in single-pass retrieval.
How does GraphRAG improve upon vector-based retrieval?
According to the graphrag_indexer.py implementation, GraphRAG builds structured knowledge graphs of entities and relationships rather than relying solely on semantic similarity. This supports multi-hop reasoning queries like "Which company founded by the CEO of ACME's competitor filed for bankruptcy?" where the answer requires traversing relationships across documents, not just keyword matching.
Can I switch between retrieval backends without code changes?
Yes. The KnowledgeBaseType enum in chapter3/contextual-retrieval/config.py enables backend selection through environment variables. Changing KNOWLEDGE_BASE_TYPE from LOCAL to GRAPHRAG or DIFY switches the retrieval mechanism while the agent logic in AgenticRAG remains identical.
Why is contextual chunking necessary for production RAG systems?
As demonstrated in contextual_chunking.py, standard chunking often creates segments with ambiguous references (e.g., "it", "the company", "this approach"). The ContextualChunker adds surrounding sentences to each chunk, providing the LLM with disambiguating context that improves citation accuracy and answer quality when processing technical documentation or long-form content.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →