Nomic

Nomic publishes open-weight text and image embedding models. There are two ways to use them with Qdrant:

Both paths support nomic-embed-text-v1.5 and nomic-embed-vision-v1.5. Embed documents and queries through the same path.

Models

ModelDimensionsMax InputInput
nomic-embed-text-v1.5768 (or 512, 256, 128, 64)8192 tokensText
nomic-embed-vision-v1.5768One PNG, JPEG, or WebP imageImage

nomic-embed-vision-v1.5 shares an embedding space with nomic-embed-text-v1.5, so text queries can retrieve images. nomic-embed-text-v1 and nomic-embed-vision-v1 are the previous generation. Use the v1.5 models for new projects. The multilingual nomic-embed-text-v2-moe is available on Hugging Face, but neither the Nomic API nor FastEmbed serves it.

FastEmbed returns text vectors that are not normalized to length 1, so use the Cosine distance metric, which ignores vector length.

Setup

Install the Nomic SDK and the Qdrant client:

  • Python: pip install nomic qdrant-client
  • TypeScript: npm install @qdrant/js-client-rest

Create an API key in Nomic Atlas and store it in the NOMIC_API_KEY environment variable. The Python SDK reads the credentials that nomic login saves, so run nomic login $NOMIC_API_KEY once. The TypeScript example calls the REST API directly and sends the key as a bearer token.

Create the Qdrant client and a collection sized for nomic-embed-text-v1.5:

from nomic import embed
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

MODEL = "nomic-embed-text-v1.5"

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ url: "http://localhost:6333" });

const MODEL = "nomic-embed-text-v1.5";

async function embed(texts: string[], taskType: string, dimensionality?: number) {
  const response = await fetch("https://api-atlas.nomic.ai/v1/embedding/text", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NOMIC_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: MODEL, texts, task_type: taskType, dimensionality }),
  });
  if (!response.ok) {
    throw new Error(`Nomic API error ${response.status}: ${await response.text()}`);
  }
  const { embeddings } = (await response.json()) as { embeddings: number[][] };
  return embeddings;
}

await client.createCollection("{collection_name}", {
  vectors: { size: 768, distance: "Cosine" },
});

Embed Documents

Set task_type to search_document for the texts you store. The response lists embeddings in input order, so this example uses each input’s position as the point ID:

documents = [
    "Qdrant is a vector search engine.",
    "Nomic Embed models turn text into dense vectors.",
    "Paris is the capital of France.",
]

output = embed.text(texts=documents, model=MODEL, task_type="search_document")

client.upsert(
    collection_name="{collection_name}",
    points=[
        models.PointStruct(id=idx, vector=vector, payload={"text": documents[idx]})
        for idx, vector in enumerate(output["embeddings"])
    ],
)
const documents = [
  "Qdrant is a vector search engine.",
  "Nomic Embed models turn text into dense vectors.",
  "Paris is the capital of France.",
];

const embeddings = await embed(documents, "search_document");

await client.upsert("{collection_name}", {
  points: embeddings.map((vector, idx) => ({
    id: idx,
    vector,
    payload: { text: documents[idx] },
  })),
});

Nomic does not document a per-request limit. The Python SDK splits long lists into requests of 10 texts, so batch large datasets in a similar way when you call the REST API directly. See Upload Points for batching on the Qdrant side.

Embed Queries

Set task_type to search_query for the query:

query_vector = embed.text(
    texts=["What is Qdrant?"],
    model=MODEL,
    task_type="search_query",
)["embeddings"][0]

hits = client.query_points(
    collection_name="{collection_name}",
    query=query_vector,
    limit=3,
).points
const [queryVector] = await embed(["What is Qdrant?"], "search_query");

const { points: hits } = await client.query("{collection_name}", {
  query: queryVector,
  limit: 3,
});

Nomic Embed models were trained with a task prefix on every input, and the API adds the prefix that matches task_type: search_document: for stored texts and search_query: for queries. The API also accepts classification and clustering.

Reduce Vector Size

nomic-embed-text-v1.5 was trained with Matryoshka representation learning. To store shorter vectors, pass dimensionality with one of the Matryoshka sizes and set the collection’s size to match. Pass the same dimensionality when you embed queries:

output = embed.text(
    texts=documents,
    model=MODEL,
    task_type="search_document",
    dimensionality=256,
)
const reduced = await embed(documents, "search_document", 256);

Quality drops as the vectors get shorter. These are the MTEB scores from the model card:

DimensionsMTEB Score
76862.28
51261.96
25661.04
12859.34
6456.10

Embed Images

embed.image accepts file paths, URLs, or Pillow images. Image-to-text scores are much lower than text-to-text scores, so store image vectors in a collection of their own instead of mixing them with text documents. This example embeds images and searches them with a text query:

client.create_collection(
    collection_name="{image_collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
)

images = ["cats.jpg", "living-room.jpg"]

output = embed.image(images=images, model="nomic-embed-vision-v1.5")

client.upsert(
    collection_name="{image_collection_name}",
    points=[
        models.PointStruct(id=idx, vector=vector, payload={"image": images[idx]})
        for idx, vector in enumerate(output["embeddings"])
    ],
)

query_vector = embed.text(
    texts=["What do cats look like?"],
    model=MODEL,
    task_type="search_query",
)["embeddings"][0]

hits = client.query_points(
    collection_name="{image_collection_name}",
    query=query_vector,
    limit=3,
).points

Run Locally with FastEmbed

FastEmbed runs nomic-ai/nomic-embed-text-v1.5 and nomic-ai/nomic-embed-vision-v1.5 on your CPU with ONNX Runtime. The models download from Hugging Face on first use, and no API key is needed. nomic-ai/nomic-embed-text-v1.5-Q is a quantized variant at 0.13 GB instead of 0.52 GB. These examples use their own collections, so you can run them next to the Nomic API examples.

pip install fastembed qdrant-client

Documents and Queries

The model was trained with task prefixes, and FastEmbed passes text to it unchanged, so add the search_document: and search_query: prefixes yourself:

from fastembed import TextEmbedding
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")
model = TextEmbedding("nomic-ai/nomic-embed-text-v1.5")

client.create_collection(
    collection_name="{fastembed_collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
)

documents = [
    "Qdrant is a vector search engine.",
    "Nomic Embed models turn text into dense vectors.",
    "Paris is the capital of France.",
]

client.upsert(
    collection_name="{fastembed_collection_name}",
    points=[
        models.PointStruct(id=idx, vector=vector.tolist(), payload={"text": documents[idx]})
        for idx, vector in enumerate(model.embed([f"search_document: {doc}" for doc in documents]))
    ],
)

query_vector = list(model.embed(["search_query: What is Qdrant?"]))[0]

hits = client.query_points(
    collection_name="{fastembed_collection_name}",
    query=query_vector.tolist(),
    limit=3,
).points

Shorter Vectors

FastEmbed always returns 768 dimensions. To get a Matryoshka size, apply layer normalization, keep the first dimensions, and normalize the result, which is the procedure on the model card. Apply it to documents and queries alike, and size the collection to match:

import numpy as np

def shorten(vector: np.ndarray, size: int) -> list[float]:
    vector = (vector - vector.mean()) / np.sqrt(vector.var() + 1e-5)
    vector = vector[:size]
    return (vector / np.linalg.norm(vector)).tolist()

query_vector_256 = shorten(query_vector, 256)

Images

Image vectors from FastEmbed share the text model’s embedding space, so a search_query: text vector retrieves them:

from fastembed import ImageEmbedding

image_model = ImageEmbedding("nomic-ai/nomic-embed-vision-v1.5")

client.create_collection(
    collection_name="{fastembed_image_collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
)

images = ["cats.jpg", "living-room.jpg"]

client.upsert(
    collection_name="{fastembed_image_collection_name}",
    points=[
        models.PointStruct(id=idx, vector=vector.tolist(), payload={"image": images[idx]})
        for idx, vector in enumerate(image_model.embed(images))
    ],
)

query_vector = list(model.embed(["search_query: What do cats look like?"]))[0]

hits = client.query_points(
    collection_name="{fastembed_image_collection_name}",
    query=query_vector.tolist(),
    limit=3,
).points

Store and Search the Vectors

Next Steps

Was this page useful?

Thank you for your feedback! 🙏

We are sorry to hear that. 😔 You can edit this page on GitHub, or create a GitHub issue.