Generating embeddings and building semantic search with Gemini is surprisingly about understanding how to turn unstructured text into a format that a neural network can grasp, and then leveraging that grasp for intelligent retrieval.
Imagine you have a pile of documents. A traditional search engine might look for exact keyword matches. Semantic search, on the other hand, understands the meaning behind your query and the documents. Gemini, Google’s powerful multimodal AI, can help us bridge this gap.
Let’s see this in action. We’ll use Python and the google-generativeai library.
First, you need to get an API key from Google AI Studio and set it up:
import google.generativeai as genai
import os
# Replace with your actual API key or set as an environment variable
API_KEY = os.environ.get("GOOGLE_API_KEY")
genai.configure(api_key=API_KEY)
# Load the embedding model
embedding_model = 'models/embedding-001'
Now, let’s create some sample text data. This could be anything: product descriptions, articles, customer reviews, etc.
documents = [
"The quick brown fox jumps over the lazy dog.",
"A fast, agile fox leaps across a sleepy canine.",
"Artificial intelligence is transforming industries.",
"Machine learning algorithms are a subset of AI.",
"The capital of France is Paris, known for the Eiffel Tower.",
"London is the capital of the United Kingdom and a global financial hub."
]
The core of semantic search is the embedding. An embedding is a numerical representation (a vector) of a piece of text’s meaning. Gemini can generate these for us.
def get_embedding(text):
"""Generates an embedding for the given text."""
try:
response = genai.embed_content(
model=embedding_model,
content=text,
task_type="retrieval_document" # or "retrieval_query" for queries
)
return response['embedding']
except Exception as e:
print(f"Error generating embedding for '{text}': {e}")
return None
# Generate embeddings for our documents
document_embeddings = {}
for doc in documents:
embedding = get_embedding(doc)
if embedding:
document_embeddings[doc] = embedding
print(f"Generated embeddings for {len(document_embeddings)} documents.")
This get_embedding function sends our text to Gemini’s embedding model. The task_type is important; retrieval_document tells the model we’re preparing text to be searched against, while retrieval_query would be used for the search term itself. The output is a list of numbers (the vector).
To perform a semantic search, we embed our query and then find the document embeddings that are "closest" to the query embedding in vector space. Closeness is typically measured by cosine similarity.
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
def search_documents(query, num_results=2):
"""Searches documents based on the query embedding."""
query_embedding = get_embedding(query)
if not query_embedding:
return []
similarities = []
for doc, doc_embedding in document_embeddings.items():
# Ensure both embeddings are numpy arrays for cosine_similarity
query_vec = np.array(query_embedding).reshape(1, -1)
doc_vec = np.array(doc_embedding).reshape(1, -1)
similarity = cosine_similarity(query_vec, doc_vec)[0][0]
similarities.append((doc, similarity))
# Sort by similarity in descending order
similarities.sort(key=lambda item: item[1], reverse=True)
return similarities[:num_results]
# Example search
query = "What is the capital of France?"
results = search_documents(query)
print(f"\nSearch results for '{query}':")
for doc, score in results:
print(f"- {doc} (Score: {score:.4f})")
query_2 = "How is AI changing things?"
results_2 = search_documents(query_2)
print(f"\nSearch results for '{query_2}':")
for doc, score in results_2:
print(f"- {doc} (Score: {score:.4f})")
Notice how the first query, "What is the capital of France?", correctly retrieves the document about Paris, even though the exact words aren’t present in the document. The second query, "How is AI changing things?", retrieves documents about AI and machine learning, demonstrating understanding of related concepts.
The mental model here is that Gemini’s embedding model has learned a vast "meaning space." When you embed text, you’re essentially finding a point in that space. Texts with similar meanings will have points that are physically close to each other in this high-dimensional space. Cosine similarity is a mathematical way to measure that proximity.
The exact levers you control are the choice of embedding model (though models/embedding-001 is the current standard for text), how you preprocess your input text (cleaning, chunking large documents), and how you store and retrieve embeddings (in-memory for small datasets, or using vector databases like Pinecone, Weaviate, or FAISS for larger ones). The task_type parameter is also a key configuration point for ensuring the embeddings are optimized for retrieval.
What most people don’t realize is that the quality of your semantic search is heavily influenced by the breadth and depth of the data Gemini was trained on. If your domain is highly specialized and not well-represented in the training data, even the best embedding model might struggle to capture nuances. This is why fine-tuning, where available, can be so powerful for niche applications.
The next step in building a robust semantic search system involves handling much larger datasets, optimizing embedding generation, and implementing efficient vector indexing for faster retrieval.