- Posted on
- • Artificial Intelligence
Artificial Intelligence Knowledge Bases
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Build an AI-Powered Knowledge Base on Linux (From Bash to Answers)
Ever lost 30 minutes grepping across wikis, READMEs, tickets, and logs just to answer, “How did we deploy staging last quarter?” Traditional search (and even good grep-fu) can’t always match the way humans ask. An AI knowledge base changes that: you ask in plain language, and it finds the relevant passages—fast.
In this guide, you’ll build a local, private AI-powered knowledge base on Linux using Bash, Python, and an open-source vector database. You’ll organize documents, embed them into vectors, index them, and query them from your terminal. No SaaS, no data leaving your machine.
You’ll get:
What an AI knowledge base is and why it matters for Linux users
A minimal, repeatable stack you can run locally or on a server
3–5 actionable steps with real commands and scripts
A working example you can adapt today
What is an AI Knowledge Base?
An AI knowledge base uses embeddings (numerical representations of text) and a vector database to find content semantically—by meaning, not just keywords. This is the foundation of Retrieval-Augmented Generation (RAG): retrieve the right context, then optionally pass it to an LLM to generate a final answer.
Even without an LLM, semantic search beats traditional grep when:
The question’s phrasing doesn’t match exact keywords in your docs
You want best matches across many doc types (markdown, logs, configs)
You value speed and privacy over SaaS convenience
Why This Matters on Linux
Private-by-default: Your code and docs stay on your box or server
Scriptable: Glue everything together with Bash, cron, systemd, and CLI tools
Portable: Works headless on laptops, workstations, or VPS
Cost-effective: Open source end to end
What We’ll Build
A document directory you control (markdowns, text files, logs)
A local vector database (Qdrant) running in Docker or Podman
A Python indexer to embed and upsert docs into Qdrant
A Python CLI to ask questions and get relevant passages
Optional: a filesystem watcher to re-index on changes
1) Install prerequisites
You need Python, a container engine (Docker or Podman), and some CLI helpers. Pick your package manager.
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y \
python3 python3-venv python3-pip \
git curl jq ripgrep inotify-tools \
docker.io docker-compose-plugin podman \
poppler-utils
# Optional: enable Docker for non-root use
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y \
python3 python3-virtualenv python3-pip \
git curl jq ripgrep inotify-tools \
moby-engine docker-compose podman \
poppler-utils
# Start and enable Docker (moby-engine)
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y \
python3 python3-pip python3-virtualenv \
git curl jq ripgrep inotify-tools \
docker docker-compose podman \
poppler-tools
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
Notes:
Docker and Podman are interchangeable for this guide; use whichever you prefer.
poppler-utils/tools provides pdftotext if you later want to index PDFs.
2) Start a local vector database (Qdrant)
Run Qdrant in a container. Use either Docker or Podman.
Using Docker:
mkdir -p ~/kb/qdrant_data
docker run -d --name qdrant \
-p 6333:6333 \
-v ~/kb/qdrant_data:/qdrant/storage \
qdrant/qdrant:latest
Using Podman:
mkdir -p ~/kb/qdrant_data
podman run -d --name qdrant \
-p 6333:6333 \
-v ~/kb/qdrant_data:/qdrant/storage:Z \
qdrant/qdrant:latest
Verify:
curl -s http://localhost:6333/ | jq
You should see a JSON response with the Qdrant version.
3) Prepare your documents
Pick a directory to store or mirror your knowledge sources.
export DOCS=~/kb/docs
mkdir -p "$DOCS"
Examples to populate:
Drop your Markdown docs, READMEs, meeting notes, and runbooks into $DOCS
Convert PDFs to text (optional):
pdftotext -layout design-doc.pdf "$DOCS/design-doc.txt"Collect logs or snippets:
cp /var/log/myapp/*.log "$DOCS"/
Quick sanity check:
rg -n "" "$DOCS" | head
4) Create a Python environment and install libraries
We’ll use sentence-transformers for embeddings and qdrant-client for indexing.
cd ~/kb
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install qdrant-client sentence-transformers tqdm
Note: The first time you run the model, it’ll download weights (~80–100 MB).
5) Index your documents into Qdrant
Save this as index.py in ~/kb:
#!/usr/bin/env python3
import os
import argparse
import pathlib
import uuid
from tqdm import tqdm
from typing import List
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
from sentence_transformers import SentenceTransformer
SUPPORTED_EXT = {".txt", ".md", ".rst", ".log", ".cfg", ".ini", ".conf", ".py", ".sh"}
def iter_files(root: str):
for p in pathlib.Path(root).rglob("*"):
if p.is_file() and p.suffix.lower() in SUPPORTED_EXT:
yield p
def read_text(path: pathlib.Path) -> str:
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
return f.read()
except Exception:
return ""
def chunk(text: str, size: int = 500, overlap: int = 50) -> List[str]:
chunks = []
start = 0
n = len(text)
while start < n:
end = min(n, start + size)
chunks.append(text[start:end])
start = end - overlap
if start < 0:
start = 0
if start >= n:
break
return [c.strip() for c in chunks if c.strip()]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--root", default=os.environ.get("DOCS", "./docs"), help="Docs root directory")
ap.add_argument("--host", default="localhost")
ap.add_argument("--port", type=int, default=6333)
ap.add_argument("--collection", default="kb")
args = ap.parse_args()
# Model: small, fast, 384-dim
model_name = "sentence-transformers/all-MiniLM-L6-v2"
model = SentenceTransformer(model_name)
client = QdrantClient(host=args.host, port=args.port)
vector_size = 384 # all-MiniLM-L6-v2 dimension
# Ensure collection exists
collections = [c.name for c in client.get_collections().collections]
if args.collection not in collections:
client.recreate_collection(
collection_name=args.collection,
vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE),
)
points = []
files = list(iter_files(args.root))
for path in tqdm(files, desc="Indexing files"):
text = read_text(path)
if not text.strip():
continue
# Simple heuristic: chunk by characters
pieces = chunk(text)
if not pieces:
continue
embs = model.encode(pieces, convert_to_numpy=True, show_progress_bar=False)
for piece, vec in zip(pieces, embs):
points.append(
PointStruct(
id=str(uuid.uuid4()),
vector=vec.tolist(),
payload={"path": str(path), "text": piece[:10000]},
)
)
# Upsert in batches to avoid huge payloads
if len(points) >= 512:
client.upsert(collection_name=args.collection, points=points)
points = []
if points:
client.upsert(collection_name=args.collection, points=points)
print(f"Indexed {len(files)} files into collection '{args.collection}'.")
if __name__ == "__main__":
main()
Run it:
source ~/kb/.venv/bin/activate
python ~/kb/index.py --root "$DOCS"
6) Query your knowledge base from the CLI
Create ask.py in ~/kb:
#!/usr/bin/env python3
import argparse
from qdrant_client import QdrantClient
from sentence_transformers import SentenceTransformer
def main():
ap = argparse.ArgumentParser()
ap.add_argument("question", nargs="+", help="Your question")
ap.add_argument("--host", default="localhost")
ap.add_argument("--port", type=int, default=6333)
ap.add_argument("--collection", default="kb")
ap.add_argument("--top-k", type=int, default=5)
args = ap.parse_args()
question = " ".join(args.question)
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
qvec = model.encode([question])[0].tolist()
client = QdrantClient(host=args.host, port=args.port)
results = client.search(
collection_name=args.collection,
query_vector=qvec,
limit=args["top_k"] if isinstance(args, dict) and "top_k" in args else args.top_k,
with_payload=True,
)
print(f"Top {args.top_k} matches for: {question}\n")
for i, r in enumerate(results, 1):
path = r.payload.get("path", "unknown")
text = r.payload.get("text", "")[:400].replace("\n", " ")
score = r.score
print(f"[{i}] {path} (score: {score:.4f})")
print(f" {text}")
print()
if __name__ == "__main__":
main()
Make it executable and ask something:
chmod +x ~/kb/ask.py
~/kb/ask.py "How do we rotate logs in production?"
Tip: Wrap it in a Bash function in your shell rc so you can just run askkb:
askkb() { ~/kb/ask.py "$@"; }
export -f askkb
7) Keep the index fresh (optional)
Automatically re-index when files change using inotify-tools:
while true; do
inotifywait -r -e close_write,move,create,delete "$DOCS" && \
source ~/kb/.venv/bin/activate && \
python ~/kb/index.py --root "$DOCS"
done
Run this in a tmux pane or a systemd user service.
Real-World Example
Ops runbooks + logs: Ask “How do we roll back a failed deploy?” and get the exact passage from your runbook, plus recent log snippets.
Onboarding: “Where is the SSO config documented?” returns the team wiki MD file and the relevant section.
Developer memory: “What’s the difference between v2 and v3 of the API?” surfaces the changelog and PR notes.
Troubleshooting
No results or poor matches:
- Ensure index ran without errors and files were detected
- Increase chunk size to 800–1000 if your docs are very terse
Model download too slow:
- Preload the model in an environment with internet, or configure a local model cache
Docker permission denied:
- Add your user to the docker group and re-login:
sudo usermod -aG docker "$USER"; newgrp docker - Or use Podman instead of Docker
- Add your user to the docker group and re-login:
Where to Go Next
Add an LLM for full answers: feed top 3–5 retrieved chunks into an LLM (local via llama.cpp or remote) to synthesize responses
Add more parsers: convert HTML, DOCX, and PDFs with dedicated tools before indexing
Reranking: use a smaller cross-encoder to reorder top hits for even better accuracy
Schedule indexing with cron or systemd timers
You now have a private, bash-friendly AI knowledge base, running locally, that understands meaning—not just keywords. Point it at your real docs and start asking better questions today.