Bradley Kirton's Blog

Published on May 11, 2026

Go home

Semantic search in Sqlite

If you need a simple low infrastructure solution to semantic search in Django here is one possible solution.

This requires that embeddings are stored as numpy arrays in sqlite. You can model this using the BinaryField (Note you will also need to generate embeddings, that is out of scope of this post).

class Model(models.Model):
    embedding = models.BinaryField(null=True, blank=True)

We then register the vector_distance_cos and vector_score_cos functions as custom functions.

import typing as t
import numpy as np

from django.apps import AppConfig
from django.db.backends.signals import connection_created


def vector_distance_cos(a: bytes, b: bytes) -> float:
    """Returns the cosine similarity between 2 vectors.

    The cosine similarity always belongs to the interval [−1, +1].

    - Two proportional vectors have a cosine similarity of +1
    - Two orthogonal vectors have a similarity of 0
    - Two opposite vectors have a similarity of −1
    """
    a_array = np.frombuffer(a, dtype=np.float64)
    b_array = np.frombuffer(b, dtype=np.float64)
    return float(a_array @ b_array / (np.linalg.norm(a_array) * np.linalg.norm(b_array)))


def vector_score_cos(a: bytes, b: bytes) -> float:
    """Returns a normalized score for cosine similarity between 0 and 1."""
    return (vector_distance_cos(a, b) + 1) / 2


def on_connection_created(sender: t.Any, connection: t.Any, **kwargs: t.Any) -> None:
    """Runs each time Django creates a DB connection."""
    if connection.vendor != "sqlite":
        return

    connection.connection.create_function("vector_distance_cos", 2, vector_distance_cos)
    connection.connection.create_function("vector_score_cos", 2, vector_score_cos)


class DbConfig(AppConfig):
    """Config for the database application."""

    name = "db"
    verbose_name = "Database"

    def ready(self) -> None:
        connection_created.connect(on_connection_created)

The vector_distance_cos and vector_score_cos functions will now be available to us in SQL.

SELECT
  vector_score_cos(embedding, :embedding) score
FROM table
ORDER BY
  score DESC
LIMIT 10