AI / LLM DevelopmentHow to Create a Semantic Search Feature With Embeddings 2026 By Team CJ August 11, 202666 viewsShareTweet 0Traditional search usually looks for matching words. If a user searches for “how to speed up my website”, a keyword-based search engine may prioritize pages containing the exact words speed, website, or fast.But what if your content uses phrases such as:Improve website performanceReduce page loading timeOptimize Core Web VitalsMake a website load fasterThese pages may be highly relevant even though they do not contain exactly the same words as the query.This is where semantic search becomes useful for finding relevant content by meaning.Semantic search attempts to understand the meaning behind a query rather than relying only on exact keyword matches. Embedding models convert text into numerical vectors, allowing systems to compare the meaning of queries and documents mathematically. Sentence Transformers describes this approach as embedding corpus entries and queries into the same vector space, then finding the closest vectors.In this tutorial, you’ll build a simple semantic search system using:PythonSentence TransformersPostgreSQLpgvectorCosine similarityThis tutorial focuses on building a practical semantic search feature with embeddings, PostgreSQL, and pgvector.The final application will allow a user to enter a natural-language query and retrieve documents with similar meaning.What Is Semantic Search?Semantic search is a search technique that focuses on the meaning and context of a query rather than only matching individual keywords.Consider these two searches:“How can I make my website faster?”and:“Ways to improve web performance”A traditional keyword search may see relatively little overlap.A semantic search system can recognize that both queries are related to website performance optimization.The basic process is:Documents ↓ Embedding Model ↓ Vector Representations ↓ Vector DatabaseWhen the user searches:How can I reduce website loading time?the process becomes:User Query ↓ Embedding Model ↓ Query Vector ↓ Similarity Search ↓ Most Relevant DocumentsThe closer two vectors are in the selected similarity space, the more semantically related their corresponding text can be.What Are Embeddings for Semantic Search?An embedding is a numerical representation of information.For example, a sentence such as:How can I improve website performance?is converted into a vector containing many numerical values.Conceptually:[ 0.021, -0.173, 0.442, ... ]The exact numbers are not important to the developer. What matters is that semantically related text tends to produce vectors that are close together in the embedding space.Embedding models can represent sentences, paragraphs, documents, images, audio, and other types of information. Sentence Transformers provides models specifically useful for semantic search and similarity tasks.For this tutorial, we’ll use Sentence Transformers because it allows the embedding model to run locally rather than requiring an external embedding API.How Semantic Search Works with EmbeddingsOur system has five major stages.1. Collect documentsFor example:Document 1: How to optimize website images for faster loading. Document 2: How to configure WordPress caching. Document 3: How to create a PHP login system. Document 4: How to improve Core Web Vitals.2. Generate embeddingsEach document is converted into an embedding vector.3. Store embeddingsThe vectors are stored in PostgreSQL using the pgvector extension.4. Embed the search queryWhen someone searches for:How do I make my website load faster?the query is converted into another vector.5. Find similar vectorsThe database finds the documents whose vectors are closest to the query vector.This is the foundation of semantic search.Step 1: Install the Required SoftwareYou will need:Python 3PostgreSQLpgvectorSentence TransformersThe pgvector project is an open-source PostgreSQL extension for vector similarity search. It supports exact and approximate nearest-neighbor search as well as cosine distance, inner product, and other distance measures.Install the Python packages:pip install sentence-transformers psycopg2-binaryIf you’re using a virtual environment:python -m venv venvActivate it on Windows:venv\Scripts\activateOn Linux or macOS:source venv/bin/activateThen install the dependencies:pip install sentence-transformers psycopg2-binaryStep 2: Install pgvectorInstall pgvector according to your PostgreSQL environment.After installation, connect to your database and enable the extension:CREATE EXTENSION IF NOT EXISTS vector;Notice that the extension is enabled using the name vector, not pgvector. The official project uses:CREATE EXTENSION vector;and then allows vector columns to be created in PostgreSQL.Create a database for the tutorial:CREATE DATABASE semantic_search;Connect to it:\c semantic_searchThen enable pgvector:CREATE EXTENSION IF NOT EXISTS vector;Step 3: Create the Documents TableWe need somewhere to store our documents and their embeddings.Create a table:CREATE TABLE documents ( id SERIAL PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, embedding VECTOR(384) );The VECTOR(384) dimension must match the embedding model we use.For this tutorial we’ll use:all-MiniLM-L6-v2This model produces 384-dimensional embeddings.Your database structure is now:documents ------------------------ id title content embeddingThe important part is that the original text and its vector are stored together.This is one of the advantages of pgvector: embeddings can live alongside ordinary PostgreSQL data and can be queried using SQL.Step 4: Create Sample DocumentsLet’s insert some content.INSERT INTO documents (title, content) VALUES ( 'Website Performance Optimization', 'Learn how to reduce page loading time by optimizing images, CSS, JavaScript and browser caching.' ), ( 'WordPress Caching Guide', 'Configure caching to improve WordPress website performance and reduce server response time.' ), ( 'PHP Login System', 'Build a secure PHP login and registration system using MySQL and password hashing.' ), ( 'Core Web Vitals Guide', 'Learn how to improve LCP, INP and CLS to create a faster and more responsive website.' ), ( 'JavaScript API Tutorial', 'Learn how to retrieve data from REST APIs using JavaScript Fetch API.' );At this point, we have normal text stored in PostgreSQL.The next step is to convert this text into vectors.Step 5: Load the Embedding ModelCreate a Python file:semantic_search.pyImport Sentence Transformers:from sentence_transformers import SentenceTransformer model = SentenceTransformer("all-MiniLM-L6-v2")Sentence Transformers provides methods for generating fixed-size vector representations and supports semantic search, similarity calculations, clustering, and related tasks.Test the model:text = "How can I make my website faster?" embedding = model.encode(text) print(embedding) print(len(embedding))You should see a vector and:384The number of dimensions must match:VECTOR(384)in PostgreSQL.Step 6: Generate Embeddings for DocumentsNow retrieve the documents from PostgreSQL.import psycopg2 from sentence_transformers import SentenceTransformer model = SentenceTransformer("all-MiniLM-L6-v2") connection = psycopg2.connect( host="localhost", database="semantic_search", user="postgres", password="YOUR_PASSWORD" ) cursor = connection.cursor() cursor.execute(""" SELECT id, content FROM documents WHERE embedding IS NULL """) documents = cursor.fetchall()Generate an embedding for every document:for document_id, content in documents: embedding = model.encode(content) cursor.execute( """ UPDATE documents SET embedding = %s WHERE id = %s """, (embedding.tolist(), document_id) ) connection.commit()Now every document has a corresponding embedding.Step 7: Create a Vector IndexFor a small tutorial dataset, exact similarity search is perfectly reasonable.As your dataset grows, however, approximate nearest-neighbor indexes can improve search performance.pgvector supports indexes including HNSW and IVFFlat.For cosine distance, create an HNSW index:CREATE INDEX documents_embedding_idx ON documents USING hnsw (embedding vector_cosine_ops);HNSW is useful when you have a larger collection of vectors and need faster approximate nearest-neighbor retrieval.Do not automatically add an index before measuring your workload. For a small dataset, the simplest exact search may be sufficient.Step 8: Convert the User Query Into an EmbeddingNow let’s implement the actual search.Create a function:def semantic_search(query, limit=5): query_embedding = model.encode(query) cursor.execute( """ SELECT id, title, content, 1 - (embedding <=> %s::vector) AS similarity FROM documents WHERE embedding IS NOT NULL ORDER BY embedding <=> %s::vector LIMIT %s """, ( query_embedding.tolist(), query_embedding.tolist(), limit ) ) return cursor.fetchall()The important part is:embedding <=> query_vectorIn pgvector, <=> represents cosine distance. Cosine similarity can be expressed as:1 - cosine distanceThe pgvector documentation provides this same relationship.Step 9: Test the Semantic SearchNow run:results = semantic_search( "How can I make my website load faster?" ) for result in results: print(result)You might receive results such as:Website Performance Optimization Core Web Vitals Guide WordPress Caching GuideNotice something important.The query didn’t necessarily contain:Core Web Vitalsor:WordPress cachingYet those documents may still be relevant because their meaning is related to website performance.That’s the key difference between semantic search and simple keyword matching.Step 10: Create a Complete Search FunctionLet’s make the function cleaner.def search_documents(query, limit=5): query_vector = model.encode( query, normalize_embeddings=True ) cursor.execute( """ SELECT id, title, content, 1 - (embedding <=> %s::vector) AS similarity FROM documents WHERE embedding IS NOT NULL ORDER BY embedding <=> %s::vector LIMIT %s """, ( query_vector.tolist(), query_vector.tolist(), limit ) ) return cursor.fetchall()Then:query = input("Search: ") results = search_documents(query) for result in results: document_id, title, content, similarity = result print("\nTitle:", title) print("Similarity:", round(similarity, 4)) print("Content:", content)You now have a basic semantic search engine.Step 11: Understand Cosine SimilarityCosine similarity measures how closely two vectors point in the same direction.Conceptually:Query Vector ↓ / \ / \ / \ Document A Query Vector ↓ / / Document BIf the vectors are pointing in similar directions, the semantic similarity is higher.This makes cosine similarity useful for comparing text embeddings.pgvector supports several distance functions, including cosine distance, L2 distance, inner product, L1 distance, Hamming distance, and Jaccard distance.For text embeddings, cosine distance is a common starting point.Step 12: Add a Similarity ThresholdNot every result should necessarily be displayed.For example:def search_documents(query, limit=5, threshold=0.45): query_vector = model.encode( query, normalize_embeddings=True ) cursor.execute( """ SELECT id, title, content, 1 - (embedding <=> %s::vector) AS similarity FROM documents WHERE embedding IS NOT NULL ORDER BY embedding <=> %s::vector LIMIT %s """, ( query_vector.tolist(), query_vector.tolist(), limit ) ) results = cursor.fetchall() return [ result for result in results if result[3] >= threshold ]The threshold should not be treated as a universal number.You should test it using your own content and queries.A threshold that works well for one embedding model or dataset may not work well for another.Step 13: Improve Results With MetadataReal applications usually need more than semantic similarity.Suppose CodexJunction has thousands of tutorials.Your database could contain:id title content category difficulty url published_date embeddingFor example:CREATE TABLE tutorials ( id SERIAL PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, category TEXT, difficulty TEXT, url TEXT, embedding VECTOR(384) );Now you can combine semantic similarity with normal database filters.For example:SELECT id, title, url, 1 - (embedding <=> %s::vector) AS similarity FROM tutorials WHERE category = 'JavaScript' ORDER BY embedding <=> %s::vector LIMIT 10;This is much more powerful than vector similarity alone.A user could search:How do I validate forms?and filter the results to:Category: JavaScript Difficulty: BeginnerThe database then handles both structured filtering and semantic ranking.Step 14: Use Chunking for Large DocumentsFor a small document, embedding the entire document can work.For large articles, however, embedding the entire article as one vector may not provide the best retrieval.Instead, split the content into smaller chunks.For example:Article ↓ Introduction ↓ Section 1 ↓ Section 2 ↓ Section 3 ↓ Section 4Each chunk receives its own embedding.A table could look like:CREATE TABLE document_chunks ( id SERIAL PRIMARY KEY, document_id INTEGER, chunk_text TEXT, embedding VECTOR(384) );Then semantic search retrieves the most relevant chunks rather than entire articles.This approach is particularly useful for:DocumentationTutorialsKnowledge basesPDFsSupport articlesProduct documentationInternal company knowledgeSentence Transformers specifically describes semantic search as embedding corpus entries and then retrieving the closest entries to an embedded query.Step 15: Build Semantic Search for CodexJunctionA practical CodexJunction implementation could index:Tutorials Tools Resources FAQs Documentation Freebies GuidesEach item could have:Title Description Content Category Technology Difficulty URL EmbeddingA user could search:I want to learn how to connect PHP to a databaseThe semantic search system could return:How to Connect PHP to MySQL Using PDOHow to Build a PHP CRUD ApplicationHow to Create a PHP Login SystemHow to Design a MySQL DatabaseHow to Build a PHP/MySQL ProjectThe user doesn’t need to know the exact article title.That’s where semantic search becomes particularly useful for a tutorial platform.Common Mistakes to Avoid1. Using Different Embedding ModelsThe documents and queries should be embedded in a compatible vector space.Do not embed documents using one model and queries using an unrelated model without understanding the compatibility requirements.2. Incorrect Vector DimensionsIf your model produces 384-dimensional vectors:VECTOR(384)must be used.If you change models and the new model produces a different number of dimensions, your database schema must accommodate that model.3. Searching Raw Keywords Instead of VectorsA semantic search system needs:Query ↓ Embedding ↓ Vector SearchIf you simply search:WHERE content ILIKE '%keyword%'you’re performing lexical matching rather than semantic retrieval.4. Embedding Huge Documents Without ChunkingLarge articles should generally be split into meaningful chunks when retrieval needs to identify a particular section.Avoid splitting purely by arbitrary character counts when you can preserve logical sections.5. Ignoring MetadataSemantic similarity alone doesn’t always determine the best result.Use metadata such as:category language difficulty date content type permissionswhere appropriate.6. Choosing a Similarity Threshold Without TestingDon’t assume:0.5 = relevantfor every dataset.Create a small evaluation set containing real user queries and expected results, then tune your retrieval strategy against that data.Semantic Search vs Keyword SearchFeatureKeyword SearchSemantic SearchExact keywords Excellent GoodSynonyms Limited StrongNatural-language queries Limited StrongMisspellings Depends on Can be more implementation tolerantMeaning/context Limited StrongImplementation Usually simpler More componentsDatabase requirements Basic database Vector-capable storageAI applications Limited ExcellentSemantic search isn’t necessarily a replacement for keyword search.In production systems, hybrid search can often be useful because lexical matching and semantic retrieval solve different problems.How to Improve Semantic Search FurtherOnce the basic implementation works, you can add more sophisticated retrieval techniques.Hybrid SearchCombine:Keyword Search + Vector SearchThis can help when exact terms are important but semantic understanding is also valuable.RerankingRetrieve the top 20 or 50 candidates using embeddings and then use a reranker to reorder the most relevant results.Sentence Transformers documents the use of bi-encoders as an initial retrieval step followed by Cross-Encoder reranking.Metadata FilteringFilter results by:CategoryTechnologyUser permissionsDateLanguageContent typeQuery ExpansionTransform a short query into a richer search representation before retrieval.EvaluationCreate a test set:Query → Expected ResultsThen measure whether the system consistently retrieves useful documents.When Should You Use a Vector Database?You don’t necessarily need a separate vector database service.If your application already uses PostgreSQL, pgvector can keep embeddings and relational data together.pgvector supports exact nearest-neighbor searches and approximate indexes such as HNSW and IVFFlat.This can be particularly convenient for applications that already depend heavily on PostgreSQL.For very large or specialized workloads, you can evaluate dedicated vector-search infrastructure based on:Dataset sizeQuery volumeLatency requirementsFiltering requirementsOperational complexityCostRecall requirementsSemantic Search ArchitectureYour final system can look like this: DOCUMENT INGESTION │ ▼ Extract Content │ ▼ Generate Embedding │ ▼ PostgreSQL + pgvector │ │ ▼ USER QUERY → Generate Query Embedding │ ▼ Vector Search │ ▼ Metadata Filtering │ ▼ Reranking │ ▼ Search ResultsFor an AI-powered application, you can extend this architecture:User Query ↓ Query Embedding ↓ Vector Search ↓ Top Relevant Documents ↓ Optional Reranking ↓ LLM ↓ Final AnswerThat pattern forms the foundation of many retrieval-augmented generation systems.Related CodexJunction ResourcesExplore related resources from CodexJunction: – MOV to JPG Converter – PNG to JPG Converter – JPG to WebP Converter – JPG ConverterConclusionSemantic search allows applications to search based on meaning rather than exact keyword matches.The fundamental workflow is straightforward:Documents ↓ Embeddings ↓ Vector Database ↓ Query Embedding ↓ Similarity Search ↓ Relevant ResultsIn this tutorial, you built that workflow using Python, Sentence Transformers, PostgreSQL, and pgvector.The key concepts to remember are:Embeddings represent text as numerical vectors.Documents and queries should be embedded into a compatible vector space.Vector databases store and retrieve those embeddings.Cosine distance can be used to compare semantic similarity.pgvector adds vector similarity search directly to PostgreSQL.Metadata filters can improve retrieval quality.Large documents can be divided into meaningful chunks.Reranking and hybrid search can improve more advanced systems.Evaluation with real queries is essential before deploying semantic search at scale.For production applications, semantic search can be combined with keyword search, metadata filters, chunking, reranking, and evaluation to improve retrieval quality.The next logical step is to turn this basic system into a production-ready semantic search engine with document chunking, hybrid keyword + vector search, metadata filters, reranking, and an API endpoint.For developers building AI-powered applications, semantic search is also an important foundation for recommendation systems, knowledge bases, documentation search, and RAG applications.
AI / LLM DevelopmentHow to Build an AI-Powered Content Summarizer With Python: Beginner’s Guide 2026 By Team CJAugust 13, 20260