Write the Search Function
Exit

Write the Search Function

Score every chunk against a query and return the top matches

💻

Writing code and entering commands is only available on desktop. Open this page on a larger screen to complete this chapter.

Putting the pieces together

You can now extract text, split it into chunks, embed those chunks, and score two vectors with cosine similarity. The search function ties all of this together: given a user's question, find the chunks that are most likely to contain the answer.

Why the query needs its own embedding

When you embedded chunks in the previous lesson, you used task_type="RETRIEVAL_DOCUMENT". For the user's question you use a different task type: "RETRIEVAL_QUERY".

Why two types? The embedding model is trained to place a short question near the documents that answer it — even though the question and the answer use different words. Setting the task type tells the model which role the text plays, so it can optimise the vector accordingly.

The search algorithm

The function takes five arguments:

ArgumentPurpose
clientThe Gemini API client
queryThe user's question as a string
chunksThe list of text chunks from the PDF
embeddingsThe list of embedding vectors (one per chunk, same order)
top_kHow many results to return (default 3)

The steps inside the function:

  1. Embed the query to get a query vector.
  2. Score every chunk by computing cosine similarity between the query vector and the chunk's embedding.
  3. Sort by score, highest first.
  4. Return the top-k chunks.

Python patterns in this function

The solution uses two patterns you will see often in Python:

  • zip(embeddings, chunks) — walks two lists in lockstep, pairing the first embedding with the first chunk, the second with the second, and so on.
  • List comprehension — builds a new list in a single expression. [(score, chunk) for emb, chunk in zip(...)] creates a list of (score, chunk) pairs.

def search(client, query, chunks, embeddings, top_k=3):
    result = client.models.embed_content(
        model="gemini-embedding-001",
        contents=query,
        config=types.EmbedContentConfig(task_type="RETRIEVAL_QUERY")
    )
    query_vector = result.embeddings[0].values
    scores = [(cosine_similarity(query_vector, emb), chunk)
              for emb, chunk in zip(embeddings, chunks)]
    scores.sort(key=lambda x: x[0], reverse=True)
    return [chunk for _, chunk in scores[:top_k]]

Instructions

Complete the search function. The starter code provides the signature.

  1. Create a variable named result. Assign it client.models.embed_content(model="gemini-embedding-001", contents=query, config=types.EmbedContentConfig(task_type="RETRIEVAL_QUERY")).
  2. Create a variable named query_vector. Assign it result.embeddings[0].values.
  3. Create a variable named scores. Assign it a list comprehension that produces (cosine_similarity(query_vector, emb), chunk) for each emb, chunk in zip(embeddings, chunks).
  4. Sort scores by calling scores.sort(key=lambda x: x[0], reverse=True).
  5. Return a list comprehension that extracts chunk from each _, chunk in scores[:top_k].