| name | rag-system-builder |
| description | Build and deploy local RAG (Retrieval-Augmented Generation) systems with offline document processing, embedding models, and vector storage. |
RAG System Builder Skill
Build complete local RAG systems that work offline with document ingestion, semantic search, and AI-powered Q&A.
🎯 What This Skill Does
This skill guides you through building a complete RAG system that:
- Ingests documents from multiple formats (TXT, PDF, DOCX, MD, HTML, JSON, XML)
- Generates embeddings using sentence-transformers (offline, no API needed)
- Stores vectors locally using FAISS for fast similarity search
- Provides Q&A interface through CLI and web interface
- Works completely offline - no external API calls required
📦 Prerequisites
python --version
pip install sentence-transformers faiss-cpu click flask
🚀 Quick Start
1. Create Project Structure
mkdir rag-system
cd rag-system
touch rag.py embeddings.py vector_store.py retriever.py config.py
2. Download Embedding Model
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='sentence-transformers/all-MiniLM-L6-v2', local_dir='./models/all-MiniLM-L6-v2')"
3. Configure System
Create config.py:
import os
from dataclasses import dataclass
@dataclass
class Config:
embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
local_model_path: str = "./models/all-MiniLM-L6-v2"
chunk_size: int = 512
chunk_overlap: int = 128
vector_store_path: str = "vector_store"
default_top_k: int = 5
supported_formats: tuple = (".txt", ".pdf", ".docx", ".md", ".html", ".json", ".xml")
4. Build Core Components
Embeddings Module (embeddings.py)
import os
import numpy as np
from typing import List
from sentence_transformers import SentenceTransformer
from config import config
class EmbeddingModel:
def __init__(self, model_name: str = None):
self.model_name = model_name or config.embedding_model
self.model = None
self._load_model()
def _load_model(self):
"""Load embedding model with local fallback"""
print(f"Loading embedding model: {self.model_name}")
local_path = config.local_model_path
if os.path.exists(local_path):
print(f"Using local model: {local_path}")
try:
self.model = SentenceTransformer(local_path)
print("Local model loaded successfully")
return
except Exception as e:
print(f"Error loading local model: {e}")
:
.model = SentenceTransformer(.model_name)
()
Exception e:
()
() -> np.ndarray:
texts:
np.array([])
embeddings = []
i (, (texts), batch_size):
batch = texts[i:i + batch_size]
batch_embeddings = .model.encode(batch, convert_to_numpy=)
embeddings.append(batch_embeddings)
np.vstack(embeddings)
Vector Store Module (vector_store.py)
import os
import json
import faiss
import numpy as np
from config import config
class VectorStore:
def __init__(self, base_path: str = "."):
self.base_path = base_path
self.vector_store_path = config.get_vector_store_path(base_path)
self.index = None
self.metadata = []
os.makedirs(self.vector_store_path, exist_ok=True)
def build_index(self, embeddings: np.ndarray, metadata: list):
"""Build FAISS index from embeddings"""
print(f"Building index with {len(embeddings)} vectors")
dimension = embeddings.shape[1]
self.index = faiss.IndexFlatIP(dimension)
faiss.normalize_L2(embeddings)
self.index.add(embeddings)
self.metadata = metadata
print(f"Built index with {len(embeddings)} vectors")
def ():
index_path = os.path.join(.vector_store_path, config.index_file)
metadata_path = os.path.join(.vector_store_path, config.metadata_file)
faiss.write_index(.index, index_path)
(metadata_path, , encoding=) f:
json.dump(.metadata, f, ensure_ascii=, indent=)
()
()
():
index_path = os.path.join(.vector_store_path, config.index_file)
metadata_path = os.path.join(.vector_store_path, config.metadata_file)
os.path.exists(index_path) os.path.exists(metadata_path):
.index = faiss.read_index(index_path)
(metadata_path, , encoding=) f:
.metadata = json.load(f)
()
Retriever Module (retriever.py)
import numpy as np
from config import config
class Retriever:
def __init__(self, vector_store):
self.vector_store = vector_store
def search(self, query: str, top_k: int = None) -> list:
"""Search for relevant documents"""
if top_k is None:
top_k = config.default_top_k
if self.vector_store.index is None:
print("No index loaded. Please ingest documents first.")
return []
from embeddings import EmbeddingModel
embedding_model = EmbeddingModel()
query_embedding = embedding_model.encode_single(query)
query_embedding = np.expand_dims(query_embedding, axis=0)
faiss.normalize_L2(query_embedding)
scores, indices = self.vector_store.index.search(query_embedding, top_k)
results = []
for i, idx in enumerate(indices[0]):
if idx < len(self.vector_store.metadata):
result = .vector_store.metadata[idx].copy()
result[] = (scores[][i])
results.append(result)
results
5. Create CLI Interface (rag.py)
import os
import sys
import click
from ingestion import IngestionPipeline
from embeddings import EmbeddingModel
from vector_store import VectorStore
from retriever import Retriever
from config import config
@click.group()
def cli():
"""OpenClaw RAG System - Local document retrieval"""
pass
@cli.command()
@click.option('--docs-path', required=True, help='Path to folder containing documents')
@click.option('--chunk-size', default=512, help='Chunk size for text splitting')
@click.option('--chunk-overlap', default=128, help='Chunk overlap size')
def ingest(docs_path, chunk_size, chunk_overlap):
"""Ingest documents from a folder into the vector store"""
click.echo(f"Starting ingestion from: {docs_path}")
ingestion = IngestionPipeline(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
embedding_model = EmbeddingModel()
vector_store = VectorStore()
try:
chunks = ingestion.ingest_folder(docs_path)
chunks:
click.echo()
texts = [chunk[] chunk chunks]
metadata = [{
: chunk[],
: chunk[],
: chunk[],
: chunk[]
} chunk chunks]
click.echo()
embeddings = embedding_model.encode(texts)
vector_store.build_index(embeddings, metadata)
vector_store.save()
click.echo()
Exception e:
click.echo()
sys.exit()
():
vector_store = VectorStore()
vector_store.load():
click.echo()
retriever = Retriever(vector_store)
results = retriever.search(query, top_k)
results:
click.echo()
click.echo()
i, result (results, ):
click.echo()
click.echo()
click.echo()
click.echo()
():
vector_store = VectorStore()
vector_store.load():
click.echo()
click.echo()
click.echo()
:
click.echo()
():
vector_store = VectorStore()
vector_store.clear()
click.echo()
__name__ == :
cli()
📋 Usage Examples
Basic Workflow
pip install sentence-transformers faiss-cpu click flask
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='sentence-transformers/all-MiniLM-L6-v2', local_dir='./models/all-MiniLM-L6-v2')"
python rag.py ingest --docs-path ./my-documents
python rag.py query --query "What is machine learning?"
python rag.py stats
Advanced Usage
python rag.py ingest --docs-path ./docs --chunk-size 1024 --chunk-overlap 256
python rag.py query --query "AI applications" --top-k 10
python rag.py interactive
🔧 Troubleshooting
Model Download Issues
Memory Issues
- Reduce chunk size:
--chunk-size 256
- Process documents in batches
- Use smaller embedding model
Encoding Issues (Windows)
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
📁 Project Structure
rag-system/
├── rag.py # CLI interface
├── embeddings.py # Embedding generation
├── vector_store.py # FAISS storage
├── retriever.py # Search functionality
├── config.py # Configuration
├── ingestion.py # Document processing
├── models/
│ └── all-MiniLM-L6-v2/ # Local embedding model
├── vector_store/ # FAISS index and metadata
└── documents/ # Your documents folder
🎯 Use Cases
-
Document Q&A System
- Upload document library
- Ask questions get relevant answers
- Support multiple documents
-
Knowledge Base Search
- Organize documents in folders
- Quick retrieval of relevant information
- Generate contextual answers
-
Research Assistant
- Collect research materials
- Fast information lookup
- Assist with paper writing
📚 References
- Embedding Model: sentence-transformers/all-MiniLM-L6-v2
- Vector Database: FAISS (Facebook AI Similarity Search)
- Similarity Metric: Cosine Similarity
- Chunk Size: 512 tokens (configurable)
- Chunk Overlap: 128 tokens (configurable)
🤝 Contributing
This skill is designed to be extended. You can:
- Add support for more document formats
- Implement different embedding models
- Add web interface features
- Create specialized RAG systems for specific domains
Skill Version: 1.0.0
Last Updated: 2026-03-05
Author: Wangwang (OpenClaw Personal Assistant)