Building AI-Powered Semantic Search with Django and pgvector: 2026 Guide

Learn how to build production-ready semantic search with Django, pgvector, and OpenAI embeddings. Step-by-step 2026 guide with code examples for vector search in Django applications.

Published: June 01, 2026

Category: AI

Why Semantic Search Matters in 2026 Keyword-based search has been the backbone of web applications for decades. But as users demand more intuitive, conversational experiences, traditional full-text search falls short. Enter semantic search — a technique that understands the meaning behind queries rather than just matching keywords. Powered by vector embeddings and PostgreSQL's pgvector extension, semantic search enables your Django applications to deliver results that truly understand user intent. Whether you're building a documentation site, an e-commerce product catalog, or an internal knowledge base, semantic search dramatically improves the user experience. What You'll Build In this guide, we'll build a production-ready semantic search system using: Django — Your trusted Python web framework pgvector — PostgreSQL's vector similarity search extension OpenAI Embeddings — State-of-the-art text embeddings (or any LLM provider) React — A dynamic, real-time search interface Step 1: Setting Up pgvector with Django First, install the required dependencies: pip install django pgvector psycopg2-binary openai Enable the pgvector extension in your PostgreSQL database: -- Run this in your PostgreSQL instance CREATE EXTENSION IF NOT EXISTS vector; Now, create a Django model that stores vector embeddings alongside your content: from django.db import models from pgvector.django import VectorField class Document(models.Model): title = models.CharField(max_length=500) content = models.TextField() embedding = VectorField(dimensions=1536) # OpenAI text-embedding-3-small created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(name='embedding_idx', fields=['embedding'], opclasses=['vector_cosine_ops']), ] Step 2: Generating Embeddings with Django Signals Use Django signals to automatically generate embeddings whenever a document is created or updated: from django.db.models.signals import post_save from django.dispatch import receiver from openai import OpenAI client = OpenAI() @receiver(post_save, sender=Document) def generate_embedding(sender, instance, created, **kwargs): text = f"{instance.title} {instance.content}" response = client.embeddings.create( model="text-embedding-3-small", input=text ) instance.embedding = response.data[0].embedding Document.objects.filter(pk=instance.pk).update( embedding=instance.embedding ) Step 3: Building the Semantic Search API Create a Django REST Framework view that accepts a query, generates its embedding, and returns the most semantically similar documents: from rest_framework.decorators import api_view from rest_framework.response import Response from pgvector.django import CosineDistance from openai import OpenAI client = OpenAI() @api_view(['POST']) def semantic_search(request): query = request.data.get('query', '') if not query: return Response({'error': 'Query is required'}, status=400) response = client.embeddings.create( model="text-embedding-3-small", input=query ) query_embedding = response.data[0].embedding results = ( Document.objects .annotate(distance=CosineDistance('embedding', query_embedding)) .order_by('distance')[:10] ) return Response([ {'title': doc.title, 'excerpt': doc.content[:200] + '...', 'score': 1 - doc.distance} for doc in results ]) Step 4: Building a React Search Component Now, let's create a React component that provides real-time semantic search: import { useState, useEffect } from 'react'; import debounce from 'lodash.debounce'; function SemanticSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const search = debounce(async (searchQuery) => { if (!searchQuery.trim()) { setResults([]); return; } setLoading(true); const res = await fetch('/api/semantic-search/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: searchQuery }), }); const data = await res.json(); setResults(data); setLoading(false); },

Back to Blog | Home | Services | Contact Us