Local embeddings let you build private search and RAG without sending documents to anyone.
Embedding models turn text into vectors (lists of numbers) so that texts with similar meaning have similar vectors. Ollama runs them locally like any other model; the docs use embeddinggemma, and the library has others such as nomic-embed-text and mxbai-embed-large.
Create embeddings with the Python library:
import ollama
result = ollama.embed(model="embeddinggemma", input=["first text", "second text"])
vectors = result.embeddings # a list of vectors, one per inputor with POST /api/embed and the same fields. Pass a list to embed many texts in one request, which is much faster than one call each.
Facts that matter:
- Vector length depends on the model, typically 384 to 1,024 numbers.
/api/embedreturns normalised vectors (length 1), so cosine similarity equals the plain dot product: multiply matching numbers and add them up.- Use the same embedding model for your documents and for search queries. Vectors from different models can't be compared.
- Inputs longer than the model's context are truncated by default. Split long documents into chunks first.
A simple semantic search: embed all document chunks once and store the vectors (in a list, or a vector database for larger sets). For a question, embed it, compute similarity with each stored vector and take the best matches. That is the retrieval half of RAG; frameworks such as LangChain and LlamaIndex can do it with Ollama embeddings too.
Embedding models are small and fast, so they often run comfortably on a CPU.
Example
import ollama
docs = ["Refunds are processed within 5 business days.",
"Our office is closed on public holidays.",
"You can reset your password from the login page."]
doc_vecs = ollama.embed(model="embeddinggemma", input=docs).embeddings
print(len(doc_vecs), len(doc_vecs[0])) # 3 768
def similarity(a, b): # vectors are normalised: dot = cosine
return sum(x * y for x, y in zip(a, b))
question = "How long until I get my money back?"
q_vec = ollama.embed(model="embeddinggemma", input=question).embeddings[0]
scores = [similarity(q_vec, v) for v in doc_vecs]
best = max(range(len(docs)), key=lambda i: scores[i])
print(round(scores[best], 2), docs[best]) # 0.62 Refunds are processed within 5 business days.Remember
ollama.embed(model, input=[...])returns one vector per input- Vectors are normalised: dot product = cosine similarity
- Use the same embedding model for documents and queries; chunk long texts
Watch out for
- Embedding documents with one model and queries with another, which makes similarity scores meaningless.