Gemini
The Gemini API provides embedding models from Google. gemini-embedding-2 maps text, images, video, audio, and PDFs into one vector space, so a text query can find an image or a video clip. Your application calls the Gemini API and sends the vectors to Qdrant.
Models
| Model | Dimensions | Max Input Tokens | Input |
|---|---|---|---|
gemini-embedding-2 | 3072 (128 to 3072) | 8,192 | Text, image, video, audio, PDF |
gemini-embedding-001 | 3072 (128 to 3072) | 2,048 | Text |
Use gemini-embedding-2 for new projects. gemini-embedding-001 remains available for text-only workloads. The two models have incompatible vector spaces, so moving from one to the other means re-embedding all of your data.
Google recommends cosine similarity. Full-size vectors are normalized, and gemini-embedding-2 also normalizes vectors you shorten with output_dimensionality. Shortened gemini-embedding-001 vectors are not normalized, which the Cosine metric handles because Qdrant normalizes vectors in Cosine collections when it stores them.
gemini-embedding-2 is also available on Google Cloud’s Gemini Enterprise Agent Platform (formerly Vertex AI), through the same SDK with genai.Client(vertexai=True, project=..., location=...). There, the SDK accepts one input per request.
Setup
Install the Google Gen AI SDK and the Qdrant client:
- Python:
pip install google-genai qdrant-client - TypeScript:
npm install @google/genai @qdrant/js-client-rest
Create both clients and a collection sized for the model. The Gemini client reads your API key from the GEMINI_API_KEY environment variable.
from google import genai
from google.genai import types
from qdrant_client import QdrantClient, models
gemini_client = genai.Client()
client = QdrantClient(url="http://localhost:6333")
MODEL = "gemini-embedding-2"
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=3072, distance=models.Distance.COSINE),
)
import { GoogleGenAI } from "@google/genai";
import { QdrantClient } from "@qdrant/js-client-rest";
const gemini = new GoogleGenAI({});
const client = new QdrantClient({ url: "http://localhost:6333" });
const MODEL = "gemini-embedding-2";
await client.createCollection("{collection_name}", {
vectors: { size: 3072, distance: "Cosine" },
});
Embed Documents
gemini-embedding-2 combines all the parts of one Content object into a single embedding. A plain list of strings is treated as one Content, so it returns one aggregated vector. To get one vector per document, wrap each document in its own Content. The response lists the embeddings in input order, which this example uses for the point IDs. Each document gets the title: ... | text: ... prefix that Google recommends for retrieval (see Task Types), and the payload stores the original text. The SDK types the response fields as optional, so the Python example asserts that they’re set:
documents = [
"Qdrant is a vector search engine.",
"Gemini embedding models map text, images, video, audio, and PDFs into one vector space.",
]
contents = [
types.Content(parts=[types.Part.from_text(text=f"title: none | text: {doc}")])
for doc in documents
]
response = gemini_client.models.embed_content(model=MODEL, contents=contents)
assert response.embeddings is not None
points = []
for idx, embedding in enumerate(response.embeddings):
assert embedding.values is not None
points.append(
models.PointStruct(
id=idx,
vector=embedding.values,
payload={"text": documents[idx]},
)
)
client.upsert(collection_name="{collection_name}", points=points)
const documents = [
"Qdrant is a vector search engine.",
"Gemini embedding models map text, images, video, audio, and PDFs into one vector space.",
];
const response = await gemini.models.embedContent({
model: MODEL,
contents: documents.map((doc) => ({ parts: [{ text: `title: none | text: ${doc}` }] })),
});
await client.upsert("{collection_name}", {
points: response.embeddings!.map((embedding, idx) => ({
id: idx,
vector: embedding.values!,
payload: { text: documents[idx] },
})),
});
Each input can be up to 8,192 tokens, and longer inputs are truncated without an error. For large datasets where latency does not matter, the Batch API embeds at half the standard price. See Upload Points for batching on the Qdrant side.
Embed Queries
Queries get a different prefix from documents. task: search result | query: ... tells the model that this text is a search query, while the title: ... | text: ... prefix marks the documents it should match:
query_response = gemini_client.models.embed_content(
model=MODEL,
contents="task: search result | query: What is Qdrant?",
)
assert query_response.embeddings is not None
query_vector = query_response.embeddings[0].values
assert query_vector is not None
hits = client.query_points(
collection_name="{collection_name}",
query=query_vector,
limit=3,
).points
const queryResponse = await gemini.models.embedContent({
model: MODEL,
contents: "task: search result | query: What is Qdrant?",
});
const queryVector = queryResponse.embeddings?.[0]?.values;
if (!queryVector) throw new Error("Gemini returned no query embedding");
const { points: hits } = await client.query("{collection_name}", {
query: queryVector,
limit: 3,
});
Task Types
gemini-embedding-2 does not accept the task_type parameter. Write the task into the text instead, and use the same format for every document and query in a collection:
| Use Case | Query Format | Document Format |
|---|---|---|
| Search | task: search result | query: {content} | title: {title} | text: {content}, with title: none if there is no title |
| Question answering | task: question answering | query: {content} | title: {title} | text: {content} |
| Fact checking | task: fact checking | query: {content} | title: {title} | text: {content} |
| Code retrieval | task: code retrieval | query: {content} | title: {title} | text: {content} |
For classification and clustering, use task: classification | query: {content} or task: clustering | query: {content} for every input. Google advises against adding a prefix to the text part of a multimodal input.
gemini-embedding-001 takes a task_type instead. For retrieval, embed documents with RETRIEVAL_DOCUMENT and queries with RETRIEVAL_QUERY, QUESTION_ANSWERING, FACT_VERIFICATION, or CODE_RETRIEVAL_QUERY. The model also supports SEMANTIC_SIMILARITY, CLASSIFICATION, and CLUSTERING. Unlike gemini-embedding-2, it returns one vector per string in a list:
legacy_response = gemini_client.models.embed_content(
model="gemini-embedding-001",
contents=documents,
config=types.EmbedContentConfig(task_type="RETRIEVAL_DOCUMENT"),
)
const legacyResponse = await gemini.models.embedContent({
model: "gemini-embedding-001",
contents: documents,
config: { taskType: "RETRIEVAL_DOCUMENT" },
});
Embed Images, Video, Audio, and PDFs
gemini-embedding-2 embeds files into the same vector space as text, so a collection of PDFs can be searched with the text query from Embed Queries. This example embeds a PDF and stores it in the same collection:
with open("filename.pdf", "rb") as f:
pdf_bytes = f.read()
pdf_response = gemini_client.models.embed_content(
model=MODEL,
contents=types.Part.from_bytes(data=pdf_bytes, mime_type="application/pdf"),
)
assert pdf_response.embeddings and pdf_response.embeddings[0].values
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=100,
vector=pdf_response.embeddings[0].values,
payload={"file": "filename.pdf"},
)
],
)
import { readFileSync } from "node:fs";
const pdfResponse = await gemini.models.embedContent({
model: MODEL,
contents: [
{ inlineData: { mimeType: "application/pdf", data: readFileSync("filename.pdf", "base64") } },
],
});
await client.upsert("{collection_name}", {
points: [{ id: 100, vector: pdfResponse.embeddings![0].values!, payload: { file: "filename.pdf" } }],
});
Images, audio, and video work the same way with their MIME types, such as image/png, audio/mpeg, and video/mp4. All inputs share the 8,192-token limit, and each modality has its own limits:
- Images: up to 6 per request, PNG or JPEG.
- Audio: up to 180 seconds, MP3 or WAV.
- Video: up to 120 seconds, MP4 or MOV. The model samples at most 32 frames and ignores the audio track, so split longer videos into overlapping clips.
- PDF: one file per request, up to 6 pages, each page costing 258 tokens plus its text. Google recommends one page per PDF for the best quality.
To embed a text caption and an image as one item, for example a social media post, put both parts in the same Content and the model returns one aggregated vector. See Embedding Aggregation in the Gemini docs.
Reduce Vector Size
Both models use Matryoshka Representation Learning, so output_dimensionality can shorten the vector with little loss in quality. Google recommends 768, 1536, or 3072. Set the collection’s size to the same value, and pass the same output_dimensionality when you embed queries:
reduced = gemini_client.models.embed_content(
model=MODEL,
contents="task: search result | query: What is Qdrant?",
config=types.EmbedContentConfig(output_dimensionality=768),
)
const reduced = await gemini.models.embedContent({
model: MODEL,
contents: "task: search result | query: What is Qdrant?",
config: { outputDimensionality: 768 },
});
To keep the full vectors but store them in less memory, use quantization.
Store and Search the Vectors
Next Steps
- Filter search results by payload fields, for example to search only PDFs.
- Gemini embeddings documentation and the embeddings quickstart notebook.
- Combine Gemini vectors with sparse vectors for hybrid search.
- Reduce memory use with quantization.