Vector databases
Skill furkangonel/cowrangler/bundled_skills/mlops/vector-databases
Autonomous terminal AI agent for workflows and feasible project procedures. Co-Worker Co-Wrangler π
npx -y skills add furkangonel/cowrangler --skill vector-databasesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Vector database operations β embed, store, search, and build RAG pipelines.
SKILL.md
12.7 KB, as published. Nobody here has run it
Vector Databases SOP
Embed documents, store vectors, run similarity search, and build RAG retrieval pipelines using ChromaDB, pgvector, and popular embedding models.
When to Use
- User wants to build semantic search over a document corpus
- User wants to implement a RAG (Retrieval-Augmented Generation) pipeline
- User wants to store and query embeddings efficiently
- User wants to find similar items by meaning, not keyword
- User wants to choose between embedding models and vector stores
Part 1 β Embedding Model Selection
| Model | Provider | Dims | Best For | Cost |
|---|---|---|---|---|
text-embedding-3-small | OpenAI | 1536 | General purpose, fast | ~$0.02/1M tokens |
text-embedding-3-large | OpenAI | 3072 | Higher accuracy | ~$0.13/1M tokens |
all-MiniLM-L6-v2 | sentence-transformers | 384 | Local, fast, English | Free |
all-mpnet-base-v2 | sentence-transformers | 768 | Local, balanced quality | Free |
BAAI/bge-m3 | HuggingFace | 1024 | Multilingual, local | Free |
nomic-embed-text | Ollama | 768 | Local, good quality | Free |
mxbai-embed-large | Ollama | 1024 | Local, high quality | Free |
OpenAI Embeddings
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def embed_openai(texts: list[str], model="text-embedding-3-small") -> list[list[float]]:
"""Embed a batch of texts. Max 2048 inputs per call."""
# Replace newlines β they degrade embedding quality
texts = [t.replace("\n", " ") for t in texts]
response = client.embeddings.create(input=texts, model=model)
return [item.embedding for item in response.data]
# Single text
vec = embed_openai(["Hello world"])[0]
print(f"Dimension: {len(vec)}")
Sentence-Transformers (local)
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
def embed_local(texts: list[str]) -> list[list[float]]:
embeddings = model.encode(texts, batch_size=64, show_progress_bar=True, normalize_embeddings=True)
return embeddings.tolist()
vecs = embed_local(["Semantic search is powerful", "Vector databases store embeddings"])
print(f"Shape: {len(vecs)} x {len(vecs[0])}")
Ollama Embeddings (local server)
import requests
def embed_ollama(texts: list[str], model="nomic-embed-text") -> list[list[float]]:
"""Requires: ollama serve && ollama pull nomic-embed-text"""
vecs = []
for text in texts:
r = requests.post("http://localhost:11434/api/embeddings",
json={"model": model, "prompt": text})
r.raise_for_status()
vecs.append(r.json()["embedding"])
return vecs
Part 2 β Chunking Strategies
Chunking quality directly impacts retrieval quality. Poor chunks = poor answers.
from typing import Generator
def chunk_fixed(text: str, chunk_size: int = 512, overlap: int = 64) -> list[str]:
"""Fixed-size character chunking with overlap."""
chunks = []
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
chunks.append(text[start:end])
start += chunk_size - overlap
return chunks
def chunk_by_paragraph(text: str, max_chars: int = 800) -> list[str]:
"""Split on double newlines, merge short paragraphs."""
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
chunks, current = [], ""
for para in paragraphs:
if len(current) + len(para) > max_chars and current:
chunks.append(current.strip())
current = para
else:
current = current + "\n\n" + para if current else para
if current:
chunks.append(current.strip())
return chunks
def chunk_by_sentence(text: str, max_sentences: int = 5) -> list[str]:
"""Simple sentence-based chunking."""
import re
sentences = re.split(r'(?<=[.!?])\s+', text)
chunks = []
for i in range(0, len(sentences), max_sentences):
chunks.append(" ".join(sentences[i:i + max_sentences]))
return chunks
Chunking guidance:
- Fixed-size with overlap: Safe default; use for mixed content.
- Paragraph-based: Best for structured documents (articles, reports, documentation).
- Sentence-based: Best for FAQ-style or Q&A retrieval.
- For code: chunk by function/class, not arbitrary size.
- Ideal chunk size: 256β512 tokens for retrieval; 512β1024 for dense context.
- Always include document metadata (source, title, page) in chunk metadata.
Part 3 β ChromaDB
ChromaDB is a lightweight, embedded vector store β no external server required for local use.
Install and Setup
pip install chromadb
Create a Collection and Add Documents
import chromadb
from chromadb.utils import embedding_functions
# Embedded (local file) β persists to disk
client = chromadb.PersistentClient(path="./chroma_db")
# Use OpenAI embeddings (or swap for sentence-transformers)
ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.environ["OPENAI_API_KEY"],
model_name="text-embedding-3-small",
)
collection = client.get_or_create_collection(
name="documents",
embedding_function=ef,
metadata={"hnsw:space": "cosine"}, # cosine similarity
)
# Add documents
docs = [
"ChromaDB is an open-source vector database.",
"Semantic search finds results by meaning, not keywords.",
"RAG combines retrieval with language model generation.",
]
metas = [
{"source": "intro.md", "section": "overview"},
{"source": "search.md", "section": "concepts"},
{"source": "rag.md", "section": "architecture"},
]
ids = [f"doc_{i}" for i in range(len(docs))]
collection.add(documents=docs, metadatas=metas, ids=ids)
print(f"Collection count: {collection.count()}")
Query
results = collection.query(
query_texts=["how does vector search work?"],
n_results=3,
include=["documents", "metadatas", "distances"],
)
for i, (doc, meta, dist) in enumerate(zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0],
)):
print(f"[{i+1}] distance={dist:.4f} source={meta['source']}")
print(f" {doc[:120]}")
Update and Delete
# Update a document
collection.update(ids=["doc_0"], documents=["Updated content for doc_0."])
# Delete
collection.delete(ids=["doc_2"])
# Filter by metadata during query
results = collection.query(
query_texts=["vector databases"],
n_results=2,
where={"source": "intro.md"}, # metadata filter
)
Part 4 β pgvector (PostgreSQL)
pgvector stores vectors as a PostgreSQL column type β ideal for production when you already use Postgres.
Setup
# Enable extension (run once)
psql -d mydb -c "CREATE EXTENSION IF NOT EXISTS vector;"
pip install pgvector psycopg2-binary sqlalchemy
Schema and Insert
import os
import psycopg2
from pgvector.psycopg2 import register_vector
conn = psycopg2.connect(os.environ["DATABASE_URL"])
register_vector(conn)
cur = conn.cursor()
# Create table
cur.execute("""
CREATE TABLE IF NOT EXISTS documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
source TEXT,
embedding vector(1536) -- match your embedding model dimension
);
""")
# Create index for fast ANN search (IVFFlat)
cur.execute("""
CREATE INDEX IF NOT EXISTS documents_embedding_idx
ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100); -- lists β sqrt(n_rows)
""")
conn.commit()
# Insert with embedding
def insert_document(content: str, source: str, embedding: list[float]):
cur.execute(
"INSERT INTO documents (content, source, embedding) VALUES (%s, %s, %s)",
(content, source, embedding)
)
conn.commit()
Similarity Search
def search_similar(query_embedding: list[float], limit: int = 5) -> list[dict]:
cur.execute("""
SELECT id, content, source,
1 - (embedding <=> %s::vector) AS similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT %s;
""", (query_embedding, query_embedding, limit))
rows = cur.fetchall()
return [{"id": r[0], "content": r[1], "source": r[2], "similarity": r[3]} for r in rows]
query_vec = embed_openai(["What is semantic search?"])[0]
results = search_similar(query_vec, limit=3)
for r in results:
print(f"similarity={r['similarity']:.4f} {r['content'][:100]}")
Part 5 β RAG Retrieval Pipeline
import os
from openai import OpenAI
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def rag_answer(question: str, collection, n_docs: int = 4, model: str = "gpt-4o-mini") -> str:
"""
1. Embed the question
2. Retrieve top-n chunks from vector store
3. Build prompt with retrieved context
4. Generate answer with LLM
"""
# Step 1: Retrieve
results = collection.query(
query_texts=[question],
n_results=n_docs,
include=["documents", "metadatas", "distances"],
)
chunks = results["documents"][0]
metas = results["metadatas"][0]
dists = results["distances"][0]
# Filter low-relevance chunks (cosine distance > 0.4 = less than 60% similarity)
relevant = [(c, m) for c, m, d in zip(chunks, metas, dists) if d < 0.4]
if not relevant:
return "I could not find relevant information to answer this question."
context_parts = []
for i, (chunk, meta) in enumerate(relevant):
src = meta.get("source", "unknown")
context_parts.append(f"[{i+1}] (source: {src})\n{chunk}")
context = "\n\n".join(context_parts)
# Step 2: Generate
messages = [
{
"role": "system",
"content": (
"You are a helpful assistant. Answer questions using ONLY the provided context. "
"If the answer is not in the context, say so. "
"Cite the source number (e.g. [1]) for each claim."
),
},
{
"role": "user",
"content": f"Context:\n{context}\n\nQuestion: {question}",
},
]
response = openai_client.chat.completions.create(
model=model,
messages=messages,
temperature=0.2,
)
return response.choices[0].message.content
# Usage
answer = rag_answer("What is ChromaDB used for?", collection)
print(answer)
Part 6 β Indexing Pipeline Template
#!/usr/bin/env python3
"""index_documents.py β read files β chunk β embed β store in ChromaDB"""
import os, glob, chromadb
from chromadb.utils import embedding_functions
SOURCE_DIR = "./docs"
CHROMA_DIR = "./chroma_db"
CHUNK_SIZE = 512
CHUNK_OVERLAP = 64
ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.environ["OPENAI_API_KEY"],
model_name="text-embedding-3-small",
)
client = chromadb.PersistentClient(path=CHROMA_DIR)
collection = client.get_or_create_collection("documents", embedding_function=ef,
metadata={"hnsw:space": "cosine"})
# Avoid re-indexing existing docs
existing_ids = set(collection.get()["ids"])
docs, metas, ids = [], [], []
for filepath in glob.glob(f"{SOURCE_DIR}/**/*.md", recursive=True):
with open(filepath, encoding="utf-8") as f:
text = f.read()
chunks = chunk_fixed(text, CHUNK_SIZE, CHUNK_OVERLAP)
for i, chunk in enumerate(chunks):
doc_id = f"{filepath}::{i}"
if doc_id in existing_ids:
continue
docs.append(chunk)
metas.append({"source": filepath, "chunk": i})
ids.append(doc_id)
# Batch insert (100 at a time to respect API limits)
BATCH = 100
for i in range(0, len(docs), BATCH):
collection.add(documents=docs[i:i+BATCH], metadatas=metas[i:i+BATCH], ids=ids[i:i+BATCH])
print(f"Indexed {min(i+BATCH, len(docs))} / {len(docs)}")
print(f"Done. Collection size: {collection.count()}")
Checklist
- Embedding model chosen based on accuracy/cost/local tradeoff
- Embedding dimension matches vector store collection configuration
- Chunking strategy matches document structure (paragraph/sentence/fixed)
- Chunk metadata includes source file path and chunk index for traceability
- Already-indexed documents skipped on re-run (idempotent indexing)
- Retrieval filters out low-similarity results before passing to LLM
- pgvector: IVFFlat or HNSW index created after initial data load (not before)
- RAG prompt instructs LLM to cite sources and acknowledge missing info