0Pricing
AI Engineering Academy · Lektion

Semantische Suche mit NumPy

Sie erstellen ein semantisches Suchsystem in reinem Python, das mit NumPy die Kosinusähnlichkeit zwischen einem Query-Embedding und einer Sammlung von Dokument-Embeddings berechnet.

Semantische Suche mit NumPy ist eine kostenlose AI Engineering Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Engineering Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.

Semantic Search Without a Database

Semantic search finds the most relevant documents for a query based on meaning rather than keyword overlap. The simplest implementation uses NumPy to compute cosine similarity between a query embedding and all document embeddings in memory — no external database required.

This approach works well for up to tens of thousands of documents and is ideal for prototyping before investing in a vector database.

Building the Document Corpus

Start by collecting your documents and generating one embedding per document using the OpenAI API. Store the embeddings as a 2D NumPy array where each row is one document vector. Keep a parallel list of document texts so you can retrieve the original content after finding the best matches.

import numpy as np
from openai import OpenAI

client = OpenAI()

documents = [
    'Python is a high-level programming language.',
    'NumPy provides fast numerical computing for Python.',
    'Embeddings represent text as dense vectors.',
    'Cosine similarity measures angle between vectors.',
    'RAG combines retrieval with language generation.'
]

response = client.embeddings.create(
    model='text-embedding-3-small',
    input=documents
)

corpus_embeddings = np.array([item.embedding for item in response.data])
print(f'Corpus shape: {corpus_embeddings.shape}')  # (5, 1536)

Embedding the User Query

When a user submits a search query, embed it using the same model that was used to embed the documents. Mixing models — for example, using text-embedding-3-small for documents and text-embedding-3-large for queries — will produce vectors in different spaces and give meaningless similarity scores.

from openai import OpenAI
import numpy as np

client = OpenAI()

query = 'How do I compute similarity between text?'

response = client.embeddings.create(
    model='text-embedding-3-small',   # must match corpus model
    input=query
)

query_embedding = np.array(response.data[0].embedding)
print(f'Query vector shape: {query_embedding.shape}')  # (1536,)

Computing Cosine Similarity with NumPy

To find the similarity between the query and every document in one operation, compute the dot product of the query vector with the matrix of document vectors. Since both OpenAI embeddings are L2-normalized, this equals cosine similarity for all documents at once — O(n * d) where n is the number of documents and d is the dimension.

import numpy as np

# corpus_embeddings: (n_docs, 1536)
# query_embedding: (1536,)

def semantic_search(query_vec, corpus_vecs):
    # Matrix-vector dot product: shape (n_docs,)
    similarities = corpus_vecs @ query_vec
    return similarities

# Example call (assuming pre-computed embeddings)
# sims = semantic_search(query_embedding, corpus_embeddings)
# print(sims)  # array of similarity scores, one per document

Ranking and Retrieving Top-K Results

Use np.argsort to rank documents by similarity score in descending order, then slice the top-k indices. This gives you the indices of the most relevant documents, which you use to look up the original text from your parallel list.

import numpy as np

def get_top_k(query_vec, corpus_vecs, documents, k=3):
    similarities = corpus_vecs @ query_vec
    # argsort gives ascending order; [::-1] reverses to descending
    ranked_indices = np.argsort(similarities)[::-1]
    top_k_indices = ranked_indices[:k]
    return [
        {'text': documents[i], 'score': float(similarities[i])}
        for i in top_k_indices
    ]

# results = get_top_k(query_embedding, corpus_embeddings, documents, k=3)
# for r in results:
#     print(f'{r["score"]:.4f}: {r["text"]}')

Full Semantic Search Example

Putting it all together: embed the corpus, embed the query, compute similarities, and return ranked results. This complete pattern is the core of every RAG retrieval step, even when a vector database replaces NumPy under the hood.

import numpy as np
from openai import OpenAI

client = OpenAI()

docs = [
    'Embeddings map text to numerical vectors.',
    'Python lists store ordered collections.',
    'Cosine similarity compares vector directions.',
    'RAG retrieves documents to ground LLM answers.',
    'Dictionaries store key-value pairs in Python.'
]

corpus_resp = client.embeddings.create(model='text-embedding-3-small', input=docs)
corpus = np.array([d.embedding for d in corpus_resp.data])

query = 'finding similar text using angles'
q_resp = client.embeddings.create(model='text-embedding-3-small', input=query)
q_vec = np.array(q_resp.data[0].embedding)

scores = corpus @ q_vec
for i in np.argsort(scores)[::-1][:3]:
    print(f'{scores[i]:.3f}: {docs[i]}')

Score Thresholding

Not all top-k results are actually relevant — sometimes the best match is still a poor semantic fit. Add a score threshold to filter out low-similarity results. A typical threshold is 0.70–0.80 for cosine similarity, but you should calibrate this against your specific domain using real queries.

import numpy as np

def search_with_threshold(query_vec, corpus_vecs, documents, k=5, threshold=0.75):
    similarities = corpus_vecs @ query_vec
    ranked = np.argsort(similarities)[::-1][:k]
    results = []
    for i in ranked:
        if similarities[i] >= threshold:
            results.append({'text': documents[i], 'score': float(similarities[i])})
    return results

# Only returns documents above the minimum similarity threshold

Performance Characteristics of NumPy Search

NumPy similarity search has time complexity O(n * d) per query, where n is the number of documents and d is the embedding dimension. For 1536-dimensional embeddings:

  • 10,000 docs: ~5ms per query on a modern CPU
  • 100,000 docs: ~50ms per query
  • 1,000,000 docs: ~500ms — too slow, switch to a vector database

NumPy is excellent for prototyping but has no approximate search, filtering, or persistence built in.

Persisting Embeddings to Disk

Recomputing embeddings on every run wastes API calls and money. Save your corpus embeddings and document texts to disk so you only re-embed when the corpus changes.

np.save stores the embedding matrix efficiently, and you can save the document list as JSON. On startup, load both files instead of calling the API.

import numpy as np
import json

# Save
np.save('/tmp/corpus_embeddings.npy', corpus_embeddings)
with open('/tmp/corpus_docs.json', 'w') as f:
    json.dump(documents, f)

# Load
corpus_embeddings = np.load('/tmp/corpus_embeddings.npy')
with open('/tmp/corpus_docs.json') as f:
    documents = json.load(f)

print(f'Loaded {len(documents)} docs, shape {corpus_embeddings.shape}')

Handling New Documents Incrementally

When new documents arrive, you do not need to re-embed the entire corpus. Embed only the new documents and use np.vstack to append their vectors to the existing matrix. Remember to append the new texts to your document list in the same order.

import numpy as np
from openai import OpenAI

client = OpenAI()

# Assume these exist from a previous session:
# corpus_embeddings: (n, 1536)
# documents: list of strings

new_docs = ['New document about vector search.']
resp = client.embeddings.create(model='text-embedding-3-small', input=new_docs)
new_vecs = np.array([item.embedding for item in resp.data])

corpus_embeddings = np.vstack([corpus_embeddings, new_vecs])
documents.extend(new_docs)
print(f'Corpus now has {len(documents)} documents')

Limitations of In-Memory Search

NumPy semantic search has significant limitations compared to a purpose-built vector database:

  • No persistence — everything lives in RAM and is lost on restart
  • No metadata filtering — you cannot filter results by date, category, or author
  • Linear scan only — no approximate nearest neighbor indexing
  • No concurrent access — not suitable for multi-user production deployments

These limitations motivate using a dedicated vector database for production RAG systems.

Quick Check

Test your understanding of AI Engineering concepts from this lesson.

Lesson Recap

In this lesson you learned: matrix dot products against L2-normalized embeddings compute cosine similarity for the entire corpus in one operation, np.argsort with reversal retrieves the top-k most similar documents, and NumPy search is ideal for prototypes but lacks persistence and metadata filtering. Next up we use clustering and UMAP to discover topic structure in an embedding collection.

Häufig gestellte Fragen

Ist die Lektion „Semantische Suche mit NumPy“ kostenlos?

Ja — der vollständige Text von „Semantische Suche mit NumPy“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Engineering Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Semantische Suche mit NumPy“?

Sie erstellen ein semantisches Suchsystem in reinem Python, das mit NumPy die Kosinusähnlichkeit zwischen einem Query-Embedding und einer Sammlung von Dokument-Embeddings berechnet. Du übst AI Engineering Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Engineering Academy zu starten?

Keine Vorkenntnisse erforderlich. AI Engineering Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Semantische Suche mit NumPy“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Engineering Academy-Lektion Code schreiben und ausführen?

Ja. Jede AI Engineering Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Was sind Vektor-Embeddings?
  2. Embeddings mit OpenAI erzeugen
  3. Semantische Suche mit NumPy
  4. Embeddings clustern und visualisieren
← Zurück zu AI Engineering Academy