How to Implement a Modular RAG System with Pluggable Components: A Complete Guide

A modular RAG system with pluggable components separates retrieval, processing, and generation into interchangeable layers bound by prompt contracts and protocol orchestration, enabling dynamic adaptation and independent scaling of each pipeline stage.

Implementing a modular retrieval-augmented generation (RAG) architecture requires moving beyond monolithic pipelines. In the davidkimai/context-engineering repository, the framework decomposes RAG into three independent layers—prompt templates, programming components, and protocol orchestration—that communicate through strict contracts. This design allows teams to swap retrieval algorithms, upgrade LLM prompts, or change orchestration logic without rewriting downstream code.

The Three-Layer Architecture of a Modular RAG System

The architecture defined in 00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md organizes functionality into distinct layers that can be developed, versioned, and deployed separately.

Prompt Templates Layer (Communication)

The prompt layer defines component interface contracts—structured prompt strings, error-handling templates, and metric specifications that dictate how layers communicate. Each component must honor these contracts to ensure interchangeability. According to the source specification, this layer includes versioned input-schema and output-schema definitions that validate data passing between retrieval and generation stages.

Programming Components Layer (Implementation)

This layer contains the concrete code executing retrieval, filtering, ranking, and synthesis. All components inherit from BaseRAGComponent, which standardizes the process pipeline, input validation, and metric recording. The source code in 00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md lines 93-118 defines this base class, while specialized implementations like AdaptiveRAGComponent and BasicGraphRAG extend it for specific retrieval strategies.

Protocol Orchestration Layer (Coordination)

The protocol layer describes workflows that wire prompts and components together, handling discovery, resource allocation, and fault tolerance. Protocol definitions such as /rag.component.basic{…} and /rag.component.adaptive{…} specify execution chains declaratively. This allows the orchestrator to dynamically select and sequence components without hard-coding dependencies.

Implementing Base RAG Components

All pluggable components in this modular RAG system derive from BaseRAGComponent, which enforces consistent interfaces and observability.

class BaseRAGComponent:
    """Foundation class for all RAG components"""
    def __init__(self, config, prompt_templates):
        self.config = config
        self.templates = prompt_templates
        self.metrics = ComponentMetrics()
    
    def process(self, input_data):
        validated_input = self.validate_input(input_data)
        processed_result = self.execute(validated_input)
        formatted_output = self.format_output(processed_result)
        self.metrics.record_execution(input_data, formatted_output)
        return formatted_output

As implemented in 00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md, the process method standardizes four phases: input validation, execution, output formatting, and metrics recording. This ensures that any component swapping maintains consistent observability and error-handling behavior.

Building Adaptive RAG Components for Dynamic Optimization

For scenarios requiring runtime optimization, AdaptiveRAGComponent extends the base class to enable context-aware strategy selection.

class AdaptiveRAGComponent(BaseRAGComponent):
    """Self-optimizing RAG component with context awareness"""
    def __init__(self, config, prompt_templates, performance_history):
        super().__init__(config, prompt_templates)
        self.performance_history = performance_history
        self.strategy_selector = StrategySelector(performance_history)
    
    def execute(self, validated_input):
        # Select strategy based on query complexity and latency constraints

        strategy = self.strategy_selector.select_strategy(
            query=validated_input,
            constraints=self.config.get('constraints', {})
        )
        return strategy.execute(validated_input)

According to the source in 00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md lines 81-112, this component analyzes the query, predicts component performance, and dynamically selects execution strategies. If quality thresholds are not met, the orchestrator can trigger fallback mechanisms or re-route to alternative components without interrupting the pipeline.

Protocol-Based Orchestration

The modular RAG system uses declarative protocols to wire components together, enabling dynamic discovery and execution.

Basic Orchestration Protocol

The /rag.component.basic{…} protocol defines a linear execution chain where each component receives the previous output as its input.

/rag.component.basic{
    intent="Coordinate basic RAG component execution",
    input={ query="<user_query>", component_chain=["retriever","processor","generator"] },
    process=[
        /component.execute{
            for_each="component in component_chain",
            action="execute component with previous output as input",
            error_handling="fallback_to_default_component"
        }
    ],
    output={ final_result="<processed_output>", execution_trace="<component_execution_log>" }
}

As specified in 00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md lines 122-144, this protocol handles resource allocation and fault tolerance through explicit error-handling directives.

Adaptive Orchestration Protocol

The advanced protocol (/rag.component.adaptive{…}) analyzes queries and predicts component performance to dynamically build execution pipelines, falling back or re-routing if quality thresholds are not met【/00_COURSE/04_retrieval_augmented_generation/01_modular_architectures.md#L140-L170】.

Extending with Graph-Enhanced RAG

The modular architecture accommodates specialized retrievers like knowledge-graph modules without modifying core orchestration logic.

The BasicGraphRAG class, defined in 00_COURSE/04_retrieval_augmented_generation/03_graph_enhanced_rag.md lines 18-45, plugs into the same component interface:

class BasicGraphRAG:
    """Foundation graph-enhanced RAG with basic relationship awareness"""
    def __init__(self, knowledge_graph, text_corpus, graph_templates):
        self.knowledge_graph = knowledge_graph
        self.text_corpus = text_corpus
        self.templates = graph_templates
        self.entity_linker = EntityLinker()
        self.graph_navigator = GraphNavigator()
    
    def process_query(self, query):
        entities = self.entity_linker.extract_entities(query)
        graph_context = self.graph_navigator.explore_subgraph(entities)
        return self.templates.render_response(query, graph_context)

Graph-aware prompt templates (lines 70-92 of the same file) define how the graph module communicates with the orchestrator, ensuring compatibility with the broader modular RAG system.

Practical Implementation Examples

Example 1: Assemble a Basic Modular RAG Pipeline

from modular_architecture import BaseRAGComponent, AdaptiveRAGComponent

# Load prompt contracts from markdown specification

prompt_templates = load_prompt_templates("component_interface_template.md")

# Instantiate concrete components

retriever = BaseRAGComponent(config=vec_cfg, prompt_templates=prompt_templates)
processor = AdaptiveRAGComponent(config=proc_cfg,
                                prompt_templates=prompt_templates,
                                performance_history=hist)

# Simple orchestrator implementing /rag.component.basic protocol

def run_basic_rag(query):
    step1 = retriever.process({"query": query})
    step2 = processor.process(step1)
    # Generator component follows same pattern

    return step2

Example 2: Integrate Graph-Enhanced Retrieval

from graph_enhanced_rag import BasicGraphRAG

# Initialize graph module with same prompt contract

graph_rag = BasicGraphRAG(knowledge_graph=my_graph,
                          text_corpus=my_corpus,
                          graph_templates=load_templates("graph_query_template.md"))

def run_graph_rag(query):
    # Returns formatted output compatible with processor components

    return graph_rag.process_query(query)

Example 3: Adaptive Orchestrator with Runtime Selection

def adaptive_orchestrator(query, context):
    # Implements /rag.component.adaptive protocol logic

    if "entity" in query.lower():
        component = graph_rag
    else:
        component = retriever
    
    # Unified interface allows interchangeable execution

    result = component.process(query if hasattr(component, "process") else {"query": query})
    return result

Summary

  • Three-layer architecture separates prompt contracts, programming components, and protocol orchestration, enabling independent development and scaling of retrieval, processing, and generation stages.
  • BaseRAGComponent enforces standardized interfaces for input validation, execution, and metrics, ensuring all pluggable components maintain consistent observability.
  • Protocol definitions (/rag.component.basic and /rag.component.adaptive) provide declarative workflow specifications that handle component discovery, chaining, and fault tolerance without hard-coded dependencies.
  • Graph-enhanced modules like BasicGraphRAG demonstrate how specialized retrievers integrate into the same modular RAG system by adhering to shared prompt contracts and the base component interface.

Frequently Asked Questions

What makes a RAG system "modular"?

A modular RAG system decomposes the pipeline into independent layers—prompt templates, programming components, and protocol orchestration—where each component implements a standardized interface (typically inheriting from BaseRAGComponent). This modularity allows any retrieval, processing, or generation module to be swapped, scaled, or updated without modifying dependent code, provided the new component honors the shared prompt contract and protocol definitions.

How does protocol orchestration differ from hard-coded pipelines?

Protocol orchestration uses declarative specifications (such as /rag.component.basic{…} or /rag.component.adaptive{…}) to define workflows, component chains, and error-handling strategies at runtime. Unlike hard-coded pipelines where execution order is fixed in source code, protocol-based orchestration enables dynamic discovery of components, adaptive selection based on query characteristics or performance history, and automatic fallback mechanisms when quality thresholds are not met.

Can I mix different retrieval strategies in the same modular RAG system?

Yes. The architecture supports heterogeneous retrieval strategies through the common BaseRAGComponent interface. For example, you can combine dense vector retrievers (inheriting from BaseRAGComponent) with knowledge-graph modules like BasicGraphRAG (defined in 00_COURSE/04_retrieval_augmented_generation/03_graph_enhanced_rag.md) within the same orchestration protocol. The adaptive orchestrator can route queries to the appropriate retriever based on entity detection, query complexity, or latency requirements.

What is the role of prompt templates in component interoperability?

Prompt templates serve as the communication contract between layers in a modular RAG system. They define structured input schemas, output formats, error-handling procedures, and metric specifications that all components must honor. By standardizing these interfaces through prompt templates (typically loaded from markdown specification files), the system ensures that a BasicGraphRAG module can seamlessly replace a standard retriever without breaking the orchestration protocol, as both adhere to the same prompt contract defined in the template layer.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →