- Posted on
- • Artificial Intelligence
RAG Explained for Linux Users
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
RAG Explained for Linux Users: Make Your Shell Smarter
If you’ve ever thought, “LLMs are cool, but they don’t know my system, my logs, or my internal docs,” Retrieval-Augmented Generation (RAG) is your fix. RAG lets you feed relevant, real documents from your machine into a language model so the model answers with context you control—no more vague, hallucinated replies about your infra.
In Linux terms: think of RAG as “grep + summarize.” You retrieve the right snippets from your knowledge base, then pipe them into a model to generate an answer. It’s composable, auditable, and scriptable.
This post explains the core idea, why it matters to Linux users, and gives you a minimal, practical RAG setup you can run from Bash using common tools plus a small Python script. You’ll get actionable steps, code you can copy, and distro-specific install commands.
What is RAG, and why should Linux users care?
Retrieval: Search your own files (docs, wikis, man pages, logs, READMEs, code) to find the most relevant chunks for a question.
Augmentation: Prepend those chunks to the prompt as context.
Generation: Ask a language model to answer using that context.
Why it’s valid and valuable:
Verifiable answers: You can show the exact sources that informed the reply.
Up-to-date and private: Your data stays on your machine; update the index anytime.
Unix-friendly: It’s “do one thing well” embodied—retrieval is one tool, generation is another, and Bash glues them together.
What we’ll build
- A lightweight, local RAG pipeline you can run from Bash: 1) Index a folder of docs into a small vector database (FAISS). 2) Query by a question to retrieve the top-k chunks. 3) Feed the chunks into a local LLM (llama.cpp) or a remote API (optional).
You can adapt this to:
Query internal READMEs for onboarding.
Summarize incident timelines from logs.
Pull config snippets from /etc and explain them.
Prerequisites and installation
We’ll use:
System tools: curl, jq, ripgrep, git, cmake, build tools
Python: venv, pip, sentence-transformers, faiss-cpu, numpy
Optional: pandoc to convert docs to text (Markdown, HTML, PDF → .txt)
Optional: llama.cpp for fully local generation
Install system dependencies.
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git build-essential cmake curl jq ripgrep pandoc
Fedora/RHEL (dnf):
sudo dnf install -y python3 python3-virtualenv python3-pip git gcc-c++ make cmake curl jq ripgrep pandoc
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y python3 python3-virtualenv python3-pip git gcc-c++ make cmake curl jq ripgrep pandoc
Set up a Python virtual environment and dependencies:
python3 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip
pip install sentence-transformers faiss-cpu numpy tqdm
Optional: build llama.cpp (local LLM inference):
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
make -j
cd ..
- Download a small GGUF model (example):
mkdir -p models
# Example: Phi-3 mini instruct quantized (check license/size/compatibility)
wget -O models/phi3-mini-instruct.gguf \
https://huggingface.co/bartowski/Phi-3-mini-4k-instruct-GGUF/resolve/main/Phi-3-mini-4k-instruct.Q4_0.gguf
Note: llama.cpp’s binary may be named llama-cli or main depending on version.
Step 1: Prepare a knowledge base
Pick a directory with documents you care about, e.g., ~/kb. Put Markdown, text files, logs, READMEs, and exported wiki pages there.
Convert non-text docs (optional, via pandoc):
mkdir -p ~/kb_text
find ~/kb -type f \( -name '*.md' -o -name '*.txt' -o -name '*.log' \) -exec cp {} ~/kb_text/ \;
# Convert HTML/PDF to text (basic)
find ~/kb -type f -name '*.html' -exec sh -c 'for f; do pandoc "$f" -t plain -o ~/kb_text/"$(basename "$f" .html)".txt; done' sh {} +
find ~/kb -type f -name '*.pdf' -exec sh -c 'for f; do pandoc "$f" -t plain -o ~/kb_text/"$(basename "$f" .pdf)".txt; done' sh {} +
Quick sanity check:
rg -n "nginx|kubernetes|onboarding" ~/kb_text
Step 2: Build a vector index (FAISS)
Create build_index.py in your project directory:
#!/usr/bin/env python3
import os, json, math
from pathlib import Path
import numpy as np
from tqdm import tqdm
import faiss
from sentence_transformers import SentenceTransformer
KB_DIR = Path(os.environ.get("KB_DIR", str(Path.home() / "kb_text")))
OUT_DIR = Path(os.environ.get("OUT_DIR", "index"))
OUT_DIR.mkdir(parents=True, exist_ok=True)
CHUNK_SIZE = 800 # characters
OVERLAP = 200 # characters
def chunk_text(text, size=CHUNK_SIZE, overlap=OVERLAP):
chunks = []
i = 0
n = max(1, size - overlap)
while i < len(text):
chunks.append(text[i:i+size])
i += n
return chunks
def collect_files(kb_dir):
for p in kb_dir.rglob("*"):
if p.is_file() and p.suffix.lower() in (".txt", ".md", ".log", ".conf"):
yield p
def main():
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
texts, metas = [], []
for f in tqdm(list(collect_files(KB_DIR)), desc="Reading files"):
try:
t = f.read_text(errors="ignore")
except Exception:
continue
for i, ch in enumerate(chunk_text(t)):
if ch.strip():
texts.append(ch.strip())
metas.append({"path": str(f), "chunk_id": i})
if not texts:
print("No texts found. Set KB_DIR or add files.")
return
embs = model.encode(texts, batch_size=64, show_progress_bar=True, normalize_embeddings=True)
embs = np.asarray(embs, dtype="float32")
index = faiss.IndexFlatIP(embs.shape[1]) # cosine via inner product on normalized vectors
index.add(embs)
faiss.write_index(index, str(OUT_DIR / "kb.index"))
with open(OUT_DIR / "meta.jsonl", "w", encoding="utf-8") as w:
for m, t in zip(metas, texts):
m2 = m.copy()
m2["text"] = t
w.write(json.dumps(m2, ensure_ascii=False) + "\n")
print(f"Indexed {len(texts)} chunks from {KB_DIR} into {OUT_DIR}")
if __name__ == "__main__":
main()
Build the index:
export KB_DIR=~/kb_text
python3 build_index.py
Step 3: Query the index
Create query.py:
#!/usr/bin/env python3
import os, json, sys
from pathlib import Path
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
OUT_DIR = Path(os.environ.get("OUT_DIR", "index"))
TOP_K = int(os.environ.get("TOP_K", "5"))
def load_meta(path):
metas = []
with open(path, "r", encoding="utf-8") as r:
for line in r:
metas.append(json.loads(line))
return metas
def main():
if len(sys.argv) < 2:
print("Usage: query.py \"your question here\"", file=sys.stderr)
sys.exit(1)
q = sys.argv[1]
index = faiss.read_index(str(OUT_DIR / "kb.index"))
metas = load_meta(OUT_DIR / "meta.jsonl")
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
qemb = model.encode([q], normalize_embeddings=True).astype("float32")
D, I = index.search(qemb, TOP_K)
results = []
for rank, idx in enumerate(I[0]):
m = metas[idx]
results.append({
"rank": rank+1,
"path": m["path"],
"chunk_id": m["chunk_id"],
"score": float(D[0][rank]),
"text": m["text"]
})
print(json.dumps(results, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
Query it:
python3 query.py "How do we deploy the staging stack?"
Or for logs:
python3 query.py "Why did nginx return 502 yesterday?"
Step 4: Generate an answer (two options)
A) Local model with llama.cpp:
QUESTION="Summarize the staging deploy steps."
CTX_JSON=$(python3 query.py "$QUESTION")
# Build a prompt with sources
CONTEXT=$(echo "$CTX_JSON" | jq -r '.[] | "Source: \(.path)#\(.chunk_id)\n-----\n\(.text)\n"')
PROMPT="You are a helpful assistant. Use ONLY the context below to answer.
If the answer isn't in the context, say you don't know.
Context:
$CONTEXT
Question: $QUESTION
Answer:"
LLAMA_BIN=$(command -v llama-cli || echo "./llama.cpp/llama-cli")
if [ ! -x "$LLAMA_BIN" ]; then
LLAMA_BIN=$(command -v ./main || echo "./llama.cpp/main")
fi
"$LLAMA_BIN" -m models/phi3-mini-instruct.gguf -p "$PROMPT" -n 512 --temp 0.2
B) Remote API (example with OpenAI-compatible endpoint; requires API key):
export OPENAI_API_KEY=sk-...
QUESTION="Summarize the staging deploy steps."
CTX_JSON=$(python3 query.py "$QUESTION")
CONTEXT=$(echo "$CTX_JSON" | jq -r '.[] | "Source: \(.path)#\(.chunk_id)\n-----\n\(.text)\n"')
PROMPT="Use ONLY the context to answer. If not present, say you don't know.
Context:
$CONTEXT
Question: $QUESTION
Answer:"
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --arg p "$PROMPT" '{model:"gpt-4o-mini", messages:[{role:"user", content:$p}], temperature:0.2, max_tokens:512}')" \
| jq -r '.choices[0].message.content'
Replace the endpoint/model to match your provider. Keep secrets out of shell history.
Real-world examples you can try today
1) Onboarding docs search
Index your team’s README.md, runbooks, and wiki exports.
Ask: “What’s the process to rotate API keys?” or “How do I run the staging smoke tests?”
Benefit: junior teammates self-serve answers with sources.
2) Log-aided incident review
Index /var/log/nginx and app logs (rotate or sample to keep size manageable).
Ask: “What changed around 14:00 that caused 502s?” or “Summarize auth errors last hour.”
Bonus: pipe in grep pre-filters to shrink noise before indexing.
3) Config explainability
Index /etc/nginx, /etc/systemd, Helm charts, and Kubernetes manifests.
Ask: “Which nginx directives control client body size?” or “Where do we set memory limits in staging?”
Answers cite exact files/lines so you can verify quickly.
4) Codebase Q&A (lightweight)
Index docs/ and comments from src/ (you can limit to .md/.txt to stay small).
Ask: “What’s the contract for the payment retry job?” or “Where is the feature flag for checkout set?”
4 Practical tips for better RAG results
Chunking matters: Use overlaps (e.g., 800 chars with 200 overlap) so facts that cross boundaries aren’t lost.
Use smaller, faster models first: all-MiniLM-L6-v2 for embeddings is fast; for generation, small quantized GGUF models are fine for docs/logs.
Show sources: Always print file paths and chunk IDs. It builds trust and speeds verification.
Reindex incrementally: Re-run build_index.py via cron after doc syncs or deployments.
Troubleshooting
faiss-cpu install issues: On rare distros/architectures, conda may be easier. As a fallback: pip install faiss-cpu==1.7.4
RAM usage: If your corpus is huge, chunk fewer files at first or shard the index.
Slow generation: Reduce max tokens (-n) in llama.cpp, try a smaller GGUF, or switch to a remote API temporarily.
No relevant results: Increase TOP_K or improve your chunk size/overlap; ensure you indexed the right directory.
Conclusion and Next Steps (CTA)
RAG puts your Linux mindset into AI: retrieve with precision, then generate with context you trust. You now have:
A working local index of your docs/logs.
A Bash-friendly way to query and summarize with sources.
Options for fully local or remote generation.
Next steps:
Point KB_DIR at your real team docs and reindex.
Wrap the query + generation into a function in your shell profile.
Add a cron job to refresh the index nightly.
Explore advanced backends (e.g., Milvus, Chroma) and metadata filters (file type, date ranges).
Have a neat use-case or hit a snag? Share your script or question—and the sources your RAG cited—so others can learn and improve with you.