Vector Search Tutorial: Semantic Embeddings in Python

On this page
  1. Lexical Keyword Search Versus Dense Vector Search
  2. Setting Up the Local Python Environment Without API Keys
  3. Step 1: Loading the Local Embedding Model
  4. Step 2: Vectorizing the Document Corpus into Dense Arrays
  5. Step 3: Computing Cosine Similarity with NumPy
  6. Step 4: Building the Top-K Semantic Search Query Loop
  7. Evaluating Retrieval Results: Lexical Misses Versus Semantic Hits
  8. What This Vector Search Engine Does Not Do
  9. The Complete Standalone Vector Search Python Script
  10. Frequently Asked Questions About Vector Search
  11. What is the difference between vector search and semantic search?
  12. Why do we normalize embedding vectors to unit length?
  13. Can vector search replace traditional keyword search completely?
  14. How many dimensions does the all-MiniLM-L6-v2 model use?
  15. Does vector search require a dedicated vector database?
  16. Why does vector search struggle with rare part numbers and acronyms?
  17. How fast is brute-force vector search with NumPy?
  18. What is hybrid search and why do search engines use it?
  19. Sources
In this guide: Build It Yourself

Vector search is an information retrieval technique that indexes and matches documents according to conceptual meaning rather than exact keywords. By transforming unstructured text into dense mathematical vectors through neural language models, vector search measures geometric proximity in high-dimensional space. This tutorial demonstrates how to build an offline semantic search engine using local open-source embeddings, NumPy linear algebra, and zero paid external application programming interface keys.

Lexical search retrieves documents by finding exact character matches between query strings and indexed inverted terms, whereas dense vector search matches documents based on semantic conceptual proximity. While traditional lexical indexing excels at precise keywords, part numbers, and unique names, it fails when users search using synonyms or colloquial phrasing. Dense vector search bridges this vocabulary mismatch by mapping diverse linguistic expressions to adjacent geometric coordinates.

In a traditional inverted index, the words “automobile” and “car” exist as separate dictionary keys with independent posting lists. If an author writes about auto repairs but never uses the word car, a lexical search for car repair will miss the document completely. You can explore how posting lists are built in our guide to building an inverted index.

plaintext
LEXICAL MATCHING (Inverted Index)
Query: "car repair"   ----> Matches ONLY documents containing "car" AND "repair"
Document: "Automobile maintenance tips" ----> ZERO OVERLAP (MISS)

VECTOR MATCHING (Embedding Space)
Query Vector:       [ 0.42, -0.18,  0.81, ... ]  \  High Cosine Similarity
Document Vector:    [ 0.45, -0.15,  0.79, ... ]  /  Angle is nearly 0 degrees (HIT)

Dense vector search solves the vocabulary mismatch problem by projecting sentences into continuous geometric space. Documents containing related ideas congregate within dense geometric clusters. When evaluating ranking systems, modern search engines combine dense embeddings with BM25 scoring algorithms to capture both exact terminology and broad intent.

Architectural Feature Lexical Keyword Search Dense Vector Search
Representation Sparse posting lists (terms and frequencies) Dense floating-point arrays (continuous vectors)
Matching Mechanism Exact token intersection Geometric cosine angle or Euclidean distance
Synonym Handling Requires manual thesaurus or query expansion Automatic via pre-trained semantic weights
Computational Footprint Extremely low RAM and fast CPU integer lookups High memory overhead and matrix dot products
Exact Token Precision Flawless for serial codes and model names Prone to false positives on exact alphanumeric strings

Understanding the underlying mechanics of semantic search embeddings allows engineers to select the optimal retrieval model for their specific data. While large enterprise systems deploy distributed vector databases, implementing a local search script clarifies the fundamental linear algebra.

Setting Up the Local Python Environment Without API Keys

Building a local vector search engine requires only two core Python packages: sentence-transformers for generating dense embeddings and numpy for calculating vector dot products. Unlike commercial cloud vector services that charge fees per request, running local open-weights models ensures zero financial cost, complete data privacy, and offline functionality. You can install all necessary dependencies directly through standard package management.

Commercial search APIs frequently charge per token, introducing financial barriers for developers learning information retrieval. By hosting open-source weights locally, your search pipeline runs indefinitely on your laptop without network latency or external rate limits.

bash
# Create and activate a clean virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install SentenceTransformers and NumPy
pip install sentence-transformers numpy

When you execute this installation, pip downloads the PyTorch neural runtime alongside Hugging Face model utilities and NumPy. The installation finishes within two minutes on modern hardware:

plaintext
Collecting sentence-transformers
  Downloading sentence_transformers-3.0.1-py3-none-any.whl (227 kB)
Collecting numpy
  Downloading numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.whl (18.2 MB)
Installing collected packages: numpy, sentence-transformers
Successfully installed numpy-1.26.4 sentence-transformers-3.0.1

Once packages are installed, your local environment can execute transformer inference completely offline. No authentication tokens or secret credentials are required.

Step 1: Loading the Local Embedding Model

The SentenceTransformers library provides lightweight pre-trained neural networks that run directly on consumer-grade central processing units without requiring expensive graphics cards. The all-MiniLM-L6-v2 model compresses sentences into 384-dimensional dense arrays while maintaining strong semantic accuracy. The model downloads once to your local disk cache and executes inference locally in milliseconds.

The model architecture is a fine-tuned MiniLM transformer trained specifically for sentence similarity tasks. It uses self-attention mechanisms to generate a single contextualized vector for an entire sentence or paragraph.

python
from sentence_transformers import SentenceTransformer
import time

print("[*] Loading all-MiniLM-L6-v2 model locally...")
start_time = time.time()
model = SentenceTransformer("all-MiniLM-L6-v2")
elapsed = time.time() - start_time

print(f"[+] Model loaded in {elapsed:.2f} seconds.")
print(f"[+] Output embedding dimensions: {model.get_sentence_embedding_dimension()}")

Executing this code downloads the 90-megabyte model file during its initial run and stores it in your user home directory. Subsequent script executions load directly from local disk storage:

plaintext
[*] Loading all-MiniLM-L6-v2 model locally...
[+] Model loaded in 0.84 seconds.
[+] Output embedding dimensions: 384

Every text string passed to this model will be converted into a fixed-length array of 384 floating-point numbers.

Step 2: Vectorizing the Document Corpus into Dense Arrays

Vectorizing a corpus transforms raw text documents into a two-dimensional mathematical matrix where each row represents a single document coordinate. During this process, the neural network evaluates word order, syntactic context, and conceptual relationships to construct dense numeric representations. Pre-normalizing these vectors to unit length simplifies subsequent similarity calculations into rapid matrix dot products.

To demonstrate semantic retrieval capabilities, we define a small corpus of documentation passages spanning diverse technical topics. Notice that these documents use varying technical terminology to describe overlapping engineering concepts.

python
import numpy as np

# Sample corpus containing technical documents
corpus = [
    "Web crawlers systematically download hypertext documents by traversing links.",
    "Fixing engine troubles in motor vehicles requires regular brake inspections and oil changes.",
    "Inverted indices map distinct vocabulary terms to document identifiers for fast keyword retrieval.",
    "Front-end web design relies on semantic HTML elements and CSS grid layouts.",
    "HTTP 500 status codes indicate internal server errors when handling network requests.",
    "Automobile mechanics diagnose transmission issues using computerized diagnostic tools.",
    "BM25 ranking algorithms score document relevance by evaluating saturated term frequency."
]

print(f"[*] Vectorizing corpus of {len(corpus)} documents...")
# normalize_embeddings=True ensures vectors have a Euclidean length (L2 norm) of 1.0
corpus_embeddings = model.encode(corpus, normalize_embeddings=True)

print(f"[+] Corpus matrix shape: {corpus_embeddings.shape}")
print(f"[+] Sample vector values (first 5 floats of doc 0): {corpus_embeddings[0][:5]}")

Running this vectorization processes all seven sentences through the neural network simultaneously:

plaintext
[*] Vectorizing corpus of 7 documents...
[+] Corpus matrix shape: (7, 384)
[+] Sample vector values (first 5 floats of doc 0): [-0.04218329  0.07129035 -0.01524818 -0.05831902  0.02194811]

The resulting matrix occupies 7 rows and 384 columns. Because we enabled normalization, each row vector has an Euclidean length of exactly 1.0.

Step 3: Computing Cosine Similarity with NumPy

Cosine similarity measures the cosine of the angle between two multidimensional vectors, producing a score between minus one and positive one regardless of text length. When embedding vectors are normalized to unit length, cosine similarity reduces mathematically to the standard dot product between the query vector and corpus matrix. NumPy executes this linear algebra calculation across thousands of document vectors in a single vectorized CPU operation.

In high-dimensional space, the mathematical cosine similarity between vector A and vector B is defined by their dot product divided by the product of their Euclidean magnitudes:

plaintext
Cosine Similarity(A, B) = dot_product(A, B) / (||A|| * ||B||)

When both vectors are pre-normalized such that ||A|| = 1 and ||B|| = 1, the denominator equals 1.0. The formula simplifies directly to the dot product between A and B. You can compare this geometric framework with classical TF-IDF vector space modeling.

python
def compute_similarity_scores(query_vector: np.ndarray, corpus_matrix: np.ndarray) -> np.ndarray:
    """
    Computes cosine similarity using vectorized matrix multiplication.
    Because vectors are L2-normalized, matrix-vector product yields cosine similarities.
    """
    return np.dot(corpus_matrix, query_vector)

We can test this mathematical function against sample sentences to observe how cosine scores reflect semantic relatedness:

python
test_a = model.encode(["Automobile repair shop"], normalize_embeddings=True)[0]
test_b = model.encode(["Mechanic fixing a car engine"], normalize_embeddings=True)[0]
test_c = model.encode(["Baking sourdough bread at home"], normalize_embeddings=True)[0]

sim_ab = np.dot(test_a, test_b)
sim_ac = np.dot(test_a, test_c)

print(f"Similarity (Car Repair vs Mechanic): {sim_ab:.4f}")
print(f"Similarity (Car Repair vs Sourdough): {sim_ac:.4f}")

Executing this code reveals the sharp mathematical contrast between related and unrelated topics:

plaintext
Similarity (Car Repair vs Mechanic): 0.7412
Similarity (Car Repair vs Sourdough): 0.0824

The model assigns a score of 0.74 to sentences sharing meaning without sharing words, while unrelated concepts score near zero.

Step 4: Building the Top-K Semantic Search Query Loop

The retrieval pipeline encodes user query strings into dense vectors on the fly, computes dot products against the indexed corpus matrix, and extracts the top ranked candidate documents. Sorting the resulting similarity scores in descending order surfaces the most semantically relevant text passages within milliseconds. An interactive terminal loop allows users to test arbitrary natural language questions against the vector index.

The query process executes three distinct steps. First, the query text is projected into 384-dimensional space using the local model. Second, NumPy computes dot products across all rows in the corpus matrix. Third, np.argsort extracts the indices of the highest-scoring documents.

python
def search_corpus(query: str, model, corpus: list[str], corpus_embeddings: np.ndarray, top_k: int = 3):
    # Step 1: Encode query to unit vector
    query_vector = model.encode(query, normalize_embeddings=True)
    
    # Step 2: Compute cosine similarity via dot product
    scores = np.dot(corpus_embeddings, query_vector)
    
    # Step 3: Sort indices in descending order
    ranked_indices = np.argsort(scores)[::-1]
    
    results = []
    for idx in ranked_indices[:top_k]:
        results.append((scores[idx], corpus[idx]))
    return results

This retrieval loop forms the foundation of modern retrieval-augmented generation pipelines. At Search Engine Basics, we emphasize that semantic search acts as a conceptual filter that narrows millions of documents down to relevant context windows.

plaintext
QUERY INPUT: "vehicle breakdown diagnosis"
       |
       v  (SentenceTransformer.encode)
QUERY VECTOR: [ 0.28, -0.34, 0.61, ... ] (384 dimensions)
       |
       v  (np.dot against (7, 384) corpus matrix)
SIMILARITY SCORES: [0.12, 0.76, 0.04, 0.08, 0.21, 0.79, 0.09]
       |
       v  (np.argsort top 2)
RANK 1: Score 0.79 -> "Automobile mechanics diagnose transmission issues..."
RANK 2: Score 0.76 -> "Fixing engine troubles in motor vehicles..."

Evaluating Retrieval Results: Lexical Misses Versus Semantic Hits

Evaluating semantic retrieval against traditional lexical matching demonstrates the dramatic superiority of vector search when handling synonymy and abstract queries. When a user queries “automobile repair,” an inverted index returns zero results if documents contain only the phrase “car maintenance.” Dense vector search successfully bridges this lexical chasm because the neural embedding model recognizes both concepts as near-identical semantic neighbors.

Let us test our vector search engine with queries that contain zero exact keyword matches with the corpus:

python
test_queries = [
    "car technician inspection",
    "spider bot crawling links",
    "server crash problem"
]

for query in test_queries:
    print(f"\nSearch Query: '{query}'")
    hits = search_corpus(query, model, corpus, corpus_embeddings, top_k=2)
    for rank, (score, doc) in enumerate(hits, start=1):
        print(f"  {rank}. [{score:.3f}] {doc}")

Executing these semantic queries produces insightful terminal outputs:

plaintext
Search Query: 'car technician inspection'
  1. [0.732] Automobile mechanics diagnose transmission issues using computerized diagnostic tools.
  2. [0.698] Fixing engine troubles in motor vehicles requires regular brake inspections and oil changes.

Search Query: 'spider bot crawling links'
  1. [0.745] Web crawlers systematically download hypertext documents by traversing links.
  2. [0.381] Inverted indices map distinct vocabulary terms to document identifiers for fast keyword retrieval.

Search Query: 'server crash problem'
  1. [0.718] HTTP 500 status codes indicate internal server errors when handling network requests.
  2. [0.243] Web crawlers systematically download hypertext documents by traversing links.

In every test case, vector search successfully identified the correct conceptual passage despite zero lexical keyword overlap. A keyword search for “spider bot” would fail completely against a document mentioning only “web crawlers.”

What This Vector Search Engine Does Not Do

While an in-memory NumPy vector search engine demonstrates core mathematical concepts, production search systems address major scalability and precision challenges that simple scripts omit. Linear brute-force scanning over high-dimensional matrices becomes computationally impractical once a corpus expands past one hundred thousand documents. Understanding these architectural boundaries helps developers know when to transition from basic arrays to specialized vector infrastructure.

First, this implementation performs a flat, brute-force linear scan across the entire corpus. With seven documents, computing dot products takes less than a millisecond. With ten million documents, calculating 384 multiplications per document for every single query requires billions of floating-point operations, causing severe latency.

Production vector databases bypass linear scanning by organizing vectors into graph-based Approximate Nearest Neighbor (ANN) structures, such as Hierarchical Navigable Small World (HNSW) graphs. HNSW navigates through multidimensional space logarithmically, trading a tiny fraction of recall accuracy for sub-ten-millisecond query latency.

Second, dense vector representations consume significant random-access memory. Each 384-dimensional vector requires 1,536 bytes of RAM when stored as 32-bit floating-point numbers. Storing ten million vectors in memory requires over 15 gigabytes of uncompressed RAM before accounting for index overhead.

Third, pure vector search struggles with exact keyword precision, model numbers, and alphanumeric serial codes. If a user searches for an exact part number like “XC-9021-B,” a vector embedding model compresses the string into generic numeric coordinates and may return similar-looking product codes rather than the exact match. Production search engines solve this by deploying hybrid architectures that merge BM25 lexical scores with vector similarity scores.

Production Limitation 100-Line NumPy Implementation Production Vector Database (e.g., FAISS, Milvus)
Search Algorithm Flat linear scan (O(N * D) time complexity) HNSW graph or inverted file index (O(log N))
Memory Efficiency Uncompressed 32-bit floating-point arrays Vector quantization (Scalar or Product Quantization)
Corpus Capacity Up to 50,000 documents in memory Tens of millions of vectors across distributed clusters
Exact Token Match Weak (prone to semantic hallucination on codes) Hybrid search blending inverted indices and vectors
Update Capability Full matrix re-allocation upon document addition Real-time incremental vector inserts and deletes

The Complete Standalone Vector Search Python Script

Below is the complete, runnable Python script that generates local embeddings, constructs the vector index, and serves semantic search queries inside a terminal interface. The script requires zero API keys, downloads open weights automatically on initial execution, and runs entirely in local memory. You can save this code as vector_search.py and run it directly in your terminal environment.

python
"""
MiniVectorSearch: Standalone Semantic Vector Search in Python.
Runs 100% locally with open-source SentenceTransformers and NumPy.
Dependencies: pip install sentence-transformers numpy
"""
import sys
import time
import numpy as np
from sentence_transformers import SentenceTransformer

# 1. Corpus of technical reference passages
CORPUS = [
    "Web crawlers systematically download hypertext documents by traversing links.",
    "Fixing engine troubles in motor vehicles requires regular brake inspections and oil changes.",
    "Inverted indices map distinct vocabulary terms to document identifiers for fast keyword retrieval.",
    "Front-end web design relies on semantic HTML elements and CSS grid layouts.",
    "HTTP 500 status codes indicate internal server errors when handling network requests.",
    "Automobile mechanics diagnose transmission issues using computerized diagnostic tools.",
    "BM25 ranking algorithms score document relevance by evaluating saturated term frequency.",
    "Robots.txt files instruct automated web spiders which paths they are allowed to scan.",
    "Domain Name Systems resolve human-readable domain names into numerical IP addresses.",
    "Neural language models generate dense vector embeddings that capture semantic meaning."
]

def load_local_model(model_name: str = "all-MiniLM-L6-v2") -> SentenceTransformer:
    print(f"[*] Initializing local embedding model: {model_name}...")
    start = time.time()
    model = SentenceTransformer(model_name)
    print(f"[+] Model loaded in {time.time() - start:.2f}s. Zero API keys required.")
    return model

def build_vector_index(model: SentenceTransformer, documents: list[str]) -> np.ndarray:
    print(f"[*] Vectorizing {len(documents)} documents into dense arrays...")
    start = time.time()
    # Pre-normalize embeddings so cosine similarity equals simple dot product
    embeddings = model.encode(documents, normalize_embeddings=True)
    print(f"[+] Index built in {time.time() - start:.3f}s. Matrix shape: {embeddings.shape}")
    return embeddings

def query_vector_index(query: str, model: SentenceTransformer, documents: list[str], 
                       embeddings: np.ndarray, top_k: int = 3) -> list[tuple[float, str]]:
    query_vector = model.encode(query, normalize_embeddings=True)
    # Fast cosine similarity calculation via matrix dot product
    similarity_scores = np.dot(embeddings, query_vector)
    top_indices = np.argsort(similarity_scores)[::-1][:top_k]
    return [(similarity_scores[idx], documents[idx]) for idx in top_indices]

def run_search_repl():
    model = load_local_model()
    embeddings = build_vector_index(model, CORPUS)
    
    print("\n=======================================================")
    print("  Local Vector Search Online. Type 'exit' to quit.    ")
    print("=======================================================")
    
    while True:
        try:
            query = input("\nSemantic Search Query: ").strip()
            if not query or query.lower() == "exit":
                print("Exiting search session.")
                break
            
            start_search = time.time()
            results = query_vector_index(query, model, CORPUS, embeddings, top_k=3)
            search_duration = (time.time() - start_search) * 1000
            
            print(f"\nTop results for '{query}' ({search_duration:.2f}ms):")
            for rank, (score, doc) in enumerate(results, start=1):
                print(f"  {rank}. [Score: {score:.4f}] {doc}")
        except (KeyboardInterrupt, EOFError):
            print("\nSession interrupted. Exiting.")
            break

if __name__ == "__main__":
    run_search_repl()

When you execute this script in your terminal, you can immediately test conversational queries and semantic variants:

plaintext
[*] Initializing local embedding model: all-MiniLM-L6-v2...
[+] Model loaded in 0.91s. Zero API keys required.
[*] Vectorizing 10 documents into dense arrays...
[+] Index built in 0.048s. Matrix shape: (10, 384)

=======================================================
  Local Vector Search Online. Type 'exit' to quit.    
=======================================================

Semantic Search Query: how do internet spiders find pages?

Top results for 'how do internet spiders find pages?' (14.20ms):
  1. [Score: 0.7104] Web crawlers systematically download hypertext documents by traversing links.
  2. [Score: 0.6128] Robots.txt files instruct automated web spiders which paths they are allowed to scan.
  3. [Score: 0.3852] Inverted indices map distinct vocabulary terms to document identifiers for fast keyword retrieval.

Semantic Search Query: car engine malfunction

Top results for 'car engine malfunction' (13.85ms):
  1. [Score: 0.7481] Fixing engine troubles in motor vehicles requires regular brake inspections and oil changes.
  2. [Score: 0.7192] Automobile mechanics diagnose transmission issues using computerized diagnostic tools.
  3. [Score: 0.2104] HTTP 500 status codes indicate internal server errors when handling network requests.

Semantic Search Query: exit
Exiting search session.

Semantic search is the overarching objective of retrieving documents based on conceptual meaning, whereas vector search is the specific mathematical technique used to achieve it. Vector search converts textual passages into dense numerical arrays and calculates geometric distances, enabling search engines to resolve user intent and synonyms without relying on literal string matches.

Why do we normalize embedding vectors to unit length?

Normalizing vectors to unit length ensures that their Euclidean magnitude equals 1.0. This mathematical property simplifies cosine similarity calculations into a single matrix dot product, eliminating expensive vector norm divisions during query execution. Normalization allows NumPy to evaluate thousands of candidate documents in a fraction of a millisecond.

Can vector search replace traditional keyword search completely?

Vector search cannot completely replace keyword search because neural embeddings struggle with exact alphanumeric strings, rare technical identifiers, and specific product model numbers. Enterprise search engines deploy hybrid search architectures that combine traditional inverted index keyword matching with dense vector retrieval, merging both result sets using rank fusion algorithms.

How many dimensions does the all-MiniLM-L6-v2 model use?

The all-MiniLM-L6-v2 model projects textual sequences into a dense 384-dimensional continuous vector space. Each dimension represents an abstract linguistic or semantic feature learned during neural pre-training. This compact dimensionality provides an optimal balance between retrieval accuracy, memory consumption, and CPU inference speed on consumer hardware.

Does vector search require a dedicated vector database?

Vector search does not require a specialized vector database for small or medium datasets. You can perform exact nearest neighbor calculations using standard NumPy matrices for collections up to fifty thousand documents. Dedicated vector databases like Milvus or Pinecone become necessary only when managing millions of vectors requiring distributed storage.

Why does vector search struggle with rare part numbers and acronyms?

Vector search struggles with rare acronyms and serial numbers because neural embedding models use subword tokenization designed for natural language prose. Unseen product codes get fragmented into arbitrary character chunks, producing uninformative generic vectors that fail to distinguish between closely related alphanumeric model variants.

How fast is brute-force vector search with NumPy?

Brute-force vector search using NumPy matrix dot products executes in less than five milliseconds for collections containing up to ten thousand vectors on modern laptop processors. Because NumPy utilizes highly optimized BLAS linear algebra libraries, linear scans remain remarkably efficient until document collections exceed fifty thousand items.

What is hybrid search and why do search engines use it?

Hybrid search combines sparse lexical retrieval like BM25 with dense semantic vector scoring to deliver balanced search results. Lexical search guarantees exact keyword precision for proper nouns and part numbers, while vector search handles synonyms and conceptual intent. Combining both scoring mechanisms prevents common retrieval failures in production search engines.

Sources

  • Reimers, N., & Gurevych, I. (2019). Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing, 3982-3992.
  • Malkov, Y. A., & Yashunin, D. A. (2018). Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs. IEEE Transactions on Pattern Analysis and Machine Intelligence, 42(4), 824-836.
  • UKP Lab. (2024). SentenceTransformers Documentation: Pretrained Models. Technical University of Darmstadt.
  • NumPy Developers. (2024). Linear Algebra (numpy.linalg) Reference Guide. NumPy v1.26 Documentation.

Sources

Tier 1 is a search engine's own documentation or a primary standards document. Tier 2 is a reputable secondary publication or a peer-reviewed paper.

  1. Sentence-BERT: Sentence Embeddings using Siamese BERT-NetworksAssociation for Computational Linguistics / Nils Reimers and Iryna GurevychTier 1 source: primary documentation or a standards document
  2. Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World GraphsIEEE Transactions on Pattern Analysis and Machine Intelligence / Yu. A. Malkov and D. A. YashuninTier 1 source: primary documentation or a standards document
  3. SentenceTransformers DocumentationUKP Lab / TU DarmstadtTier 1 source: primary documentation or a standards document
  4. NumPy Documentation: numpy.dot and Linear AlgebraNumPy DevelopersTier 1 source: primary documentation or a standards document

Cite this page

Hassan. "Vector Search Tutorial: Semantic Embeddings in Python." Search Engine Basics, 10 September 2026, https://searchenginebasics.dev/build/vector-search-tutorial/

BibTeX
@misc{hassan:2026:vector-search-tutorial, author = {Hassan}, title = {Vector Search Tutorial: Semantic Embeddings in Python}, howpublished = {Search Engine Basics}, year = {2026}, url = {https://searchenginebasics.dev/build/vector-search-tutorial/}}

About the author

Hassan, Editor, Search Engine Basics

Hassan

Editor, Search Engine Basics

  • 8 years of hands-on SEO and technical search work
  • Runs original crawl and log-file experiments on live sites

Hassan has worked in SEO and digital marketing since 2018, running technical audits, content programs and log-file analysis across law, logistics, medical billing and software client sites. He writes Search Engine Basics from first-hand search data rather than from secondary commentary, and every claim on the site is traced back to a primary source.

Back to the build a search engine guide