← Python Code AI Agents & LLM Apps
Browse Python Concepts

Embeddings and Similarity Search — How Vector Search Works

Mental Model

Imagine a vector's magnitude as its "length" or "intensity." Cosine similarity measures the angle between two vectors, and if one vector has zero length, it points nowhere. Attempting to calculate the angle to a point that doesn't exist (a vector with zero magnitude) creates an undefined mathematical operation, leading to division by zero.

Rule: When writing vector math routines, always leverage NumPy or PyTorch to vectorize operations and explicitly guard against zero-magnitude vectors.

The Setup

You are building a fallback local similarity search for your retrieval agent. You write a standard cosine similarity helper function to rank matching text chunks by comparing query embeddings against your stored document embedding vectors.

What Does This Print?

Broken code
Python
import math

def cosine_similarity(v1: list[float], v2: list[float]) -> float:
    # Manual calculation of dot product divided by magnitudes
    dot_product = sum(x * y for x, y in zip(v1, v2))
    mag1 = math.sqrt(sum(x * x for x in v1))
    mag2 = math.sqrt(sum(x * x for x in v2))
    return dot_product / (mag1 * mag2)

# Simulating embeddings. Document 2 is generated from an empty/malformed text chunk
query = [0.15, 0.22, 0.81]
doc1 = [0.12, 0.20, 0.85]
doc2 = [0.0, 0.0, 0.0] 

print("Doc 1 Similarity:", cosine_similarity(query, doc1))
print("Doc 2 Similarity:", cosine_similarity(query, doc2))
Predict what happens when the code attempts to compute the similarity of the second document.

The Output

What actually happens
Doc 1 Similarity: 0.9991206190989345 Traceback (most recent call last): ... ZeroDivisionError: float division by zero

The script crashes with a ZeroDivisionError. When an empty chunk or system noise results in an all-zero vector, its magnitude (mag2) computes to 0.0. Dividing the dot product by this zero value causes Python to immediately raise an exception, breaking your indexing or retrieval pipeline.

Why Python Does This

Under the hood, Python's floating-point numbers are represented as IEEE 754 double-precision floats. When performing vector operations inside standard loops, Python must resolve dynamic types, allocate memory for temporary floats, and process exception handling checks on every operation. If a vector has a magnitude of 0.0, the mathematical definition of cosine similarity collapses because the direction of a zero-length vector is undefined. Unlike C libraries or NumPy, which handle these boundary limits by returning NaN or 0.0 depending on configuration, pure Python raises ZeroDivisionError. Additionally, calculating vector mathematical operations using list comprehensions and standard loops is incredibly slow due to the Python interpreter's opcode evaluation overhead. For any performance-critical vector manipulation, vectorization via NumPy or PyTorch should be used, which executes compiled C/C++ loops bypassing Python's VM overhead.

The Fix

Corrected pattern
Python
import numpy as np

def cosine_similarity_safe(v1: list[float], v2: list[float]) -> float:
    # Convert lists to NumPy arrays for vectorized C execution
    arr1, arr2 = np.array(v1), np.array(v2)
    norm1 = np.linalg.norm(arr1)
    norm2 = np.linalg.norm(arr2)
    
    # Prevent division by zero safely without raising exceptions
    if norm1 == 0.0 or norm2 == 0.0:
        return 0.0
        
    return float(np.dot(arr1, arr2) / (norm1 * norm2))

Explicitly checking for zero magnitudes (e.g., if mag1 == 0 or mag2 == 0: return 0.0) before the division prevents the ZeroDivisionError. This handles the edge case of all-zero vectors, ensuring the function returns a sensible default (e.g., 0.0, indicating no similarity) rather than crashing. Using libraries like NumPy can also implicitly handle these cases more gracefully.

How This Fails in Real Systems

A financial analysis bot ingested user uploaded PDFs. During parsing, some blank pages produced all-zero embedding vectors. The search engine crashed with ZeroDivisionError when a user searched across these files, locking up the background Celery task queue and preventing 500 active users from accessing their processed insights.

Key Takeaway

When writing vector math routines, always leverage NumPy or PyTorch to vectorize operations and explicitly guard against zero-magnitude vectors.
Common mistake: Developers implement vector math algorithms without robust error handling for edge cases like zero vectors, assuming all inputs will be well-formed and non-degenerate.