DEV Community

Cover image for Build a Tiny Semantic Search Engine in Python
Taha hussein
Taha hussein

Posted on

Build a Tiny Semantic Search Engine in Python

What if a search engine could understand that these two sentences are related?

"I love programming"

"I enjoy coding"
Enter fullscreen mode Exit fullscreen mode

They don't use the exact same words, but their meanings are close.

That's the basic idea behind semantic search.

In this tutorial, we'll build a tiny semantic search engine in Python using:

  • Sentence Transformers
  • Embeddings
  • Cosine Similarity
  • scikit-learn

No database.
No RAG.
No complicated architecture.

Just the core idea.


Step 1 — Install the Libraries

pip install sentence-transformers scikit-learn
Enter fullscreen mode Exit fullscreen mode

Then import them:

from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
Enter fullscreen mode Exit fullscreen mode

Step 2 — Load an Embedding Model

model = SentenceTransformer("all-MiniLM-L6-v2")
Enter fullscreen mode Exit fullscreen mode

This model converts text into numerical embeddings.

Think of the process as:

"I love programming"
        ↓
   Embedding Model
        ↓
   [0.02, -0.14, 0.08, ...]
Enter fullscreen mode Exit fullscreen mode

The sentence has now been represented by numbers.


Step 3 — Create Some Sentences

sentences = [
    "I love programming",
    "I enjoy coding",
    "The weather is very hot"
]
Enter fullscreen mode Exit fullscreen mode

Generate their embeddings:

embeddings = model.encode(sentences)

print(embeddings.shape)
Enter fullscreen mode Exit fullscreen mode

You'll get something similar to:

(3, 384)
Enter fullscreen mode Exit fullscreen mode

So we have:

3 sentences
384 numbers for each sentence
Enter fullscreen mode Exit fullscreen mode

Step 4 — Compare the Sentences

Now let's calculate their similarity:

similarity = cosine_similarity(embeddings)

print(similarity)
Enter fullscreen mode Exit fullscreen mode

You might see:

[
 [1.00, 0.63, 0.12],
 [0.63, 1.00, 0.22],
 [0.12, 0.22, 1.00]
]
Enter fullscreen mode Exit fullscreen mode

Remember our sentences:

0 → I love programming
1 → I enjoy coding
2 → The weather is very hot
Enter fullscreen mode Exit fullscreen mode

So:

similarity[0][1]
Enter fullscreen mode Exit fullscreen mode

means:

"I love programming"
        ↕
"I enjoy coding"
Enter fullscreen mode Exit fullscreen mode

and gives approximately:

0.63
Enter fullscreen mode Exit fullscreen mode

That's considerably higher than the similarity between programming and weather.


Step 5 — Let's Build an Actual Search

Now we'll create a few documents:

documents = [
    "Python is a programming language.",
    "Machine learning is a branch of artificial intelligence.",
    "The weather is very hot today.",
    "Football is a popular sport."
]
Enter fullscreen mode Exit fullscreen mode

Our user will search for:

query = "I want to learn coding"
Enter fullscreen mode Exit fullscreen mode

The interesting question is:

Which document is closest to this query?


Step 6 — Embed the Documents

document_embeddings = model.encode(documents)
Enter fullscreen mode Exit fullscreen mode

Each document now has its own embedding.


Step 7 — Embed the Query

query_embedding = model.encode([query])
Enter fullscreen mode Exit fullscreen mode

Now we have:

Query → Embedding

Documents → Embeddings
Enter fullscreen mode Exit fullscreen mode

Step 8 — Calculate Similarity

scores = cosine_similarity(
    query_embedding,
    document_embeddings
)[0]

print(scores)
Enter fullscreen mode Exit fullscreen mode

You might get:

[0.70, 0.55, 0.12, 0.08]
Enter fullscreen mode Exit fullscreen mode

The exact numbers can vary.

But the interpretation is:

Python                    0.70
Machine learning          0.55
Weather                   0.12
Football                  0.08
Enter fullscreen mode Exit fullscreen mode

The Python document is the closest match to our query.


Step 9 — Rank the Search Results

Let's make the output easier to read:

results = sorted(
    zip(scores, documents),
    reverse=True
)

for score, document in results:
    print(f"{score:.3f} - {document}")
Enter fullscreen mode Exit fullscreen mode

Example:

0.700 - Python is a programming language.
0.550 - Machine learning is a branch of artificial intelligence.
0.120 - The weather is very hot today.
0.080 - Football is a popular sport.
Enter fullscreen mode Exit fullscreen mode

Congratulations.

You just built a tiny semantic search engine.


The Complete Code

from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity

model = SentenceTransformer("all-MiniLM-L6-v2")

documents = [
    "Python is a programming language.",
    "Machine learning is a branch of artificial intelligence.",
    "The weather is very hot today.",
    "Football is a popular sport."
]

query = "I want to learn coding"

document_embeddings = model.encode(documents)
query_embedding = model.encode([query])

scores = cosine_similarity(
    query_embedding,
    document_embeddings
)[0]

results = sorted(
    zip(scores, documents),
    reverse=True
)

for score, document in results:
    print(f"{score:.3f} - {document}")
Enter fullscreen mode Exit fullscreen mode

What Did We Actually Build?

The architecture is surprisingly simple:

              Query
                ↓
           Embedding
                ↓
       ┌────────┴────────┐
       ↓        ↓        ↓
    Document Document Document
    Embedding Embedding Embedding
       └────────┬────────┘
                ↓
       Cosine Similarity
                ↓
          Similarity Scores
                ↓
             Ranking
                ↓
        Search Results
Enter fullscreen mode Exit fullscreen mode

Why Not Just Search for Keywords?

Suppose the user searches:

"I want to learn coding"
Enter fullscreen mode Exit fullscreen mode

But the document says:

"Python is a programming language."
Enter fullscreen mode Exit fullscreen mode

There may be no exact match for the word coding.

Semantic search doesn't depend only on exact words.

The embedding model creates representations that allow us to compare the meaning of the texts.

That's why:

coding
Enter fullscreen mode Exit fullscreen mode

and:

programming
Enter fullscreen mode Exit fullscreen mode

can produce relatively similar embeddings.


Three Concepts to Remember

If you're learning NLP or modern AI systems, keep these three concepts separate.

1. Embedding

Turns text into numbers.

Text → Numbers
Enter fullscreen mode Exit fullscreen mode

2. Cosine Similarity

Compares two embeddings.

Embedding A
     +
Embedding B
     ↓
Similarity Score
Enter fullscreen mode Exit fullscreen mode

3. Semantic Search

Uses those similarities to find relevant information.

Query
 ↓
Embedding
 ↓
Compare
 ↓
Rank
 ↓
Results
Enter fullscreen mode Exit fullscreen mode

Where Do We Go From Here?

This example is intentionally small.

Real applications may contain thousands or millions of documents, so calculating similarity against every document isn't always efficient.

That's where techniques such as vector databases and approximate nearest-neighbor search become useful.

And when retrieved documents are passed to a language model to help answer a user's question, we enter the world of Retrieval-Augmented Generation (RAG).

But before moving there, make sure this basic pipeline is clear:

Text
 ↓
Embedding
 ↓
Similarity
 ↓
Search
Enter fullscreen mode Exit fullscreen mode

Once this clicks, the more advanced systems become much easier to understand.

LinkedIn: https://www.linkedin.com/in/taha-hussein-b0a583425/

YouTube: https://www.youtube.com/@Tahahussein-Ai

Top comments (0)